use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use std::os::raw::c_int;
use std::os::unix::process::ExitStatusExt;
use std::process;
use libc::EXIT_SUCCESS;
if_waitid! {
use libc::siginfo_t;
use libc::CLD_CONTINUED;
use libc::CLD_DUMPED;
use libc::CLD_EXITED;
use libc::CLD_KILLED;
use libc::CLD_STOPPED;
use libc::CLD_TRAPPED;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ExitStatusKind {
Continued,
Dumped,
Exited,
Killed,
Stopped,
#[cfg(process_control_unix_waitid)]
Trapped,
Uncategorized,
}
impl ExitStatusKind {
if_waitid! {
const fn new(raw: c_int) -> Self {
match raw {
CLD_CONTINUED => Self::Continued,
CLD_DUMPED => Self::Dumped,
CLD_EXITED => Self::Exited,
CLD_KILLED => Self::Killed,
CLD_STOPPED => Self::Stopped,
CLD_TRAPPED => Self::Trapped,
_ => Self::Uncategorized,
}
}
}
}
macro_rules! code_method {
( $method:ident , $($kind_token:tt)+ ) => {
pub(crate) fn $method(self) -> Option<c_int> {
matches!(self.kind, $($kind_token)+).then_some(self.value)
}
};
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) struct ExitStatus {
value: c_int,
kind: ExitStatusKind,
}
impl ExitStatus {
if_waitid! {
pub(super) unsafe fn new(process_info: siginfo_t) -> Self {
Self {
value: unsafe { process_info.si_status() },
kind: ExitStatusKind::new(process_info.si_code),
}
}
}
pub(crate) fn success(self) -> bool {
self.code() == Some(EXIT_SUCCESS)
}
pub(crate) fn continued(self) -> bool {
self.kind == ExitStatusKind::Continued
}
pub(crate) fn core_dumped(self) -> bool {
self.kind == ExitStatusKind::Dumped
}
code_method!(code, ExitStatusKind::Exited);
code_method!(signal, ExitStatusKind::Dumped | ExitStatusKind::Killed);
code_method!(stopped_signal, ExitStatusKind::Stopped);
}
impl Display for ExitStatus {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self.kind {
ExitStatusKind::Continued => {
f.write_str("continued (WIFCONTINUED)")
}
ExitStatusKind::Dumped => {
write!(f, "signal: {} (core dumped)", self.value)
}
ExitStatusKind::Exited => write!(f, "exit code: {}", self.value),
ExitStatusKind::Killed => write!(f, "signal: {}", self.value),
ExitStatusKind::Stopped => {
write!(f, "stopped (not terminated) by signal: {}", self.value)
}
#[cfg(process_control_unix_waitid)]
ExitStatusKind::Trapped => f.write_str("trapped"),
ExitStatusKind::Uncategorized => {
write!(f, "uncategorized wait status: {}", self.value)
}
}
}
}
impl From<process::ExitStatus> for ExitStatus {
fn from(value: process::ExitStatus) -> Self {
if let Some(exit_code) = value.code() {
Self {
value: exit_code,
kind: ExitStatusKind::Exited,
}
} else if let Some(signal) = value.signal() {
Self {
value: signal,
kind: if value.core_dumped() {
ExitStatusKind::Dumped
} else {
ExitStatusKind::Killed
},
}
} else if let Some(signal) = value.stopped_signal() {
Self {
value: signal,
kind: ExitStatusKind::Stopped,
}
} else {
Self {
value: value.into_raw(),
kind: if value.continued() {
ExitStatusKind::Continued
} else {
ExitStatusKind::Uncategorized
},
}
}
}
}