reverie_process/
exit_status.rs1use std::os::unix::process::ExitStatusExt;
10
11use nix::sys::signal;
12use nix::sys::signal::SigHandler;
13use nix::sys::signal::SigSet;
14use nix::sys::signal::SigmaskHow;
15use nix::sys::signal::Signal;
16
17#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
23pub enum ExitStatus {
24 Exited(i32),
26 Signaled(Signal, bool),
28}
29
30impl ExitStatus {
31 pub const SUCCESS: Self = ExitStatus::Exited(0);
33
34 pub fn from_raw(code: i32) -> Self {
36 if libc::WIFEXITED(code) {
37 ExitStatus::Exited(libc::WEXITSTATUS(code))
38 } else {
39 ExitStatus::Signaled(
40 Signal::try_from(libc::WTERMSIG(code)).unwrap(),
41 libc::WCOREDUMP(code),
42 )
43 }
44 }
45
46 pub fn into_raw(self) -> i32 {
48 match self {
49 ExitStatus::Exited(code) => code << 8,
50 ExitStatus::Signaled(sig, coredump) => {
51 if coredump {
52 (sig as i32 | 0x80) & 0xff
53 } else {
54 sig as i32 & 0x7f
55 }
56 }
57 }
58 }
59
60 pub fn signal(&self) -> Option<i32> {
62 match self {
63 ExitStatus::Exited(_) => None,
64 ExitStatus::Signaled(sig, _) => Some(*sig as i32 & 0x7f),
65 }
66 }
67
68 pub fn success(&self) -> bool {
71 self == &ExitStatus::Exited(0)
72 }
73
74 pub fn code(&self) -> Option<i32> {
77 if let ExitStatus::Exited(code) = *self {
78 Some(code)
79 } else {
80 None
81 }
82 }
83
84 pub fn raise_or_exit(self) -> ! {
87 match self {
88 ExitStatus::Signaled(signal, core_dump) => {
89 if core_dump {
90 let limit = libc::rlimit {
93 rlim_cur: 0,
94 rlim_max: 0,
95 };
96 unsafe { libc::setrlimit(libc::RLIMIT_CORE, &limit) };
97 }
98
99 let _ = unsafe { signal::signal(signal, SigHandler::SigDfl) };
101 let _ = signal::raise(signal);
102
103 let mut mask = SigSet::empty();
105 mask.add(signal);
106 let _ = signal::sigprocmask(SigmaskHow::SIG_UNBLOCK, Some(&mask), None);
107
108 std::process::exit(signal as i32 + 128);
110 }
111 ExitStatus::Exited(code) => std::process::exit(code),
112 }
113 }
114}
115
116impl From<ExitStatus> for std::process::ExitStatus {
117 fn from(status: ExitStatus) -> Self {
118 Self::from_raw(status.into_raw())
119 }
120}
121
122impl From<std::process::ExitStatus> for ExitStatus {
123 fn from(status: std::process::ExitStatus) -> Self {
124 if let Some(sig) = status.signal() {
125 ExitStatus::Signaled(Signal::try_from(sig).unwrap(), true)
126 } else {
127 ExitStatus::Exited(status.code().unwrap_or(255))
128 }
129 }
130}
131
132impl serde::Serialize for ExitStatus {
133 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
134 where
135 S: serde::ser::Serializer,
136 {
137 serializer.serialize_i32(self.into_raw())
138 }
139}
140
141impl<'de> serde::Deserialize<'de> for ExitStatus {
142 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
143 where
144 D: serde::de::Deserializer<'de>,
145 {
146 let value = i32::deserialize(deserializer)?;
147 Ok(ExitStatus::from_raw(value))
148 }
149}
150
151#[cfg(all(test, not(sanitized)))]
155mod tests_non_sanitized {
156 use nix::sys::signal;
157 use nix::sys::signal::Signal;
158 use nix::sys::wait::WaitStatus;
159 use nix::sys::wait::waitpid;
160 use nix::unistd::ForkResult;
161 use nix::unistd::fork;
162
163 use super::*;
164
165 fn run_forked<F>(f: F) -> nix::Result<ExitStatus>
167 where
168 F: FnOnce() -> nix::Result<()>,
169 {
170 match unsafe { fork() }? {
171 ForkResult::Parent { child, .. } => {
172 match waitpid(child, None)? {
174 WaitStatus::Exited(_, code) => Ok(ExitStatus::Exited(code)),
175 WaitStatus::Signaled(_, sig, coredump) => {
176 Ok(ExitStatus::Signaled(sig, coredump))
177 }
178 wait_status => unreachable!("Got unexpected wait status: {:?}", wait_status),
179 }
180 }
181 ForkResult::Child => {
182 let limit = libc::rlimit {
184 rlim_cur: 0,
185 rlim_max: 0,
186 };
187 unsafe {
188 for &sig in &[libc::SIGALRM, libc::SIGINT, libc::SIGVTALRM] {
190 libc::signal(sig, libc::SIG_DFL);
191 }
192 libc::setrlimit(libc::RLIMIT_CORE, &limit)
194 };
195
196 let code = match f() {
198 Ok(()) => 0,
199 Err(err) => {
200 eprintln!("{}", err);
201 1
202 }
203 };
204
205 unsafe { ::libc::_exit(code) };
212 }
213 }
214 }
215
216 #[test]
217 fn normal_exit() {
218 if crate::test_runs_in_own_process() {
219 return;
220 }
221 assert_eq!(
222 run_forked(|| { unsafe { libc::_exit(0) } }),
223 Ok(ExitStatus::Exited(0))
224 );
225
226 assert_eq!(
227 run_forked(|| { unsafe { libc::_exit(42) } }),
228 Ok(ExitStatus::Exited(42))
229 );
230
231 assert_eq!(
233 run_forked(|| {
234 unsafe { libc::syscall(libc::SYS_exit, 42) };
235 unreachable!();
236 }),
237 Ok(ExitStatus::Exited(42))
238 );
239
240 assert_eq!(
242 run_forked(|| {
243 unsafe { libc::syscall(libc::SYS_exit_group, 42) };
244 unreachable!();
245 }),
246 Ok(ExitStatus::Exited(42))
247 );
248 }
249
250 #[test]
251 fn exit_by_signal() {
252 if crate::test_runs_in_own_process() {
253 return;
254 }
255 assert_eq!(
256 run_forked(|| {
257 signal::raise(Signal::SIGALRM)?;
258 unreachable!();
259 }),
260 Ok(ExitStatus::Signaled(Signal::SIGALRM, false))
261 );
262
263 assert_eq!(
264 run_forked(|| {
265 signal::raise(Signal::SIGILL)?;
266 unreachable!();
267 }),
268 Ok(ExitStatus::Signaled(Signal::SIGILL, true))
269 );
270 }
271
272 #[test]
273 fn propagate_exit() {
274 if crate::test_runs_in_own_process() {
275 return;
276 }
277 assert_eq!(
285 run_forked(|| { ExitStatus::Exited(0).raise_or_exit() }),
286 Ok(ExitStatus::Exited(0))
287 );
288 assert_eq!(
289 run_forked(|| { ExitStatus::Exited(42).raise_or_exit() }),
290 Ok(ExitStatus::Exited(42))
291 );
292 }
293
294 #[test]
295 fn propagate_signal() {
296 if crate::test_runs_in_own_process() {
297 return;
298 }
299 assert_eq!(
300 run_forked(|| { ExitStatus::Signaled(Signal::SIGILL, true).raise_or_exit() }),
301 Ok(ExitStatus::Signaled(Signal::SIGILL, true))
302 );
303 assert_eq!(
304 run_forked(|| { ExitStatus::Signaled(Signal::SIGALRM, false).raise_or_exit() }),
305 Ok(ExitStatus::Signaled(Signal::SIGALRM, false))
306 );
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313
314 #[test]
315 fn exit_code_into_raw() {
316 assert_eq!(ExitStatus::Exited(1).into_raw(), 0x1 << 8);
317 assert_eq!(
318 ExitStatus::Signaled(Signal::SIGINT, false).into_raw(),
319 Signal::SIGINT as i32
320 );
321 assert_eq!(
322 ExitStatus::Signaled(Signal::SIGILL, true).into_raw(),
323 0x80 | Signal::SIGILL as i32
324 );
325 assert_ne!(
326 ExitStatus::Exited(2).into_raw(),
327 ExitStatus::Signaled(Signal::SIGINT, false).into_raw()
328 );
329 }
330
331 #[test]
332 fn exit_status_from_raw() {
333 assert_eq!(ExitStatus::from_raw(0x100).code(), Some(1));
334 assert_eq!(ExitStatus::from_raw(0x100).signal(), None);
335 assert_eq!(ExitStatus::from_raw(0x84).code(), None);
336 assert_eq!(ExitStatus::from_raw(0x84).signal(), Some(4));
337 }
338}