1 /* $OpenBSD: uthread_file.c,v 1.11 2004/06/07 21:11:23 marc Exp $ */
2 /*
3 * Copyright (c) 1995 John Birrell <jb@cimlogic.com.au>.
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. All advertising materials mentioning features or use of this software
15 * must display the following acknowledgement:
16 * This product includes software developed by John Birrell.
17 * 4. Neither the name of the author nor the names of any co-contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY JOHN BIRRELL AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 *
33 * $FreeBSD: uthread_file.c,v 1.9 1999/08/28 00:03:32 peter Exp $
34 *
35 * POSIX stdio FILE locking functions. These assume that the locking
36 * is only required at FILE structure level, not at file descriptor
37 * level too.
38 *
39 */
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <sys/queue.h>
44 #ifdef _THREAD_SAFE
45 #include <pthread.h>
46 #include "pthread_private.h"
47
48 /*
49 * The FILE lock structure. The FILE *fp is locked if the owner is
50 * not NULL. If not locked, the file lock structure can be
51 * reassigned to a different file by setting fp.
52 */
53 struct file_lock {
54 LIST_ENTRY(file_lock) entry; /* Entry if file list. */
55 V_TAILQ_HEAD(lock_head, pthread)
56 l_head; /* Head of queue for threads */
57 /* waiting on this lock. */
58 FILE *fp; /* The target file. */
59 pthread_t owner; /* Thread that owns lock. */
60 int count; /* Lock count for owner. */
61 };
62
63 /*
64 * The number of file lock lists into which the file pointer is
65 * hashed. Ideally, the FILE structure size would have been increased,
66 * but this causes incompatibility, so separate data structures are
67 * required.
68 */
69 #define NUM_HEADS 128
70
71 /*
72 * This macro casts a file pointer to a long integer and right
73 * shifts this by the number of bytes in a pointer. The shifted
74 * value is then remaindered using the maximum number of hash
75 * entries to produce and index into the array of static lock
76 * structures. If there is a collision, a linear search of the
77 * dynamic list of locks linked to each static lock is perfomed.
78 */
79 #define file_idx(_p) ((((u_long) _p) >> sizeof(void *)) % NUM_HEADS)
80
81 /*
82 * Global array of file locks. The first lock for each hash bucket is
83 * allocated statically in the hope that there won't be too many
84 * collisions that require a malloc and an element added to the list.
85 */
86 struct static_file_lock {
87 LIST_HEAD(file_list_head, file_lock) head;
88 struct file_lock fl;
89 } flh[NUM_HEADS];
90
91 /* Set to non-zero when initialisation is complete: */
92 static int init_done = 0;
93
94 /* Lock for accesses to the hash table: */
95 static spinlock_t hash_lock = _SPINLOCK_INITIALIZER;
96
97 /*
98 * Find a lock structure for a FILE, return NULL if the file is
99 * not locked:
100 */
101 static
102 struct file_lock *
find_lock(int idx,FILE * fp)103 find_lock(int idx, FILE *fp)
104 {
105 struct file_lock *p;
106
107 /* Check if the file is locked using the static structure: */
108 if (flh[idx].fl.fp == fp && flh[idx].fl.owner != NULL)
109 /* Return a pointer to the static lock: */
110 p = &flh[idx].fl;
111 else {
112 /* Point to the first dynamic lock: */
113 p = flh[idx].head.lh_first;
114
115 /*
116 * Loop through the dynamic locks looking for the
117 * target file:
118 */
119 while (p != NULL && (p->fp != fp || p->owner == NULL))
120 /* Not this file, try the next: */
121 p = p->entry.le_next;
122 }
123 return(p);
124 }
125
126 /*
127 * Lock a file, assuming that there is no lock structure currently
128 * assigned to it.
129 */
130 static
131 struct file_lock *
do_lock(int idx,FILE * fp)132 do_lock(int idx, FILE *fp)
133 {
134 struct file_lock *p;
135
136 /* Check if the static structure is not being used: */
137 if (flh[idx].fl.owner == NULL) {
138 /* Return a pointer to the static lock: */
139 p = &flh[idx].fl;
140 }
141 else {
142 /* Point to the first dynamic lock: */
143 p = flh[idx].head.lh_first;
144
145 /*
146 * Loop through the dynamic locks looking for a
147 * lock structure that is not being used:
148 */
149 while (p != NULL && p->owner != NULL)
150 /* This one is used, try the next: */
151 p = p->entry.le_next;
152 }
153
154 /*
155 * If an existing lock structure has not been found,
156 * allocate memory for a new one:
157 */
158 if (p == NULL && (p = (struct file_lock *)
159 malloc(sizeof(struct file_lock))) != NULL) {
160 /* Add the new element to the list: */
161 LIST_INSERT_HEAD(&flh[idx].head, p, entry);
162 }
163
164 /* Check if there is a lock structure to acquire: */
165 if (p != NULL) {
166 /* Acquire the lock for the running thread: */
167 p->fp = fp;
168 p->owner = _thread_run;
169 p->count = 1;
170 TAILQ_INIT(&p->l_head);
171 }
172 return(p);
173 }
174
175 void
flockfile(FILE * fp)176 flockfile(FILE * fp)
177 {
178 int idx = file_idx(fp);
179 struct file_lock *p;
180
181 /* Check if this is a real file: */
182 if (fp->_file >= 0) {
183 /* Lock the hash table: */
184 _SPINLOCK(&hash_lock);
185
186 /* Check if the static array has not been initialised: */
187 if (!init_done) {
188 /* Initialise the global array: */
189 memset(flh,0,sizeof(flh));
190
191 /* Flag the initialisation as complete: */
192 init_done = 1;
193 }
194
195 /* Get a pointer to any existing lock for the file: */
196 if ((p = find_lock(idx, fp)) == NULL) {
197 /*
198 * The file is not locked, so this thread can
199 * grab the lock:
200 */
201 p = do_lock(idx, fp);
202
203 /* Unlock the hash table: */
204 _SPINUNLOCK(&hash_lock);
205
206 /*
207 * The file is already locked, so check if the
208 * running thread is the owner:
209 */
210 } else if (p->owner == _thread_run) {
211 /*
212 * The running thread is already the
213 * owner, so increment the count of
214 * the number of times it has locked
215 * the file:
216 */
217 p->count++;
218
219 /* Unlock the hash table: */
220 _SPINUNLOCK(&hash_lock);
221 } else {
222 /*
223 * The file is locked for another thread.
224 * Append this thread to the queue of
225 * threads waiting on the lock.
226 */
227 TAILQ_INSERT_TAIL(&p->l_head,_thread_run,qe);
228
229 /* Unlock the hash table: */
230 _SPINUNLOCK(&hash_lock);
231
232 /* Wait on the FILE lock: */
233 _thread_kern_sched_state(PS_FILE_WAIT, "", 0);
234 }
235 }
236 return;
237 }
238
239 int
ftrylockfile(FILE * fp)240 ftrylockfile(FILE * fp)
241 {
242 int ret = -1;
243 int idx = file_idx(fp);
244 struct file_lock *p;
245
246 /* Check if this is a real file: */
247 if (fp->_file >= 0) {
248 /* Lock the hash table: */
249 _SPINLOCK(&hash_lock);
250
251 /* Get a pointer to any existing lock for the file: */
252 if ((p = find_lock(idx, fp)) == NULL) {
253 /*
254 * The file is not locked, so this thread can
255 * grab the lock:
256 */
257 p = do_lock(idx, fp);
258
259 /*
260 * The file is already locked, so check if the
261 * running thread is the owner:
262 */
263 } else if (p->owner == _thread_run) {
264 /*
265 * The running thread is already the
266 * owner, so increment the count of
267 * the number of times it has locked
268 * the file:
269 */
270 p->count++;
271 } else {
272 /*
273 * The file is locked for another thread,
274 * so this try fails.
275 */
276 p = NULL;
277 }
278
279 /* Check if the lock was obtained: */
280 if (p != NULL)
281 /* Return success: */
282 ret = 0;
283
284 /* Unlock the hash table: */
285 _SPINUNLOCK(&hash_lock);
286
287 }
288 return (ret);
289 }
290
291 void
funlockfile(FILE * fp)292 funlockfile(FILE * fp)
293 {
294 int idx = file_idx(fp);
295 struct file_lock *p;
296
297 /* Check if this is a real file: */
298 if (fp->_file >= 0) {
299 /*
300 * Defer signals to protect the scheduling queues from
301 * access by the signal handler:
302 */
303 _thread_kern_sig_defer();
304
305 /* Lock the hash table: */
306 _SPINLOCK(&hash_lock);
307
308 /*
309 * Get a pointer to the lock for the file and check that
310 * the running thread is the one with the lock:
311 */
312 if ((p = find_lock(idx, fp)) != NULL &&
313 p->owner == _thread_run) {
314 /*
315 * Check if this thread has locked the FILE
316 * more than once:
317 */
318 if (p->count > 1)
319 /*
320 * Decrement the count of the number of
321 * times the running thread has locked this
322 * file:
323 */
324 p->count--;
325 else {
326 /*
327 * The running thread will release the
328 * lock now:
329 */
330 p->count = 0;
331
332 /* Get the new owner of the lock: */
333 if ((p->owner = TAILQ_FIRST(&p->l_head)) != NULL) {
334 /* Pop the thread off the queue: */
335 TAILQ_REMOVE(&p->l_head,p->owner,qe);
336
337 /*
338 * This is the first lock for the new
339 * owner:
340 */
341 p->count = 1;
342
343 /* Allow the new owner to run: */
344 PTHREAD_NEW_STATE(p->owner,PS_RUNNING);
345 }
346 }
347 }
348
349 /* Unlock the hash table: */
350 _SPINUNLOCK(&hash_lock);
351
352 /*
353 * Undefer and handle pending signals, yielding if
354 * necessary:
355 */
356 _thread_kern_sig_undefer();
357 }
358 return;
359 }
360
361 #endif
362