sem_new.c revision 265847
150477Speter/*
2139749Simp * Copyright (C) 2010 David Xu <davidxu@freebsd.org>.
3196008Smjacob * All rights reserved.
4167403Smjacob *
5167403Smjacob * Redistribution and use in source and binary forms, with or without
6167403Smjacob * modification, are permitted provided that the following conditions
7167403Smjacob * are met:
8167403Smjacob * 1. Redistributions of source code must retain the above copyright
9167403Smjacob *    notice(s), this list of conditions and the following disclaimer as
10167403Smjacob *    the first lines of this file unmodified other than the possible
11167403Smjacob *    addition of one or more copyright notices.
12167403Smjacob * 2. Redistributions in binary form must reproduce the above copyright
13167403Smjacob *    notice(s), this list of conditions and the following disclaimer in
14167403Smjacob *    the documentation and/or other materials provided with the
15167403Smjacob *    distribution.
16167403Smjacob *
17167403Smjacob * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY
18167403Smjacob * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19167403Smjacob * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20167403Smjacob * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) BE
21167403Smjacob * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22167403Smjacob * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23167403Smjacob * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
24167403Smjacob * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25167403Smjacob * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
26167403Smjacob * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
27196008Smjacob * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28167403Smjacob *
29197373Smjacob * $FreeBSD: head/lib/libc/gen/sem_new.c 265847 2014-05-10 19:08:07Z kib $
30167403Smjacob */
3139235Sgibbs
3235388Smjacob#include "namespace.h"
3335388Smjacob#include <sys/types.h>
3435388Smjacob#include <sys/queue.h>
3535388Smjacob#include <sys/mman.h>
3635388Smjacob#include <sys/stat.h>
3735388Smjacob#include <errno.h>
3835388Smjacob#include <machine/atomic.h>
3935388Smjacob#include <sys/umtx.h>
4035388Smjacob#include <limits.h>
4135388Smjacob#include <fcntl.h>
4235388Smjacob#include <pthread.h>
4335388Smjacob#include <stdarg.h>
4435388Smjacob#include <stdlib.h>
4535388Smjacob#include <string.h>
4635388Smjacob#include <time.h>
4735388Smjacob#include <semaphore.h>
48165308Smjacob#include <unistd.h>
4935388Smjacob#include "un-namespace.h"
50163899Smjacob#include "libc_private.h"
5135388Smjacob
52160080Smjacob__weak_reference(_sem_close, sem_close);
5335388Smjacob__weak_reference(_sem_destroy, sem_destroy);
5490754Smjacob__weak_reference(_sem_getvalue, sem_getvalue);
5535388Smjacob__weak_reference(_sem_init, sem_init);
5635388Smjacob__weak_reference(_sem_open, sem_open);
5735388Smjacob__weak_reference(_sem_post, sem_post);
5835388Smjacob__weak_reference(_sem_timedwait, sem_timedwait);
5935388Smjacob__weak_reference(_sem_trywait, sem_trywait);
6035388Smjacob__weak_reference(_sem_unlink, sem_unlink);
6135388Smjacob__weak_reference(_sem_wait, sem_wait);
6235388Smjacob
6335388Smjacob#define SEM_PREFIX	"/tmp/SEMD"
6435388Smjacob#define SEM_MAGIC	((u_int32_t)0x73656d31)
6535388Smjacob
6635388Smjacobstruct sem_nameinfo {
6735388Smjacob	int open_count;
6835388Smjacob	char *name;
6935388Smjacob	dev_t dev;
7035388Smjacob	ino_t ino;
7135388Smjacob	sem_t *sem;
7235388Smjacob	LIST_ENTRY(sem_nameinfo) next;
7335388Smjacob};
7435388Smjacob
7535388Smjacobstatic pthread_once_t once = PTHREAD_ONCE_INIT;
7635388Smjacobstatic pthread_mutex_t sem_llock;
7735388Smjacobstatic LIST_HEAD(,sem_nameinfo) sem_list = LIST_HEAD_INITIALIZER(sem_list);
7835388Smjacob
7981987Smjacobstatic void
8035388Smjacobsem_prefork()
8135388Smjacob{
8246966Smjacob
8335388Smjacob	_pthread_mutex_lock(&sem_llock);
8435388Smjacob}
8535388Smjacob
8635388Smjacobstatic void
8735388Smjacobsem_postfork()
8835388Smjacob{
8935388Smjacob	_pthread_mutex_unlock(&sem_llock);
9035388Smjacob}
9135388Smjacob
9235388Smjacobstatic void
9346966Smjacobsem_child_postfork()
9435388Smjacob{
9535388Smjacob	_pthread_mutex_unlock(&sem_llock);
9635388Smjacob}
9735388Smjacob
9835388Smjacobstatic void
9946966Smjacobsem_module_init(void)
10035388Smjacob{
10135388Smjacob	pthread_mutexattr_t ma;
10235388Smjacob
10335388Smjacob	_pthread_mutexattr_init(&ma);
10435388Smjacob	_pthread_mutexattr_settype(&ma,  PTHREAD_MUTEX_RECURSIVE);
10535388Smjacob	_pthread_mutex_init(&sem_llock, &ma);
10635388Smjacob	_pthread_mutexattr_destroy(&ma);
10735388Smjacob	_pthread_atfork(sem_prefork, sem_postfork, sem_child_postfork);
10843420Smjacob}
10943420Smjacob
11090224Smjacobstatic inline int
11143420Smjacobsem_check_validity(sem_t *sem)
11290224Smjacob{
11390224Smjacob
11435388Smjacob	if (sem->_magic == SEM_MAGIC)
115103825Smjacob		return (0);
116103825Smjacob	else {
117103825Smjacob		errno = EINVAL;
11884241Smjacob		return (-1);
11955363Smjacob	}
12075194Smjacob}
12175194Smjacob
12284241Smjacobint
12355138Smjacob_sem_init(sem_t *sem, int pshared, unsigned int value)
12484241Smjacob{
12584241Smjacob
126196008Smjacob	if (value > SEM_VALUE_MAX) {
127196008Smjacob		errno = EINVAL;
128196008Smjacob		return (-1);
129196008Smjacob	}
130196008Smjacob
131196008Smjacob	bzero(sem, sizeof(sem_t));
13284241Smjacob	sem->_magic = SEM_MAGIC;
13384241Smjacob	sem->_kern._count = (u_int32_t)value;
13484241Smjacob	sem->_kern._has_waiters = 0;
135163899Smjacob	sem->_kern._flags = pshared ? USYNC_PROCESS_SHARED : 0;
13684241Smjacob	return (0);
137196008Smjacob}
138196008Smjacob
139196008Smjacobsem_t *
14084241Smjacob_sem_open(const char *name, int flags, ...)
14184241Smjacob{
14284241Smjacob	char path[PATH_MAX];
14384241Smjacob
14484241Smjacob	struct stat sb;
14584241Smjacob	va_list ap;
14684241Smjacob	struct sem_nameinfo *ni = NULL;
14784241Smjacob	sem_t *sem = NULL;
14884241Smjacob	int fd = -1, mode, len, errsave;
14984241Smjacob	int value = 0;
15084241Smjacob
15184241Smjacob	if (name[0] != '/') {
15284241Smjacob		errno = EINVAL;
15384241Smjacob		return (SEM_FAILED);
15484241Smjacob	}
15584241Smjacob	name++;
15684241Smjacob	strcpy(path, SEM_PREFIX);
15784241Smjacob	if (strlcat(path, name, sizeof(path)) >= sizeof(path)) {
15884241Smjacob		errno = ENAMETOOLONG;
159290104Smav		return (SEM_FAILED);
160290104Smav	}
161151834Smjacob	if (flags & ~(O_CREAT|O_EXCL)) {
16235388Smjacob		errno = EINVAL;
16398282Smjacob		return (SEM_FAILED);
16498282Smjacob	}
16598282Smjacob	if ((flags & O_CREAT) != 0) {
166163899Smjacob		va_start(ap, flags);
167163899Smjacob		mode = va_arg(ap, int);
168163899Smjacob		value = va_arg(ap, int);
169163899Smjacob		va_end(ap);
17084241Smjacob	}
17184241Smjacob	fd = -1;
17284241Smjacob	_pthread_once(&once, sem_module_init);
173163899Smjacob
174196008Smjacob	_pthread_mutex_lock(&sem_llock);
17584241Smjacob	LIST_FOREACH(ni, &sem_list, next) {
17684241Smjacob		if (ni->name != NULL && strcmp(name, ni->name) == 0) {
17735388Smjacob			fd = _open(path, flags | O_RDWR | O_CLOEXEC |
17835388Smjacob			    O_EXLOCK, mode);
17935388Smjacob			if (fd == -1 || _fstat(fd, &sb) == -1)
18035388Smjacob				goto error;
18135388Smjacob			if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT |
18239235Sgibbs			    O_EXCL) || ni->dev != sb.st_dev ||
18339235Sgibbs			    ni->ino != sb.st_ino) {
18439235Sgibbs				ni->name = NULL;
18539235Sgibbs				ni = NULL;
18639235Sgibbs				break;
18739235Sgibbs			}
18839235Sgibbs			ni->open_count++;
18939235Sgibbs			sem = ni->sem;
19057587Smjacob			_pthread_mutex_unlock(&sem_llock);
19157587Smjacob			_close(fd);
19257587Smjacob			return (sem);
19357587Smjacob		}
194290147Smav	}
195290147Smav
196290147Smav	len = sizeof(*ni) + strlen(name) + 1;
197290147Smav	ni = (struct sem_nameinfo *)malloc(len);
198163899Smjacob	if (ni == NULL) {
199163899Smjacob		errno = ENOSPC;
200163899Smjacob		goto error;
201163899Smjacob	}
202290147Smav
203290147Smav	ni->name = (char *)(ni+1);
204290147Smav	strcpy(ni->name, name);
20539235Sgibbs
20639235Sgibbs	if (fd == -1) {
20739235Sgibbs		fd = _open(path, flags | O_RDWR | O_CLOEXEC | O_EXLOCK, mode);
20839235Sgibbs		if (fd == -1 || _fstat(fd, &sb) == -1)
20939235Sgibbs			goto error;
21039235Sgibbs	}
21139235Sgibbs	if (sb.st_size < sizeof(sem_t)) {
21239235Sgibbs		sem_t tmp;
21339235Sgibbs
21439235Sgibbs		tmp._magic = SEM_MAGIC;
21541520Smjacob		tmp._kern._has_waiters = 0;
21639235Sgibbs		tmp._kern._count = value;
21739235Sgibbs		tmp._kern._flags = USYNC_PROCESS_SHARED | SEM_NAMED;
21839235Sgibbs		if (_write(fd, &tmp, sizeof(tmp)) != sizeof(tmp))
21939235Sgibbs			goto error;
22039235Sgibbs	}
22139235Sgibbs	flock(fd, LOCK_UN);
22239235Sgibbs	sem = (sem_t *)mmap(NULL, sizeof(sem_t), PROT_READ|PROT_WRITE,
22339235Sgibbs		MAP_SHARED|MAP_NOSYNC, fd, 0);
22439235Sgibbs	if (sem == MAP_FAILED) {
22541520Smjacob		sem = NULL;
22639235Sgibbs		if (errno == ENOMEM)
22781792Smjacob			errno = ENOSPC;
228163899Smjacob		goto error;
229163899Smjacob	}
23079237Smjacob	if (sem->_magic != SEM_MAGIC) {
23179237Smjacob		errno = EINVAL;
232204397Smjacob		goto error;
233204397Smjacob	}
23479237Smjacob	ni->open_count = 1;
23579237Smjacob	ni->sem = sem;
23679237Smjacob	ni->dev = sb.st_dev;
23779237Smjacob	ni->ino = sb.st_ino;
23879237Smjacob	LIST_INSERT_HEAD(&sem_list, ni, next);
23979237Smjacob	_close(fd);
24057149Smjacob	_pthread_mutex_unlock(&sem_llock);
241204397Smjacob	return (sem);
242204397Smjacob
243204397Smjacoberror:
244204397Smjacob	errsave = errno;
245204397Smjacob	if (fd != -1)
24657149Smjacob		_close(fd);
24757149Smjacob	if (sem != NULL)
24857149Smjacob		munmap(sem, sizeof(sem_t));
24957149Smjacob	free(ni);
25057149Smjacob	_pthread_mutex_unlock(&sem_llock);
25157149Smjacob	errno = errsave;
252204397Smjacob	return (SEM_FAILED);
253204397Smjacob}
254163899Smjacob
255163899Smjacobint
25679237Smjacob_sem_close(sem_t *sem)
257197372Smjacob{
258197372Smjacob	struct sem_nameinfo *ni;
259197372Smjacob
260197372Smjacob	if (sem_check_validity(sem) != 0)
261197372Smjacob		return (-1);
262197372Smjacob
263197372Smjacob	if (!(sem->_kern._flags & SEM_NAMED)) {
264197372Smjacob		errno = EINVAL;
265197372Smjacob		return (-1);
266197372Smjacob	}
267197372Smjacob
268197372Smjacob	_pthread_once(&once, sem_module_init);
269197372Smjacob
270197372Smjacob	_pthread_mutex_lock(&sem_llock);
271197372Smjacob	LIST_FOREACH(ni, &sem_list, next) {
272197372Smjacob		if (sem == ni->sem) {
273197372Smjacob			if (--ni->open_count > 0) {
274197372Smjacob				_pthread_mutex_unlock(&sem_llock);
275197372Smjacob				return (0);
276197372Smjacob			}
277197372Smjacob			else
278197372Smjacob				break;
27979237Smjacob		}
28079237Smjacob	}
28179237Smjacob
28279237Smjacob	if (ni) {
28357149Smjacob		LIST_REMOVE(ni, next);
28439235Sgibbs		_pthread_mutex_unlock(&sem_llock);
285197372Smjacob		munmap(sem, sizeof(*sem));
286197372Smjacob		free(ni);
287197372Smjacob		return (0);
288197372Smjacob	}
289163899Smjacob	_pthread_mutex_unlock(&sem_llock);
29079237Smjacob	errno = EINVAL;
291163899Smjacob	return (-1);
29279237Smjacob}
293163899Smjacob
294163899Smjacobint
295163899Smjacob_sem_unlink(const char *name)
296163899Smjacob{
29779237Smjacob	char path[PATH_MAX];
29879237Smjacob
29935388Smjacob	if (name[0] != '/') {
30035388Smjacob		errno = ENOENT;
30135388Smjacob		return -1;
30235388Smjacob	}
303155704Smjacob	name++;
304155704Smjacob	strcpy(path, SEM_PREFIX);
30535388Smjacob	if (strlcat(path, name, sizeof(path)) >= sizeof(path)) {
30635388Smjacob		errno = ENAMETOOLONG;
30779237Smjacob		return (-1);
308155704Smjacob	}
309155704Smjacob
310155704Smjacob	return (unlink(path));
31179237Smjacob}
31279237Smjacob
31387635Smjacobint
31487635Smjacob_sem_destroy(sem_t *sem)
31579237Smjacob{
316155704Smjacob
317155704Smjacob	if (sem_check_validity(sem) != 0)
318155704Smjacob		return (-1);
31979237Smjacob
32079237Smjacob	if (sem->_kern._flags & SEM_NAMED) {
32179237Smjacob		errno = EINVAL;
32235388Smjacob		return (-1);
323155704Smjacob	}
324155704Smjacob	sem->_magic = 0;
325155704Smjacob	return (0);
326155704Smjacob}
32745283Smjacob
32835388Smjacobint
32935388Smjacob_sem_getvalue(sem_t * __restrict sem, int * __restrict sval)
33035388Smjacob{
33135388Smjacob
33235388Smjacob	if (sem_check_validity(sem) != 0)
33335388Smjacob		return (-1);
334196008Smjacob
335196008Smjacob	*sval = (int)sem->_kern._count;
336196008Smjacob	return (0);
33735388Smjacob}
33835388Smjacob
33939235Sgibbsstatic __inline int
34039235Sgibbsusem_wake(struct _usem *sem)
34139235Sgibbs{
34239235Sgibbs	return _umtx_op(sem, UMTX_OP_SEM_WAKE, 0, NULL, NULL);
34339235Sgibbs}
34439235Sgibbs
34555363Smjacobstatic __inline int
34639235Sgibbsusem_wait(struct _usem *sem, const struct timespec *abstime)
34739235Sgibbs{
34839235Sgibbs	struct _umtx_time *tm_p, timeout;
34939235Sgibbs	size_t tm_size;
35039235Sgibbs
35139235Sgibbs	if (abstime == NULL) {
35239235Sgibbs		tm_p = NULL;
35339235Sgibbs		tm_size = 0;
35439235Sgibbs	} else {
35539235Sgibbs		timeout._clockid = CLOCK_REALTIME;
356163899Smjacob		timeout._flags = UMTX_ABSTIME;
35779237Smjacob		timeout._timeout = *abstime;
358163899Smjacob		tm_p = &timeout;
35939235Sgibbs		tm_size = sizeof(timeout);
36079237Smjacob	}
36179237Smjacob	return _umtx_op(sem, UMTX_OP_SEM_WAIT, 0,
362163899Smjacob		    (void *)tm_size, __DECONST(void*, tm_p));
36339235Sgibbs}
36479237Smjacob
36579237Smjacobint
36679237Smjacob_sem_trywait(sem_t *sem)
36779237Smjacob{
36879237Smjacob	int val;
36979237Smjacob
37079237Smjacob	if (sem_check_validity(sem) != 0)
371163899Smjacob		return (-1);
372164370Smjacob
373196008Smjacob	while ((val = sem->_kern._count) > 0) {
374196008Smjacob		if (atomic_cmpset_acq_int(&sem->_kern._count, val, val - 1))
375196008Smjacob			return (0);
376163899Smjacob	}
377163899Smjacob	errno = EAGAIN;
378163899Smjacob	return (-1);
379163899Smjacob}
380163899Smjacob
38135388Smjacobint
38239235Sgibbs_sem_timedwait(sem_t * __restrict sem,
38335388Smjacob	const struct timespec * __restrict abstime)
38435388Smjacob{
38535388Smjacob	int val, retval;
386155704Smjacob
387155704Smjacob	if (sem_check_validity(sem) != 0)
388155704Smjacob		return (-1);
389155704Smjacob
390155704Smjacob	retval = 0;
391155704Smjacob	_pthread_testcancel();
392155704Smjacob	for (;;) {
393155704Smjacob		while ((val = sem->_kern._count) > 0) {
394155704Smjacob			if (atomic_cmpset_acq_int(&sem->_kern._count, val, val - 1))
39535388Smjacob				return (0);
39635388Smjacob		}
397103825Smjacob
398103825Smjacob		if (retval) {
399163899Smjacob			_pthread_testcancel();
400163899Smjacob			break;
401163899Smjacob		}
402163899Smjacob
403163899Smjacob		/*
404163899Smjacob		 * The timeout argument is only supposed to
405163899Smjacob		 * be checked if the thread would have blocked.
406163899Smjacob		 */
407163899Smjacob		if (abstime != NULL) {
408163899Smjacob			if (abstime->tv_nsec >= 1000000000 || abstime->tv_nsec < 0) {
409163899Smjacob				errno = EINVAL;
410163899Smjacob				return (-1);
411163899Smjacob			}
412163899Smjacob		}
413163899Smjacob		_pthread_cancel_enter(1);
414163899Smjacob		retval = usem_wait(&sem->_kern, abstime);
415163899Smjacob		_pthread_cancel_leave(0);
416163899Smjacob	}
417163899Smjacob	return (retval);
418163899Smjacob}
419163899Smjacob
420163899Smjacobint
421163899Smjacob_sem_wait(sem_t *sem)
422163899Smjacob{
423163899Smjacob	return _sem_timedwait(sem, NULL);
42445283Smjacob}
42545283Smjacob
42645283Smjacob/*
427154704Smjacob * POSIX:
42845283Smjacob * The sem_post() interface is reentrant with respect to signals and may be
42982689Smjacob * invoked from a signal-catching function.
43035388Smjacob * The implementation does not use lock, so it should be safe.
43135388Smjacob */
432155704Smjacobint
433155704Smjacob_sem_post(sem_t *sem)
434155704Smjacob{
435155704Smjacob	unsigned int count;
436155704Smjacob
437238869Smjacob	if (sem_check_validity(sem) != 0)
438238869Smjacob		return (-1);
439155704Smjacob
440155704Smjacob	do {
441155704Smjacob		count = sem->_kern._count;
442155704Smjacob		if (count + 1 > SEM_VALUE_MAX)
44335388Smjacob			return (EOVERFLOW);
44435388Smjacob	} while(!atomic_cmpset_rel_int(&sem->_kern._count, count, count+1));
44535388Smjacob	(void)usem_wake(&sem->_kern);
446154704Smjacob	return (0);
447154704Smjacob}
448155704Smjacob