summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-01 15:52:44 +0100
committersalmonik <salmonik@tuta.io>2026-11-01 15:52:44 +0100
commit316a2a239446fe927e35b029b651cbeb08ff3f48 (patch)
treec16bcc30c25c9eaad99445d2a30b703f43c0d091
parentbins: read user args from argv[1..]; fix date year digit (diff)
downloadrs-dos-316a2a239446fe927e35b029b651cbeb08ff3f48.tar.gz
rs-dos-316a2a239446fe927e35b029b651cbeb08ff3f48.zip
fs: /bin alias for the system volume
ADR 0006 deferred root-path unification because it "needs VFS bind/alias machinery". Implement the /bin half: paths whose first component is `bin` are rewritten to the first mounted volume whose root has a \bin directory, so `ls /bin/cli`, `cat /bin/cli/x.efi` and a binary's own fs_resolve("/bin/cli/x.efi") work without knowing the volume UID. The alias is transparent — it creates no node, so `ls /` keeps its old listing and /bin simply does not exist when no system volume is mounted — and allocates nothing for non-matching paths. exec::resolve keeps its per-file volume scan (stricter for multiple volumes). Update ADR 0006 to record the decision and what remains deferred (\etc and friends at the root).
-rw-r--r--docs/adr/0006-executable-binaries-and-bin-tree.md12
-rw-r--r--rsnix/src/fs.rs41
2 files changed, 52 insertions, 1 deletions
diff --git a/docs/adr/0006-executable-binaries-and-bin-tree.md b/docs/adr/0006-executable-binaries-and-bin-tree.md
index 17109c4..f3de95f 100644
--- a/docs/adr/0006-executable-binaries-and-bin-tree.md
+++ b/docs/adr/0006-executable-binaries-and-bin-tree.md
@@ -48,6 +48,14 @@ and so a launcher can enumerate launchable apps by listing a directory.
48- **Resolution rule.** For name N: run `\bin\<kind>\N` from the first 48- **Resolution rule.** For name N: run `\bin\<kind>\N` from the first
49 mounted volume that has it; else the built-in N; else "unknown 49 mounted volume that has it; else the built-in N; else "unknown
50 command". No volume is privileged; no boot-volume identity exists. 50 command". No volume is privileged; no boot-volume identity exists.
51- **`/bin` is a VFS path alias.** User paths whose first component is
52 `bin` are rewritten (in `fs::resolve`) to the first mounted volume
53 whose root has `\bin` — so `ls /bin/cli`, `cat /bin/cli/x.efi` and a
54 binary's own `fs_resolve("/bin/cli/x.efi")` work without knowing the
55 volume UID. The alias is transparent: it creates no node (`ls /` does
56 not list `bin`), and without a system volume mounted `/bin` does not
57 exist. It covers `/bin` only — the rest of root-path unification
58 stays deferred, but the bind/alias machinery it needed now exists.
51- **Kind = launch surface.** `/bin/cli` holds programs that run inside a 59- **Kind = launch surface.** `/bin/cli` holds programs that run inside a
52 terminal session; `/bin/gui` holds programs launchable with no terminal 60 terminal session; `/bin/gui` holds programs launchable with no terminal
53 (they own their windows). A future launcher enumerates `/bin/gui`. 61 (they own their windows). A future launcher enumerates `/bin/gui`.
@@ -91,7 +99,9 @@ and so a launcher can enumerate launchable apps by listing a directory.
91 through FAT write-back persists across reboots, and the image doubles 99 through FAT write-back persists across reboots, and the image doubles
92 as the distribution artifact. 100 as the distribution artifact.
93- **Boot-volume identity / root-path unification** (`\etc` visible at 101- **Boot-volume identity / root-path unification** (`\etc` visible at
94 `/etc`) — deferred; needs VFS bind/alias machinery. 102 `/etc`) — deferred; needed VFS bind/alias machinery. The `/bin` half
103 landed as a path alias (see the resolution-rule section); `\etc`,
104 `\bzImage` etc. at the root stay deferred.
95- **A third "TUI" binary kind for rsdosedit** — rejected; `edit` is 105- **A third "TUI" binary kind for rsdosedit** — rejected; `edit` is
96 classified CLI (it lives in a terminal session) and migrates only after 106 classified CLI (it lives in a terminal session) and migrates only after
97 the CLI ABI grows raw-mode surface access. Until then it stays a 107 the CLI ABI grows raw-mode surface access. Until then it stays a
diff --git a/rsnix/src/fs.rs b/rsnix/src/fs.rs
index 3585c00..a3bae6d 100644
--- a/rsnix/src/fs.rs
+++ b/rsnix/src/fs.rs
@@ -274,12 +274,53 @@ fn lookup(dir: usize, name: &str) -> Option<usize> {
274 None 274 None
275} 275}
276 276
277/// `/bin` alias for the system volume (ADR 0006): the binaries actually
278/// live on the FAT volume at `\<vol>\bin\<kind>\`, but `/bin` is the
279/// stable user-facing path. When the first path component is `bin`, the
280/// path is rewritten to the first mounted volume whose root has a `\bin`
281/// directory — so `ls /bin/cli` and a binary's own
282/// `fs_resolve("/bin/cli/x.efi")` work without knowing the volume UID.
283/// Every other path returns `None` here without allocating. (The alias is
284/// transparent: `ls /` does not list `bin`, and with no system volume
285/// mounted `/bin` simply does not exist.)
286fn alias_bin(path: &str) -> Option<String> {
287 let p = path.trim_start_matches('/');
288 let p = p.trim_start_matches("./"); // tolerate a leading ./ prefix
289 let first = match p.find('/') {
290 Some(i) => &p[..i],
291 None => p,
292 };
293 if first != "bin" {
294 return None;
295 }
296 for vol in 0..crate::fat::volume_count() {
297 if !crate::fat::is_mounted(vol) {
298 continue;
299 }
300 if crate::fat::find(vol, crate::fat::root_cluster(vol), "bin").is_err() {
301 continue; // volume has no \bin root — not the system volume
302 }
303 let uid = String::from(crate::fat::uid(vol));
304 return Some(alloc::format!("/disk/{}/{}", uid, p));
305 }
306 None
307}
308
277/// Resolve an absolute path (supports `.` and `..`). Paths under 309/// Resolve an absolute path (supports `.` and `..`). Paths under
278/// `/disk` walk the mounted FAT32 volume through ephemeral Disk nodes. 310/// `/disk` walk the mounted FAT32 volume through ephemeral Disk nodes.
311/// A leading `bin` component goes through the `/bin` alias first.
279pub fn resolve(path: &str) -> Option<usize> { 312pub fn resolve(path: &str) -> Option<usize> {
280 if nodes().is_empty() { 313 if nodes().is_empty() {
281 return None; 314 return None;
282 } 315 }
316 let aliased;
317 let path = match alias_bin(path) {
318 Some(p) => {
319 aliased = p;
320 aliased.as_str()
321 }
322 None => path,
323 };
283 let mut cur = 0usize; // root 324 let mut cur = 0usize; // root
284 // (vol, cluster, parent, name) of directories descended into under /disk. 325 // (vol, cluster, parent, name) of directories descended into under /disk.
285 let mut dstack: Vec<(usize, u32, u32, String)> = Vec::new(); 326 let mut dstack: Vec<(usize, u32, u32, String)> = Vec::new();