//
// Syd: rock-solid unikernel
// src/err.rs: Error types and error handling code
//
// Copyright (c) 2024 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0-or-later

use std::{
    array::TryFromSliceError,
    env::VarError,
    fmt, io,
    net::AddrParseError,
    num::{ParseIntError, TryFromIntError},
    str::Utf8Error,
};

#[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::{SeccompErrno, SeccompError};
use log::SetLoggerError;
use nix::errno::Errno;
use procfs::ProcError;
use shellexpand::LookupError;
#[cfg(feature = "oci")]
use tracing::subscriber::SetGlobalDefaultError;

use crate::{caps::errors::CapsError, elf::ElfError};

/// Convenience type to use for functions returning a SydError.
pub type SydResult<T> = std::result::Result<T, SydError>;

/// A macro to create a SydError from the last errno.
#[macro_export]
macro_rules! lasterrno {
    () => {
        SydError::Nix(nix::errno::Errno::last())
    };
}

/// Enum representing different types of errors
pub enum SydError {
    /// This error represents a network address parse error.
    Addr(AddrParseError),
    /// This error type represents a getargs error.
    ArgsByte(getargs::Error<&'static [u8]>),
    /// This error type represents a getargs error.
    ArgsUtf8(getargs::Error<&'static str>),
    /// This error type represents a capability error.
    Caps(CapsError),
    /// This error type represents an ELF parse error.
    Elf(ElfError),
    /// This error type represents an error in environment variable lookup in configuration.
    Env(LookupError<VarError>),
    /// This error type represents an `Errno`.
    Nix(Errno),
    /// This error type represents JSON errors.
    Json(serde_json::Error),
    /// This error type represents integer parse errors.
    ParseInt(ParseIntError),
    /// This error type represents integer parse errors.
    TryInt(TryFromIntError),
    /// This error type represents a slice conversion error.
    TrySlice(TryFromSliceError),
    /// This error type represents a /proc filesystem error.
    Proc(ProcError),
    /// This error type represents a regular expression error.
    Regex(regex::Error),
    /// This error type represents Seccomp errors.
    Scmp(SeccompError),
    /// This error type represents set logger errors.
    SetLog(SetLoggerError),
    /// This error type represents UTF-8 errors.
    Utf8(Utf8Error),
    #[cfg(feature = "oci")]
    /// This error type represents a Cgroup setup error.
    CgSetup(CreateCgroupSetupError),
    #[cfg(feature = "oci")]
    /// This error type represents a miscallenous Cgroup error.
    CgMisc(AnyManagerError),
    #[cfg(feature = "oci")]
    /// This error type represents container errors.
    Cont(LibcontainerError),
    #[cfg(feature = "oci")]
    /// This error type represents a path canonicalization error.
    Pext(PathBufExtError),
    #[cfg(feature = "oci")]
    /// This error type represents a tracing subscriber error.
    SetTracing(SetGlobalDefaultError),
    #[cfg(feature = "oci")]
    /// This error type represents a signal parsing error.
    Signal(SignalError<String>),
    #[cfg(feature = "oci")]
    /// This error type represents OCI-Spec errors.
    Spec(oci_spec::OciSpecError),
}

impl SydError {
    /// Converts a SydError to an Errno if applicable.
    #[inline(always)]
    pub fn errno(&self) -> Option<Errno> {
        match self {
            Self::Nix(errno) => Some(*errno),
            Self::Proc(error) => proc_error_to_errno(error),
            Self::Scmp(error) => {
                if let Some(scmp_errno) = error.errno() {
                    seccomp2errno(scmp_errno)
                } else {
                    None
                }
            }
            _ => 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::ArgsByte(error) => write!(f, "ArgsError: {error:?}"),
            Self::ArgsUtf8(error) => write!(f, "ArgsError: {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, "NixError: {errno:?}"),
            Self::Json(error) => write!(f, "JsonError: {error:?}"),
            Self::ParseInt(error) => write!(f, "ParseIntError: {error:?}"),
            Self::Scmp(error) => write!(f, "SeccompError: {error:?}"),
            Self::SetLog(error) => write!(f, "SetLogError: {error:?}"),
            Self::TryInt(error) => write!(f, "TryFromIntError: {error:?}"),
            Self::TrySlice(error) => write!(f, "TryFromSliceError: {error:?}"),
            Self::Proc(error) => write!(f, "ProcError: {error:?}"),
            Self::Regex(error) => write!(f, "RegexError: {error:?}"),
            Self::Utf8(error) => write!(f, "Utf8Error: {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::ArgsByte(error) => write!(f, "ArgsError: {error:?}"),
            Self::ArgsUtf8(error) => write!(f, "ArgsError: {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, "NixError: {errno}"),
            Self::Json(error) => write!(f, "JsonError: {error}"),
            Self::ParseInt(error) => write!(f, "ParseIntError: {error}"),
            Self::Scmp(error) => write!(f, "SeccompError: {error}"),
            Self::SetLog(error) => write!(f, "SetLogError: {error}"),
            Self::TryInt(error) => write!(f, "TryFromIntError: {error}"),
            Self::TrySlice(error) => write!(f, "TryFromSliceError: {error}"),
            Self::Proc(error) => write!(f, "ProcError: {error}"),
            Self::Regex(error) => write!(f, "RegexError: {error}"),
            Self::Utf8(error) => write!(f, "Utf8Error: {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::ArgsUtf8(error) => Some(error),
            Self::Nix(errno) => Some(errno),
            Self::ParseInt(error) => Some(error),
            Self::SetLog(error) => Some(error),
            Self::TryInt(error) => Some(error),
            Self::TrySlice(error) => Some(error),
            Self::Proc(error) => Some(error),
            Self::Regex(error) => Some(error),
            Self::Utf8(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,
        }
    }
}

// Conversions from std::io::Error to SydError.
impl From<io::Error> for SydError {
    fn from(err: io::Error) -> SydError {
        SydError::Nix(err2no(err))
    }
}

// Conversions from AddrParseError to SydError.
impl From<AddrParseError> for SydError {
    fn from(err: AddrParseError) -> SydError {
        SydError::Addr(err)
    }
}

// Conversions from Utf8Error to SydError.
impl From<Utf8Error> for SydError {
    fn from(err: Utf8Error) -> SydError {
        SydError::Utf8(err)
    }
}

// Conversions from ProcError to SydError.
impl From<ProcError> for SydError {
    fn from(err: ProcError) -> SydError {
        SydError::Proc(err)
    }
}

// Conversions from regex::Error to SydError.
impl From<regex::Error> for SydError {
    fn from(err: regex::Error) -> SydError {
        SydError::Regex(err)
    }
}

// Conversions from getargs::Error to SydError.
impl From<getargs::Error<&'static [u8]>> for SydError {
    fn from(err: getargs::Error<&'static [u8]>) -> SydError {
        SydError::ArgsByte(err)
    }
}

// Conversions from getargs::Error to SydError.
impl From<getargs::Error<&'static str>> for SydError {
    fn from(err: getargs::Error<&'static str>) -> SydError {
        SydError::ArgsUtf8(err)
    }
}

// Conversions from CapsError to SydError.
impl From<CapsError> for SydError {
    fn from(err: CapsError) -> SydError {
        SydError::Caps(err)
    }
}

// Conversions from ElfError to SydError.
impl From<ElfError> for SydError {
    fn from(err: ElfError) -> SydError {
        SydError::Elf(err)
    }
}

// Conversions from LookupError<VarError> to SydError.
impl From<LookupError<VarError>> for SydError {
    fn from(err: LookupError<VarError>) -> SydError {
        SydError::Env(err)
    }
}

// Conversions from nix::errno::Errno to SydError.
impl From<Errno> for SydError {
    fn from(err: Errno) -> SydError {
        SydError::Nix(err)
    }
}

// Conversions from serde_json::Error to SydError.
impl From<serde_json::Error> for SydError {
    fn from(err: serde_json::Error) -> SydError {
        SydError::Json(err)
    }
}

// Conversions from AnyManagerError to SydError.
#[cfg(feature = "oci")]
impl From<AnyManagerError> for SydError {
    fn from(err: AnyManagerError) -> SydError {
        SydError::CgMisc(err)
    }
}

// Conversions from CreateCgroupSetupError to SydError.
#[cfg(feature = "oci")]
impl From<CreateCgroupSetupError> for SydError {
    fn from(err: CreateCgroupSetupError) -> SydError {
        SydError::CgSetup(err)
    }
}

// Conversions from LibcontainerError to SydError.
#[cfg(feature = "oci")]
impl From<LibcontainerError> for SydError {
    fn from(err: LibcontainerError) -> SydError {
        SydError::Cont(err)
    }
}

// Conversions from PathBufExtError to SydError.
#[cfg(feature = "oci")]
impl From<PathBufExtError> for SydError {
    fn from(err: PathBufExtError) -> SydError {
        SydError::Pext(err)
    }
}

// Conversions from SetGlobalDefaultError to SydError.
#[cfg(feature = "oci")]
impl From<SetGlobalDefaultError> for SydError {
    fn from(err: SetGlobalDefaultError) -> SydError {
        SydError::SetTracing(err)
    }
}

// Conversions from SignalError<String> to SydError.
#[cfg(feature = "oci")]
impl From<SignalError<String>> for SydError {
    fn from(err: SignalError<String>) -> SydError {
        SydError::Signal(err)
    }
}

// Conversions from OciSpecError to SydError.
#[cfg(feature = "oci")]
impl From<oci_spec::OciSpecError> for SydError {
    fn from(err: oci_spec::OciSpecError) -> SydError {
        SydError::Spec(err)
    }
}

// Conversions from ParseIntError to SydError.
impl From<ParseIntError> for SydError {
    fn from(err: ParseIntError) -> SydError {
        SydError::ParseInt(err)
    }
}

// Conversions from TryFromIntError to SydError.
impl From<TryFromIntError> for SydError {
    fn from(err: TryFromIntError) -> SydError {
        SydError::TryInt(err)
    }
}

// Conversions from TryFromSliceError to SydError.
impl From<TryFromSliceError> for SydError {
    fn from(err: TryFromSliceError) -> SydError {
        SydError::TrySlice(err)
    }
}

// Conversions from SeccompError to SydError.
impl From<SeccompError> for SydError {
    fn from(err: SeccompError) -> SydError {
        SydError::Scmp(err)
    }
}

// Conversions from SetLoggerError to SydError.
impl From<SetLoggerError> for SydError {
    fn from(err: SetLoggerError) -> SydError {
        SydError::SetLog(err)
    }
}

/// Convert a std::io::Error into a nix::Errno.
#[inline(always)]
pub fn err2no(err: std::io::Error) -> Errno {
    err.raw_os_error()
        .map(Errno::from_raw)
        .unwrap_or(Errno::ENOSYS)
}

/// Convert a std::io::Error reference into a nix::Errno.
#[inline(always)]
pub fn err2no_ref(err: &std::io::Error) -> Errno {
    err.raw_os_error()
        .map(Errno::from_raw)
        .unwrap_or(Errno::ENOSYS)
}

/// Convert libseccomp errno to nix errno.
#[inline(always)]
pub fn seccomp2errno(errno: SeccompErrno) -> Option<Errno> {
    match errno {
        SeccompErrno::EACCES => Some(Errno::EACCES),
        SeccompErrno::ECANCELED => Some(Errno::ECANCELED),
        SeccompErrno::EDOM => Some(Errno::EDOM),
        SeccompErrno::EEXIST => Some(Errno::EEXIST),
        SeccompErrno::EFAULT => Some(Errno::EFAULT),
        SeccompErrno::EINVAL => Some(Errno::EINVAL),
        SeccompErrno::ENOENT => Some(Errno::ENOENT),
        SeccompErrno::ENOMEM => Some(Errno::ENOMEM),
        SeccompErrno::EOPNOTSUPP => Some(Errno::EOPNOTSUPP),
        SeccompErrno::ERANGE => Some(Errno::ERANGE),
        SeccompErrno::ESRCH => Some(Errno::ESRCH),
        _ => None,
    }
}

/// Convert proc errno to nix errno.
#[inline(always)]
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_ref(error)),
        ProcError::Other(_) => None,
        ProcError::Incomplete(_) => None,
        ProcError::InternalError(_) => None,
    }
}
