about summary refs log tree commit diff
path: root/src/thread/sem_open.c
diff options
context:
space:
mode:
authorRich Felker <dalias@aerifal.cx>2011-03-04 00:45:59 -0500
committerRich Felker <dalias@aerifal.cx>2011-03-04 00:45:59 -0500
commit6fc5fdbdc70dd17ea8e681a361fb4dae541ee953 (patch)
treeeee28ceddc2de85e528e50340e045a875e6a8e60 /src/thread/sem_open.c
parent03dcc3417ce4388a652bdd053cb2b6af860daf00 (diff)
downloadmusl-6fc5fdbdc70dd17ea8e681a361fb4dae541ee953.tar.gz
musl-6fc5fdbdc70dd17ea8e681a361fb4dae541ee953.tar.xz
musl-6fc5fdbdc70dd17ea8e681a361fb4dae541ee953.zip
implement POSIX semaphores
Diffstat (limited to 'src/thread/sem_open.c')
-rw-r--r--src/thread/sem_open.c116
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;
+}