use std::io;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum JailerError {
#[error("isolation: {0}")]
Isolation(#[from] IsolationError),
#[error("system: {0}")]
System(#[from] SystemError),
#[error("config: {0}")]
Config(#[from] ConfigError),
#[error("jailer not supported on this platform")]
UnsupportedPlatform,
#[error("cgroup: {0}")]
Cgroup(String),
#[error("io: {0}")]
Io(#[from] io::Error),
}
#[derive(Debug, Error)]
pub enum IsolationError {
#[error("{namespace} namespace: {source}")]
Namespace {
namespace: &'static str,
#[source]
source: io::Error,
},
#[error("chroot at {path}: {source}")]
Chroot {
path: String,
#[source]
source: io::Error,
},
#[error("pivot_root: {0}")]
PivotRoot(#[source] io::Error),
#[error("device node {path}: {source}")]
DeviceNode {
path: String,
#[source]
source: io::Error,
},
#[error("seccomp: {0}")]
Seccomp(String),
#[error("landlock: {0}")]
Landlock(String),
#[error("cgroup: {0}")]
Cgroup(String),
}
#[derive(Debug, Error)]
pub enum SystemError {
#[error("close fds: {0}")]
CloseFds(#[source] io::Error),
#[error("rlimit {resource}: {source}")]
Rlimit {
resource: &'static str,
#[source]
source: io::Error,
},
#[error("privilege drop to {uid}:{gid}: {source}")]
PrivilegeDrop {
uid: u32,
gid: u32,
#[source]
source: io::Error,
},
}
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("sandbox profile: {0}")]
SandboxProfile(String),
#[error("profile not found: {0}")]
ProfileNotFound(String),
#[error("invalid config: {0}")]
InvalidConfig(String),
}
impl From<JailerError> for boxlite_shared::errors::BoxliteError {
fn from(err: JailerError) -> Self {
boxlite_shared::errors::BoxliteError::Engine(err.to_string())
}
}
impl IsolationError {
pub fn namespace(ns: &'static str, source: io::Error) -> Self {
Self::Namespace {
namespace: ns,
source,
}
}
pub fn chroot(path: impl Into<String>, source: io::Error) -> Self {
Self::Chroot {
path: path.into(),
source,
}
}
pub fn device_node(path: impl Into<String>, source: io::Error) -> Self {
Self::DeviceNode {
path: path.into(),
source,
}
}
}
impl SystemError {
pub fn rlimit(resource: &'static str, source: io::Error) -> Self {
Self::Rlimit { resource, source }
}
pub fn privilege_drop(uid: u32, gid: u32, source: io::Error) -> Self {
Self::PrivilegeDrop { uid, gid, source }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_hierarchy() {
let iso_err = IsolationError::Seccomp("test".into());
let jailer_err: JailerError = iso_err.into();
assert!(matches!(jailer_err, JailerError::Isolation(_)));
let sys_err = SystemError::CloseFds(io::Error::other("test"));
let jailer_err: JailerError = sys_err.into();
assert!(matches!(jailer_err, JailerError::System(_)));
let cfg_err = ConfigError::ProfileNotFound("/path".into());
let jailer_err: JailerError = cfg_err.into();
assert!(matches!(jailer_err, JailerError::Config(_)));
}
#[test]
fn test_error_display() {
let err = JailerError::Isolation(IsolationError::Seccomp("blocked syscall".into()));
assert_eq!(err.to_string(), "isolation: seccomp: blocked syscall");
let err = JailerError::System(SystemError::rlimit(
"RLIMIT_NOFILE",
io::Error::new(io::ErrorKind::PermissionDenied, "denied"),
));
assert!(err.to_string().contains("rlimit RLIMIT_NOFILE"));
}
#[test]
fn test_boxlite_error_conversion() {
let err = JailerError::UnsupportedPlatform;
let boxlite_err: boxlite_shared::errors::BoxliteError = err.into();
assert!(boxlite_err.to_string().contains("not supported"));
}
}