From 3ed376e15393fa59614ff29ae192415c9364d20a Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Thu, 16 Jul 2026 21:28:52 +0900 Subject: [PATCH] sys_wait: implement waitid (idtype_t/id_t/P_*/CLD_*, siginfo population) Adapted from the archived local P3-waitid.patch. Fills a POSIX gap needed by job-control-capable shells (brush/nix). waitid() maps (idtype,id)->waitpid pid, translates WEXITED/WSTOPPED/WCONTINUED/WNOWAIT options, and populates siginfo_t (si_pid/si_signo/si_code/si_status) from the wait status. --- src/header/sys_wait/mod.rs | 173 +++++++++++++++++++++++++++++++++---- 1 file changed, 158 insertions(+), 15 deletions(-) diff --git a/src/header/sys_wait/mod.rs b/src/header/sys_wait/mod.rs index 8aefb9f03a..65f9398e39 100644 --- a/src/header/sys_wait/mod.rs +++ b/src/header/sys_wait/mod.rs @@ -4,13 +4,38 @@ use crate::{ error::ResultExt, + header::signal::siginfo_t, out::Out, platform::{ - Pal, Sys, - types::{c_int, pid_t}, + ERRNO, Pal, Sys, + types::{c_int, id_t, pid_t}, }, }; +/// `idtype_t` for [`waitid`]. POSIX specifies this as an enum; relibc uses the +/// underlying integer representation together with the `P_*` constants below. +pub type idtype_t = c_int; + +/// [`waitid`]: wait for any child. +pub const P_ALL: idtype_t = 0; +/// [`waitid`]: wait for the child whose process ID equals `id`. +pub const P_PID: idtype_t = 1; +/// [`waitid`]: wait for any child whose process group ID equals `id`. +pub const P_PGID: idtype_t = 2; + +/// `siginfo_t.si_code`: child exited normally. +pub const CLD_EXITED: c_int = 1; +/// `siginfo_t.si_code`: child was killed by a signal. +pub const CLD_KILLED: c_int = 2; +/// `siginfo_t.si_code`: child was killed by a signal and dumped core. +pub const CLD_DUMPED: c_int = 3; +/// `siginfo_t.si_code`: traced child has trapped. +pub const CLD_TRAPPED: c_int = 4; +/// `siginfo_t.si_code`: child has stopped. +pub const CLD_STOPPED: c_int = 5; +/// `siginfo_t.si_code`: stopped child has continued. +pub const CLD_CONTINUED: c_int = 6; + /// Do not hang if no status is available; return immediately. pub const WNOHANG: c_int = 1; /// Report status of stopped child process. @@ -54,19 +79,137 @@ pub unsafe extern "C" fn wait(stat_loc: *mut c_int) -> pid_t { unsafe { waitpid(!0, stat_loc, 0) } } -/* - * TODO: implement idtype_t, id_t, and siginfo_t - * - * #[unsafe(no_mangle)] - * pub unsafe extern "C" fn waitid( - * idtype: idtype_t, - * id: id_t, - * infop: siginfo_t, - * options: c_int - * ) -> c_int { - * unimplemented!(); - * } - */ +fn wexitstatus(status: c_int) -> c_int { + (status >> 8) & 0xff +} + +fn wtermsig(status: c_int) -> c_int { + status & 0x7f +} + +fn wstopsig(status: c_int) -> c_int { + wexitstatus(status) +} + +fn wcoredump(status: c_int) -> bool { + (status & 0x80) != 0 +} + +fn wifexited(status: c_int) -> bool { + (status & 0x7f) == 0 +} + +fn wifstopped(status: c_int) -> bool { + (status & 0xff) == 0x7f +} + +fn wifcontinued(status: c_int) -> bool { + status == 0xffff +} + +/// Translate a `(idtype, id)` pair into the `pid` argument understood by +/// [`waitpid`]. Returns `None` for an unrecognised `idtype`. +fn map_waitid_target(idtype: idtype_t, id: id_t) -> Option { + match idtype { + P_ALL => Some(-1), + P_PID => Some(id as pid_t), + P_PGID => Some(if id == 0 { 0 } else { -(id as pid_t) }), + _ => None, + } +} + +/// Translate `waitid` options into `waitpid` options. `waitid` requires at +/// least one of `WEXITED`/`WSTOPPED`/`WCONTINUED`; `None` signals `EINVAL`. +fn map_waitid_options(options: c_int) -> Option { + let interest = options & (WEXITED | WSTOPPED | WCONTINUED); + if interest == 0 { + return None; + } + + let mut waitpid_options = 0; + if options & WNOHANG != 0 { + waitpid_options |= WNOHANG; + } + if options & WSTOPPED != 0 { + waitpid_options |= WUNTRACED; + } + if options & WCONTINUED != 0 { + waitpid_options |= WCONTINUED; + } + if options & WNOWAIT != 0 { + waitpid_options |= WNOWAIT; + } + + Some(waitpid_options) +} + +/// See . +/// +/// Waits for one of the caller's children to change state and reports the +/// change through `infop`. On success returns `0`; on error returns `-1` and +/// sets errno. With `WNOHANG` set and no child ready, returns `0` with +/// `infop->si_pid == 0`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn waitid( + idtype: idtype_t, + id: id_t, + infop: *mut siginfo_t, + options: c_int, +) -> c_int { + if infop.is_null() { + ERRNO.set(crate::header::errno::EFAULT); + return -1; + } + + let Some(pid_target) = map_waitid_target(idtype, id) else { + ERRNO.set(crate::header::errno::EINVAL); + return -1; + }; + let Some(waitpid_options) = map_waitid_options(options) else { + ERRNO.set(crate::header::errno::EINVAL); + return -1; + }; + + let mut status = 0; + let pid = Sys::waitpid(pid_target, Some(unsafe { Out::from_mut(&mut status) }), waitpid_options) + .or_minus_one_errno(); + if pid < 0 { + return -1; + } + + unsafe { + *infop = core::mem::zeroed(); + } + if pid == 0 { + // WNOHANG requested and no child was ready. + return 0; + } + + unsafe { + (*infop).si_pid = pid; + (*infop).si_signo = crate::header::signal::SIGCHLD as c_int; + (*infop).si_errno = 0; + if wifexited(status) { + (*infop).si_code = CLD_EXITED; + (*infop).si_status = wexitstatus(status); + } else if wifstopped(status) { + (*infop).si_code = CLD_STOPPED; + (*infop).si_status = wstopsig(status); + } else if wifcontinued(status) { + (*infop).si_code = CLD_CONTINUED; + (*infop).si_status = crate::header::signal::SIGCONT as c_int; + } else { + (*infop).si_status = wtermsig(status); + (*infop).si_code = if wcoredump(status) { + CLD_DUMPED + } else { + CLD_KILLED + }; + } + } + + 0 +} /// See . ///