#
cargo_util/
process_error.rs

    1//! Error value for [`crate::ProcessBuilder`] when a process fails.
    2
    3use std::fmt;
    4use std::process::{ExitStatus, Output};
    5use std::str;
    6
    7#[derive(Debug)]
    8pub struct ProcessError {
    9    /// A detailed description to show to the user why the process failed.
    10    pub desc: String,
    11
    12    /// The exit status of the process.
    13    ///
    14    /// This can be `None` if the process failed to launch (like process not
    15    /// found) or if the exit status wasn't a code but was instead something
    16    /// like termination via a signal.
    17    pub code: Option<i32>,
    18
    19    /// The stdout from the process.
    20    ///
    21    /// This can be `None` if the process failed to launch, or the output was
    22    /// not captured.
    23    pub stdout: Option<Vec<u8>>,
    24
    25    /// The stderr from the process.
    26    ///
    27    /// This can be `None` if the process failed to launch, or the output was
    28    /// not captured.
    29    pub stderr: Option<Vec<u8>>,
    30}
    31
    32impl fmt::Display for ProcessError {
    33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    34        self.desc.fmt(f)
    35    }
    36}
    37
    38impl std::error::Error for ProcessError {}
    39
    40impl ProcessError {
    41    /// Creates a new [`ProcessError`].
    42    ///
    43    /// * `status` can be `None` if the process did not launch.
    44    /// * `output` can be `None` if the process did not launch, or output was not captured.
    45    pub fn new(msg: &str, status: Option<ExitStatus>, output: Option<&Output>) -> ProcessError {
    46        let exit = match status {
    47            Some(s) => exit_status_to_string(s),
    48            None => "never executed".to_string(),
    49        };
    50
    51        Self::new_raw(
    52            msg,
    53            status.and_then(|s| s.code()),
    54            &exit,
    55            output.map(|s| s.stdout.as_slice()),
    56            output.map(|s| s.stderr.as_slice()),
    57        )
    58    }
    59
    60    /// Creates a new [`ProcessError`] with the raw output data.
    61    ///
    62    /// * `code` can be `None` for situations like being killed by a signal on unix.
    63    pub fn new_raw(
    64        msg: &str,
    65        code: Option<i32>,
    66        status: &str,
    67        stdout: Option<&[u8]>,
    68        stderr: Option<&[u8]>,
    69    ) -> ProcessError {
    70        let mut desc = format!("{} ({})", msg, status);
    71
    72        if let Some(out) = stdout {
    73            match str::from_utf8(out) {
    74                Ok(s) if !s.trim().is_empty() => {
    75                    desc.push_str("\n--- stdout\n");
    76                    desc.push_str(s);
    77                }
    78                Ok(..) | Err(..) => {}
    79            }
    80        }
    81        if let Some(out) = stderr {
    82            match str::from_utf8(out) {
    83                Ok(s) if !s.trim().is_empty() => {
    84                    desc.push_str("\n--- stderr\n");
    85                    desc.push_str(s);
    86                }
    87                Ok(..) | Err(..) => {}
    88            }
    89        }
    90
    91        ProcessError {
    92            desc,
    93            code,
    94            stdout: stdout.map(|s| s.to_vec()),
    95            stderr: stderr.map(|s| s.to_vec()),
    96        }
    97    }
    98
    99    /// Creates a [`ProcessError`] with "could not execute process {cmd}".
    100    ///
    101    /// * `cmd` is usually but not limited to [`std::process::Command`].
    102    pub fn could_not_execute(cmd: impl fmt::Display) -> ProcessError {
    103        ProcessError::new(&format!("could not execute process {cmd}"), None, None)
    104    }
    105}
    106
    107/// Converts an [`ExitStatus`]  to a human-readable string suitable for
    108/// displaying to a user.
    109pub fn exit_status_to_string(status: ExitStatus) -> String {
    110    return status_to_string(status);
    111
    112    #[cfg(unix)]
    113    fn status_to_string(status: ExitStatus) -> String {
    114        use std::os::unix::process::*;
    115
    116        if let Some(signal) = status.signal() {
    117            let name = match signal as libc::c_int {
    118                libc::SIGABRT => ", SIGABRT: process abort signal",
    119                libc::SIGALRM => ", SIGALRM: alarm clock",
    120                libc::SIGFPE => ", SIGFPE: erroneous arithmetic operation",
    121                libc::SIGHUP => ", SIGHUP: hangup",
    122                libc::SIGILL => ", SIGILL: illegal instruction",
    123                libc::SIGINT => ", SIGINT: terminal interrupt signal",
    124                libc::SIGKILL => ", SIGKILL: kill",
    125                libc::SIGPIPE => ", SIGPIPE: write on a pipe with no one to read",
    126                libc::SIGQUIT => ", SIGQUIT: terminal quit signal",
    127                libc::SIGSEGV => ", SIGSEGV: invalid memory reference",
    128                libc::SIGTERM => ", SIGTERM: termination signal",
    129                libc::SIGBUS => ", SIGBUS: access to undefined memory",
    130                #[cfg(not(target_os = "haiku"))]
    131                libc::SIGSYS => ", SIGSYS: bad system call",
    132                libc::SIGTRAP => ", SIGTRAP: trace/breakpoint trap",
    133                _ => "",
    134            };
    135            format!("signal: {}{}", signal, name)
    136        } else {
    137            status.to_string()
    138        }
    139    }
    140
    141    #[cfg(windows)]
    142    fn status_to_string(status: ExitStatus) -> String {
    143        use windows_sys::Win32::Foundation::*;
    144
    145        let mut base = status.to_string();
    146        let extra = match status.code().unwrap() as i32 {
    147            STATUS_ACCESS_VIOLATION => "STATUS_ACCESS_VIOLATION",
    148            STATUS_IN_PAGE_ERROR => "STATUS_IN_PAGE_ERROR",
    149            STATUS_INVALID_HANDLE => "STATUS_INVALID_HANDLE",
    150            STATUS_INVALID_PARAMETER => "STATUS_INVALID_PARAMETER",
    151            STATUS_NO_MEMORY => "STATUS_NO_MEMORY",
    152            STATUS_ILLEGAL_INSTRUCTION => "STATUS_ILLEGAL_INSTRUCTION",
    153            STATUS_NONCONTINUABLE_EXCEPTION => "STATUS_NONCONTINUABLE_EXCEPTION",
    154            STATUS_INVALID_DISPOSITION => "STATUS_INVALID_DISPOSITION",
    155            STATUS_ARRAY_BOUNDS_EXCEEDED => "STATUS_ARRAY_BOUNDS_EXCEEDED",
    156            STATUS_FLOAT_DENORMAL_OPERAND => "STATUS_FLOAT_DENORMAL_OPERAND",
    157            STATUS_FLOAT_DIVIDE_BY_ZERO => "STATUS_FLOAT_DIVIDE_BY_ZERO",
    158            STATUS_FLOAT_INEXACT_RESULT => "STATUS_FLOAT_INEXACT_RESULT",
    159            STATUS_FLOAT_INVALID_OPERATION => "STATUS_FLOAT_INVALID_OPERATION",
    160            STATUS_FLOAT_OVERFLOW => "STATUS_FLOAT_OVERFLOW",
    161            STATUS_FLOAT_STACK_CHECK => "STATUS_FLOAT_STACK_CHECK",
    162            STATUS_FLOAT_UNDERFLOW => "STATUS_FLOAT_UNDERFLOW",
    163            STATUS_INTEGER_DIVIDE_BY_ZERO => "STATUS_INTEGER_DIVIDE_BY_ZERO",
    164            STATUS_INTEGER_OVERFLOW => "STATUS_INTEGER_OVERFLOW",
    165            STATUS_PRIVILEGED_INSTRUCTION => "STATUS_PRIVILEGED_INSTRUCTION",
    166            STATUS_STACK_OVERFLOW => "STATUS_STACK_OVERFLOW",
    167            STATUS_DLL_NOT_FOUND => "STATUS_DLL_NOT_FOUND",
    168            STATUS_ORDINAL_NOT_FOUND => "STATUS_ORDINAL_NOT_FOUND",
    169            STATUS_ENTRYPOINT_NOT_FOUND => "STATUS_ENTRYPOINT_NOT_FOUND",
    170            STATUS_CONTROL_C_EXIT => "STATUS_CONTROL_C_EXIT",
    171            STATUS_DLL_INIT_FAILED => "STATUS_DLL_INIT_FAILED",
    172            STATUS_FLOAT_MULTIPLE_FAULTS => "STATUS_FLOAT_MULTIPLE_FAULTS",
    173            STATUS_FLOAT_MULTIPLE_TRAPS => "STATUS_FLOAT_MULTIPLE_TRAPS",
    174            STATUS_REG_NAT_CONSUMPTION => "STATUS_REG_NAT_CONSUMPTION",
    175            STATUS_HEAP_CORRUPTION => "STATUS_HEAP_CORRUPTION",
    176            STATUS_STACK_BUFFER_OVERRUN => "STATUS_STACK_BUFFER_OVERRUN",
    177            STATUS_ASSERTION_FAILURE => "STATUS_ASSERTION_FAILURE",
    178            _ => return base,
    179        };
    180        base.push_str(", ");
    181        base.push_str(extra);
    182        base
    183    }
    184}
    185
    186/// Returns `true` if the given process exit code is something a normal
    187/// process would exit with.
    188///
    189/// This helps differentiate from abnormal termination codes, such as
    190/// segmentation faults or signals.
    191pub fn is_simple_exit_code(code: i32) -> bool {
    192    // Typical unix exit codes are 0 to 127.
    193    // Windows doesn't have anything "typical", and is a
    194    // 32-bit number (which appears signed here, but is really
    195    // unsigned). However, most of the interesting NTSTATUS
    196    // codes are very large. This is just a rough
    197    // approximation of which codes are "normal" and which
    198    // ones are abnormal termination.
    199    code >= 0 && code <= 127
    200}