88 lines
3.3 KiB
Rust
88 lines
3.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::{
|
|
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)?;
|
|
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)?;
|
|
}
|
|
}
|
|
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,
|
|
})
|
|
}
|
|
}
|