/* * Copyright (C) 2008 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* implement flockfile(), ftrylockfile() and funlockfile() * * we can't use the OpenBSD implementation which uses kernel-specific * APIs not available on Linux. * * Ideally, this would be trivially implemented by adding a * pthread_mutex_t field to struct __sFILE as defined in * <stdio.h>. * * However, since we don't want to bring pthread into the mix * as well as change the size of a public API/ABI structure, * we're going to store the data out-of-band. * * we use a hash-table to map FILE* pointers to recursive mutexes * fclose() will call __fremovelock() defined below to remove * a pointer from the table. * * the behaviour, if fclose() is called while the corresponding * file is locked is totally undefined. */ #include <stdio.h> #include <pthread.h> #include <string.h> /* a node in the hash table */ typedef struct FileLock { struct FileLock* next; FILE* file; pthread_mutex_t mutex; } FileLock; /* use a static hash table. We assume that we're not going to * lock a really large number of FILE* objects on an embedded * system. */ #define FILE_LOCK_BUCKETS 32 typedef struct { pthread_mutex_t lock; FileLock* buckets[ FILE_LOCK_BUCKETS ]; } LockTable; static LockTable* _lockTable; static pthread_once_t _lockTable_once = PTHREAD_ONCE_INIT; static void lock_table_init( void ) { _lockTable = malloc(sizeof(*_lockTable)); if (_lockTable != NULL) { pthread_mutex_init(&_lockTable->lock, NULL); memset(_lockTable->buckets, 0, sizeof(_lockTable->buckets)); } } static LockTable* lock_table_lock( void ) { pthread_once( &_lockTable_once, lock_table_init ); pthread_mutex_lock( &_lockTable->lock ); return _lockTable; } static void lock_table_unlock( LockTable* t ) { pthread_mutex_unlock( &t->lock ); } static FileLock** lock_table_lookup( LockTable* t, FILE* f ) { uint32_t hash = (uint32_t)(void*)f; FileLock** pnode; hash = (hash >> 2) ^ (hash << 17); pnode = &t->buckets[hash % FILE_LOCK_BUCKETS]; for (;;) { FileLock* node = *pnode; if (node == NULL || node->file == f) break; pnode = &node->next; } return pnode; } void flockfile(FILE * fp) { LockTable* t = lock_table_lock(); if (t != NULL) { FileLock** lookup = lock_table_lookup(t, fp); FileLock* lock = *lookup; if (lock == NULL) { pthread_mutexattr_t attr; /* create a new node in the hash table */ lock = malloc(sizeof(*lock)); if (lock == NULL) { lock_table_unlock(t); return; } lock->next = NULL; lock->file = fp; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init( &lock->mutex, &attr ); *lookup = lock; } lock_table_unlock(t); /* we assume that another thread didn't destroy 'lock' * by calling fclose() on the FILE*. This can happen if * the client is *really* buggy, but we don't care about * such code here. */ pthread_mutex_lock(&lock->mutex); } } int ftrylockfile(FILE *fp) { int ret = -1; LockTable* t = lock_table_lock(); if (t != NULL) { FileLock** lookup = lock_table_lookup(t, fp); FileLock* lock = *lookup; lock_table_unlock(t); /* see above comment about why we assume that 'lock' can * be accessed from here */ if (lock != NULL && !pthread_mutex_trylock(&lock->mutex)) { ret = 0; /* signal success */ } } return ret; } void funlockfile(FILE * fp) { LockTable* t = lock_table_lock(); if (t != NULL) { FileLock** lookup = lock_table_lookup(t, fp); FileLock* lock = *lookup; if (lock != NULL) pthread_mutex_unlock(&lock->mutex); lock_table_unlock(t); } } /* called from fclose() to remove the file lock */ __LIBC_HIDDEN__ void __fremovelock(FILE* fp) { LockTable* t = lock_table_lock(); if (t != NULL) { FileLock** lookup = lock_table_lookup(t, fp); FileLock* lock = *lookup; if (lock != NULL) { *lookup = lock->next; lock->file = NULL; } lock_table_unlock(t); free(lock); } }