use std::io;
use std::fmt;
use crate::status::ExitStatus;
use nix;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorCode {
CreatePipe = 1,
Fork = 2,
Exec = 3,
Chdir = 4,
ParentDeathSignal = 5,
PipeError = 6,
StdioError = 7,
SetUser = 8,
ChangeRoot = 9,
SetIdMap = 10,
SetPGid = 11,
SetNs = 12,
CapSet = 13,
PreExec = 14,
}
#[derive(Debug)]
pub enum Error {
NixError, UnknownError,
CreatePipe(i32),
Fork(i32),
Exec(i32),
Chdir(i32),
ParentDeathSignal(i32),
PipeError(i32),
WaitError(i32),
StdioError(i32),
SetUser(i32),
ChangeRoot(i32),
SetIdMap(i32),
AuxCommandExited(i32),
AuxCommandKilled(i32),
SetPGid(i32),
SetNs(i32),
CapSet(i32),
BeforeUnfreeze(Box<dyn (::std::error::Error) + Send + Sync + 'static>),
PreExec(i32),
}
impl Error {
pub fn raw_os_error(&self) -> Option<i32> {
use self::Error::*;
match self {
&UnknownError => None,
&NixError => None,
&CreatePipe(x) => Some(x),
&Fork(x) => Some(x),
&Exec(x) => Some(x),
&Chdir(x) => Some(x),
&ParentDeathSignal(x) => Some(x),
&PipeError(x) => Some(x),
&WaitError(x) => Some(x),
&StdioError(x) => Some(x),
&SetUser(x) => Some(x),
&ChangeRoot(x) => Some(x),
&SetIdMap(x) => Some(x),
&AuxCommandExited(..) => None,
&AuxCommandKilled(..) => None,
&SetPGid(x) => Some(x),
&SetNs(x) => Some(x),
&CapSet(x) => Some(x),
&BeforeUnfreeze(..) => None,
&PreExec(x) => Some(x),
}
}
}
impl Error {
fn title(&self) -> &'static str {
use self::Error::*;
match self {
&UnknownError => "unexpected value received via signal pipe",
&NixError => "some unknown nix error",
&CreatePipe(_) => "can't create pipe",
&Fork(_) => "error when forking",
&Exec(_) => "error when executing",
&Chdir(_) => "error when setting working directory",
&ParentDeathSignal(_) => "error when death signal",
&PipeError(_) => "error in signalling pipe",
&WaitError(_) => "error in waiting for child",
&StdioError(_) => "error setting up stdio for child",
&SetUser(_) => "error setting user or groups",
&ChangeRoot(_) => "error changing root directory",
&SetIdMap(_) => "error setting uid/gid mappings",
&AuxCommandExited(_) => "aux command exited with non-zero code",
&AuxCommandKilled(_) => "aux command was killed by signal",
&SetPGid(_) => "error when calling setpgid",
&SetNs(_) => "error when calling setns",
&CapSet(_) => "error when setting capabilities",
&BeforeUnfreeze(_) => "error in before_unfreeze callback",
&PreExec(_) => "error in pre_exec callback",
}
}
}
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
use crate::Error::*;
if let Some(code) = self.raw_os_error() {
let errno = nix::errno::from_i32(code);
if let nix::errno::Errno::UnknownErrno = errno {
write!(fmt, "{}: {}", self.title(),
io::Error::from_raw_os_error(code))
} else {
write!(fmt, "{}: {} (os error {})", self.title(),
errno.desc(), code)
}
} else {
match self {
BeforeUnfreeze(err) => {
write!(fmt, "{}: {}", self.title(), err)
}
_ => write!(fmt, "{}", self.title()),
}
}
}
}
#[inline]
pub fn result<T, E: IntoError>(code: ErrorCode, r: Result<T, E>)
-> Result<T, Error>
{
r.map_err(|e| e.into_error(code))
}
#[inline]
pub fn cmd_result<E: IntoError>(def_code: ErrorCode, r: Result<ExitStatus, E>)
-> Result<(), Error>
{
match r.map_err(|e| e.into_error(def_code))? {
ExitStatus::Exited(0) => Ok(()),
ExitStatus::Exited(x) => Err(Error::AuxCommandExited(x as i32)),
ExitStatus::Signaled(x, _) => Err(Error::AuxCommandKilled(x as i32)),
}
}
pub trait IntoError {
fn into_error(self, code: ErrorCode) -> Error;
}
impl IntoError for nix::Error {
fn into_error(self, code: ErrorCode) -> Error {
match self {
nix::Error::Sys(x) => code.wrap(x as i32),
_ => Error::NixError,
}
}
}
impl IntoError for io::Error {
fn into_error(self, code: ErrorCode) -> Error {
code.wrap(self.raw_os_error().unwrap_or(-1))
}
}
impl IntoError for Error {
fn into_error(self, code: ErrorCode) -> Error {
code.wrap(self.raw_os_error().unwrap_or(-1))
}
}
impl ErrorCode {
pub fn wrap(self, errno: i32) -> Error {
use self::ErrorCode as C;
use self::Error as E;
match self {
C::CreatePipe => E::CreatePipe(errno),
C::Fork => E::Fork(errno),
C::Exec => E::Exec(errno),
C::Chdir => E::Chdir(errno),
C::ParentDeathSignal => E::ParentDeathSignal(errno),
C::PipeError => E::PipeError(errno),
C::StdioError => E::StdioError(errno),
C::SetUser => E::SetUser(errno),
C::ChangeRoot => E::ChangeRoot(errno),
C::SetIdMap => E::SetIdMap(errno),
C::SetPGid => E::SetPGid(errno),
C::SetNs => E::SetNs(errno),
C::CapSet => E::CapSet(errno),
C::PreExec => E::PreExec(errno),
}
}
pub fn from_i32(code: i32, errno: i32) -> Error {
use self::ErrorCode as C;
use self::Error as E;
match code {
c if c == C::CreatePipe as i32 => E::CreatePipe(errno),
c if c == C::Fork as i32 => E::Fork(errno),
c if c == C::Exec as i32 => E::Exec(errno),
c if c == C::Chdir as i32 => E::Chdir(errno),
c if c == C::ParentDeathSignal as i32
=> E::ParentDeathSignal(errno),
c if c == C::PipeError as i32 => E::PipeError(errno),
c if c == C::StdioError as i32 => E::StdioError(errno),
c if c == C::SetUser as i32 => E::SetUser(errno),
c if c == C::ChangeRoot as i32 => E::ChangeRoot(errno),
c if c == C::SetIdMap as i32 => E::SetIdMap(errno),
c if c == C::SetPGid as i32 => E::SetPGid(errno),
c if c == C::SetNs as i32 => E::SetNs(errno),
c if c == C::CapSet as i32 => E::CapSet(errno),
c if c == C::PreExec as i32 => E::PreExec(errno),
_ => E::UnknownError,
}
}
}