| //! 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<Pid> { | |
| 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> { | |
| 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> { | |
| 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<WaitStatus> { | |
| 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<WaitStatus> { | |
| 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<WaitStatus> { | |
| 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<WaitStatus> { | |
| 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<P: Into<Option<Pid>>>( | |
| pid: P, | |
| options: Option<WaitPidFlag>, | |
| ) -> Result<WaitStatus> { | |
| 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<WaitStatus> { | |
| 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<WaitStatus> { | |
| 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) } | |
| } |