Added trait `SystemError`: Allows errno codes to be attached to `Error` types. Added `ErrnoFormatter` struct for a confirugrably-buffered `fmt::Display` impl to print `strerror_r()` results on an errno code.

Fortune for mempipe's current commit: Future blessing − 末吉
master
Avril 2 years ago
parent 144c85e9ab
commit 63f700ea79
Signed by: flanchan
GPG Key ID: 284488987C31F630

@ -4,4 +4,7 @@ use super::*;
pub use loli::{
DupExt,
Dup2Ext,
SystemError,
SystemErrorExt,
};

@ -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<const BUFFER_SIZE: usize>(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result;
fn get_message_with_buffer<const BUFFER_SIZE: usize>(&self) -> ErrnoFormatter<BUFFER_SIZE>;
fn message_with_buffer<const BUFFER_SIZE: usize>(&self) -> String;
}
impl<T: ?Sized> SystemErrorExt for T
where T: SystemError
{
#[inline]
fn fmt_message_with_buffer<const BUFFER_SIZE: usize>(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
errno_fmt_message::<BUFFER_SIZE>(self.code(), f)
}
#[inline]
fn get_message_with_buffer<const BUFFER_SIZE: usize>(&self) -> ErrnoFormatter<BUFFER_SIZE>
{
ErrnoFormatter::<BUFFER_SIZE>::new(self.code())
}
#[inline]
fn message_with_buffer<const BUFFER_SIZE: usize>(&self) -> String
{
ErrnoFormatter::<BUFFER_SIZE>::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<const MESSAGE_BUFFER_SIZE: usize = DEFAULT_ERRNO_MESSAGE_BUFFER_SIZE>(RawErrno);
//TODO: Document the following
impl ErrnoFormatter<DEFAULT_ASCII_ERRNO_MESSAGE_BUFFER_SIZE>
{
#[inline(always)]
pub const fn new_ascii(code: RawErrno) -> Self
{
Self(code)
}
}
impl ErrnoFormatter<DEFAULT_ERRNO_MESSAGE_BUFFER_SIZE>
{
#[inline(always)]
pub const fn new_default(code: RawErrno) -> Self
{
Self(code)
}
}
impl<const BUFF_SIZE: usize> ErrnoFormatter<BUFF_SIZE>
{
#[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<const NBUFF_SIZE: usize>(self) -> ErrnoFormatter<NBUFF_SIZE>
{
ErrnoFormatter::<NBUFF_SIZE>::new(self.0)
}
pub fn try_to_string(&self) -> Result<String, Result<UString, RawErrno>>
{
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<UString, RawErrno>
{
errno_ustring_buffer::<BUFF_SIZE>(self.0)
}
#[inline]
pub fn try_to_c_string(&self) -> Result<CString, RawErrno>
{
errno_cstring_buffer::<BUFF_SIZE>(self.0)
}
#[inline]
pub fn to_raw_buffer(&self) -> Result<[u8; BUFF_SIZE], RawErrno>
{
errno_raw_buffer(self.0)
}
}
impl<const BUFF_SIZE: usize> fmt::Display for ErrnoFormatter<BUFF_SIZE>
{
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
errno_fmt_message::<BUFF_SIZE>(self.0, f)
}
}
fn errno_fmt_message<const BUFF_SIZE: usize>(code: RawErrno, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
if let Ok(buffer) = errno_raw_buffer::<BUFF_SIZE>(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, "<!unknown: empty message for errno {}>", code)
}
_failed_path(code, f)
}
} else {
#[cold]
#[inline(never)]
fn _failed_path(code: RawErrno, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "<!unknown: message extraction failed for errno {}>", code)
}
_failed_path(code, f)
}
}
fn errno_raw_buffer<const BUFF_SIZE: usize>(errno: RawErrno) -> Result<[u8; BUFF_SIZE], RawErrno>
{
use std::mem::MaybeUninit;
@ -121,11 +309,11 @@ fn errno_lossy_string_buffer<const BUFF_SIZE: usize>(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"<empty error message buffer>"));
write!(f, "{}: {}", self.0, string)
} else {
write!(f, "{} <failed to extract code message at 1024 bytes buffer size>", 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::<UStr>::into_raw(us) as *mut [u8])
})
}
pub fn into_string(self) -> Result<String, Self>
{
let vec = Vec::from(self.0);
@ -421,6 +606,14 @@ impl UString
Self(e.into_bytes().into())
})
}
pub fn try_into_string(self) -> Result<String, std::string::FromUtf8Error>
{
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<UStr> 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<UStr> for UString
{
#[inline(always)]
@ -527,6 +721,91 @@ impl DerefMut for UString
}
}
// UStr/ing <--?> CStr/ing
impl From<CString> 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<UString> for CString
{
type Error = std::ffi::FromVecWithNulError;
#[inline]
fn try_from(from: UString) -> Result<Self, Self::Error>
{
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<Self, Self::Error>
{
from.as_c_str()
}
}
// UStr/ing <--?> str/ing
impl From<String> 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<UString> for String
{
type Error = std::string::FromUtf8Error;
#[inline]
fn try_from(from: UString) -> Result<Self, Self::Error>
{
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<Self, Self::Error>
{
from.to_str()
}
}
#[cfg(test)]
mod tests
{

Loading…
Cancel
Save