use std::process::ExitStatus;
pub trait ExitStatusFromCode {
#[cfg(unix)]
#[must_use]
fn from_code(code: u8) -> ExitStatus;
#[cfg(windows)]
#[must_use]
fn from_code(code: u32) -> ExitStatus;
}
impl ExitStatusFromCode for ExitStatus {
#[cfg(unix)]
fn from_code(code: u8) -> ExitStatus {
use std::os::unix::process::ExitStatusExt;
ExitStatus::from_raw((code as i32) << 8)
}
#[cfg(windows)]
fn from_code(code: u32) -> ExitStatus {
use std::os::windows::process::ExitStatusExt;
ExitStatus::from_raw(code)
}
}
pub(crate) fn status_from_error(error: &std::io::Error) -> ExitStatus {
use std::io::ErrorKind;
let code = match error.kind() {
ErrorKind::NotFound => 127, ErrorKind::PermissionDenied | ErrorKind::IsADirectory | ErrorKind::NotADirectory | ErrorKind::ArgumentListTooLong | ErrorKind::OutOfMemory | ErrorKind::ExecutableFileBusy => 126,
_ => 1,
};
ExitStatus::from_code(code)
}
pub(crate) fn signal_line(status: &ExitStatus) -> Option<String> {
#[cfg(unix)]
{
use std::os::unix::process::ExitStatusExt;
status.signal().map(|signal| {
let name = match signal {
1 => "SIGHUP",
2 => "SIGINT",
3 => "SIGQUIT",
6 => "SIGABRT",
9 => "SIGKILL",
14 => "SIGALRM",
15 => "SIGTERM",
_ => "",
};
if name.is_empty() {
format!("signal: {signal}")
} else {
format!("signal: {signal} ({name})")
}
})
}
#[cfg(windows)]
{
let _ = status;
None
}
}
pub(crate) fn bashify(status: &ExitStatus) -> i32 {
#[cfg(unix)]
{
use std::os::unix::process::ExitStatusExt;
status
.code()
.or_else(|| status.signal().map(|s| 128 + s))
.unwrap_or(1)
}
#[cfg(windows)]
{
status.code().unwrap_or(1)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(unix)]
fn maps_signal_to_exit() {
use std::os::unix::process::ExitStatusExt;
let sigterm = 15;
let status = ExitStatus::from_raw(sigterm);
assert_eq!(None, status.code());
assert_eq!(Some(15), status.signal());
let code = bashify(&status);
assert_eq!(143, code);
}
#[test]
#[cfg(unix)]
fn maps_signal_to_human_readable_output() {
use std::os::unix::process::ExitStatusExt;
let sigterm = 15;
let status = ExitStatus::from_raw(sigterm);
assert_eq!(None, status.code());
assert_eq!(Some(15), status.signal());
assert_eq!(
String::from("signal: 15 (SIGTERM)"),
signal_line(&status).unwrap()
);
let signal = 126;
let status = ExitStatus::from_raw(signal);
assert_eq!(None, status.code());
assert_eq!(Some(126), status.signal());
assert_eq!(String::from("signal: 126"), signal_line(&status).unwrap());
}
#[test]
fn maps_error_kinds_to_shell_codes() {
use std::io::{Error, ErrorKind};
assert_eq!(
Some(127),
status_from_error(&Error::from(ErrorKind::NotFound)).code()
);
assert_eq!(
Some(126),
status_from_error(&Error::from(ErrorKind::PermissionDenied)).code()
);
assert_eq!(
Some(1),
status_from_error(&Error::from(ErrorKind::Other)).code()
);
}
#[test]
fn test_status_from_code() {
for code in 0..=255 {
let status = ExitStatus::from_code(code);
assert_eq!(Some(code as i32), status.code());
}
}
}