fix failure behavior of sem_open when sem does not exist
[musl] / src / thread / sem_open.c
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
15 static struct {
16         ino_t ino;
17         sem_t *sem;
18         int refcnt;
19 } *semtab;
20
21 static int semcnt;
22 static pthread_spinlock_t lock;
23 static pthread_once_t once;
24
25 static void init()
26 {
27         semtab = calloc(sizeof *semtab, SEM_NSEMS_MAX);
28 }
29
30 static sem_t *find_map(ino_t ino)
31 {
32         int i;
33         for (i=0; i<SEM_NSEMS_MAX && semtab[i].ino != ino; i++);
34         if (i==SEM_NSEMS_MAX) return 0;
35         if (semtab[i].refcnt == INT_MAX) return (sem_t *)-1;
36         semtab[i].refcnt++;
37         return semtab[i].sem;
38 }
39
40 sem_t *sem_open(const char *name, int flags, ...)
41 {
42         va_list ap;
43         mode_t mode;
44         unsigned value;
45         int fd, tfd, dir;
46         sem_t newsem;
47         void *map;
48         char tmp[64];
49         struct timespec ts;
50         struct stat st;
51         int i;
52
53         while (*name=='/') name++;
54         if (strchr(name, '/')) {
55                 errno = EINVAL;
56                 return SEM_FAILED;
57         }
58
59         pthread_once(&once, init);
60         if (!semtab) {
61                 errno = ENOMEM;
62                 return SEM_FAILED;
63         }
64
65         if (flags & O_CREAT) {
66                 va_start(ap, flags);
67                 mode = va_arg(ap, mode_t) & 0666;
68                 value = va_arg(ap, unsigned);
69                 va_end(ap);
70                 if (value > SEM_VALUE_MAX) {
71                         errno = EINVAL;
72                         return SEM_FAILED;
73                 }
74                 sem_init(&newsem, 0, value);
75                 clock_gettime(CLOCK_REALTIME, &ts);
76                 snprintf(tmp, sizeof(tmp), "/dev/shm/%p-%p-%d-%d",
77                         &name, name, (int)getpid(), (int)ts.tv_nsec);
78                 tfd = open(tmp, O_CREAT|O_EXCL|O_RDWR, mode);
79                 if (tfd<0) return SEM_FAILED;
80                 dir = open("/dev/shm", O_DIRECTORY|O_RDONLY);
81                 if (dir<0 || write(tfd,&newsem,sizeof newsem)!=sizeof newsem) {
82                         if (dir >= 0) close(dir);
83                         close(tfd);
84                         unlink(tmp);
85                         return SEM_FAILED;
86                 }
87         }
88
89         flags &= ~O_ACCMODE;
90         flags |= O_RDWR;
91
92         pthread_spin_lock(&lock);
93
94         for (;;) {
95                 if (!(flags & O_EXCL)) {
96                         fd = shm_open(name, flags&~O_CREAT, mode);
97                         if (fd >= 0 || errno != ENOENT) {
98                                 if (flags & O_CREAT) {
99                                         close(dir);
100                                         close(tfd);
101                                         unlink(tmp);
102                                 }
103                                 if (fd >= 0 && fstat(fd, &st) < 0) {
104                                         close(fd);
105                                         fd = -1;
106                                 }
107                                 if (fd < 0) {
108                                         pthread_spin_unlock(&lock);
109                                         return SEM_FAILED;
110                                 }
111                                 if ((map = find_map(st.st_ino))) {
112                                         pthread_spin_unlock(&lock);
113                                         close(fd);
114                                         if (map == (sem_t *)-1)
115                                                 return SEM_FAILED;
116                                         return map;
117                                 }
118                                 break;
119                         }
120                 }
121                 if (!(flags & O_CREAT)) {
122                         pthread_spin_unlock(&lock);
123                         return SEM_FAILED;
124                 }
125                 if (!linkat(AT_FDCWD, tmp, dir, name, 0)) {
126                         fd = tfd;
127                         close(dir);
128                         unlink(tmp);
129                         break;
130                 }
131                 if ((flags & O_EXCL) || errno != EEXIST) {
132                         close(dir);
133                         close(tfd);
134                         unlink(tmp);
135                         return SEM_FAILED;
136                 }
137         }
138         if (fstat(fd, &st) < 0) {
139                 pthread_spin_unlock(&lock);
140                 close(fd);
141                 return SEM_FAILED;
142         }
143         if (semcnt == SEM_NSEMS_MAX) {
144                 pthread_spin_unlock(&lock);
145                 close(fd);
146                 errno = EMFILE;
147                 return SEM_FAILED;
148         }
149         for (i=0; i<SEM_NSEMS_MAX && semtab[i].sem; i++);
150         map = mmap(0, sizeof(sem_t), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
151         close(fd);
152         if (map == MAP_FAILED) {
153                 pthread_spin_unlock(&lock);
154                 return SEM_FAILED;
155         }
156         semtab[i].ino = st.st_ino;
157         semtab[i].sem = map;
158         semtab[i].refcnt = 1;
159         pthread_spin_unlock(&lock);
160         return map;
161 }
162
163 int sem_close(sem_t *sem)
164 {
165         int i;
166         pthread_spin_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         pthread_spin_unlock(&lock);
173         return munmap(sem, sizeof *sem);
174 }