#![forbid(unsafe_code)]
use std::{
array::TryFromSliceError,
env::VarError,
error::Error,
fmt, io,
net::AddrParseError,
num::{ParseIntError, TryFromIntError},
str::Utf8Error,
thread::JoinHandle,
};
use btoi::ParseIntegerError;
#[cfg(feature = "oci")]
use libcgroups::common::AnyManagerError;
#[cfg(feature = "oci")]
use libcgroups::common::CreateCgroupSetupError;
#[cfg(feature = "oci")]
use libcontainer::error::LibcontainerError;
#[cfg(feature = "oci")]
use libcontainer::signal::SignalError;
#[cfg(feature = "oci")]
use libcontainer::utils::PathBufExtError;
use libseccomp::error::SeccompError;
use nix::errno::Errno;
use procfs_core::ProcError;
use shellexpand::LookupError;
#[cfg(feature = "oci")]
use tracing::subscriber::SetGlobalDefaultError;
use crate::{caps::errors::CapsError, elf::ElfError, landlock::RulesetError, sandbox::Capability};
pub type SydResult<T> = std::result::Result<T, SydError>;
pub type SydJoinHandle<T> = JoinHandle<SydResult<T>>;
#[macro_export]
macro_rules! lasterrno {
() => {
SydError::Nix(nix::errno::Errno::last())
};
}
pub enum SydError {
Addr(AddrParseError),
Args(lexopt::Error),
Caps(CapsError),
Elf(ElfError),
Env(LookupError<VarError>),
Nix(Errno),
Json(serde_json::Error),
ParseInt(ParseIntError),
ParseInteger(ParseIntegerError),
TryInt(TryFromIntError),
TrySlice(TryFromSliceError),
ParseSize(parse_size::Error),
ParseShell(shell_words::ParseError),
Proc(ProcError),
Scmp(SeccompError),
Utf8(Utf8Error),
Var(VarError),
#[cfg(feature = "oci")]
CgSetup(CreateCgroupSetupError),
#[cfg(feature = "oci")]
CgMisc(AnyManagerError),
#[cfg(feature = "oci")]
Cont(LibcontainerError),
#[cfg(feature = "oci")]
Pext(PathBufExtError),
#[cfg(feature = "oci")]
SetTracing(SetGlobalDefaultError),
#[cfg(feature = "oci")]
Signal(SignalError<String>),
#[cfg(feature = "oci")]
Spec(oci_spec::OciSpecError),
}
impl SydError {
pub fn errno(&self) -> Option<Errno> {
match self {
Self::Nix(errno) => Some(*errno),
Self::Proc(error) => proc_error_to_errno(error),
Self::Scmp(error) => scmp2no(error),
Self::ParseInt(_)
| Self::ParseInteger(_)
| Self::ParseSize(_)
| Self::ParseShell(_)
| Self::Var(_) => Some(Errno::EINVAL),
_ => None,
}
}
}
impl fmt::Debug for SydError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Addr(error) => write!(f, "AddrParseError: {error:?}"),
Self::Args(error) => write!(f, "ArgsParseError: {error:?}"),
Self::Caps(error) => write!(f, "CapsError: {error:?}"),
Self::Elf(error) => write!(f, "ElfError: {error:?}"),
Self::Env(error) => write!(f, "LookupError<VarError>: {error:?}"),
Self::Nix(errno) => write!(f, "LinuxError: {errno:?}"),
Self::Json(error) => write!(f, "JsonError: {error:?}"),
Self::ParseInt(error) => write!(f, "ParseIntError: {error:?}"),
Self::ParseInteger(error) => write!(f, "ParseIntegerError: {error:?}"),
Self::Scmp(error) => write!(f, "SeccompError: {error:?}"),
Self::TryInt(error) => write!(f, "TryFromIntError: {error:?}"),
Self::TrySlice(error) => write!(f, "TryFromSliceError: {error:?}"),
Self::ParseSize(error) => write!(f, "ParseSizeError: {error:?}"),
Self::ParseShell(error) => write!(f, "ParseShellError: {error:?}"),
Self::Proc(error) => write!(f, "ProcError: {error:?}"),
Self::Utf8(error) => write!(f, "Utf8Error: {error:?}"),
Self::Var(error) => write!(f, "VarError: {error:?}"),
#[cfg(feature = "oci")]
Self::CgSetup(error) => write!(f, "CgroupSetupError: {error:?}"),
#[cfg(feature = "oci")]
Self::CgMisc(error) => write!(f, "AnyManagerError: {error:?}"),
#[cfg(feature = "oci")]
Self::Cont(error) => write!(f, "ContainerError: {error:?}"),
#[cfg(feature = "oci")]
Self::Pext(error) => write!(f, "PathBufExtError: {error:?}"),
#[cfg(feature = "oci")]
Self::SetTracing(error) => write!(f, "SetGlobalDefaultError: {error:?}"),
#[cfg(feature = "oci")]
Self::Signal(error) => write!(f, "SignalError: {error:?}"),
#[cfg(feature = "oci")]
Self::Spec(error) => write!(f, "OciSpecError: {error:?}"),
}
}
}
impl fmt::Display for SydError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Addr(error) => write!(f, "AddrParseError: {error}"),
Self::Args(error) => write!(f, "ArgsParseError: {error}"),
Self::Caps(error) => write!(f, "CapsError: {error}"),
Self::Elf(error) => write!(f, "ElfError: {error}"),
Self::Env(error) => write!(f, "LookupError<VarError>: {error}"),
Self::Nix(errno) => write!(f, "LinuxError: {errno}"),
Self::Json(error) => write!(f, "JsonError: {error}"),
Self::ParseInt(error) => write!(f, "ParseIntError: {error}"),
Self::ParseInteger(error) => write!(f, "ParseIntegerError: {error}"),
Self::Scmp(error) => write!(f, "SeccompError: {error}"),
Self::TryInt(error) => write!(f, "TryFromIntError: {error}"),
Self::TrySlice(error) => write!(f, "TryFromSliceError: {error}"),
Self::ParseSize(error) => write!(f, "ParseSizeError: {error}"),
Self::ParseShell(error) => write!(f, "ParseShellError: {error}"),
Self::Proc(error) => write!(f, "ProcError: {error}"),
Self::Utf8(error) => write!(f, "Utf8Error: {error}"),
Self::Var(error) => write!(f, "VarError: {error}"),
#[cfg(feature = "oci")]
Self::CgSetup(error) => write!(f, "CgroupSetupError: {error}"),
#[cfg(feature = "oci")]
Self::CgMisc(error) => write!(f, "AnyManagerError: {error}"),
#[cfg(feature = "oci")]
Self::Cont(error) => write!(f, "ContainerError: {error}"),
#[cfg(feature = "oci")]
Self::Pext(error) => write!(f, "PathBufExtError: {error}"),
#[cfg(feature = "oci")]
Self::SetTracing(error) => write!(f, "SetGlobalDefaultError: {error}"),
#[cfg(feature = "oci")]
Self::Signal(error) => write!(f, "SignalError: {error}"),
#[cfg(feature = "oci")]
Self::Spec(error) => write!(f, "OciSpecError: {error}"),
}
}
}
impl std::error::Error for SydError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Addr(error) => Some(error),
Self::Args(error) => Some(error),
Self::Nix(errno) => Some(errno),
Self::ParseInt(error) => Some(error),
Self::ParseInteger(error) => Some(error),
Self::TryInt(error) => Some(error),
Self::TrySlice(error) => Some(error),
Self::ParseSize(error) => Some(error),
Self::ParseShell(error) => Some(error),
Self::Proc(error) => Some(error),
Self::Utf8(error) => Some(error),
Self::Var(error) => Some(error),
#[cfg(feature = "oci")]
Self::CgSetup(error) => Some(error),
#[cfg(feature = "oci")]
Self::CgMisc(error) => Some(error),
#[cfg(feature = "oci")]
Self::Cont(error) => Some(error),
#[cfg(feature = "oci")]
Self::Pext(error) => Some(error),
#[cfg(feature = "oci")]
Self::SetTracing(error) => Some(error),
#[cfg(feature = "oci")]
Self::Signal(error) => Some(error),
#[cfg(feature = "oci")]
Self::Spec(error) => Some(error),
_ => None,
}
}
}
impl From<io::Error> for SydError {
fn from(err: io::Error) -> Self {
Self::Nix(err2no(&err))
}
}
impl From<SydError> for io::Error {
fn from(err: SydError) -> Self {
match err.errno() {
Some(errno) => Self::from_raw_os_error(errno as i32),
None => Self::other(err),
}
}
}
impl From<AddrParseError> for SydError {
fn from(err: AddrParseError) -> Self {
Self::Addr(err)
}
}
impl From<Utf8Error> for SydError {
fn from(err: Utf8Error) -> SydError {
Self::Utf8(err)
}
}
impl From<ProcError> for SydError {
fn from(err: ProcError) -> SydError {
Self::Proc(err)
}
}
impl From<lexopt::Error> for SydError {
fn from(err: lexopt::Error) -> SydError {
Self::Args(err)
}
}
impl From<CapsError> for SydError {
fn from(err: CapsError) -> Self {
Self::Caps(err)
}
}
impl From<ElfError> for SydError {
fn from(err: ElfError) -> Self {
Self::Elf(err)
}
}
impl From<LookupError<VarError>> for SydError {
fn from(err: LookupError<VarError>) -> Self {
Self::Env(err)
}
}
impl From<VarError> for SydError {
fn from(err: VarError) -> Self {
Self::Var(err)
}
}
impl From<Errno> for SydError {
fn from(err: Errno) -> Self {
Self::Nix(err)
}
}
impl From<serde_json::Error> for SydError {
fn from(err: serde_json::Error) -> Self {
Self::Json(err)
}
}
#[cfg(feature = "oci")]
impl From<AnyManagerError> for SydError {
fn from(err: AnyManagerError) -> Self {
Self::CgMisc(err)
}
}
#[cfg(feature = "oci")]
impl From<CreateCgroupSetupError> for SydError {
fn from(err: CreateCgroupSetupError) -> Self {
Self::CgSetup(err)
}
}
#[cfg(feature = "oci")]
impl From<LibcontainerError> for SydError {
fn from(err: LibcontainerError) -> Self {
Self::Cont(err)
}
}
#[cfg(feature = "oci")]
impl From<PathBufExtError> for SydError {
fn from(err: PathBufExtError) -> Self {
Self::Pext(err)
}
}
#[cfg(feature = "oci")]
impl From<SetGlobalDefaultError> for SydError {
fn from(err: SetGlobalDefaultError) -> Self {
Self::SetTracing(err)
}
}
#[cfg(feature = "oci")]
impl From<SignalError<String>> for SydError {
fn from(err: SignalError<String>) -> Self {
Self::Signal(err)
}
}
#[cfg(feature = "oci")]
impl From<oci_spec::OciSpecError> for SydError {
fn from(err: oci_spec::OciSpecError) -> Self {
Self::Spec(err)
}
}
impl From<ParseIntError> for SydError {
fn from(err: ParseIntError) -> Self {
Self::ParseInt(err)
}
}
impl From<ParseIntegerError> for SydError {
fn from(err: ParseIntegerError) -> Self {
Self::ParseInteger(err)
}
}
impl From<TryFromIntError> for SydError {
fn from(err: TryFromIntError) -> Self {
Self::TryInt(err)
}
}
impl From<TryFromSliceError> for SydError {
fn from(err: TryFromSliceError) -> Self {
Self::TrySlice(err)
}
}
impl From<parse_size::Error> for SydError {
fn from(err: parse_size::Error) -> Self {
Self::ParseSize(err)
}
}
impl From<shell_words::ParseError> for SydError {
fn from(err: shell_words::ParseError) -> Self {
Self::ParseShell(err)
}
}
impl From<SeccompError> for SydError {
fn from(err: SeccompError) -> Self {
Self::Scmp(err)
}
}
pub fn err2no(err: &std::io::Error) -> Errno {
err.raw_os_error()
.map(Errno::from_raw)
.unwrap_or(Errno::ENOSYS)
}
#[cfg(feature = "oci")]
pub fn err2io(errno: Errno) -> LibcontainerError {
LibcontainerError::OtherIO(io::Error::from_raw_os_error(errno as i32))
}
pub fn err2set(err: &RulesetError) -> Errno {
err.source()
.and_then(|s| s.downcast_ref::<std::io::Error>())
.map(err2no)
.unwrap_or(Errno::EINVAL)
}
pub fn scmp2no(err: &SeccompError) -> Option<Errno> {
err.sysrawrc().map(|errno| errno.abs()).map(Errno::from_raw)
}
pub fn proc_error_to_errno(error: &ProcError) -> Option<Errno> {
match error {
ProcError::PermissionDenied(_) => Some(Errno::EACCES),
ProcError::NotFound(_) => Some(Errno::ESRCH),
ProcError::Io(error, _) => Some(err2no(error)),
ProcError::Other(_) => None,
ProcError::Incomplete(_) => None,
ProcError::InternalError(_) => None,
}
}
pub fn cap2no(cap: Capability) -> Errno {
if cap.intersects(Capability::CAP_WALK | Capability::CAP_STAT) {
Errno::ENOENT
} else {
Errno::EACCES
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_err2no_1() {
let err = io::Error::from_raw_os_error(libc::ENOENT);
assert_eq!(err2no(&err), Errno::ENOENT);
}
#[test]
fn test_err2no_2() {
let err = io::Error::from_raw_os_error(libc::EPERM);
assert_eq!(err2no(&err), Errno::EPERM);
}
#[test]
fn test_err2no_3() {
let err = io::Error::new(io::ErrorKind::Other, "custom error");
assert_eq!(err2no(&err), Errno::ENOSYS);
}
#[test]
fn test_cap2no_1() {
assert_eq!(cap2no(Capability::CAP_WALK), Errno::ENOENT);
}
#[test]
fn test_cap2no_2() {
assert_eq!(cap2no(Capability::CAP_STAT), Errno::ENOENT);
}
#[test]
fn test_cap2no_3() {
assert_eq!(
cap2no(Capability::CAP_WALK | Capability::CAP_STAT),
Errno::ENOENT
);
}
#[test]
fn test_cap2no_4() {
assert_eq!(cap2no(Capability::CAP_READ), Errno::EACCES);
}
#[test]
fn test_errno_1() {
let err = SydError::Nix(Errno::EAGAIN);
assert_eq!(err.errno(), Some(Errno::EAGAIN));
}
#[test]
fn test_errno_2() {
let parse_err: ParseIntError = "not_a_number".parse::<i32>().unwrap_err();
let err = SydError::ParseInt(parse_err);
assert_eq!(err.errno(), Some(Errno::EINVAL));
}
#[test]
fn test_errno_3() {
let err = SydError::Var(VarError::NotPresent);
assert_eq!(err.errno(), Some(Errno::EINVAL));
}
#[test]
fn test_errno_4() {
let addr_err = "not_an_addr".parse::<std::net::IpAddr>().unwrap_err();
let err = SydError::Addr(addr_err);
assert_eq!(err.errno(), None);
}
#[test]
fn test_debug_1() {
let err = SydError::Nix(Errno::ENOENT);
let debug = format!("{err:?}");
assert!(debug.contains("LinuxError"));
}
#[test]
fn test_display_1() {
let err = SydError::Nix(Errno::ENOENT);
let display = format!("{err}");
assert!(display.contains("LinuxError"));
}
#[test]
fn test_display_2() {
let parse_err: ParseIntError = "abc".parse::<i32>().unwrap_err();
let err = SydError::ParseInt(parse_err);
let display = format!("{err}");
assert!(display.contains("ParseIntError"));
}
#[test]
fn test_display_3() {
let err = SydError::Var(VarError::NotPresent);
let display = format!("{err}");
assert!(display.contains("VarError"));
}
#[test]
fn test_display_4() {
let bytes = vec![0, 159, 146, 150];
let utf8_err = std::str::from_utf8(&bytes).unwrap_err();
let err = SydError::Utf8(utf8_err);
let display = format!("{err}");
assert!(display.contains("Utf8Error"));
}
#[test]
fn test_source_1() {
let err = SydError::Nix(Errno::ENOENT);
assert!(err.source().is_some());
}
#[test]
fn test_source_2() {
let json_err = serde_json::from_str::<bool>("not_json").unwrap_err();
let err = SydError::Json(json_err);
assert!(err.source().is_none());
}
#[test]
fn test_from_1() {
let io_err = io::Error::from_raw_os_error(libc::EACCES);
let syd_err: SydError = io_err.into();
assert_eq!(syd_err.errno(), Some(Errno::EACCES));
}
#[test]
fn test_from_2() {
let syd_err: SydError = Errno::EPERM.into();
assert_eq!(syd_err.errno(), Some(Errno::EPERM));
}
#[test]
fn test_from_3() {
let syd_err: SydError = VarError::NotPresent.into();
assert_eq!(syd_err.errno(), Some(Errno::EINVAL));
}
#[test]
fn test_from_4() {
let syd_err = SydError::Nix(Errno::EACCES);
let io_err: io::Error = syd_err.into();
assert_eq!(io_err.raw_os_error(), Some(libc::EACCES));
}
#[test]
fn test_from_5() {
let addr_err = "bad".parse::<std::net::IpAddr>().unwrap_err();
let syd_err = SydError::Addr(addr_err);
let io_err: io::Error = syd_err.into();
assert_eq!(io_err.kind(), io::ErrorKind::Other);
}
#[test]
fn test_proc_error_to_errno_1() {
let err = ProcError::PermissionDenied(None);
assert_eq!(proc_error_to_errno(&err), Some(Errno::EACCES));
}
#[test]
fn test_proc_error_to_errno_2() {
let err = ProcError::NotFound(None);
assert_eq!(proc_error_to_errno(&err), Some(Errno::ESRCH));
}
#[test]
fn test_proc_error_to_errno_3() {
let err = ProcError::Other("something".into());
assert_eq!(proc_error_to_errno(&err), None);
}
#[test]
fn test_proc_error_to_errno_4() {
let err = ProcError::Incomplete(None);
assert_eq!(proc_error_to_errno(&err), None);
}
#[test]
fn test_proc_error_to_errno_5() {
let err = ProcError::InternalError(procfs_core::InternalError {
msg: "test".into(),
file: file!(),
line: line!(),
});
assert_eq!(proc_error_to_errno(&err), None);
}
#[test]
fn test_proc_error_to_errno_6() {
let err = ProcError::Io(io::Error::from_raw_os_error(libc::ENOENT), None);
assert_eq!(proc_error_to_errno(&err), Some(Errno::ENOENT));
}
}