use crate::process::Pid; use crate::{backend, io}; use bitflags::bitflags; #[cfg(target_os = "linux")] use crate::fd::BorrowedFd; #[cfg(linux_raw)] use crate::backend::process::wait::SiginfoExt; bitflags! { /// Options for modifying the behavior of [`wait`]/[`waitpid`]. #[repr(transparent)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub struct WaitOptions: u32 { /// Return immediately if no child has exited. const NOHANG = bitcast!(backend::process::wait::WNOHANG); /// Return if a child has stopped (but not traced via [`ptrace`]) /// /// [`ptrace`]: https://man7.org/linux/man-pages/man2/ptrace.2.html const UNTRACED = bitcast!(backend::process::wait::WUNTRACED); /// Return if a stopped child has been resumed by delivery of /// [`Signal::Cont`]. const CONTINUED = bitcast!(backend::process::wait::WCONTINUED); /// const _ = !0; } } #[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "wasi")))] bitflags! { /// Options for modifying the behavior of [`waitid`]. #[repr(transparent)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub struct WaitidOptions: u32 { /// Return immediately if no child has exited. const NOHANG = bitcast!(backend::process::wait::WNOHANG); /// Return if a stopped child has been resumed by delivery of /// [`Signal::Cont`]. const CONTINUED = bitcast!(backend::process::wait::WCONTINUED); /// Wait for processed that have exited. const EXITED = bitcast!(backend::process::wait::WEXITED); /// Keep processed in a waitable state. const NOWAIT = bitcast!(backend::process::wait::WNOWAIT); /// Wait for processes that have been stopped. const STOPPED = bitcast!(backend::process::wait::WSTOPPED); /// const _ = !0; } } /// The status of a child process after calling [`wait`]/[`waitpid`]. #[derive(Debug, Clone, Copy)] #[repr(transparent)] pub struct WaitStatus(u32); impl WaitStatus { /// Creates a `WaitStatus` out of an integer. #[inline] pub(crate) fn new(status: u32) -> Self { Self(status) } /// Converts a `WaitStatus` into its raw representation as an integer. #[inline] pub const fn as_raw(self) -> u32 { self.0 } /// Returns whether the process is currently stopped. #[inline] pub fn stopped(self) -> bool { backend::process::wait::WIFSTOPPED(self.0 as _) } /// Returns whether the process has exited normally. #[inline] pub fn exited(self) -> bool { backend::process::wait::WIFEXITED(self.0 as _) } /// Returns whether the process was terminated by a signal. #[inline] pub fn signaled(self) -> bool { backend::process::wait::WIFSIGNALED(self.0 as _) } /// Returns whether the process has continued from a job control stop. #[inline] pub fn continued(self) -> bool { backend::process::wait::WIFCONTINUED(self.0 as _) } /// Returns the number of the signal that stopped the process, if the /// process was stopped by a signal. #[inline] pub fn stopping_signal(self) -> Option { if self.stopped() { Some(backend::process::wait::WSTOPSIG(self.0 as _) as _) } else { None } } /// Returns the exit status number returned by the process, if it exited /// normally. #[inline] pub fn exit_status(self) -> Option { if self.exited() { Some(backend::process::wait::WEXITSTATUS(self.0 as _) as _) } else { None } } /// Returns the number of the signal that terminated the process, if the /// process was terminated by a signal. #[inline] pub fn terminating_signal(self) -> Option { if self.signaled() { Some(backend::process::wait::WTERMSIG(self.0 as _) as _) } else { None } } } /// The status of a process after calling [`waitid`]. #[derive(Clone, Copy)] #[repr(transparent)] #[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "wasi")))] pub struct WaitidStatus(pub(crate) backend::c::siginfo_t); #[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "wasi")))] impl WaitidStatus { /// Returns whether the process is currently stopped. #[inline] pub fn stopped(&self) -> bool { self.si_code() == backend::c::CLD_STOPPED } /// Returns whether the process is currently trapped. #[inline] pub fn trapped(&self) -> bool { self.si_code() == backend::c::CLD_TRAPPED } /// Returns whether the process has exited normally. #[inline] pub fn exited(&self) -> bool { self.si_code() == backend::c::CLD_EXITED } /// Returns whether the process was terminated by a signal and did not /// create a core file. #[inline] pub fn killed(&self) -> bool { self.si_code() == backend::c::CLD_KILLED } /// Returns whether the process was terminated by a signal and did create a /// core file. #[inline] pub fn dumped(&self) -> bool { self.si_code() == backend::c::CLD_DUMPED } /// Returns whether the process has continued from a job control stop. #[inline] pub fn continued(&self) -> bool { self.si_code() == backend::c::CLD_CONTINUED } /// Returns the number of the signal that stopped the process, if the /// process was stopped by a signal. #[inline] #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))] pub fn stopping_signal(&self) -> Option { if self.stopped() { Some(self.si_status() as _) } else { None } } /// Returns the number of the signal that trapped the process, if the /// process was trapped by a signal. #[inline] #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))] pub fn trapping_signal(&self) -> Option { if self.trapped() { Some(self.si_status() as _) } else { None } } /// Returns the exit status number returned by the process, if it exited /// normally. #[inline] #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))] pub fn exit_status(&self) -> Option { if self.exited() { Some(self.si_status() as _) } else { None } } /// Returns the number of the signal that terminated the process, if the /// process was terminated by a signal. #[inline] #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))] pub fn terminating_signal(&self) -> Option { if self.killed() || self.dumped() { Some(self.si_status() as _) } else { None } } /// Returns a reference to the raw platform-specific `siginfo_t` struct. #[inline] pub const fn as_raw(&self) -> &backend::c::siginfo_t { &self.0 } #[cfg(linux_raw)] fn si_code(&self) -> u32 { self.0.si_code() as u32 // CLD_ consts are unsigned } #[cfg(not(linux_raw))] fn si_code(&self) -> backend::c::c_int { self.0.si_code } #[cfg(not(any(target_os = "emscripten", target_os = "fuchsia", target_os = "netbsd")))] #[allow(unsafe_code)] fn si_status(&self) -> backend::c::c_int { // SAFETY: POSIX [specifies] that the `siginfo_t` returned by a // `waitid` call always has a valid `si_status` value. // // [specifies]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html unsafe { self.0.si_status() } } } /// The identifier to wait on in a call to [`waitid`]. #[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "wasi")))] #[derive(Debug, Clone)] #[non_exhaustive] pub enum WaitId<'a> { /// Wait on all processes. #[doc(alias = "P_ALL")] All, /// Wait for a specific process ID. #[doc(alias = "P_PID")] Pid(Pid), /// Wait for a specific process group ID, or the calling process' group ID. #[doc(alias = "P_PGID")] Pgid(Option), /// Wait for a specific process file descriptor. #[cfg(target_os = "linux")] #[doc(alias = "P_PIDFD")] PidFd(BorrowedFd<'a>), /// Eat the lifetime for non-Linux platforms. #[doc(hidden)] #[cfg(not(target_os = "linux"))] __EatLifetime(core::marker::PhantomData<&'a ()>), } /// `waitpid(pid, waitopts)`—Wait for a specific process to change state. /// /// If the pid is `None`, the call will wait for any child process whose /// process group id matches that of the calling process. /// /// Otherwise, the call will wait for the child process with the given pid. /// /// On Success, returns the status of the selected process. /// /// If `NOHANG` was specified in the options, and the selected child process /// didn't change state, returns `None`. /// /// # Bugs /// /// This function does not currently support waiting for given process group /// (the < 0 case of `waitpid`); to do that, currently the [`waitpgid`] or /// [`waitid`] function must be used. /// /// This function does not currently support waiting for any process (the /// `-1` case of `waitpid`); to do that, currently the [`wait`] function must /// be used. /// /// # References /// - [POSIX] /// - [Linux] /// /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html /// [Linux]: https://man7.org/linux/man-pages/man2/waitpid.2.html #[cfg(not(target_os = "wasi"))] #[inline] pub fn waitpid(pid: Option, waitopts: WaitOptions) -> io::Result> { Ok(backend::process::syscalls::waitpid(pid, waitopts)?.map(|(_, status)| status)) } /// `waitpid(-pgid, waitopts)`—Wait for a process in a specific process group /// to change state. /// /// The call will wait for any child process with the given pgid. /// /// On Success, returns the status of the selected process. /// /// If `NOHANG` was specified in the options, and no selected child process /// changed state, returns `None`. /// /// # References /// - [POSIX] /// - [Linux] /// /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html /// [Linux]: https://man7.org/linux/man-pages/man2/waitpid.2.html #[cfg(not(target_os = "wasi"))] #[inline] pub fn waitpgid(pgid: Pid, waitopts: WaitOptions) -> io::Result> { Ok(backend::process::syscalls::waitpgid(pgid, waitopts)?.map(|(_, status)| status)) } /// `wait(waitopts)`—Wait for any of the children of calling process to /// change state. /// /// On success, returns the pid of the child process whose state changed, and /// the status of said process. /// /// If `NOHANG` was specified in the options, and the selected child process /// didn't change state, returns `None`. /// /// # References /// - [POSIX] /// - [Linux] /// /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html /// [Linux]: https://man7.org/linux/man-pages/man2/waitpid.2.html #[cfg(not(target_os = "wasi"))] #[inline] pub fn wait(waitopts: WaitOptions) -> io::Result> { backend::process::syscalls::wait(waitopts) } /// `waitid(_, _, _, opts)`—Wait for the specified child process to change /// state. #[cfg(not(any(target_os = "openbsd", target_os = "redox", target_os = "wasi")))] #[inline] pub fn waitid<'a>( id: impl Into>, options: WaitidOptions, ) -> io::Result> { backend::process::syscalls::waitid(id.into(), options) }