xref: /openbsd/lib/libc/thread/rthread_file.c (revision d89ec533)
1 /*	$OpenBSD: rthread_file.c,v 1.2 2017/08/15 06:38:41 guenther 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 perfomed.
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 *
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 *
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
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
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
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