xref: /openbsd/lib/librthread/rthread_sem.c (revision d415bd75)
1 /*	$OpenBSD: rthread_sem.c,v 1.33 2022/05/14 14:52:20 cheloha 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 || !timespecisvalid(abstime)) {
258 		errno = EINVAL;
259 		return (-1);
260 	}
261 
262 	if (!_threads_ready)
263 		_rthread_init();
264 	self = tib->tib_thread;
265 
266 	ENTER_DELAYED_CANCEL_POINT(tib, self);
267 	error = _sem_wait(sem, 1, abstime, &self->delayed_cancel);
268 	LEAVE_CANCEL_POINT_INNER(tib, error);
269 
270 	if (error) {
271 		errno = (error == EWOULDBLOCK) ? ETIMEDOUT : error;
272 		_rthread_debug(1, "%s: v=%d errno=%d\n", __func__,
273 		    sem->value, errno);
274 		return (-1);
275 	}
276 
277 	return (0);
278 }
279 
280 int
281 sem_trywait(sem_t *semp)
282 {
283 	sem_t sem;
284 	unsigned int val;
285 
286 	if (!semp || !(sem = *semp)) {
287 		errno = EINVAL;
288 		return (-1);
289 	}
290 
291 	while ((val = sem->value) > 0) {
292 		if (atomic_cas_uint(&sem->value, val, val - 1) == val) {
293 			membar_enter_after_atomic();
294 			return (0);
295 		}
296 	}
297 
298 	errno = EAGAIN;
299 	_rthread_debug(1, "%s: v=%d errno=%d\n", __func__, sem->value, errno);
300 	return (-1);
301 }
302 
303 
304 static void
305 makesempath(const char *origpath, char *sempath, size_t len)
306 {
307 	char buf[SHA256_DIGEST_STRING_LENGTH];
308 
309 	SHA256Data(origpath, strlen(origpath), buf);
310 	snprintf(sempath, len, "/tmp/%s.sem", buf);
311 }
312 
313 sem_t *
314 sem_open(const char *name, int oflag, ...)
315 {
316 	char sempath[SEM_PATH_SIZE];
317 	struct stat sb;
318 	sem_t sem, *semp;
319 	unsigned int value = 0;
320 	int created = 0, fd;
321 
322 	if (!_threads_ready)
323 		_rthread_init();
324 
325 	if (oflag & ~(O_CREAT | O_EXCL)) {
326 		errno = EINVAL;
327 		return (SEM_FAILED);
328 	}
329 
330 	if (oflag & O_CREAT) {
331 		va_list ap;
332 		va_start(ap, oflag);
333 		/* 3rd parameter mode is not used */
334 		va_arg(ap, mode_t);
335 		value = va_arg(ap, unsigned);
336 		va_end(ap);
337 
338 		if (value > SEM_VALUE_MAX) {
339 			errno = EINVAL;
340 			return (SEM_FAILED);
341 		}
342 	}
343 
344 	makesempath(name, sempath, sizeof(sempath));
345 	fd = open(sempath, O_RDWR | O_NOFOLLOW | oflag, 0600);
346 	if (fd == -1)
347 		return (SEM_FAILED);
348 	if (fstat(fd, &sb) == -1 || !S_ISREG(sb.st_mode)) {
349 		close(fd);
350 		errno = EINVAL;
351 		return (SEM_FAILED);
352 	}
353 	if (sb.st_uid != geteuid()) {
354 		close(fd);
355 		errno = EPERM;
356 		return (SEM_FAILED);
357 	}
358 	if (sb.st_size != (off_t)SEM_MMAP_SIZE) {
359 		if (!(oflag & O_CREAT)) {
360 			close(fd);
361 			errno = EINVAL;
362 			return (SEM_FAILED);
363 		}
364 		if (sb.st_size != 0) {
365 			close(fd);
366 			errno = EINVAL;
367 			return (SEM_FAILED);
368 		}
369 		if (ftruncate(fd, SEM_MMAP_SIZE) == -1) {
370 			close(fd);
371 			errno = EINVAL;
372 			return (SEM_FAILED);
373 		}
374 
375 		created = 1;
376 	}
377 	sem = mmap(NULL, SEM_MMAP_SIZE, PROT_READ | PROT_WRITE,
378 	    MAP_SHARED, fd, 0);
379 	close(fd);
380 	if (sem == MAP_FAILED) {
381 		errno = EINVAL;
382 		return (SEM_FAILED);
383 	}
384 	semp = malloc(sizeof(*semp));
385 	if (!semp) {
386 		munmap(sem, SEM_MMAP_SIZE);
387 		errno = ENOSPC;
388 		return (SEM_FAILED);
389 	}
390 	if (created) {
391 		sem->value = value;
392 		sem->shared = 1;
393 	}
394 	*semp = sem;
395 
396 	return (semp);
397 }
398 
399 int
400 sem_close(sem_t *semp)
401 {
402 	sem_t sem;
403 
404 	if (!semp || !(sem = *semp) || !sem->shared) {
405 		errno = EINVAL;
406 		return (-1);
407 	}
408 
409 	*semp = NULL;
410 	munmap(sem, SEM_MMAP_SIZE);
411 	free(semp);
412 
413 	return (0);
414 }
415 
416 int
417 sem_unlink(const char *name)
418 {
419 	char sempath[SEM_PATH_SIZE];
420 
421 	makesempath(name, sempath, sizeof(sempath));
422 	return (unlink(sempath));
423 }
424