//! World folders and atomic build output helpers. use anyhow::{Result, bail}; use std::path::{Path, PathBuf}; #[derive(Clone)] /// The absolute host folder shared by all processes. pub struct World { /// The absolute path of the world on the host. pub root: PathBuf, } impl World { /// Validate reserved root names and resolve the world path. pub fn open(path: &Path) -> Result { for name in ["proc", "run", "self"] { if path.join(name).symlink_metadata().is_ok() { bail!("reserved world path: {name}"); } } Ok(Self { root: path.canonicalize()?, }) } /// Create the folders and empty boot configuration of a new world. pub fn create(path: &Path) -> Result { if path.exists() { bail!("world already exists: {}", path.display()); } for dir in ["plugins", "state/core", "config"] { std::fs::create_dir_all(path.join(dir))?; } std::fs::write(path.join("config/boot.yaml"), "[]\n")?; Self::open(path) } /// Return the folder that contains a plugin component. pub fn plugin_dir(&self, name: &str) -> PathBuf { self.root.join("plugins").join(name) } /// Return the persistent state folder of a plugin. pub fn state_dir(&self, name: &str) -> PathBuf { self.root.join("state").join(name) } } /// Write a temporary sibling and rename it over the destination. pub fn write_atomic(path: &Path, bytes: &[u8]) -> Result<()> { std::fs::create_dir_all(path.parent().unwrap())?; let tmp = path.with_extension("tmp"); std::fs::write(&tmp, bytes)?; std::fs::rename(tmp, path)?; Ok(()) } /// Replace a build copy with a source tree, without target folders. pub fn replace_source(from: &Path, to: &Path) -> Result<()> { if to.exists() { std::fs::remove_dir_all(to)?; } copy_tree(from, to) } fn copy_tree(from: &Path, to: &Path) -> Result<()> { std::fs::create_dir_all(to)?; for entry in std::fs::read_dir(from)? { let entry = entry?; if entry.file_name() == "target" { continue; } if entry.file_type()?.is_dir() { copy_tree(&entry.path(), &to.join(entry.file_name()))?; } else { std::fs::copy(entry.path(), to.join(entry.file_name()))?; } } Ok(()) }