1use std::fmt;
4use std::process::{ExitStatus, Output};
5use std::str;
6
7#[derive(Debug)]
8pub struct ProcessError {
9 pub desc: String,
11
12 pub code: Option<i32>,
18
19 pub stdout: Option<Vec<u8>>,
24
25 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 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 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 pub fn could_not_execute(cmd: impl fmt::Display) -> ProcessError {
103 ProcessError::new(&format!("could not execute process {cmd}"), None, None)
104 }
105}
106
107pub 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
186pub fn is_simple_exit_code(code: i32) -> bool {
192 code >= 0 && code <= 127
200}