| //===-- Integration test for sys/sem --------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "src/__support/threads/sleep.h" |
| #include "src/fcntl/open.h" |
| #include "src/signal/kill.h" |
| #include "src/stdlib/exit.h" |
| #include "src/sys/ipc/ftok.h" |
| #include "src/sys/sem/semctl.h" |
| #include "src/sys/sem/semget.h" |
| #include "src/sys/sem/semop.h" |
| #include "src/sys/wait/waitpid.h" |
| #include "src/unistd/close.h" |
| #include "src/unistd/fork.h" |
| #include "src/unistd/unlink.h" |
| |
| #include "test/IntegrationTest/test.h" |
| |
| #include "hdr/fcntl_macros.h" |
| #include "hdr/sys_ipc_macros.h" |
| #include "hdr/sys_sem_macros.h" |
| #include "hdr/types/struct_sembuf.h" |
| |
| #include <errno.h> |
| #include <signal.h> |
| #include <sys/wait.h> |
| |
| // child try IPC_CREAT|IPC_EXCL, |
| // but expect EEXIST then fall back to get the existing semaphore. |
| static int sem_joiner_get(key_t semkey) { |
| // this expect to fail and return -1 because we used the same key |
| // and this semaphore has been initialized. |
| int sid = LIBC_NAMESPACE::semget(semkey, 1, IPC_CREAT | IPC_EXCL | 0666); |
| if (sid == -1 && errno == EEXIST) |
| // get the initialized semaphore id |
| sid = LIBC_NAMESPACE::semget(semkey, 0, 0); |
| return sid; |
| } |
| |
| // fork a child that acquires the semaphore and hold untill killed. |
| // returns child pid. |
| static pid_t fork_sem_holder(key_t semkey) { |
| pid_t pid = LIBC_NAMESPACE::fork(); |
| if (pid == 0) { |
| int sid = sem_joiner_get(semkey); |
| if (sid < 0) |
| LIBC_NAMESPACE::exit(1); |
| // try acquire and hold |
| // set flag to UNDO if process terminate unexpectedly |
| struct sembuf acq = {0, -1, SEM_UNDO}; |
| if (LIBC_NAMESPACE::semop(sid, &acq, 1) != 0) |
| LIBC_NAMESPACE::exit(2); |
| |
| // hold the semaphore until killed by parent. |
| while (1) |
| LIBC_NAMESPACE::sleep_briefly(); |
| } |
| return pid; |
| } |
| |
| // fork a child that tries to acquire (IPC_NOWAIT). |
| // exit code: 0 = acquired, 1 = EAGAIN (full, try later again), 2+ = error. |
| static pid_t fork_sem_try_acquire(key_t semkey) { |
| pid_t pid = LIBC_NAMESPACE::fork(); |
| if (pid == 0) { |
| int sid = sem_joiner_get(semkey); |
| if (sid < 0) |
| LIBC_NAMESPACE::exit(3); |
| // try acquire |
| struct sembuf acq = {0, -1, SEM_UNDO | IPC_NOWAIT}; |
| |
| if (LIBC_NAMESPACE::semop(sid, &acq, 1) == 0) |
| LIBC_NAMESPACE::exit(0); // acquired |
| |
| // exit with code 1 if EAGAIN |
| LIBC_NAMESPACE::exit(errno == EAGAIN ? 1 : 2); |
| } |
| return pid; |
| } |
| |
| // wait for child to terminate. |
| static int wait_for_child(pid_t pid) { |
| int status; |
| LIBC_NAMESPACE::waitpid(pid, &status, 0); |
| return status; |
| } |
| |
| // semaphore gate that allows at most two processes to enter |
| void sem_gate_test() { |
| constexpr const char *TEST_FILE = "/tmp/sem_gate_test"; |
| int fd = LIBC_NAMESPACE::open(TEST_FILE, O_CREAT | O_WRONLY, 0666); |
| ASSERT_TRUE(fd >= 0); |
| ASSERT_ERRNO_SUCCESS(); |
| ASSERT_TRUE(LIBC_NAMESPACE::close(fd) == 0); |
| |
| key_t semkey = LIBC_NAMESPACE::ftok(TEST_FILE, 'a'); |
| ASSERT_TRUE(semkey != key_t(-1)); |
| ASSERT_ERRNO_SUCCESS(); |
| |
| // clean up stale semaphore from previous run because semaphore |
| // are kernel persistent, they can remain on the system if previous |
| // test fail and exit. |
| int old_semid = LIBC_NAMESPACE::semget(semkey, 1, 0); |
| if (old_semid != -1) |
| LIBC_NAMESPACE::semctl(old_semid, 0, IPC_RMID); |
| errno = 0; |
| |
| // create a semaphore |
| int semid = LIBC_NAMESPACE::semget(semkey, 1, IPC_CREAT | IPC_EXCL | 0666); |
| ASSERT_TRUE(semid >= 0); |
| ASSERT_ERRNO_SUCCESS(); |
| |
| // increment the first semaphore by 2 that allows two processes to access |
| struct sembuf sbuf = {0, 2, 0}; |
| ASSERT_TRUE(LIBC_NAMESPACE::semop(semid, &sbuf, 1) == 0); |
| |
| // verify the first semaphore value |
| ASSERT_TRUE(LIBC_NAMESPACE::semctl(semid, 0, GETVAL) == 2); |
| |
| // fork two children to hold the semaphore |
| pid_t holder1 = fork_sem_holder(semkey); |
| ASSERT_TRUE(holder1 > 0); |
| pid_t holder2 = fork_sem_holder(semkey); |
| ASSERT_TRUE(holder2 > 0); |
| |
| // wait until both children have acquired so semaphore value reaches 0. |
| while (LIBC_NAMESPACE::semctl(semid, 0, GETVAL) > 0) |
| LIBC_NAMESPACE::sleep_briefly(); |
| |
| // check my current semaphore value is 0 |
| ASSERT_TRUE(LIBC_NAMESPACE::semctl(semid, 0, GETVAL) == 0); |
| |
| // now the semaphore gate is full |
| // fork another process to try to acquire it |
| pid_t blocked = fork_sem_try_acquire(semkey); |
| ASSERT_TRUE(blocked > 0); |
| int blocked_status = wait_for_child(blocked); |
| // check the exit status |
| ASSERT_TRUE(WIFEXITED(blocked_status)); |
| ASSERT_EQ(WEXITSTATUS(blocked_status), 1); // EAGAIN (blocked) |
| |
| // kill the first holder to make one slot avalaible in semaphore |
| LIBC_NAMESPACE::kill(holder1, SIGKILL); |
| wait_for_child(holder1); |
| ASSERT_TRUE(LIBC_NAMESPACE::semctl(semid, 0, GETVAL) == 1); |
| |
| // then fork child again to try acquire |
| pid_t unblocked = fork_sem_try_acquire(semkey); |
| ASSERT_TRUE(unblocked > 0); |
| int unblocked_status = wait_for_child(unblocked); |
| // this child should get it since the slot is avalaible now |
| ASSERT_TRUE(WIFEXITED(unblocked_status)); |
| ASSERT_EQ(WEXITSTATUS(unblocked_status), 0); // acquired |
| |
| // cleanup |
| LIBC_NAMESPACE::kill(holder2, SIGKILL); |
| wait_for_child(holder2); |
| ASSERT_TRUE(LIBC_NAMESPACE::semctl(semid, 0, IPC_RMID) == 0); |
| ASSERT_TRUE(LIBC_NAMESPACE::unlink(TEST_FILE) == 0); |
| } |
| |
| TEST_MAIN([[maybe_unused]] int argc, [[maybe_unused]] char **argv, |
| [[maybe_unused]] char **envp) { |
| sem_gate_test(); |
| return 0; |
| } |