xref: /openbsd-src/lib/librthread/rthread_sem.c (revision c90a81c56dcebd6a1b73fe4aff9b03385b8e63b3)
1 /*	$OpenBSD: rthread_sem.c,v 1.29 2018/06/08 13:53:01 pirofti Exp $ */
2 /*
3  * Copyright (c) 2004,2005,2013 Ted Unangst <tedu@openbsd.org>
4  * Copyright (c) 2018 Paul Irofti <pirofti@openbsd.org>
5  * All Rights Reserved.
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include <sys/types.h>
21 #include <sys/mman.h>
22 #include <sys/stat.h>
23 #include <sys/atomic.h>
24 #include <sys/time.h>
25 #include <sys/futex.h>
26 
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <sha2.h>
30 #include <stdarg.h>
31 #include <stdlib.h>
32 #include <stdio.h>
33 #include <string.h>
34 #include <unistd.h>
35 
36 #include <pthread.h>
37 
38 #include "rthread.h"
39 #include "cancel.h"		/* in libc/include */
40 #include "synch.h"
41 
42 #define SHARED_IDENT ((void *)-1)
43 
44 /* SHA256_DIGEST_STRING_LENGTH includes nul */
45 /* "/tmp/" + sha256 + ".sem" */
46 #define SEM_PATH_SIZE (5 + SHA256_DIGEST_STRING_LENGTH + 4)
47 
48 /* long enough to be hard to guess */
49 #define SEM_RANDOM_NAME_LEN	10
50 
51 /*
52  * Size of memory to be mmap()'ed by named semaphores.
53  * Should be >= SEM_PATH_SIZE and page-aligned.
54  */
55 #define SEM_MMAP_SIZE	_thread_pagesize
56 
57 /*
58  * Internal implementation of semaphores
59  */
60 int
61 _sem_wait(sem_t sem, int can_eintr, const struct timespec *abstime,
62     int *delayed_cancel)
63 {
64 	int r = 0;
65 	int v, ov;
66 
67 	atomic_inc_int(&sem->waitcount);
68 	for (;;) {
69 		while ((v = sem->value) > 0) {
70 			ov = atomic_cas_uint(&sem->value, v, v - 1);
71 			if (ov == v) {
72 				membar_enter_after_atomic();
73 				atomic_dec_int(&sem->waitcount);
74 				return 0;
75 			}
76 		}
77 		if (r)
78 			break;
79 
80 		r = _twait(&sem->value, 0, CLOCK_REALTIME, abstime);
81 		/* ignore interruptions other than cancelation */
82 		if ((r == ECANCELED && *delayed_cancel == 0) ||
83 		    (r == EINTR && !can_eintr) || r == EAGAIN)
84 			r = 0;
85 	}
86 	atomic_dec_int(&sem->waitcount);
87 
88 	return r;
89 }
90 
91 /* always increment count */
92 int
93 _sem_post(sem_t sem)
94 {
95 	membar_exit_before_atomic();
96 	atomic_inc_int(&sem->value);
97 	_wake(&sem->value, 1);
98 	return 0;
99 }
100 
101 /*
102  * exported semaphores
103  */
104 int
105 sem_init(sem_t *semp, int pshared, unsigned int value)
106 {
107 	sem_t sem;
108 
109 	if (value > SEM_VALUE_MAX) {
110 		errno = EINVAL;
111 		return (-1);
112 	}
113 
114 	if (pshared) {
115 		errno = EPERM;
116 		return (-1);
117 #ifdef notyet
118 		char name[SEM_RANDOM_NAME_LEN];
119 		sem_t *sempshared;
120 		int i;
121 
122 		for (;;) {
123 			for (i = 0; i < SEM_RANDOM_NAME_LEN - 1; i++)
124 				name[i] = arc4random_uniform(255) + 1;
125 			name[SEM_RANDOM_NAME_LEN - 1] = '\0';
126 			sempshared = sem_open(name, O_CREAT | O_EXCL, 0, value);
127 			if (sempshared != SEM_FAILED)
128 				break;
129 			if (errno == EEXIST)
130 				continue;
131 			if (errno != EPERM)
132 				errno = ENOSPC;
133 			return (-1);
134 		}
135 
136 		/* unnamed semaphore should not be opened twice */
137 		if (sem_unlink(name) == -1) {
138 			sem_close(sempshared);
139 			errno = ENOSPC;
140 			return (-1);
141 		}
142 
143 		*semp = *sempshared;
144 		free(sempshared);
145 		return (0);
146 #endif
147 	}
148 
149 	sem = calloc(1, sizeof(*sem));
150 	if (!sem) {
151 		errno = ENOSPC;
152 		return (-1);
153 	}
154 	sem->value = value;
155 	*semp = sem;
156 
157 	return (0);
158 }
159 
160 int
161 sem_destroy(sem_t *semp)
162 {
163 	sem_t sem;
164 
165 	if (!_threads_ready)		 /* for SEM_MMAP_SIZE */
166 		_rthread_init();
167 
168 	if (!semp || !(sem = *semp)) {
169 		errno = EINVAL;
170 		return (-1);
171 	}
172 
173 	if (sem->waitcount) {
174 #define MSG "sem_destroy on semaphore with waiters!\n"
175 		write(2, MSG, sizeof(MSG) - 1);
176 #undef MSG
177 		errno = EBUSY;
178 		return (-1);
179 	}
180 
181 	*semp = NULL;
182 	if (sem->shared)
183 		munmap(sem, SEM_MMAP_SIZE);
184 	else
185 		free(sem);
186 
187 	return (0);
188 }
189 
190 int
191 sem_getvalue(sem_t *semp, int *sval)
192 {
193 	sem_t sem;
194 
195 	if (!semp || !(sem = *semp)) {
196 		errno = EINVAL;
197 		return (-1);
198 	}
199 
200 	*sval = sem->value;
201 
202 	return (0);
203 }
204 
205 int
206 sem_post(sem_t *semp)
207 {
208 	sem_t sem;
209 
210 	if (!semp || !(sem = *semp)) {
211 		errno = EINVAL;
212 		return (-1);
213 	}
214 
215 	_sem_post(sem);
216 
217 	return (0);
218 }
219 
220 int
221 sem_wait(sem_t *semp)
222 {
223 	struct tib *tib = TIB_GET();
224 	pthread_t self;
225 	sem_t sem;
226 	int r;
227 	PREP_CANCEL_POINT(tib);
228 
229 	if (!_threads_ready)
230 		_rthread_init();
231 	self = tib->tib_thread;
232 
233 	if (!semp || !(sem = *semp)) {
234 		errno = EINVAL;
235 		return (-1);
236 	}
237 
238 	ENTER_DELAYED_CANCEL_POINT(tib, self);
239 	r = _sem_wait(sem, 1, NULL, &self->delayed_cancel);
240 	LEAVE_CANCEL_POINT_INNER(tib, r);
241 
242 	if (r) {
243 		errno = r;
244 		_rthread_debug(1, "%s: v=%d errno=%d\n", __func__,
245 		    sem->value, errno);
246 		return (-1);
247 	}
248 
249 	return (0);
250 }
251 
252 int
253 sem_timedwait(sem_t *semp, const struct timespec *abstime)
254 {
255 	struct tib *tib = TIB_GET();
256 	pthread_t self;
257 	sem_t sem;
258 	int r;
259 	PREP_CANCEL_POINT(tib);
260 
261 	if (!semp || !(sem = *semp) || abstime == NULL ||
262 	   abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000) {
263 		errno = EINVAL;
264 		return (-1);
265 	}
266 
267 	if (!_threads_ready)
268 		_rthread_init();
269 	self = tib->tib_thread;
270 
271 	ENTER_DELAYED_CANCEL_POINT(tib, self);
272 	r = _sem_wait(sem, 1, abstime, &self->delayed_cancel);
273 	LEAVE_CANCEL_POINT_INNER(tib, r);
274 
275 	if (r) {
276 		errno = r == EWOULDBLOCK ? ETIMEDOUT : r;
277 		_rthread_debug(1, "%s: v=%d errno=%d\n", __func__,
278 		    sem->value, errno);
279 		return (-1);
280 	}
281 
282 	return (0);
283 }
284 
285 int
286 sem_trywait(sem_t *semp)
287 {
288 	sem_t sem;
289 	int v, ov;
290 
291 	if (!semp || !(sem = *semp)) {
292 		errno = EINVAL;
293 		return (-1);
294 	}
295 
296 	while ((v = sem->value) > 0) {
297 		ov = atomic_cas_uint(&sem->value, v, v - 1);
298 		if (ov == v) {
299 			membar_enter_after_atomic();
300 			return (0);
301 		}
302 	}
303 
304 	errno = EAGAIN;
305 	_rthread_debug(1, "%s: v=%d errno=%d\n", __func__, sem->value, errno);
306 	return (-1);
307 }
308 
309 
310 static void
311 makesempath(const char *origpath, char *sempath, size_t len)
312 {
313 	char buf[SHA256_DIGEST_STRING_LENGTH];
314 
315 	SHA256Data(origpath, strlen(origpath), buf);
316 	snprintf(sempath, len, "/tmp/%s.sem", buf);
317 }
318 
319 sem_t *
320 sem_open(const char *name, int oflag, ...)
321 {
322 	char sempath[SEM_PATH_SIZE];
323 	struct stat sb;
324 	sem_t sem, *semp;
325 	unsigned int value = 0;
326 	int created = 0, fd;
327 
328 	if (!_threads_ready)
329 		_rthread_init();
330 
331 	if (oflag & ~(O_CREAT | O_EXCL)) {
332 		errno = EINVAL;
333 		return (SEM_FAILED);
334 	}
335 
336 	if (oflag & O_CREAT) {
337 		va_list ap;
338 		va_start(ap, oflag);
339 		/* 3rd parameter mode is not used */
340 		va_arg(ap, mode_t);
341 		value = va_arg(ap, unsigned);
342 		va_end(ap);
343 
344 		if (value > SEM_VALUE_MAX) {
345 			errno = EINVAL;
346 			return (SEM_FAILED);
347 		}
348 	}
349 
350 	makesempath(name, sempath, sizeof(sempath));
351 	fd = open(sempath, O_RDWR | O_NOFOLLOW | oflag, 0600);
352 	if (fd == -1)
353 		return (SEM_FAILED);
354 	if (fstat(fd, &sb) == -1 || !S_ISREG(sb.st_mode)) {
355 		close(fd);
356 		errno = EINVAL;
357 		return (SEM_FAILED);
358 	}
359 	if (sb.st_uid != geteuid()) {
360 		close(fd);
361 		errno = EPERM;
362 		return (SEM_FAILED);
363 	}
364 	if (sb.st_size != (off_t)SEM_MMAP_SIZE) {
365 		if (!(oflag & O_CREAT)) {
366 			close(fd);
367 			errno = EINVAL;
368 			return (SEM_FAILED);
369 		}
370 		if (sb.st_size != 0) {
371 			close(fd);
372 			errno = EINVAL;
373 			return (SEM_FAILED);
374 		}
375 		if (ftruncate(fd, SEM_MMAP_SIZE) == -1) {
376 			close(fd);
377 			errno = EINVAL;
378 			return (SEM_FAILED);
379 		}
380 
381 		created = 1;
382 	}
383 	sem = mmap(NULL, SEM_MMAP_SIZE, PROT_READ | PROT_WRITE,
384 	    MAP_SHARED, fd, 0);
385 	close(fd);
386 	if (sem == MAP_FAILED) {
387 		errno = EINVAL;
388 		return (SEM_FAILED);
389 	}
390 	semp = malloc(sizeof(*semp));
391 	if (!semp) {
392 		munmap(sem, SEM_MMAP_SIZE);
393 		errno = ENOSPC;
394 		return (SEM_FAILED);
395 	}
396 	if (created) {
397 		sem->value = value;
398 		sem->shared = 1;
399 	}
400 	*semp = sem;
401 
402 	return (semp);
403 }
404 
405 int
406 sem_close(sem_t *semp)
407 {
408 	sem_t sem;
409 
410 	if (!semp || !(sem = *semp) || !sem->shared) {
411 		errno = EINVAL;
412 		return (-1);
413 	}
414 
415 	*semp = NULL;
416 	munmap(sem, SEM_MMAP_SIZE);
417 	free(semp);
418 
419 	return (0);
420 }
421 
422 int
423 sem_unlink(const char *name)
424 {
425 	char sempath[SEM_PATH_SIZE];
426 
427 	makesempath(name, sempath, sizeof(sempath));
428 	return (unlink(sempath));
429 }
430