1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in 12 * the documentation and/or other materials provided with the 13 * distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 18 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 19 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 22 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 25 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29/* implement flockfile(), ftrylockfile() and funlockfile() 30 * 31 * we can't use the OpenBSD implementation which uses kernel-specific 32 * APIs not available on Linux. 33 * 34 * Ideally, this would be trivially implemented by adding a 35 * pthread_mutex_t field to struct __sFILE as defined in 36 * <stdio.h>. 37 * 38 * However, since we don't want to bring pthread into the mix 39 * as well as change the size of a public API/ABI structure, 40 * we're going to store the data out-of-band. 41 * 42 * we use a hash-table to map FILE* pointers to recursive mutexes 43 * fclose() will call __fremovelock() defined below to remove 44 * a pointer from the table. 45 * 46 * the behaviour, if fclose() is called while the corresponding 47 * file is locked is totally undefined. 48 */ 49#include <stdio.h> 50#include <pthread.h> 51#include <string.h> 52 53/* a node in the hash table */ 54typedef struct FileLock { 55 struct FileLock* next; 56 FILE* file; 57 pthread_mutex_t mutex; 58} FileLock; 59 60/* use a static hash table. We assume that we're not going to 61 * lock a really large number of FILE* objects on an embedded 62 * system. 63 */ 64#define FILE_LOCK_BUCKETS 32 65 66typedef struct { 67 pthread_mutex_t lock; 68 FileLock* buckets[ FILE_LOCK_BUCKETS ]; 69} LockTable; 70 71static LockTable* _lockTable; 72static pthread_once_t _lockTable_once = PTHREAD_ONCE_INIT; 73 74static void 75lock_table_init( void ) 76{ 77 _lockTable = malloc(sizeof(*_lockTable)); 78 if (_lockTable != NULL) { 79 pthread_mutex_init(&_lockTable->lock, NULL); 80 memset(_lockTable->buckets, 0, sizeof(_lockTable->buckets)); 81 } 82} 83 84static LockTable* 85lock_table_lock( void ) 86{ 87 pthread_once( &_lockTable_once, lock_table_init ); 88 pthread_mutex_lock( &_lockTable->lock ); 89 return _lockTable; 90} 91 92static void 93lock_table_unlock( LockTable* t ) 94{ 95 pthread_mutex_unlock( &t->lock ); 96} 97 98static FileLock** 99lock_table_lookup( LockTable* t, FILE* f ) 100{ 101 uint32_t hash = (uint32_t)(void*)f; 102 FileLock** pnode; 103 104 hash = (hash >> 2) ^ (hash << 17); 105 pnode = &t->buckets[hash % FILE_LOCK_BUCKETS]; 106 for (;;) { 107 FileLock* node = *pnode; 108 if (node == NULL || node->file == f) 109 break; 110 pnode = &node->next; 111 } 112 return pnode; 113} 114 115void 116flockfile(FILE * fp) 117{ 118 LockTable* t = lock_table_lock(); 119 120 if (t != NULL) { 121 FileLock** lookup = lock_table_lookup(t, fp); 122 FileLock* lock = *lookup; 123 124 if (lock == NULL) { 125 pthread_mutexattr_t attr; 126 127 /* create a new node in the hash table */ 128 lock = malloc(sizeof(*lock)); 129 if (lock == NULL) { 130 lock_table_unlock(t); 131 return; 132 } 133 lock->next = NULL; 134 lock->file = fp; 135 136 pthread_mutexattr_init(&attr); 137 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); 138 pthread_mutex_init( &lock->mutex, &attr ); 139 140 *lookup = lock; 141 } 142 lock_table_unlock(t); 143 144 /* we assume that another thread didn't destroy 'lock' 145 * by calling fclose() on the FILE*. This can happen if 146 * the client is *really* buggy, but we don't care about 147 * such code here. 148 */ 149 pthread_mutex_lock(&lock->mutex); 150 } 151} 152 153 154int 155ftrylockfile(FILE *fp) 156{ 157 int ret = -1; 158 LockTable* t = lock_table_lock(); 159 160 if (t != NULL) { 161 FileLock** lookup = lock_table_lookup(t, fp); 162 FileLock* lock = *lookup; 163 164 lock_table_unlock(t); 165 166 /* see above comment about why we assume that 'lock' can 167 * be accessed from here 168 */ 169 if (lock != NULL && !pthread_mutex_trylock(&lock->mutex)) { 170 ret = 0; /* signal success */ 171 } 172 } 173 return ret; 174} 175 176void 177funlockfile(FILE * fp) 178{ 179 LockTable* t = lock_table_lock(); 180 181 if (t != NULL) { 182 FileLock** lookup = lock_table_lookup(t, fp); 183 FileLock* lock = *lookup; 184 185 if (lock != NULL) 186 pthread_mutex_unlock(&lock->mutex); 187 188 lock_table_unlock(t); 189 } 190} 191 192 193/* called from fclose() to remove the file lock */ 194void 195__fremovelock(FILE* fp) 196{ 197 LockTable* t = lock_table_lock(); 198 199 if (t != NULL) { 200 FileLock** lookup = lock_table_lookup(t, fp); 201 FileLock* lock = *lookup; 202 203 if (lock != NULL) { 204 *lookup = lock->next; 205 lock->file = NULL; 206 } 207 lock_table_unlock(t); 208 free(lock); 209 } 210} 211