diff options
Diffstat (limited to 'src/thread/sem_open.c')
-rw-r--r-- | src/thread/sem_open.c | 116 |
1 files changed, 116 insertions, 0 deletions
diff --git a/src/thread/sem_open.c b/src/thread/sem_open.c new file mode 100644 index 00000000..55d5ef6b --- /dev/null +++ b/src/thread/sem_open.c @@ -0,0 +1,116 @@ +#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> + +static void *find_map(int fd) +{ + char c; + struct stat st; + FILE *f; + void *addr; + unsigned long long off, ino; + char buf[100]; + + 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; +} + +sem_t *sem_open(const char *name, int flags, ...) +{ + va_list ap; + mode_t mode; + unsigned value; + int fd, tfd, dir; + sem_t newsem; + void *map; + char tmp[64]; + struct timespec ts; + + while (*name=='/') name++; + if (strchr(name, '/')) { + errno = EINVAL; + 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; + } + } + + flags &= ~O_ACCMODE; + flags |= O_RDWR; + + 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 (fd < 0) return SEM_FAILED; + if ((map = find_map(fd))) + return map; + break; + } + } + if (!linkat(AT_FDCWD, tmp, dir, name, 0)) { + fd = tfd; + close(dir); + unlink(tmp); + break; + } + if ((flags & O_EXCL) || errno != EEXIST) { + close(dir); + close(tfd); + unlink(tmp); + return SEM_FAILED; + } + } + map = mmap(0, sizeof(sem_t), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); + close(fd); + if (map == MAP_FAILED) return SEM_FAILED; + return map; +} |