xref: /dragonfly/lib/libthread_xu/thread/thr_sem.c (revision d4ef6694)
1 /*
2  * Copyright (C) 2005 David Xu <davidxu@freebsd.org>.
3  * Copyright (C) 2000 Jason Evans <jasone@freebsd.org>.
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(s), this list of conditions and the following disclaimer as
11  *    the first lines of this file unmodified other than the possible
12  *    addition of one or more copyright notices.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice(s), this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) BE
22  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
25  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
27  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #include "namespace.h"
32 #include <machine/tls.h>
33 #include <sys/semaphore.h>
34 #include <sys/mman.h>
35 #include <sys/queue.h>
36 #include <sys/stat.h>
37 #include <sys/types.h>
38 
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <limits.h>
42 #include <pthread.h>
43 #include <stdarg.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <time.h>
47 #include <unistd.h>
48 #include "un-namespace.h"
49 #include "thr_private.h"
50 
51 #define container_of(ptr, type, member)				\
52 ({								\
53 	__typeof(((type *)0)->member) *_p = (ptr);		\
54 	(type *)((char *)_p - offsetof(type, member));		\
55 })
56 
57 /*
58  * Semaphore definitions.
59  */
60 struct sem {
61 	u_int32_t		magic;
62 	volatile umtx_t		count;
63 	int			semid;
64 	int			unused; /* pad */
65 };
66 
67 #define	SEM_MAGIC	((u_int32_t) 0x09fa4012)
68 
69 static char const *sem_prefix = "/var/run/sem";
70 
71 
72 /*
73  * POSIX requires that two successive calls to sem_open return
74  * the same address if no call to unlink nor close have been
75  * done in the middle. For that, we keep a list of open semaphore
76  * and search for an existing one before remapping a semaphore.
77  * We have to keep the fd open to check for races.
78  *
79  * Example :
80  * sem_open("/test", O_CREAT | O_EXCL...) -> fork() ->
81  * parent :
82  *   sem_unlink("/test") -> sem_open("/test", O_CREAT | O_EXCl ...)
83  * child :
84  *   sem_open("/test", 0).
85  * We need to check that the cached mapping is the one of the most up
86  * to date file linked at this name, or child process will reopen the
87  * *old* version of the semaphore, which is wrong.
88  *
89  * fstat and nlink check is used to test for this race.
90  */
91 
92 struct sem_info {
93 	int open_count;
94 	ino_t inode;
95 	dev_t dev;
96 	int fd;
97 	sem_t sem;
98 	LIST_ENTRY(sem_info) next;
99 };
100 
101 
102 
103 static pthread_once_t once = PTHREAD_ONCE_INIT;
104 static pthread_mutex_t sem_lock;
105 static LIST_HEAD(,sem_info) sem_list = LIST_HEAD_INITIALIZER(sem_list);
106 
107 
108 #define SEMID_LWP	0
109 #define SEMID_FORK	1
110 #define SEMID_NAMED	2
111 
112 static void
113 sem_prefork(void)
114 {
115 	_pthread_mutex_lock(&sem_lock);
116 }
117 
118 static void
119 sem_postfork(void)
120 {
121 	_pthread_mutex_unlock(&sem_lock);
122 }
123 
124 static void
125 sem_child_postfork(void)
126 {
127 	_pthread_mutex_unlock(&sem_lock);
128 }
129 
130 static void
131 sem_module_init(void)
132 {
133 	pthread_mutexattr_t ma;
134 
135 	_pthread_mutexattr_init(&ma);
136 	_pthread_mutexattr_settype(&ma,  PTHREAD_MUTEX_RECURSIVE);
137 	_pthread_mutex_init(&sem_lock, &ma);
138 	_pthread_mutexattr_destroy(&ma);
139 	_pthread_atfork(sem_prefork, sem_postfork, sem_child_postfork);
140 }
141 
142 static inline int
143 sem_check_validity(sem_t *sem)
144 {
145 
146 	if ((sem != NULL) && (*sem != NULL) && ((*sem)->magic == SEM_MAGIC)) {
147 		return (0);
148 	} else {
149 		errno = EINVAL;
150 		return (-1);
151 	}
152 }
153 
154 static sem_t
155 sem_alloc(unsigned int value, int pshared)
156 {
157 	sem_t sem;
158 	int semid;
159 
160 	if (value > SEM_VALUE_MAX) {
161 		errno = EINVAL;
162 		return (NULL);
163 	}
164 	if (pshared) {
165 		static __thread sem_t sem_base;
166 		static __thread int sem_count;
167 
168 		if (sem_base == NULL) {
169 			sem_base = mmap(NULL, getpagesize(),
170 					PROT_READ | PROT_WRITE,
171 					MAP_ANON | MAP_SHARED,
172 					-1, 0);
173 			sem_count = getpagesize() / sizeof(*sem);
174 		}
175 		sem = sem_base++;
176 		if (--sem_count == 0)
177 			sem_base = NULL;
178 		semid = SEMID_FORK;
179 	} else {
180 		sem = malloc(sizeof(struct sem));
181 		semid = SEMID_LWP;
182 	}
183 	if (sem == NULL) {
184 		errno = ENOSPC;
185 		return (NULL);
186 	}
187 	sem->magic = SEM_MAGIC;
188 	sem->count = (u_int32_t)value;
189 	sem->semid = semid;
190 	return (sem);
191 }
192 
193 int
194 _sem_init(sem_t *sem, int pshared, unsigned int value)
195 {
196 	if (sem == NULL) {
197 		errno = EINVAL;
198 		return (-1);
199 	}
200 
201 	*sem = sem_alloc(value, pshared);
202 	if (*sem == NULL)
203 		return (-1);
204 	return (0);
205 }
206 
207 int
208 _sem_destroy(sem_t *sem)
209 {
210 	if (sem_check_validity(sem) != 0) {
211 		errno = EINVAL;
212 		return (-1);
213 	}
214 
215 	(*sem)->magic = 0;
216 
217 	switch ((*sem)->semid) {
218 		case SEMID_LWP:
219 			free(*sem);
220 			break;
221 		case SEMID_FORK:
222 			/* memory is left intact */
223 			break;
224 		default:
225 			errno = EINVAL;
226 			return (-1);
227 	}
228 	return (0);
229 }
230 
231 int
232 _sem_getvalue(sem_t * __restrict sem, int * __restrict sval)
233 {
234 	if (sem_check_validity(sem) != 0)
235 		return (-1);
236 
237 	*sval = (*sem)->count;
238 	return (0);
239 }
240 
241 int
242 _sem_trywait(sem_t *sem)
243 {
244 	int val;
245 
246 	if (sem_check_validity(sem) != 0)
247 		return (-1);
248 
249 	while ((val = (*sem)->count) > 0) {
250 		if (atomic_cmpset_int(&(*sem)->count, val, val - 1))
251 			return (0);
252 	}
253 	errno = EAGAIN;
254 	return (-1);
255 }
256 
257 int
258 _sem_wait(sem_t *sem)
259 {
260 	struct pthread *curthread;
261 	int val, oldcancel, retval;
262 
263 	if (sem_check_validity(sem) != 0)
264 		return (-1);
265 
266 	curthread = tls_get_curthread();
267 	_pthread_testcancel();
268 	do {
269 		while ((val = (*sem)->count) > 0) {
270 			if (atomic_cmpset_acq_int(&(*sem)->count, val, val - 1))
271 				return (0);
272 		}
273 		oldcancel = _thr_cancel_enter(curthread);
274 		retval = _thr_umtx_wait(&(*sem)->count, 0, NULL, 0);
275 		_thr_cancel_leave(curthread, oldcancel);
276 	} while (retval == 0);
277 	errno = retval;
278 	return (-1);
279 }
280 
281 int
282 _sem_timedwait(sem_t * __restrict sem, const struct timespec * __restrict abstime)
283 {
284 	struct timespec ts, ts2;
285 	struct pthread *curthread;
286 	int val, oldcancel, retval;
287 
288 	if (sem_check_validity(sem) != 0)
289 		return (-1);
290 
291 	curthread = tls_get_curthread();
292 
293 	/*
294 	 * The timeout argument is only supposed to
295 	 * be checked if the thread would have blocked.
296 	 */
297 	_pthread_testcancel();
298 	do {
299 		while ((val = (*sem)->count) > 0) {
300 			if (atomic_cmpset_acq_int(&(*sem)->count, val, val - 1))
301 				return (0);
302 		}
303 		if (abstime == NULL ||
304 				abstime->tv_nsec >= 1000000000 || abstime->tv_nsec < 0) {
305 			errno = EINVAL;
306 			return (-1);
307 		}
308 		clock_gettime(CLOCK_REALTIME, &ts);
309 		TIMESPEC_SUB(&ts2, abstime, &ts);
310 		oldcancel = _thr_cancel_enter(curthread);
311 		retval = _thr_umtx_wait(&(*sem)->count, 0, &ts2,
312 				CLOCK_REALTIME);
313 		_thr_cancel_leave(curthread, oldcancel);
314 	} while (retval == 0);
315 	errno = retval;
316 	return (-1);
317 }
318 
319 int
320 _sem_post(sem_t *sem)
321 {
322 	int val;
323 
324 	if (sem_check_validity(sem) != 0)
325 		return (-1);
326 
327 	/*
328 	 * sem_post() is required to be safe to call from within
329 	 * signal handlers, these code should work as that.
330 	 */
331 	do {
332 		val = (*sem)->count;
333 	} while (!atomic_cmpset_acq_int(&(*sem)->count, val, val + 1));
334 	_thr_umtx_wake(&(*sem)->count, val + 1);
335 	return (0);
336 }
337 
338 static int
339 get_path(const char *name, char *path, size_t len, char const **prefix)
340 {
341 	size_t path_len;
342 
343 	*prefix = NULL;
344 
345 	if (name[0] == '/') {
346 		*prefix = getenv("LIBTHREAD_SEM_PREFIX");
347 
348 		if (*prefix == NULL)
349 			*prefix = sem_prefix;
350 
351 		path_len = strlcpy(path, *prefix, len);
352 
353 		if (path_len > len) {
354 			return (ENAMETOOLONG);
355 		}
356 	}
357 
358 	path_len = strlcat(path, name, len);
359 
360 	if (path_len > len)
361 		return (ENAMETOOLONG);
362 
363 	return (0);
364 }
365 
366 
367 static sem_t *
368 sem_get_mapping(ino_t inode, dev_t dev)
369 {
370 	struct sem_info *ni;
371 	struct stat sbuf;
372 
373 	LIST_FOREACH(ni, &sem_list, next) {
374 		if (ni->inode == inode && ni->dev == dev) {
375 			/* Check for races */
376 			if(_fstat(ni->fd, &sbuf) == 0) {
377 				if (sbuf.st_nlink > 0) {
378 					ni->open_count++;
379 					return (&ni->sem);
380 				} else {
381 					ni->inode = 0;
382 					LIST_REMOVE(ni, next);
383 				}
384 			}
385 			return (SEM_FAILED);
386 
387 		}
388 	}
389 
390 	return (SEM_FAILED);
391 }
392 
393 
394 static sem_t *
395 sem_add_mapping(ino_t inode, dev_t dev, sem_t sem, int fd)
396 {
397 	struct sem_info *ni;
398 
399 	ni = malloc(sizeof(struct sem_info));
400 	if (ni == NULL) {
401 		errno = ENOSPC;
402 		return (SEM_FAILED);
403 	}
404 
405 	bzero(ni, sizeof(*ni));
406 	ni->open_count = 1;
407 	ni->sem = sem;
408 	ni->fd = fd;
409 	ni->inode = inode;
410 	ni->dev = dev;
411 
412 	LIST_INSERT_HEAD(&sem_list, ni, next);
413 
414 	return (&ni->sem);
415 }
416 
417 static int
418 sem_close_mapping(sem_t *sem)
419 {
420 	struct sem_info *ni;
421 
422 	if ((*sem)->semid != SEMID_NAMED)
423 		return (EINVAL);
424 
425 	ni = container_of(sem, struct sem_info, sem);
426 
427 	if ( --ni->open_count > 0) {
428 		return (0);
429 	} else {
430 		if (ni->inode != 0) {
431 			LIST_REMOVE(ni, next);
432 		}
433 		munmap(ni->sem, getpagesize());
434 		__sys_close(ni->fd);
435 		free(ni);
436 		return (0);
437 	}
438 }
439 
440 sem_t *
441 _sem_open(const char *name, int oflag, ...)
442 {
443 	char path[PATH_MAX];
444 	char tmppath[PATH_MAX];
445 	char const *prefix = NULL;
446 	char *tmpname = NULL;
447 	size_t path_len;
448 	int error, fd, create;
449 	sem_t *sem;
450 	sem_t semtmp;
451 	va_list ap;
452 	mode_t mode;
453 	struct stat sbuf;
454 	unsigned int value = 0;
455 	unsigned int retry_count;
456 
457 	create = 0;
458 	error = 0;
459 	fd = -1;
460 	sem = SEM_FAILED;
461 
462 	/*
463 	 * Bail out if invalid flags specified.
464 	 */
465 	if (oflag & ~(O_CREAT|O_EXCL)) {
466 		errno = EINVAL;
467 		return (SEM_FAILED);
468 	}
469 
470 	oflag |= O_RDWR;
471 	oflag |= O_CLOEXEC;
472 
473 	if (name == NULL) {
474 		errno = EINVAL;
475 		return (SEM_FAILED);
476 	}
477 
478 	_pthread_once(&once, sem_module_init);
479 
480 	_pthread_mutex_lock(&sem_lock);
481 
482 	error = get_path(name, path, PATH_MAX, &prefix);
483 	if (error) {
484 		errno = error;
485 		goto error;
486 	}
487 
488 retry:
489 	tmpname = NULL;
490 	retry_count = 10;
491 
492 	fd = __sys_open(path, O_RDWR | O_CLOEXEC);
493 
494 	if (fd > 0) {
495 
496 		if ((oflag & O_EXCL) == O_EXCL) {
497 			__sys_close(fd);
498 			errno = EEXIST;
499 			goto error;
500 		}
501 
502 		if (_fstat(fd, &sbuf) != 0) {
503 			/* Bad things happened, like another thread closing our descriptor */
504 			__sys_close(fd);
505 			errno = EINVAL;
506 			goto error;
507 		}
508 
509 		sem = sem_get_mapping(sbuf.st_ino, sbuf.st_dev);
510 
511 		if (sem != SEM_FAILED) {
512 			__sys_close(fd);
513 			goto done;
514 		}
515 
516 		if ((sbuf.st_mode & S_IFREG) == 0) {
517 			/* We only want regular files here */
518 			__sys_close(fd);
519 			errno = EINVAL;
520 			goto error;
521 		}
522 	} else if ((oflag & O_CREAT) && errno == ENOENT) {
523 
524 		va_start(ap, oflag);
525 
526 		mode = (mode_t) va_arg(ap, int);
527 		value = (unsigned int) va_arg(ap, int);
528 
529 		va_end(ap);
530 
531 		if (value > SEM_VALUE_MAX) {
532 			errno = EINVAL;
533 			goto error;
534 		}
535 
536 		strlcpy(tmppath, prefix, sizeof(tmppath));
537 		path_len = strlcat(tmppath, "/sem.XXXXXX", sizeof(tmppath));
538 
539 		if (path_len > sizeof(tmppath)) {
540 			errno = ENAMETOOLONG;
541 			goto error;
542 		}
543 
544 		while (retry_count-- > 0) {
545 			tmpname = mktemp(tmppath);
546 
547 			if ( tmpname == NULL) {
548 				errno = EINVAL;
549 				goto error;
550 			}
551 
552 			fd = __sys_open(tmpname, O_RDWR | O_CLOEXEC | O_CREAT | O_EXCL, mode);
553 
554 			if (fd > 0 || errno != EEXIST) {
555 				break;
556 			}
557 
558 		}
559 
560 		if (retry_count == 0) {
561 			__sys_close(fd);
562 			errno = ENOSPC; /* XXX POSIX does not allow for EAGAIN */
563 			goto error;
564 		}
565 
566 		create = 1;
567 	}
568 
569 	if (fd == -1) {
570 		switch (errno) {
571 			case ENOTDIR:
572 			case EISDIR:
573 			case EMLINK:
574 			case ELOOP:
575 				errno = EINVAL;
576 				break;
577 			case EDQUOT:
578 			case EIO:
579 				errno = ENOSPC;
580 				break;
581 			case EROFS:
582 				errno = EACCES;
583 		}
584 		goto error;
585 	}
586 
587 	semtmp = (sem_t) mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE,
588 			MAP_NOSYNC | MAP_SHARED, fd, 0);
589 
590 	if (semtmp == MAP_FAILED) {
591 		if (errno != EACCES && errno != EMFILE)
592 			errno = ENOMEM;
593 
594 		if (create)
595 			unlink(tmpname);
596 
597 		__sys_close(fd);
598 		goto error;
599 	}
600 
601 	if (create) {
602 		ftruncate(fd, sizeof(struct sem));
603 		semtmp->magic = SEM_MAGIC;
604 		semtmp->count = (u_int32_t)value;
605 		semtmp->semid = SEMID_NAMED;
606 
607 		if (link(tmpname, path) != 0) {
608 			munmap(semtmp, getpagesize());
609 			__sys_close(fd);
610 			unlink(tmpname);
611 
612 			if (errno == EEXIST && (oflag & O_EXCL) == 0) {
613 				goto retry;
614 			}
615 
616 			goto error;
617 		}
618 		unlink(tmpname);
619 
620 		if (_fstat(fd, &sbuf) != 0) {
621 			/* Bad things happened, like another thread closing our descriptor */
622 			munmap(semtmp, getpagesize());
623 			__sys_close(fd);
624 			errno = EINVAL;
625 			goto error;
626 		}
627 
628 	}
629 	sem = sem_add_mapping(sbuf.st_ino, sbuf.st_dev, semtmp, fd);
630 
631 done:
632 	_pthread_mutex_unlock(&sem_lock);
633 	return (sem);
634 
635 error:
636 	_pthread_mutex_unlock(&sem_lock);
637 	return (SEM_FAILED);
638 
639 }
640 
641 int
642 _sem_close(sem_t *sem)
643 {
644 	_pthread_once(&once, sem_module_init);
645 
646 	_pthread_mutex_lock(&sem_lock);
647 
648 	if (sem_check_validity(sem)) {
649 		_pthread_mutex_unlock(&sem_lock);
650 		errno = EINVAL;
651 		return (-1);
652 	}
653 
654 	if (sem_close_mapping(sem)) {
655 		_pthread_mutex_unlock(&sem_lock);
656 		errno = EINVAL;
657 		return (-1);
658 	}
659 	_pthread_mutex_unlock(&sem_lock);
660 
661 	return (0);
662 }
663 
664 int
665 _sem_unlink(const char *name)
666 {
667 	char path[PATH_MAX];
668 	const char *prefix;
669 	int error;
670 
671 	error = get_path(name, path, PATH_MAX, &prefix);
672 	if (error) {
673 		errno = error;
674 		return (-1);
675 	}
676 
677 	error = unlink(path);
678 
679 	if(error) {
680 		if (errno != ENAMETOOLONG && errno != ENOENT)
681 			errno = EACCES;
682 
683 		return (-1);
684 	}
685 
686 	return (0);
687 }
688 
689 __strong_reference(_sem_destroy, sem_destroy);
690 __strong_reference(_sem_getvalue, sem_getvalue);
691 __strong_reference(_sem_init, sem_init);
692 __strong_reference(_sem_trywait, sem_trywait);
693 __strong_reference(_sem_wait, sem_wait);
694 __strong_reference(_sem_timedwait, sem_timedwait);
695 __strong_reference(_sem_post, sem_post);
696 __strong_reference(_sem_open, sem_open);
697 __strong_reference(_sem_close, sem_close);
698 __strong_reference(_sem_unlink, sem_unlink);
699 
700