1201546Sdavidxu/*
2201546Sdavidxu * Copyright (C) 2010 David Xu <davidxu@freebsd.org>.
3201546Sdavidxu * All rights reserved.
4201546Sdavidxu *
5201546Sdavidxu * Redistribution and use in source and binary forms, with or without
6201546Sdavidxu * modification, are permitted provided that the following conditions
7201546Sdavidxu * are met:
8201546Sdavidxu * 1. Redistributions of source code must retain the above copyright
9201546Sdavidxu *    notice(s), this list of conditions and the following disclaimer as
10201546Sdavidxu *    the first lines of this file unmodified other than the possible
11201546Sdavidxu *    addition of one or more copyright notices.
12201546Sdavidxu * 2. Redistributions in binary form must reproduce the above copyright
13201546Sdavidxu *    notice(s), this list of conditions and the following disclaimer in
14201546Sdavidxu *    the documentation and/or other materials provided with the
15201546Sdavidxu *    distribution.
16201546Sdavidxu *
17201546Sdavidxu * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY
18201546Sdavidxu * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19201546Sdavidxu * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20201546Sdavidxu * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) BE
21201546Sdavidxu * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22201546Sdavidxu * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23201546Sdavidxu * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
24201546Sdavidxu * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25201546Sdavidxu * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
26201546Sdavidxu * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
27201546Sdavidxu * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28201546Sdavidxu *
29201546Sdavidxu * $FreeBSD$
30201546Sdavidxu */
31201546Sdavidxu
32201546Sdavidxu#include "namespace.h"
33201546Sdavidxu#include <sys/types.h>
34201546Sdavidxu#include <sys/queue.h>
35201546Sdavidxu#include <sys/mman.h>
36201546Sdavidxu#include <sys/stat.h>
37201546Sdavidxu#include <errno.h>
38201546Sdavidxu#include <machine/atomic.h>
39201546Sdavidxu#include <sys/umtx.h>
40201546Sdavidxu#include <limits.h>
41201546Sdavidxu#include <fcntl.h>
42201546Sdavidxu#include <pthread.h>
43201546Sdavidxu#include <stdarg.h>
44201546Sdavidxu#include <stdlib.h>
45201546Sdavidxu#include <string.h>
46201546Sdavidxu#include <time.h>
47201546Sdavidxu#include <semaphore.h>
48201546Sdavidxu#include <unistd.h>
49201546Sdavidxu#include "un-namespace.h"
50213153Sdavidxu#include "libc_private.h"
51201546Sdavidxu
52201561Sdavidxu__weak_reference(_sem_close, sem_close);
53201561Sdavidxu__weak_reference(_sem_destroy, sem_destroy);
54201561Sdavidxu__weak_reference(_sem_getvalue, sem_getvalue);
55201561Sdavidxu__weak_reference(_sem_init, sem_init);
56201561Sdavidxu__weak_reference(_sem_open, sem_open);
57201561Sdavidxu__weak_reference(_sem_post, sem_post);
58201561Sdavidxu__weak_reference(_sem_timedwait, sem_timedwait);
59201561Sdavidxu__weak_reference(_sem_trywait, sem_trywait);
60201561Sdavidxu__weak_reference(_sem_unlink, sem_unlink);
61201561Sdavidxu__weak_reference(_sem_wait, sem_wait);
62201546Sdavidxu
63201546Sdavidxu#define SEM_PREFIX	"/tmp/SEMD"
64233263Sdavidxu#define SEM_MAGIC	((u_int32_t)0x73656d31)
65201546Sdavidxu
66201546Sdavidxustruct sem_nameinfo {
67201546Sdavidxu	int open_count;
68201546Sdavidxu	char *name;
69201546Sdavidxu	sem_t *sem;
70201546Sdavidxu	LIST_ENTRY(sem_nameinfo) next;
71201546Sdavidxu};
72201546Sdavidxu
73201546Sdavidxustatic pthread_once_t once = PTHREAD_ONCE_INIT;
74201546Sdavidxustatic pthread_mutex_t sem_llock;
75201546Sdavidxustatic LIST_HEAD(,sem_nameinfo) sem_list = LIST_HEAD_INITIALIZER(sem_list);
76201546Sdavidxu
77201546Sdavidxustatic void
78201546Sdavidxusem_prefork()
79201546Sdavidxu{
80201546Sdavidxu
81201546Sdavidxu	_pthread_mutex_lock(&sem_llock);
82201546Sdavidxu}
83201546Sdavidxu
84201546Sdavidxustatic void
85201546Sdavidxusem_postfork()
86201546Sdavidxu{
87201546Sdavidxu	_pthread_mutex_unlock(&sem_llock);
88201546Sdavidxu}
89201546Sdavidxu
90201546Sdavidxustatic void
91201546Sdavidxusem_child_postfork()
92201546Sdavidxu{
93201546Sdavidxu	_pthread_mutex_unlock(&sem_llock);
94201546Sdavidxu}
95201546Sdavidxu
96201546Sdavidxustatic void
97201546Sdavidxusem_module_init(void)
98201546Sdavidxu{
99201546Sdavidxu	pthread_mutexattr_t ma;
100201546Sdavidxu
101201546Sdavidxu	_pthread_mutexattr_init(&ma);
102201546Sdavidxu	_pthread_mutexattr_settype(&ma,  PTHREAD_MUTEX_RECURSIVE);
103201546Sdavidxu	_pthread_mutex_init(&sem_llock, &ma);
104201546Sdavidxu	_pthread_mutexattr_destroy(&ma);
105201546Sdavidxu	_pthread_atfork(sem_prefork, sem_postfork, sem_child_postfork);
106201546Sdavidxu}
107201546Sdavidxu
108201546Sdavidxustatic inline int
109201546Sdavidxusem_check_validity(sem_t *sem)
110201546Sdavidxu{
111201546Sdavidxu
112233263Sdavidxu	if (sem->_magic == SEM_MAGIC)
113201546Sdavidxu		return (0);
114201546Sdavidxu	else {
115201546Sdavidxu		errno = EINVAL;
116201546Sdavidxu		return (-1);
117201546Sdavidxu	}
118201546Sdavidxu}
119201546Sdavidxu
120201546Sdavidxuint
121201561Sdavidxu_sem_init(sem_t *sem, int pshared, unsigned int value)
122201546Sdavidxu{
123201546Sdavidxu
124201546Sdavidxu	if (value > SEM_VALUE_MAX) {
125201546Sdavidxu		errno = EINVAL;
126201546Sdavidxu		return (-1);
127201546Sdavidxu	}
128201546Sdavidxu
129201546Sdavidxu	bzero(sem, sizeof(sem_t));
130201546Sdavidxu	sem->_magic = SEM_MAGIC;
131201546Sdavidxu	sem->_kern._count = (u_int32_t)value;
132201546Sdavidxu	sem->_kern._has_waiters = 0;
133233263Sdavidxu	sem->_kern._flags = pshared ? USYNC_PROCESS_SHARED : 0;
134201546Sdavidxu	return (0);
135201546Sdavidxu}
136201546Sdavidxu
137201546Sdavidxusem_t *
138201561Sdavidxu_sem_open(const char *name, int flags, ...)
139201546Sdavidxu{
140201546Sdavidxu	char path[PATH_MAX];
141201546Sdavidxu
142201546Sdavidxu	struct stat sb;
143201546Sdavidxu	va_list ap;
144201546Sdavidxu	struct sem_nameinfo *ni = NULL;
145201546Sdavidxu	sem_t *sem = NULL;
146202557Sdavidxu	int fd = -1, mode, len, errsave;
147201715Sdavidxu	int value = 0;
148201546Sdavidxu
149201546Sdavidxu	if (name[0] != '/') {
150201546Sdavidxu		errno = EINVAL;
151202185Sdavidxu		return (SEM_FAILED);
152201546Sdavidxu	}
153201546Sdavidxu	name++;
154201546Sdavidxu
155201546Sdavidxu	if (flags & ~(O_CREAT|O_EXCL)) {
156201546Sdavidxu		errno = EINVAL;
157202185Sdavidxu		return (SEM_FAILED);
158201546Sdavidxu	}
159201546Sdavidxu
160201546Sdavidxu	_pthread_once(&once, sem_module_init);
161201546Sdavidxu
162201546Sdavidxu	_pthread_mutex_lock(&sem_llock);
163201546Sdavidxu	LIST_FOREACH(ni, &sem_list, next) {
164201546Sdavidxu		if (strcmp(name, ni->name) == 0) {
165234057Sjilles			if ((flags & (O_CREAT|O_EXCL)) == (O_CREAT|O_EXCL)) {
166234057Sjilles				_pthread_mutex_unlock(&sem_llock);
167234057Sjilles				errno = EEXIST;
168234057Sjilles				return (SEM_FAILED);
169234057Sjilles			} else {
170234057Sjilles				ni->open_count++;
171234057Sjilles				sem = ni->sem;
172234057Sjilles				_pthread_mutex_unlock(&sem_llock);
173234057Sjilles				return (sem);
174234057Sjilles			}
175201546Sdavidxu		}
176201546Sdavidxu	}
177201546Sdavidxu
178201546Sdavidxu	if (flags & O_CREAT) {
179201546Sdavidxu		va_start(ap, flags);
180201546Sdavidxu		mode = va_arg(ap, int);
181201715Sdavidxu		value = va_arg(ap, int);
182201546Sdavidxu		va_end(ap);
183201546Sdavidxu	}
184201546Sdavidxu
185201546Sdavidxu	len = sizeof(*ni) + strlen(name) + 1;
186201546Sdavidxu	ni = (struct sem_nameinfo *)malloc(len);
187201546Sdavidxu	if (ni == NULL) {
188201546Sdavidxu		errno = ENOSPC;
189201546Sdavidxu		goto error;
190201546Sdavidxu	}
191201546Sdavidxu
192201546Sdavidxu	ni->name = (char *)(ni+1);
193201546Sdavidxu	strcpy(ni->name, name);
194201546Sdavidxu
195201546Sdavidxu	strcpy(path, SEM_PREFIX);
196201546Sdavidxu	if (strlcat(path, name, sizeof(path)) >= sizeof(path)) {
197201546Sdavidxu		errno = ENAMETOOLONG;
198201546Sdavidxu		goto error;
199201546Sdavidxu	}
200201546Sdavidxu
201246872Sdavidxu	fd = _open(path, flags|O_RDWR|O_CLOEXEC|O_EXLOCK, mode);
202201546Sdavidxu	if (fd == -1)
203201546Sdavidxu		goto error;
204246872Sdavidxu	if (_fstat(fd, &sb))
205201546Sdavidxu		goto error;
206201546Sdavidxu	if (sb.st_size < sizeof(sem_t)) {
207201546Sdavidxu		sem_t tmp;
208201546Sdavidxu
209201546Sdavidxu		tmp._magic = SEM_MAGIC;
210201546Sdavidxu		tmp._kern._has_waiters = 0;
211201715Sdavidxu		tmp._kern._count = value;
212233263Sdavidxu		tmp._kern._flags = USYNC_PROCESS_SHARED | SEM_NAMED;
213246872Sdavidxu		if (_write(fd, &tmp, sizeof(tmp)) != sizeof(tmp))
214201546Sdavidxu			goto error;
215201546Sdavidxu	}
216201546Sdavidxu	flock(fd, LOCK_UN);
217201546Sdavidxu	sem = (sem_t *)mmap(NULL, sizeof(sem_t), PROT_READ|PROT_WRITE,
218201546Sdavidxu		MAP_SHARED|MAP_NOSYNC, fd, 0);
219201546Sdavidxu	if (sem == MAP_FAILED) {
220201546Sdavidxu		sem = NULL;
221201546Sdavidxu		if (errno == ENOMEM)
222201546Sdavidxu			errno = ENOSPC;
223201546Sdavidxu		goto error;
224201546Sdavidxu	}
225201546Sdavidxu	if (sem->_magic != SEM_MAGIC) {
226201546Sdavidxu		errno = EINVAL;
227201546Sdavidxu		goto error;
228201546Sdavidxu	}
229201546Sdavidxu	ni->open_count = 1;
230201546Sdavidxu	ni->sem = sem;
231201546Sdavidxu	LIST_INSERT_HEAD(&sem_list, ni, next);
232246894Sdavidxu	_close(fd);
233201546Sdavidxu	_pthread_mutex_unlock(&sem_llock);
234201546Sdavidxu	return (sem);
235201546Sdavidxu
236201546Sdavidxuerror:
237202557Sdavidxu	errsave = errno;
238201546Sdavidxu	if (fd != -1)
239201546Sdavidxu		_close(fd);
240201546Sdavidxu	if (sem != NULL)
241201546Sdavidxu		munmap(sem, sizeof(sem_t));
242201546Sdavidxu	free(ni);
243246894Sdavidxu	_pthread_mutex_unlock(&sem_llock);
244202557Sdavidxu	errno = errsave;
245201546Sdavidxu	return (SEM_FAILED);
246201546Sdavidxu}
247201546Sdavidxu
248201546Sdavidxuint
249201561Sdavidxu_sem_close(sem_t *sem)
250201546Sdavidxu{
251201546Sdavidxu	struct sem_nameinfo *ni;
252201546Sdavidxu
253201546Sdavidxu	if (sem_check_validity(sem) != 0)
254201546Sdavidxu		return (-1);
255201546Sdavidxu
256201546Sdavidxu	if (!(sem->_kern._flags & SEM_NAMED)) {
257201546Sdavidxu		errno = EINVAL;
258201546Sdavidxu		return (-1);
259201546Sdavidxu	}
260201546Sdavidxu
261202326Sdavidxu	_pthread_once(&once, sem_module_init);
262202326Sdavidxu
263201546Sdavidxu	_pthread_mutex_lock(&sem_llock);
264201546Sdavidxu	LIST_FOREACH(ni, &sem_list, next) {
265201546Sdavidxu		if (sem == ni->sem) {
266201546Sdavidxu			if (--ni->open_count > 0) {
267201546Sdavidxu				_pthread_mutex_unlock(&sem_llock);
268201546Sdavidxu				return (0);
269201546Sdavidxu			}
270201546Sdavidxu			else
271201546Sdavidxu				break;
272201546Sdavidxu		}
273201546Sdavidxu	}
274201546Sdavidxu
275201546Sdavidxu	if (ni) {
276201546Sdavidxu		LIST_REMOVE(ni, next);
277201546Sdavidxu		_pthread_mutex_unlock(&sem_llock);
278201546Sdavidxu		munmap(sem, sizeof(*sem));
279201546Sdavidxu		free(ni);
280201546Sdavidxu		return (0);
281201546Sdavidxu	}
282201546Sdavidxu	_pthread_mutex_unlock(&sem_llock);
283202185Sdavidxu	errno = EINVAL;
284201546Sdavidxu	return (-1);
285201546Sdavidxu}
286201546Sdavidxu
287201546Sdavidxuint
288201561Sdavidxu_sem_unlink(const char *name)
289201546Sdavidxu{
290201546Sdavidxu	char path[PATH_MAX];
291201546Sdavidxu
292201546Sdavidxu	if (name[0] != '/') {
293201546Sdavidxu		errno = ENOENT;
294201546Sdavidxu		return -1;
295201546Sdavidxu	}
296201546Sdavidxu	name++;
297201546Sdavidxu
298201546Sdavidxu	strcpy(path, SEM_PREFIX);
299201546Sdavidxu	if (strlcat(path, name, sizeof(path)) >= sizeof(path)) {
300201546Sdavidxu		errno = ENAMETOOLONG;
301201546Sdavidxu		return (-1);
302201546Sdavidxu	}
303201546Sdavidxu	return unlink(path);
304201546Sdavidxu}
305201546Sdavidxu
306201546Sdavidxuint
307201561Sdavidxu_sem_destroy(sem_t *sem)
308201546Sdavidxu{
309201546Sdavidxu
310201546Sdavidxu	if (sem_check_validity(sem) != 0)
311201546Sdavidxu		return (-1);
312201546Sdavidxu
313201546Sdavidxu	if (sem->_kern._flags & SEM_NAMED) {
314201546Sdavidxu		errno = EINVAL;
315201546Sdavidxu		return (-1);
316201546Sdavidxu	}
317201546Sdavidxu	sem->_magic = 0;
318201546Sdavidxu	return (0);
319201546Sdavidxu}
320201546Sdavidxu
321201546Sdavidxuint
322201561Sdavidxu_sem_getvalue(sem_t * __restrict sem, int * __restrict sval)
323201546Sdavidxu{
324201546Sdavidxu
325201546Sdavidxu	if (sem_check_validity(sem) != 0)
326201546Sdavidxu		return (-1);
327201546Sdavidxu
328233263Sdavidxu	*sval = (int)sem->_kern._count;
329201546Sdavidxu	return (0);
330201546Sdavidxu}
331201546Sdavidxu
332201547Sdavidxustatic __inline int
333233263Sdavidxuusem_wake(struct _usem *sem)
334233263Sdavidxu{
335233263Sdavidxu	return _umtx_op(sem, UMTX_OP_SEM_WAKE, 0, NULL, NULL);
336233263Sdavidxu}
337233263Sdavidxu
338233263Sdavidxustatic __inline int
339232144Sdavidxuusem_wait(struct _usem *sem, const struct timespec *abstime)
340201546Sdavidxu{
341232144Sdavidxu	struct _umtx_time *tm_p, timeout;
342232144Sdavidxu	size_t tm_size;
343232144Sdavidxu
344232144Sdavidxu	if (abstime == NULL) {
345232144Sdavidxu		tm_p = NULL;
346232144Sdavidxu		tm_size = 0;
347232144Sdavidxu	} else {
348232144Sdavidxu		timeout._clockid = CLOCK_REALTIME;
349232144Sdavidxu		timeout._flags = UMTX_ABSTIME;
350232144Sdavidxu		timeout._timeout = *abstime;
351232144Sdavidxu		tm_p = &timeout;
352232144Sdavidxu		tm_size = sizeof(timeout);
353201546Sdavidxu	}
354232144Sdavidxu	return _umtx_op(sem, UMTX_OP_SEM_WAIT, 0,
355232144Sdavidxu		    (void *)tm_size, __DECONST(void*, tm_p));
356201546Sdavidxu}
357201546Sdavidxu
358201546Sdavidxuint
359201561Sdavidxu_sem_trywait(sem_t *sem)
360201546Sdavidxu{
361233263Sdavidxu	int val;
362201546Sdavidxu
363201546Sdavidxu	if (sem_check_validity(sem) != 0)
364201546Sdavidxu		return (-1);
365233263Sdavidxu
366233263Sdavidxu	while ((val = sem->_kern._count) > 0) {
367233263Sdavidxu		if (atomic_cmpset_acq_int(&sem->_kern._count, val, val - 1))
368233263Sdavidxu			return (0);
369233263Sdavidxu	}
370233263Sdavidxu	errno = EAGAIN;
371201546Sdavidxu	return (-1);
372201546Sdavidxu}
373201546Sdavidxu
374201546Sdavidxuint
375201561Sdavidxu_sem_timedwait(sem_t * __restrict sem,
376201546Sdavidxu	const struct timespec * __restrict abstime)
377201546Sdavidxu{
378233263Sdavidxu	int val, retval;
379201546Sdavidxu
380201546Sdavidxu	if (sem_check_validity(sem) != 0)
381201546Sdavidxu		return (-1);
382201546Sdavidxu
383201546Sdavidxu	retval = 0;
384201546Sdavidxu	for (;;) {
385233263Sdavidxu		while ((val = sem->_kern._count) > 0) {
386233263Sdavidxu			if (atomic_cmpset_acq_int(&sem->_kern._count, val, val - 1))
387233263Sdavidxu				return (0);
388233263Sdavidxu		}
389201546Sdavidxu
390213153Sdavidxu		if (retval) {
391213153Sdavidxu			_pthread_testcancel();
392201546Sdavidxu			break;
393213153Sdavidxu		}
394201546Sdavidxu
395201546Sdavidxu		/*
396201546Sdavidxu		 * The timeout argument is only supposed to
397201546Sdavidxu		 * be checked if the thread would have blocked.
398201546Sdavidxu		 */
399201546Sdavidxu		if (abstime != NULL) {
400201546Sdavidxu			if (abstime->tv_nsec >= 1000000000 || abstime->tv_nsec < 0) {
401201546Sdavidxu				errno = EINVAL;
402201546Sdavidxu				return (-1);
403201546Sdavidxu			}
404201546Sdavidxu		}
405213153Sdavidxu		_pthread_cancel_enter(1);
406232144Sdavidxu		retval = usem_wait(&sem->_kern, abstime);
407213153Sdavidxu		_pthread_cancel_leave(0);
408201546Sdavidxu	}
409201546Sdavidxu	return (retval);
410201546Sdavidxu}
411201546Sdavidxu
412201546Sdavidxuint
413201561Sdavidxu_sem_wait(sem_t *sem)
414201546Sdavidxu{
415201561Sdavidxu	return _sem_timedwait(sem, NULL);
416201546Sdavidxu}
417201546Sdavidxu
418201546Sdavidxu/*
419201546Sdavidxu * POSIX:
420201546Sdavidxu * The sem_post() interface is reentrant with respect to signals and may be
421201546Sdavidxu * invoked from a signal-catching function.
422201546Sdavidxu * The implementation does not use lock, so it should be safe.
423201546Sdavidxu */
424201546Sdavidxuint
425201561Sdavidxu_sem_post(sem_t *sem)
426201546Sdavidxu{
427233913Sdavidxu	unsigned int count;
428201546Sdavidxu
429201546Sdavidxu	if (sem_check_validity(sem) != 0)
430201546Sdavidxu		return (-1);
431201546Sdavidxu
432233913Sdavidxu	do {
433233913Sdavidxu		count = sem->_kern._count;
434233913Sdavidxu		if (count + 1 > SEM_VALUE_MAX)
435233913Sdavidxu			return (EOVERFLOW);
436233913Sdavidxu	} while(!atomic_cmpset_rel_int(&sem->_kern._count, count, count+1));
437233913Sdavidxu	(void)usem_wake(&sem->_kern);
438233913Sdavidxu	return (0);
439201546Sdavidxu}
440