arcbox-computer-runtime 0.10.4

Sandbox orchestration over nested microVMs behind the arcbox-vm-driver port: boot, exec, files, checkpoints, warm pools. The in-sandbox init is arcbox-vm-agent; the wire vocabulary is arcbox-vm-proto.
Documentation
//! Read injected images through the kernel's ext4 driver.

#[path = "rootfs_cancellation.rs"]
mod cancellation;

use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::sync::Arc;

use anyhow::{Context, Result, ensure};
use arcbox_computer_runtime::snapshot_cow::{BlockTools, BusyboxBlockTools};
use arcbox_computer_runtime::{
    ROOTFS_CAPACITY_GRANULARITY, RootfsBuilder, RootfsPaths, RootfsSource, RootfsSpec,
    VM_AGENT_PATH,
};
use arcbox_ext4::constants::file_mode;
use arcbox_ext4::{FormatOptions, Formatter};

use super::common::LOOP_DEVICE_TEST;

fn block_tools() -> Option<Arc<dyn BlockTools>> {
    let busybox = std::env::var("BUSYBOX").unwrap_or_else(|_| "/bin/busybox".into());
    if super::common::is_root() && Path::new(&busybox).exists() {
        Some(Arc::new(BusyboxBlockTools::new(busybox)))
    } else {
        assert!(
            std::env::var_os("ARCBOX_REQUIRE_BLOCK_TOOLS").is_none(),
            "rootfs integration requires root and busybox"
        );
        eprintln!("SKIP rootfs integration: requires root and busybox");
        None
    }
}

fn builder(agent: &Path, tools: Arc<dyn BlockTools>) -> RootfsBuilder {
    RootfsBuilder::new(
        RootfsPaths {
            vm_agent: agent.to_path_buf(),
            cache_dir: agent.parent().unwrap().join("cache"),
            busybox: "/nonexistent/busybox".into(),
        },
        tools,
    )
}

fn with_mounted(
    tools: &dyn BlockTools,
    image: &Path,
    check: impl FnOnce(&Path) -> Result<()>,
) -> Result<()> {
    use nix::mount::{MsFlags, mount, umount};

    let mount_dir = tempfile::tempdir()?;
    let device = tools.attach_loop(image, true)?;
    let mounted = mount(
        Some(device.as_str()),
        mount_dir.path(),
        Some("ext4"),
        MsFlags::MS_RDONLY,
        None::<&str>,
    );
    let result = match mounted {
        Ok(()) => {
            let checked = check(mount_dir.path());
            umount(mount_dir.path())
                .context("unmount test image")
                .and(checked)
        }
        Err(error) => Err(error).context("mount test image"),
    };
    tools
        .detach_loop(&device)
        .context("detach test image")
        .and(result)
}

fn verify_agent_files(root: &Path, expected_agent: &[u8]) -> Result<()> {
    let agent = root.join(VM_AGENT_PATH.trim_start_matches('/'));
    ensure!(
        std::fs::read(&agent)? == expected_agent,
        "agent bytes differ"
    );
    ensure!(
        std::fs::metadata(&agent)?.permissions().mode() & 0o777 == 0o755,
        "agent must be executable"
    );
    ensure!(
        std::fs::read_link(root.join("etc/resolv.conf"))? == Path::new("../run/resolv.conf"),
        "resolver must point into the run tmpfs"
    );
    Ok(())
}

fn verify_boot_files(root: &Path, expected_agent: &[u8]) -> Result<()> {
    verify_agent_files(root, expected_agent)?;
    ensure!(
        std::fs::read(root.join("sbin/init"))? == b"distribution-init",
        "injection must preserve the distribution init"
    );
    Ok(())
}

#[tokio::test]
async fn injection_preserves_distribution_files_and_can_replace_the_agent() {
    let Some(tools) = block_tools() else { return };
    let _serial = LOOP_DEVICE_TEST.lock().await;
    let dir = tempfile::tempdir().unwrap();
    let image = dir.path().join("existing.ext4");
    let mut formatter =
        Formatter::with_options(&image, FormatOptions::new(128 * 1024 * 1024)).unwrap();
    for path in ["/sbin", "/etc", "/run"] {
        formatter
            .create(
                path,
                file_mode::S_IFDIR | 0o755,
                None,
                None,
                None,
                None,
                None,
                None,
            )
            .unwrap();
    }
    formatter
        .create(
            "/sbin/init",
            file_mode::S_IFREG | 0o755,
            None,
            None,
            Some(&mut b"distribution-init".as_slice()),
            None,
            None,
            None,
        )
        .unwrap();
    formatter.close().unwrap();

    let agent = dir.path().join("vm-agent");
    for contents in [b"first-agent".as_slice(), b"replacement-agent".as_slice()] {
        std::fs::write(&agent, contents).unwrap();
        builder(&agent, Arc::clone(&tools))
            .inject_vm_agent(&image)
            .await
            .unwrap();
        with_mounted(&*tools, &image, |root| verify_boot_files(root, contents)).unwrap();
    }
}

#[tokio::test]
async fn a_caller_sized_build_is_mountable_and_replaces_the_destination() {
    let Some(tools) = block_tools() else { return };
    let _serial = LOOP_DEVICE_TEST.lock().await;
    let dir = tempfile::tempdir().unwrap();
    let layer = dir.path().join("ABCDEF");
    std::fs::create_dir_all(layer.join("diff/sbin")).unwrap();
    std::fs::write(layer.join("diff/sbin/init"), b"distribution-init").unwrap();
    std::fs::write(layer.join("link"), "ABCDEF").unwrap();
    let image = dir.path().join("images/digest/rootfs.ext4");
    std::fs::create_dir_all(image.parent().unwrap()).unwrap();
    std::fs::write(&image, b"previous-image").unwrap();
    let agent = dir.path().join("vm-agent");
    std::fs::write(&agent, b"staged-agent").unwrap();
    let size = 2 * ROOTFS_CAPACITY_GRANULARITY;

    builder(&agent, Arc::clone(&tools))
        .build_rootfs(RootfsSpec {
            source: RootfsSource::Directory(layer.clone()),
            out: image.clone(),
            size,
        })
        .await
        .unwrap();

    assert_eq!(std::fs::metadata(&image).unwrap().len(), size);
    assert_eq!(
        std::fs::read_dir(image.parent().unwrap()).unwrap().count(),
        1
    );
    assert!(!dir.path().join("cache").exists());
    with_mounted(&*tools, &image, |root| {
        verify_boot_files(root, b"staged-agent")
    })
    .unwrap();

    let builder = builder(&agent, Arc::clone(&tools));
    let cached = builder
        .convert_layer_to_rootfs(layer.to_str().unwrap(), &Default::default())
        .await
        .unwrap();
    assert_eq!(std::fs::metadata(&cached).unwrap().len(), 512 * 1024 * 1024);
    let stem = Path::new(&cached).file_stem().unwrap().to_str().unwrap();
    let keys = stem.strip_prefix("rootfs-").unwrap();
    let (layer_key, agent_key) = keys.split_once('-').unwrap();
    assert_eq!((layer_key.len(), agent_key.len()), (16, 16));
    assert_eq!(
        builder
            .convert_layer_to_rootfs(layer.to_str().unwrap(), &Default::default())
            .await
            .unwrap(),
        cached
    );
    with_mounted(&*tools, Path::new(&cached), |root| {
        verify_boot_files(root, b"staged-agent")
    })
    .unwrap();

    let blocked = image.parent().unwrap().join("directory");
    std::fs::create_dir(&blocked).unwrap();
    let error = builder
        .build_rootfs(RootfsSpec {
            source: RootfsSource::Directory(layer.clone()),
            out: blocked.clone(),
            size,
        })
        .await
        .unwrap_err();
    assert!(
        error.to_string().contains("publish rootfs image"),
        "{error}"
    );
    assert!(blocked.is_dir());
    assert_eq!(
        std::fs::read_dir(image.parent().unwrap()).unwrap().count(),
        2
    );

    std::fs::create_dir_all(layer.join("diff/etc/resolv.conf")).unwrap();
    let error = builder
        .build_rootfs(RootfsSpec {
            source: RootfsSource::Directory(layer),
            out: image.clone(),
            size,
        })
        .await
        .unwrap_err();
    assert!(
        error.to_string().contains("replace /etc/resolv.conf"),
        "{error}"
    );
    assert_eq!(
        std::fs::read_dir(image.parent().unwrap()).unwrap().count(),
        2
    );
    with_mounted(&*tools, &image, |root| {
        verify_boot_files(root, b"staged-agent")
    })
    .unwrap();
}

#[tokio::test]
async fn cached_templates_can_grow_beyond_the_initial_capacity() {
    use std::io::{Read, Seek, SeekFrom, Write};

    const INITIAL_SIZE: u64 = 512 * 1024 * 1024;
    const PAYLOAD_SIZE: u64 = INITIAL_SIZE + 4096;
    const TAIL: &[u8] = b"template-payload-tail";
    let tail_offset = -i64::try_from(TAIL.len()).unwrap();
    let Some(tools) = block_tools() else { return };
    let _serial = LOOP_DEVICE_TEST.lock().await;
    let dir = tempfile::tempdir().unwrap();
    let layer = dir.path().join("ABCDEF");
    std::fs::create_dir_all(layer.join("diff/sbin")).unwrap();
    std::fs::write(layer.join("diff/sbin/init"), b"distribution-init").unwrap();
    std::fs::write(layer.join("link"), "ABCDEF").unwrap();
    let mut payload = std::fs::File::create(layer.join("diff/payload")).unwrap();
    payload.set_len(PAYLOAD_SIZE).unwrap();
    payload.seek(SeekFrom::End(tail_offset)).unwrap();
    payload.write_all(TAIL).unwrap();

    let agent = dir.path().join("vm-agent");
    std::fs::write(&agent, b"staged-agent").unwrap();
    let builder = builder(&agent, Arc::clone(&tools));
    let image = dir.path().join("images/rootfs.ext4");
    std::fs::create_dir(image.parent().unwrap()).unwrap();
    std::fs::write(&image, b"previous-image").unwrap();
    let error = builder
        .build_rootfs(RootfsSpec {
            source: RootfsSource::Directory(layer.clone()),
            out: image.clone(),
            size: INITIAL_SIZE,
        })
        .await
        .unwrap_err();
    assert!(
        error.to_string().contains("requested rootfs capacity"),
        "{error}"
    );
    assert_eq!(std::fs::read(&image).unwrap(), b"previous-image");
    assert_eq!(
        std::fs::read_dir(image.parent().unwrap()).unwrap().count(),
        1
    );

    let cached = builder
        .convert_layer_to_rootfs(layer.to_str().unwrap(), &Default::default())
        .await
        .unwrap();
    assert!(std::fs::metadata(&cached).unwrap().len() > INITIAL_SIZE);
    assert_eq!(
        std::fs::read_dir(dir.path().join("cache")).unwrap().count(),
        1
    );
    with_mounted(&*tools, Path::new(&cached), |root| {
        verify_boot_files(root, b"staged-agent")?;
        let mut payload = std::fs::File::open(root.join("payload"))?;
        ensure!(
            payload.metadata()?.len() == PAYLOAD_SIZE,
            "payload was truncated"
        );
        payload.seek(SeekFrom::End(tail_offset))?;
        let mut tail = vec![0; TAIL.len()];
        payload.read_exact(&mut tail)?;
        ensure!(tail == TAIL, "payload tail differs");
        Ok(())
    })
    .unwrap();
    assert_eq!(
        builder
            .convert_layer_to_rootfs(layer.to_str().unwrap(), &Default::default())
            .await
            .unwrap(),
        cached
    );
}

#[cfg(feature = "remote-image")]
#[tokio::test]
#[ignore = "requires root, registry access, and a 32 GiB sparse image"]
async fn a_registry_image_builds_at_sparse_computer_capacity() {
    use std::os::unix::fs::MetadataExt;
    use std::time::Instant;

    use arcbox_computer_runtime::oci2rootfs::RemoteRef;

    const CAPACITY: u64 = 32 * 1024 * 1024 * 1024;
    let Some(tools) = block_tools() else { return };
    let _serial = LOOP_DEVICE_TEST.lock().await;
    let reference =
        std::env::var("ARCBOX_TEST_IMAGE").unwrap_or_else(|_| "docker.io/library/debian:12".into());
    let source = RemoteRef::new(&reference).fetch().await.unwrap();
    let digest = source
        .manifest_digest()
        .expect("a registry image has a manifest digest")
        .to_string();
    let config = source
        .config()
        .cloned()
        .expect("a registry image has a config");
    let dir = tempfile::tempdir().unwrap();
    let agent = dir.path().join("vm-agent");
    std::fs::write(&agent, b"staged-agent").unwrap();
    let image = dir
        .path()
        .join("images")
        .join(digest.replace(':', "-"))
        .join("rootfs.ext4");
    let started = Instant::now();
    builder(&agent, Arc::clone(&tools))
        .build_rootfs(RootfsSpec {
            source: RootfsSource::Image(source),
            out: image.clone(),
            size: CAPACITY,
        })
        .await
        .unwrap();
    let elapsed = started.elapsed();
    let metadata = std::fs::metadata(&image).unwrap();
    let allocated = metadata.blocks() * 512;
    eprintln!(
        "registry_rootfs reference={reference} digest={digest} apparent_bytes={} allocated_bytes={allocated} elapsed_ms={} cmd={:?} entrypoint={:?}",
        metadata.len(),
        elapsed.as_millis(),
        config.cmd(),
        config.entrypoint(),
    );
    assert_eq!(metadata.len(), CAPACITY);
    assert!(
        allocated < CAPACITY / 8,
        "32 GiB registry fixture must be sparse, got {allocated} allocated bytes"
    );
    with_mounted(&*tools, &image, |root| {
        verify_agent_files(root, b"staged-agent")
    })
    .unwrap();
}