syscall/
wait.rs

1//!
2//! This file is part of syscall-rs
3//!
4
5use crate::{Result, Signal};
6
7/// A [`WaitStatus`] is the result of [`wait()`]ing for a child process
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub enum WaitStatus {
10    /// Process exited normally with the given return code
11    Exited(libc::pid_t, libc::c_int),
12
13    /// Process was signaled by `Signal`. If a core dump was produced,
14    /// the third field is `true`.
15    Signaled(libc::pid_t, Signal, bool),
16
17    /// Process is still alive, but was stopped by `Signal`
18    Stopped(libc::pid_t, Signal),
19
20    /// Process was stopped but has resumed execution after receiving a
21    /// `SIGCONT` signal
22    Continued(libc::pid_t),
23}
24
25impl WaitStatus {
26    /// Convert a raw `status` as returned by [`libc::waitpid()`] into a [`WaitStatus`]
27    ///
28    /// This function is using the standard set of wait status related macros in
29    /// order to dissect `status`
30    pub fn from_raw(pid: libc::pid_t, status: libc::c_int) -> Result<WaitStatus> {
31        Ok(if libc::WIFEXITED(status) {
32            WaitStatus::Exited(pid, libc::WEXITSTATUS(status))
33        } else if libc::WIFSIGNALED(status) {
34            WaitStatus::Signaled(
35                pid,
36                libc::WTERMSIG(status).try_into()?,
37                libc::WCOREDUMP(status),
38            )
39        } else if libc::WIFSTOPPED(status) {
40            WaitStatus::Stopped(pid, libc::WSTOPSIG(status).try_into()?)
41        } else {
42            assert!(libc::WIFCONTINUED(status));
43            WaitStatus::Continued(pid)
44        })
45    }
46}
47
48/// Wait for one of the children of the calling process to terminate and
49/// return a [`WaitStatus`]. Note that is function will **block** until
50/// a child termination status becomes available.
51///
52/// If `pid` is greater than `0`, wait for the child whose process ID equals
53/// `pid`. In case `pid` is `None`, wait for **any** child of the calling
54/// process
55pub fn wait<P>(pid: P) -> Result<WaitStatus>
56where
57    P: Into<Option<libc::pid_t>>,
58{
59    let mut status: i32 = 0;
60
61    let res = syscall!(waitpid(
62        pid.into().unwrap_or(-1_i32),
63        &mut status as &mut libc::c_int,
64        libc::WUNTRACED
65    ))?;
66
67    WaitStatus::from_raw(res, status)
68}
69
70#[cfg(test)]
71mod tests {
72    use std::process::exit;
73
74    use crate::{wait, Result, Signal, WaitStatus};
75
76    /// This test is inherently falky and **must not** run together with other
77    /// tests. Otherwise it will most likely fail by [`wait()`] returning the
78    /// wait status from some random child process forked by another thread
79    /// in some unrelated test instead of the wait status of `child`
80    #[test]
81    #[ignore = "must run in isolation"]
82    fn wait_any() -> Result<()> {
83        let child = match syscall!(fork())? {
84            // parent
85            pid if pid != 0 => pid,
86            // child
87            _ => {
88                exit(42);
89            }
90        };
91
92        if let WaitStatus::Exited(pid, status) = wait(None)? {
93            assert_eq!(pid, child);
94            assert_eq!(42, status);
95        } else {
96            // test failure
97            assert!(false);
98        }
99
100        Ok(())
101    }
102
103    #[test]
104    fn wait_exit() -> Result<()> {
105        let child = match syscall!(fork())? {
106            // parent
107            pid if pid != 0 => pid,
108            // child
109            _ => {
110                exit(42);
111            }
112        };
113
114        if let WaitStatus::Exited(pid, status) = wait(child)? {
115            assert_eq!(pid, child);
116            assert_eq!(42, status);
117        } else {
118            // test failure
119            assert!(false);
120        }
121
122        Ok(())
123    }
124
125    #[test]
126    fn wait_stop() -> Result<()> {
127        let child = match syscall!(fork())? {
128            // parent
129            pid if pid != 0 => pid,
130            // child
131            _ => loop {
132                std::thread::sleep(std::time::Duration::from_millis(5));
133            },
134        };
135
136        syscall!(kill(child, Signal::SIGSTOP as libc::c_int))?;
137
138        if let WaitStatus::Stopped(pid, signal) = wait(child)? {
139            assert_eq!(pid, child);
140            assert_eq!(signal, Signal::SIGSTOP);
141        } else {
142            // test failure
143            assert!(false);
144        }
145
146        Ok(())
147    }
148
149    #[test]
150    fn wait_kill() -> Result<()> {
151        let child = match syscall!(fork())? {
152            // parent
153            pid if pid != 0 => pid,
154            // child
155            _ => loop {
156                std::thread::sleep(std::time::Duration::from_millis(5));
157            },
158        };
159
160        syscall!(kill(child, Signal::SIGKILL as libc::c_int))?;
161
162        if let WaitStatus::Signaled(pid, signal, core) = wait(child)? {
163            assert_eq!(pid, child);
164            assert_eq!(signal, Signal::SIGKILL);
165            assert_eq!(core, false);
166        } else {
167            // test failure
168            assert!(false);
169        }
170
171        Ok(())
172    }
173
174    #[test]
175    fn wait_stop_kill() -> Result<()> {
176        let child = match syscall!(fork())? {
177            // parent
178            pid if pid != 0 => pid,
179            // child
180            _ => loop {
181                std::thread::sleep(std::time::Duration::from_millis(5));
182            },
183        };
184
185        syscall!(kill(child, Signal::SIGSTOP as libc::c_int))?;
186
187        if let WaitStatus::Stopped(pid, signal) = wait(child)? {
188            assert_eq!(pid, child);
189            assert_eq!(signal, Signal::SIGSTOP);
190        } else {
191            // test failure
192            assert!(false);
193        }
194
195        syscall!(kill(child, Signal::SIGKILL as libc::c_int))?;
196
197        if let WaitStatus::Signaled(pid, signal, core) = wait(child)? {
198            assert_eq!(pid, child);
199            assert_eq!(signal, Signal::SIGKILL);
200            assert_eq!(core, false);
201        } else {
202            // test failure
203            assert!(false);
204        }
205
206        Ok(())
207    }
208
209    #[test]
210    fn wait_unknown() {
211        let res = wait(42);
212
213        assert_eq!(
214            format!("{}", res.err().unwrap()),
215            "System call error: No child processes (os error 10)"
216        );
217    }
218}