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;
69266306Skib	dev_t dev;
70266306Skib	ino_t ino;
71201546Sdavidxu	sem_t *sem;
72201546Sdavidxu	LIST_ENTRY(sem_nameinfo) next;
73201546Sdavidxu};
74201546Sdavidxu
75201546Sdavidxustatic pthread_once_t once = PTHREAD_ONCE_INIT;
76201546Sdavidxustatic pthread_mutex_t sem_llock;
77201546Sdavidxustatic LIST_HEAD(,sem_nameinfo) sem_list = LIST_HEAD_INITIALIZER(sem_list);
78201546Sdavidxu
79201546Sdavidxustatic void
80201546Sdavidxusem_prefork()
81201546Sdavidxu{
82201546Sdavidxu
83201546Sdavidxu	_pthread_mutex_lock(&sem_llock);
84201546Sdavidxu}
85201546Sdavidxu
86201546Sdavidxustatic void
87201546Sdavidxusem_postfork()
88201546Sdavidxu{
89201546Sdavidxu	_pthread_mutex_unlock(&sem_llock);
90201546Sdavidxu}
91201546Sdavidxu
92201546Sdavidxustatic void
93201546Sdavidxusem_child_postfork()
94201546Sdavidxu{
95201546Sdavidxu	_pthread_mutex_unlock(&sem_llock);
96201546Sdavidxu}
97201546Sdavidxu
98201546Sdavidxustatic void
99201546Sdavidxusem_module_init(void)
100201546Sdavidxu{
101201546Sdavidxu	pthread_mutexattr_t ma;
102201546Sdavidxu
103201546Sdavidxu	_pthread_mutexattr_init(&ma);
104201546Sdavidxu	_pthread_mutexattr_settype(&ma,  PTHREAD_MUTEX_RECURSIVE);
105201546Sdavidxu	_pthread_mutex_init(&sem_llock, &ma);
106201546Sdavidxu	_pthread_mutexattr_destroy(&ma);
107201546Sdavidxu	_pthread_atfork(sem_prefork, sem_postfork, sem_child_postfork);
108201546Sdavidxu}
109201546Sdavidxu
110201546Sdavidxustatic inline int
111201546Sdavidxusem_check_validity(sem_t *sem)
112201546Sdavidxu{
113201546Sdavidxu
114233263Sdavidxu	if (sem->_magic == SEM_MAGIC)
115201546Sdavidxu		return (0);
116201546Sdavidxu	else {
117201546Sdavidxu		errno = EINVAL;
118201546Sdavidxu		return (-1);
119201546Sdavidxu	}
120201546Sdavidxu}
121201546Sdavidxu
122201546Sdavidxuint
123201561Sdavidxu_sem_init(sem_t *sem, int pshared, unsigned int value)
124201546Sdavidxu{
125201546Sdavidxu
126201546Sdavidxu	if (value > SEM_VALUE_MAX) {
127201546Sdavidxu		errno = EINVAL;
128201546Sdavidxu		return (-1);
129201546Sdavidxu	}
130201546Sdavidxu
131201546Sdavidxu	bzero(sem, sizeof(sem_t));
132201546Sdavidxu	sem->_magic = SEM_MAGIC;
133201546Sdavidxu	sem->_kern._count = (u_int32_t)value;
134201546Sdavidxu	sem->_kern._has_waiters = 0;
135233263Sdavidxu	sem->_kern._flags = pshared ? USYNC_PROCESS_SHARED : 0;
136201546Sdavidxu	return (0);
137201546Sdavidxu}
138201546Sdavidxu
139201546Sdavidxusem_t *
140201561Sdavidxu_sem_open(const char *name, int flags, ...)
141201546Sdavidxu{
142201546Sdavidxu	char path[PATH_MAX];
143201546Sdavidxu
144201546Sdavidxu	struct stat sb;
145201546Sdavidxu	va_list ap;
146201546Sdavidxu	struct sem_nameinfo *ni = NULL;
147201546Sdavidxu	sem_t *sem = NULL;
148202557Sdavidxu	int fd = -1, mode, len, errsave;
149201715Sdavidxu	int value = 0;
150201546Sdavidxu
151201546Sdavidxu	if (name[0] != '/') {
152201546Sdavidxu		errno = EINVAL;
153202185Sdavidxu		return (SEM_FAILED);
154201546Sdavidxu	}
155201546Sdavidxu	name++;
156266306Skib	strcpy(path, SEM_PREFIX);
157266306Skib	if (strlcat(path, name, sizeof(path)) >= sizeof(path)) {
158266306Skib		errno = ENAMETOOLONG;
159266306Skib		return (SEM_FAILED);
160266306Skib	}
161201546Sdavidxu	if (flags & ~(O_CREAT|O_EXCL)) {
162201546Sdavidxu		errno = EINVAL;
163202185Sdavidxu		return (SEM_FAILED);
164201546Sdavidxu	}
165266306Skib	if ((flags & O_CREAT) != 0) {
166266306Skib		va_start(ap, flags);
167266306Skib		mode = va_arg(ap, int);
168266306Skib		value = va_arg(ap, int);
169266306Skib		va_end(ap);
170266306Skib	}
171266306Skib	fd = -1;
172201546Sdavidxu	_pthread_once(&once, sem_module_init);
173201546Sdavidxu
174201546Sdavidxu	_pthread_mutex_lock(&sem_llock);
175201546Sdavidxu	LIST_FOREACH(ni, &sem_list, next) {
176266306Skib		if (ni->name != NULL && strcmp(name, ni->name) == 0) {
177266306Skib			fd = _open(path, flags | O_RDWR | O_CLOEXEC |
178266306Skib			    O_EXLOCK, mode);
179266306Skib			if (fd == -1 || _fstat(fd, &sb) == -1)
180266306Skib				goto error;
181266306Skib			if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT |
182266306Skib			    O_EXCL) || ni->dev != sb.st_dev ||
183266306Skib			    ni->ino != sb.st_ino) {
184266306Skib				ni->name = NULL;
185266306Skib				ni = NULL;
186266306Skib				break;
187234057Sjilles			}
188266306Skib			ni->open_count++;
189266306Skib			sem = ni->sem;
190266306Skib			_pthread_mutex_unlock(&sem_llock);
191266306Skib			_close(fd);
192266306Skib			return (sem);
193201546Sdavidxu		}
194201546Sdavidxu	}
195201546Sdavidxu
196201546Sdavidxu	len = sizeof(*ni) + strlen(name) + 1;
197201546Sdavidxu	ni = (struct sem_nameinfo *)malloc(len);
198201546Sdavidxu	if (ni == NULL) {
199201546Sdavidxu		errno = ENOSPC;
200201546Sdavidxu		goto error;
201201546Sdavidxu	}
202201546Sdavidxu
203201546Sdavidxu	ni->name = (char *)(ni+1);
204201546Sdavidxu	strcpy(ni->name, name);
205201546Sdavidxu
206266306Skib	if (fd == -1) {
207266306Skib		fd = _open(path, flags | O_RDWR | O_CLOEXEC | O_EXLOCK, mode);
208266306Skib		if (fd == -1 || _fstat(fd, &sb) == -1)
209266306Skib			goto error;
210201546Sdavidxu	}
211201546Sdavidxu	if (sb.st_size < sizeof(sem_t)) {
212201546Sdavidxu		sem_t tmp;
213201546Sdavidxu
214201546Sdavidxu		tmp._magic = SEM_MAGIC;
215201546Sdavidxu		tmp._kern._has_waiters = 0;
216201715Sdavidxu		tmp._kern._count = value;
217233263Sdavidxu		tmp._kern._flags = USYNC_PROCESS_SHARED | SEM_NAMED;
218246872Sdavidxu		if (_write(fd, &tmp, sizeof(tmp)) != sizeof(tmp))
219201546Sdavidxu			goto error;
220201546Sdavidxu	}
221201546Sdavidxu	flock(fd, LOCK_UN);
222201546Sdavidxu	sem = (sem_t *)mmap(NULL, sizeof(sem_t), PROT_READ|PROT_WRITE,
223201546Sdavidxu		MAP_SHARED|MAP_NOSYNC, fd, 0);
224201546Sdavidxu	if (sem == MAP_FAILED) {
225201546Sdavidxu		sem = NULL;
226201546Sdavidxu		if (errno == ENOMEM)
227201546Sdavidxu			errno = ENOSPC;
228201546Sdavidxu		goto error;
229201546Sdavidxu	}
230201546Sdavidxu	if (sem->_magic != SEM_MAGIC) {
231201546Sdavidxu		errno = EINVAL;
232201546Sdavidxu		goto error;
233201546Sdavidxu	}
234201546Sdavidxu	ni->open_count = 1;
235201546Sdavidxu	ni->sem = sem;
236266306Skib	ni->dev = sb.st_dev;
237266306Skib	ni->ino = sb.st_ino;
238201546Sdavidxu	LIST_INSERT_HEAD(&sem_list, ni, next);
239246894Sdavidxu	_close(fd);
240201546Sdavidxu	_pthread_mutex_unlock(&sem_llock);
241201546Sdavidxu	return (sem);
242201546Sdavidxu
243201546Sdavidxuerror:
244202557Sdavidxu	errsave = errno;
245201546Sdavidxu	if (fd != -1)
246201546Sdavidxu		_close(fd);
247201546Sdavidxu	if (sem != NULL)
248201546Sdavidxu		munmap(sem, sizeof(sem_t));
249201546Sdavidxu	free(ni);
250246894Sdavidxu	_pthread_mutex_unlock(&sem_llock);
251202557Sdavidxu	errno = errsave;
252201546Sdavidxu	return (SEM_FAILED);
253201546Sdavidxu}
254201546Sdavidxu
255201546Sdavidxuint
256201561Sdavidxu_sem_close(sem_t *sem)
257201546Sdavidxu{
258201546Sdavidxu	struct sem_nameinfo *ni;
259201546Sdavidxu
260201546Sdavidxu	if (sem_check_validity(sem) != 0)
261201546Sdavidxu		return (-1);
262201546Sdavidxu
263201546Sdavidxu	if (!(sem->_kern._flags & SEM_NAMED)) {
264201546Sdavidxu		errno = EINVAL;
265201546Sdavidxu		return (-1);
266201546Sdavidxu	}
267201546Sdavidxu
268202326Sdavidxu	_pthread_once(&once, sem_module_init);
269202326Sdavidxu
270201546Sdavidxu	_pthread_mutex_lock(&sem_llock);
271201546Sdavidxu	LIST_FOREACH(ni, &sem_list, next) {
272201546Sdavidxu		if (sem == ni->sem) {
273201546Sdavidxu			if (--ni->open_count > 0) {
274201546Sdavidxu				_pthread_mutex_unlock(&sem_llock);
275201546Sdavidxu				return (0);
276201546Sdavidxu			}
277201546Sdavidxu			else
278201546Sdavidxu				break;
279201546Sdavidxu		}
280201546Sdavidxu	}
281201546Sdavidxu
282201546Sdavidxu	if (ni) {
283201546Sdavidxu		LIST_REMOVE(ni, next);
284201546Sdavidxu		_pthread_mutex_unlock(&sem_llock);
285201546Sdavidxu		munmap(sem, sizeof(*sem));
286201546Sdavidxu		free(ni);
287201546Sdavidxu		return (0);
288201546Sdavidxu	}
289201546Sdavidxu	_pthread_mutex_unlock(&sem_llock);
290202185Sdavidxu	errno = EINVAL;
291201546Sdavidxu	return (-1);
292201546Sdavidxu}
293201546Sdavidxu
294201546Sdavidxuint
295201561Sdavidxu_sem_unlink(const char *name)
296201546Sdavidxu{
297201546Sdavidxu	char path[PATH_MAX];
298201546Sdavidxu
299201546Sdavidxu	if (name[0] != '/') {
300201546Sdavidxu		errno = ENOENT;
301201546Sdavidxu		return -1;
302201546Sdavidxu	}
303201546Sdavidxu	name++;
304201546Sdavidxu	strcpy(path, SEM_PREFIX);
305201546Sdavidxu	if (strlcat(path, name, sizeof(path)) >= sizeof(path)) {
306201546Sdavidxu		errno = ENAMETOOLONG;
307201546Sdavidxu		return (-1);
308201546Sdavidxu	}
309266305Skib
310266305Skib	return (unlink(path));
311201546Sdavidxu}
312201546Sdavidxu
313201546Sdavidxuint
314201561Sdavidxu_sem_destroy(sem_t *sem)
315201546Sdavidxu{
316201546Sdavidxu
317201546Sdavidxu	if (sem_check_validity(sem) != 0)
318201546Sdavidxu		return (-1);
319201546Sdavidxu
320201546Sdavidxu	if (sem->_kern._flags & SEM_NAMED) {
321201546Sdavidxu		errno = EINVAL;
322201546Sdavidxu		return (-1);
323201546Sdavidxu	}
324201546Sdavidxu	sem->_magic = 0;
325201546Sdavidxu	return (0);
326201546Sdavidxu}
327201546Sdavidxu
328201546Sdavidxuint
329201561Sdavidxu_sem_getvalue(sem_t * __restrict sem, int * __restrict sval)
330201546Sdavidxu{
331201546Sdavidxu
332201546Sdavidxu	if (sem_check_validity(sem) != 0)
333201546Sdavidxu		return (-1);
334201546Sdavidxu
335233263Sdavidxu	*sval = (int)sem->_kern._count;
336201546Sdavidxu	return (0);
337201546Sdavidxu}
338201546Sdavidxu
339201547Sdavidxustatic __inline int
340233263Sdavidxuusem_wake(struct _usem *sem)
341233263Sdavidxu{
342233263Sdavidxu	return _umtx_op(sem, UMTX_OP_SEM_WAKE, 0, NULL, NULL);
343233263Sdavidxu}
344233263Sdavidxu
345233263Sdavidxustatic __inline int
346232144Sdavidxuusem_wait(struct _usem *sem, const struct timespec *abstime)
347201546Sdavidxu{
348232144Sdavidxu	struct _umtx_time *tm_p, timeout;
349232144Sdavidxu	size_t tm_size;
350232144Sdavidxu
351232144Sdavidxu	if (abstime == NULL) {
352232144Sdavidxu		tm_p = NULL;
353232144Sdavidxu		tm_size = 0;
354232144Sdavidxu	} else {
355232144Sdavidxu		timeout._clockid = CLOCK_REALTIME;
356232144Sdavidxu		timeout._flags = UMTX_ABSTIME;
357232144Sdavidxu		timeout._timeout = *abstime;
358232144Sdavidxu		tm_p = &timeout;
359232144Sdavidxu		tm_size = sizeof(timeout);
360201546Sdavidxu	}
361232144Sdavidxu	return _umtx_op(sem, UMTX_OP_SEM_WAIT, 0,
362232144Sdavidxu		    (void *)tm_size, __DECONST(void*, tm_p));
363201546Sdavidxu}
364201546Sdavidxu
365201546Sdavidxuint
366201561Sdavidxu_sem_trywait(sem_t *sem)
367201546Sdavidxu{
368233263Sdavidxu	int val;
369201546Sdavidxu
370201546Sdavidxu	if (sem_check_validity(sem) != 0)
371201546Sdavidxu		return (-1);
372233263Sdavidxu
373233263Sdavidxu	while ((val = sem->_kern._count) > 0) {
374233263Sdavidxu		if (atomic_cmpset_acq_int(&sem->_kern._count, val, val - 1))
375233263Sdavidxu			return (0);
376233263Sdavidxu	}
377233263Sdavidxu	errno = EAGAIN;
378201546Sdavidxu	return (-1);
379201546Sdavidxu}
380201546Sdavidxu
381201546Sdavidxuint
382201561Sdavidxu_sem_timedwait(sem_t * __restrict sem,
383201546Sdavidxu	const struct timespec * __restrict abstime)
384201546Sdavidxu{
385233263Sdavidxu	int val, retval;
386201546Sdavidxu
387201546Sdavidxu	if (sem_check_validity(sem) != 0)
388201546Sdavidxu		return (-1);
389201546Sdavidxu
390201546Sdavidxu	retval = 0;
391263258Sdavidxu	_pthread_testcancel();
392201546Sdavidxu	for (;;) {
393233263Sdavidxu		while ((val = sem->_kern._count) > 0) {
394233263Sdavidxu			if (atomic_cmpset_acq_int(&sem->_kern._count, val, val - 1))
395233263Sdavidxu				return (0);
396233263Sdavidxu		}
397201546Sdavidxu
398213153Sdavidxu		if (retval) {
399213153Sdavidxu			_pthread_testcancel();
400201546Sdavidxu			break;
401213153Sdavidxu		}
402201546Sdavidxu
403201546Sdavidxu		/*
404201546Sdavidxu		 * The timeout argument is only supposed to
405201546Sdavidxu		 * be checked if the thread would have blocked.
406201546Sdavidxu		 */
407201546Sdavidxu		if (abstime != NULL) {
408201546Sdavidxu			if (abstime->tv_nsec >= 1000000000 || abstime->tv_nsec < 0) {
409201546Sdavidxu				errno = EINVAL;
410201546Sdavidxu				return (-1);
411201546Sdavidxu			}
412201546Sdavidxu		}
413213153Sdavidxu		_pthread_cancel_enter(1);
414232144Sdavidxu		retval = usem_wait(&sem->_kern, abstime);
415213153Sdavidxu		_pthread_cancel_leave(0);
416201546Sdavidxu	}
417201546Sdavidxu	return (retval);
418201546Sdavidxu}
419201546Sdavidxu
420201546Sdavidxuint
421201561Sdavidxu_sem_wait(sem_t *sem)
422201546Sdavidxu{
423201561Sdavidxu	return _sem_timedwait(sem, NULL);
424201546Sdavidxu}
425201546Sdavidxu
426201546Sdavidxu/*
427201546Sdavidxu * POSIX:
428201546Sdavidxu * The sem_post() interface is reentrant with respect to signals and may be
429201546Sdavidxu * invoked from a signal-catching function.
430201546Sdavidxu * The implementation does not use lock, so it should be safe.
431201546Sdavidxu */
432201546Sdavidxuint
433201561Sdavidxu_sem_post(sem_t *sem)
434201546Sdavidxu{
435233913Sdavidxu	unsigned int count;
436201546Sdavidxu
437201546Sdavidxu	if (sem_check_validity(sem) != 0)
438201546Sdavidxu		return (-1);
439201546Sdavidxu
440233913Sdavidxu	do {
441233913Sdavidxu		count = sem->_kern._count;
442233913Sdavidxu		if (count + 1 > SEM_VALUE_MAX)
443233913Sdavidxu			return (EOVERFLOW);
444233913Sdavidxu	} while(!atomic_cmpset_rel_int(&sem->_kern._count, count, count+1));
445233913Sdavidxu	(void)usem_wake(&sem->_kern);
446233913Sdavidxu	return (0);
447201546Sdavidxu}
448