ia-sandbox 0.4.0

A CLI to sandbox (jail) and collect usage of applications.
Documentation
#![allow(clippy::use_self)]

use std::ffi::OsString;
use std::path::PathBuf;
use std::result::Result as StdResult;

use serde::{Deserialize, Serialize};
use thiserror::Error;

#[derive(Error, Debug, Serialize, Deserialize)]
pub enum FFIError {
    #[error("Could not chdir to {path:?}: {error}")]
    ChdirError { path: PathBuf, error: String },
    #[error("Could not chroot to {path:?}: {error}")]
    ChrootError { path: PathBuf, error: String },
    #[error("Could not clone process: {0}")]
    CloneError(String),
    #[error("Could not dup file descriptor {name}({fd}): {error}")]
    DupFdError {
        fd: i32,
        name: String,
        error: String,
    },
    #[error("Could not create directory {path:?}: {error}")]
    CreateDirError { path: PathBuf, error: String },
    #[error("Could not exec {command:?} (arguments: {arguments:?}): {error}")]
    ExecError {
        command: PathBuf,
        arguments: Vec<OsString>,
        error: String,
    },
    #[error("Could not mount path: {path:?}: {error}")]
    MountError { path: PathBuf, error: String },
    #[error("Could not open file descriptor {name}({fd}): {error}")]
    OpenFdError {
        fd: i32,
        name: String,
        error: String,
    },
    #[error("Could not disable aslr: {0}")]
    PersonalityError(String),
    #[error("Could not create pipe: {0}")]
    Pipe2Error(String),
    #[error("Could not pivot_root to {new_root:?} with old root at {old_root:?}: {error}")]
    PivotRootError {
        new_root: PathBuf,
        old_root: PathBuf,
        error: String,
    },
    #[error("Could not set process to die when parent dies: {0}")]
    PrSetPDeathSigError(String),
    #[error("Could not disable future priveleges on execve: {0}")]
    PrSetNoNewPrivsError(String),
    #[error("Could not set interval timer alarm: {0}")]
    SetITimerError(String),
    #[error("Could not set process group id of {pid} to {pgid}: {error}")]
    SetpgidError { pid: i32, pgid: i32, error: String },
    #[error("Could not set resource `{resource}` limit: {error}")]
    SetRLimitError { resource: String, error: String },
    #[error("Could not set a signal handler for {signal}: {error}")]
    SigActionError { signal: String, error: String },
    #[error("Could not umount path: {path:?}: {error}")]
    UMountError { path: PathBuf, error: String },
    #[error("Could not unshare cgroup namespace: {0}")]
    UnshareCgroupsError(String),
    #[error("Could not usleep for {time} microseconds: {error}")]
    UsleepError { time: u32, error: String },
    #[error("Could not write /proc/self/uid_map file: {0}")]
    WriteUidError(String),
    #[error("Could not write /proc/self/uid_map file: {0}")]
    WriteGidError(String),
    #[error("Could not wait for process: {0}")]
    WaitPidError(String),
    #[error("Could not write /proc/self/setgroups file: {0}")]
    WriteSetGroupsError(String),
}

#[derive(Error, Debug, Serialize, Deserialize)]
pub enum CgroupsError {
    #[error("Cgroups hierarchy missing: {0:?}")]
    HierarchyMissing(PathBuf),
    #[error("Could not clear instance hierarchy at {hierarchy_path:?}: {error}")]
    InstanceClearError {
        hierarchy_path: PathBuf,
        error: String,
    },
    #[error(
        "Could not create instance hierarchy under {hierarchy_path:?} for {instance_name:?}: {error}",
    )]
    InstanceHierarchyCreateError {
        hierarchy_path: PathBuf,
        instance_name: OsString,
        error: String,
    },
    #[error("Could not open {file:?} for hierarchy {hierarchy_path:?}: {error}")]
    OpenCgroupsFileError {
        hierarchy_path: PathBuf,
        file: PathBuf,
        error: String,
    },
    #[error("Could not parse `{buffer}` from {file:?} for hierarchy {hierarchy_path:?}: {error}")]
    ParseCgroupsFileError {
        hierarchy_path: PathBuf,
        file: PathBuf,
        buffer: String,
        error: String,
    },

    #[error("Could not read from {file:?} for hierarchy {hierarchy_path:?}: {error}")]
    ReadCgroupsFileError {
        hierarchy_path: PathBuf,
        file: PathBuf,
        error: String,
    },
    #[error("Could not find field `{field}` in file {file:?} inside hierarchy {hierarchy_path:?}")]
    MissingFieldError {
        hierarchy_path: PathBuf,
        file: PathBuf,
        field: String,
    },
    #[error("Could not write to {file:?} for hierarchy {hierarchy_path:?}: {error}")]
    WriteCgroupsFileError {
        hierarchy_path: PathBuf,
        file: PathBuf,
        error: String,
    },
}

#[derive(Error, Debug, Serialize, Deserialize)]
pub enum ChildError {
    #[error("Caps error occurred: {0}")]
    CapsError(i32),
    #[error("Cgroups error occurred.")]
    CgroupsError(#[from] CgroupsError),
    #[error("FFI Error occurred.")]
    FFIError(#[from] FFIError),
}

impl From<capctl::Error> for ChildError {
    fn from(err: capctl::Error) -> Self {
        Self::CapsError(err.code())
    }
}

#[derive(Error, Debug, Serialize, Deserialize)]
pub enum Error {
    #[error("Cgroups error occurred.")]
    CgroupsError(#[from] CgroupsError),
    #[error("Child process error occurred.")]
    ChildError(#[from] ChildError),
    #[error("Child process successfully completed even though it used exec")]
    ContinuedPastExecError(String),
    #[error("Could not deserialize process result: {}", _0)]
    DeserializeError(String),
    #[error("FFI Error occurred.")]
    FFIError(#[from] FFIError),
    #[error("Child process stopped/continued unexpected")]
    StoppedContinuedError,
    #[error("Supervisor process died and could not collect execution information")]
    SupervisorProcessDiedError,
}

pub type Result<T> = StdResult<T, Error>;