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 */
57int
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 */
88int
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 */
100int
101sem_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
156int
157sem_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
186int
187sem_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
201int
202sem_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
216int
217sem_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
248int
249sem_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
280int
281sem_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
304static void
305makesempath(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
313sem_t *
314sem_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
399int
400sem_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
416int
417sem_unlink(const char *name)
418{
419	char sempath[SEM_PATH_SIZE];
420
421	makesempath(name, sempath, sizeof(sempath));
422	return (unlink(sempath));
423}
424