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