This commit is contained in:
+6
-5
@@ -255,7 +255,8 @@ impl Broker {
|
||||
}
|
||||
InstanceConfig {
|
||||
mounts: vec![
|
||||
Mount { guest: "/actor".into(), host: self.world.actors_root(), writable: false },
|
||||
Mount { guest: "/".into(), host: self.world.root.clone(), writable: true },
|
||||
Mount { guest: "/actor".into(), host: self.world.actors_root(), writable: true },
|
||||
Mount { guest: "/public".into(), host: own.join("public"), writable: true },
|
||||
Mount { guest: "/data".into(), host: own.join("data"), writable: true },
|
||||
],
|
||||
@@ -445,7 +446,7 @@ impl Broker {
|
||||
self.host_error("the file watcher dropped events; watchers get a rescan, actors a full pass");
|
||||
let watchers: Vec<String> =
|
||||
self.metas.lock().unwrap().iter().filter(|(_, m)| !m.watches.is_empty()).map(|(id, _)| id.clone()).collect();
|
||||
self.inject(&watchers, vfs::ID, json!({"type": "fs_changed", "path": "/actor", "op": "rescan"}));
|
||||
self.inject(&watchers, vfs::ID, json!({"type": "fs_changed", "path": "/", "op": "rescan"}));
|
||||
let report = self.scan().await;
|
||||
for change in &report {
|
||||
eprintln!("sausage: {change}");
|
||||
@@ -599,9 +600,9 @@ impl Broker {
|
||||
let on = ty == "watch";
|
||||
let path_s = b["path"].as_str().ok_or_else(|| anyhow!("missing \"path\""))?;
|
||||
let global = self.world.global_path(&self.world.resolve(&env.from, path_s)?);
|
||||
let path = global.ok_or_else(|| anyhow!("{path_s} is not under /actor"))?;
|
||||
let path = global.ok_or_else(|| anyhow!("{path_s} is not under the world root"))?;
|
||||
let path = match path.trim_end_matches('/') {
|
||||
"" => "/actor".to_string(),
|
||||
"" => "/".to_string(),
|
||||
p => p.to_string(),
|
||||
};
|
||||
self.update_meta(&env.from, |m| {
|
||||
@@ -649,7 +650,7 @@ fn same_file(a: &Path, b: &Path) -> bool {
|
||||
|
||||
/// Whether the namespace path `path` is `prefix` or inside it.
|
||||
fn under(path: &str, prefix: &str) -> bool {
|
||||
path.strip_prefix(prefix).is_some_and(|rest| rest.is_empty() || rest.starts_with('/'))
|
||||
prefix == "/" || path.strip_prefix(prefix).is_some_and(|rest| rest.is_empty() || rest.starts_with('/'))
|
||||
}
|
||||
|
||||
pub fn truncate(s: &str, max: usize) -> String {
|
||||
|
||||
+2
-2
@@ -390,8 +390,8 @@ impl preopens::Host for VfsView<'_> {
|
||||
fn get_directories(&mut self) -> wasmtime::Result<Vec<(Resource<types::Descriptor>, String)>> {
|
||||
let dirs = preopens::Host::get_directories(&mut self.inner)?;
|
||||
for (fd, name) in &dirs {
|
||||
if name == "/actor" {
|
||||
self.state.dirs.insert(fd.rep(), "/actor".to_string());
|
||||
if name == "/actor" || name == "/" {
|
||||
self.state.dirs.insert(fd.rep(), name.clone());
|
||||
}
|
||||
}
|
||||
Ok(dirs)
|
||||
|
||||
+10
-8
@@ -5,7 +5,7 @@
|
||||
//! Events are coalesced per path: once a path is quiet for `QUIET` (or has
|
||||
//! kept changing for `MAX_DELAY`), it becomes one change
|
||||
//! `{path, op: write | create | remove | rename, to?}` with a namespace path
|
||||
//! (`/actor/...`). Then the broker
|
||||
//! (relative to the world root, such as `/actor/...` or `/config/...`). Then the broker
|
||||
//!
|
||||
//! - sends `fs_changed` to the actors that watch the path (`watch` request),
|
||||
//! - starts or upgrades an actor whose `actor/<id>/actor.wasm` (or folder
|
||||
@@ -75,7 +75,7 @@ impl Pending {
|
||||
}
|
||||
|
||||
async fn run(broker: Arc<Broker>, mut events: mpsc::UnboundedReceiver<notify::Result<notify::Event>>) {
|
||||
let root = broker.world.actors_root();
|
||||
let root = broker.world.root.clone();
|
||||
let mut pending: HashMap<String, Pending> = HashMap::new();
|
||||
// Actor checks and full passes. Dropping the set (when the host stops) aborts them.
|
||||
let mut tasks = tokio::task::JoinSet::new();
|
||||
@@ -122,11 +122,11 @@ async fn sleep_until(t: Option<Instant>) {
|
||||
}
|
||||
}
|
||||
|
||||
/// The namespace path (`/actor/...`) of a host path, unless it is outside
|
||||
/// `actor/` or a temporary file of the host.
|
||||
/// The world namespace path of a host path, unless it is outside the
|
||||
/// world or a temporary file of the host.
|
||||
fn namespace(root: &Path, path: &Path) -> Option<String> {
|
||||
let rel = path.strip_prefix(root).ok()?;
|
||||
let mut out = String::from("/actor");
|
||||
let mut out = String::new();
|
||||
for part in rel.components() {
|
||||
let name = part.as_os_str().to_str()?;
|
||||
if name.starts_with(TMP_PREFIX) {
|
||||
@@ -135,7 +135,7 @@ fn namespace(root: &Path, path: &Path) -> Option<String> {
|
||||
out.push('/');
|
||||
out.push_str(name);
|
||||
}
|
||||
Some(out)
|
||||
Some(if out.is_empty() { "/".to_string() } else { out })
|
||||
}
|
||||
|
||||
/// One change: namespace path, op, and the new path of a rename.
|
||||
@@ -219,7 +219,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn a_rename_is_one_change_and_temp_files_are_skipped() {
|
||||
let root = Path::new("/w/actor");
|
||||
let root = Path::new("/w");
|
||||
let now = Instant::now();
|
||||
let mut pending = HashMap::new();
|
||||
let ev = |kind, paths: &[&str]| {
|
||||
@@ -236,7 +236,8 @@ mod tests {
|
||||
ev(name(RenameMode::Both), &["/w/actor/a/public/x", "/w/actor/a/public/y"]),
|
||||
ev(EventKind::Create(notify::event::CreateKind::File), &["/w/actor/a/.sausage-tmp-host-1-actor.json"]),
|
||||
ev(name(RenameMode::Both), &["/w/actor/a/.sausage-tmp-host-1-actor.json", "/w/actor/a/actor.json"]),
|
||||
ev(EventKind::Create(notify::event::CreateKind::File), &["/w/.lock"]),
|
||||
ev(EventKind::Create(notify::event::CreateKind::File), &["/outside/.lock"]),
|
||||
ev(EventKind::Create(notify::event::CreateKind::File), &["/w/config/x"]),
|
||||
] {
|
||||
for (path, op, to) in changes(root, &e) {
|
||||
add(&mut pending, path, op, to, now);
|
||||
@@ -250,6 +251,7 @@ mod tests {
|
||||
vec![
|
||||
("/actor/a/actor.json".to_string(), "write", None),
|
||||
("/actor/a/public/x".to_string(), "rename", Some("/actor/a/public/y".to_string())),
|
||||
("/config/x".to_string(), "create", None),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
+13
-8
@@ -3,13 +3,14 @@
|
||||
//! watches the folder (`watch.rs`).
|
||||
//!
|
||||
//! ```text
|
||||
//! <world>/
|
||||
//! <world>/ mounted at /, readable and writable by every actor
|
||||
//! config/ global configuration by convention (no secrets)
|
||||
//! actor/<id>/
|
||||
//! actor.wasm the current version
|
||||
//! actor.json version, running, subscriptions, tapped, watches (broker)
|
||||
//! source/ source of the current version
|
||||
//! public/ written by the actor, read-only for others
|
||||
//! data/ state, writable only by the actor
|
||||
//! public/ published files, readable and writable by everyone
|
||||
//! data/ state, readable and writable by everyone
|
||||
//! stderr.log stderr of the actor and notes of the broker
|
||||
//! versions/<n>/ every version that was installed: actor.wasm, source/, meta.json
|
||||
//! ```
|
||||
@@ -131,6 +132,7 @@ impl World {
|
||||
for id in NATIVE {
|
||||
world.ensure_actor_dirs(id)?;
|
||||
}
|
||||
std::fs::create_dir_all(world.root.join("config"))?;
|
||||
Ok(world)
|
||||
}
|
||||
|
||||
@@ -191,8 +193,8 @@ impl World {
|
||||
|
||||
/// Map a path in an actor's namespace to a host path.
|
||||
///
|
||||
/// - `/actor/<id>/...` is the global filesystem.
|
||||
/// - `/public/...` and `/data/...` belong to `sender`.
|
||||
/// - Absolute paths are relative to the world root.
|
||||
/// - `/public/...` and `/data/...` alias `sender`'s folders.
|
||||
/// - Native senders (the console) can also give absolute host paths.
|
||||
pub fn resolve(&self, sender: &str, path: &str) -> Result<PathBuf> {
|
||||
let under = |base: PathBuf, rest: &str| -> Result<PathBuf> {
|
||||
@@ -221,13 +223,16 @@ impl World {
|
||||
if NATIVE.contains(&sender) && Path::new(path).is_absolute() {
|
||||
return Ok(PathBuf::from(path));
|
||||
}
|
||||
bail!("cannot resolve path {path} for {sender}: use /actor/<id>/..., /public/... or /data/...")
|
||||
if Path::new(path).is_absolute() {
|
||||
return under(self.root.clone(), path);
|
||||
}
|
||||
bail!("cannot resolve path {path} for {sender}: use an absolute world path")
|
||||
}
|
||||
|
||||
/// The path of a host file in the global filesystem, if it has one.
|
||||
pub fn global_path(&self, host: &Path) -> Option<String> {
|
||||
let rel = host.strip_prefix(self.actors_root()).ok()?;
|
||||
Some(format!("/actor/{}", rel.display()))
|
||||
let rel = host.strip_prefix(&self.root).ok()?;
|
||||
Some(format!("/{}", rel.display()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user