xref: /dragonfly/lib/libthread_xu/thread/thr_sem.c (revision 0fe220b4)
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 #include <errno.h>
39 #include <fcntl.h>
40 #include <limits.h>
41 #include <pthread.h>
42 #include <stdarg.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <time.h>
46 #include <unistd.h>
47 #include "un-namespace.h"
48 
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 	_thr_atfork_kern(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 		errno = EINVAL;
236 		return (-1);
237 	}
238 
239 	*sval = (*sem)->count;
240 	return (0);
241 }
242 
243 int
244 _sem_trywait(sem_t *sem)
245 {
246 	int val;
247 
248 	if (sem_check_validity(sem) != 0) {
249 		errno = EINVAL;
250 		return (-1);
251 	}
252 
253 	while ((val = (*sem)->count) > 0) {
254 		if (atomic_cmpset_int(&(*sem)->count, val, val - 1))
255 			return (0);
256 	}
257 	errno = EAGAIN;
258 	return (-1);
259 }
260 
261 int
262 _sem_wait(sem_t *sem)
263 {
264 	struct pthread *curthread;
265 	int val, oldcancel, retval;
266 
267 	if (sem_check_validity(sem) != 0) {
268 		errno = EINVAL;
269 		return (-1);
270 	}
271 
272 	curthread = tls_get_curthread();
273 	_pthread_testcancel();
274 
275 	for (;;) {
276 		while ((val = (*sem)->count) > 0) {
277 			if (atomic_cmpset_acq_int(&(*sem)->count, val, val - 1))
278 				return (0);
279 		}
280 		oldcancel = _thr_cancel_enter(curthread);
281 		retval = _thr_umtx_wait(&(*sem)->count, 0, NULL, 0);
282 		_thr_cancel_leave(curthread, oldcancel);
283 		/* ignore retval */
284 	}
285 
286 	errno = retval;
287 
288 	return (-1);
289 }
290 
291 int
292 _sem_timedwait(sem_t * __restrict sem, const struct timespec * __restrict abstime)
293 {
294 	struct timespec ts, ts2;
295 	struct pthread *curthread;
296 	int val, oldcancel, retval;
297 
298 	if (sem_check_validity(sem) != 0)
299 		return (-1);
300 
301 	curthread = tls_get_curthread();
302 	_pthread_testcancel();
303 
304 	/*
305 	 * The timeout argument is only supposed to
306 	 * be checked if the thread would have blocked.
307 	 */
308 	do {
309 		while ((val = (*sem)->count) > 0) {
310 			if (atomic_cmpset_acq_int(&(*sem)->count, val, val - 1))
311 				return (0);
312 		}
313 		if (abstime == NULL ||
314 		    abstime->tv_nsec >= 1000000000 ||
315 		    abstime->tv_nsec < 0) {
316 			errno = EINVAL;
317 			return (-1);
318 		}
319 		clock_gettime(CLOCK_REALTIME, &ts);
320 		TIMESPEC_SUB(&ts2, abstime, &ts);
321 		oldcancel = _thr_cancel_enter(curthread);
322 		retval = _thr_umtx_wait(&(*sem)->count, 0, &ts2,
323 					CLOCK_REALTIME);
324 		_thr_cancel_leave(curthread, oldcancel);
325 	} while (retval != ETIMEDOUT && retval != EINTR);
326 
327 	errno = retval;
328 
329 	return (-1);
330 }
331 
332 int
333 _sem_post(sem_t *sem)
334 {
335 	int val;
336 
337 	if (sem_check_validity(sem) != 0)
338 		return (-1);
339 
340 	/*
341 	 * sem_post() is required to be safe to call from within
342 	 * signal handlers, these code should work as that.
343 	 */
344 	do {
345 		val = (*sem)->count;
346 	} while (!atomic_cmpset_acq_int(&(*sem)->count, val, val + 1));
347 	_thr_umtx_wake(&(*sem)->count, val + 1);
348 	return (0);
349 }
350 
351 static int
352 get_path(const char *name, char *path, size_t len, char const **prefix)
353 {
354 	size_t path_len;
355 
356 	*prefix = NULL;
357 
358 	if (name[0] == '/') {
359 		*prefix = getenv("LIBTHREAD_SEM_PREFIX");
360 
361 		if (*prefix == NULL)
362 			*prefix = sem_prefix;
363 
364 		path_len = strlcpy(path, *prefix, len);
365 
366 		if (path_len > len) {
367 			return (ENAMETOOLONG);
368 		}
369 	}
370 
371 	path_len = strlcat(path, name, len);
372 
373 	if (path_len > len)
374 		return (ENAMETOOLONG);
375 
376 	return (0);
377 }
378 
379 
380 static sem_t *
381 sem_get_mapping(ino_t inode, dev_t dev)
382 {
383 	struct sem_info *ni;
384 	struct stat sbuf;
385 
386 	LIST_FOREACH(ni, &sem_list, next) {
387 		if (ni->inode == inode && ni->dev == dev) {
388 			/* Check for races */
389 			if(_fstat(ni->fd, &sbuf) == 0) {
390 				if (sbuf.st_nlink > 0) {
391 					ni->open_count++;
392 					return (&ni->sem);
393 				} else {
394 					ni->inode = 0;
395 					LIST_REMOVE(ni, next);
396 				}
397 			}
398 			return (SEM_FAILED);
399 
400 		}
401 	}
402 
403 	return (SEM_FAILED);
404 }
405 
406 
407 static sem_t *
408 sem_add_mapping(ino_t inode, dev_t dev, sem_t sem, int fd)
409 {
410 	struct sem_info *ni;
411 
412 	ni = malloc(sizeof(struct sem_info));
413 	if (ni == NULL) {
414 		errno = ENOSPC;
415 		return (SEM_FAILED);
416 	}
417 
418 	bzero(ni, sizeof(*ni));
419 	ni->open_count = 1;
420 	ni->sem = sem;
421 	ni->fd = fd;
422 	ni->inode = inode;
423 	ni->dev = dev;
424 
425 	LIST_INSERT_HEAD(&sem_list, ni, next);
426 
427 	return (&ni->sem);
428 }
429 
430 static int
431 sem_close_mapping(sem_t *sem)
432 {
433 	struct sem_info *ni;
434 
435 	if ((*sem)->semid != SEMID_NAMED)
436 		return (EINVAL);
437 
438 	ni = container_of(sem, struct sem_info, sem);
439 
440 	if ( --ni->open_count > 0) {
441 		return (0);
442 	} else {
443 		if (ni->inode != 0) {
444 			LIST_REMOVE(ni, next);
445 		}
446 		munmap(ni->sem, getpagesize());
447 		__sys_close(ni->fd);
448 		free(ni);
449 		return (0);
450 	}
451 }
452 
453 sem_t *
454 _sem_open(const char *name, int oflag, ...)
455 {
456 	char path[PATH_MAX];
457 	char tmppath[PATH_MAX];
458 	char const *prefix = NULL;
459 	size_t path_len;
460 	int error, fd, create;
461 	sem_t *sem;
462 	sem_t semtmp;
463 	va_list ap;
464 	mode_t mode;
465 	struct stat sbuf;
466 	unsigned int value = 0;
467 
468 	create = 0;
469 	error = 0;
470 	fd = -1;
471 	sem = SEM_FAILED;
472 
473 	/*
474 	 * Bail out if invalid flags specified.
475 	 */
476 	if (oflag & ~(O_CREAT|O_EXCL)) {
477 		errno = EINVAL;
478 		return (SEM_FAILED);
479 	}
480 
481 	oflag |= O_RDWR;
482 	oflag |= O_CLOEXEC;
483 
484 	if (name == NULL) {
485 		errno = EINVAL;
486 		return (SEM_FAILED);
487 	}
488 
489 	_pthread_once(&once, sem_module_init);
490 
491 	_pthread_mutex_lock(&sem_lock);
492 
493 	error = get_path(name, path, PATH_MAX, &prefix);
494 	if (error) {
495 		errno = error;
496 		goto error;
497 	}
498 
499 retry:
500 	fd = __sys_open(path, O_RDWR | O_CLOEXEC);
501 
502 	if (fd > 0) {
503 
504 		if ((oflag & O_EXCL) == O_EXCL) {
505 			__sys_close(fd);
506 			errno = EEXIST;
507 			goto error;
508 		}
509 
510 		if (_fstat(fd, &sbuf) != 0) {
511 			/* Bad things happened, like another thread closing our descriptor */
512 			__sys_close(fd);
513 			errno = EINVAL;
514 			goto error;
515 		}
516 
517 		sem = sem_get_mapping(sbuf.st_ino, sbuf.st_dev);
518 
519 		if (sem != SEM_FAILED) {
520 			__sys_close(fd);
521 			goto done;
522 		}
523 
524 		if ((sbuf.st_mode & S_IFREG) == 0) {
525 			/* We only want regular files here */
526 			__sys_close(fd);
527 			errno = EINVAL;
528 			goto error;
529 		}
530 	} else if ((oflag & O_CREAT) && errno == ENOENT) {
531 
532 		va_start(ap, oflag);
533 
534 		mode = (mode_t) va_arg(ap, int);
535 		value = (unsigned int) va_arg(ap, int);
536 
537 		va_end(ap);
538 
539 		if (value > SEM_VALUE_MAX) {
540 			errno = EINVAL;
541 			goto error;
542 		}
543 
544 		strlcpy(tmppath, prefix, sizeof(tmppath));
545 		path_len = strlcat(tmppath, "/sem.XXXXXX", sizeof(tmppath));
546 
547 		if (path_len > sizeof(tmppath)) {
548 			errno = ENAMETOOLONG;
549 			goto error;
550 		}
551 
552 
553 		fd = mkstemp(tmppath);
554 
555 		if ( fd == -1 ) {
556 			errno = EINVAL;
557 			goto error;
558 		}
559 
560 		error = fchmod(fd, mode);
561 		if ( error == -1 ) {
562 			__sys_close(fd);
563 			errno = EINVAL;
564 			goto error;
565 		}
566 
567 		error = __sys_fcntl(fd, F_SETFD, FD_CLOEXEC);
568 		if ( error == -1 ) {
569 			__sys_close(fd);
570 			errno = EINVAL;
571 			goto error;
572 		}
573 
574 		create = 1;
575 	}
576 
577 	if (fd == -1) {
578 		switch (errno) {
579 			case ENOTDIR:
580 			case EISDIR:
581 			case EMLINK:
582 			case ELOOP:
583 				errno = EINVAL;
584 				break;
585 			case EDQUOT:
586 			case EIO:
587 				errno = ENOSPC;
588 				break;
589 			case EROFS:
590 				errno = EACCES;
591 		}
592 		goto error;
593 	}
594 
595 	semtmp = (sem_t) mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE,
596 			MAP_NOSYNC | MAP_SHARED, fd, 0);
597 
598 	if (semtmp == MAP_FAILED) {
599 		if (errno != EACCES && errno != EMFILE)
600 			errno = ENOMEM;
601 
602 		if (create)
603 			unlink(tmppath);
604 
605 		__sys_close(fd);
606 		goto error;
607 	}
608 
609 	if (create) {
610 		ftruncate(fd, sizeof(struct sem));
611 		semtmp->magic = SEM_MAGIC;
612 		semtmp->count = (u_int32_t)value;
613 		semtmp->semid = SEMID_NAMED;
614 
615 		if (link(tmppath, path) != 0) {
616 			munmap(semtmp, getpagesize());
617 			__sys_close(fd);
618 			unlink(tmppath);
619 
620 			if (errno == EEXIST && (oflag & O_EXCL) == 0) {
621 				goto retry;
622 			}
623 
624 			goto error;
625 		}
626 		unlink(tmppath);
627 
628 		if (_fstat(fd, &sbuf) != 0) {
629 			/* Bad things happened, like another thread closing our descriptor */
630 			munmap(semtmp, getpagesize());
631 			__sys_close(fd);
632 			errno = EINVAL;
633 			goto error;
634 		}
635 
636 	}
637 	sem = sem_add_mapping(sbuf.st_ino, sbuf.st_dev, semtmp, fd);
638 
639 done:
640 	_pthread_mutex_unlock(&sem_lock);
641 	return (sem);
642 
643 error:
644 	_pthread_mutex_unlock(&sem_lock);
645 	return (SEM_FAILED);
646 
647 }
648 
649 int
650 _sem_close(sem_t *sem)
651 {
652 	_pthread_once(&once, sem_module_init);
653 
654 	_pthread_mutex_lock(&sem_lock);
655 
656 	if (sem_check_validity(sem)) {
657 		_pthread_mutex_unlock(&sem_lock);
658 		errno = EINVAL;
659 		return (-1);
660 	}
661 
662 	if (sem_close_mapping(sem)) {
663 		_pthread_mutex_unlock(&sem_lock);
664 		errno = EINVAL;
665 		return (-1);
666 	}
667 	_pthread_mutex_unlock(&sem_lock);
668 
669 	return (0);
670 }
671 
672 int
673 _sem_unlink(const char *name)
674 {
675 	char path[PATH_MAX];
676 	const char *prefix;
677 	int error;
678 
679 	error = get_path(name, path, PATH_MAX, &prefix);
680 	if (error) {
681 		errno = error;
682 		return (-1);
683 	}
684 
685 	error = unlink(path);
686 
687 	if(error) {
688 		if (errno != ENAMETOOLONG && errno != ENOENT)
689 			errno = EACCES;
690 
691 		return (-1);
692 	}
693 
694 	return (0);
695 }
696 
697 __strong_reference(_sem_destroy, sem_destroy);
698 __strong_reference(_sem_getvalue, sem_getvalue);
699 __strong_reference(_sem_init, sem_init);
700 __strong_reference(_sem_trywait, sem_trywait);
701 __strong_reference(_sem_wait, sem_wait);
702 __strong_reference(_sem_timedwait, sem_timedwait);
703 __strong_reference(_sem_post, sem_post);
704 __strong_reference(_sem_open, sem_open);
705 __strong_reference(_sem_close, sem_close);
706 __strong_reference(_sem_unlink, sem_unlink);
707