#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);
+
+ 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 (!(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);
+ e = link(tmp, name) ? errno : 0;
+ unlink(tmp);
+ if (!e) break;
+ /* 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);
+ 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;
}