about summary refs log tree commit diff
path: root/src/thread/sem_open.c
blob: 55d5ef6b7808bae8e070e1d979d0b67083407375 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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;
}