diff --git a/src/header/sys_wait/mod.rs b/src/header/sys_wait/mod.rs --- a/src/header/sys_wait/mod.rs +++ b/src/header/sys_wait/mod.rs @@ -4,12 +4,16 @@ use crate::{ error::ResultExt, + header::signal::siginfo_t, out::Out, platform::{ - Pal, Sys, - types::{c_int, pid_t}, + ERRNO, Pal, Sys, + types::{c_int, c_uint, pid_t}, }, }; + +pub type idtype_t = c_int; +pub type id_t = c_uint; pub const WNOHANG: c_int = 1; pub const WUNTRACED: c_int = 2; @@ -24,25 +28,143 @@ #[allow(overflowing_literals)] pub const __WCLONE: c_int = 0x8000_0000; +pub const P_ALL: idtype_t = 0; +pub const P_PID: idtype_t = 1; +pub const P_PGID: idtype_t = 2; + +pub const CLD_EXITED: c_int = 1; +pub const CLD_KILLED: c_int = 2; +pub const CLD_DUMPED: c_int = 3; +pub const CLD_TRAPPED: c_int = 4; +pub const CLD_STOPPED: c_int = 5; +pub const CLD_CONTINUED: c_int = 6; + +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 +} + /// See . #[unsafe(no_mangle)] 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 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, + } +} + +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) +} + +#[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(Out::from_mut(&mut status)), waitpid_options) + .or_minus_one_errno(); + if pid < 0 { + return -1; + } + + unsafe { + *infop = core::mem::zeroed(); + } + if pid == 0 { + 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 . #[unsafe(no_mangle)] diff --git a/tests/Makefile.tests.mk b/tests/Makefile.tests.mk --- a/tests/Makefile.tests.mk +++ b/tests/Makefile.tests.mk @@ -312,6 +312,7 @@ grp/getgrgid_r \ grp/getgrnam_r \ grp/gr_iter \ + waitid \ waitpid \ waitpid_multiple \ $(FAILING_TESTS) diff --git a/tests/waitid.c b/tests/waitid.c new file mode 100644 --- /dev/null +++ b/tests/waitid.c @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include + +#include "test_helpers.h" + +static void wait_until_child_exits(pid_t pid) { + siginfo_t info; + for (int i = 0; i < 50; ++i) { + info.si_pid = 0; + int ret = waitid(P_PID, pid, &info, WEXITED | WNOHANG | WNOWAIT); + ERROR_IF(waitid, ret, == -1); + if (info.si_pid == pid) { + assert(info.si_code == CLD_EXITED); + assert(info.si_status == 42); + return; + } + usleep(10000); + } + assert(!"waitid never observed child exit"); +} + +int main(void) { + pid_t pid = fork(); + ERROR_IF(fork, pid, == -1); + + if (pid == 0) { + usleep(50000); + _Exit(42); + } + + siginfo_t info; + info.si_pid = 0; + int ret = waitid(P_PID, pid, &info, WEXITED | WNOHANG | WNOWAIT); + ERROR_IF(waitid, ret, == -1); + assert(info.si_pid == 0); + + wait_until_child_exits(pid); + + int status = 0; + pid_t waited = waitpid(pid, &status, 0); + ERROR_IF(waitpid, waited, == -1); + assert(waited == pid); + assert(WIFEXITED(status)); + assert(WEXITSTATUS(status) == 42); + + return EXIT_SUCCESS; +}