Coverage Report

Created: 2025-12-28 06:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/nix-0.28.0/src/sys/wait.rs
Line
Count
Source
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
0
    pub fn pid(&self) -> Option<Pid> {
122
        use self::WaitStatus::*;
123
0
        match *self {
124
0
            Exited(p, _) | Signaled(p, _, _) | Stopped(p, _) | Continued(p) => {
125
0
                Some(p)
126
            }
127
0
            StillAlive => None,
128
            #[cfg(linux_android)]
129
0
            PtraceEvent(p, _, _) | PtraceSyscall(p) => Some(p),
130
        }
131
0
    }
132
}
133
134
0
fn exited(status: i32) -> bool {
135
0
    libc::WIFEXITED(status)
136
0
}
137
138
0
fn exit_status(status: i32) -> i32 {
139
0
    libc::WEXITSTATUS(status)
140
0
}
141
142
0
fn signaled(status: i32) -> bool {
143
0
    libc::WIFSIGNALED(status)
144
0
}
145
146
0
fn term_signal(status: i32) -> Result<Signal> {
147
0
    Signal::try_from(libc::WTERMSIG(status))
148
0
}
149
150
0
fn dumped_core(status: i32) -> bool {
151
0
    libc::WCOREDUMP(status)
152
0
}
153
154
0
fn stopped(status: i32) -> bool {
155
0
    libc::WIFSTOPPED(status)
156
0
}
157
158
0
fn stop_signal(status: i32) -> Result<Signal> {
159
0
    Signal::try_from(libc::WSTOPSIG(status))
160
0
}
161
162
#[cfg(linux_android)]
163
0
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
0
    libc::WSTOPSIG(status) == libc::SIGTRAP | 0x80
169
0
}
170
171
#[cfg(linux_android)]
172
0
fn stop_additional(status: i32) -> c_int {
173
0
    (status >> 16) as c_int
174
0
}
175
176
0
fn continued(status: i32) -> bool {
177
0
    libc::WIFCONTINUED(status)
178
0
}
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
0
    pub fn from_raw(pid: Pid, status: i32) -> Result<WaitStatus> {
199
0
        Ok(if exited(status) {
200
0
            WaitStatus::Exited(pid, exit_status(status))
201
0
        } else if signaled(status) {
202
0
            WaitStatus::Signaled(pid, term_signal(status)?, dumped_core(status))
203
0
        } else if stopped(status) {
204
            cfg_if! {
205
                if #[cfg(linux_android)] {
206
0
                    fn decode_stopped(pid: Pid, status: i32) -> Result<WaitStatus> {
207
0
                        let status_additional = stop_additional(status);
208
0
                        Ok(if syscall_stop(status) {
209
0
                            WaitStatus::PtraceSyscall(pid)
210
0
                        } else if status_additional == 0 {
211
0
                            WaitStatus::Stopped(pid, stop_signal(status)?)
212
                        } else {
213
0
                            WaitStatus::PtraceEvent(pid, stop_signal(status)?,
214
0
                                                    stop_additional(status))
215
                        })
216
0
                    }
217
                } else {
218
                    fn decode_stopped(pid: Pid, status: i32) -> Result<WaitStatus> {
219
                        Ok(WaitStatus::Stopped(pid, stop_signal(status)?))
220
                    }
221
                }
222
            }
223
0
            return decode_stopped(pid, status);
224
        } else {
225
0
            assert!(continued(status));
226
0
            WaitStatus::Continued(pid)
227
        })
228
0
    }
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
0
    unsafe fn from_siginfo(siginfo: &libc::siginfo_t) -> Result<WaitStatus> {
248
0
        let si_pid = unsafe { siginfo.si_pid() };
249
0
        if si_pid == 0 {
250
0
            return Ok(WaitStatus::StillAlive);
251
0
        }
252
253
0
        assert_eq!(siginfo.si_signo, libc::SIGCHLD);
254
255
0
        let pid = Pid::from_raw(si_pid);
256
0
        let si_status = unsafe { siginfo.si_status() };
257
258
0
        let status = match siginfo.si_code {
259
0
            libc::CLD_EXITED => WaitStatus::Exited(pid, si_status),
260
            libc::CLD_KILLED | libc::CLD_DUMPED => WaitStatus::Signaled(
261
0
                pid,
262
0
                Signal::try_from(si_status)?,
263
0
                siginfo.si_code == libc::CLD_DUMPED,
264
            ),
265
            libc::CLD_STOPPED => {
266
0
                WaitStatus::Stopped(pid, Signal::try_from(si_status)?)
267
            }
268
0
            libc::CLD_CONTINUED => WaitStatus::Continued(pid),
269
            #[cfg(linux_android)]
270
            libc::CLD_TRAPPED => {
271
0
                if si_status == libc::SIGTRAP | 0x80 {
272
0
                    WaitStatus::PtraceSyscall(pid)
273
                } else {
274
                    WaitStatus::PtraceEvent(
275
0
                        pid,
276
0
                        Signal::try_from(si_status & 0xff)?,
277
0
                        (si_status >> 8) as c_int,
278
                    )
279
                }
280
            }
281
0
            _ => return Err(Errno::EINVAL),
282
        };
283
284
0
        Ok(status)
285
0
    }
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
0
pub fn waitpid<P: Into<Option<Pid>>>(
292
0
    pid: P,
293
0
    options: Option<WaitPidFlag>,
294
0
) -> Result<WaitStatus> {
295
    use self::WaitStatus::*;
296
297
0
    let mut status: i32 = 0;
298
299
0
    let option_bits = match options {
300
0
        Some(bits) => bits.bits(),
301
0
        None => 0,
302
    };
303
304
0
    let res = unsafe {
305
0
        libc::waitpid(
306
0
            pid.into().unwrap_or_else(|| Pid::from_raw(-1)).into(),
307
0
            &mut status as *mut c_int,
308
0
            option_bits,
309
        )
310
    };
311
312
0
    match Errno::result(res)? {
313
0
        0 => Ok(StillAlive),
314
0
        res => WaitStatus::from_raw(Pid::from_raw(res), status),
315
    }
316
0
}
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
0
pub fn wait() -> Result<WaitStatus> {
322
0
    waitpid(None, None)
323
0
}
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
0
pub fn waitid(id: Id, flags: WaitPidFlag) -> Result<WaitStatus> {
361
0
    let (idtype, idval) = match id {
362
0
        Id::All => (libc::P_ALL, 0),
363
0
        Id::Pid(pid) => (libc::P_PID, pid.as_raw() as libc::id_t),
364
0
        Id::PGid(pid) => (libc::P_PGID, pid.as_raw() as libc::id_t),
365
        #[cfg(linux_android)]
366
0
        Id::PIDFd(fd) => (libc::P_PIDFD, fd.as_raw_fd() as libc::id_t),
367
        Id::_Unreachable(_) => {
368
0
            unreachable!("This variant could never be constructed")
369
        }
370
    };
371
372
0
    let siginfo = unsafe {
373
        // Memory is zeroed rather than uninitialized, as not all platforms
374
        // initialize the memory in the StillAlive case
375
0
        let mut siginfo: libc::siginfo_t = std::mem::zeroed();
376
0
        Errno::result(libc::waitid(idtype, idval, &mut siginfo, flags.bits()))?;
377
0
        siginfo
378
    };
379
380
0
    unsafe { WaitStatus::from_siginfo(&siginfo) }
381
0
}