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