| #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[1]; |
| |
| #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; |
| } |