| 1 | use crate::process::Pid; |
| 2 | use crate::{backend, io}; |
| 3 | use bitflags::bitflags; |
| 4 | |
| 5 | #[cfg (target_os = "linux" )] |
| 6 | use crate::fd::BorrowedFd; |
| 7 | |
| 8 | #[cfg (linux_raw)] |
| 9 | use crate::backend::process::wait::SiginfoExt; |
| 10 | |
| 11 | bitflags! { |
| 12 | /// Options for modifying the behavior of [`wait`]/[`waitpid`]. |
| 13 | #[repr (transparent)] |
| 14 | #[derive (Copy, Clone, Eq, PartialEq, Hash, Debug)] |
| 15 | pub struct WaitOptions: u32 { |
| 16 | /// Return immediately if no child has exited. |
| 17 | const NOHANG = bitcast!(backend::process::wait::WNOHANG); |
| 18 | /// Return if a child has stopped (but not traced via [`ptrace`]). |
| 19 | /// |
| 20 | /// [`ptrace`]: https://man7.org/linux/man-pages/man2/ptrace.2.html |
| 21 | const UNTRACED = bitcast!(backend::process::wait::WUNTRACED); |
| 22 | /// Return if a stopped child has been resumed by delivery of |
| 23 | /// [`Signal::Cont`]. |
| 24 | /// |
| 25 | /// [`Signal::Cont`]: crate::process::Signal::Cont |
| 26 | const CONTINUED = bitcast!(backend::process::wait::WCONTINUED); |
| 27 | |
| 28 | /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags> |
| 29 | const _ = !0; |
| 30 | } |
| 31 | } |
| 32 | |
| 33 | #[cfg (not(any(target_os = "openbsd" , target_os = "redox" , target_os = "wasi" )))] |
| 34 | bitflags! { |
| 35 | /// Options for modifying the behavior of [`waitid`]. |
| 36 | #[repr (transparent)] |
| 37 | #[derive (Copy, Clone, Eq, PartialEq, Hash, Debug)] |
| 38 | pub struct WaitidOptions: u32 { |
| 39 | /// Return immediately if no child has exited. |
| 40 | const NOHANG = bitcast!(backend::process::wait::WNOHANG); |
| 41 | /// Return if a stopped child has been resumed by delivery of |
| 42 | /// [`Signal::Cont`]. |
| 43 | /// |
| 44 | /// [`Signal::Cont`]: crate::process::Signal::Cont |
| 45 | const CONTINUED = bitcast!(backend::process::wait::WCONTINUED); |
| 46 | /// Wait for processed that have exited. |
| 47 | const EXITED = bitcast!(backend::process::wait::WEXITED); |
| 48 | /// Keep processed in a waitable state. |
| 49 | const NOWAIT = bitcast!(backend::process::wait::WNOWAIT); |
| 50 | /// Wait for processes that have been stopped. |
| 51 | const STOPPED = bitcast!(backend::process::wait::WSTOPPED); |
| 52 | |
| 53 | /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags> |
| 54 | const _ = !0; |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | /// The status of a child process after calling [`wait`]/[`waitpid`]. |
| 59 | #[derive (Debug, Clone, Copy)] |
| 60 | #[repr (transparent)] |
| 61 | pub struct WaitStatus(u32); |
| 62 | |
| 63 | impl WaitStatus { |
| 64 | /// Creates a `WaitStatus` out of an integer. |
| 65 | #[inline ] |
| 66 | pub(crate) fn new(status: u32) -> Self { |
| 67 | Self(status) |
| 68 | } |
| 69 | |
| 70 | /// Converts a `WaitStatus` into its raw representation as an integer. |
| 71 | #[inline ] |
| 72 | pub const fn as_raw(self) -> u32 { |
| 73 | self.0 |
| 74 | } |
| 75 | |
| 76 | /// Returns whether the process is currently stopped. |
| 77 | #[inline ] |
| 78 | pub fn stopped(self) -> bool { |
| 79 | backend::process::wait::WIFSTOPPED(self.0 as _) |
| 80 | } |
| 81 | |
| 82 | /// Returns whether the process has exited normally. |
| 83 | #[inline ] |
| 84 | pub fn exited(self) -> bool { |
| 85 | backend::process::wait::WIFEXITED(self.0 as _) |
| 86 | } |
| 87 | |
| 88 | /// Returns whether the process was terminated by a signal. |
| 89 | #[inline ] |
| 90 | pub fn signaled(self) -> bool { |
| 91 | backend::process::wait::WIFSIGNALED(self.0 as _) |
| 92 | } |
| 93 | |
| 94 | /// Returns whether the process has continued from a job control stop. |
| 95 | #[inline ] |
| 96 | pub fn continued(self) -> bool { |
| 97 | backend::process::wait::WIFCONTINUED(self.0 as _) |
| 98 | } |
| 99 | |
| 100 | /// Returns the number of the signal that stopped the process, if the |
| 101 | /// process was stopped by a signal. |
| 102 | #[inline ] |
| 103 | pub fn stopping_signal(self) -> Option<u32> { |
| 104 | if self.stopped() { |
| 105 | Some(backend::process::wait::WSTOPSIG(self.0 as _) as _) |
| 106 | } else { |
| 107 | None |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | /// Returns the exit status number returned by the process, if it exited |
| 112 | /// normally. |
| 113 | #[inline ] |
| 114 | pub fn exit_status(self) -> Option<u32> { |
| 115 | if self.exited() { |
| 116 | Some(backend::process::wait::WEXITSTATUS(self.0 as _) as _) |
| 117 | } else { |
| 118 | None |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | /// Returns the number of the signal that terminated the process, if the |
| 123 | /// process was terminated by a signal. |
| 124 | #[inline ] |
| 125 | pub fn terminating_signal(self) -> Option<u32> { |
| 126 | if self.signaled() { |
| 127 | Some(backend::process::wait::WTERMSIG(self.0 as _) as _) |
| 128 | } else { |
| 129 | None |
| 130 | } |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | /// The status of a process after calling [`waitid`]. |
| 135 | #[derive (Clone, Copy)] |
| 136 | #[repr (transparent)] |
| 137 | #[cfg (not(any(target_os = "openbsd" , target_os = "redox" , target_os = "wasi" )))] |
| 138 | pub struct WaitidStatus(pub(crate) backend::c::siginfo_t); |
| 139 | |
| 140 | #[cfg (not(any(target_os = "openbsd" , target_os = "redox" , target_os = "wasi" )))] |
| 141 | impl WaitidStatus { |
| 142 | /// Returns whether the process is currently stopped. |
| 143 | #[inline ] |
| 144 | pub fn stopped(&self) -> bool { |
| 145 | self.si_code() == backend::c::CLD_STOPPED |
| 146 | } |
| 147 | |
| 148 | /// Returns whether the process is currently trapped. |
| 149 | #[inline ] |
| 150 | pub fn trapped(&self) -> bool { |
| 151 | self.si_code() == backend::c::CLD_TRAPPED |
| 152 | } |
| 153 | |
| 154 | /// Returns whether the process has exited normally. |
| 155 | #[inline ] |
| 156 | pub fn exited(&self) -> bool { |
| 157 | self.si_code() == backend::c::CLD_EXITED |
| 158 | } |
| 159 | |
| 160 | /// Returns whether the process was terminated by a signal and did not |
| 161 | /// create a core file. |
| 162 | #[inline ] |
| 163 | pub fn killed(&self) -> bool { |
| 164 | self.si_code() == backend::c::CLD_KILLED |
| 165 | } |
| 166 | |
| 167 | /// Returns whether the process was terminated by a signal and did create a |
| 168 | /// core file. |
| 169 | #[inline ] |
| 170 | pub fn dumped(&self) -> bool { |
| 171 | self.si_code() == backend::c::CLD_DUMPED |
| 172 | } |
| 173 | |
| 174 | /// Returns whether the process has continued from a job control stop. |
| 175 | #[inline ] |
| 176 | pub fn continued(&self) -> bool { |
| 177 | self.si_code() == backend::c::CLD_CONTINUED |
| 178 | } |
| 179 | |
| 180 | /// Returns the number of the signal that stopped the process, if the |
| 181 | /// process was stopped by a signal. |
| 182 | #[inline ] |
| 183 | #[cfg (not(any(target_os = "emscripten" , target_os = "fuchsia" , target_os = "netbsd" )))] |
| 184 | pub fn stopping_signal(&self) -> Option<u32> { |
| 185 | if self.stopped() { |
| 186 | Some(self.si_status() as _) |
| 187 | } else { |
| 188 | None |
| 189 | } |
| 190 | } |
| 191 | |
| 192 | /// Returns the number of the signal that trapped the process, if the |
| 193 | /// process was trapped by a signal. |
| 194 | #[inline ] |
| 195 | #[cfg (not(any(target_os = "emscripten" , target_os = "fuchsia" , target_os = "netbsd" )))] |
| 196 | pub fn trapping_signal(&self) -> Option<u32> { |
| 197 | if self.trapped() { |
| 198 | Some(self.si_status() as _) |
| 199 | } else { |
| 200 | None |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | /// Returns the exit status number returned by the process, if it exited |
| 205 | /// normally. |
| 206 | #[inline ] |
| 207 | #[cfg (not(any(target_os = "emscripten" , target_os = "fuchsia" , target_os = "netbsd" )))] |
| 208 | pub fn exit_status(&self) -> Option<u32> { |
| 209 | if self.exited() { |
| 210 | Some(self.si_status() as _) |
| 211 | } else { |
| 212 | None |
| 213 | } |
| 214 | } |
| 215 | |
| 216 | /// Returns the number of the signal that terminated the process, if the |
| 217 | /// process was terminated by a signal. |
| 218 | #[inline ] |
| 219 | #[cfg (not(any(target_os = "emscripten" , target_os = "fuchsia" , target_os = "netbsd" )))] |
| 220 | pub fn terminating_signal(&self) -> Option<u32> { |
| 221 | if self.killed() || self.dumped() { |
| 222 | Some(self.si_status() as _) |
| 223 | } else { |
| 224 | None |
| 225 | } |
| 226 | } |
| 227 | |
| 228 | /// Returns a reference to the raw platform-specific `siginfo_t` struct. |
| 229 | #[inline ] |
| 230 | pub const fn as_raw(&self) -> &backend::c::siginfo_t { |
| 231 | &self.0 |
| 232 | } |
| 233 | |
| 234 | #[cfg (linux_raw)] |
| 235 | fn si_code(&self) -> u32 { |
| 236 | self.0.si_code() as u32 // CLD_ consts are unsigned |
| 237 | } |
| 238 | |
| 239 | #[cfg (not(linux_raw))] |
| 240 | fn si_code(&self) -> backend::c::c_int { |
| 241 | self.0.si_code |
| 242 | } |
| 243 | |
| 244 | #[cfg (not(any(target_os = "emscripten" , target_os = "fuchsia" , target_os = "netbsd" )))] |
| 245 | #[allow (unsafe_code)] |
| 246 | fn si_status(&self) -> backend::c::c_int { |
| 247 | // SAFETY: POSIX [specifies] that the `siginfo_t` returned by a |
| 248 | // `waitid` call always has a valid `si_status` value. |
| 249 | // |
| 250 | // [specifies]: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/signal.h.html |
| 251 | unsafe { self.0.si_status() } |
| 252 | } |
| 253 | } |
| 254 | |
| 255 | /// The identifier to wait on in a call to [`waitid`]. |
| 256 | #[cfg (not(any(target_os = "openbsd" , target_os = "redox" , target_os = "wasi" )))] |
| 257 | #[derive (Debug, Clone)] |
| 258 | #[non_exhaustive ] |
| 259 | pub enum WaitId<'a> { |
| 260 | /// Wait on all processes. |
| 261 | #[doc (alias = "P_ALL" )] |
| 262 | All, |
| 263 | |
| 264 | /// Wait for a specific process ID. |
| 265 | #[doc (alias = "P_PID" )] |
| 266 | Pid(Pid), |
| 267 | |
| 268 | /// Wait for a specific process group ID, or the calling process' group ID. |
| 269 | #[doc (alias = "P_PGID" )] |
| 270 | Pgid(Option<Pid>), |
| 271 | |
| 272 | /// Wait for a specific process file descriptor. |
| 273 | #[cfg (target_os = "linux" )] |
| 274 | #[doc (alias = "P_PIDFD" )] |
| 275 | PidFd(BorrowedFd<'a>), |
| 276 | |
| 277 | /// Eat the lifetime for non-Linux platforms. |
| 278 | #[doc (hidden)] |
| 279 | #[cfg (not(target_os = "linux" ))] |
| 280 | __EatLifetime(core::marker::PhantomData<&'a ()>), |
| 281 | } |
| 282 | |
| 283 | /// `waitpid(pid, waitopts)`—Wait for a specific process to change state. |
| 284 | /// |
| 285 | /// If the pid is `None`, the call will wait for any child process whose |
| 286 | /// process group id matches that of the calling process. |
| 287 | /// |
| 288 | /// Otherwise, the call will wait for the child process with the given pid. |
| 289 | /// |
| 290 | /// On Success, returns the status of the selected process. |
| 291 | /// |
| 292 | /// If `NOHANG` was specified in the options, and the selected child process |
| 293 | /// didn't change state, returns `None`. |
| 294 | /// |
| 295 | /// # Bugs |
| 296 | /// |
| 297 | /// This function does not currently support waiting for given process group |
| 298 | /// (the < 0 case of `waitpid`); to do that, currently the [`waitpgid`] or |
| 299 | /// [`waitid`] function must be used. |
| 300 | /// |
| 301 | /// This function does not currently support waiting for any process (the |
| 302 | /// `-1` case of `waitpid`); to do that, currently the [`wait`] function must |
| 303 | /// be used. |
| 304 | /// |
| 305 | /// # References |
| 306 | /// - [POSIX] |
| 307 | /// - [Linux] |
| 308 | /// |
| 309 | /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/wait.html |
| 310 | /// [Linux]: https://man7.org/linux/man-pages/man2/waitpid.2.html |
| 311 | #[cfg (not(target_os = "wasi" ))] |
| 312 | #[inline ] |
| 313 | pub fn waitpid(pid: Option<Pid>, waitopts: WaitOptions) -> io::Result<Option<WaitStatus>> { |
| 314 | Ok(backend::process::syscalls::waitpid(pid, waitopts)?.map(|(_, status: WaitStatus)| status)) |
| 315 | } |
| 316 | |
| 317 | /// `waitpid(-pgid, waitopts)`—Wait for a process in a specific process group |
| 318 | /// to change state. |
| 319 | /// |
| 320 | /// The call will wait for any child process with the given pgid. |
| 321 | /// |
| 322 | /// On Success, returns the status of the selected process. |
| 323 | /// |
| 324 | /// If `NOHANG` was specified in the options, and no selected child process |
| 325 | /// changed state, returns `None`. |
| 326 | /// |
| 327 | /// # References |
| 328 | /// - [POSIX] |
| 329 | /// - [Linux] |
| 330 | /// |
| 331 | /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/wait.html |
| 332 | /// [Linux]: https://man7.org/linux/man-pages/man2/waitpid.2.html |
| 333 | #[cfg (not(target_os = "wasi" ))] |
| 334 | #[inline ] |
| 335 | pub fn waitpgid(pgid: Pid, waitopts: WaitOptions) -> io::Result<Option<WaitStatus>> { |
| 336 | Ok(backend::process::syscalls::waitpgid(pgid, waitopts)?.map(|(_, status: WaitStatus)| status)) |
| 337 | } |
| 338 | |
| 339 | /// `wait(waitopts)`—Wait for any of the children of calling process to |
| 340 | /// change state. |
| 341 | /// |
| 342 | /// On success, returns the pid of the child process whose state changed, and |
| 343 | /// the status of said process. |
| 344 | /// |
| 345 | /// If `NOHANG` was specified in the options, and the selected child process |
| 346 | /// didn't change state, returns `None`. |
| 347 | /// |
| 348 | /// # References |
| 349 | /// - [POSIX] |
| 350 | /// - [Linux] |
| 351 | /// |
| 352 | /// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/wait.html |
| 353 | /// [Linux]: https://man7.org/linux/man-pages/man2/waitpid.2.html |
| 354 | #[cfg (not(target_os = "wasi" ))] |
| 355 | #[inline ] |
| 356 | pub fn wait(waitopts: WaitOptions) -> io::Result<Option<(Pid, WaitStatus)>> { |
| 357 | backend::process::syscalls::wait(waitopts) |
| 358 | } |
| 359 | |
| 360 | /// `waitid(_, _, _, opts)`—Wait for the specified child process to change |
| 361 | /// state. |
| 362 | #[cfg (not(any(target_os = "openbsd" , target_os = "redox" , target_os = "wasi" )))] |
| 363 | #[inline ] |
| 364 | pub fn waitid<'a>( |
| 365 | id: impl Into<WaitId<'a>>, |
| 366 | options: WaitidOptions, |
| 367 | ) -> io::Result<Option<WaitidStatus>> { |
| 368 | backend::process::syscalls::waitid(id.into(), options) |
| 369 | } |
| 370 | |