devela/sys/os/linux/
error.rs

1// devela::sys::os::linux::error
2//
3//! Defines [`LinuxError`] and [`LinuxResult`].
4//
5
6use crate::{IoError, IoErrorKind, LINUX_ERRNO as ERRNO, LINUX_EXIT as EXIT, is};
7
8#[doc = crate::TAG_RESULT!()]
9/// The return type for Linux-related functions that can fail.
10pub type LinuxResult<T> = crate::Result<T, LinuxError>;
11
12#[doc = crate::TAG_ERROR_COMPOSITE!()]
13/// Represents a Linux-related error.
14///
15/// Encapsulates errors that can occur when interacting with Linux syscalls or
16/// performing Linux-specific operations.
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum LinuxError {
19    /// An error reported by the system, containing a [`LINUX_ERRNO`][ERRNO] code.
20    ///
21    /// This wraps error codes from:
22    /// - Failed system calls (when they return -1 and set errno)
23    /// - Standard library functions that follow errno conventions
24    /// - Other OS interfaces using the same error numbering
25    Sys(isize),
26    /// No input was available (e.g., when reading from stdin).
27    NoInput,
28    /// The input was not a valid UTF-8 sequence.
29    InvalidUtf8,
30    // /// A custom error with a static string message.
31    // Other(&'static str),
32}
33macro_rules! match_linux_to_io {
34    ($self:ident) => {
35        match $self {
36            LinuxError::Sys(errno) => {
37                let kind = match errno {
38                    ERRNO::EPERM => IoErrorKind::PermissionDenied,
39                    ERRNO::ENOENT => IoErrorKind::NotFound,
40                    ERRNO::EINTR => IoErrorKind::Interrupted,
41                    ERRNO::EIO => IoErrorKind::Other,
42                    ERRNO::ENXIO => IoErrorKind::NotFound,
43                    ERRNO::EAGAIN => IoErrorKind::WouldBlock,
44                    ERRNO::ENOMEM => IoErrorKind::OutOfMemory,
45                    ERRNO::EACCES => IoErrorKind::PermissionDenied,
46                    ERRNO::EFAULT => IoErrorKind::InvalidInput,
47                    ERRNO::EBUSY => IoErrorKind::ResourceBusy,
48                    ERRNO::EEXIST => IoErrorKind::AlreadyExists,
49                    ERRNO::ENOTDIR => IoErrorKind::NotADirectory,
50                    ERRNO::EISDIR => IoErrorKind::IsADirectory,
51                    ERRNO::EINVAL => IoErrorKind::InvalidInput,
52                    ERRNO::ENOSPC => IoErrorKind::StorageFull,
53                    ERRNO::EROFS => IoErrorKind::ReadOnlyFilesystem,
54                    ERRNO::EMLINK => IoErrorKind::TooManyLinks,
55                    ERRNO::EPIPE => IoErrorKind::BrokenPipe,
56                    ERRNO::EDOM => IoErrorKind::InvalidInput,
57                    ERRNO::ERANGE => IoErrorKind::InvalidInput,
58                    ERRNO::EDEADLK => IoErrorKind::Deadlock,
59                    // WAIT:1.87 [io_error_more](https://github.com/rust-lang/rust/pull/134076)
60                    // ERRNO::ENAMETOOLONG => IoErrorKind::InvalidFilename,
61                    ERRNO::ENOLCK => IoErrorKind::ResourceBusy,
62                    ERRNO::ENOSYS => IoErrorKind::Unsupported,
63                    ERRNO::ENOTEMPTY => IoErrorKind::DirectoryNotEmpty,
64                    // WAIT:1.?? [io_error_more](https://github.com/rust-lang/rust/issues/86442)
65                    // ERRNO::ELOOP => IoErrorKind::FilesystemLoop,
66                    ERRNO::ENODEV => IoErrorKind::NotFound,
67                    ERRNO::ETIMEDOUT => IoErrorKind::TimedOut,
68                    ERRNO::EXDEV => IoErrorKind::CrossesDevices,
69                    ERRNO::ETXTBSY => IoErrorKind::ExecutableFileBusy,
70                    _ => IoErrorKind::Other,
71                };
72                IoError::new(kind, "system call failed")
73            }
74            LinuxError::NoInput => IoError::new(IoErrorKind::UnexpectedEof, "no input available"),
75            LinuxError::InvalidUtf8 => IoError::new(IoErrorKind::InvalidData, "invalid UTF-8 data"),
76        }
77    };
78}
79macro_rules! match_io_to_linux {
80    ($err:ident) => {
81        match $err.kind() {
82            IoErrorKind::PermissionDenied => LinuxError::Sys(ERRNO::EACCES),
83            IoErrorKind::NotFound => LinuxError::Sys(ERRNO::ENOENT),
84            IoErrorKind::Interrupted => LinuxError::Sys(ERRNO::EINTR),
85            IoErrorKind::WouldBlock => LinuxError::Sys(ERRNO::EAGAIN),
86            IoErrorKind::OutOfMemory => LinuxError::Sys(ERRNO::ENOMEM),
87            IoErrorKind::InvalidInput => LinuxError::Sys(ERRNO::EINVAL),
88            IoErrorKind::StorageFull => LinuxError::Sys(ERRNO::ENOSPC),
89            IoErrorKind::BrokenPipe => LinuxError::Sys(ERRNO::EPIPE),
90            IoErrorKind::UnexpectedEof => LinuxError::NoInput,
91            IoErrorKind::InvalidData => LinuxError::InvalidUtf8,
92            _ => LinuxError::Sys(ERRNO::EIO), // Default to "I/O error"
93        }
94    };
95}
96#[rustfmt::skip]
97impl LinuxError {
98    /// Converts `LinuxError` to `IoError`.
99    ///
100    /// This will only be *const* if the `std` feature is **disabled**,
101    /// because `std::io::Error::new` is not *const*.
102    #[cfg(feature = "std")]
103    pub fn to_io(self) -> IoError { match_linux_to_io!(self) }
104    /// Converts `LinuxError` to `IoError`.
105    #[cfg(not(feature = "std"))]
106    pub const fn to_io(self) -> IoError { match_linux_to_io!(self) }
107
108    /// Converts `IoError` to `LinuxError`.
109    ///
110    /// This will only be *const* if the `std` feature is **disabled**,
111    /// because `std::io::Error::kind` is not *const*.
112    #[cfg(feature = "std")]
113    pub fn from_io(err: IoError) -> LinuxError { match_io_to_linux!(err) }
114    /// Converts `IoError` to `LinuxError`.
115    #[cfg(not(feature = "std"))]
116    pub const fn from_io(err: IoError) -> LinuxError { match_io_to_linux!(err) }
117}
118impl From<LinuxError> for IoError {
119    fn from(err: LinuxError) -> Self {
120        err.to_io()
121    }
122}
123impl From<IoError> for LinuxError {
124    fn from(err: IoError) -> Self {
125        LinuxError::from_io(err)
126    }
127}
128
129impl LinuxError {
130    /// Convert the error to [`LINUX_EXIT`][EXIT] with guaranteed valid value (0..=255).
131    ///
132    /// Invalid values are converted to `INTERNAL_ERROR`.
133    pub const fn to_exit_code(self) -> i32 {
134        let code = self.to_raw_exit_code();
135        is![code >= EXIT::SUCCESS && code <= EXIT::MAX; code; EXIT::INTERNAL_ERROR]
136    }
137
138    /// Convert the error to [`LINUX_EXIT`][EXIT] without validation.
139    pub const fn to_raw_exit_code(self) -> i32 {
140        match self {
141            LinuxError::Sys(errno) => {
142                match errno {
143                    ERRNO::EPERM => EXIT::NOPERM,
144                    ERRNO::ENOENT => EXIT::NOINPUT,
145                    ERRNO::EACCES => EXIT::NOPERM,
146                    ERRNO::EINVAL => EXIT::USAGE,
147                    ERRNO::ENOSYS => EXIT::SOFTWARE,
148                    ERRNO::ENOMEM => EXIT::OSERR,
149                    ERRNO::EIO => EXIT::IOERR,
150                    ERRNO::ENFILE | ERRNO::EMFILE => EXIT::OSFILE,
151                    ERRNO::EEXIST => EXIT::CANTCREAT,
152                    ERRNO::ENOTDIR => EXIT::DATAERR,
153                    ERRNO::EISDIR => EXIT::DATAERR,
154                    ERRNO::ETIMEDOUT => EXIT::TEMPFAIL,
155                    // Add other specific mappings as needed
156                    _ => EXIT::OSERR,
157                }
158            }
159            LinuxError::NoInput => EXIT::NOINPUT,
160            LinuxError::InvalidUtf8 => EXIT::DATAERR,
161        }
162    }
163}