rsbash/
error.rs

1use libc::{__errno_location, c_int};
2use std::ffi::{CStr, NulError};
3use thiserror::Error;
4
5use crate::process::ProcessError;
6
7/// The error thrown if something went wrong in the processing of the command.
8#[cfg(unix)]
9#[derive(Error, Debug, PartialEq)]
10pub enum RashError {
11    /// The given command contained a null byte.
12    /// Commands must **not** contain null bytes as they're converted into CStrings.
13    ///
14    /// If this error is thrown, the error message will contain the position
15    /// of the null byte in the command.
16    #[error("Null byte found in command at pos {}", pos)]
17    NullByteInCommand {
18        pos: usize,
19    },
20    /// A system call failed.
21    ///
22    /// If this error is thrown, the error message will contain the errno,
23    /// a description of syscall that failed, and the strerror output.
24    #[error("{:?}", message)]
25    KernelError {
26        message: String,
27    },
28    /// We couldn't obtain stdout.
29    /// This can occur if the stdout is not valid UTF-8
30    /// or for any standard IO error kind.
31    ///
32    /// If this error is thrown, the error message will be the error message
33    /// given by calling `to_string()` on the source error.
34    #[error("Couldn't read stdout: {:?}", message)]
35    FailedToReadStdout {
36        message: String,
37    },
38    /// We couldn't obtain stderr.
39    /// This can occur if the stderr is not valid UTF-8
40    /// or for any standard IO error kind.
41    ///
42    /// If this error is thrown, the error message will be the error message
43    /// given by calling `to_string()` on the source error.
44    #[error("Couldn't read stderr: {:?}", message)]
45    FailedToReadStderr {
46        message: String,
47    },
48}
49
50impl From<ProcessError> for RashError {
51    fn from(v: ProcessError) -> Self {
52        fn into_kernel_error<S: AsRef<str>>(s: S) -> RashError {
53            RashError::KernelError {
54                message: unsafe { RashError::format_kernel_error_message(s) },
55            }
56        }
57        match v {
58            ProcessError::CouldNotFork
59            | ProcessError::CouldNotCreatePipe
60            | ProcessError::CouldNotDupFd(_)
61            | ProcessError::OpenDidNotCloseNormally => into_kernel_error(v.to_string()),
62            ProcessError::CouldNotGetStderr => RashError::FailedToReadStderr {
63                message: v.to_string(),
64            },
65            ProcessError::CouldNotGetStdout => RashError::FailedToReadStdout {
66                message: v.to_string(),
67            },
68        }
69    }
70}
71
72impl From<NulError> for RashError {
73    fn from(v: NulError) -> Self {
74        RashError::NullByteInCommand {
75            pos: v.nul_position(),
76        }
77    }
78}
79
80impl RashError {
81    pub(crate) unsafe fn format_kernel_error_message<S: AsRef<str>>(description: S) -> String {
82        let errno = *__errno_location();
83        let strerror = Self::strerror(errno);
84        format!(
85            "Received errno {}, Description: {}, strerror output: {strerror}.",
86            errno.to_string(),
87            description.as_ref()
88        )
89    }
90
91    unsafe fn strerror(errno: c_int) -> String {
92        let strerror = libc::strerror(errno);
93        if strerror.is_null() {
94            return "Couldn't get strerror - libc::strerror returned null.".to_string();
95        }
96        return match CStr::from_ptr(strerror).to_str() {
97            Ok(s) => s.to_string(),
98            Err(e) => e.to_string(),
99        };
100    }
101}