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