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 */
54 typedef 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
66 typedef struct {
67 pthread_mutex_t lock;
68 FileLock* buckets[ FILE_LOCK_BUCKETS ];
69 } LockTable;
70
71 static LockTable* _lockTable;
72 static pthread_once_t _lockTable_once = PTHREAD_ONCE_INIT;
73
74 static void
lock_table_init(void)75 lock_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
84 static LockTable*
lock_table_lock(void)85 lock_table_lock( void )
86 {
87 pthread_once( &_lockTable_once, lock_table_init );
88 pthread_mutex_lock( &_lockTable->lock );
89 return _lockTable;
90 }
91
92 static void
lock_table_unlock(LockTable * t)93 lock_table_unlock( LockTable* t )
94 {
95 pthread_mutex_unlock( &t->lock );
96 }
97
98 static FileLock**
lock_table_lookup(LockTable * t,FILE * f)99 lock_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
115 void
flockfile(FILE * fp)116 flockfile(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
154 int
ftrylockfile(FILE * fp)155 ftrylockfile(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
176 void
funlockfile(FILE * fp)177 funlockfile(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 */
194 __LIBC_HIDDEN__ void
__fremovelock(FILE * fp)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