known_errors/
sysexits.rs

1// This is free and unencumbered software released into the public domain.
2
3#![allow(unused)]
4
5#[cfg(feature = "std")]
6extern crate std;
7
8pub type SysexitsResult<T> = core::result::Result<T, SysexitsError>;
9
10/// Standard `<sysexits.h>` preferable exit codes for programs on Unix systems.
11///
12/// Quoting the rationale given in the [`sysexits(3)`] man page on BSD systems:
13///
14/// > According to `style(9)`, it is not a good practice to call `exit(3)` with
15/// > arbitrary values to indicate a failure condition when ending a program.
16/// > Instead, the pre-defined exit codes from `sysexits` should be used, so
17/// > the caller of the process can get a rough estimation about the failure
18/// > class without looking up the source code.
19///
20/// [`sysexits(3)`]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/sysexits.3.html
21#[allow(non_camel_case_types)]
22#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
23#[repr(u8)]
24pub enum SysexitsError {
25    #[default]
26    /// Successful termination.
27    EX_OK = 0,
28
29    /// The command was used incorrectly, e.g., with the wrong number of
30    /// arguments, a bad flag, a bad syntax in a parameter, or whatever.
31    EX_USAGE = 64,
32
33    /// The input data was incorrect in some way.
34    ///
35    /// This should only be used for user's data and not system files.
36    EX_DATAERR = 65,
37
38    /// An input file (not a system file) did not exist or was not readable.
39    ///
40    /// This could also include errors like "No message" to a mailer (if it
41    /// cared to catch it).
42    EX_NOINPUT = 66,
43
44    /// The user specified did not exist.
45    ///
46    /// This might be used for mail addresses or remote logins.
47    EX_NOUSER = 67,
48
49    /// The host specified did not exist.
50    ///
51    /// This is used in mail addresses or network requests.
52    EX_NOHOST = 68,
53
54    /// A service is unavailable.
55    ///
56    /// This can occur if a support program or file does not exist.
57    /// This can also be used as a catchall message when something you wanted
58    /// to do doesn't work, but you don't know why.
59    EX_UNAVAILABLE = 69,
60
61    /// An internal software error has been detected.
62    ///
63    /// This should be limited to non-operating system related errors as
64    /// possible.
65    EX_SOFTWARE = 70,
66
67    /// An operating system error has been detected.
68    ///
69    /// This is intended to be used for such things as "cannot fork", "cannot
70    /// create pipe", or the like. It includes things like `getuid` returning a
71    /// user that does not exist in the `/etc/passwd` file.
72    EX_OSERR = 71,
73
74    /// Some system file (e.g., `/etc/passwd`, `/var/run/utmp`, etc.) does not
75    /// exist, cannot be opened, or has some sort of error (e.g., syntax
76    /// error).
77    EX_OSFILE = 72,
78
79    /// A (user specified) output file cannot be created.
80    EX_CANTCREAT = 73,
81
82    /// An error occurred while doing I/O on some file.
83    EX_IOERR = 74,
84
85    /// Temporary failure, indicating something that is not really an error.
86    ///
87    /// In sendmail, this means that a mailer (e.g.) could not create a
88    /// connection, and the request should be reattempted later.
89    EX_TEMPFAIL = 75,
90
91    /// The remote system returned something that was "not possible" during a
92    /// protocol exchange.
93    EX_PROTOCOL = 76,
94
95    /// You did not have sufficient permission to perform the operation.
96    ///
97    /// This is not intended for file system problems, which should use
98    /// `EX_NOINPUT` or `EX_CANTCREAT`, but rather for higher level
99    /// permissions.
100    EX_NOPERM = 77,
101
102    /// Something was found in an unconfigured or misconfigured state.
103    EX_CONFIG = 78,
104}
105
106impl SysexitsError {
107    pub fn is_success(&self) -> bool {
108        *self == Self::EX_OK
109    }
110
111    pub fn is_failure(&self) -> bool {
112        !self.is_success()
113    }
114
115    pub fn as_u8(&self) -> u8 {
116        *self as u8
117    }
118
119    pub fn as_i32(&self) -> i32 {
120        *self as i32
121    }
122
123    pub fn as_str(&self) -> &'static str {
124        self.name()
125    }
126
127    #[cfg(feature = "std")]
128    pub fn as_exit_code(&self) -> std::process::ExitCode {
129        std::process::ExitCode::from(self.as_u8())
130    }
131
132    #[cfg(feature = "std")]
133    fn as_exit_status(&self) -> Option<std::process::ExitStatus> {
134        match *self {
135            Self::EX_OK => Some(std::process::ExitStatus::default()),
136            _ => None,
137        }
138    }
139
140    pub fn code(&self) -> Option<i32> {
141        Some(self.as_i32())
142    }
143
144    pub fn name(&self) -> &'static str {
145        use SysexitsError::*;
146        match *self {
147            EX_OK => "EX_OK",
148            EX_USAGE => "EX_USAGE",
149            EX_DATAERR => "EX_DATAERR",
150            EX_NOINPUT => "EX_NOINPUT",
151            EX_NOUSER => "EX_NOUSER",
152            EX_NOHOST => "EX_NOHOST",
153            EX_UNAVAILABLE => "EX_UNAVAILABLE",
154            EX_SOFTWARE => "EX_SOFTWARE",
155            EX_OSERR => "EX_OSERR",
156            EX_OSFILE => "EX_OSFILE",
157            EX_CANTCREAT => "EX_CANTCREAT",
158            EX_IOERR => "EX_IOERR",
159            EX_TEMPFAIL => "EX_TEMPFAIL",
160            EX_PROTOCOL => "EX_PROTOCOL",
161            EX_NOPERM => "EX_NOPERM",
162            EX_CONFIG => "EX_CONFIG",
163        }
164    }
165
166    pub fn summary(&self) -> &'static str {
167        // See: https://github.com/openbsd/src/blob/master/include/sysexits.h
168        use SysexitsError::*;
169        match *self {
170            EX_OK => "successful termination",
171            EX_USAGE => "command line usage error",
172            EX_DATAERR => "data format error",
173            EX_NOINPUT => "cannot open input",
174            EX_NOUSER => "addressee unknown",
175            EX_NOHOST => "host name unknown",
176            EX_UNAVAILABLE => "service unavailable",
177            EX_SOFTWARE => "internal software error",
178            EX_OSERR => "system error",
179            EX_OSFILE => "critical OS file missing",
180            EX_CANTCREAT => "can't create (user) output file",
181            EX_IOERR => "input/output error",
182            EX_TEMPFAIL => "temporary failure",
183            EX_PROTOCOL => "remote error in protocol",
184            EX_NOPERM => "permission denied",
185            EX_CONFIG => "configuration error",
186        }
187    }
188}
189
190impl core::fmt::Display for SysexitsError {
191    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
192        write!(f, "{}", self.as_str())
193    }
194}
195
196impl core::str::FromStr for SysexitsError {
197    type Err = ();
198
199    fn from_str(input: &str) -> Result<Self, Self::Err> {
200        use SysexitsError::*;
201        Ok(match input {
202            "EX_OK" => EX_OK,
203            "EX_USAGE" => EX_USAGE,
204            "EX_DATAERR" => EX_DATAERR,
205            "EX_NOINPUT" => EX_NOINPUT,
206            "EX_NOUSER" => EX_NOUSER,
207            "EX_NOHOST" => EX_NOHOST,
208            "EX_UNAVAILABLE" => EX_UNAVAILABLE,
209            "EX_SOFTWARE" => EX_SOFTWARE,
210            "EX_OSERR" => EX_OSERR,
211            "EX_OSFILE" => EX_OSFILE,
212            "EX_CANTCREAT" => EX_CANTCREAT,
213            "EX_IOERR" => EX_IOERR,
214            "EX_TEMPFAIL" => EX_TEMPFAIL,
215            "EX_PROTOCOL" => EX_PROTOCOL,
216            "EX_NOPERM" => EX_NOPERM,
217            "EX_CONFIG" => EX_CONFIG,
218            _ => match u8::from_str(input) {
219                Ok(code) => return Self::try_from(code).map_err(|_| ()),
220                Err(_) => return Err(()),
221            },
222        })
223    }
224}
225
226impl TryFrom<u8> for SysexitsError {
227    type Error = u8;
228
229    fn try_from(code: u8) -> Result<Self, Self::Error> {
230        use SysexitsError::*;
231        Ok(match code {
232            0 => EX_OK,
233            64 => EX_USAGE,
234            65 => EX_DATAERR,
235            66 => EX_NOINPUT,
236            67 => EX_NOUSER,
237            68 => EX_NOHOST,
238            69 => EX_UNAVAILABLE,
239            70 => EX_SOFTWARE,
240            71 => EX_OSERR,
241            72 => EX_OSFILE,
242            73 => EX_CANTCREAT,
243            74 => EX_IOERR,
244            75 => EX_TEMPFAIL,
245            76 => EX_PROTOCOL,
246            77 => EX_NOPERM,
247            78 => EX_CONFIG,
248            _ => return Err(code),
249        })
250    }
251}
252
253impl TryFrom<i32> for SysexitsError {
254    type Error = i32;
255
256    fn try_from(code: i32) -> Result<Self, Self::Error> {
257        Self::try_from(code as u8).map_err(|_| code)
258    }
259}
260
261#[cfg(feature = "std")]
262impl TryFrom<std::process::ExitStatus> for SysexitsError {
263    type Error = Option<i32>;
264
265    fn try_from(status: std::process::ExitStatus) -> Result<Self, Self::Error> {
266        match status.code() {
267            Some(code) => Self::try_from(code).map_err(|_| Some(code)),
268            None => Err(None),
269        }
270    }
271}
272
273#[cfg(feature = "std")]
274impl From<std::boxed::Box<dyn std::error::Error>> for SysexitsError {
275    fn from(error: std::boxed::Box<dyn std::error::Error>) -> Self {
276        Self::from(&error)
277    }
278}
279
280#[cfg(feature = "std")]
281impl From<&std::boxed::Box<dyn std::error::Error>> for SysexitsError {
282    fn from(_error: &std::boxed::Box<dyn std::error::Error>) -> Self {
283        Self::EX_SOFTWARE
284    }
285}
286
287#[cfg(feature = "std")]
288impl From<std::io::Error> for SysexitsError {
289    fn from(error: std::io::Error) -> Self {
290        Self::from(&error)
291    }
292}
293
294#[cfg(feature = "std")]
295impl From<&std::io::Error> for SysexitsError {
296    fn from(error: &std::io::Error) -> Self {
297        use std::io::ErrorKind::*;
298        match error.kind() {
299            AddrInUse => Self::EX_TEMPFAIL,
300            AddrNotAvailable => Self::EX_USAGE,
301            AlreadyExists => Self::EX_CANTCREAT,
302            BrokenPipe => Self::EX_IOERR,
303            ConnectionAborted => Self::EX_PROTOCOL,
304            ConnectionRefused => Self::EX_UNAVAILABLE,
305            ConnectionReset => Self::EX_PROTOCOL,
306            Interrupted => Self::EX_TEMPFAIL,
307            InvalidData => Self::EX_DATAERR,
308            InvalidInput => Self::EX_DATAERR,
309            NotConnected => Self::EX_PROTOCOL,
310            NotFound => Self::EX_NOINPUT,
311            Other => Self::EX_UNAVAILABLE,
312            OutOfMemory => Self::EX_TEMPFAIL,
313            PermissionDenied => Self::EX_NOPERM,
314            TimedOut => Self::EX_IOERR,
315            UnexpectedEof => Self::EX_IOERR,
316            Unsupported => Self::EX_SOFTWARE,
317            WouldBlock => Self::EX_IOERR,
318            WriteZero => Self::EX_IOERR,
319            _ => Self::EX_UNAVAILABLE, // catch-all
320        }
321    }
322}
323
324#[cfg(feature = "gofer")]
325impl From<gofer::Error> for SysexitsError {
326    fn from(error: gofer::Error) -> Self {
327        Self::from(&error)
328    }
329}
330
331#[cfg(feature = "gofer")]
332impl From<&gofer::Error> for SysexitsError {
333    fn from(error: &gofer::Error) -> Self {
334        // See: https://docs.rs/gofer/latest/gofer/enum.Error.html
335        use gofer::Error::*;
336        match error {
337            InvalidUrl(_) => Self::EX_DATAERR,
338            UnknownScheme(_) => Self::EX_DATAERR,
339            _ => Self::EX_SOFTWARE, // catch-all
340        }
341    }
342}
343
344#[cfg(feature = "serde-json")]
345impl From<serde_json::Error> for SysexitsError {
346    fn from(error: serde_json::Error) -> Self {
347        Self::from(&error)
348    }
349}
350
351#[cfg(feature = "serde-json")]
352impl From<&serde_json::Error> for SysexitsError {
353    fn from(error: &serde_json::Error) -> Self {
354        // See: https://docs.rs/serde_json/latest/serde_json/struct.Error.html
355        use serde_json::error::Category::*;
356        match error.classify() {
357            Io => Self::EX_IOERR,
358            Syntax => Self::EX_DATAERR,
359            Data => Self::EX_DATAERR,
360            Eof => Self::EX_NOINPUT,
361            _ => Self::EX_SOFTWARE, // catch-all
362        }
363    }
364}
365
366#[cfg(feature = "tokio")]
367impl From<tokio::task::JoinError> for SysexitsError {
368    fn from(error: tokio::task::JoinError) -> Self {
369        Self::from(&error)
370    }
371}
372
373#[cfg(feature = "tokio")]
374impl From<&tokio::task::JoinError> for SysexitsError {
375    fn from(_error: &tokio::task::JoinError) -> Self {
376        // See: https://docs.rs/tokio/latest/tokio/task/struct.JoinError.html
377        Self::EX_SOFTWARE
378    }
379}
380
381#[cfg(feature = "std")]
382impl std::process::Termination for SysexitsError {
383    fn report(self) -> std::process::ExitCode {
384        (self as u8).into()
385    }
386}
387
388#[cfg(feature = "std")]
389impl std::error::Error for SysexitsError {}
390
391#[cfg(feature = "error-stack")]
392impl error_stack::Context for SysexitsError {}
393
394/// Exit the process with the given exit code.
395#[cfg(feature = "std")]
396pub fn exit(code: SysexitsError) -> ! {
397    std::process::exit(code as i32);
398}
399
400/// Exit the process with the given exit code and error string to be printed to
401/// standard error.
402#[cfg(feature = "std")]
403#[macro_export]
404macro_rules! abort {
405    ($code:expr, $($t:tt)*) => {{
406        std::eprintln!($($t)*);
407        exit($code)
408    }};
409}