ia-sandbox 0.4.0

A CLI to sandbox (jail) and collect usage of applications.
Documentation
use std::ffi::{OsStr, OsString};
use std::fmt::{self, Display, Formatter};
use std::path::{Path, PathBuf};
use std::time::Duration;

use serde::{Deserialize, Serialize};

#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
pub enum ShareNet {
    Share,
    #[default]
    Unshare,
}

#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
pub enum SwapRedirects {
    Yes,
    #[default]
    No,
}

#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
pub enum ClearUsage {
    #[default]
    Yes,
    No,
}

#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
pub enum Interactive {
    Yes,
    #[default]
    No,
}

#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
pub enum Aslr {
    #[default]
    Randomize,
    NoRandomize,
}

#[derive(Debug, Eq, PartialEq, Copy, Clone, PartialOrd, Ord, Default, Serialize, Deserialize)]
pub struct SpaceUsage(u64);

impl SpaceUsage {
    pub fn from_bytes(bytes: u64) -> Self {
        Self(bytes)
    }

    pub fn from_kilobytes(kilobytes: u64) -> Self {
        Self::from_bytes(kilobytes * 1_000)
    }

    pub fn from_megabytes(megabytes: u64) -> Self {
        Self::from_kilobytes(megabytes * 1_000)
    }

    pub fn from_gigabytes(gigabytes: u64) -> Self {
        Self::from_megabytes(gigabytes * 1_000)
    }

    pub fn from_kibibytes(kibibytes: u64) -> Self {
        Self::from_bytes(kibibytes * 1_024)
    }

    pub fn from_mebibytes(mebibytes: u64) -> Self {
        Self::from_kibibytes(mebibytes * 1_024)
    }

    pub fn from_gibibytes(gibibytes: u64) -> Self {
        Self::from_mebibytes(gibibytes * 1_024)
    }

    pub fn as_bytes(self) -> u64 {
        self.0
    }

    pub fn as_kilobytes(self) -> u64 {
        self.0 / 1_000
    }
}

impl Display for SpaceUsage {
    fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
        if self.0 % (1 << 30) == 0 {
            write!(fmt, "{} gibibytes", self.0 >> 30)
        } else if self.0 % (1 << 20) == 0 {
            write!(fmt, "{} mebibytes", self.0 >> 20)
        } else if self.0 % (1 << 10) == 0 {
            write!(fmt, "{} kibibytes", self.0 >> 10)
        } else if self.0 % 1_000_000_000 == 0 {
            write!(fmt, "{} gigabytes", self.0 / 1_000_000_000)
        } else if self.0 % 1_000_000 == 0 {
            write!(fmt, "{} megabytes", self.0 / 1_000_000)
        } else if self.0 % 1_000 == 0 {
            write!(fmt, "{} kilobytes", self.0 / 1_000)
        } else {
            write!(fmt, "{} bytes", self.0)
        }
    }
}

/// Limits for memory/time
#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
pub struct Limits {
    pub wall_time: Option<Duration>,
    pub user_time: Option<Duration>,
    pub memory: Option<SpaceUsage>,
    pub stack: Option<SpaceUsage>,
    pub pids: Option<usize>,
}

impl Limits {
    pub fn wall_time(&self) -> Option<Duration> {
        self.wall_time
    }

    pub fn user_time(&self) -> Option<Duration> {
        self.user_time
    }

    pub fn memory(&self) -> Option<SpaceUsage> {
        self.memory
    }

    pub fn stack(&self) -> Option<SpaceUsage> {
        self.stack
    }

    pub fn pids(&self) -> Option<usize> {
        self.pids
    }
}

#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub struct MountOptions {
    read_only: bool,
    dev: bool,
    exec: bool,
}

impl MountOptions {
    pub fn read_only(self) -> bool {
        self.read_only
    }

    pub fn dev(self) -> bool {
        self.dev
    }

    pub fn exec(self) -> bool {
        self.exec
    }

    pub fn set_read_only(&mut self, value: bool) {
        self.read_only = value;
    }

    pub fn set_dev(&mut self, value: bool) {
        self.dev = value;
    }

    pub fn set_exec(&mut self, value: bool) {
        self.exec = value;
    }
}

impl Default for MountOptions {
    fn default() -> Self {
        Self {
            read_only: true,
            dev: false,
            exec: false,
        }
    }
}

#[derive(Debug, Eq, PartialEq, Clone)]
pub struct Mount {
    source: PathBuf,
    destination: PathBuf,
    mount_options: MountOptions,
}

impl Mount {
    pub fn new(source: PathBuf, destination: PathBuf, mount_options: MountOptions) -> Self {
        Self {
            source,
            destination,
            mount_options,
        }
    }

    pub fn source(&self) -> &Path {
        &self.source
    }

    pub fn destination(&self) -> &Path {
        &self.destination
    }

    pub fn mount_options(&self) -> MountOptions {
        self.mount_options
    }
}

#[derive(Debug, Eq, PartialEq, Clone)]
pub enum Environment {
    Forward,
    EnvList(Vec<(String, String)>),
}

impl Default for Environment {
    fn default() -> Self {
        Self::EnvList(Vec::new())
    }
}

#[derive(Debug, Eq, PartialEq)]
pub struct Config {
    pub command: PathBuf,
    pub args: Vec<OsString>,
    pub new_root: Option<PathBuf>,
    pub share_net: ShareNet,
    pub redirect_stdin: Option<PathBuf>,
    pub redirect_stdout: Option<PathBuf>,
    pub redirect_stderr: Option<PathBuf>,
    pub limits: Limits,
    pub instance_name: OsString,
    pub hierarchy_path: Option<PathBuf>,
    pub mounts: Vec<Mount>,
    pub swap_redirects: SwapRedirects,
    pub clear_usage: ClearUsage,
    pub interactive: Interactive,
    pub aslr: Aslr,
    pub privileged: bool,
    pub environment: Environment,
}

impl Config {
    pub fn command(&self) -> &Path {
        &self.command
    }

    pub fn args(&self) -> Vec<&OsStr> {
        self.args.iter().map(OsString::as_os_str).collect()
    }

    pub fn new_root(&self) -> Option<&Path> {
        self.new_root.as_deref()
    }

    pub fn share_net(&self) -> ShareNet {
        self.share_net
    }

    pub fn redirect_stdin(&self) -> Option<&Path> {
        self.redirect_stdin.as_deref()
    }

    pub fn redirect_stdout(&self) -> Option<&Path> {
        self.redirect_stdout.as_deref()
    }

    pub fn redirect_stderr(&self) -> Option<&Path> {
        self.redirect_stderr.as_deref()
    }

    pub fn limits(&self) -> Limits {
        self.limits
    }

    pub fn instance_name(&self) -> &OsStr {
        &self.instance_name
    }

    pub fn hierarchy_path(&self) -> Option<&Path> {
        self.hierarchy_path.as_deref()
    }

    pub fn mounts(&self) -> &[Mount] {
        self.mounts.as_ref()
    }

    pub fn swap_redirects(&self) -> SwapRedirects {
        self.swap_redirects
    }

    pub fn clear_usage(&self) -> ClearUsage {
        self.clear_usage
    }

    pub fn interactive(&self) -> Interactive {
        self.interactive
    }

    pub fn aslr(&self) -> Aslr {
        self.aslr
    }

    pub fn environment(&self) -> &Environment {
        &self.environment
    }
}