capctl/
err.rs

1use core::fmt;
2
3pub type Result<T> = core::result::Result<T, Error>;
4
5/// Represents an OS error encountered when performing an operation.
6///
7/// Note: Parsing errors (i.e. errors returned by `FromStr` implementations) have their own types;
8/// for example [`ParseCapError`]).
9///
10/// [`ParseCapError`]: ./caps/struct.ParseCapError.html
11pub struct Error(i32);
12
13impl Error {
14    /// Get the last OS error that occured (i.e. the current `errno` value).
15    #[inline]
16    pub fn last() -> Self {
17        Self(unsafe { *libc::__errno_location() })
18    }
19
20    /// Construct an `Error` from an `errno` code.
21    #[inline]
22    pub fn from_code(eno: i32) -> Self {
23        Self(eno)
24    }
25
26    /// Get the `errno` code represented by this `Error` object.
27    #[inline]
28    pub fn code(&self) -> i32 {
29        self.0
30    }
31
32    fn strerror<'a>(&self, buf: &'a mut [u8]) -> &'a str {
33        static UNKNOWN_ERROR: &str = "Unknown error";
34        if self.0 < 0 {
35            return UNKNOWN_ERROR;
36        }
37
38        let ret = unsafe { libc::strerror_r(self.0, buf.as_mut_ptr() as *mut _, buf.len()) };
39        if ret == libc::EINVAL {
40            return UNKNOWN_ERROR;
41        }
42        assert_eq!(ret, 0, "strerror_r() returned {}", ret);
43
44        #[cfg(feature = "std")]
45        let msg = unsafe { std::ffi::CStr::from_ptr(buf.as_ptr() as *const _) }
46            .to_str()
47            .unwrap();
48
49        #[cfg(not(feature = "std"))]
50        let msg = {
51            let len = buf.iter().position(|&ch| ch == 0).unwrap();
52            core::str::from_utf8(&buf[..len]).unwrap()
53        };
54
55        #[cfg(target_env = "musl")]
56        if msg == "No error information" {
57            return UNKNOWN_ERROR;
58        }
59
60        msg
61    }
62}
63
64impl fmt::Display for Error {
65    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
66        let mut buf = [0u8; 1024];
67        f.write_str(self.strerror(&mut buf))?;
68        write!(f, " (code {})", self.0)
69    }
70}
71
72impl fmt::Debug for Error {
73    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
74        let mut buf = [0u8; 1024];
75        let message = self.strerror(&mut buf);
76        f.debug_struct("Error")
77            .field("code", &self.0)
78            .field("message", &message)
79            .finish()
80    }
81}
82
83#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
84#[cfg(feature = "std")]
85impl std::error::Error for Error {}
86
87#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
88#[cfg(feature = "std")]
89impl From<Error> for std::io::Error {
90    #[inline]
91    fn from(e: Error) -> Self {
92        Self::from_raw_os_error(e.0)
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn test_code() {
102        assert_eq!(Error::from_code(libc::EPERM).code(), libc::EPERM);
103        assert_eq!(Error::from_code(libc::ENOENT).code(), libc::ENOENT);
104    }
105
106    #[test]
107    fn test_last() {
108        unsafe {
109            *libc::__errno_location() = libc::EPERM;
110        }
111        assert_eq!(Error::last().code(), libc::EPERM);
112
113        unsafe {
114            *libc::__errno_location() = libc::ENOENT;
115        }
116        assert_eq!(Error::last().code(), libc::ENOENT);
117    }
118
119    #[test]
120    fn test_strerror() {
121        let mut buf = [0u8; 1024];
122
123        assert_eq!(
124            Error::from_code(libc::EISDIR).strerror(&mut buf),
125            "Is a directory"
126        );
127
128        assert_eq!(Error::from_code(-1).strerror(&mut buf), "Unknown error");
129        assert_eq!(Error::from_code(8192).strerror(&mut buf), "Unknown error");
130    }
131
132    #[cfg(feature = "std")]
133    #[test]
134    fn test_display() {
135        assert_eq!(
136            Error::from_code(libc::EISDIR).to_string(),
137            format!("Is a directory (code {})", libc::EISDIR)
138        );
139    }
140
141    #[cfg(feature = "std")]
142    #[test]
143    fn test_debug() {
144        assert_eq!(
145            format!("{:?}", Error::from_code(libc::EISDIR)),
146            format!(
147                "Error {{ code: {}, message: \"Is a directory\" }}",
148                libc::EISDIR
149            )
150        );
151    }
152
153    #[cfg(feature = "std")]
154    #[test]
155    fn test_from_error() {
156        assert_eq!(
157            std::io::Error::from(Error::from_code(libc::ENOENT)).raw_os_error(),
158            Some(libc::ENOENT)
159        );
160    }
161}