Skip to main content

a3s_box_runtime/rootfs/
mod.rs

1//! Guest rootfs management module.
2//!
3//! This module handles preparation and management of guest rootfs for MicroVM instances.
4//! The rootfs contains the minimal filesystem required to boot the guest agent.
5//!
6//! Two rootfs providers are available:
7//! - `CopyProvider` — full recursive copy (works everywhere)
8//! - `OverlayProvider` — Linux overlayfs mount (near-instant CoW)
9
10mod builder;
11mod layout;
12pub(crate) mod overlay;
13mod provider;
14
15pub use builder::RootfsBuilder;
16pub use layout::{GuestLayout, GUEST_WORKDIR};
17pub use provider::{default_provider, CopyProvider, OverlayProvider, RootfsProvider};
18
19use std::path::Path;
20
21/// Unmount a box's overlayfs `merged` view — best-effort and idempotent.
22///
23/// Box teardown must release this mount BEFORE removing the box dir, or
24/// `remove_dir_all` deletes *into* the live mount and fails with "Stale file
25/// handle", leaking the mount. A restart re-mounts without unmounting first, so
26/// the overlay can be stacked (mounted 2–3×); unmount in a bounded loop until
27/// `merged` is no longer a mountpoint. No-op if it was never mounted.
28pub fn unmount_box_overlay(merged: &Path) {
29    for _ in 0..8 {
30        if !is_mountpoint(merged) {
31            break;
32        }
33        if overlay::overlay_unmount(merged).is_err() {
34            break;
35        }
36    }
37}
38
39/// True if `path` is a mountpoint (its device id differs from its parent's).
40#[cfg(target_os = "linux")]
41pub(crate) fn is_mountpoint(path: &Path) -> bool {
42    use std::os::unix::fs::MetadataExt;
43    match (std::fs::metadata(path), std::fs::metadata(path.join(".."))) {
44        (Ok(here), Ok(parent)) => here.dev() != parent.dev(),
45        _ => false,
46    }
47}
48
49#[cfg(not(target_os = "linux"))]
50pub(crate) fn is_mountpoint(_path: &Path) -> bool {
51    false
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn missing_path_is_not_mountpoint() {
60        let temp = tempfile::tempdir().unwrap();
61        let missing = temp.path().join("missing");
62
63        assert!(!is_mountpoint(&missing));
64    }
65
66    #[test]
67    fn unmount_overlay_noops_for_non_mountpoint() {
68        let temp = tempfile::tempdir().unwrap();
69        let merged = temp.path().join("merged");
70        std::fs::create_dir(&merged).unwrap();
71
72        unmount_box_overlay(&merged);
73
74        assert!(merged.exists());
75    }
76}