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