genpac 0.1.0

Sandbox for Gentoo ebuild development using bubblewrap
// Copyright (C) 2023 Gokul Das B
// SPDX-License-Identifier: GPL-3.0-or-later
//! Workspace verification
//!
//! This module deals with verifying the workspace directory, maintaining backend information and
//! deriving paths for snapshot/chroot directories.

use super::chroots::ChrootUnverified;
use super::{GlobalsFinal, Vars};
use crate::snapshot::BackendTypes;
use anyhow::Result as AResult;
use serde::Deserialize;
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};

#[derive(Deserialize)]
pub(super) struct WSConfig {
    location: String,
    backend: Option<BackendTypes>,
}

pub(super) struct Workspace {
    location: PathBuf,
    backend: BackendTypes,
}

impl WSConfig {
    pub(super) fn verify(self, vars: &Vars) -> AResult<Workspace> {
        let location = vars.resolve_path(&self.location);
        let backend = self.backend.unwrap_or_default();

        // Get metadata for workspace directory
        let meta = match std::fs::metadata(&location) {
            Ok(m) => m,
            Err(e) => {
                log::error!("Failed to get metadata of workspace directory:");
                log::error!("{e}");
                anyhow::bail!("{e}");
            }
        };

        if meta.uid() != users::get_current_uid() {
            let msg = "You are not the owner of the workspace directory";
            log::error!("{msg}");
            anyhow::bail!(msg);
        }

        if (meta.mode() & 0o700) != 0o700u32 {
            let msg = "Insufficient permissions (rwx) for workspace directory";
            log::error!("{msg}");
            anyhow::bail!(msg);
        }

        Ok(Workspace { location, backend })
    }
}

impl GlobalsFinal {
    #[inline]
    pub fn chroot(&self, name: &Path) -> ChrootUnverified {
        ChrootUnverified::new(name, &self.cfg.workspace)
    }

    #[inline]
    pub fn workspace(&self) -> &Path {
        &self.cfg.workspace
    }

    #[inline]
    pub fn backend(&self) -> BackendTypes {
        self.cfg.workspace.backend
    }

    pub fn list_chroots(&self) -> AResult<Vec<String>> {
        let mut chroots = Vec::new();
        let contents = std::fs::read_dir(self.cfg.workspace.location.as_path())?;
        for content in contents {
            let Ok(content) = content else {
                log::warn!("Intermittent error in reading workspace");
                continue;
            };
            let name = content.file_name().to_str().unwrap_or_default().to_string();
            let name = match content.file_type().map(|x| x.is_dir()) {
                Ok(true) => name,
                Ok(false) => continue,
                Err(e) => {
                    log::warn!("Error identifying entry type of {name}:");
                    log::warn!("{e}");
                    continue;
                }
            };
            chroots.push(name.to_string());
        }
        Ok(chroots)
    }
}

// Allow treating workspace as a Path
impl std::ops::Deref for Workspace {
    type Target = Path;

    #[inline]
    fn deref(&self) -> &Self::Target {
        self.location.as_path()
    }
}