Files
sausage/host/src/world.rs
T

130 lines
5.3 KiB
Rust

//! A world directory and the one virtual root that host code and guests share.
//!
//! Layout of a world directory:
//! - `world/`: the virtual root `/` that every guest sees. Agents change everything in it.
//! - `host/`: host state outside the sandbox (harness storage, later the vault). No guest sees it.
use sausage_vfs::{Generated, Host, Layer, Memory, Mounts, Process};
use std::{
os::unix::fs::PermissionsExt,
path::{Path, PathBuf},
sync::Arc,
};
/// An open world. `root` is the filesystem every guest sees as `/`; host code (the harness's file
/// tools) reads and writes through the same layer, so both always see the same files.
pub struct World {
pub root: Arc<dyn Layer>,
/// The host state folder, `<dir>/host`.
pub host_dir: PathBuf,
/// Live process metadata shared with the process table.
pub processes: crate::process::Processes,
}
fn copy_tree(source: &Path, destination: &Path) -> std::io::Result<()> {
std::fs::create_dir_all(destination)?;
let mut permissions = std::fs::metadata(destination)?.permissions();
permissions.set_mode(permissions.mode() | 0o200);
std::fs::set_permissions(destination, permissions)?;
for entry in std::fs::read_dir(source)? {
let entry = entry?;
let target = destination.join(entry.file_name());
if entry.file_type()?.is_dir() {
copy_tree(&entry.path(), &target)?;
} else {
std::fs::copy(entry.path(), &target)?;
let mut permissions = std::fs::metadata(&target)?.permissions();
permissions.set_mode(permissions.mode() | 0o200);
std::fs::set_permissions(&target, permissions)?;
}
}
Ok(())
}
impl World {
/// Open (and create if missing) the world directory `dir`, including `/proc` and `/run` mounts.
pub fn open(dir: &Path) -> anyhow::Result<Self> {
Self::open_with_userland(dir, None)
}
/// Copy an optional bundle into a new world. Existing worlds retain their programs.
pub fn open_with_userland(dir: &Path, userland: Option<&Path>) -> anyhow::Result<Self> {
let root_dir = dir.join("world");
let host_dir = dir.join("host");
let fresh = !root_dir.exists();
std::fs::create_dir_all(root_dir.join("usr/local/bin"))?;
if fresh && let Some(bundle) = userland {
for folder in ["bin", "src"] {
copy_tree(&bundle.join(folder), &root_dir.join(folder))?;
}
}
std::fs::create_dir_all(&host_dir)?;
let mut mounts = Mounts::default();
mounts.mount(
"/",
Arc::new(Host::new(&root_dir, Process::default())?),
false,
);
let processes: crate::process::Processes =
Arc::new(std::sync::Mutex::new(Default::default()));
let snapshot = processes.clone();
let mut proc = Generated::default();
proc.insert("/processes", move || {
let mut text = String::from("PID\tPPID\tSTATE\tCWD\tCONVERSATION\tARGV\n");
for (pid, info) in snapshot.lock().unwrap().iter() {
text.push_str(&format!(
"{pid}\t{}\t{}\t{}\t{}\t{}\n",
info.parent,
info.state,
info.cwd,
info.conversation.as_deref().unwrap_or("-"),
info.argv.join(" ")
));
}
Ok(text.into_bytes())
});
mounts.mount("/proc", Arc::new(proc), true);
mounts.mount("/run", Arc::new(Memory::default()), false);
let root: Arc<dyn Layer> = Arc::new(mounts);
Ok(World {
root,
host_dir,
processes,
})
}
}
#[cfg(test)]
#[test]
fn readonly_bundle_copies_are_writable() -> std::io::Result<()> {
use std::fs;
let bundle = tempfile::tempdir()?;
let world = tempfile::tempdir()?;
for (name, mode) in [("bin/tool.wasm", 0o555), ("src/lib.rs", 0o444)] {
let path = bundle.path().join(name);
fs::create_dir_all(path.parent().unwrap())?;
fs::write(&path, b"original")?;
fs::set_permissions(path, fs::Permissions::from_mode(mode))?;
}
for path in [bundle.path().join("bin"), bundle.path().join("src"), bundle.path().to_owned(), world.path().to_owned()] {
fs::set_permissions(path, fs::Permissions::from_mode(0o555))?;
}
let result = copy_tree(bundle.path(), world.path());
// Restore source directories so the temporary directory can remove its files.
for path in [bundle.path(), &bundle.path().join("bin"), &bundle.path().join("src")] {
fs::set_permissions(path, fs::Permissions::from_mode(0o755))?;
}
result?;
assert_eq!(fs::metadata(world.path())?.permissions().mode() & 0o777, 0o755);
for (name, mode) in [("bin/tool.wasm", 0o555), ("src/lib.rs", 0o444)] {
let source = bundle.path().join(name);
let target = world.path().join(name);
assert_eq!(fs::metadata(&source)?.permissions().mode() & 0o777, mode);
assert_eq!(fs::metadata(&target)?.permissions().mode() & 0o777, mode | 0o200);
fs::write(&target, b"changed")?;
assert_eq!(fs::read(source)?, b"original");
}
fs::write(world.path().join("bin/added.wasm"), b"new")?;
fs::write(world.path().join("src/added.rs"), b"new")?;
Ok(())
}