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:
@@ -161,7 +161,7 @@ test "append/read round trip" {
|
||||
defer std.testing.allocator.destroy(ring);
|
||||
ring.* = .{};
|
||||
|
||||
_ = ring.append(7, "/system/services/fat", .info, 123, "mounted /mnt/usb", false);
|
||||
_ = ring.append(7, "/system/services/fat", .info, 123, "mounted /volumes/usb", false);
|
||||
_ = ring.append(0, "kernel", .raw, 456, "wall clock online", false);
|
||||
|
||||
const first = parseAt(ring, ring.tail);
|
||||
@@ -169,7 +169,7 @@ test "append/read round trip" {
|
||||
try std.testing.expectEqual(abi.KlogLevel.info, first.header.level);
|
||||
try std.testing.expectEqual(@as(u64, 123), first.header.timestamp_ns);
|
||||
try std.testing.expectEqualStrings("/system/services/fat", first.nameSlice());
|
||||
try std.testing.expectEqualStrings("mounted /mnt/usb", first.messageSlice());
|
||||
try std.testing.expectEqualStrings("mounted /volumes/usb", first.messageSlice());
|
||||
|
||||
const second = parseAt(ring, first.next(ring.tail));
|
||||
try std.testing.expectEqual(@as(u32, 0), second.header.pid);
|
||||
|
||||
+15
-11
@@ -1970,7 +1970,7 @@ fn initTest(boot_information: *const BootInformation) void {
|
||||
check("init loaded and spawned as a process", spawned);
|
||||
|
||||
// Wait (real time) until the LAST write is a heartbeat — proving init got
|
||||
// through its boot chatter (heap ok, the /etc/init.csv lookup) and settled
|
||||
// through its boot chatter (heap ok, the /system/configuration/init.csv lookup) and settled
|
||||
// into its beat-and-sleep loop (~1 s between beats). Waiting on the text
|
||||
// rather than a raw write count: the boot chatter alone satisfies a count,
|
||||
// which is exactly the too-early check that used to fail here.
|
||||
@@ -2220,7 +2220,7 @@ fn vfsClientDeathTest(boot_information: *const BootInformation) void {
|
||||
};
|
||||
|
||||
process.write_count = 0;
|
||||
// The full tree: the storage chain must come up for /mnt/usb to exist —
|
||||
// The full tree: the storage chain must come up for /volumes/usb to exist —
|
||||
// the fat server (not a router) now owns client file state and its sweep.
|
||||
process.setInitialRamdisk(image);
|
||||
const init_ok = if (process.spawnBundled("/system/services/init")) true else |_| false;
|
||||
@@ -2606,7 +2606,7 @@ fn usbStorageTest(boot_information: *const BootInformation) void {
|
||||
|
||||
/// The FAT mount chain: boot the full tree (init spawns the fat server, which
|
||||
/// brings up the USB storage chain, mounts the FAT volume, and mounts itself into
|
||||
/// the VFS at /mnt/usb), then spawn a fat-test client that lists and reads through
|
||||
/// the VFS at /volumes/usb), then spawn a fat-test client that lists and reads through
|
||||
/// the mount. The harness attaches a usb-storage device; the expect regex requires
|
||||
/// the fat mount and the client's success.
|
||||
fn fatMountTest(boot_information: *const BootInformation) void {
|
||||
@@ -2801,11 +2801,13 @@ fn initialRamdiskTest(boot_information: *const BootInformation) void {
|
||||
var i: u32 = 0;
|
||||
while (i < rd.count) : (i += 1) {
|
||||
const item = rd.entry(i) orelse continue;
|
||||
// The FHS boot tree ferries data files too (/etc/devices.csv,
|
||||
// /etc/init.csv — served read-only by the kernel VFS, never spawned);
|
||||
// only the /system and /test trees hold programs, so only those count
|
||||
// toward the spawn-everything sweep.
|
||||
const is_program = std.mem.startsWith(u8, item.name, "/system/") or
|
||||
// The boot tree ferries data files too (/system/configuration/devices.csv,
|
||||
// /system/configuration/init.csv — served read-only by the kernel VFS,
|
||||
// never spawned); only the /system and /test trees hold programs, and
|
||||
// /system/configuration holds none, so only the rest counts toward the
|
||||
// spawn-everything sweep.
|
||||
const is_program = (std.mem.startsWith(u8, item.name, "/system/") and
|
||||
!std.mem.startsWith(u8, item.name, "/system/configuration/")) or
|
||||
std.mem.startsWith(u8, item.name, "/test/");
|
||||
if (!is_program) continue;
|
||||
programs += 1;
|
||||
@@ -3267,21 +3269,23 @@ fn kernelVfsTest(boot_information: *const BootInformation) void {
|
||||
check("its first bytes are an ELF magic", n == 4 and header[0] == 0x7f and header[1] == 'E' and header[2] == 'L' and header[3] == 'F');
|
||||
}
|
||||
|
||||
// Directories resolve and enumerate: /system lists services/drivers.
|
||||
// Directories resolve and enumerate: /system lists services/drivers/
|
||||
// configuration (the CSV data files ride the same initrd tree).
|
||||
const root_directory = kernel_vfs.resolvePath("/system", false);
|
||||
check("/system resolves to a directory node", root_directory == .kernel_node);
|
||||
var saw_services = false;
|
||||
var saw_drivers = false;
|
||||
var saw_configuration = false;
|
||||
var saw_stray_in_root = false;
|
||||
var saw_files_in_services = false;
|
||||
if (root_directory == .kernel_node) {
|
||||
var cursor: u64 = 0;
|
||||
var name: [64]u8 = undefined;
|
||||
while (kernel_vfs.nodeReaddir(root_directory.kernel_node, cursor, &name)) |entry| : (cursor += 1) {
|
||||
if (eql(name[0..entry.name_len], "services")) saw_services = true else if (eql(name[0..entry.name_len], "drivers")) saw_drivers = true else saw_stray_in_root = true;
|
||||
if (eql(name[0..entry.name_len], "services")) saw_services = true else if (eql(name[0..entry.name_len], "drivers")) saw_drivers = true else if (eql(name[0..entry.name_len], "configuration")) saw_configuration = true else saw_stray_in_root = true;
|
||||
}
|
||||
}
|
||||
check("readdir /system yields services and drivers", saw_services and saw_drivers);
|
||||
check("readdir /system yields services, drivers, configuration", saw_services and saw_drivers and saw_configuration);
|
||||
check("readdir /system yields nothing else (no /test leakage)", !saw_stray_in_root);
|
||||
const services = kernel_vfs.resolvePath("/system/services", false);
|
||||
if (services == .kernel_node) {
|
||||
|
||||
+32
-14
@@ -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" {
|
||||
|
||||
Reference in New Issue
Block a user