xref: /openbsd-src/lib/librthread/rthread_sem.c (revision 9f11ffb7133c203312a01e4b986886bc88c7d74b)
1 /*	$OpenBSD: rthread_sem.c,v 1.30 2019/01/29 17:43:23 mpi 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 /* SHA256_DIGEST_STRING_LENGTH includes nul */
43 /* "/tmp/" + sha256 + ".sem" */
44 #define SEM_PATH_SIZE (5 + SHA256_DIGEST_STRING_LENGTH + 4)
45 
46 /* long enough to be hard to guess */
47 #define SEM_RANDOM_NAME_LEN	10
48 
49 /*
50  * Size of memory to be mmap()'ed by named semaphores.
51  * Should be >= SEM_PATH_SIZE and page-aligned.
52  */
53 #define SEM_MMAP_SIZE	_thread_pagesize
54 
55 /*
56  * Internal implementation of semaphores
57  */
58 int
59 _sem_wait(sem_t sem, int can_eintr, const struct timespec *abstime,
60     int *delayed_cancel)
61 {
62 	unsigned int val;
63 	int error = 0;
64 
65 	atomic_inc_int(&sem->waitcount);
66 	for (;;) {
67 		while ((val = sem->value) > 0) {
68 			if (atomic_cas_uint(&sem->value, val, val - 1) == val) {
69 				membar_enter_after_atomic();
70 				atomic_dec_int(&sem->waitcount);
71 				return (0);
72 			}
73 		}
74 		if (error)
75 			break;
76 
77 		error = _twait(&sem->value, 0, CLOCK_REALTIME, abstime);
78 		/* ignore interruptions other than cancelation */
79 		if ((error == ECANCELED && *delayed_cancel == 0) ||
80 		    (error == EINTR && !can_eintr) || error == EAGAIN)
81 			error = 0;
82 	}
83 	atomic_dec_int(&sem->waitcount);
84 
85 	return (error);
86 }
87 
88 /* always increment count */
89 int
90 _sem_post(sem_t sem)
91 {
92 	membar_exit_before_atomic();
93 	atomic_inc_int(&sem->value);
94 	_wake(&sem->value, 1);
95 	return 0;
96 }
97 
98 /*
99  * exported semaphores
100  */
101 int
102 sem_init(sem_t *semp, int pshared, unsigned int value)
103 {
104 	sem_t sem;
105 
106 	if (value > SEM_VALUE_MAX) {
107 		errno = EINVAL;
108 		return (-1);
109 	}
110 
111 	if (pshared) {
112 		errno = EPERM;
113 		return (-1);
114 #ifdef notyet
115 		char name[SEM_RANDOM_NAME_LEN];
116 		sem_t *sempshared;
117 		int i;
118 
119 		for (;;) {
120 			for (i = 0; i < SEM_RANDOM_NAME_LEN - 1; i++)
121 				name[i] = arc4random_uniform(255) + 1;
122 			name[SEM_RANDOM_NAME_LEN - 1] = '\0';
123 			sempshared = sem_open(name, O_CREAT | O_EXCL, 0, value);
124 			if (sempshared != SEM_FAILED)
125 				break;
126 			if (errno == EEXIST)
127 				continue;
128 			if (errno != EPERM)
129 				errno = ENOSPC;
130 			return (-1);
131 		}
132 
133 		/* unnamed semaphore should not be opened twice */
134 		if (sem_unlink(name) == -1) {
135 			sem_close(sempshared);
136 			errno = ENOSPC;
137 			return (-1);
138 		}
139 
140 		*semp = *sempshared;
141 		free(sempshared);
142 		return (0);
143 #endif
144 	}
145 
146 	sem = calloc(1, sizeof(*sem));
147 	if (!sem) {
148 		errno = ENOSPC;
149 		return (-1);
150 	}
151 	sem->value = value;
152 	*semp = sem;
153 
154 	return (0);
155 }
156 
157 int
158 sem_destroy(sem_t *semp)
159 {
160 	sem_t sem;
161 
162 	if (!_threads_ready)		 /* for SEM_MMAP_SIZE */
163 		_rthread_init();
164 
165 	if (!semp || !(sem = *semp)) {
166 		errno = EINVAL;
167 		return (-1);
168 	}
169 
170 	if (sem->waitcount) {
171 #define MSG "sem_destroy on semaphore with waiters!\n"
172 		write(2, MSG, sizeof(MSG) - 1);
173 #undef MSG
174 		errno = EBUSY;
175 		return (-1);
176 	}
177 
178 	*semp = NULL;
179 	if (sem->shared)
180 		munmap(sem, SEM_MMAP_SIZE);
181 	else
182 		free(sem);
183 
184 	return (0);
185 }
186 
187 int
188 sem_getvalue(sem_t *semp, int *sval)
189 {
190 	sem_t sem;
191 
192 	if (!semp || !(sem = *semp)) {
193 		errno = EINVAL;
194 		return (-1);
195 	}
196 
197 	*sval = sem->value;
198 
199 	return (0);
200 }
201 
202 int
203 sem_post(sem_t *semp)
204 {
205 	sem_t sem;
206 
207 	if (!semp || !(sem = *semp)) {
208 		errno = EINVAL;
209 		return (-1);
210 	}
211 
212 	_sem_post(sem);
213 
214 	return (0);
215 }
216 
217 int
218 sem_wait(sem_t *semp)
219 {
220 	struct tib *tib = TIB_GET();
221 	pthread_t self;
222 	sem_t sem;
223 	int error;
224 	PREP_CANCEL_POINT(tib);
225 
226 	if (!_threads_ready)
227 		_rthread_init();
228 	self = tib->tib_thread;
229 
230 	if (!semp || !(sem = *semp)) {
231 		errno = EINVAL;
232 		return (-1);
233 	}
234 
235 	ENTER_DELAYED_CANCEL_POINT(tib, self);
236 	error = _sem_wait(sem, 1, NULL, &self->delayed_cancel);
237 	LEAVE_CANCEL_POINT_INNER(tib, error);
238 
239 	if (error) {
240 		errno = error;
241 		_rthread_debug(1, "%s: v=%d errno=%d\n", __func__,
242 		    sem->value, errno);
243 		return (-1);
244 	}
245 
246 	return (0);
247 }
248 
249 int
250 sem_timedwait(sem_t *semp, const struct timespec *abstime)
251 {
252 	struct tib *tib = TIB_GET();
253 	pthread_t self;
254 	sem_t sem;
255 	int error;
256 	PREP_CANCEL_POINT(tib);
257 
258 	if (!semp || !(sem = *semp) || abstime == NULL ||
259 	   abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000) {
260 		errno = EINVAL;
261 		return (-1);
262 	}
263 
264 	if (!_threads_ready)
265 		_rthread_init();
266 	self = tib->tib_thread;
267 
268 	ENTER_DELAYED_CANCEL_POINT(tib, self);
269 	error = _sem_wait(sem, 1, abstime, &self->delayed_cancel);
270 	LEAVE_CANCEL_POINT_INNER(tib, error);
271 
272 	if (error) {
273 		errno = (error == EWOULDBLOCK) ? ETIMEDOUT : error;
274 		_rthread_debug(1, "%s: v=%d errno=%d\n", __func__,
275 		    sem->value, errno);
276 		return (-1);
277 	}
278 
279 	return (0);
280 }
281 
282 int
283 sem_trywait(sem_t *semp)
284 {
285 	sem_t sem;
286 	unsigned int val;
287 
288 	if (!semp || !(sem = *semp)) {
289 		errno = EINVAL;
290 		return (-1);
291 	}
292 
293 	while ((val = sem->value) > 0) {
294 		if (atomic_cas_uint(&sem->value, val, val - 1) == val) {
295 			membar_enter_after_atomic();
296 			return (0);
297 		}
298 	}
299 
300 	errno = EAGAIN;
301 	_rthread_debug(1, "%s: v=%d errno=%d\n", __func__, sem->value, errno);
302 	return (-1);
303 }
304 
305 
306 static void
307 makesempath(const char *origpath, char *sempath, size_t len)
308 {
309 	char buf[SHA256_DIGEST_STRING_LENGTH];
310 
311 	SHA256Data(origpath, strlen(origpath), buf);
312 	snprintf(sempath, len, "/tmp/%s.sem", buf);
313 }
314 
315 sem_t *
316 sem_open(const char *name, int oflag, ...)
317 {
318 	char sempath[SEM_PATH_SIZE];
319 	struct stat sb;
320 	sem_t sem, *semp;
321 	unsigned int value = 0;
322 	int created = 0, fd;
323 
324 	if (!_threads_ready)
325 		_rthread_init();
326 
327 	if (oflag & ~(O_CREAT | O_EXCL)) {
328 		errno = EINVAL;
329 		return (SEM_FAILED);
330 	}
331 
332 	if (oflag & O_CREAT) {
333 		va_list ap;
334 		va_start(ap, oflag);
335 		/* 3rd parameter mode is not used */
336 		va_arg(ap, mode_t);
337 		value = va_arg(ap, unsigned);
338 		va_end(ap);
339 
340 		if (value > SEM_VALUE_MAX) {
341 			errno = EINVAL;
342 			return (SEM_FAILED);
343 		}
344 	}
345 
346 	makesempath(name, sempath, sizeof(sempath));
347 	fd = open(sempath, O_RDWR | O_NOFOLLOW | oflag, 0600);
348 	if (fd == -1)
349 		return (SEM_FAILED);
350 	if (fstat(fd, &sb) == -1 || !S_ISREG(sb.st_mode)) {
351 		close(fd);
352 		errno = EINVAL;
353 		return (SEM_FAILED);
354 	}
355 	if (sb.st_uid != geteuid()) {
356 		close(fd);
357 		errno = EPERM;
358 		return (SEM_FAILED);
359 	}
360 	if (sb.st_size != (off_t)SEM_MMAP_SIZE) {
361 		if (!(oflag & O_CREAT)) {
362 			close(fd);
363 			errno = EINVAL;
364 			return (SEM_FAILED);
365 		}
366 		if (sb.st_size != 0) {
367 			close(fd);
368 			errno = EINVAL;
369 			return (SEM_FAILED);
370 		}
371 		if (ftruncate(fd, SEM_MMAP_SIZE) == -1) {
372 			close(fd);
373 			errno = EINVAL;
374 			return (SEM_FAILED);
375 		}
376 
377 		created = 1;
378 	}
379 	sem = mmap(NULL, SEM_MMAP_SIZE, PROT_READ | PROT_WRITE,
380 	    MAP_SHARED, fd, 0);
381 	close(fd);
382 	if (sem == MAP_FAILED) {
383 		errno = EINVAL;
384 		return (SEM_FAILED);
385 	}
386 	semp = malloc(sizeof(*semp));
387 	if (!semp) {
388 		munmap(sem, SEM_MMAP_SIZE);
389 		errno = ENOSPC;
390 		return (SEM_FAILED);
391 	}
392 	if (created) {
393 		sem->value = value;
394 		sem->shared = 1;
395 	}
396 	*semp = sem;
397 
398 	return (semp);
399 }
400 
401 int
402 sem_close(sem_t *semp)
403 {
404 	sem_t sem;
405 
406 	if (!semp || !(sem = *semp) || !sem->shared) {
407 		errno = EINVAL;
408 		return (-1);
409 	}
410 
411 	*semp = NULL;
412 	munmap(sem, SEM_MMAP_SIZE);
413 	free(semp);
414 
415 	return (0);
416 }
417 
418 int
419 sem_unlink(const char *name)
420 {
421 	char sempath[SEM_PATH_SIZE];
422 
423 	makesempath(name, sempath, sizeof(sempath));
424 	return (unlink(sempath));
425 }
426