use std::path::{Path, PathBuf};
use std::sync::RwLock;
use super::{RootSource, RuntimePaths, absolutize, user_runtime_root};
static CHILD_PROFILE: RwLock<Option<RuntimePaths>> = RwLock::new(None);
pub fn set_child_profile(paths: Option<RuntimePaths>) -> bool {
if paths.as_ref().is_some_and(|p| p.child_id().is_none()) {
return false;
}
*CHILD_PROFILE.write().unwrap_or_else(|e| e.into_inner()) = paths;
true
}
pub fn child_profile() -> Option<RuntimePaths> {
CHILD_PROFILE.read().unwrap_or_else(|e| e.into_inner()).clone()
}
pub const SPAWN_JSON_FILE: &str = "spawn.json";
pub const PARENT_POLICY_FILE: &str = "parent-policy.json";
pub const STATE_JSON_FILE: &str = "state.json";
pub const PROJECT_KEY_FILE: &str = "project.key";
pub const PROJECT_CERT_FILE: &str = "project.cert.json";
pub const OVERLAY_FILE: &str = "overlay.toml";
pub const REVOKED_FILE: &str = "revoked";
pub fn child_run_dir(run_root: &Path, id: &str) -> Option<PathBuf> {
safe_component(id).then(|| run_root.join(id))
}
pub fn revoked_marker(run_root: &Path, id: &str) -> Option<PathBuf> {
child_run_dir(run_root, id).map(|d| d.join(REVOKED_FILE))
}
fn safe_component(id: &str) -> bool {
!id.is_empty() && id.bytes().all(|b| b.is_ascii_alphanumeric())
}
impl RuntimePaths {
pub fn child_at(
run_dir: impl Into<PathBuf>,
id: &str,
project_root: impl Into<PathBuf>,
) -> Option<Self> {
let run_dir = absolutize(&run_dir.into());
safe_component(id).then(|| Self {
root: run_dir.clone(),
source: RootSource::Child {
id: id.to_owned(),
project_root: absolutize(&project_root.into()),
trust_root: run_dir,
},
})
}
pub fn child_container_at(
run_dir: impl Into<PathBuf>,
trust_dir: impl Into<PathBuf>,
id: &str,
project_root: impl Into<PathBuf>,
) -> Option<Self> {
let mut paths = Self::child_at(run_dir, id, project_root)?;
if let RootSource::Child { trust_root, .. } = &mut paths.source {
*trust_root = absolutize(&trust_dir.into());
}
Some(paths)
}
pub fn trust_root(&self) -> &Path {
match &self.source {
RootSource::Child { trust_root, .. } => trust_root,
_ => &self.root,
}
}
pub fn child_with(home: &Path, id: &str, project_root: impl Into<PathBuf>) -> Option<Self> {
Self::child_at(child_run_dir(&user_runtime_root(home), id)?, id, project_root)
}
pub(super) fn child_weftos_dir(&self) -> Option<PathBuf> {
match &self.source {
RootSource::Child { project_root, .. } => Some(project_root.join(".weftos")),
_ => None,
}
}
pub fn child_id(&self) -> Option<&str> {
match &self.source {
RootSource::Child { id, .. } => Some(id),
_ => None,
}
}
pub fn spawn_json(&self) -> PathBuf {
self.file(SPAWN_JSON_FILE)
}
pub fn parent_policy(&self) -> PathBuf {
self.trust_root().join(PARENT_POLICY_FILE)
}
pub fn state_json(&self) -> PathBuf {
self.file(STATE_JSON_FILE)
}
pub fn revoked_marker(&self) -> PathBuf {
self.trust_root().join(REVOKED_FILE)
}
pub fn project_key(&self) -> Option<PathBuf> {
self.child_weftos_dir().map(|d| d.join(PROJECT_KEY_FILE))
}
pub fn project_cert(&self) -> Option<PathBuf> {
self.child_weftos_dir().map(|d| d.join(PROJECT_CERT_FILE))
}
pub fn overlay(&self) -> Option<PathBuf> {
self.child_weftos_dir().map(|d| d.join(OVERLAY_FILE))
}
pub fn state_dir(&self) -> Option<PathBuf> {
self.child_weftos_dir().map(|d| d.join("state"))
}
}
#[cfg(test)]
mod tests {
use super::*;
const ID: &str = "01JB8Z3Q0V6X9KQ4M2N7T5R1WD";
fn child() -> RuntimePaths {
RuntimePaths::child_with(Path::new("/h"), ID, "/work/p").unwrap()
}
#[test]
fn layout_table() {
let p = child();
let run = PathBuf::from(format!("/h/.weftos/run/{ID}"));
assert_eq!(p.root(), run);
assert_eq!(p.child_id(), Some(ID));
assert_eq!(p.socket(), run.join("kernel.sock"));
assert_eq!(p.pid(), run.join("kernel.pid"));
assert_eq!(p.lock(), run.join("kernel.lock"));
assert_eq!(p.log(), run.join("kernel.log"));
assert_eq!(p.spawn_json(), run.join("spawn.json"));
assert_eq!(p.parent_policy(), run.join("parent-policy.json"));
assert_eq!(p.state_json(), run.join("state.json"));
let w = PathBuf::from("/work/p/.weftos");
assert_eq!(p.project_key(), Some(w.join("project.key")));
assert_eq!(p.project_cert(), Some(w.join("project.cert.json")));
assert_eq!(p.overlay(), Some(w.join("overlay.toml")));
assert_eq!(p.state_dir(), Some(w.join("state")));
assert_eq!(p.chain_checkpoint(), w.join("chain/chain.json"));
assert_eq!(p.chain_rvf(), w.join("chain/chain.rvf"));
assert_eq!(p.chain_tree(), w.join("chain/chain.tree.json"));
assert_eq!(p.chain_dir(), w.join("chain"));
assert_eq!(p.anchors_ledger(), w.join("chain/anchors.jsonl"));
assert_eq!(p.workloads(), w.join("state/workloads.json"));
assert_eq!(p.apps(), w.join("state/apps.json"));
}
#[test]
fn one_project_key_is_node_and_chain_key() {
let p = child();
assert_eq!(p.node_key(), p.chain_key());
assert_eq!(Some(p.node_key()), p.project_key());
}
#[test]
fn non_child_roots_keep_their_layout_and_have_no_project_files() {
let p = RuntimePaths::at("/run/probe");
assert_eq!(p.node_key(), PathBuf::from("/run/probe/node.key"));
assert_eq!(p.chain_key(), PathBuf::from("/run/probe/chain.key"));
assert_eq!(p.workloads(), PathBuf::from("/run/probe/workloads.json"));
assert_eq!(p.child_id(), None);
assert_eq!(p.project_key(), None);
assert_eq!(p.overlay(), None);
}
#[test]
fn walk_up_never_selects_a_child() {
let t = tempfile::tempdir().unwrap();
let home = t.path().join("home");
let root = t.path().join("proj");
std::fs::create_dir_all(root.join(".weftos")).unwrap();
std::fs::write(root.join(".weftos/project.toml"), "").unwrap();
let run = home.join(".weftos/run").join(ID);
std::fs::create_dir_all(&run).unwrap();
for cwd in [&root, &run, &home] {
let p = RuntimePaths::resolve_with(None, Some(cwd), Some(&home));
assert!(!matches!(p.source(), RootSource::Child { .. }), "{cwd:?}");
}
let p = RuntimePaths::resolve_with(None, Some(&root), Some(&home));
assert!(matches!(p.source(), RootSource::Project(_)));
}
#[test]
fn unsafe_ids_are_refused() {
for bad in ["", "..", "a/b", "a\\b", "x.y", "../etc"] {
assert!(
RuntimePaths::child_with(Path::new("/h"), bad, "/p").is_none(),
"{bad:?}"
);
}
}
#[test]
fn relative_inputs_are_made_absolute() {
let p = RuntimePaths::child_at("rel/run", ID, "rel/proj").unwrap();
let cwd = std::env::current_dir().unwrap();
assert_eq!(p.root(), cwd.join("rel/run"));
assert_eq!(p.chain_dir(), cwd.join("rel/proj/.weftos/chain"));
assert!(p.project_key().unwrap().is_absolute());
}
#[test]
fn revoked_marker_is_one_path_for_parent_and_child() {
let run_root = Path::new("/iso/run");
let marker = revoked_marker(run_root, ID).unwrap();
assert_eq!(marker, PathBuf::from(format!("/iso/run/{ID}/revoked")));
let dir = child_run_dir(run_root, ID).unwrap();
let at = RuntimePaths::child_at(&dir, ID, "/p").unwrap();
assert_eq!(at.revoked_marker(), marker);
assert_eq!(
child().revoked_marker(),
revoked_marker(&user_runtime_root(Path::new("/h")), ID).unwrap()
);
for bad in ["", "..", "a/b"] {
assert!(revoked_marker(run_root, bad).is_none() && child_run_dir(run_root, bad).is_none());
}
}
#[test]
fn child_at_honours_explicit_run_dir() {
let p = RuntimePaths::child_at("/iso/run", ID, "/p").unwrap();
assert_eq!(p.socket(), PathBuf::from("/iso/run/kernel.sock"));
assert_eq!(p.chain_dir(), PathBuf::from("/p/.weftos/chain"));
}
}