22#include <sys/syscall.h>
28 unsigned short* array;
32#define CHECK(expression) \
34 if (!(expression)) { \
35 printf("IPC-SEM: line %d failed: %s (errno=%d)\n", __LINE__, #expression, errno); \
40static int reap(pid_t child) {
44 result = waitpid(child, &status, 0);
45 }
while (result < 0 && errno == EINTR);
46 if (result == child && WIFEXITED(status) && !WEXITSTATUS(status))
48 printf(
"IPC-SEM: child %d returned %d status=%#x errno=%d\n", child, result,
49 result == child ? status : 0, errno);
53static int wait_value(
int id,
int command,
int value) {
54 struct timespec start, now, pause = {0, 1000000};
55 if (clock_gettime(CLOCK_MONOTONIC, &start))
58 int result = semctl(
id, 0, command);
61 if (result < 0 || clock_gettime(CLOCK_MONOTONIC, &now))
63 nanosleep(&pause, NULL);
64 }
while (now.tv_sec - start.tv_sec < 3);
68static void interrupted(
int signal_number) {
72static int waiter_contract(
int id,
int zero,
int expected_error,
int remove_set) {
78 struct sigaction action = {.sa_handler = interrupted};
79 sigemptyset(&action.sa_mask);
80 if (sigaction(SIGUSR1, &action, NULL))
82 struct sembuf operation = {0, zero ? 0 : -1, 0};
83 const int result = semop(
id, &operation, 1);
84 _exit(expected_error ? !(result == -1 && errno == expected_error) : result != 0);
86 const int command = zero ? GETZCNT : GETNCNT;
87 int ok = wait_value(
id, command, 1);
90 ok = semctl(
id, 0, IPC_RMID) == 0;
91 else if (expected_error == EINTR)
92 ok = kill(child, SIGUSR1) == 0;
94 ok = semctl(
id, 0, SETVAL, (
union test_semun){.val = zero ? 0 : 1}) == 0;
98 ok = reap(child) && ok;
99 if (!remove_set && semctl(
id, 0, command) != 0)
104static void* undo_worker(
void* argument) {
105 struct sembuf operation = {0, -1, SEM_UNDO};
106 return (
void*)(intptr_t)semop(*(
int*)argument, &operation, 1);
113 struct sembuf operation;
116_Static_assert(offsetof(
struct raw_undo, tid) == 4,
"clone child TID offset");
117_Static_assert(offsetof(
struct raw_undo, result) == 8,
"semop result offset");
118_Static_assert(offsetof(
struct raw_undo, operation) == 12,
"semop operation offset");
122long ipc_sem_raw_clone(
void* stack,
struct raw_undo* state);
123#define ASM_VALUE_INNER(value) #value
124#define ASM_VALUE(value) ASM_VALUE_INNER(value)
126 ".global ipc_sem_raw_clone\n"
127 ".hidden ipc_sem_raw_clone\n"
128 ".type ipc_sem_raw_clone,@function\n"
129 "ipc_sem_raw_clone:\n"
133 "mov $(" ASM_VALUE(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD |
134 CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID)
"),%edi\n"
138 "mov $" ASM_VALUE(SYS_clone)
",%eax\n"
145 "mov $" ASM_VALUE(SYS_semop)
",%eax\n"
149 "mov $" ASM_VALUE(SYS_exit)
",%eax\n"
153 ".size ipc_sem_raw_clone,.-ipc_sem_raw_clone\n");
155#undef ASM_VALUE_INNER
157static int undo_contracts(
int id) {
159 pid_t child = fork();
166 if (pthread_create(&thread, NULL, undo_worker, &
id) || pthread_join(thread, &result) ||
167 result || semctl(
id, 0, GETVAL) != 0)
171 if (!reap(child) || semctl(
id, 0, GETVAL) != 1)
174 struct sembuf operation = {0, -1, SEM_UNDO};
175 if (semop(
id, &operation, 1))
184 if (!reap(child) || semctl(
id, 0, GETVAL) != 0 ||
185 semctl(
id, 0, SETVAL, (
union test_semun){.val = 1}))
194 const size_t size = 65536;
195 void* stack = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
196 if (stack == MAP_FAILED)
198 struct raw_undo state = {id, -1, -1, {0, -1, SEM_UNDO}};
199 const long tid = ipc_sem_raw_clone((
char*)stack + size, &state);
201 printf(
"IPC-SEM: independent undo clone failed: %ld\n", tid);
204 while (__atomic_load_n(&state.tid, __ATOMIC_ACQUIRE))
206 const int value = semctl(
id, 0, GETVAL);
207 if (state.result || value != 1) {
208 printf(
"IPC-SEM: independent undo semop=%d value=%d\n", state.result, value);
214 if (!reap(child) || semctl(
id, 0, GETVAL) != 1)
218 int ready[2], release[2];
239 if (semop(
id, &operation, 1) || write(ready[1], &token, 1) != 1 ||
240 read(release[0], &token, 1) != 1)
248 read(ready[0], &token, 1) == 1 && semctl(
id, 0, SETVAL, (
union test_semun){.val = 4}) == 0;
251 ok = write(release[1], &token, 1) == 1;
254 kill(child, SIGKILL);
255 return reap(child) && ok && semctl(
id, 0, GETVAL) == 4;
258int ipc_test_semaphores(
void) {
259 int id = -1, keyed = -1;
260 id = semget(IPC_PRIVATE, 3, 0600);
262 struct semid_ds metadata;
263 CHECK(semctl(
id, 0, IPC_STAT, (
union test_semun){.buf = &metadata}) == 0);
264 CHECK(metadata.sem_nsems == 3 && !metadata.sem_otime && metadata.sem_ctime > 0);
265 CHECK(metadata.sem_perm.uid == geteuid() && (metadata.sem_perm.mode & 0777) == 0600);
266 struct seminfo information;
267 const int highest = semctl(0, 0, IPC_INFO, (
union test_semun){.info = &information});
268 CHECK(highest >= 0 && information.semmsl >= 3 && information.semvmx == 32767 &&
269 information.semopm >= 3);
271 for (
int index = 0; index <= highest; ++index) {
272 if (semctl(index, 0, SEM_STAT, (
union test_semun){.buf = &metadata}) ==
id) {
273 CHECK(metadata.sem_nsems == 3);
274 CHECK(semctl(index, 0, SEM_STAT_ANY, (
union test_semun){.buf = &metadata}) ==
id);
280 CHECK(semctl(0, 0, SEM_INFO, (
union test_semun){.info = &information}) >= 0 &&
281 information.semusz >= 1 && information.semaem >= 3);
283 key_t key = (key_t)(0x53450000u | (getpid() & 0xffff));
284 keyed = semget(key, 2, IPC_CREAT | IPC_EXCL | 0600);
286 CHECK(semget(key, 0, 0600) == keyed);
288 CHECK(semget(key, 2, IPC_CREAT | IPC_EXCL | 0600) == -1 && errno == EEXIST);
290 CHECK(semget(key, 3, 0600) == -1 && errno == EINVAL);
291 CHECK(semctl(keyed, 0, IPC_RMID) == 0);
294 CHECK(semget(key, 0, 0600) == -1 && errno == ENOENT);
296 unsigned short values[3] = {99, 99, 99};
297 CHECK(semctl(
id, 0, GETALL, (
union test_semun){.array = values}) == 0);
298 CHECK(!values[0] && !values[1] && !values[2]);
301 CHECK(semctl(
id, 0, SETALL, (
union test_semun){.array = values}) == 0);
302 CHECK(semctl(
id, 0, GETPID) == getpid());
306 CHECK(semctl(
id, 0, SETALL, (
union test_semun){.array = values}) == -1 && errno == ERANGE);
307 CHECK(semctl(
id, 0, GETVAL) == 2 && semctl(
id, 1, GETVAL) == 0);
309 struct sembuf failed_pair[2] = {{0, -1, 0}, {1, -1, IPC_NOWAIT}};
311 CHECK(semop(
id, failed_pair, 2) == -1 && errno == EAGAIN);
312 CHECK(semctl(
id, 0, GETVAL) == 2);
313 struct sembuf duplicate[3] = {{0, -1, 0}, {0, -1, 0}, {1, 2, 0}};
314 CHECK(semop(
id, duplicate, 3) == 0);
315 CHECK(semctl(
id, 0, GETVAL) == 0 && semctl(
id, 1, GETVAL) == 2);
316 struct sembuf out_of_range = {3, 1, IPC_NOWAIT};
318 CHECK(semop(
id, &out_of_range, 1) == -1 && errno == EFBIG);
320 CHECK(semop(
id, duplicate, 0) == -1 && errno == EINVAL);
321 struct sembuf zero = {0, 0, 0};
322 CHECK(semop(
id, &zero, 1) == 0);
323 CHECK(semctl(
id, 0, IPC_STAT, (
union test_semun){.buf = &metadata}) == 0 &&
324 metadata.sem_otime > 0);
325 metadata.sem_perm.mode = 0640;
326 CHECK(semctl(
id, 0, IPC_SET, (
union test_semun){.buf = &metadata}) == 0);
327 CHECK(semctl(
id, 0, IPC_STAT, (
union test_semun){.buf = &metadata}) == 0 &&
328 (metadata.sem_perm.mode & 0777) == 0640);
330 struct timespec duration = {0, 20000000};
331 const struct timespec original = duration;
332 struct sembuf timed = {2, -2, 0};
334 CHECK(semtimedop(
id, &timed, 1, &duration) == -1 && errno == EAGAIN);
335 CHECK(!memcmp(&duration, &original,
sizeof(duration)) && semctl(
id, 2, GETVAL) == 1);
336 duration.tv_nsec = 1000000000;
338 CHECK(semtimedop(
id, &timed, 1, &duration) == -1 && errno == EINVAL);
339 duration = (
struct timespec){0, 0};
341 CHECK(semtimedop(
id, &timed, 1, &duration) == -1 && errno == EAGAIN);
343 CHECK(waiter_contract(
id, 0, 0, 0));
344 CHECK(semctl(
id, 0, SETVAL, (
union test_semun){.val = 1}) == 0);
345 CHECK(waiter_contract(
id, 1, 0, 0));
346 CHECK(waiter_contract(
id, 0, EINTR, 0));
347 CHECK(semctl(
id, 0, SETVAL, (
union test_semun){.val = 1}) == 0);
348 CHECK(undo_contracts(
id));
351 pid_t child = fork();
355 if (setgid(65534) || setuid(65534))
358 if (semctl(
id, 0, GETVAL) != -1 || errno != EACCES)
361 _exit(!(semctl(
id, 0, IPC_RMID) == -1 && errno == EPERM));
365 CHECK(semctl(
id, 0, SETVAL, (
union test_semun){.val = 0}) == 0);
366 CHECK(waiter_contract(
id, 0, EIDRM, 1));
368 CHECK(semctl(
id, 0, GETVAL) == -1 && (errno == EINVAL || errno == EIDRM));
370 puts(
"IPC-SEM: values, atomic vectors, waits, metadata, removal and undo passed");
375 semctl(keyed, 0, IPC_RMID);
377 semctl(
id, 0, IPC_RMID);