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.
This commit is contained in:
Red Bear OS
2026-07-16 21:28:52 +09:00
parent a7663b3adf
commit 3ed376e153
+158 -15
View File
@@ -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<pid_t> {
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<c_int> {
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 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/waitid.html>.
///
/// 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 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/waitpid.html>.
///