//! Wait for a process to change status use crate::errno::Errno; use crate::sys::signal::Signal; use crate::unistd::Pid; use crate::Result; use cfg_if::cfg_if; use libc::{self, c_int}; use std::convert::TryFrom; #[cfg(any( target_os = "android", all(target_os = "linux", not(target_env = "uclibc")), ))] use std::os::unix::io::{AsRawFd, BorrowedFd}; libc_bitflags!( /// Controls the behavior of [`waitpid`]. pub struct WaitPidFlag: c_int { /// Do not block when there are no processes wishing to report status. WNOHANG; /// Report the status of selected processes which are stopped due to a /// [`SIGTTIN`](crate::sys::signal::Signal::SIGTTIN), /// [`SIGTTOU`](crate::sys::signal::Signal::SIGTTOU), /// [`SIGTSTP`](crate::sys::signal::Signal::SIGTSTP), or /// [`SIGSTOP`](crate::sys::signal::Signal::SIGSTOP) signal. WUNTRACED; /// Report the status of selected processes which have terminated. #[cfg(any(target_os = "android", target_os = "freebsd", target_os = "haiku", target_os = "ios", target_os = "linux", target_os = "redox", target_os = "macos", target_os = "netbsd"))] #[cfg_attr(docsrs, doc(cfg(all())))] WEXITED; /// Report the status of selected processes that have continued from a /// job control stop by receiving a /// [`SIGCONT`](crate::sys::signal::Signal::SIGCONT) signal. WCONTINUED; /// An alias for WUNTRACED. #[cfg(any(target_os = "android", target_os = "freebsd", target_os = "haiku", target_os = "ios", target_os = "linux", target_os = "redox", target_os = "macos", target_os = "netbsd"))] #[cfg_attr(docsrs, doc(cfg(all())))] WSTOPPED; /// Don't reap, just poll status. #[cfg(any(target_os = "android", target_os = "freebsd", target_os = "haiku", target_os = "ios", target_os = "linux", target_os = "redox", target_os = "macos", target_os = "netbsd"))] #[cfg_attr(docsrs, doc(cfg(all())))] WNOWAIT; /// Don't wait on children of other threads in this group #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))] #[cfg_attr(docsrs, doc(cfg(all())))] __WNOTHREAD; /// Wait on all children, regardless of type #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))] #[cfg_attr(docsrs, doc(cfg(all())))] __WALL; /// Wait for "clone" children only. #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))] #[cfg_attr(docsrs, doc(cfg(all())))] __WCLONE; } ); /// Possible return values from `wait()` or `waitpid()`. /// /// Each status (other than `StillAlive`) describes a state transition /// in a child process `Pid`, such as the process exiting or stopping, /// plus additional data about the transition if any. /// /// Note that there are two Linux-specific enum variants, `PtraceEvent` /// and `PtraceSyscall`. Portable code should avoid exhaustively /// matching on `WaitStatus`. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum WaitStatus { /// The process exited normally (as with `exit()` or returning from /// `main`) with the given exit code. This case matches the C macro /// `WIFEXITED(status)`; the second field is `WEXITSTATUS(status)`. Exited(Pid, i32), /// The process was killed by the given signal. The third field /// indicates whether the signal generated a core dump. This case /// matches the C macro `WIFSIGNALED(status)`; the last two fields /// correspond to `WTERMSIG(status)` and `WCOREDUMP(status)`. Signaled(Pid, Signal, bool), /// The process is alive, but was stopped by the given signal. This /// is only reported if `WaitPidFlag::WUNTRACED` was passed. This /// case matches the C macro `WIFSTOPPED(status)`; the second field /// is `WSTOPSIG(status)`. Stopped(Pid, Signal), /// The traced process was stopped by a `PTRACE_EVENT_*` event. See /// [`nix::sys::ptrace`] and [`ptrace`(2)] for more information. All /// currently-defined events use `SIGTRAP` as the signal; the third /// field is the `PTRACE_EVENT_*` value of the event. /// /// [`nix::sys::ptrace`]: ../ptrace/index.html /// [`ptrace`(2)]: https://man7.org/linux/man-pages/man2/ptrace.2.html #[cfg(any(target_os = "linux", target_os = "android"))] #[cfg_attr(docsrs, doc(cfg(all())))] PtraceEvent(Pid, Signal, c_int), /// The traced process was stopped by execution of a system call, /// and `PTRACE_O_TRACESYSGOOD` is in effect. See [`ptrace`(2)] for /// more information. /// /// [`ptrace`(2)]: https://man7.org/linux/man-pages/man2/ptrace.2.html #[cfg(any(target_os = "linux", target_os = "android"))] #[cfg_attr(docsrs, doc(cfg(all())))] PtraceSyscall(Pid), /// The process was previously stopped but has resumed execution /// after receiving a `SIGCONT` signal. This is only reported if /// `WaitPidFlag::WCONTINUED` was passed. This case matches the C /// macro `WIFCONTINUED(status)`. Continued(Pid), /// There are currently no state changes to report in any awaited /// child process. This is only returned if `WaitPidFlag::WNOHANG` /// was used (otherwise `wait()` or `waitpid()` would block until /// there was something to report). StillAlive, } impl WaitStatus { /// Extracts the PID from the WaitStatus unless it equals StillAlive. pub fn pid(&self) -> Option { use self::WaitStatus::*; match *self { Exited(p, _) | Signaled(p, _, _) | Stopped(p, _) | Continued(p) => { Some(p) } StillAlive => None, #[cfg(any(target_os = "android", target_os = "linux"))] PtraceEvent(p, _, _) | PtraceSyscall(p) => Some(p), } } } fn exited(status: i32) -> bool { libc::WIFEXITED(status) } fn exit_status(status: i32) -> i32 { libc::WEXITSTATUS(status) } fn signaled(status: i32) -> bool { libc::WIFSIGNALED(status) } fn term_signal(status: i32) -> Result { Signal::try_from(libc::WTERMSIG(status)) } fn dumped_core(status: i32) -> bool { libc::WCOREDUMP(status) } fn stopped(status: i32) -> bool { libc::WIFSTOPPED(status) } fn stop_signal(status: i32) -> Result { Signal::try_from(libc::WSTOPSIG(status)) } #[cfg(any(target_os = "android", target_os = "linux"))] fn syscall_stop(status: i32) -> bool { // From ptrace(2), setting PTRACE_O_TRACESYSGOOD has the effect // of delivering SIGTRAP | 0x80 as the signal number for syscall // stops. This allows easily distinguishing syscall stops from // genuine SIGTRAP signals. libc::WSTOPSIG(status) == libc::SIGTRAP | 0x80 } #[cfg(any(target_os = "android", target_os = "linux"))] fn stop_additional(status: i32) -> c_int { (status >> 16) as c_int } fn continued(status: i32) -> bool { libc::WIFCONTINUED(status) } impl WaitStatus { /// Convert a raw `wstatus` as returned by `waitpid`/`wait` into a `WaitStatus` /// /// # Errors /// /// Returns an `Error` corresponding to `EINVAL` for invalid status values. /// /// # Examples /// /// Convert a `wstatus` obtained from `libc::waitpid` into a `WaitStatus`: /// /// ``` /// use nix::sys::wait::WaitStatus; /// use nix::sys::signal::Signal; /// let pid = nix::unistd::Pid::from_raw(1); /// let status = WaitStatus::from_raw(pid, 0x0002); /// assert_eq!(status, Ok(WaitStatus::Signaled(pid, Signal::SIGINT, false))); /// ``` pub fn from_raw(pid: Pid, status: i32) -> Result { Ok(if exited(status) { WaitStatus::Exited(pid, exit_status(status)) } else if signaled(status) { WaitStatus::Signaled(pid, term_signal(status)?, dumped_core(status)) } else if stopped(status) { cfg_if! { if #[cfg(any(target_os = "android", target_os = "linux"))] { fn decode_stopped(pid: Pid, status: i32) -> Result { let status_additional = stop_additional(status); Ok(if syscall_stop(status) { WaitStatus::PtraceSyscall(pid) } else if status_additional == 0 { WaitStatus::Stopped(pid, stop_signal(status)?) } else { WaitStatus::PtraceEvent(pid, stop_signal(status)?, stop_additional(status)) }) } } else { fn decode_stopped(pid: Pid, status: i32) -> Result { Ok(WaitStatus::Stopped(pid, stop_signal(status)?)) } } } return decode_stopped(pid, status); } else { assert!(continued(status)); WaitStatus::Continued(pid) }) } /// Convert a `siginfo_t` as returned by `waitid` to a `WaitStatus` /// /// # Errors /// /// Returns an `Error` corresponding to `EINVAL` for invalid values. /// /// # Safety /// /// siginfo_t is actually a union, not all fields may be initialized. /// The functions si_pid() and si_status() must be valid to call on /// the passed siginfo_t. #[cfg(any( target_os = "android", target_os = "freebsd", target_os = "haiku", all(target_os = "linux", not(target_env = "uclibc")), ))] unsafe fn from_siginfo(siginfo: &libc::siginfo_t) -> Result { let si_pid = siginfo.si_pid(); if si_pid == 0 { return Ok(WaitStatus::StillAlive); } assert_eq!(siginfo.si_signo, libc::SIGCHLD); let pid = Pid::from_raw(si_pid); let si_status = siginfo.si_status(); let status = match siginfo.si_code { libc::CLD_EXITED => WaitStatus::Exited(pid, si_status), libc::CLD_KILLED | libc::CLD_DUMPED => WaitStatus::Signaled( pid, Signal::try_from(si_status)?, siginfo.si_code == libc::CLD_DUMPED, ), libc::CLD_STOPPED => { WaitStatus::Stopped(pid, Signal::try_from(si_status)?) } libc::CLD_CONTINUED => WaitStatus::Continued(pid), #[cfg(any(target_os = "android", target_os = "linux"))] libc::CLD_TRAPPED => { if si_status == libc::SIGTRAP | 0x80 { WaitStatus::PtraceSyscall(pid) } else { WaitStatus::PtraceEvent( pid, Signal::try_from(si_status & 0xff)?, (si_status >> 8) as c_int, ) } } _ => return Err(Errno::EINVAL), }; Ok(status) } } /// Wait for a process to change status /// /// See also [waitpid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitpid.html) pub fn waitpid>>( pid: P, options: Option, ) -> Result { use self::WaitStatus::*; let mut status: i32 = 0; let option_bits = match options { Some(bits) => bits.bits(), None => 0, }; let res = unsafe { libc::waitpid( pid.into().unwrap_or_else(|| Pid::from_raw(-1)).into(), &mut status as *mut c_int, option_bits, ) }; match Errno::result(res)? { 0 => Ok(StillAlive), res => WaitStatus::from_raw(Pid::from_raw(res), status), } } /// Wait for any child process to change status or a signal is received. /// /// See also [wait(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html) pub fn wait() -> Result { waitpid(None, None) } /// The ID argument for `waitid` #[cfg(any( target_os = "android", target_os = "freebsd", target_os = "haiku", all(target_os = "linux", not(target_env = "uclibc")), ))] #[derive(Debug)] pub enum Id<'fd> { /// Wait for any child All, /// Wait for the child whose process ID matches the given PID Pid(Pid), /// Wait for the child whose process group ID matches the given PID /// /// If the PID is zero, the caller's process group is used since Linux 5.4. PGid(Pid), /// Wait for the child referred to by the given PID file descriptor #[cfg(any(target_os = "android", target_os = "linux"))] PIDFd(BorrowedFd<'fd>), /// A helper variant to resolve the unused parameter (`'fd`) problem on platforms /// other than Linux and Android. #[doc(hidden)] _Unreachable(std::marker::PhantomData<&'fd std::convert::Infallible>), } /// Wait for a process to change status /// /// See also [waitid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitid.html) #[cfg(any( target_os = "android", target_os = "freebsd", target_os = "haiku", all(target_os = "linux", not(target_env = "uclibc")), ))] pub fn waitid(id: Id, flags: WaitPidFlag) -> Result { let (idtype, idval) = match id { Id::All => (libc::P_ALL, 0), Id::Pid(pid) => (libc::P_PID, pid.as_raw() as libc::id_t), Id::PGid(pid) => (libc::P_PGID, pid.as_raw() as libc::id_t), #[cfg(any(target_os = "android", target_os = "linux"))] Id::PIDFd(fd) => (libc::P_PIDFD, fd.as_raw_fd() as libc::id_t), Id::_Unreachable(_) => unreachable!("This variant could never be constructed"), }; let siginfo = unsafe { // Memory is zeroed rather than uninitialized, as not all platforms // initialize the memory in the StillAlive case let mut siginfo: libc::siginfo_t = std::mem::zeroed(); Errno::result(libc::waitid(idtype, idval, &mut siginfo, flags.bits()))?; siginfo }; unsafe { WaitStatus::from_siginfo(&siginfo) } }