diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-01 15:52:44 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-01 15:52:44 +0100 |
| commit | 316a2a239446fe927e35b029b651cbeb08ff3f48 (patch) | |
| tree | c16bcc30c25c9eaad99445d2a30b703f43c0d091 | |
| parent | bins: read user args from argv[1..]; fix date year digit (diff) | |
| download | rs-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.md | 12 | ||||
| -rw-r--r-- | rsnix/src/fs.rs | 41 |
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.) | ||
| 286 | fn 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. | ||
| 279 | pub fn resolve(path: &str) -> Option<usize> { | 312 | pub 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(); |
