proc_result/unix/
wait_state.rs

1use super::{ExitCode, Signal};
2
3/// Conditions that interpret a Unix `int status` returned by `waitpid` or similar functions.
4///
5/// Provides methods to check the status of a process, such as whether it exited normally, was
6/// terminated by a signal, stopped, or continued, and to retrieve the exit code or signal number
7/// associated with the process's termination or stopping, without dependence on external crates
8/// such as `libc`.
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum WaitState {
11    /// Indicates that the process exited normally with a specific exit code.
12    Exited {
13        /// The exit code of the process.
14        exit_code: ExitCode,
15    },
16
17    /// Indicates that the process was terminated by a signal, with an optional core dump.
18    Signaled {
19        /// The signal that caused the termination.
20        signal: Signal,
21
22        /// Whether a core dump occurred.
23        core_dump: bool,
24    },
25
26    /// Indicates a wait status code that is not recognized or supported.
27    Unsupported(i32),
28}
29
30impl WaitState {
31    /// Creates a new `UnixWaitIf` from the underlying `i32` status code.
32    #[must_use]
33    pub const fn from_raw(status: i32) -> Self {
34        if Self::is_w_exited(status) {
35            Self::Exited {
36                exit_code: ExitCode::from_raw(Self::w_exit_status(status)),
37            }
38        } else if Self::is_w_signaled(status) {
39            Self::Signaled {
40                signal: Signal::from_raw(Self::w_term_sig(status)),
41                core_dump: Self::is_w_coredump(status),
42            }
43        } else {
44            Self::Unsupported(status)
45        }
46    }
47
48    /// Returns a `i32` status code that represents the current wait status code.
49    ///
50    /// # Note
51    ///
52    /// It is not guaranteed that the result of this function will be reflexive, meaning that
53    /// `UnixWaitIf::from_raw(status).to_raw() == status` may not always hold true; this function
54    /// is provided as a convenience to be able to programatically create a wait status code where
55    /// certain conditions are met, i.e. for testing or simulation purposes.
56    #[must_use]
57    pub const fn to_raw(&self) -> i32 {
58        match self {
59            Self::Exited { exit_code } => (exit_code.to_raw() as i32) << 8,
60            Self::Signaled { signal, core_dump } => {
61                (signal.to_raw() as i32) | if *core_dump { 0x80 } else { 0 }
62            }
63            Self::Unsupported(code) => *code,
64        }
65    }
66
67    /// Represents the stopped status bit.
68    const _WSTOPPED: i32 = 0x7F;
69
70    /// A copy of the Unix `_WSTATUS(status)` macro.
71    #[allow(non_snake_case)]
72    #[inline]
73    #[must_use]
74    const fn _WSTATUS(status: i32) -> i32 {
75        status & 0xFF
76    }
77
78    /// A copy of the Unix `WIFSIGNALED(status)` macro.
79    #[allow(non_snake_case)]
80    #[inline]
81    #[must_use]
82    const fn WIFSIGNALED(status: i32) -> bool {
83        Self::_WSTATUS(status) != Self::_WSTOPPED && Self::_WSTATUS(status) != 0
84    }
85
86    /// A copy of the Unix `WTERMSIG(status)` macro.
87    #[allow(non_snake_case)]
88    #[inline]
89    #[must_use]
90    const fn WTERMSIG(status: i32) -> i32 {
91        status & 0x7F
92    }
93
94    /// A copy of the Unix `WIFEXITED(status)` macro.
95    #[allow(non_snake_case, clippy::verbose_bit_mask)]
96    #[inline]
97    #[must_use]
98    const fn WIFEXITED(status: i32) -> bool {
99        (status & 0o177) == 0
100    }
101
102    /// A copy of the Unix `WEXITSTATUS(status)` macro.
103    #[allow(non_snake_case)]
104    #[inline]
105    #[must_use]
106    const fn WEXITSTATUS(status: i32) -> i32 {
107        (status >> 8) & 0xFF
108    }
109
110    /// A copy of the Unix `WCOREDUMP(status)` macro.
111    #[allow(non_snake_case)]
112    #[inline]
113    #[must_use]
114    const fn WCOREDUMP(status: i32) -> bool {
115        (status & 0o200) != 0
116    }
117
118    /// Returns `true` if the status indicates that the process exited successfully.
119    ///
120    /// Equivalent to the Unix `WIFEXITED(status)` macro.
121    #[must_use]
122    pub const fn is_w_exited(status: i32) -> bool {
123        Self::WIFEXITED(status)
124    }
125
126    /// Returns the exit code of the process.
127    ///
128    /// Equivalent to the Unix `WEXITSTATUS(status)` macro.
129    ///
130    /// # Panics
131    ///
132    /// If [`is_w_exited`](Self::is_w_exited) returns `false`, this function will panic.
133    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
134    #[must_use]
135    pub const fn w_exit_status(status: i32) -> u8 {
136        Self::WEXITSTATUS(status) as u8
137    }
138
139    /// Returns `true` if the status indicates that the process was terminated by a signal.
140    ///
141    /// Equivalent to the Unix `WIFSIGNALED(status)` macro.
142    #[must_use]
143    pub const fn is_w_signaled(status: i32) -> bool {
144        Self::WIFSIGNALED(status)
145    }
146
147    /// Returns the signal number that caused the process to terminate.
148    ///
149    /// Equivalent to the Unix `WTERMSIG(status)` macro.
150    ///
151    /// # Panics
152    ///
153    /// If [`is_w_signaled`](Self::is_w_signaled) returns `false`, this function will panic.
154    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
155    #[must_use]
156    pub const fn w_term_sig(status: i32) -> u8 {
157        Self::WTERMSIG(status) as u8
158    }
159
160    /// Returns `true` if the status indicates a core dump occurred.
161    ///
162    /// Equivalent to the Unix `WCOREDUMP(status)` macro.
163    ///
164    /// # Note
165    ///
166    /// Not universally supported across all Unix-like systems; `WCOREDUMP` is a Linux/BSD
167    /// extension, and is checked only if `WIFSIGNALED(status)` is true.
168    #[must_use]
169    pub const fn is_w_coredump(status: i32) -> bool {
170        Self::WCOREDUMP(status)
171    }
172}
173
174impl From<i32> for WaitState {
175    fn from(status: i32) -> Self {
176        Self::from_raw(status)
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183
184    #[test]
185    fn test_from_raw_exited_0() {
186        let status = WaitState::from_raw(0x0000_0000);
187        assert_eq!(
188            status,
189            WaitState::Exited {
190                exit_code: ExitCode::from_raw(0),
191            }
192        );
193    }
194
195    #[test]
196    fn test_to_raw_exited_0() {
197        let status = WaitState::Exited {
198            exit_code: ExitCode::from_raw(0),
199        };
200        assert_eq!(status.to_raw(), 0x0000_0000);
201    }
202
203    #[test]
204    fn test_from_raw_exited_1() {
205        let status = WaitState::from_raw(0x0000_0100);
206        assert_eq!(
207            status,
208            WaitState::Exited {
209                exit_code: ExitCode::from_raw(1),
210            }
211        );
212    }
213
214    #[test]
215    fn test_to_raw_exited_1() {
216        let status = WaitState::Exited {
217            exit_code: ExitCode::from_raw(1),
218        };
219        assert_eq!(status.to_raw(), 0x0000_0100);
220    }
221
222    #[test]
223    fn test_from_raw_signaled() {
224        let status = WaitState::from_raw(0x0000_0001);
225        assert_eq!(
226            status,
227            WaitState::Signaled {
228                signal: Signal::from_raw(1),
229                core_dump: false,
230            }
231        );
232    }
233
234    #[test]
235    fn test_to_raw_signaled() {
236        let status = WaitState::Signaled {
237            signal: Signal::from_raw(1),
238            core_dump: false,
239        };
240        assert_eq!(status.to_raw(), 0x0000_0001);
241    }
242
243    #[test]
244    fn test_from_raw_signaled_with_coredump() {
245        let status = WaitState::from_raw(0x0000_0081);
246        assert_eq!(
247            status,
248            WaitState::Signaled {
249                signal: Signal::from_raw(1),
250                core_dump: true,
251            }
252        );
253    }
254
255    #[test]
256    fn test_to_raw_signaled_with_coredump() {
257        let status = WaitState::Signaled {
258            signal: Signal::from_raw(1),
259            core_dump: true,
260        };
261        assert_eq!(status.to_raw(), 0x0000_0081);
262    }
263}
264
265// Tests that compare the behavior of the `UnixWaitIf` struct with the libc macros.
266#[cfg(all(test, unix))]
267mod libc_verification_tests {
268    use super::*;
269    use libc::{WCOREDUMP, WEXITSTATUS, WIFEXITED, WIFSIGNALED, WTERMSIG};
270
271    #[test]
272    fn test_wifexited_true() {
273        assert!(WIFEXITED(0x0000_0000));
274        assert!(WaitState::is_w_exited(0x0000_0000));
275    }
276
277    #[test]
278    fn test_wifexited_false() {
279        assert!(!WIFEXITED(0x0000_0001));
280        assert!(!WaitState::is_w_exited(0x0000_0001));
281    }
282
283    #[test]
284    fn test_wexitstatus_success() {
285        assert_eq!(WEXITSTATUS(0x0000_0000), 0);
286        assert_eq!(WaitState::w_exit_status(0x0000_0000), 0);
287    }
288
289    #[test]
290    fn test_wexitstatus_failure() {
291        assert_eq!(WEXITSTATUS(0x0000_0100), 1);
292        assert_eq!(WaitState::w_exit_status(0x0000_0100), 1);
293    }
294
295    #[test]
296    fn test_wifsignaled_true() {
297        assert!(WIFSIGNALED(0x0000_0001));
298        assert!(WaitState::is_w_signaled(0x0000_0001));
299    }
300
301    #[test]
302    fn test_wifsignaled_false() {
303        assert!(!WIFSIGNALED(0x0000_0000));
304        assert!(!WaitState::is_w_signaled(0x0000_0000));
305    }
306
307    #[test]
308    fn test_wtermsig() {
309        assert_eq!(WTERMSIG(0x0000_0001), 1);
310        assert_eq!(WaitState::w_term_sig(0x0000_0001), 1);
311    }
312
313    #[test]
314    fn test_wcoredump_true() {
315        assert!(WCOREDUMP(0x0000_0081));
316        assert!(WaitState::is_w_coredump(0x0000_0081));
317    }
318
319    #[test]
320    fn test_wcoredump_false() {
321        assert!(!WCOREDUMP(0x0000_0001));
322        assert!(!WaitState::is_w_coredump(0x0000_0001));
323    }
324}