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