#include <semaphore.h> #include <sys/mman.h> #include <limits.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <stdarg.h> #include <errno.h> #include <time.h> #include <stdio.h> #include <sys/stat.h> #include <stdlib.h> #include <pthread.h> #include "libc.h" char *__shm_mapname(const char *, char *); static struct { ino_t ino; sem_t *sem; int refcnt; } *semtab; static volatile 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, 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]; 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; } /* 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_CREAT|O_EXCL); pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs); /* 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; goto fail; } for (;;) { /* 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; } close(fd); break; } if (errno != ENOENT) goto fail; } 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 (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); goto fail; } close(fd); e = link(tmp, name) ? errno : 0; unlink(tmp); if (!e) break; munmap(map, sizeof(sem_t)); /* 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; } /* 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); LOCK(lock); semtab[slot].sem = 0; UNLOCK(lock); 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; }