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