build: the unix paths retire — configuration, logs, and volumes move into the danos tree

/etc/init.csv and /etc/devices.csv become /system/configuration/*.csv (the
repo's etc/ moves to system/configuration/, mirroring the runtime tree),
/var/log becomes /system/logs, and /mnt/usb becomes /volumes/usb. The
kernel VFS gains a carve-out so FAT may serve exactly /system/configuration
and /system/logs beneath the initrd-backed /system while /system and /test
themselves stay unshadowable; FAT's single /var mount splits into those two
rewritten mounts. The kvfs readdir check learns /system's third child and
the ramdisk spawn sweep skips the configuration tree.

Suite 106/106.
This commit is contained in:
Daniel Samson
2026-07-31 19:41:35 +01:00
parent e4da4e0610
commit c4f16a5448
31 changed files with 161 additions and 128 deletions
+32 -14
View File
@@ -7,7 +7,8 @@
//! mount (the initrd trees at /system and /test, the scratch ram nodes) resolves to a
//! stateless node TOKEN served directly by `fs_node` (read/status/readdir
//! with copy-out). A path under a USERSPACE mount (the fat server at
//! /mnt/usb and /var) resolves to the backend's ENDPOINT: the kernel
//! /volumes/usb, /system/configuration, and /system/logs) resolves to the
//! backend's ENDPOINT: the kernel
//! installs a (deduplicated) handle in the caller's table, rewrites the
//! path mount-relative, and the caller speaks the unchanged vfs-protocol
//! to the backend over the ordinary ipc_call rendezvous. The kernel never
@@ -21,9 +22,10 @@
//! Mounting is `fs_mount(prefix, backend_handle, rewrite)`: possession of the
//! backend endpoint handle is the capability, exactly the trust of the old
//! userspace router's op-6 cap-pass. An optional REWRITE prefix maps the mount
//! into the backend's namespace ("/var" -> fat's "/var" subtree while the same
//! backend also serves "/mnt/usb" from its root), so FHS paths stay decoupled
//! from which volume happens to carry them.
//! into the backend's namespace ("/system/logs" -> the boot volume's
//! identically-named subtree while the same backend also serves "/volumes/usb"
//! from its root), so hierarchy paths stay decoupled from which volume happens
//! to carry them.
const std = @import("std");
const abi = @import("abi");
@@ -99,7 +101,7 @@ var directory_count: usize = 0;
/// If `path` lies under `mount_prefix` — equal to it, or the prefix followed by
/// a path separator — return the path relative to the mount ("/" for an exact
/// match, otherwise the tail beginning with '/'). Null when not under the
/// mount, so "/mnt/usb" never captures "/mnt/usbextra".
/// mount, so "/volumes/usb" never captures "/volumes/usbextra".
pub fn underMount(path: []const u8, mount_prefix: []const u8) ?[]const u8 {
if (path.len < mount_prefix.len) return null;
if (!std.mem.eql(u8, path[0..mount_prefix.len], mount_prefix)) return null;
@@ -326,14 +328,30 @@ pub fn nodeReaddir(node_token: u64, cursor: u64, name_out: []u8) ?struct { heade
// --- mount/unmount (syscall bodies; caller resolved the handle) --------------
/// The writable subtrees a backend may mount beneath an initrd tree — exactly
/// these two, nothing else. Longest-prefix resolution then routes them to the
/// volume while every other /system and /test path stays initrd-served, so no
/// bundled binary can ever be shadowed.
const initrd_carve_outs = [_][]const u8{ "/system/configuration", "/system/logs" };
fn isInitrdCarveOut(prefix: []const u8) bool {
for (initrd_carve_outs) |allowed| {
if (std.mem.eql(u8, prefix, allowed)) return true;
}
return false;
}
/// Mount `backend` at `prefix` with an optional backend-side `rewrite` prefix.
/// The endpoint reference is taken by the caller (process.zig bumps it); refuses
/// shadowing or replacing the initrd trees (/system, /test).
/// shadowing or replacing the initrd trees (/system, /test) — except the two
/// carve-outs in `initrd_carve_outs`, the writable configuration/log subtrees.
pub fn mountBackend(prefix: []const u8, backend: *ipc.Endpoint, rewrite: []const u8) bool {
if (!isAbsolute(prefix) or prefix.len < 2 or prefix.len > maximum_prefix) return false;
if (rewrite.len > maximum_rewrite) return false;
for (&mounts) |*m| { // the initrd trees are not shadowable
if (m.used and m.kind == .kernel_initrd and underMount(prefix, m.prefixSlice()) != null) return false;
for (&mounts) |*m| { // the initrd trees are not shadowable (carve-outs aside)
if (m.used and m.kind == .kernel_initrd and underMount(prefix, m.prefixSlice()) != null) {
if (!isInitrdCarveOut(prefix)) return false;
}
}
installMount(prefix, .backend, backend, rewrite);
return true;
@@ -353,12 +371,12 @@ pub fn unmount(prefix: []const u8) bool {
// --- tests (host) ------------------------------------------------------------
test "underMount matches only at path boundaries" {
try std.testing.expectEqualStrings("/", underMount("/mnt/usb", "/mnt/usb").?);
try std.testing.expectEqualStrings("/system/kernel", underMount("/mnt/usb/system/kernel", "/mnt/usb").?);
try std.testing.expect(underMount("/mnt/usbextra", "/mnt/usb") == null);
try std.testing.expect(underMount("/mnt", "/mnt/usb") == null);
try std.testing.expect(underMount("/other", "/mnt/usb") == null);
try std.testing.expect(underMount("greeting", "/mnt/usb") == null);
try std.testing.expectEqualStrings("/", underMount("/volumes/usb", "/volumes/usb").?);
try std.testing.expectEqualStrings("/system/kernel", underMount("/volumes/usb/system/kernel", "/volumes/usb").?);
try std.testing.expect(underMount("/volumes/usbextra", "/volumes/usb") == null);
try std.testing.expect(underMount("/volumes", "/volumes/usb") == null);
try std.testing.expect(underMount("/other", "/volumes/usb") == null);
try std.testing.expect(underMount("greeting", "/volumes/usb") == null);
}
test "parentOf walks toward the root" {