xref: /dragonfly/lib/libthread_xu/thread/thr_sem.c (revision 67640b13)
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 	size_t path_len;
447 	int error, fd, create;
448 	sem_t *sem;
449 	sem_t semtmp;
450 	va_list ap;
451 	mode_t mode;
452 	struct stat sbuf;
453 	unsigned int value = 0;
454 
455 	create = 0;
456 	error = 0;
457 	fd = -1;
458 	sem = SEM_FAILED;
459 
460 	/*
461 	 * Bail out if invalid flags specified.
462 	 */
463 	if (oflag & ~(O_CREAT|O_EXCL)) {
464 		errno = EINVAL;
465 		return (SEM_FAILED);
466 	}
467 
468 	oflag |= O_RDWR;
469 	oflag |= O_CLOEXEC;
470 
471 	if (name == NULL) {
472 		errno = EINVAL;
473 		return (SEM_FAILED);
474 	}
475 
476 	_pthread_once(&once, sem_module_init);
477 
478 	_pthread_mutex_lock(&sem_lock);
479 
480 	error = get_path(name, path, PATH_MAX, &prefix);
481 	if (error) {
482 		errno = error;
483 		goto error;
484 	}
485 
486 retry:
487 	fd = __sys_open(path, O_RDWR | O_CLOEXEC);
488 
489 	if (fd > 0) {
490 
491 		if ((oflag & O_EXCL) == O_EXCL) {
492 			__sys_close(fd);
493 			errno = EEXIST;
494 			goto error;
495 		}
496 
497 		if (_fstat(fd, &sbuf) != 0) {
498 			/* Bad things happened, like another thread closing our descriptor */
499 			__sys_close(fd);
500 			errno = EINVAL;
501 			goto error;
502 		}
503 
504 		sem = sem_get_mapping(sbuf.st_ino, sbuf.st_dev);
505 
506 		if (sem != SEM_FAILED) {
507 			__sys_close(fd);
508 			goto done;
509 		}
510 
511 		if ((sbuf.st_mode & S_IFREG) == 0) {
512 			/* We only want regular files here */
513 			__sys_close(fd);
514 			errno = EINVAL;
515 			goto error;
516 		}
517 	} else if ((oflag & O_CREAT) && errno == ENOENT) {
518 
519 		va_start(ap, oflag);
520 
521 		mode = (mode_t) va_arg(ap, int);
522 		value = (unsigned int) va_arg(ap, int);
523 
524 		va_end(ap);
525 
526 		if (value > SEM_VALUE_MAX) {
527 			errno = EINVAL;
528 			goto error;
529 		}
530 
531 		strlcpy(tmppath, prefix, sizeof(tmppath));
532 		path_len = strlcat(tmppath, "/sem.XXXXXX", sizeof(tmppath));
533 
534 		if (path_len > sizeof(tmppath)) {
535 			errno = ENAMETOOLONG;
536 			goto error;
537 		}
538 
539 
540 		fd = mkstemp(tmppath);
541 
542 		if ( fd == -1 ) {
543 			errno = EINVAL;
544 			goto error;
545 		}
546 
547 		error = fchmod(fd, mode);
548 		if ( error == -1 ) {
549 			__sys_close(fd);
550 			errno = EINVAL;
551 			goto error;
552 		}
553 
554 		error = __sys_fcntl(fd, F_SETFD, FD_CLOEXEC);
555 		if ( error == -1 ) {
556 			__sys_close(fd);
557 			errno = EINVAL;
558 			goto error;
559 		}
560 
561 		create = 1;
562 	}
563 
564 	if (fd == -1) {
565 		switch (errno) {
566 			case ENOTDIR:
567 			case EISDIR:
568 			case EMLINK:
569 			case ELOOP:
570 				errno = EINVAL;
571 				break;
572 			case EDQUOT:
573 			case EIO:
574 				errno = ENOSPC;
575 				break;
576 			case EROFS:
577 				errno = EACCES;
578 		}
579 		goto error;
580 	}
581 
582 	semtmp = (sem_t) mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE,
583 			MAP_NOSYNC | MAP_SHARED, fd, 0);
584 
585 	if (semtmp == MAP_FAILED) {
586 		if (errno != EACCES && errno != EMFILE)
587 			errno = ENOMEM;
588 
589 		if (create)
590 			unlink(tmppath);
591 
592 		__sys_close(fd);
593 		goto error;
594 	}
595 
596 	if (create) {
597 		ftruncate(fd, sizeof(struct sem));
598 		semtmp->magic = SEM_MAGIC;
599 		semtmp->count = (u_int32_t)value;
600 		semtmp->semid = SEMID_NAMED;
601 
602 		if (link(tmppath, path) != 0) {
603 			munmap(semtmp, getpagesize());
604 			__sys_close(fd);
605 			unlink(tmppath);
606 
607 			if (errno == EEXIST && (oflag & O_EXCL) == 0) {
608 				goto retry;
609 			}
610 
611 			goto error;
612 		}
613 		unlink(tmppath);
614 
615 		if (_fstat(fd, &sbuf) != 0) {
616 			/* Bad things happened, like another thread closing our descriptor */
617 			munmap(semtmp, getpagesize());
618 			__sys_close(fd);
619 			errno = EINVAL;
620 			goto error;
621 		}
622 
623 	}
624 	sem = sem_add_mapping(sbuf.st_ino, sbuf.st_dev, semtmp, fd);
625 
626 done:
627 	_pthread_mutex_unlock(&sem_lock);
628 	return (sem);
629 
630 error:
631 	_pthread_mutex_unlock(&sem_lock);
632 	return (SEM_FAILED);
633 
634 }
635 
636 int
637 _sem_close(sem_t *sem)
638 {
639 	_pthread_once(&once, sem_module_init);
640 
641 	_pthread_mutex_lock(&sem_lock);
642 
643 	if (sem_check_validity(sem)) {
644 		_pthread_mutex_unlock(&sem_lock);
645 		errno = EINVAL;
646 		return (-1);
647 	}
648 
649 	if (sem_close_mapping(sem)) {
650 		_pthread_mutex_unlock(&sem_lock);
651 		errno = EINVAL;
652 		return (-1);
653 	}
654 	_pthread_mutex_unlock(&sem_lock);
655 
656 	return (0);
657 }
658 
659 int
660 _sem_unlink(const char *name)
661 {
662 	char path[PATH_MAX];
663 	const char *prefix;
664 	int error;
665 
666 	error = get_path(name, path, PATH_MAX, &prefix);
667 	if (error) {
668 		errno = error;
669 		return (-1);
670 	}
671 
672 	error = unlink(path);
673 
674 	if(error) {
675 		if (errno != ENAMETOOLONG && errno != ENOENT)
676 			errno = EACCES;
677 
678 		return (-1);
679 	}
680 
681 	return (0);
682 }
683 
684 __strong_reference(_sem_destroy, sem_destroy);
685 __strong_reference(_sem_getvalue, sem_getvalue);
686 __strong_reference(_sem_init, sem_init);
687 __strong_reference(_sem_trywait, sem_trywait);
688 __strong_reference(_sem_wait, sem_wait);
689 __strong_reference(_sem_timedwait, sem_timedwait);
690 __strong_reference(_sem_post, sem_post);
691 __strong_reference(_sem_open, sem_open);
692 __strong_reference(_sem_close, sem_close);
693 __strong_reference(_sem_unlink, sem_unlink);
694 
695