Files
vlibc/tests/test_wait.c
T

381 lines
9.6 KiB
C

/*
* vlibc — wait family test (todo 23).
*
* Exercises wait/waitpid/waitid (+ L2 wait3/wait4) end to end against real
* children. Children are created with the RAW SYS_clone(SIGCHLD) syscall and
* exit with raw SYS_exit — deliberately NOT the fork() wrapper of the
* parallel todo 20, so this test stays independent of it.
*
* Default-mode scenarios (in run order):
*
* 1. waitpid(-1, &st, WNOHANG) with no children yet -> -1 (ECHILD).
* Runs FIRST: the test process must have no live children.
* 2. child exits 7 -> waitpid returns the child, WIFEXITED, WEXITSTATUS 7.
* 3. child exits 42 -> the QA happy path: WEXITSTATUS 42.
* 4. child killed via raw SYS_kill(SIGKILL) -> WIFSIGNALED, WTERMSIG 9,
* and WCOREDUMP false (SIGKILL never dumps core).
* 5. WNOHANG: child sleeps 100 ms via raw SYS_nanosleep -> waitpid(WNOHANG)
* returns 0 while it runs, then the blocking waitpid reaps it.
* 6. wait() == waitpid(-1): child exits 3 -> wait() reaps it, WEXITSTATUS 3.
* 7. waitid(P_PID, child, &info, WEXITED): returns 0, info.si_pid == child,
* si_signo == SIGCHLD, si_code == CLD_EXITED, si_status == 5.
*
* Level-2 gated section: wait4(child,...) and wait3(...) reaping exit 6/9.
*
* The negative path in scenario 1 makes the LIBRARY write errno
* (syscall_ret), which under a host-linked binary targets glibc's private
* dtv slot at %fs:0+8. In the default mode that call is bracketed with a
* save/restore of the slot (task 13 technique) — only vlibc/raw-syscall code
* runs between the write and the restore. The test itself NEVER reads
* errno; the negative is asserted on the return value. The -f mode runs
* the failure scenario alone and exits via raw SYS_exit_group (house
* pattern, tests/test_malloc.c).
*
* All diagnostics go through raw SYS_write (no stdio): under -Iinclude the
* vlibc public headers shadow GCC's internal ones, so a host header would
* not compile. Not part of the library proper; compiled manually for this
* todo (the tests/ + make check wiring is owned by a later todo).
*/
#include "../include/sys/wait.h"
#include "../src/internal/syscall.h"
/* Kernel-UAPI signal numbers, local to this test (signal.h is todo 28). */
#define T23_SIGCHLD 17
#define T23_SIGKILL 9
/* Kernel timespec (time.h is a later todo). */
struct t23_timespec
{
long tv_sec;
long tv_nsec;
};
static int failures;
/* Write a NUL-terminated string to fd via the raw syscall layer. The
* optimize attribute keeps GCC from lowering the length loop into a
* strlen call, which would leave a vlibc-owned symbol undefined in this
* host-linked standalone binary (house idiom, see src/string). */
static __attribute__((optimize("no-tree-loop-distribute-patterns"))) void
say(int fd, const char *s)
{
long n = 0;
while (s[n] != '\0')
{
n++;
}
__syscall3(SYS_write, fd, (long)s, n);
}
static void
check(int cond, const char *what)
{
if (cond)
{
say(1, "PASS: ");
say(1, what);
say(1, "\n");
}
else
{
say(2, "FAIL: ");
say(2, what);
say(2, "\n");
failures++;
}
}
/*
* Host-TCB slot-1 bracket: the library's errno write on a negative path
* lands at %fs:0+8, glibc's dtv pointer. Save and restore it around each
* such call; only vlibc/raw-syscall code runs in between (task 13
* technique).
*/
static unsigned long
tcb_slot1(void)
{
return *(unsigned long *)((char *)__builtin_thread_pointer() + 8);
}
static void
tcb_slot1_set(unsigned long v)
{
*(unsigned long *)((char *)__builtin_thread_pointer() + 8) = v;
}
/* Terminate the current thread via the raw syscall (never returns). */
static void
child_exit(int code)
{
__syscall1(SYS_exit, code);
__builtin_unreachable();
}
/* fork-equivalent via raw clone: parent gets the child pid, child gets 0. */
static pid_t
spawn_child(void)
{
return (pid_t)__syscall5(SYS_clone, T23_SIGCHLD, 0, 0, 0, 0);
}
/* Reap a child that has already been spawned (pid > 0 in the parent). */
static void
reap_exited(const char *what, pid_t pid, int want_status)
{
int st = -1;
pid_t r = waitpid(pid, &st, 0);
check(r == pid && WIFEXITED(st) && WEXITSTATUS(st) == want_status, what);
}
/*
* waitpid(-1, &st, WNOHANG) with no children -> -1 (ECHILD). Must run before
* any child is created for the result to be deterministic.
*/
static void
scenario_no_children(void)
{
int st = 0;
unsigned long saved = tcb_slot1();
check(waitpid(-1, &st, WNOHANG) == -1, "waitpid WNOHANG with no children -> -1");
tcb_slot1_set(saved);
}
/* Child exits 7 -> WIFEXITED && WEXITSTATUS == 7. */
static void
scenario_exit_7(void)
{
pid_t pid = spawn_child();
if (pid == 0)
{
child_exit(7);
}
if (pid > 0)
{
reap_exited("waitpid reaps exit(7): WIFEXITED && WEXITSTATUS 7", pid, 7);
}
else
{
check(0, "clone for exit(7) child");
}
}
/* QA happy path: child exits 42 -> WEXITSTATUS == 42. */
static void
scenario_exit_42(void)
{
pid_t pid = spawn_child();
if (pid == 0)
{
child_exit(42);
}
if (pid > 0)
{
reap_exited("waitpid reaps exit(42): WEXITSTATUS 42", pid, 42);
}
else
{
check(0, "clone for exit(42) child");
}
}
/* Child killed by SIGKILL -> WIFSIGNALED && WTERMSIG == 9, no core dump. */
static void
scenario_killed(void)
{
int st = -1;
pid_t pid = spawn_child();
if (pid == 0)
{
/* Stay alive until the parent's SIGKILL arrives (10 s is plenty). */
struct t23_timespec ts = {.tv_sec = 10, .tv_nsec = 0};
__syscall2(SYS_nanosleep, (long)&ts, 0);
child_exit(0);
}
if (pid > 0)
{
pid_t r;
__syscall2(SYS_kill, pid, T23_SIGKILL);
r = waitpid(pid, &st, 0);
check(r == pid && WIFSIGNALED(st) && WTERMSIG(st) == T23_SIGKILL && !WCOREDUMP(st),
"waitpid reaps SIGKILLed child: WIFSIGNALED && WTERMSIG 9");
}
else
{
check(0, "clone for SIGKILL child");
}
}
/* WNOHANG returns 0 while the child runs; the blocking waitpid then reaps. */
static void
scenario_wnohang(void)
{
int st = -1;
pid_t pid = spawn_child();
if (pid == 0)
{
struct t23_timespec ts = {.tv_sec = 0, .tv_nsec = 100000000L}; /* 100 ms */
__syscall2(SYS_nanosleep, (long)&ts, 0);
child_exit(0);
}
if (pid > 0)
{
pid_t r;
/* The child cannot have exited yet: 100 ms of nanosleep remain. */
r = waitpid(pid, &st, WNOHANG);
check(r == 0, "waitpid WNOHANG returns 0 while child still runs");
r = waitpid(pid, &st, 0);
check(r == pid && WIFEXITED(st) && WEXITSTATUS(st) == 0,
"blocking waitpid reaps the child after WNOHANG");
}
else
{
check(0, "clone for WNOHANG child");
}
}
/* wait() == waitpid(-1): reaps any child. */
static void
scenario_wait_any(void)
{
int st = -1;
pid_t pid = spawn_child();
if (pid == 0)
{
child_exit(3);
}
if (pid > 0)
{
pid_t r = wait(&st);
check(r == pid && WIFEXITED(st) && WEXITSTATUS(st) == 3,
"wait() reaps any child: WEXITSTATUS 3");
}
else
{
check(0, "clone for wait() child");
}
}
/* waitid(P_PID, ...) fills siginfo with the reaped child's details. */
static void
scenario_waitid(void)
{
pid_t pid = spawn_child();
if (pid == 0)
{
child_exit(5);
}
if (pid > 0)
{
siginfo_t info = {0};
check(waitid(P_PID, (id_t)pid, &info, WEXITED) == 0, "waitid WEXITED returns 0");
check(info.si_pid == pid, "waitid info.si_pid == child");
check(info.si_signo == T23_SIGCHLD, "waitid info.si_signo == SIGCHLD");
check(info.si_code == CLD_EXITED, "waitid info.si_code == CLD_EXITED");
check(info.si_status == 5, "waitid info.si_status == exit status");
}
else
{
check(0, "clone for waitid child");
}
}
#if VLIBC_LEVEL_GE(2)
/* Level 2 (muslmimic): wait3/wait4 pass-through (NULL rusage is legal). */
static void
scenario_wait3_wait4(void)
{
int st = -1;
pid_t pid = spawn_child();
if (pid == 0)
{
child_exit(6);
}
if (pid > 0)
{
pid_t r = wait4(pid, &st, 0, 0);
check(r == pid && WIFEXITED(st) && WEXITSTATUS(st) == 6,
"wait4 reaps the child: WEXITSTATUS 6");
}
else
{
check(0, "clone for wait4 child");
return;
}
pid = spawn_child();
if (pid == 0)
{
child_exit(9);
}
if (pid > 0)
{
pid_t r = wait3(&st, 0, 0);
check(r == pid && WIFEXITED(st) && WEXITSTATUS(st) == 9,
"wait3 reaps any child: WEXITSTATUS 9");
}
else
{
check(0, "clone for wait3 child");
}
}
#endif /* VLIBC_LEVEL_GE(2) */
/* The failure scenario, run alone under -f (writes errno; see top). */
static int
failure_scenarios(void)
{
scenario_no_children();
return failures;
}
int
main(int argc, char **argv)
{
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
{
/*
* The failure scenario writes errno inside the library; under the
* host libc that slot is glibc's private TLS state, so leave via
* the raw syscall without running host cleanup.
*/
int rc = failure_scenarios();
__syscall1(SYS_exit_group, rc);
return rc; /* not reached */
}
scenario_no_children(); /* first: the process must have no children */
scenario_exit_7();
scenario_exit_42();
scenario_killed();
scenario_wnohang();
scenario_wait_any();
scenario_waitid();
#if VLIBC_LEVEL_GE(2)
scenario_wait3_wait4();
#endif
return failures != 0;
}