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
_sem_wait(sem_t sem,int can_eintr,const struct timespec * abstime,int * delayed_cancel)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
_sem_post(sem_t sem)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
sem_init(sem_t * semp,int pshared,unsigned int value)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
sem_destroy(sem_t * semp)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
sem_getvalue(sem_t * semp,int * sval)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
sem_post(sem_t * semp)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
sem_wait(sem_t * semp)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
sem_timedwait(sem_t * semp,const struct timespec * abstime)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
sem_trywait(sem_t * semp)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
makesempath(const char * origpath,char * sempath,size_t len)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 *
sem_open(const char * name,int oflag,...)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
sem_close(sem_t * semp)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
sem_unlink(const char * name)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