#![forbid(unsafe_code)]
use crate::{resolvers::opath::SymlinkStackError, syscalls::Error as SyscallError};
use std::{borrow::Cow, io::Error as IOError};
#[derive(thiserror::Error, Debug)]
#[error(transparent)]
pub struct Error(#[from] Box<ErrorImpl>);
impl<E: Into<ErrorImpl>> From<E> for Error {
#[doc(hidden)]
fn from(err: E) -> Self {
Self(Box::new(err.into()))
}
}
impl Error {
pub fn kind(&self) -> ErrorKind {
self.0.kind()
}
pub fn can_retry(&self) -> bool {
self.0.kind().can_retry()
}
pub(crate) fn is_safety_violation(&self) -> bool {
self.0.is_safety_violation()
}
#[cfg(test)]
pub(crate) fn into_inner(self) -> ErrorImpl {
*self.0
}
}
#[derive(thiserror::Error, Debug)]
pub(crate) enum ErrorImpl {
#[allow(dead_code)]
#[error("feature {feature} is not implemented")]
NotImplemented { feature: Cow<'static, str> },
#[error("feature {feature} not supported by the system")]
NotSupported { feature: Cow<'static, str> },
#[error("invalid {name} argument: {description}")]
InvalidArgument {
name: Cow<'static, str>,
description: Cow<'static, str>,
},
#[cfg(feature = "capi")]
#[error("invalid {name} structure: extra non-zero trailing bytes found")]
UnsupportedStructureData { name: Cow<'static, str> },
#[error("violation of safety requirement: {description}")]
SafetyViolation { description: Cow<'static, str> },
#[error("broken symlink stack during iteration: {description}")]
BadSymlinkStackError {
description: Cow<'static, str>,
source: SymlinkStackError,
},
#[error("{operation} failed")]
OsError {
operation: Cow<'static, str>,
source: IOError,
},
#[error("{operation} failed")]
RawOsError {
operation: Cow<'static, str>,
source: SyscallError,
},
#[cfg(feature = "capi")]
#[error("error while parsing c struct: {description}")]
BytemuckPodCastError {
description: Cow<'static, str>,
source: bytemuck::PodCastError,
},
#[error("integer parsing failed")]
ParseIntError(#[from] std::num::ParseIntError),
#[error("impossible error: infallible error failed")]
InfallibleError(#[from] std::convert::Infallible),
#[error("{context}")]
Wrapped {
context: Cow<'static, str>,
source: Box<ErrorImpl>,
},
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[non_exhaustive]
pub enum ErrorKind {
NotImplemented,
NotSupported,
InvalidArgument,
#[cfg(feature = "capi")]
UnsupportedStructureData,
SafetyViolation,
InternalError,
OsError(Option<i32>),
}
impl ErrorImpl {
pub(crate) fn kind(&self) -> ErrorKind {
match self {
Self::NotImplemented { .. } => ErrorKind::NotImplemented,
Self::NotSupported { .. } => ErrorKind::NotSupported,
Self::InvalidArgument { .. } => ErrorKind::InvalidArgument,
#[cfg(feature = "capi")]
Self::UnsupportedStructureData { .. } => ErrorKind::UnsupportedStructureData,
Self::SafetyViolation { .. } => ErrorKind::SafetyViolation,
Self::OsError { source, .. } => ErrorKind::OsError(source.raw_os_error()),
Self::RawOsError { source, .. } => {
ErrorKind::OsError(source.root_cause().raw_os_error())
}
Self::BadSymlinkStackError { .. }
| Self::ParseIntError(_)
| Self::InfallibleError(_) => ErrorKind::InternalError,
#[cfg(feature = "capi")]
Self::BytemuckPodCastError { .. } => ErrorKind::InternalError,
Self::Wrapped { source, .. } => source.kind(),
}
}
pub(crate) fn is_safety_violation(&self) -> bool {
self.kind().is_safety_violation()
}
}
impl ErrorKind {
pub(crate) fn errno(&self) -> Option<i32> {
match self {
ErrorKind::NotImplemented | ErrorKind::NotSupported => Some(libc::ENOSYS),
ErrorKind::InvalidArgument => Some(libc::EINVAL),
#[cfg(feature = "capi")]
ErrorKind::UnsupportedStructureData => Some(libc::E2BIG),
ErrorKind::SafetyViolation => Some(libc::EXDEV),
ErrorKind::OsError(errno) => *errno,
_ => None,
}
}
pub fn can_retry(&self) -> bool {
matches!(self.errno(), Some(libc::EAGAIN) | Some(libc::EINTR))
}
pub(crate) fn is_safety_violation(&self) -> bool {
self.errno() == Self::SafetyViolation.errno()
}
}
pub(crate) trait ErrorExt: Sized {
fn wrap<S: Into<String>>(self, context: S) -> Self {
self.with_wrap(|| context.into())
}
fn with_wrap<F>(self, context_fn: F) -> Self
where
F: FnOnce() -> String;
}
impl ErrorExt for ErrorImpl {
fn with_wrap<F>(self, context_fn: F) -> Self
where
F: FnOnce() -> String,
{
Self::Wrapped {
context: context_fn().into(),
source: self.into(),
}
}
}
impl ErrorExt for Error {
fn with_wrap<F>(self, context_fn: F) -> Self
where
F: FnOnce() -> String,
{
self.0.with_wrap(context_fn).into()
}
}
impl<T, E: ErrorExt> ErrorExt for Result<T, E> {
fn with_wrap<F>(self, context_fn: F) -> Self
where
F: FnOnce() -> String,
{
self.map_err(|err| err.with_wrap(context_fn))
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn error_kind_errno() {
assert_eq!(
ErrorKind::InvalidArgument.errno(),
Some(libc::EINVAL),
"ErrorKind::InvalidArgument is equivalent to EINVAL"
);
assert_eq!(
ErrorKind::NotImplemented.errno(),
Some(libc::ENOSYS),
"ErrorKind::NotImplemented is equivalent to ENOSYS"
);
assert_eq!(
ErrorKind::SafetyViolation.errno(),
Some(libc::EXDEV),
"ErrorKind::SafetyViolation is equivalent to EXDEV"
);
assert_eq!(
ErrorKind::OsError(Some(libc::ENOANO)).errno(),
Some(libc::ENOANO),
"ErrorKind::OsError(...)::errno() returns the inner errno"
);
}
}