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