feat(wait): wait/waitpid/waitid and status macros
This commit is contained in:
@@ -0,0 +1,207 @@
|
|||||||
|
#ifndef VLIBC_SYS_WAIT_H
|
||||||
|
#define VLIBC_SYS_WAIT_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — <sys/wait.h>.
|
||||||
|
*
|
||||||
|
* Child-process status collection (POSIX.1-2008) and the wait status macros.
|
||||||
|
* Everything here is a pass-through to the kernel: wait/waitpid/wait3/wait4
|
||||||
|
* ride SYS_wait4, waitid rides SYS_waitid, and failures are reported as -1
|
||||||
|
* (or the child pid on success) with errno set by the syscall layer.
|
||||||
|
*
|
||||||
|
* Level 1 (onlyposix): wait, waitpid, waitid + the W* status macros.
|
||||||
|
* Level 2 (muslmimic): wait3, wait4 (XSI).
|
||||||
|
*
|
||||||
|
* The W* decode macros follow the kernel's wait status encoding: bits 0-6
|
||||||
|
* hold the terminating signal (or 0x7f for a stop), bit 7 the core-dump
|
||||||
|
* flag, bits 8-15 the exit status, and the word 0xffff marks a continued
|
||||||
|
* child. WEXITED/WSTOPPED/WNOWAIT are the waitid-only option bits (XSI;
|
||||||
|
* kernel-identical values) and WCOREDUMP is an XSI/Linux extension (bit
|
||||||
|
* 0x80) kept alongside the POSIX set for source compatibility.
|
||||||
|
*
|
||||||
|
* None of the declarations carry an intent attribute: every function has
|
||||||
|
* kernel-visible side effects and reports failures through errno.
|
||||||
|
*
|
||||||
|
* <signal.h> does not exist yet (a later todo owns it). The minimal
|
||||||
|
* siginfo_t below and the CLD_* constants live here under guards so that
|
||||||
|
* header can take them over without conflict; see the notes by each.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <vlibc/features.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Selector type for waitid(). POSIX defines idtype_t as an integer type and
|
||||||
|
* places it in <sys/types.h>, which does not define it yet; it is provided
|
||||||
|
* here under a VLIBC_ guard so sys/types.h can take it over later without a
|
||||||
|
* redefinition. int matches the kernel's `which` argument on x86_64.
|
||||||
|
*/
|
||||||
|
#ifndef VLIBC_DEFINED_IDTYPE_T
|
||||||
|
#define VLIBC_DEFINED_IDTYPE_T
|
||||||
|
typedef int idtype_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* waitid() idtype selectors (kernel-identical). */
|
||||||
|
#define P_ALL 0 /* wait for any child */
|
||||||
|
#define P_PID 1 /* wait for the specific child */
|
||||||
|
#define P_PGID 2 /* wait for any child in the process group */
|
||||||
|
|
||||||
|
/* wait/waitpid option bits (kernel-identical). */
|
||||||
|
#define WNOHANG 1 /* do not block; return 0 if no child has exited */
|
||||||
|
#define WUNTRACED 2 /* also report stopped children */
|
||||||
|
#define WCONTINUED 8 /* also report continued children */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* waitid-only option bits (kernel-identical; XSI). WSTOPPED shares its
|
||||||
|
* value with WUNTRACED, so the two names are interchangeable where both
|
||||||
|
* apply.
|
||||||
|
*/
|
||||||
|
#define WEXITED 4 /* wait for exited children */
|
||||||
|
#define WSTOPPED 2 /* wait for stopped children */
|
||||||
|
#define WNOWAIT 0x01000000 /* report but do not reap */
|
||||||
|
|
||||||
|
/* Status decode macros. The argument is the raw wait status word. */
|
||||||
|
|
||||||
|
/* True when the child terminated normally via exit() or _exit(). */
|
||||||
|
#define WIFEXITED(s) (((s) & 0x7f) == 0)
|
||||||
|
|
||||||
|
/* Exit status of a normally terminated child (WIFEXITED true). */
|
||||||
|
#define WEXITSTATUS(s) (((s) & 0xff00) >> 8)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* True when the child was killed by a signal. The 0x7f exclusion keeps the
|
||||||
|
* stop code (0x7f) from being misread as a terminating signal.
|
||||||
|
*/
|
||||||
|
#define WIFSIGNALED(s) (((s) & 0x7f) != 0 && ((s) & 0x7f) != 0x7f)
|
||||||
|
|
||||||
|
/* Number of the signal that killed the child (WIFSIGNALED true). */
|
||||||
|
#define WTERMSIG(s) ((s) & 0x7f)
|
||||||
|
|
||||||
|
/* True when the child is stopped by a signal (WUNTRACED). */
|
||||||
|
#define WIFSTOPPED(s) (((s) & 0xff) == 0x7f)
|
||||||
|
|
||||||
|
/* Number of the signal that stopped the child (WIFSTOPPED true). */
|
||||||
|
#define WSTOPSIG(s) WEXITSTATUS(s)
|
||||||
|
|
||||||
|
/* True when the child was resumed by SIGCONT (WCONTINUED). */
|
||||||
|
#define WIFCONTINUED(s) ((s) == 0xffff)
|
||||||
|
|
||||||
|
/* True when the killed child dumped core (XSI/Linux extension, bit 0x80). */
|
||||||
|
#define WCOREDUMP(s) (((s) & 0x80) != 0)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* si_code values reported by waitid() (kernel-identical). POSIX defines
|
||||||
|
* these in <signal.h>, which does not exist yet; guarded per name so the
|
||||||
|
* future signal.h can define them without a redefinition warning.
|
||||||
|
*/
|
||||||
|
#ifndef CLD_EXITED
|
||||||
|
#define CLD_EXITED 1 /* child exited normally */
|
||||||
|
#define CLD_KILLED 2 /* child killed by a signal */
|
||||||
|
#define CLD_DUMPED 3 /* child killed by a signal and dumped core */
|
||||||
|
#define CLD_TRAPPED 4 /* child stopped by a trace event */
|
||||||
|
#define CLD_STOPPED 5 /* child stopped by a signal */
|
||||||
|
#define CLD_CONTINUED 6 /* child resumed by SIGCONT */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Minimal siginfo_t, sized and laid out to match the kernel's x86_64
|
||||||
|
* siginfo_t (128 bytes) for the fields waitid() fills: si_signo, si_errno,
|
||||||
|
* si_code at offsets 0/4/8, then the wait-fields si_pid/si_uid/si_status at
|
||||||
|
* offsets 16/20/24. The union is 8-aligned (si_utime/si_stime are 8-byte
|
||||||
|
* clock_t in both the kernel and glibc layouts), so it starts at offset 16
|
||||||
|
* with implicit padding after si_code. The kernel copies the full 128 bytes,
|
||||||
|
* so the size must stay 128; the pad member guarantees it and the static
|
||||||
|
* asserts pin the layout. The signal-handling fields (si_addr, si_value,
|
||||||
|
* timers, ...) are deliberately absent — the future <signal.h> owns the
|
||||||
|
* complete siginfo_t and must reconcile this guard.
|
||||||
|
*/
|
||||||
|
#ifndef VLIBC_INTERNAL_SIGINFO_DEFINED
|
||||||
|
#define VLIBC_INTERNAL_SIGINFO_DEFINED
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int si_signo;
|
||||||
|
int si_errno;
|
||||||
|
int si_code;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
pid_t si_pid; /* 16 */
|
||||||
|
uid_t si_uid; /* 20 */
|
||||||
|
int si_status; /* 24 */
|
||||||
|
long si_utime; /* 32 — 8-byte clock_t, matches the kernel/glibc ABI */
|
||||||
|
long si_stime; /* 40 */
|
||||||
|
};
|
||||||
|
int vlibc_siginfo_pad[28]; /* union sized 112 so the struct stays 128 */
|
||||||
|
};
|
||||||
|
} siginfo_t;
|
||||||
|
|
||||||
|
_Static_assert(sizeof(siginfo_t) == 128, "siginfo_t must match the kernel size");
|
||||||
|
_Static_assert(offsetof(siginfo_t, si_pid) == 16, "si_pid must sit at offset 16");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Level 1 (POSIX base). */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait for any child to terminate or stop and store its status in
|
||||||
|
* *stat_loc (NULL skips the store); return the child pid, or -1 with
|
||||||
|
* errno set. Equivalent to waitpid(-1, stat_loc, 0).
|
||||||
|
*/
|
||||||
|
pid_t
|
||||||
|
wait(int *stat_loc);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait for the child identified by pid (-1: any child, 0: any child in the
|
||||||
|
* calling process group, < -1: any child in the process group -pid) and
|
||||||
|
* store its status in *stat_loc (NULL skips the store). options are the
|
||||||
|
* WNOHANG/WUNTRACED/WCONTINUED bits. Return the child pid, 0 when WNOHANG
|
||||||
|
* found nothing, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
pid_t
|
||||||
|
waitpid(pid_t pid, int *stat_loc, int options);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait for a child selected by idtype/id (P_ALL, P_PID, P_PGID) and fill
|
||||||
|
* *infop with the siginfo details (si_pid, si_uid, si_status and a CLD_*
|
||||||
|
* si_code); options are the WEXITED/WSTOPPED/WCONTINUED/WNOHANG/WNOWAIT
|
||||||
|
* bits. Return 0, or -1 with errno set. infop must point to at least
|
||||||
|
* 128 bytes (the kernel writes a full siginfo).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
/* Level 2 (muslmimic): XSI. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* struct rusage is defined by <sys/resource.h>, which does not exist yet;
|
||||||
|
* a forward declaration is enough to pass a pointer through to the kernel.
|
||||||
|
*/
|
||||||
|
struct rusage;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like waitpid(-1, stat_loc, options), and additionally store resource
|
||||||
|
* usage in *rusage (NULL skips the store); return the child pid, or -1
|
||||||
|
* with errno set. XSI.
|
||||||
|
*/
|
||||||
|
pid_t
|
||||||
|
wait3(int *stat_loc, int options, struct rusage *rusage);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like waitpid(), and additionally store resource usage in *rusage (NULL
|
||||||
|
* skips the store); return the child pid, or -1 with errno set. XSI.
|
||||||
|
*/
|
||||||
|
pid_t
|
||||||
|
wait4(pid_t pid, int *stat_loc, int options, struct rusage *rusage);
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* VLIBC_SYS_WAIT_H */
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* wait/waitpid/waitid/wait3/wait4 — child-process status collection.
|
||||||
|
*
|
||||||
|
* All four are pass-throughs to the kernel: wait/waitpid/wait3/wait4 ride
|
||||||
|
* SYS_wait4 (the kernel does the pid selection, the option masking, and the
|
||||||
|
* status-word encoding), waitid rides SYS_waitid (which fills a full
|
||||||
|
* 128-byte siginfo with the si_pid/si_uid/si_status details and a CLD_*
|
||||||
|
* si_code). The raw result goes through syscall_ret(), so a child pid (or
|
||||||
|
* waitid's 0) is returned on success and -1 with errno set on error;
|
||||||
|
* WNOHANG-with-no-child returns 0 from the kernel untouched.
|
||||||
|
*
|
||||||
|
* No argument inspection is needed: the kernel interprets pid == -1 (any
|
||||||
|
* child), 0 (own process group) and pid < -1 (process group) for wait4, and
|
||||||
|
* the which/id pair for waitid, exactly as POSIX specifies. The options are
|
||||||
|
* the kernel-identical W* bits from <sys/wait.h>. rusage passes through
|
||||||
|
* verbatim for wait3/wait4 (NULL is legal and skips the fill).
|
||||||
|
*/
|
||||||
|
|
||||||
|
pid_t
|
||||||
|
wait(int *stat_loc)
|
||||||
|
{
|
||||||
|
return (pid_t)syscall_ret(__syscall4(SYS_wait4, -1, (long)stat_loc, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t
|
||||||
|
waitpid(pid_t pid, int *stat_loc, int options)
|
||||||
|
{
|
||||||
|
return (pid_t)syscall_ret(__syscall4(SYS_wait4, pid, (long)stat_loc, options, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall5(SYS_waitid, idtype, id, (long)infop, options, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
/* Level 2 (muslmimic): XSI wait3/wait4. */
|
||||||
|
|
||||||
|
pid_t
|
||||||
|
wait3(int *stat_loc, int options, struct rusage *rusage)
|
||||||
|
{
|
||||||
|
return (pid_t)syscall_ret(__syscall4(SYS_wait4, -1, (long)stat_loc, options, (long)rusage));
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t
|
||||||
|
wait4(pid_t pid, int *stat_loc, int options, struct rusage *rusage)
|
||||||
|
{
|
||||||
|
return (pid_t)syscall_ret(__syscall4(SYS_wait4, pid, (long)stat_loc, options, (long)rusage));
|
||||||
|
}
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,380 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user