sbox 0.2.4

Tiny Linux containers implementation
Documentation
use std::{
    fs::File,
    io::ErrorKind,
    ops::Deref,
    path::{Path, PathBuf},
    sync::Once,
};

use rand::distributions::{Alphanumeric, DistString as _};
use sbox::{Cgroup, Error};
use tar::Archive;

pub struct TempDir(PathBuf);

impl TempDir {
    #[allow(unused)]
    pub fn new() -> Result<Self, Error> {
        let tmpdir = Path::new(env!("CARGO_TARGET_TMPDIR"));
        let path = loop {
            let path = tmpdir.join(format!("test-{}", rand_string(32)));
            match std::fs::metadata(&path) {
                Ok(_) => continue,
                Err(v) if v.kind() == ErrorKind::NotFound => break path,
                Err(v) => return Err(v.into()),
            }
        };
        std::fs::create_dir_all(&path)?;
        Ok(Self(path))
    }

    #[allow(unused)]
    pub fn join<P: AsRef<Path>>(&self, path: P) -> PathBuf {
        self.0.join(path)
    }

    #[allow(unused)]
    pub fn as_path(&self) -> &Path {
        self.0.as_path()
    }

    #[allow(unused)]
    pub fn remove(self) -> Result<(), Error> {
        Ok(std::fs::remove_dir_all(&self.0)?)
    }
}

impl Drop for TempDir {
    fn drop(&mut self) {
        let _ = std::fs::remove_dir_all(&self.0);
    }
}

#[allow(unused)]
pub fn rand_string(len: usize) -> String {
    Alphanumeric.sample_string(&mut rand::thread_rng(), len)
}

#[allow(unused)]
pub fn get_rootfs() -> Result<Archive<File>, Error> {
    static ONCE: Once = Once::new();
    ONCE.call_once(|| {
        assert!(std::process::Command::new("curl")
            .arg("-fsSL")
            .arg("--retry")
            .arg("5")
            .arg("https://github.com/docker-library/busybox/raw/31d342ad033e27c18723a516a2274ab39547be27/stable/glibc/busybox.tar.xz")
            .arg("-o")
            .arg(format!("rootfs.tar.xz"))
            .current_dir("./tests")
            .spawn()
            .unwrap()
            .wait()
            .unwrap()
            .success());
        assert!(std::process::Command::new("xz")
            .arg("-df")
            .arg("rootfs.tar.xz")
            .current_dir("./tests")
            .spawn()
            .unwrap()
            .wait()
            .unwrap()
            .success());
    });
    let mut rootfs = Archive::new(File::open("./tests/rootfs.tar")?);
    rootfs.set_preserve_permissions(true);
    rootfs.set_preserve_ownerships(true);
    rootfs.set_unpack_xattrs(true);
    Ok(rootfs)
}

#[allow(unused)]
pub fn get_cgroup() -> Result<Cgroup, Error> {
    if let Ok(v) = std::env::var("TEST_CGROUP_PATH") {
        let path = PathBuf::from(v);
        let root_path = "/sys/fs/cgroup";
        return Cgroup::new(root_path, path.strip_prefix(root_path).unwrap());
    }
    Ok(Cgroup::current()?
        .parent()
        .ok_or("Current process cannot be in root cgroup")?)
}

pub struct TempCgroup(Cgroup);

impl TempCgroup {
    #[allow(unused)]
    pub fn new() -> Result<Self, Error> {
        let cgroup = get_cgroup()?.child(format!("test-{}", rand_string(32)))?;
        cgroup.create()?;
        Ok(Self(cgroup))
    }
}

impl Deref for TempCgroup {
    type Target = Cgroup;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl Drop for TempCgroup {
    fn drop(&mut self) {
        let _ = self.0.remove();
    }
}