1#include <semaphore.h>
2#include <sys/mman.h>
3#include <limits.h>
4#include <fcntl.h>
5#include <unistd.h>
6#include <string.h>
7#include <stdarg.h>
8#include <errno.h>
9#include <time.h>
10#include <stdio.h>
11#include <sys/stat.h>
12#include <stdlib.h>
13#include <pthread.h>
14#include "libc.h"
15
16char *__shm_mapname(const char *, char *);
17
18static struct {
19	ino_t ino;
20	sem_t *sem;
21	int refcnt;
22} *semtab;
23static volatile int lock[2];
24
25#define FLAGS (O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NONBLOCK)
26
27sem_t *sem_open(const char *name, int flags, ...)
28{
29	va_list ap;
30	mode_t mode;
31	unsigned value;
32	int fd, i, e, slot, first=1, cnt, cs;
33	sem_t newsem;
34	void *map;
35	char tmp[64];
36	struct timespec ts;
37	struct stat st;
38	char buf[NAME_MAX+10];
39
40	if (!(name = __shm_mapname(name, buf)))
41		return SEM_FAILED;
42
43	LOCK(lock);
44	/* Allocate table if we don't have one yet */
45	if (!semtab && !(semtab = calloc(sizeof *semtab, SEM_NSEMS_MAX))) {
46		UNLOCK(lock);
47		return SEM_FAILED;
48	}
49
50	/* Reserve a slot in case this semaphore is not mapped yet;
51	 * this is necessary because there is no way to handle
52	 * failures after creation of the file. */
53	slot = -1;
54	for (cnt=i=0; i<SEM_NSEMS_MAX; i++) {
55		cnt += semtab[i].refcnt;
56		if (!semtab[i].sem && slot < 0) slot = i;
57	}
58	/* Avoid possibility of overflow later */
59	if (cnt == INT_MAX || slot < 0) {
60		errno = EMFILE;
61		UNLOCK(lock);
62		return SEM_FAILED;
63	}
64	/* Dummy pointer to make a reservation */
65	semtab[slot].sem = (sem_t *)-1;
66	UNLOCK(lock);
67
68	flags &= (O_CREAT|O_EXCL);
69
70	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
71
72	/* Early failure check for exclusive open; otherwise the case
73	 * where the semaphore already exists is expensive. */
74	if (flags == (O_CREAT|O_EXCL) && access(name, F_OK) == 0) {
75		errno = EEXIST;
76		goto fail;
77	}
78
79	for (;;) {
80		/* If exclusive mode is not requested, try opening an
81		 * existing file first and fall back to creation. */
82		if (flags != (O_CREAT|O_EXCL)) {
83			fd = open(name, FLAGS);
84			if (fd >= 0) {
85				if (fstat(fd, &st) < 0 ||
86				    (map = mmap(0, sizeof(sem_t), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
87					close(fd);
88					goto fail;
89				}
90				close(fd);
91				break;
92			}
93			if (errno != ENOENT)
94				goto fail;
95		}
96		if (!(flags & O_CREAT))
97			goto fail;
98		if (first) {
99			first = 0;
100			va_start(ap, flags);
101			mode = va_arg(ap, mode_t) & 0666;
102			value = va_arg(ap, unsigned);
103			va_end(ap);
104			if (value > SEM_VALUE_MAX) {
105				errno = EINVAL;
106				goto fail;
107			}
108			sem_init(&newsem, 1, value);
109		}
110		/* Create a temp file with the new semaphore contents
111		 * and attempt to atomically link it as the new name */
112		clock_gettime(CLOCK_REALTIME, &ts);
113		snprintf(tmp, sizeof(tmp), "/dev/shm/tmp-%d", (int)ts.tv_nsec);
114		fd = open(tmp, O_CREAT|O_EXCL|FLAGS, mode);
115		if (fd < 0) {
116			if (errno == EEXIST) continue;
117			goto fail;
118		}
119		if (write(fd, &newsem, sizeof newsem) != sizeof newsem || fstat(fd, &st) < 0 ||
120		    (map = mmap(0, sizeof(sem_t), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
121			close(fd);
122			unlink(tmp);
123			goto fail;
124		}
125		close(fd);
126		e = link(tmp, name) ? errno : 0;
127		unlink(tmp);
128		if (!e) break;
129		munmap(map, sizeof(sem_t));
130		/* Failure is only fatal when doing an exclusive open;
131		 * otherwise, next iteration will try to open the
132		 * existing file. */
133		if (e != EEXIST || flags == (O_CREAT|O_EXCL))
134			goto fail;
135	}
136
137	/* See if the newly mapped semaphore is already mapped. If
138	 * so, unmap the new mapping and use the existing one. Otherwise,
139	 * add it to the table of mapped semaphores. */
140	LOCK(lock);
141	for (i=0; i<SEM_NSEMS_MAX && semtab[i].ino != st.st_ino; i++);
142	if (i<SEM_NSEMS_MAX) {
143		munmap(map, sizeof(sem_t));
144		semtab[slot].sem = 0;
145		slot = i;
146		map = semtab[i].sem;
147	}
148	semtab[slot].refcnt++;
149	semtab[slot].sem = map;
150	semtab[slot].ino = st.st_ino;
151	UNLOCK(lock);
152	pthread_setcancelstate(cs, 0);
153	return map;
154
155fail:
156	pthread_setcancelstate(cs, 0);
157	LOCK(lock);
158	semtab[slot].sem = 0;
159	UNLOCK(lock);
160	return SEM_FAILED;
161}
162
163int sem_close(sem_t *sem)
164{
165	int i;
166	LOCK(lock);
167	for (i=0; i<SEM_NSEMS_MAX && semtab[i].sem != sem; i++);
168	if (!--semtab[i].refcnt) {
169		semtab[i].sem = 0;
170		semtab[i].ino = 0;
171	}
172	UNLOCK(lock);
173	munmap(sem, sizeof *sem);
174	return 0;
175}
176