in sem_open, don't leak vm mapping if fstat fails
[musl] / src / thread / sem_open.c
index 55d5ef6..8a72d4c 100644 (file)
 #include <time.h>
 #include <stdio.h>
 #include <sys/stat.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include "libc.h"
 
-static void *find_map(int fd)
-{
-       char c;
-       struct stat st;
-       FILE *f;
-       void *addr;
-       unsigned long long off, ino;
-       char buf[100];
+char *__shm_mapname(const char *, char *);
 
-       if (fstat(fd, &st) < 0) return 0;
-       if (!(f = fopen("/proc/self/maps", "rb"))) return 0;
-       
-       while (fgets(buf, sizeof buf, f)) {
-               sscanf(buf, "%lx-%*lx %*s %llx %*x:%*x %llu /dev/shm%c",
-                       (long *)&addr, &off, &ino, &c);
-               while (!strchr(buf, '\n') && fgets(buf, sizeof buf, f));
-               if (c!='/') continue;
-               c = 0;
-               if (!off && st.st_ino == ino) {
-                       fclose(f);
-                       return addr;
-               }
-       }
-       fclose(f);
-       return 0;
-}
+static struct {
+       ino_t ino;
+       sem_t *sem;
+       int refcnt;
+} *semtab;
+static int lock[2];
+
+#define FLAGS (O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NONBLOCK)
 
 sem_t *sem_open(const char *name, int flags, ...)
 {
        va_list ap;
        mode_t mode;
        unsigned value;
-       int fd, tfd, dir;
+       int fd, i, e, slot, first=1, cnt, cs;
        sem_t newsem;
        void *map;
        char tmp[64];
        struct timespec ts;
+       struct stat st;
+       char buf[NAME_MAX+10];
 
-       while (*name=='/') name++;
-       if (strchr(name, '/')) {
-               errno = EINVAL;
+       if (!(name = __shm_mapname(name, buf)))
+               return SEM_FAILED;
+
+       LOCK(lock);
+       /* Allocate table if we don't have one yet */
+       if (!semtab && !(semtab = calloc(sizeof *semtab, SEM_NSEMS_MAX))) {
+               UNLOCK(lock);
                return SEM_FAILED;
        }
 
-       if (flags & O_CREAT) {
-               va_start(ap, flags);
-               mode = va_arg(ap, mode_t) & 0666;
-               value = va_arg(ap, unsigned);
-               va_end(ap);
-               if (value > SEM_VALUE_MAX) {
-                       errno = EINVAL;
-                       return SEM_FAILED;
-               }
-               sem_init(&newsem, 0, value);
-               clock_gettime(CLOCK_REALTIME, &ts);
-               snprintf(tmp, sizeof(tmp), "/dev/shm/%p-%p-%d-%d",
-                       &name, name, (int)getpid(), (int)ts.tv_nsec);
-               tfd = open(tmp, O_CREAT|O_EXCL|O_RDWR, mode);
-               if (tfd<0) return SEM_FAILED;
-               dir = open("/dev/shm", O_DIRECTORY|O_RDONLY);
-               if (dir<0 || write(tfd,&newsem,sizeof newsem)!=sizeof newsem) {
-                       if (dir >= 0) close(dir);
-                       close(tfd);
-                       unlink(tmp);
-                       return SEM_FAILED;
-               }
+       /* Reserve a slot in case this semaphore is not mapped yet;
+        * this is necessary because there is no way to handle
+        * failures after creation of the file. */
+       slot = -1;
+       for (cnt=i=0; i<SEM_NSEMS_MAX; i++) {
+               cnt += semtab[i].refcnt;
+               if (!semtab[i].sem && slot < 0) slot = i;
+       }
+       /* Avoid possibility of overflow later */
+       if (cnt == INT_MAX || slot < 0) {
+               errno = EMFILE;
+               UNLOCK(lock);
+               return SEM_FAILED;
        }
+       /* Dummy pointer to make a reservation */
+       semtab[slot].sem = (sem_t *)-1;
+       UNLOCK(lock);
 
-       flags &= ~O_ACCMODE;
-       flags |= O_RDWR;
+       flags &= (O_CREAT|O_EXCL);
+
+       /* Early failure check for exclusive open; otherwise the case
+        * where the semaphore already exists is expensive. */
+       if (flags == (O_CREAT|O_EXCL) && access(name, F_OK) == 0) {
+               errno = EEXIST;
+               return SEM_FAILED;
+       }
+
+       pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
 
        for (;;) {
-               if (!(flags & O_EXCL)) {
-                       fd = shm_open(name, flags&~O_CREAT, mode);
-                       if (fd >= 0 || errno != ENOENT) {
-                               if (flags & O_CREAT) {
-                                       close(dir);
-                                       close(tfd);
-                                       unlink(tmp);
+               /* If exclusive mode is not requested, try opening an
+                * existing file first and fall back to creation. */
+               if (flags != (O_CREAT|O_EXCL)) {
+                       fd = open(name, FLAGS);
+                       if (fd >= 0) {
+                               if (fstat(fd, &st) < 0 ||
+                                   (map = mmap(0, sizeof(sem_t), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
+                                       close(fd);
+                                       goto fail;
                                }
-                               if (fd < 0) return SEM_FAILED;
-                               if ((map = find_map(fd)))
-                                       return map;
+                               close(fd);
                                break;
                        }
+                       if (errno != ENOENT)
+                               goto fail;
                }
-               if (!linkat(AT_FDCWD, tmp, dir, name, 0)) {
-                       fd = tfd;
-                       close(dir);
-                       unlink(tmp);
-                       break;
+               if (!(flags & O_CREAT))
+                       goto fail;
+               if (first) {
+                       first = 0;
+                       va_start(ap, flags);
+                       mode = va_arg(ap, mode_t) & 0666;
+                       value = va_arg(ap, unsigned);
+                       va_end(ap);
+                       if (value > SEM_VALUE_MAX) {
+                               errno = EINVAL;
+                               goto fail;
+                       }
+                       sem_init(&newsem, 1, value);
+               }
+               /* Create a temp file with the new semaphore contents
+                * and attempt to atomically link it as the new name */
+               clock_gettime(CLOCK_REALTIME, &ts);
+               snprintf(tmp, sizeof(tmp), "/dev/shm/tmp-%d", (int)ts.tv_nsec);
+               fd = open(tmp, O_CREAT|O_EXCL|FLAGS, mode);
+               if (fd < 0) {
+                       if (errno == EEXIST) continue;
+                       goto fail;
                }
-               if ((flags & O_EXCL) || errno != EEXIST) {
-                       close(dir);
-                       close(tfd);
+               if (write(fd, &newsem, sizeof newsem) != sizeof newsem || fstat(fd, &st) < 0 ||
+                   (map = mmap(0, sizeof(sem_t), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
+                       close(fd);
                        unlink(tmp);
-                       return SEM_FAILED;
+                       goto fail;
                }
+               close(fd);
+               if (link(tmp, name) == 0) break;
+               e = errno;
+               unlink(tmp);
+               /* Failure is only fatal when doing an exclusive open;
+                * otherwise, next iteration will try to open the
+                * existing file. */
+               if (e != EEXIST || flags == (O_CREAT|O_EXCL))
+                       goto fail;
        }
-       map = mmap(0, sizeof(sem_t), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
-       close(fd);
-       if (map == MAP_FAILED) return SEM_FAILED;
+
+       /* See if the newly mapped semaphore is already mapped. If
+        * so, unmap the new mapping and use the existing one. Otherwise,
+        * add it to the table of mapped semaphores. */
+       LOCK(lock);
+       for (i=0; i<SEM_NSEMS_MAX && semtab[i].ino != st.st_ino; i++);
+       if (i<SEM_NSEMS_MAX) {
+               munmap(map, sizeof(sem_t));
+               semtab[slot].sem = 0;
+               slot = i;
+               map = semtab[i].sem;
+       }
+       semtab[slot].refcnt++;
+       semtab[slot].sem = map;
+       semtab[slot].ino = st.st_ino;
+       UNLOCK(lock);
+       pthread_setcancelstate(cs, 0);
        return map;
+
+fail:
+       pthread_setcancelstate(cs, 0);
+       return SEM_FAILED;
+}
+
+int sem_close(sem_t *sem)
+{
+       int i;
+       LOCK(lock);
+       for (i=0; i<SEM_NSEMS_MAX && semtab[i].sem != sem; i++);
+       if (!--semtab[i].refcnt) {
+               semtab[i].sem = 0;
+               semtab[i].ino = 0;
+       }
+       UNLOCK(lock);
+       munmap(sem, sizeof *sem);
+       return 0;
 }