1 /*	$OpenBSD: uthread_fd.c,v 1.22 2004/06/07 21:11:23 marc Exp $	*/
2 /*
3  * Copyright (c) 1995-1998 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_fd.c,v 1.13 1999/08/28 00:03:31 peter Exp $
34  *
35  */
36 #include <errno.h>
37 #include <fcntl.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #ifdef _THREAD_SAFE
41 #include <pthread.h>
42 #include "pthread_private.h"
43 
44 /* Static variables: */
45 static	spinlock_t	fd_table_lock	= _SPINLOCK_INITIALIZER;
46 
47 /*
48  * Build a new fd entry and return it.
49  */
50 static struct fd_table_entry *
_thread_fd_entry(void)51 _thread_fd_entry(void)
52 {
53 	struct fd_table_entry *entry;
54 
55 	entry = (struct fd_table_entry *) malloc(sizeof(struct fd_table_entry));
56 	if (entry != NULL) {
57 		memset(entry, 0, sizeof *entry);
58 		_SPINLOCK_INIT(&entry->lock);
59 		TAILQ_INIT(&entry->r_queue);
60 		TAILQ_INIT(&entry->w_queue);
61 	}
62 	return entry;
63 }
64 
65 /*
66  * Initialize the thread fd table for dup-ed fds, typically the stdio
67  * fds.
68  */
69 
70 void
_thread_fd_init(void)71 _thread_fd_init(void)
72 {
73 	int saved_errno;
74 	int fd;
75 	int fd2;
76 	int flag;
77 	int *flags;
78 	struct fd_table_entry *entry;
79 
80 	saved_errno = errno;
81 	flags = calloc(_thread_dtablesize, sizeof *flags);
82 	if (flags == NULL)
83 		PANIC("Cannot allocate memory for flags table");
84 
85 
86 	/* read the current file flags */
87 	for (fd = 0; fd < _thread_dtablesize; fd += 1)
88 		flags[fd] = _thread_sys_fcntl(fd, F_GETFL, 0);
89 
90 	/*
91 	 * Now toggle the sync flags and see what other fd's
92 	 * change.   Those are the dup-ed fd's.   Dup-ed fd's are
93 	 * added to the table, all others are NOT added to the
94 	 * table.  They MUST NOT be added as the fds may belong
95 	 * to dlopen.   As dlclose doesn't go through the thread code
96 	 * so the entries would never be cleaned.
97 	 */
98 
99 	_SPINLOCK(&fd_table_lock);
100 	for (fd = 0; fd < _thread_dtablesize; fd += 1) {
101 		if (flags[fd] == -1)
102 			continue;
103 		entry = _thread_fd_entry();
104 		if (entry != NULL) {
105 			entry->flags = flags[fd];
106 			_thread_sys_fcntl(fd, F_SETFL,
107 					  entry->flags ^ O_SYNC);
108 			for (fd2 = fd + 1; fd2 < _thread_dtablesize; fd2 += 1) {
109 				if (flags[fd2] == -1)
110 					continue;
111 				flag = _thread_sys_fcntl(fd2, F_GETFL, 0);
112 				if (flag != flags[fd2]) {
113 					entry->refcnt += 1;
114 					_thread_fd_table[fd2] = entry;
115 					flags[fd2] = -1;
116 				}
117 			}
118 			if (entry->refcnt) {
119 				entry->refcnt += 1;
120 				_thread_fd_table[fd] = entry;
121 				flags[fd] |= O_NONBLOCK;
122 			} else
123 				free(entry);
124 		}
125 	}
126 	_SPINUNLOCK(&fd_table_lock);
127 
128 	/* lastly, restore the file flags.   Flags for files that we
129 	   know to be duped have been modified so set the non-blocking'
130 	   flag.  Other files will be set to non-blocking when the
131 	   thread code is forced to take notice of the file. */
132 	for (fd = 0; fd < _thread_dtablesize; fd += 1)
133 		if (flags[fd] != -1)
134 			_thread_sys_fcntl(fd, F_SETFL, flags[fd]);
135 
136 	free(flags);
137 	errno = saved_errno;
138 }
139 
140 /*
141  * Initialize the fd_table entry for the given fd.
142  *
143  * This function *must* return -1 and set the thread specific errno
144  * as a system call. This is because the error return from this
145  * function is propagated directly back from thread-wrapped system
146  * calls.
147  */
148 int
_thread_fd_table_init(int fd)149 _thread_fd_table_init(int fd)
150 {
151 	int	ret = 0;
152 	struct fd_table_entry *entry;
153 	int	saved_errno;
154 
155 	if (fd < 0 || fd >= _thread_dtablesize) {
156 		/*
157 		 * file descriptor is out of range, Return a bad file
158 		 * descriptor error:
159 		 */
160 		errno = EBADF;
161 		ret = -1;
162 	} else if (_thread_fd_table[fd] == NULL) {
163 		/* First time for this fd, build an entry */
164 		entry = _thread_fd_entry();
165 		if (entry == NULL) {
166 			errno = ENOMEM;
167 			ret = -1;
168 		} else {
169 			entry->flags = _thread_sys_fcntl(fd, F_GETFL, 0);
170 			if (entry->flags == -1)
171 				/* use the errno fcntl returned */
172 				ret = -1;
173 			else {
174 				/*
175 				 * Make the file descriptor non-blocking.
176 				 * This might fail if the device driver does
177 				 * not support non-blocking calls, or if the
178 				 * driver is naturally non-blocking.
179 				 */
180 				if ((entry->flags & O_NONBLOCK) == 0) {
181 					saved_errno = errno;
182 					_thread_sys_fcntl(fd, F_SETFL,
183 						  entry->flags | O_NONBLOCK);
184 					errno = saved_errno;
185 				}
186 
187 				/* Lock the file descriptor table: */
188 				_SPINLOCK(&fd_table_lock);
189 
190 				/*
191 				 * Check if another thread allocated the
192 				 * file descriptor entry while this thread
193 				 * was doing the same thing. The table wasn't
194 				 * kept locked during this operation because
195 				 * it has the potential to recurse.
196 				 */
197 				if (_thread_fd_table[fd] == NULL) {
198 					/* This thread wins: */
199 					entry->refcnt += 1;
200 					_thread_fd_table[fd] = entry;
201 				}
202 
203 				/* Unlock the file descriptor table: */
204 				_SPINUNLOCK(&fd_table_lock);
205 			}
206 
207 			/*
208 			 * If there was an error in getting the flags for
209 			 * the file or if another thread initialized the
210 			 * table entry throw this entry away.
211 			 */
212 			if (entry->refcnt == 0)
213 				free(entry);
214 		}
215 	}
216 
217 	/* Return the completion status: */
218 	return (ret);
219 }
220 
221 /*
222  * Dup from_fd -> to_fd.  from_fd is assumed to be locked (which
223  * guarantees that _thread_fd_table[from_fd] exists).
224  */
225 int
_thread_fd_table_dup(int from_fd,int to_fd)226 _thread_fd_table_dup(int from_fd, int to_fd)
227 {
228 	struct fd_table_entry	*entry;
229 	int ret;
230 
231 	if (from_fd != to_fd) {
232 		/* release any existing to_fd table entry */
233 		entry = _thread_fd_table[to_fd];
234 		if (entry != NULL) {
235 			ret = _FD_LOCK(to_fd, FD_RDWR, NULL);
236 			if (ret != -1)
237 				_thread_fd_table_remove(to_fd);
238 		} else
239 			ret = 0;
240 
241 		/* to_fd is a copy of from_fd */
242 		if (ret != -1) {
243 			_SPINLOCK(&fd_table_lock);
244 			_thread_fd_table[to_fd] = _thread_fd_table[from_fd];
245 			_thread_fd_table[to_fd]->refcnt += 1;
246 			_SPINUNLOCK(&fd_table_lock);
247 		}
248 	} else
249 		ret = 0;
250 
251 	return (ret);
252 }
253 
254 /*
255  * Remove an fd entry from the table and free it if it's reference count
256  * goes to zero.   The entry is assumed to be locked with a RDWR lock!  It
257  * will be unlocked if it is not freed.
258  */
259 void
_thread_fd_table_remove(int fd)260 _thread_fd_table_remove(int fd)
261 {
262 	struct fd_table_entry	*entry;
263 
264 	_SPINLOCK(&fd_table_lock);
265 	entry = _thread_fd_table[fd];
266 	if (--entry->refcnt == 0)
267 		free(entry);
268 	else
269 		_FD_UNLOCK(fd, FD_RDWR);
270 	_thread_fd_table[fd] = NULL;
271 	_SPINUNLOCK(&fd_table_lock);
272 }
273 
274 /*
275  * Unlock the fd table entry for a given thread, fd, and lock type.
276  */
277 void
_thread_fd_unlock_thread(struct pthread * thread,int fd,int lock_type)278 _thread_fd_unlock_thread(struct pthread	*thread, int fd, int lock_type)
279 {
280 	struct fd_table_entry *entry;
281 	int	ret;
282 
283 	/*
284 	 * Check that the file descriptor table is initialised for this
285 	 * entry:
286 	 */
287 	ret = _thread_fd_table_init(fd);
288 	if (ret == 0) {
289 		entry = _thread_fd_table[fd];
290 
291 		/*
292 		 * Defer signals to protect the scheduling queues from
293 		 * access by the signal handler:
294 		 */
295 		_thread_kern_sig_defer();
296 
297 		/*
298 		 * Lock the file descriptor table entry to prevent
299 		 * other threads for clashing with the current
300 		 * thread's accesses:
301 		 */
302 		_SPINLOCK(&entry->lock);
303 
304 		/* Check if the running thread owns the read lock: */
305 		if (entry->r_owner == thread &&
306 		    (lock_type == FD_READ || lock_type == FD_RDWR)) {
307 			/*
308 			 * Decrement the read lock count for the
309 			 * running thread:
310 			 */
311 			entry->r_lockcount--;
312 			if (entry->r_lockcount == 0) {
313 				/*
314 				 * no read locks, dequeue any threads
315 				 * waiting for a read lock
316 				 */
317 				entry->r_owner = TAILQ_FIRST(&entry->r_queue);
318 				if (entry->r_owner != NULL) {
319 					TAILQ_REMOVE(&entry->r_queue,
320 						     entry->r_owner, qe);
321 
322 					/*
323 					 * Set the state of the new owner of
324 					 * the thread to running:
325 					 */
326 					PTHREAD_NEW_STATE(entry->r_owner,
327 							  PS_RUNNING);
328 
329 					/*
330 					 * Reset the number of read locks.
331 					 * This will be incremented by the new
332 					 * owner of the lock when it sees that
333 					 *it has the lock.
334 					 */
335 					entry->r_lockcount = 0;
336 				}
337 			}
338 
339 		}
340 		/* Check if the running thread owns the write lock: */
341 		if (entry->w_owner == thread &&
342 		    (lock_type == FD_WRITE || lock_type == FD_RDWR)) {
343 			/*
344 			 * Decrement the write lock count for the
345 			 * running thread:
346 			 */
347 			entry->w_lockcount--;
348 			if (entry->w_lockcount == 0) {
349 				/*
350 				 * no write locks, dequeue any threads
351 				 * waiting on a write lock.
352 				 */
353 				entry->w_owner = TAILQ_FIRST(&entry->w_queue);
354 				if (entry->w_owner != NULL) {
355 					/* Remove this thread from the queue: */
356 					TAILQ_REMOVE(&entry->w_queue,
357 						     entry->w_owner, qe);
358 
359 					/*
360 					 * Set the state of the new owner of
361 					 * the thread to running:
362 					 */
363 					PTHREAD_NEW_STATE(entry->w_owner,
364 							  PS_RUNNING);
365 
366 					/*
367 					 * Reset the number of write locks.
368 					 * This will be incremented by the
369 					 * new owner of the lock when it
370 					 * sees that it has the lock.
371 					 */
372 					entry->w_lockcount = 0;
373 				}
374 			}
375 		}
376 
377 		/* Unlock the file descriptor table entry: */
378 		_SPINUNLOCK(&entry->lock);
379 
380 		/*
381 		 * Undefer and handle pending signals, yielding if
382 		 * necessary:
383 		 */
384 		_thread_kern_sig_undefer();
385 	}
386 
387 	/* Nothing to return. */
388 	return;
389 }
390 
391 /*
392  * Unlock an fd table entry for the given fd and lock type.
393  */
394 void
_thread_fd_unlock(int fd,int lock_type)395 _thread_fd_unlock(int fd, int lock_type)
396 {
397 	struct pthread	*curthread = _get_curthread();
398 	_thread_fd_unlock_thread(curthread, fd, lock_type);
399 }
400 
401 /*
402  * Unlock all fd table entries owned by the given thread
403  */
404 void
_thread_fd_unlock_owned(pthread_t pthread)405 _thread_fd_unlock_owned(pthread_t pthread)
406 {
407 	struct fd_table_entry *entry;
408 	int do_unlock;
409 	int fd;
410 
411 	for (fd = 0; fd < _thread_dtablesize; fd++) {
412 		entry = _thread_fd_table[fd];
413 		if (entry) {
414 			_SPINLOCK(&entry->lock);
415 			do_unlock = 0;
416 			/* force an unlock regardless of the recursion level */
417 			if (entry->r_owner == pthread) {
418 				entry->r_lockcount = 1;
419 				do_unlock++;
420 			}
421 			if (entry->w_owner == pthread) {
422 				entry->w_lockcount = 1;
423 				do_unlock++;
424 			}
425 			_SPINUNLOCK(&entry->lock);
426 			if (do_unlock)
427 				_thread_fd_unlock_thread(pthread, fd, FD_RDWR);
428 		}
429 	}
430 }
431 
432 /*
433  * Lock an fd table entry for the given fd and lock type.
434  */
435 int
_thread_fd_lock(int fd,int lock_type,struct timespec * timeout)436 _thread_fd_lock(int fd, int lock_type, struct timespec * timeout)
437 {
438 	struct pthread	*curthread = _get_curthread();
439 	struct fd_table_entry *entry;
440 	int	ret;
441 
442 	/*
443 	 * Check that the file descriptor table is initialised for this
444 	 * entry:
445 	 */
446 	ret = _thread_fd_table_init(fd);
447 	if (ret == 0) {
448 		entry = _thread_fd_table[fd];
449 
450 		/*
451 		 * Lock the file descriptor table entry to prevent
452 		 * other threads for clashing with the current
453 		 * thread's accesses:
454 		 */
455 		_SPINLOCK(&entry->lock);
456 		/* Handle read locks */
457 		if (lock_type == FD_READ || lock_type == FD_RDWR) {
458 			/*
459 			 * Enter a loop to wait for the file descriptor to be
460 			 * locked    for read for the current thread:
461 			 */
462 			while (entry->r_owner != curthread) {
463 				/*
464 				 * Check if the file descriptor is locked by
465 				 * another thread:
466 				 */
467 				if (entry->r_owner != NULL) {
468 					/*
469 					 * Another thread has locked the file
470 					 * descriptor for read, so join the
471 					 * queue of threads waiting for a
472 					 * read lock on this file descriptor:
473 					 */
474 					TAILQ_INSERT_TAIL(&entry->r_queue,
475 							  curthread, qe);
476 
477 					/*
478 					 * Save the file descriptor details
479 					 * in the thread structure for the
480 					 * running thread:
481 					 */
482 					curthread->data.fd.fd = fd;
483 
484 					/* Set the timeout: */
485 					_thread_kern_set_timeout(timeout);
486 
487 					/*
488 					 * Unlock the file descriptor
489 					 * table entry:
490 					 */
491 					_SPINUNLOCK(&entry->lock);
492 
493 					/*
494 					 * Schedule this thread to wait on
495 					 * the read lock. It will only be
496 					 * woken when it becomes the next in
497 					 * the   queue and is granted access
498 					 * to the lock by the thread that is
499 					 * unlocking the file descriptor.
500 					 */
501 					_thread_kern_sched_state(PS_FDLR_WAIT,
502 								 __FILE__,
503 								 __LINE__);
504 
505 					/*
506 					 * Lock the file descriptor
507 					 * table entry again:
508 					 */
509 					_SPINLOCK(&entry->lock);
510 
511 				} else {
512 					/*
513 					 * The running thread now owns the
514 					 * read lock on this file descriptor:
515 					 */
516 					entry->r_owner = curthread;
517 
518 					/*
519 					 * Reset the number of read locks for
520 					 * this file descriptor:
521 					 */
522 					entry->r_lockcount = 0;
523 				}
524 			}
525 
526 			/* Increment the read lock count: */
527 			entry->r_lockcount++;
528 		}
529 
530 		/* Handle write locks */
531 		if (lock_type == FD_WRITE || lock_type == FD_RDWR) {
532 			/*
533 			 * Enter a loop to wait for the file descriptor to be
534 			 * locked for write for the current thread:
535 			 */
536 			while (entry->w_owner != curthread) {
537 				/*
538 				 * Check if the file descriptor is locked by
539 				 * another thread:
540 				 */
541 				if (entry->w_owner != NULL) {
542 					/*
543 					 * Another thread has locked the file
544 					 * descriptor for write, so join the
545 					 * queue of threads waiting for a
546 					 * write lock on this file
547 					 * descriptor:
548 					 */
549 					TAILQ_INSERT_TAIL(&entry->w_queue,
550 							  curthread, qe);
551 
552 					/*
553 					 * Save the file descriptor details
554 					 * in the thread structure for the
555 					 * running thread:
556 					 */
557 					curthread->data.fd.fd = fd;
558 
559 					/* Set the timeout: */
560 					_thread_kern_set_timeout(timeout);
561 
562 					/*
563 					 * Unlock the file descriptor
564 					 * table entry:
565 					 */
566 					_SPINUNLOCK(&entry->lock);
567 
568 					/*
569 					 * Schedule this thread to wait on
570 					 * the write lock. It will only be
571 					 * woken when it becomes the next in
572 					 * the queue and is granted access to
573 					 * the lock by the thread that is
574 					 * unlocking the file descriptor.
575 					 */
576 					_thread_kern_sched_state(PS_FDLW_WAIT,
577 								 __FILE__,
578 								 __LINE__);
579 
580 					/*
581 					 * Lock the file descriptor
582 					 * table entry again:
583 					 */
584 					_SPINLOCK(&entry->lock);
585 				} else {
586 					/*
587 					 * The running thread now owns the
588 					 * write lock on this file descriptor:
589 					 */
590 					entry->w_owner = curthread;
591 
592 					/*
593 					 * Reset the number of write locks
594 					 * for this file descriptor:
595 					 */
596 					entry->w_lockcount = 0;
597 				}
598 			}
599 
600 			/* Increment the write lock count: */
601 			entry->w_lockcount++;
602 		}
603 
604 		/* Unlock the file descriptor table entry: */
605 		_SPINUNLOCK(&entry->lock);
606 	}
607 
608 	/* Return the completion status: */
609 	return (ret);
610 }
611 
612 #endif
613