| 1 | //! Wait for a process to change status |
| 2 | use crate::errno::Errno; |
| 3 | use crate::sys::signal::Signal; |
| 4 | use crate::unistd::Pid; |
| 5 | use crate::Result; |
| 6 | use cfg_if::cfg_if; |
| 7 | use libc::{self, c_int}; |
| 8 | use std::convert::TryFrom; |
| 9 | #[cfg (any( |
| 10 | target_os = "android" , |
| 11 | all(target_os = "linux" , not(target_env = "uclibc" )), |
| 12 | ))] |
| 13 | use std::os::unix::io::{AsRawFd, BorrowedFd}; |
| 14 | |
| 15 | libc_bitflags!( |
| 16 | /// Controls the behavior of [`waitpid`]. |
| 17 | pub struct WaitPidFlag: c_int { |
| 18 | /// Do not block when there are no processes wishing to report status. |
| 19 | WNOHANG; |
| 20 | /// Report the status of selected processes which are stopped due to a |
| 21 | /// [`SIGTTIN`](crate::sys::signal::Signal::SIGTTIN), |
| 22 | /// [`SIGTTOU`](crate::sys::signal::Signal::SIGTTOU), |
| 23 | /// [`SIGTSTP`](crate::sys::signal::Signal::SIGTSTP), or |
| 24 | /// [`SIGSTOP`](crate::sys::signal::Signal::SIGSTOP) signal. |
| 25 | WUNTRACED; |
| 26 | /// Report the status of selected processes which have terminated. |
| 27 | #[cfg (any(linux_android, |
| 28 | apple_targets, |
| 29 | target_os = "freebsd" , |
| 30 | target_os = "haiku" , |
| 31 | target_os = "redox" , |
| 32 | target_os = "netbsd" ))] |
| 33 | WEXITED; |
| 34 | /// Report the status of selected processes that have continued from a |
| 35 | /// job control stop by receiving a |
| 36 | /// [`SIGCONT`](crate::sys::signal::Signal::SIGCONT) signal. |
| 37 | WCONTINUED; |
| 38 | /// An alias for WUNTRACED. |
| 39 | #[cfg (any(linux_android, |
| 40 | apple_targets, |
| 41 | target_os = "freebsd" , |
| 42 | target_os = "haiku" , |
| 43 | target_os = "redox" , |
| 44 | target_os = "netbsd" ))] |
| 45 | WSTOPPED; |
| 46 | /// Don't reap, just poll status. |
| 47 | #[cfg (any(linux_android, |
| 48 | apple_targets, |
| 49 | target_os = "freebsd" , |
| 50 | target_os = "haiku" , |
| 51 | target_os = "redox" , |
| 52 | target_os = "netbsd" ))] |
| 53 | WNOWAIT; |
| 54 | /// Don't wait on children of other threads in this group |
| 55 | #[cfg (any(linux_android, target_os = "redox" ))] |
| 56 | __WNOTHREAD; |
| 57 | /// Wait on all children, regardless of type |
| 58 | #[cfg (any(linux_android, target_os = "redox" ))] |
| 59 | __WALL; |
| 60 | /// Wait for "clone" children only. |
| 61 | #[cfg (any(linux_android, target_os = "redox" ))] |
| 62 | __WCLONE; |
| 63 | } |
| 64 | ); |
| 65 | |
| 66 | /// Possible return values from `wait()` or `waitpid()`. |
| 67 | /// |
| 68 | /// Each status (other than `StillAlive`) describes a state transition |
| 69 | /// in a child process `Pid`, such as the process exiting or stopping, |
| 70 | /// plus additional data about the transition if any. |
| 71 | /// |
| 72 | /// Note that there are two Linux-specific enum variants, `PtraceEvent` |
| 73 | /// and `PtraceSyscall`. Portable code should avoid exhaustively |
| 74 | /// matching on `WaitStatus`. |
| 75 | #[derive (Clone, Copy, Debug, Eq, Hash, PartialEq)] |
| 76 | pub enum WaitStatus { |
| 77 | /// The process exited normally (as with `exit()` or returning from |
| 78 | /// `main`) with the given exit code. This case matches the C macro |
| 79 | /// `WIFEXITED(status)`; the second field is `WEXITSTATUS(status)`. |
| 80 | Exited(Pid, i32), |
| 81 | /// The process was killed by the given signal. The third field |
| 82 | /// indicates whether the signal generated a core dump. This case |
| 83 | /// matches the C macro `WIFSIGNALED(status)`; the last two fields |
| 84 | /// correspond to `WTERMSIG(status)` and `WCOREDUMP(status)`. |
| 85 | Signaled(Pid, Signal, bool), |
| 86 | /// The process is alive, but was stopped by the given signal. This |
| 87 | /// is only reported if `WaitPidFlag::WUNTRACED` was passed. This |
| 88 | /// case matches the C macro `WIFSTOPPED(status)`; the second field |
| 89 | /// is `WSTOPSIG(status)`. |
| 90 | Stopped(Pid, Signal), |
| 91 | /// The traced process was stopped by a `PTRACE_EVENT_*` event. See |
| 92 | /// [`nix::sys::ptrace`] and [`ptrace`(2)] for more information. All |
| 93 | /// currently-defined events use `SIGTRAP` as the signal; the third |
| 94 | /// field is the `PTRACE_EVENT_*` value of the event. |
| 95 | /// |
| 96 | /// [`nix::sys::ptrace`]: ../ptrace/index.html |
| 97 | /// [`ptrace`(2)]: https://man7.org/linux/man-pages/man2/ptrace.2.html |
| 98 | #[cfg (linux_android)] |
| 99 | PtraceEvent(Pid, Signal, c_int), |
| 100 | /// The traced process was stopped by execution of a system call, |
| 101 | /// and `PTRACE_O_TRACESYSGOOD` is in effect. See [`ptrace`(2)] for |
| 102 | /// more information. |
| 103 | /// |
| 104 | /// [`ptrace`(2)]: https://man7.org/linux/man-pages/man2/ptrace.2.html |
| 105 | #[cfg (linux_android)] |
| 106 | PtraceSyscall(Pid), |
| 107 | /// The process was previously stopped but has resumed execution |
| 108 | /// after receiving a `SIGCONT` signal. This is only reported if |
| 109 | /// `WaitPidFlag::WCONTINUED` was passed. This case matches the C |
| 110 | /// macro `WIFCONTINUED(status)`. |
| 111 | Continued(Pid), |
| 112 | /// There are currently no state changes to report in any awaited |
| 113 | /// child process. This is only returned if `WaitPidFlag::WNOHANG` |
| 114 | /// was used (otherwise `wait()` or `waitpid()` would block until |
| 115 | /// there was something to report). |
| 116 | StillAlive, |
| 117 | } |
| 118 | |
| 119 | impl WaitStatus { |
| 120 | /// Extracts the PID from the WaitStatus unless it equals StillAlive. |
| 121 | pub fn pid(&self) -> Option<Pid> { |
| 122 | use self::WaitStatus::*; |
| 123 | match *self { |
| 124 | Exited(p: Pid, _) | Signaled(p: Pid, _, _) | Stopped(p: Pid, _) | Continued(p: Pid) => { |
| 125 | Some(p) |
| 126 | } |
| 127 | StillAlive => None, |
| 128 | #[cfg (linux_android)] |
| 129 | PtraceEvent(p: Pid, _, _) | PtraceSyscall(p: Pid) => Some(p), |
| 130 | } |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | fn exited(status: i32) -> bool { |
| 135 | libc::WIFEXITED(status) |
| 136 | } |
| 137 | |
| 138 | fn exit_status(status: i32) -> i32 { |
| 139 | libc::WEXITSTATUS(status) |
| 140 | } |
| 141 | |
| 142 | fn signaled(status: i32) -> bool { |
| 143 | libc::WIFSIGNALED(status) |
| 144 | } |
| 145 | |
| 146 | fn term_signal(status: i32) -> Result<Signal> { |
| 147 | Signal::try_from(libc::WTERMSIG(status)) |
| 148 | } |
| 149 | |
| 150 | fn dumped_core(status: i32) -> bool { |
| 151 | libc::WCOREDUMP(status) |
| 152 | } |
| 153 | |
| 154 | fn stopped(status: i32) -> bool { |
| 155 | libc::WIFSTOPPED(status) |
| 156 | } |
| 157 | |
| 158 | fn stop_signal(status: i32) -> Result<Signal> { |
| 159 | Signal::try_from(libc::WSTOPSIG(status)) |
| 160 | } |
| 161 | |
| 162 | #[cfg (linux_android)] |
| 163 | fn syscall_stop(status: i32) -> bool { |
| 164 | // From ptrace(2), setting PTRACE_O_TRACESYSGOOD has the effect |
| 165 | // of delivering SIGTRAP | 0x80 as the signal number for syscall |
| 166 | // stops. This allows easily distinguishing syscall stops from |
| 167 | // genuine SIGTRAP signals. |
| 168 | libc::WSTOPSIG(status) == libc::SIGTRAP | 0x80 |
| 169 | } |
| 170 | |
| 171 | #[cfg (linux_android)] |
| 172 | fn stop_additional(status: i32) -> c_int { |
| 173 | (status >> 16) as c_int |
| 174 | } |
| 175 | |
| 176 | fn continued(status: i32) -> bool { |
| 177 | libc::WIFCONTINUED(status) |
| 178 | } |
| 179 | |
| 180 | impl WaitStatus { |
| 181 | /// Convert a raw `wstatus` as returned by `waitpid`/`wait` into a `WaitStatus` |
| 182 | /// |
| 183 | /// # Errors |
| 184 | /// |
| 185 | /// Returns an `Error` corresponding to `EINVAL` for invalid status values. |
| 186 | /// |
| 187 | /// # Examples |
| 188 | /// |
| 189 | /// Convert a `wstatus` obtained from `libc::waitpid` into a `WaitStatus`: |
| 190 | /// |
| 191 | /// ``` |
| 192 | /// use nix::sys::wait::WaitStatus; |
| 193 | /// use nix::sys::signal::Signal; |
| 194 | /// let pid = nix::unistd::Pid::from_raw(1); |
| 195 | /// let status = WaitStatus::from_raw(pid, 0x0002); |
| 196 | /// assert_eq!(status, Ok(WaitStatus::Signaled(pid, Signal::SIGINT, false))); |
| 197 | /// ``` |
| 198 | pub fn from_raw(pid: Pid, status: i32) -> Result<WaitStatus> { |
| 199 | Ok(if exited(status) { |
| 200 | WaitStatus::Exited(pid, exit_status(status)) |
| 201 | } else if signaled(status) { |
| 202 | WaitStatus::Signaled(pid, term_signal(status)?, dumped_core(status)) |
| 203 | } else if stopped(status) { |
| 204 | cfg_if! { |
| 205 | if #[cfg(linux_android)] { |
| 206 | fn decode_stopped(pid: Pid, status: i32) -> Result<WaitStatus> { |
| 207 | let status_additional = stop_additional(status); |
| 208 | Ok(if syscall_stop(status) { |
| 209 | WaitStatus::PtraceSyscall(pid) |
| 210 | } else if status_additional == 0 { |
| 211 | WaitStatus::Stopped(pid, stop_signal(status)?) |
| 212 | } else { |
| 213 | WaitStatus::PtraceEvent(pid, stop_signal(status)?, |
| 214 | stop_additional(status)) |
| 215 | }) |
| 216 | } |
| 217 | } else { |
| 218 | fn decode_stopped(pid: Pid, status: i32) -> Result<WaitStatus> { |
| 219 | Ok(WaitStatus::Stopped(pid, stop_signal(status)?)) |
| 220 | } |
| 221 | } |
| 222 | } |
| 223 | return decode_stopped(pid, status); |
| 224 | } else { |
| 225 | assert!(continued(status)); |
| 226 | WaitStatus::Continued(pid) |
| 227 | }) |
| 228 | } |
| 229 | |
| 230 | /// Convert a `siginfo_t` as returned by `waitid` to a `WaitStatus` |
| 231 | /// |
| 232 | /// # Errors |
| 233 | /// |
| 234 | /// Returns an `Error` corresponding to `EINVAL` for invalid values. |
| 235 | /// |
| 236 | /// # Safety |
| 237 | /// |
| 238 | /// siginfo_t is actually a union, not all fields may be initialized. |
| 239 | /// The functions si_pid() and si_status() must be valid to call on |
| 240 | /// the passed siginfo_t. |
| 241 | #[cfg (any( |
| 242 | target_os = "android" , |
| 243 | target_os = "freebsd" , |
| 244 | target_os = "haiku" , |
| 245 | all(target_os = "linux" , not(target_env = "uclibc" )), |
| 246 | ))] |
| 247 | unsafe fn from_siginfo(siginfo: &libc::siginfo_t) -> Result<WaitStatus> { |
| 248 | let si_pid = unsafe { siginfo.si_pid() }; |
| 249 | if si_pid == 0 { |
| 250 | return Ok(WaitStatus::StillAlive); |
| 251 | } |
| 252 | |
| 253 | assert_eq!(siginfo.si_signo, libc::SIGCHLD); |
| 254 | |
| 255 | let pid = Pid::from_raw(si_pid); |
| 256 | let si_status = unsafe { siginfo.si_status() }; |
| 257 | |
| 258 | let status = match siginfo.si_code { |
| 259 | libc::CLD_EXITED => WaitStatus::Exited(pid, si_status), |
| 260 | libc::CLD_KILLED | libc::CLD_DUMPED => WaitStatus::Signaled( |
| 261 | pid, |
| 262 | Signal::try_from(si_status)?, |
| 263 | siginfo.si_code == libc::CLD_DUMPED, |
| 264 | ), |
| 265 | libc::CLD_STOPPED => { |
| 266 | WaitStatus::Stopped(pid, Signal::try_from(si_status)?) |
| 267 | } |
| 268 | libc::CLD_CONTINUED => WaitStatus::Continued(pid), |
| 269 | #[cfg (linux_android)] |
| 270 | libc::CLD_TRAPPED => { |
| 271 | if si_status == libc::SIGTRAP | 0x80 { |
| 272 | WaitStatus::PtraceSyscall(pid) |
| 273 | } else { |
| 274 | WaitStatus::PtraceEvent( |
| 275 | pid, |
| 276 | Signal::try_from(si_status & 0xff)?, |
| 277 | (si_status >> 8) as c_int, |
| 278 | ) |
| 279 | } |
| 280 | } |
| 281 | _ => return Err(Errno::EINVAL), |
| 282 | }; |
| 283 | |
| 284 | Ok(status) |
| 285 | } |
| 286 | } |
| 287 | |
| 288 | /// Wait for a process to change status |
| 289 | /// |
| 290 | /// See also [waitpid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitpid.html) |
| 291 | pub fn waitpid<P: Into<Option<Pid>>>( |
| 292 | pid: P, |
| 293 | options: Option<WaitPidFlag>, |
| 294 | ) -> Result<WaitStatus> { |
| 295 | use self::WaitStatus::*; |
| 296 | |
| 297 | let mut status: i32 = 0; |
| 298 | |
| 299 | let option_bits: i32 = match options { |
| 300 | Some(bits: WaitPidFlag) => bits.bits(), |
| 301 | None => 0, |
| 302 | }; |
| 303 | |
| 304 | let res: i32 = unsafe { |
| 305 | libc::waitpid( |
| 306 | pid.into().unwrap_or_else(|| Pid::from_raw(-1)).into(), |
| 307 | &mut status as *mut c_int, |
| 308 | options:option_bits, |
| 309 | ) |
| 310 | }; |
| 311 | |
| 312 | match Errno::result(res)? { |
| 313 | 0 => Ok(StillAlive), |
| 314 | res: i32 => WaitStatus::from_raw(Pid::from_raw(pid:res), status), |
| 315 | } |
| 316 | } |
| 317 | |
| 318 | /// Wait for any child process to change status or a signal is received. |
| 319 | /// |
| 320 | /// See also [wait(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html) |
| 321 | pub fn wait() -> Result<WaitStatus> { |
| 322 | waitpid(pid:None, options:None) |
| 323 | } |
| 324 | |
| 325 | /// The ID argument for `waitid` |
| 326 | #[cfg (any( |
| 327 | target_os = "android" , |
| 328 | target_os = "freebsd" , |
| 329 | target_os = "haiku" , |
| 330 | all(target_os = "linux" , not(target_env = "uclibc" )), |
| 331 | ))] |
| 332 | #[derive (Debug)] |
| 333 | pub enum Id<'fd> { |
| 334 | /// Wait for any child |
| 335 | All, |
| 336 | /// Wait for the child whose process ID matches the given PID |
| 337 | Pid(Pid), |
| 338 | /// Wait for the child whose process group ID matches the given PID |
| 339 | /// |
| 340 | /// If the PID is zero, the caller's process group is used since Linux 5.4. |
| 341 | PGid(Pid), |
| 342 | /// Wait for the child referred to by the given PID file descriptor |
| 343 | #[cfg (linux_android)] |
| 344 | PIDFd(BorrowedFd<'fd>), |
| 345 | /// A helper variant to resolve the unused parameter (`'fd`) problem on platforms |
| 346 | /// other than Linux and Android. |
| 347 | #[doc (hidden)] |
| 348 | _Unreachable(std::marker::PhantomData<&'fd std::convert::Infallible>), |
| 349 | } |
| 350 | |
| 351 | /// Wait for a process to change status |
| 352 | /// |
| 353 | /// See also [waitid(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitid.html) |
| 354 | #[cfg (any( |
| 355 | target_os = "android" , |
| 356 | target_os = "freebsd" , |
| 357 | target_os = "haiku" , |
| 358 | all(target_os = "linux" , not(target_env = "uclibc" )), |
| 359 | ))] |
| 360 | pub fn waitid(id: Id, flags: WaitPidFlag) -> Result<WaitStatus> { |
| 361 | let (idtype: u32, idval: u32) = match id { |
| 362 | Id::All => (libc::P_ALL, 0), |
| 363 | Id::Pid(pid: Pid) => (libc::P_PID, pid.as_raw() as libc::id_t), |
| 364 | Id::PGid(pid: Pid) => (libc::P_PGID, pid.as_raw() as libc::id_t), |
| 365 | #[cfg (linux_android)] |
| 366 | Id::PIDFd(fd: BorrowedFd<'_>) => (libc::P_PIDFD, fd.as_raw_fd() as libc::id_t), |
| 367 | Id::_Unreachable(_) => { |
| 368 | unreachable!("This variant could never be constructed" ) |
| 369 | } |
| 370 | }; |
| 371 | |
| 372 | let siginfo: siginfo_t = unsafe { |
| 373 | // Memory is zeroed rather than uninitialized, as not all platforms |
| 374 | // initialize the memory in the StillAlive case |
| 375 | let mut siginfo: libc::siginfo_t = std::mem::zeroed(); |
| 376 | Errno::result(libc::waitid(idtype, id:idval, &mut siginfo, options:flags.bits()))?; |
| 377 | siginfo |
| 378 | }; |
| 379 | |
| 380 | unsafe { WaitStatus::from_siginfo(&siginfo) } |
| 381 | } |
| 382 | |