1/*
2 * Copyright (C) 2000 Jason Evans <jasone@freebsd.org>.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice(s), this list of conditions and the following disclaimer as
10 *    the first lines of this file unmodified other than the possible
11 *    addition of one or more copyright notices.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice(s), this list of conditions and the following disclaimer in
14 *    the documentation and/or other materials provided with the
15 *    distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
24 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
26 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
27 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 *
29 * $FreeBSD: releng/10.3/lib/libkse/thread/thr_sem.c 174689 2007-12-16 23:29:57Z deischen $
30 */
31
32#include "namespace.h"
33#include <sys/types.h>
34#include <sys/queue.h>
35#include <errno.h>
36#include <fcntl.h>
37#include <pthread.h>
38#include <semaphore.h>
39#include <stdlib.h>
40#include <time.h>
41#include <_semaphore.h>
42#include "un-namespace.h"
43#include "libc_private.h"
44#include "thr_private.h"
45
46__weak_reference(_sem_init, sem_init);
47__weak_reference(_sem_wait, sem_wait);
48__weak_reference(_sem_timedwait, sem_timedwait);
49__weak_reference(_sem_post, sem_post);
50
51
52static inline int
53sem_check_validity(sem_t *sem)
54{
55
56	if ((sem != NULL) && ((*sem)->magic == SEM_MAGIC))
57		return (0);
58	else {
59		errno = EINVAL;
60		return (-1);
61	}
62}
63
64static void
65decrease_nwaiters(void *arg)
66{
67	sem_t *sem = (sem_t *)arg;
68
69	(*sem)->nwaiters--;
70	/*
71	 * this function is called from cancellation point,
72	 * the mutex should already be hold.
73	 */
74	_pthread_mutex_unlock(&(*sem)->lock);
75}
76
77static sem_t
78sem_alloc(unsigned int value, semid_t semid, int system_sem)
79{
80	sem_t sem;
81
82	if (value > SEM_VALUE_MAX) {
83		errno = EINVAL;
84		return (NULL);
85	}
86
87	sem = (sem_t)malloc(sizeof(struct sem));
88	if (sem == NULL) {
89		errno = ENOSPC;
90		return (NULL);
91	}
92
93	/*
94	 * Initialize the semaphore.
95	 */
96	if (_pthread_mutex_init(&sem->lock, NULL) != 0) {
97		free(sem);
98		errno = ENOSPC;
99		return (NULL);
100	}
101
102	if (_pthread_cond_init(&sem->gtzero, NULL) != 0) {
103		_pthread_mutex_destroy(&sem->lock);
104		free(sem);
105		errno = ENOSPC;
106		return (NULL);
107	}
108
109	sem->count = (u_int32_t)value;
110	sem->nwaiters = 0;
111	sem->magic = SEM_MAGIC;
112	sem->semid = semid;
113	sem->syssem = system_sem;
114	return (sem);
115}
116
117int
118_sem_init(sem_t *sem, int pshared, unsigned int value)
119{
120	semid_t semid;
121
122	semid = (semid_t)SEM_USER;
123	if ((pshared != 0) && (ksem_init(&semid, value) != 0))
124		return (-1);
125
126	(*sem) = sem_alloc(value, semid, pshared);
127	if ((*sem) == NULL) {
128		if (pshared != 0)
129			ksem_destroy(semid);
130		return (-1);
131	}
132	return (0);
133}
134
135int
136_sem_wait(sem_t *sem)
137{
138	struct pthread *curthread;
139	int retval;
140
141	if (sem_check_validity(sem) != 0)
142		return (-1);
143
144	curthread = _get_curthread();
145	if ((*sem)->syssem != 0) {
146		_thr_cancel_enter(curthread);
147		retval = ksem_wait((*sem)->semid);
148		_thr_cancel_leave(curthread, retval != 0);
149	}
150	else {
151		_pthread_testcancel();
152		_pthread_mutex_lock(&(*sem)->lock);
153
154		while ((*sem)->count <= 0) {
155			(*sem)->nwaiters++;
156			THR_CLEANUP_PUSH(curthread, decrease_nwaiters, sem);
157			_pthread_cond_wait(&(*sem)->gtzero, &(*sem)->lock);
158			THR_CLEANUP_POP(curthread, 0);
159			(*sem)->nwaiters--;
160		}
161		(*sem)->count--;
162
163		_pthread_mutex_unlock(&(*sem)->lock);
164
165		retval = 0;
166	}
167	return (retval);
168}
169
170int
171_sem_timedwait(sem_t * __restrict sem,
172    const struct timespec * __restrict abs_timeout)
173{
174	struct pthread *curthread;
175	int retval;
176	int timeout_invalid;
177
178	if (sem_check_validity(sem) != 0)
179		return (-1);
180
181	if ((*sem)->syssem != 0) {
182		curthread = _get_curthread();
183		_thr_cancel_enter(curthread);
184		retval = ksem_timedwait((*sem)->semid, abs_timeout);
185		_thr_cancel_leave(curthread, retval != 0);
186	}
187	else {
188		/*
189		 * The timeout argument is only supposed to
190		 * be checked if the thread would have blocked.
191		 * This is checked outside of the lock so a
192		 * segfault on an invalid address doesn't end
193		 * up leaving the mutex locked.
194		 */
195		_pthread_testcancel();
196		timeout_invalid = (abs_timeout->tv_nsec >= 1000000000) ||
197		    (abs_timeout->tv_nsec < 0);
198		_pthread_mutex_lock(&(*sem)->lock);
199
200		if ((*sem)->count <= 0) {
201			if (timeout_invalid) {
202				_pthread_mutex_unlock(&(*sem)->lock);
203				errno = EINVAL;
204				return (-1);
205			}
206			(*sem)->nwaiters++;
207			_pthread_cleanup_push(decrease_nwaiters, sem);
208			_pthread_cond_timedwait(&(*sem)->gtzero,
209			    &(*sem)->lock, abs_timeout);
210			_pthread_cleanup_pop(0);
211			(*sem)->nwaiters--;
212		}
213		if ((*sem)->count == 0) {
214			errno = ETIMEDOUT;
215			retval = -1;
216		}
217		else {
218			(*sem)->count--;
219			retval = 0;
220		}
221
222		_pthread_mutex_unlock(&(*sem)->lock);
223	}
224
225	return (retval);
226}
227
228int
229_sem_post(sem_t *sem)
230{
231	struct pthread *curthread;
232	int retval;
233
234	if (sem_check_validity(sem) != 0)
235		return (-1);
236
237	if ((*sem)->syssem != 0)
238		retval = ksem_post((*sem)->semid);
239	else {
240		/*
241		 * sem_post() is required to be safe to call from within
242		 * signal handlers.  Thus, we must enter a critical region.
243		 */
244		curthread = _get_curthread();
245		_thr_critical_enter(curthread);
246		_pthread_mutex_lock(&(*sem)->lock);
247
248		(*sem)->count++;
249		if ((*sem)->nwaiters > 0)
250			_pthread_cond_signal(&(*sem)->gtzero);
251
252		_pthread_mutex_unlock(&(*sem)->lock);
253		_thr_critical_leave(curthread);
254		retval = 0;
255	}
256
257	return (retval);
258}
259