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