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