1 /* $OpenBSD: rthread_file.c,v 1.3 2022/12/27 17:10:06 jmc 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
41 #include <sys/queue.h>
42 #include <pthread.h>
43 #include <stdint.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46
47 #include "rthread.h"
48 #include "rthread_cb.h"
49
50 /*
51 * The FILE lock structure. The FILE *fp is locked if the owner is
52 * not NULL. If not locked, the file lock structure can be
53 * reassigned to a different file by setting fp.
54 */
55 struct file_lock {
56 LIST_ENTRY(file_lock) entry; /* Entry if file list. */
57 FILE *fp; /* The target file. */
58 struct pthread_queue lockers;
59 pthread_t owner;
60 int count;
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 performed.
78 */
79 #define file_idx(_p) ((int)((((uintptr_t) _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 static struct static_file_lock {
87 LIST_HEAD(file_list_head, file_lock) head;
88 struct file_lock fl;
89 } flh[NUM_HEADS];
90
91 /* Lock for accesses to the hash table: */
92 static _atomic_lock_t hash_lock = _SPINLOCK_UNLOCKED;
93
94 /*
95 * Find a lock structure for a FILE, return NULL if the file is
96 * not locked:
97 */
98 static
99 struct file_lock *
find_lock(int idx,FILE * fp)100 find_lock(int idx, FILE *fp)
101 {
102 struct file_lock *p;
103
104 /* Check if the file is locked using the static structure: */
105 if (flh[idx].fl.fp == fp && flh[idx].fl.owner != NULL)
106 /* Return a pointer to the static lock: */
107 p = &flh[idx].fl;
108 else {
109 /* Point to the first dynamic lock: */
110 p = LIST_FIRST(&flh[idx].head);
111
112 /*
113 * Loop through the dynamic locks looking for the
114 * target file:
115 */
116 while (p != NULL && (p->fp != fp || p->owner == NULL))
117 /* Not this file, try the next: */
118 p = LIST_NEXT(p, entry);
119 }
120 return(p);
121 }
122
123 /*
124 * Lock a file, assuming that there is no lock structure currently
125 * assigned to it.
126 */
127 static
128 struct file_lock *
do_lock(int idx,FILE * fp)129 do_lock(int idx, FILE *fp)
130 {
131 struct file_lock *p;
132
133 /* Check if the static structure is not being used: */
134 if (flh[idx].fl.owner == NULL) {
135 /* Return a pointer to the static lock: */
136 p = &flh[idx].fl;
137 }
138 else {
139 /* Point to the first dynamic lock: */
140 p = LIST_FIRST(&flh[idx].head);
141
142 /*
143 * Loop through the dynamic locks looking for a
144 * lock structure that is not being used:
145 */
146 while (p != NULL && p->owner != NULL)
147 /* This one is used, try the next: */
148 p = LIST_NEXT(p, entry);
149 }
150
151 /*
152 * If an existing lock structure has not been found,
153 * allocate memory for a new one:
154 */
155 if (p == NULL && (p = (struct file_lock *)
156 malloc(sizeof(struct file_lock))) != NULL) {
157 /* Add the new element to the list: */
158 LIST_INSERT_HEAD(&flh[idx].head, p, entry);
159 }
160
161 /* Check if there is a lock structure to acquire: */
162 if (p != NULL) {
163 /* Acquire the lock for the running thread: */
164 p->fp = fp;
165 p->owner = pthread_self();
166 p->count = 1;
167 TAILQ_INIT(&p->lockers);
168 }
169 return(p);
170 }
171
172 void
_thread_flockfile(FILE * fp)173 _thread_flockfile(FILE * fp)
174 {
175 int idx = file_idx(fp);
176 struct file_lock *p;
177 pthread_t self = pthread_self();
178
179 /* Lock the hash table: */
180 _spinlock(&hash_lock);
181
182 /* Get a pointer to any existing lock for the file: */
183 if ((p = find_lock(idx, fp)) == NULL) {
184 /*
185 * The file is not locked, so this thread can
186 * grab the lock:
187 */
188 do_lock(idx, fp);
189
190 /*
191 * The file is already locked, so check if the
192 * running thread is the owner:
193 */
194 } else if (p->owner == self) {
195 /*
196 * The running thread is already the
197 * owner, so increment the count of
198 * the number of times it has locked
199 * the file:
200 */
201 p->count++;
202 } else {
203 /*
204 * The file is locked for another thread.
205 * Append this thread to the queue of
206 * threads waiting on the lock.
207 */
208 TAILQ_INSERT_TAIL(&p->lockers,self,waiting);
209 while (p->owner != self) {
210 __thrsleep(self, 0, NULL, &hash_lock, NULL);
211 _spinlock(&hash_lock);
212 }
213 }
214
215 /* Unlock the hash table: */
216 _spinunlock(&hash_lock);
217 }
218
219 int
_thread_ftrylockfile(FILE * fp)220 _thread_ftrylockfile(FILE * fp)
221 {
222 int ret = -1;
223 int idx = file_idx(fp);
224 struct file_lock *p;
225
226 /* Lock the hash table: */
227 _spinlock(&hash_lock);
228
229 /* Get a pointer to any existing lock for the file: */
230 if ((p = find_lock(idx, fp)) == NULL) {
231 /*
232 * The file is not locked, so this thread can
233 * grab the lock:
234 */
235 p = do_lock(idx, fp);
236
237 /*
238 * The file is already locked, so check if the
239 * running thread is the owner:
240 */
241 } else if (p->owner == pthread_self()) {
242 /*
243 * The running thread is already the
244 * owner, so increment the count of
245 * the number of times it has locked
246 * the file:
247 */
248 p->count++;
249 } else {
250 /*
251 * The file is locked for another thread,
252 * so this try fails.
253 */
254 p = NULL;
255 }
256
257 /* Unlock the hash table: */
258 _spinunlock(&hash_lock);
259
260 /* Check if the lock was obtained: */
261 if (p != NULL)
262 /* Return success: */
263 ret = 0;
264
265 return (ret);
266 }
267
268 void
_thread_funlockfile(FILE * fp)269 _thread_funlockfile(FILE * fp)
270 {
271 int idx = file_idx(fp);
272 struct file_lock *p;
273
274 /* Lock the hash table: */
275 _spinlock(&hash_lock);
276
277 /*
278 * Get a pointer to the lock for the file and check that
279 * the running thread is the one with the lock:
280 */
281 if ((p = find_lock(idx, fp)) != NULL && p->owner == pthread_self()) {
282 /*
283 * Check if this thread has locked the FILE
284 * more than once:
285 */
286 if (--p->count == 0) {
287 /* Get the new owner of the lock: */
288 if ((p->owner = TAILQ_FIRST(&p->lockers)) != NULL) {
289 /* Pop the thread off the queue: */
290 TAILQ_REMOVE(&p->lockers,p->owner,waiting);
291
292 /*
293 * This is the first lock for the new
294 * owner:
295 */
296 p->count = 1;
297
298 __thrwakeup(p->owner, 1);
299 }
300 }
301 }
302
303 /* Unlock the hash table: */
304 _spinunlock(&hash_lock);
305 }
306