diff --git a/src/ext.rs b/src/ext.rs index 36be6a1..352814c 100644 --- a/src/ext.rs +++ b/src/ext.rs @@ -4,4 +4,7 @@ use super::*; pub use loli::{ DupExt, Dup2Ext, + + SystemError, + SystemErrorExt, }; diff --git a/src/loli.rs b/src/loli.rs index ac13fe6..5881e13 100644 --- a/src/loli.rs +++ b/src/loli.rs @@ -71,9 +71,197 @@ mod raw { // Errno error handling +//TODO: Move all errno related stuff into a submodule inside `srd/loli/` to un-clutter this file; and add a `RawErrno`-like struct for errors that are *just* an errno value (that implements `SystemError`) + /// A raw `errno` value pub type RawErrno = raw::c_int; +/// Default max buffer size for formatting `errno` message codes. +/// +/// This is the size used by glibc's `perror()`. It should cover all internationalised error messages. +pub const DEFAULT_ERRNO_MESSAGE_BUFFER_SIZE: usize = 1024; + +/// Default buffer size short `errno` message code strings. +/// +/// # Internationalisation +/// This size covers all current English `errno` messages. However, if the program is ran with a non-ASCII locale, the size may exceed this by far. See `DEFAULT_ERRNO_MESSAGE_BUFFER_SIZE` for those cases. +pub const DEFAULT_ASCII_ERRNO_MESSAGE_BUFFER_SIZE: usize = 64; + +/// An error type which also contains an `errno` code. +pub trait SystemError: std::error::Error +{ + fn code(&self) -> RawErrno; + + #[inline(always)] + fn fmt_message(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + errno_fmt_message::<1024>(self.code(), f) + } + + #[inline] + fn get_message(&self) -> ErrnoFormatter + { + ErrnoFormatter::new_default(self.code()) + } + + #[inline] + fn message(&self) -> String + { + self.get_message().to_string() + } +} + +/// Extension trait for `SystemError` types. +/// +/// # Specific buffered sizes +/// Provides user-configurable buffer sized methods for message formatting +pub trait SystemErrorExt: SystemError +{ + fn fmt_message_with_buffer(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result; + fn get_message_with_buffer(&self) -> ErrnoFormatter; + fn message_with_buffer(&self) -> String; +} + +impl SystemErrorExt for T +where T: SystemError +{ + #[inline] + fn fmt_message_with_buffer(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + errno_fmt_message::(self.code(), f) + } + + #[inline] + fn get_message_with_buffer(&self) -> ErrnoFormatter + { + ErrnoFormatter::::new(self.code()) + } + #[inline] + fn message_with_buffer(&self) -> String + { + ErrnoFormatter::::new(self.code()).to_string() + } +} + +/// String formatter for an `errno` value. +/// +/// # `MESSAGE_BUFFER_SIZE` +/// This structure contains only the `errno` code. The `MESSAGE_BUFFER_SIZE` const generic is used as a max size for formatting the error messages. +/// It **must** be above 0. +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)] +#[repr(transparent)] +pub struct ErrnoFormatter(RawErrno); + +//TODO: Document the following + +impl ErrnoFormatter +{ + #[inline(always)] + pub const fn new_ascii(code: RawErrno) -> Self + { + Self(code) + } +} + +impl ErrnoFormatter +{ + #[inline(always)] + pub const fn new_default(code: RawErrno) -> Self + { + Self(code) + } +} + +impl ErrnoFormatter +{ + #[inline(always)] + pub const fn new(code: RawErrno) -> Self + { + Self(code) + } + + #[inline] + pub const fn raw(self) -> RawErrno + { + self.0 + } + + #[inline] + pub const fn buffer_size(self) -> usize + { + BUFF_SIZE + } + + #[inline] + pub const fn with_buffer_size(self) -> ErrnoFormatter + { + ErrnoFormatter::::new(self.0) + } + + pub fn try_to_string(&self) -> Result> + { + match self.try_to_raw_string() { + Ok(string) => string.into_string().map_err(|us| Ok(us)), + Err(en) => Err(Err(en)), + } + } + + #[inline] + pub fn try_to_raw_string(&self) -> Result + { + errno_ustring_buffer::(self.0) + } + + #[inline] + pub fn try_to_c_string(&self) -> Result + { + errno_cstring_buffer::(self.0) + } + + #[inline] + pub fn to_raw_buffer(&self) -> Result<[u8; BUFF_SIZE], RawErrno> + { + errno_raw_buffer(self.0) + } +} + +impl fmt::Display for ErrnoFormatter +{ + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + errno_fmt_message::(self.0, f) + } +} + + +fn errno_fmt_message(code: RawErrno, f: &mut fmt::Formatter<'_>) -> fmt::Result +{ + if let Ok(buffer) = errno_raw_buffer::(code) + { + if let Some(first) = buffer.split(|&b| b == 0).next() + { + f.write_str(String::from_utf8_lossy(first).as_ref()) + } else { + #[cold] + #[inline(never)] //XXX: Should we noinline this cold path? I think we should. + fn _failed_path(code: RawErrno, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "", code) + } + _failed_path(code, f) + } + } else { + #[cold] + #[inline(never)] + fn _failed_path(code: RawErrno, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "", code) + } + _failed_path(code, f) + } +} + fn errno_raw_buffer(errno: RawErrno) -> Result<[u8; BUFF_SIZE], RawErrno> { use std::mem::MaybeUninit; @@ -121,11 +309,11 @@ fn errno_lossy_string_buffer(errno: RawErrno) -> Result< #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct DupError(RawErrno); -impl DupError +impl SystemError for DupError { /// The `errno` code for this error. #[inline] - pub fn code(&self) -> RawErrno + fn code(&self) -> RawErrno { self.0 } @@ -136,16 +324,8 @@ impl fmt::Display for DupError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "dup() failed with ")?; - - if let Ok(buffer) = errno_raw_buffer::<1024>(self.0) { - let string = String::from_utf8_lossy(buffer.split(|&b| b == 0) - .next() - .unwrap_or(b"")); - write!(f, "{}: {}", self.0, string) - } else { - write!(f, "{} ", self.0) - } + write!(f, "dup() failed with {}: ", self.0)?; + self.fmt_message(f) } } @@ -199,6 +379,8 @@ fn errno_if_nz(rv: raw::c_int) -> Result<(), RawErrno> // Wrapper traits +//TODO: Move wrapper traits into a seperate module in src/loli/ to unclutter this file + /// Extension methods for `dup()`ing file descriptors pub trait DupExt: Sized { @@ -233,7 +415,10 @@ where T: FromRawFd + AsRawFd } } -//TODO: Document the following, and make it work like CStr/CString +//TODO: Document the following, and make sure it works like CStr/CString (minus the nul-termination bs) + +//TODO: Move UStr/UString and related stuffs into a seperate module in src/loli/ to unclutter this file + /// A reference to an unmanaged string that may contain non-utf8 characters. /// @@ -413,7 +598,7 @@ impl UString Box::<[u8]>::from_raw(Box::::into_raw(us) as *mut [u8]) }) } - + pub fn into_string(self) -> Result { let vec = Vec::from(self.0); @@ -421,6 +606,14 @@ impl UString Self(e.into_bytes().into()) }) } + + pub fn try_into_string(self) -> Result + { + let vec = Vec::from(self.0); + String::from_utf8(vec)/*.map_err(|e| { + Self(e.into_bytes().into()) + })*/ + } /// Allocate a new owned `CString` from this nul-terminated unmanaged string. /// @@ -486,8 +679,9 @@ fn deref(&self) -> &Self::Target { &self.0[..] } } + XXX: Is DerefMut needed, too? -XXX: What about AsMut? +XXX: What about AsMut? I think those two are unsafe, especially if the &mut UStr comes from an &mut str. There are unsafe methods for this anyway. */ impl AsRef for UString @@ -510,7 +704,7 @@ impl Deref for UString } } -//XXX: Should we allow mutation of the `ustr` here? I think so, because we don't allow mutation of the underlying [u8] +//Should we allow mutation of the `ustr` here? I think so, because we don't allow mutation of the underlying [u8] impl AsMut for UString { #[inline(always)] @@ -527,6 +721,91 @@ impl DerefMut for UString } } +// UStr/ing <--?> CStr/ing + +impl From for UString +{ + #[inline] + fn from(from: CString) -> Self + { + Self::from_c_string(from) + } +} + +impl<'a> From<&'a CStr> for &'a UStr +{ + #[inline] + fn from(from: &'a CStr) -> Self + { + UStr::from_c_str(from) + } +} + +impl TryFrom for CString +{ + type Error = std::ffi::FromVecWithNulError; + + #[inline] + fn try_from(from: UString) -> Result + { + from.into_c_string() + } +} + +impl<'a> TryFrom<&'a UStr> for &'a CStr +{ + type Error = std::ffi::FromBytesWithNulError; + + #[inline] + fn try_from(from: &'a UStr) -> Result + { + from.as_c_str() + } +} + +// UStr/ing <--?> str/ing + +impl From for UString +{ + #[inline] + fn from(from: String) -> Self + { + Self::new(from.into_bytes()) + } +} + +impl<'a> From<&'a str> for &'a UStr +{ + #[inline] + fn from(from: &'a str) -> Self + { + UStr::new(from.as_bytes()) + } +} +// No From<&'_ mut str> for &'_ mut UStr: The bytes belonging to the &'_ str may be modified in safe code if this were allowed. + +impl TryFrom for String +{ + type Error = std::string::FromUtf8Error; + + #[inline] + fn try_from(from: UString) -> Result + { + from.try_into_string() + } +} + +impl<'a> TryFrom<&'a UStr> for &'a str +{ + type Error = std::str::Utf8Error; + + #[inline] + fn try_from(from: &'a UStr) -> Result + { + from.to_str() + } +} + #[cfg(test)] mod tests {