use serde::{Serialize, Serializer};
use std::error::Error as StdError;
use std::fmt;
use std::io;
use std::path::PathBuf;
pub(crate) type Cause = Box<dyn StdError + Send + Sync>;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, strum_macros::Display)]
#[strum(serialize_all = "lowercase")]
#[serde(rename_all = "lowercase")]
pub(crate) enum Mechanism {
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
Landlock,
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
Seccomp,
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
Seatbelt,
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
Appcontainer,
}
#[derive(Debug, strum_macros::IntoStaticStr)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
pub(crate) enum Error {
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
FilesystemDenied,
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
NetworkDenied,
#[strum(serialize = "USAGE_ERROR")]
Usage {
message: String,
},
PolicyParseFailed {
source: Cause,
},
PolicyIoFailed {
source: io::Error,
},
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
PolicyUnrestrictedRead,
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
PolicyTcpBindUnsupported,
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
PolicyUnixSocketUnsupported,
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
PolicyUnixSocketPath,
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
PolicyDenyWriteSymlinkAncestor,
PolicyInvalidPort,
PolicyEmptyPath,
PolicyHomeUnavailable,
PolicyTraversalDepth,
SandboxSetupFailed {
mechanism: Mechanism,
source: Cause,
},
#[cfg_attr(not(any(target_os = "linux", target_os = "windows")), allow(dead_code))]
SuperviseFailed {
source: Cause,
},
LaunchFailed {
tool: PathBuf,
source: Cause,
},
#[cfg_attr(not(any(target_os = "linux", target_os = "windows")), allow(dead_code))]
IntegerTooLarge,
#[cfg_attr(
any(target_os = "linux", target_os = "macos", target_os = "windows"),
allow(dead_code)
)]
PlatformUnsupported,
}
impl Error {
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
pub(crate) const DENIAL_ERRNO: i32 = libc::EACCES;
pub(crate) fn code(&self) -> &'static str {
self.into()
}
#[cfg(unix)]
pub(crate) fn errno(&self) -> Option<i32> {
StdError::source(self)?
.downcast_ref::<io::Error>()?
.raw_os_error()
}
#[cfg(not(unix))]
#[allow(clippy::unused_self)]
pub(crate) fn errno(&self) -> Option<i32> {
None
}
pub(crate) fn detail(&self) -> Option<String> {
match self {
Self::Usage { message } => Some(message.clone()),
Self::LaunchFailed { tool, .. } => Some(tool.display().to_string()),
_ => None,
}
}
}
pub(crate) fn errno_name(errno: i32) -> Option<&'static str> {
match errno {
libc::E2BIG => Some("E2BIG"),
libc::EACCES => Some("EACCES"),
libc::EAGAIN => Some("EAGAIN"),
libc::EBADF => Some("EBADF"),
libc::EFAULT => Some("EFAULT"),
libc::EINVAL => Some("EINVAL"),
libc::EIO => Some("EIO"),
libc::EISDIR => Some("EISDIR"),
libc::ELOOP => Some("ELOOP"),
libc::ENAMETOOLONG => Some("ENAMETOOLONG"),
libc::ENOENT => Some("ENOENT"),
libc::ENOEXEC => Some("ENOEXEC"),
libc::ENOMEM => Some("ENOMEM"),
libc::ENOTDIR => Some("ENOTDIR"),
libc::EPERM => Some("EPERM"),
_ => None,
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.code())?;
match self.detail() {
Some(detail) => write!(f, ": {detail}"),
None => Ok(()),
}
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
Self::PolicyIoFailed { source } => Some(source),
Self::PolicyParseFailed { source }
| Self::SandboxSetupFailed { source, .. }
| Self::SuperviseFailed { source }
| Self::LaunchFailed { source, .. } => Some(&**source),
_ => None,
}
}
}
impl Serialize for Error {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.code())
}
}