a3s-box-core 3.0.10

Core types, config, and error handling for A3S Box MicroVM runtime
Documentation
//! Guest-captured filesystem metadata used by stopped-box commit.

use std::path::Path;

use serde::{Deserialize, Serialize};

/// Location written inside a persistent rootfs before guest shutdown.
pub const ROOTFS_METADATA_PATH: &str = "/.a3s_rootfs_metadata_v1.json";
/// Location used to carry OCI header ownership across a rootless host extraction.
pub const IMAGE_ROOTFS_METADATA_PATH: &str = "/.a3s_image_metadata_v1.json";
/// Runtime-staged container environment consumed by guest-init before exec.
pub const RUNTIME_ENV_PATH: &str = "/.a3s-box-env";
/// Stable manifest schema identifier.
pub const ROOTFS_METADATA_SCHEMA: &str = "a3s.box.rootfs-metadata.v1";

/// Return the canonical mode for rootfs files generated by the runtime.
///
/// OCI image and terminal manifests describe the image or previous container
/// generation. The runtime rewrites these files for every launch, so replaying
/// older manifest metadata after that write would either reject the refreshed
/// guest init or make active resolver and hostname configuration inaccessible
/// to non-root image users.
pub fn runtime_managed_rootfs_mode(path: &Path) -> Option<u32> {
    match path.to_str() {
        Some("etc/hostname" | "etc/hosts" | "etc/resolv.conf") => Some(0o644),
        Some("sbin/init" | "usr/sbin/init") => Some(0o755),
        Some(".a3s-box-env") => Some(0o600),
        _ => None,
    }
}

/// Metadata kind supported by OCI rootfs archives.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RootfsEntryKind {
    Directory,
    Regular,
    Symlink,
}

/// One guest-visible filesystem entry. Paths are base64-encoded raw Unix bytes.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RootfsMetadataEntry {
    pub path_base64: String,
    pub kind: RootfsEntryKind,
    pub mode: u32,
    pub uid: u64,
    pub gid: u64,
    pub mtime: u64,
    pub size: u64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub link_target_base64: Option<String>,
}

/// Complete terminal metadata snapshot for one rootfs generation.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RootfsMetadataManifest {
    pub schema: String,
    pub entries: Vec<RootfsMetadataEntry>,
}

impl RootfsMetadataManifest {
    pub fn new(entries: Vec<RootfsMetadataEntry>) -> Self {
        Self {
            schema: ROOTFS_METADATA_SCHEMA.to_string(),
            entries,
        }
    }

    pub fn validate(&self) -> Result<(), String> {
        if self.schema != ROOTFS_METADATA_SCHEMA {
            return Err(format!(
                "unsupported rootfs metadata schema: {}",
                self.schema
            ));
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn runtime_managed_rootfs_files_have_canonical_modes() {
        for path in ["etc/hostname", "etc/hosts", "etc/resolv.conf"] {
            assert_eq!(runtime_managed_rootfs_mode(Path::new(path)), Some(0o644));
        }
        for path in ["sbin/init", "usr/sbin/init"] {
            assert_eq!(runtime_managed_rootfs_mode(Path::new(path)), Some(0o755));
        }
        assert_eq!(
            runtime_managed_rootfs_mode(Path::new(".a3s-box-env")),
            Some(0o600)
        );
        assert_eq!(runtime_managed_rootfs_mode(Path::new("etc/passwd")), None);
    }
}