xref: /openbsd-src/lib/librthread/rthread_sem.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*	$OpenBSD: rthread_sem.c,v 1.25 2016/09/04 10:13:35 akfaew 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 (!semp || !(sem = *semp)) {
176 		errno = EINVAL;
177 		return (-1);
178 	}
179 
180 	if (sem->waitcount) {
181 #define MSG "sem_destroy on semaphore with waiters!\n"
182 		write(2, MSG, sizeof(MSG) - 1);
183 #undef MSG
184 		errno = EBUSY;
185 		return (-1);
186 	}
187 
188 	*semp = NULL;
189 	if (sem->shared)
190 		munmap(sem, SEM_MMAP_SIZE);
191 	else
192 		free(sem);
193 
194 	return (0);
195 }
196 
197 int
198 sem_getvalue(sem_t *semp, int *sval)
199 {
200 	sem_t sem;
201 
202 	if (!semp || !(sem = *semp)) {
203 		errno = EINVAL;
204 		return (-1);
205 	}
206 
207 	_spinlock(&sem->lock);
208 	*sval = sem->value;
209 	_spinunlock(&sem->lock);
210 
211 	return (0);
212 }
213 
214 int
215 sem_post(sem_t *semp)
216 {
217 	sem_t sem;
218 
219 	if (!semp || !(sem = *semp)) {
220 		errno = EINVAL;
221 		return (-1);
222 	}
223 
224 	_sem_post(sem);
225 
226 	return (0);
227 }
228 
229 int
230 sem_wait(sem_t *semp)
231 {
232 	struct tib *tib = TIB_GET();
233 	pthread_t self;
234 	sem_t sem;
235 	int r;
236 	PREP_CANCEL_POINT(tib);
237 
238 	if (!_threads_ready)
239 		_rthread_init();
240 	self = tib->tib_thread;
241 
242 	if (!semp || !(sem = *semp)) {
243 		errno = EINVAL;
244 		return (-1);
245 	}
246 
247 	ENTER_DELAYED_CANCEL_POINT(tib, self);
248 	r = _sem_wait(sem, 0, NULL, &self->delayed_cancel);
249 	LEAVE_CANCEL_POINT_INNER(tib, r);
250 
251 	if (r) {
252 		errno = r;
253 		return (-1);
254 	}
255 
256 	return (0);
257 }
258 
259 int
260 sem_timedwait(sem_t *semp, const struct timespec *abstime)
261 {
262 	struct tib *tib = TIB_GET();
263 	pthread_t self;
264 	sem_t sem;
265 	int r;
266 	PREP_CANCEL_POINT(tib);
267 
268 	if (!_threads_ready)
269 		_rthread_init();
270 	self = tib->tib_thread;
271 
272 	if (!semp || !(sem = *semp)) {
273 		errno = EINVAL;
274 		return (-1);
275 	}
276 
277 	ENTER_DELAYED_CANCEL_POINT(tib, self);
278 	r = _sem_wait(sem, 0, abstime, &self->delayed_cancel);
279 	LEAVE_CANCEL_POINT_INNER(tib, r);
280 
281 	if (r) {
282 		errno = r == EWOULDBLOCK ? ETIMEDOUT : r;
283 		return (-1);
284 	}
285 
286 	return (0);
287 }
288 
289 int
290 sem_trywait(sem_t *semp)
291 {
292 	sem_t sem;
293 	int r;
294 
295 	if (!semp || !(sem = *semp)) {
296 		errno = EINVAL;
297 		return (-1);
298 	}
299 
300 	r = _sem_wait(sem, 1, NULL, NULL);
301 
302 	if (r) {
303 		errno = r;
304 		return (-1);
305 	}
306 
307 	return (0);
308 }
309 
310 
311 static void
312 makesempath(const char *origpath, char *sempath, size_t len)
313 {
314 	char buf[SHA256_DIGEST_STRING_LENGTH];
315 
316 	SHA256Data(origpath, strlen(origpath), buf);
317 	snprintf(sempath, len, "/tmp/%s.sem", buf);
318 }
319 
320 sem_t *
321 sem_open(const char *name, int oflag, ...)
322 {
323 	char sempath[SEM_PATH_SIZE];
324 	struct stat sb;
325 	sem_t sem, *semp;
326 	unsigned int value = 0;
327 	int created = 0, fd;
328 
329 	if (!_threads_ready)
330 		_rthread_init();
331 
332 	if (oflag & ~(O_CREAT | O_EXCL)) {
333 		errno = EINVAL;
334 		return (SEM_FAILED);
335 	}
336 
337 	if (oflag & O_CREAT) {
338 		va_list ap;
339 		va_start(ap, oflag);
340 		/* 3rd parameter mode is not used */
341 		va_arg(ap, mode_t);
342 		value = va_arg(ap, unsigned);
343 		va_end(ap);
344 
345 		if (value > SEM_VALUE_MAX) {
346 			errno = EINVAL;
347 			return (SEM_FAILED);
348 		}
349 	}
350 
351 	makesempath(name, sempath, sizeof(sempath));
352 	fd = open(sempath, O_RDWR | O_NOFOLLOW | oflag, 0600);
353 	if (fd == -1)
354 		return (SEM_FAILED);
355 	if (fstat(fd, &sb) == -1 || !S_ISREG(sb.st_mode)) {
356 		close(fd);
357 		errno = EINVAL;
358 		return (SEM_FAILED);
359 	}
360 	if (sb.st_uid != geteuid()) {
361 		close(fd);
362 		errno = EPERM;
363 		return (SEM_FAILED);
364 	}
365 	if (sb.st_size != (off_t)SEM_MMAP_SIZE) {
366 		if (!(oflag & O_CREAT)) {
367 			close(fd);
368 			errno = EINVAL;
369 			return (SEM_FAILED);
370 		}
371 		if (sb.st_size != 0) {
372 			close(fd);
373 			errno = EINVAL;
374 			return (SEM_FAILED);
375 		}
376 		if (ftruncate(fd, SEM_MMAP_SIZE) == -1) {
377 			close(fd);
378 			errno = EINVAL;
379 			return (SEM_FAILED);
380 		}
381 
382 		created = 1;
383 	}
384 	sem = mmap(NULL, SEM_MMAP_SIZE, PROT_READ | PROT_WRITE,
385 	    MAP_SHARED, fd, 0);
386 	close(fd);
387 	if (sem == MAP_FAILED) {
388 		errno = EINVAL;
389 		return (SEM_FAILED);
390 	}
391 	semp = malloc(sizeof(*semp));
392 	if (!semp) {
393 		munmap(sem, SEM_MMAP_SIZE);
394 		errno = ENOSPC;
395 		return (SEM_FAILED);
396 	}
397 	if (created) {
398 		sem->lock = _SPINLOCK_UNLOCKED;
399 		sem->value = value;
400 		sem->shared = 1;
401 	}
402 	*semp = sem;
403 
404 	return (semp);
405 }
406 
407 int
408 sem_close(sem_t *semp)
409 {
410 	sem_t sem;
411 
412 	if (!semp || !(sem = *semp) || !sem->shared) {
413 		errno = EINVAL;
414 		return (-1);
415 	}
416 
417 	*semp = NULL;
418 	munmap(sem, SEM_MMAP_SIZE);
419 	free(semp);
420 
421 	return (0);
422 }
423 
424 int
425 sem_unlink(const char *name)
426 {
427 	char sempath[SEM_PATH_SIZE];
428 
429 	makesempath(name, sempath, sizeof(sempath));
430 	return (unlink(sempath));
431 }
432