tartarus-api 0.1.0

Structured API for sandboxing system (currently utilizing `bubblewrap`)
Documentation
use crate::{
    error::{Error, ErrorKind, Result, with_io_context},
    sandbox::DryRun,
};
use std::{borrow::Cow, fmt::Display, io, path::Path};

#[derive(Debug, Clone, Copy)]
pub(super) struct FsHandle {
    dry_run: DryRun,
}

impl FsHandle {
    pub const fn new(dry_run: DryRun) -> Self {
        Self { dry_run }
    }

    pub(super) fn sync_dir(self, initial_source: &Path, initial_dest: &Path) -> Result<()> {
        if matches!(self.dry_run, DryRun::Enabled) {
            return Ok(());
        }

        if !initial_dest.is_dir() {
            return Err(with_io_context(
                format!("from {} to {}", initial_source.display(), initial_dest.display()),
                "unable to sync to non-directory output",
                io::ErrorKind::NotADirectory,
            ));
        }

        let mut queue = vec![(Cow::Borrowed(initial_source), Cow::Borrowed(initial_dest))];

        while let Some((current_source, current_dest)) = queue.pop() {
            self.create_dir(
                &current_dest,
                format_args!("syncing directory {}", current_source.display()),
            )?;

            let entries = current_source.read_dir().map_err(|e| {
                with_io_context(
                    format!("{} under {}", current_source.display(), initial_source.display()),
                    "unable to read dir for syncing",
                    e,
                )
            })?;

            for entry in entries {
                let entry = entry.map_err(|e| {
                    with_io_context(
                        format!("{} under {}", current_source.display(), initial_source.display()),
                        "unable to read entry from when syncing",
                        e,
                    )
                })?;

                let path = entry.path();
                let output = current_dest.join(entry.file_name());

                if path.is_dir() {
                    queue.push((Cow::Owned(path), Cow::Owned(output)));
                } else {
                    std::fs::copy(&path, current_dest.join(entry.file_name())).map_err(|e| {
                        with_io_context(
                            format!("{} to {}", path.display(), output.display()),
                            "unable to copy when syncing",
                            e,
                        )
                    })?;
                }
            }
        }

        Ok(())
    }

    pub(super) fn validate_is_dir(self, path: &Path, context: &'static str) -> Result<()> {
        if matches!(self.dry_run, DryRun::Enabled) {
            return Ok(());
        }

        if !path.is_dir() {
            return Err(with_io_context(path.display(), context, io::ErrorKind::NotADirectory));
        }

        Ok(())
    }

    pub(super) fn validate_relative_path(
        self,
        path: &Path,
        context: impl Display,
    ) -> Result<(), Error> {
        if matches!(self.dry_run, DryRun::Enabled) {
            return Ok(());
        }

        if path.is_absolute() {
            return Err(Error {
                kind: ErrorKind::NonRelativePath(path.into()),
                context: context.to_string(),
            });
        }

        Ok(())
    }

    pub(super) fn create_dir(
        self,
        passthrough_dest: &Path,
        context: impl Display,
    ) -> Result<(), Error> {
        if matches!(self.dry_run, DryRun::Enabled) {
            return Ok(());
        }

        std::fs::create_dir_all(passthrough_dest)
            .map_err(|e| with_io_context(passthrough_dest.display(), context, e.kind()))?;
        Ok(())
    }
}