/* * 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; }