summaryrefslogtreecommitdiff
path: root/crates/nu-protocol/src/process/exit_status.rs
blob: 0ea6b0e72bc4bd2a26f18efb2b57fe8b1e988d62 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
use crate::{ShellError, Span};
use std::process;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExitStatus {
    Exited(i32),
    #[cfg(unix)]
    Signaled {
        signal: i32,
        core_dumped: bool,
    },
}

impl ExitStatus {
    pub fn code(self) -> i32 {
        match self {
            ExitStatus::Exited(code) => code,
            #[cfg(unix)]
            ExitStatus::Signaled { signal, .. } => -signal,
        }
    }

    pub fn check_ok(self, span: Span) -> Result<(), ShellError> {
        match self {
            ExitStatus::Exited(exit_code) => {
                if let Ok(exit_code) = exit_code.try_into() {
                    Err(ShellError::NonZeroExitCode { exit_code, span })
                } else {
                    Ok(())
                }
            }
            #[cfg(unix)]
            ExitStatus::Signaled {
                signal,
                core_dumped,
            } => {
                use nix::sys::signal::Signal;

                let sig = Signal::try_from(signal);

                if sig == Ok(Signal::SIGPIPE) {
                    // Processes often exit with SIGPIPE, but this is not an error condition.
                    Ok(())
                } else {
                    let signal_name = sig.map(Signal::as_str).unwrap_or("unknown signal").into();
                    Err(if core_dumped {
                        ShellError::CoreDumped {
                            signal_name,
                            signal,
                            span,
                        }
                    } else {
                        ShellError::TerminatedBySignal {
                            signal_name,
                            signal,
                            span,
                        }
                    })
                }
            }
        }
    }
}

#[cfg(unix)]
impl From<process::ExitStatus> for ExitStatus {
    fn from(status: process::ExitStatus) -> Self {
        use std::os::unix::process::ExitStatusExt;

        match (status.code(), status.signal()) {
            (Some(code), None) => Self::Exited(code),
            (None, Some(signal)) => Self::Signaled {
                signal,
                core_dumped: status.core_dumped(),
            },
            (None, None) => {
                debug_assert!(false, "ExitStatus should have either a code or a signal");
                Self::Exited(-1)
            }
            (Some(code), Some(signal)) => {
                // Should be unreachable, as `code()` will be `None` if `signal()` is `Some`
                // according to the docs for `ExitStatus::code`.
                debug_assert!(
                    false,
                    "ExitStatus cannot have both a code ({code}) and a signal ({signal})"
                );
                Self::Signaled {
                    signal,
                    core_dumped: status.core_dumped(),
                }
            }
        }
    }
}

#[cfg(not(unix))]
impl From<process::ExitStatus> for ExitStatus {
    fn from(status: process::ExitStatus) -> Self {
        let code = status.code();
        debug_assert!(
            code.is_some(),
            "`ExitStatus::code` cannot return `None` on windows"
        );
        Self::Exited(code.unwrap_or(-1))
    }
}