From c4f16a544873e724882c226669fa538885d3401e Mon Sep 17 00:00:00 2001 From: Daniel Samson <12231216+daniel-samson@users.noreply.github.com> Date: Fri, 31 Jul 2026 19:41:35 +0100 Subject: [PATCH] =?UTF-8?q?build:=20the=20unix=20paths=20retire=20?= =?UTF-8?q?=E2=80=94=20configuration,=20logs,=20and=20volumes=20move=20int?= =?UTF-8?q?o=20the=20danos=20tree?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /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. --- build.zig | 22 +++++---- build/images.zig | 2 +- build/qemu.zig | 4 +- docs/security-track-plan.md | 2 +- library/csv/build.zig | 2 +- library/csv/csv.zig | 4 +- library/device/build.zig | 2 +- library/device/build.zig.zon | 2 +- library/device/model/device-abi.zig | 2 +- library/device/registry/device-registry.zig | 6 +-- library/kernel/file-system.zig | 7 +-- .../device-manager-protocol.zig | 4 +- system/abi.zig | 2 +- {etc => system/configuration}/devices.csv | 2 +- .../configuration}/init-diagnose.csv | 7 +-- {etc => system/configuration}/init.csv | 4 +- system/drivers/pci-bus/pci-bus.zig | 4 +- system/drivers/usb-xhci-bus/usb-xhci-bus.zig | 2 +- system/drivers/virtio-gpu/virtio-gpu.zig | 2 +- system/kernel/log-ring.zig | 4 +- system/kernel/tests.zig | 26 ++++++----- system/kernel/vfs.zig | 46 +++++++++++++------ system/services/acpi/acpi.zig | 2 +- .../device-manager/device-manager.zig | 18 ++++---- system/services/fat/engine.zig | 2 +- system/services/fat/fat.zig | 27 +++++++---- system/services/init/init.zig | 20 ++++---- system/services/logger/logger.zig | 14 +++--- test/qemu_test.py | 12 ++--- test/system/services/fat-test/fat-test.zig | 34 +++++++------- test/system/services/vfs-test/vfs-test.zig | 2 +- 31 files changed, 161 insertions(+), 128 deletions(-) rename {etc => system/configuration}/devices.csv (96%) rename {etc => system/configuration}/init-diagnose.csv (64%) rename {etc => system/configuration}/init.csv (85%) diff --git a/build.zig b/build.zig index be36e5e..5a3899a 100644 --- a/build.zig +++ b/build.zig @@ -115,7 +115,8 @@ fn driverArtifact(comptime package: []const u8, comptime artifact: []const u8) S /// The production ship table — what a plain `zig build` image contains, /// beyond the specials the build fn adds around it (init, discovery, the -/// /etc data files; the /test fixtures join only under -Dtest-case). +/// /system/configuration data files; the /test fixtures join only under +/// -Dtest-case). /// Selecting what goes into a build = selecting rows: a package in no row is /// not just unshipped, its build file is never even loaded /// (docs/build-packages-plan.md). @@ -265,9 +266,9 @@ pub fn build(b: *std.Build) void { // (receives the root's -Dserial as a dependency option — its liveness // heartbeat is a serial/test-build diagnostic the QEMU harness asserts // on; a flashable image leaves it out), discovery (the -Ddiscovery pick), - // and the /etc data files. Each binary builds itself against the domain - // packages via build-support's shared recipe; the root just takes - // artifacts (docs/build-packages-plan.md). + // and the /system/configuration data files. Each binary builds itself + // against the domain packages via build-support's shared recipe; the root + // just takes artifacts (docs/build-packages-plan.md). var bundled_list: std.ArrayListUnmanaged(images.BundledBinary) = .empty; bundled_list.append(b.allocator, .{ .path = "system/services/init", @@ -300,15 +301,16 @@ pub fn build(b: *std.Build) void { .binary = b.dependency(row.package, .{}).artifact(row.artifact).getEmittedBin(), }) catch @panic("OOM"); } - // Data files, not binaries: packing them under /etc makes the kernel - // auto-mount /etc as a read-only initrd tree (system/kernel/vfs.zig + // Data files, not binaries: packing them under /system/configuration rides + // the kernel's read-only initrd mount of /system (system/kernel/vfs.zig // setInitialRamdisk) — the device manager reads its registry and init its // service list with no filesystem service running. -Ddiagnose selects the // init.csv variant that omits the display stack (so the kernel's boot - // transcript stays on screen); both bundle at the same /etc/init.csv path. - const init_csv_source = if (diagnose) "etc/init-diagnose.csv" else "etc/init.csv"; - bundled_list.append(b.allocator, .{ .path = "etc/devices.csv", .binary = b.path("etc/devices.csv") }) catch @panic("OOM"); - bundled_list.append(b.allocator, .{ .path = "etc/init.csv", .binary = b.path(init_csv_source) }) catch @panic("OOM"); + // transcript stays on screen); both bundle at the same + // /system/configuration/init.csv path. + const init_csv_source = if (diagnose) "system/configuration/init-diagnose.csv" else "system/configuration/init.csv"; + bundled_list.append(b.allocator, .{ .path = "system/configuration/devices.csv", .binary = b.path("system/configuration/devices.csv") }) catch @panic("OOM"); + bundled_list.append(b.allocator, .{ .path = "system/configuration/init.csv", .binary = b.path(init_csv_source) }) catch @panic("OOM"); // A no-option build assumes neither -Dtest-case nor -Ddiagnose: it ships the // production set only. The userspace test fixtures under /test join in only // for a test build — which the QEMU harness signals by passing diff --git a/build/images.zig b/build/images.zig index 6300aa2..f0aec5e 100644 --- a/build/images.zig +++ b/build/images.zig @@ -83,7 +83,7 @@ pub fn addImageSteps(b: *std.Build, options: Options) std.Build.LazyPath { // holding the EFI stub, the kernel, and the whole /system tree of user // binaries at their FHS paths. QEMU presents this image as a USB mass-storage // device the guest boots from (see run-x86-64 and the test harness), and the - // danos fat driver mounts the same image at /mnt/usb. + // danos fat driver mounts the same image at /volumes/usb. const fat_image = addBootImage(b, options.kernel.getEmittedBin(), options.efi.getEmittedBin(), manifest_file, capsule_img, options.bundled); const fat_image_install = b.addInstallFile(fat_image, "danos-usb.img"); b.getInstallStep().dependOn(&fat_image_install.step); diff --git a/build/qemu.zig b/build/qemu.zig index c61e34e..dfff813 100644 --- a/build/qemu.zig +++ b/build/qemu.zig @@ -42,8 +42,8 @@ pub fn addRunSteps(b: *std.Build, fat_image_serial: std.Build.LazyPath) void { const vars_out = vars_copy.addOutputFileArg("OVMF_VARS.4m.fd"); // Capture the guest's serial0 (danos's machine-readable log) to the qemu-test - // scratch area — a dev/host artifact, kept out of the FHS boot volume we mount. - // (/var/log/system is reserved for the kernel's own logging system later.) One + // scratch area — a dev/host artifact, kept out of the boot volume we mount. + // (/system/logs on the volume belongs to the guest's own logger.) One // timestamped file per run. const log_dir = b.fmt("{s}/qemu-test", .{b.install_path}); const make_log_dir = b.addSystemCommand(&.{ "mkdir", "-p", log_dir }); diff --git a/docs/security-track-plan.md b/docs/security-track-plan.md index 16ef192..5173b02 100644 --- a/docs/security-track-plan.md +++ b/docs/security-track-plan.md @@ -32,7 +32,7 @@ the logging/USB-lifecycle track). ## Status - [x] **Phase 0** — baseline: suite green on `main` (106/106, 2026-07-31; `zig build` + `zig build test` clean at 9a32380), plan committed -- [ ] **PM** — path-migration flag-day (`/etc`→`/system/configuration`, `/var/log`→`/system/logs`, `/mnt/usb`→`/volumes/usb`) +- [x] **PM** — path-migration flag-day (`/etc`→`/system/configuration`, `/var/log`→`/system/logs`, `/mnt/usb`→`/volumes/usb`; vfs carve-out for the two writable `/system` subtrees, FAT's `/var` mount split in two; suite 106/106) - [ ] **H1** — the `user-memory` module; nine stragglers converted; leaf U/S+W checks - [ ] **merge** group 1 → main, push - [ ] **P1** — envelope module + `Define`; vfs `NodeKind.protocol` + open-reply-capability; client `Channel` diff --git a/library/csv/build.zig b/library/csv/build.zig index f6ae051..b98fcb5 100644 --- a/library/csv/build.zig +++ b/library/csv/build.zig @@ -1,5 +1,5 @@ //! The "csv" library domain: shared CSV helpers (comment stripping, field -//! iteration) for the /etc/*.csv config files — the device registry and the +//! iteration) for the /system/configuration/*.csv config files — the device registry and the //! init service list both parse them. const std = @import("std"); diff --git a/library/csv/csv.zig b/library/csv/csv.zig index e327183..90c9609 100644 --- a/library/csv/csv.zig +++ b/library/csv/csv.zig @@ -1,5 +1,5 @@ -//! Minimal CSV helpers shared by the `/etc/*.csv` config files — the device -//! registry (`/etc/devices.csv`) and the init service list (`/etc/init.csv`). +//! Minimal CSV helpers shared by the `/system/configuration/*.csv` config files — the device +//! registry (`/system/configuration/devices.csv`) and the init service list (`/system/configuration/init.csv`). //! Freestanding, no allocator: returned fields are slices into the source line, //! so the source must outlive them. `#` starts a comment (whole-line or trailing); //! whitespace around a field is trimmed, so columns may be padded for alignment. diff --git a/library/device/build.zig b/library/device/build.zig index 12580f6..6107331 100644 --- a/library/device/build.zig +++ b/library/device/build.zig @@ -97,7 +97,7 @@ pub fn build(b: *std.Build) void { .{ .name = "block-protocol", .module = protocol.module("block-protocol") }, }, }); - // The device registry: parse /etc/devices.csv into match rules and bind a + // The device registry: parse /system/configuration/devices.csv into match rules and bind a // reported device to a driver. Pure logic (no hardware, no syscalls), so it // unit-tests on the host; the device manager imports it. _ = b.addModule("device-registry", .{ diff --git a/library/device/build.zig.zon b/library/device/build.zig.zon index 05b0797..f6bde5b 100644 --- a/library/device/build.zig.zon +++ b/library/device/build.zig.zon @@ -8,7 +8,7 @@ .kernel = .{ .path = "../kernel" }, // driver speaks device-manager-protocol; block/usb their transfer protocols. .protocol = .{ .path = "../protocol" }, - // device-registry parses /etc/devices.csv with the shared csv helpers. + // device-registry parses /system/configuration/devices.csv with the shared csv helpers. .csv = .{ .path = "../csv" }, }, .paths = .{""}, diff --git a/library/device/model/device-abi.zig b/library/device/model/device-abi.zig index 355da1f..1017a9b 100644 --- a/library/device/model/device-abi.zig +++ b/library/device/model/device-abi.zig @@ -126,7 +126,7 @@ pub const DeviceDescriptor = extern struct { // names with the pci-class module. pci_class: u64, // Numeric identity beyond the class triple, mirrored in the bus report's - // ChildAdded so /etc/devices.csv can bind on it: `vendor`/`device` are the PCI + // ChildAdded so /system/configuration/devices.csv can bind on it: `vendor`/`device` are the PCI // vendor/device (or USB idVendor/idProduct), `subsystem` is the PCI subsystem id // packed `(subsystem_vendor << 16) | subsystem_device`. Zero where the bus has no // such concept. Defaulted so existing descriptor literals keep compiling and lay diff --git a/library/device/registry/device-registry.zig b/library/device/registry/device-registry.zig index 26ac4fd..3c8d4ed 100644 --- a/library/device/registry/device-registry.zig +++ b/library/device/registry/device-registry.zig @@ -1,4 +1,4 @@ -//! The device registry: parse `/etc/devices.csv` into match rules and bind a +//! The device registry: parse `/system/configuration/devices.csv` into match rules and bind a //! reported device to a driver. This is the data-driven replacement for the //! device manager's three hand-written `switch` tables (`pciDriverForIdentity`, //! `hidDriverFor`, `usbDriverForIdentity`); the registry is now **authoritative** @@ -11,7 +11,7 @@ //! That keeps this module freestanding and unit-testable with plain `zig test`. //! //! The file format (docs/device-driver-development/device-manager.md, and the -//! `/etc/devices.csv` header itself): one rule per line, nine comma-separated +//! `/system/configuration/devices.csv` header itself): one rule per line, nine comma-separated //! fields, `#` starts a comment (whole-line or trailing), blank lines ignored. //! //! bus, base, class, prog_if, vendor, device, subsystem, hid, driver @@ -226,7 +226,7 @@ fn parseLine(line: []const u8) Line { } }; } -/// Parse a whole `/etc/devices.csv` into `out_rules`. The string fields of the +/// Parse a whole `/system/configuration/devices.csv` into `out_rules`. The string fields of the /// returned rules point into `source`, which must outlive them. pub fn parse(source: []const u8, out_rules: []Rule) ParseResult { var result: ParseResult = .{ .count = 0, .malformed = 0, .truncated = false }; diff --git a/library/kernel/file-system.zig b/library/kernel/file-system.zig index 0b4e79e..5f243ae 100644 --- a/library/kernel/file-system.zig +++ b/library/kernel/file-system.zig @@ -305,7 +305,7 @@ pub fn makePath(path: []const u8) bool { while (end < path.len and path[end] != '/') end += 1; const prefix = path[0..end]; if (prefix.len == 0 or (prefix.len == 1 and prefix[0] == '/')) continue; - // Best-effort per prefix: components at or above a mount point ("/mnt") + // Best-effort per prefix: components at or above a mount point ("/volumes") // are router names, not filesystem nodes — they neither exist as nodes // nor accept mkdir, and that is fine. Only the final verdict counts. if (!exists(prefix)) _ = makeDirectory(prefix); @@ -348,8 +348,9 @@ pub fn mount(target: []const u8, backend: ipc.Handle) bool { } /// As `mount`, with a backend-side rewrite prefix: a path under `target` reaches -/// the backend as `rewrite` + the mount-relative tail. How one volume serves two -/// mounts ("/mnt/usb" from its root, "/var" from its /var subtree). +/// the backend as `rewrite` + the mount-relative tail. How one volume serves +/// several mounts ("/volumes/usb" from its root, "/system/logs" from its +/// /system/logs subtree). pub fn mountRewritten(target: []const u8, backend: ipc.Handle, rewrite: []const u8) bool { return fsMount(target, backend, rewrite); } diff --git a/library/protocol/device-manager/device-manager-protocol.zig b/library/protocol/device-manager/device-manager-protocol.zig index 76900fc..a7228e8 100644 --- a/library/protocol/device-manager/device-manager-protocol.zig +++ b/library/protocol/device-manager/device-manager-protocol.zig @@ -12,7 +12,7 @@ pub const version: u16 = 1; /// Which bus a `child_added` came from — stated by the reporting bus driver so -/// the manager's /etc/devices.csv matcher knows how to read the report's identity +/// the manager's /system/configuration/devices.csv matcher knows how to read the report's identity /// (a PCI class triple vs a USB class triple are the same 24 bits but different /// namespaces) and which `bus` column a rule must name to bind it. `unknown` is /// the zero default, so an un-upgraded reporter fails to match rather than @@ -96,7 +96,7 @@ pub const ChildAdded = extern struct { /// for an unregistered leaf (a USB port before the descriptor track). device_id: u64 = no_device, /// The vendor id (PCI vendor / USB idVendor), or 0 when the bus has no such - /// concept (ACPI). Carried so the manager's /etc/devices.csv matcher can bind + /// concept (ACPI). Carried so the manager's /system/configuration/devices.csv matcher can bind /// on vendor — a level the bus-native `identity` (a class triple) cannot express. vendor: u16 = 0, /// The device id (PCI device / USB idProduct), or 0. The most specific numeric diff --git a/system/abi.zig b/system/abi.zig index dff3018..b623c11 100644 --- a/system/abi.zig +++ b/system/abi.zig @@ -295,7 +295,7 @@ pub const ServiceId = enum(u32) { power = 5, // system power: events (button, lid, battery) + shutdown (docs/power.md; domain-named per docs/discovery.md — the acpi service registers it on x86, a PSCI service will on ARM) usb_bus = 6, // the xHCI host-controller driver's transfer endpoint; USB class drivers look it up and `callCap`-open their device to get a private per-device transfer channel (docs/driver-model.md) block = 7, // a block-device driver (USB mass storage today): read/write of fixed-size blocks, the storage a filesystem sits on - fat = 8, // the FAT filesystem server; the VFS mounts it and forwards paths under its mount point (/mnt/usb) to it + fat = 8, // the FAT filesystem server; the VFS mounts it and forwards paths under its mount point (/volumes/usb) to it display = 9, // the display service: owns the framebuffer, composites a layer stack, presents frames (docs/display.md) shared_memory_test = 10, // the shared-memory test server (V2): a client passes it a shared-memory capability, it maps + verifies (docs/display-v2.md) scanout = 11, // a native scanout driver (virtio-gpu): the compositor finds it here to upgrade off the GOP framebuffer (docs/display-v2.md) diff --git a/etc/devices.csv b/system/configuration/devices.csv similarity index 96% rename from etc/devices.csv rename to system/configuration/devices.csv index 44afff1..b70a4b4 100644 --- a/etc/devices.csv +++ b/system/configuration/devices.csv @@ -1,4 +1,4 @@ -# /etc/devices.csv — the device→driver registry. +# /system/configuration/devices.csv — the device→driver registry. # # The device manager reads this at boot and binds each device a bus driver # reports to the driver named here. It is AUTHORITATIVE: a device that no row diff --git a/etc/init-diagnose.csv b/system/configuration/init-diagnose.csv similarity index 64% rename from etc/init-diagnose.csv rename to system/configuration/init-diagnose.csv index 55f387e..4e1e627 100644 --- a/etc/init-diagnose.csv +++ b/system/configuration/init-diagnose.csv @@ -1,9 +1,10 @@ -# /etc/init.csv — diagnose variant (-Ddiagnose), bundled at /etc/init.csv. +# /system/configuration/init.csv — diagnose variant (-Ddiagnose), bundled at +# /system/configuration/init.csv. # # The display stack (display, display-demo) is omitted so the kernel's timestamped # on-screen boot transcript is never suppressed — the bring-up timeline (USB, -# storage, logger) stays readable on real hardware with no serial. See etc/init.csv -# for the format; this file must otherwise track it. +# storage, logger) stays readable on real hardware with no serial. See +# system/configuration/init.csv for the format; this file must otherwise track it. # # service args... /system/services/input diff --git a/etc/init.csv b/system/configuration/init.csv similarity index 85% rename from etc/init.csv rename to system/configuration/init.csv index 4bea925..2609284 100644 --- a/etc/init.csv +++ b/system/configuration/init.csv @@ -1,4 +1,4 @@ -# /etc/init.csv — the services init (PID 1) starts at boot, in order. +# /system/configuration/init.csv — the services init (PID 1) starts at boot, in order. # # init reads this at startup and spawns each service supervised (restarting it on # a crash, up to a cap). Startup order is top->bottom; shutdown is the reverse, so @@ -9,7 +9,7 @@ # '#' starts a comment (whole-line or trailing); blank lines are ignored. The # first field is the service binary path; any fields after it are the service's # argv. Drivers are absent on purpose — the device manager discovers hardware and -# spawns those (see /etc/devices.csv). +# spawns those (see /system/configuration/devices.csv). # # service args... /system/services/input diff --git a/system/drivers/pci-bus/pci-bus.zig b/system/drivers/pci-bus/pci-bus.zig index 6ce7728..39239cd 100644 --- a/system/drivers/pci-bus/pci-bus.zig +++ b/system/drivers/pci-bus/pci-bus.zig @@ -21,7 +21,7 @@ const logging = @import("logging"); const device_manager_protocol = @import("device-manager-protocol"); const pci_class = @import("pci-class"); -/// Log a discovered function as its would-be /etc/devices.csv columns (bus, base, +/// Log a discovered function as its would-be /system/configuration/devices.csv columns (bus, base, /// class, prog_if, vendor, device, subsystem) followed by the human-readable /// class/subclass/prog-IF names — so a row for a new driver reads straight off the /// boot log. `subsystem` prints as `*` when the function has none, matching the CSV @@ -154,7 +154,7 @@ fn registerAndReport(bus: u64, dev: u64, function: u64, class_triple: u32) void descriptor.class = @intFromEnum(device.DeviceClass.pci_device); descriptor.pci_class = class_triple; // Vendor/device from the first config dword (0x00): low half vendor, high half - // device. These carry to the manager's /etc/devices.csv matcher so a function + // device. These carry to the manager's /system/configuration/devices.csv matcher so a function // can bind on its exact 1AF4:1050 identity, not just its class triple. const vendor_device = configRead(bus, dev, function, 0x00); descriptor.vendor = @truncate(vendor_device); diff --git a/system/drivers/usb-xhci-bus/usb-xhci-bus.zig b/system/drivers/usb-xhci-bus/usb-xhci-bus.zig index d516e72..5249d05 100644 --- a/system/drivers/usb-xhci-bus/usb-xhci-bus.zig +++ b/system/drivers/usb-xhci-bus/usb-xhci-bus.zig @@ -459,7 +459,7 @@ fn reportInterface(manager: ipc.Handle, port: u32, interface: library.InterfaceI return null; }; // The devices.csv columns (bus=usb, and the class triple as base/class/prog_if) - // then the human-readable interface name — a would-be /etc/devices.csv row read + // then the human-readable interface name — a would-be /system/configuration/devices.csv row read // straight off the boot log. std.log.info("port {d} interface {d} bus=usb base={X:0>2} class={X:0>2} prog_if={X:0>2} — {s} registered as device {d}", .{ port, diff --git a/system/drivers/virtio-gpu/virtio-gpu.zig b/system/drivers/virtio-gpu/virtio-gpu.zig index 4cee955..fa1bcec 100644 --- a/system/drivers/virtio-gpu/virtio-gpu.zig +++ b/system/drivers/virtio-gpu/virtio-gpu.zig @@ -205,7 +205,7 @@ fn initialise(endpoint: ipc.Handle) bool { return false; }; - // Config space is resource 0. The registry (/etc/devices.csv) bound this driver by the + // Config space is resource 0. The registry (/system/configuration/devices.csv) bound this driver by the // exact virtio-gpu identity (vendor 0x1AF4 / device 0x1050), so there is no re-confirm to // do here any more — map config space and enable memory-space decode + bus mastering (the // device DMAs the ring and backing out of RAM; pci-bus only preserves whatever the firmware diff --git a/system/kernel/log-ring.zig b/system/kernel/log-ring.zig index 0aa5759..7ae4088 100644 --- a/system/kernel/log-ring.zig +++ b/system/kernel/log-ring.zig @@ -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); diff --git a/system/kernel/tests.zig b/system/kernel/tests.zig index 38ce2b0..b7cab8a 100644 --- a/system/kernel/tests.zig +++ b/system/kernel/tests.zig @@ -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) { diff --git a/system/kernel/vfs.zig b/system/kernel/vfs.zig index c0f684d..951c490 100644 --- a/system/kernel/vfs.zig +++ b/system/kernel/vfs.zig @@ -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" { diff --git a/system/services/acpi/acpi.zig b/system/services/acpi/acpi.zig index a4807db..fe5f92b 100644 --- a/system/services/acpi/acpi.zig +++ b/system/services/acpi/acpi.zig @@ -215,7 +215,7 @@ fn onInit(endpoint: ipc.Handle) bool { const entry = registered[i]; const hid = entry.hid[0..entry.hid_len]; // The devices.csv columns (bus=acpi, hid) then the human-readable name — a - // would-be /etc/devices.csv row read straight off the boot log. + // would-be /system/configuration/devices.csv row read straight off the boot log. const desc = acpi_ids.description(hid); if (desc.len != 0) std.log.info("device {d} bus=acpi hid={s} — {s} ({d} resources)", .{ entry.device_id, hid, desc, entry.resource_count }) diff --git a/system/services/device-manager/device-manager.zig b/system/services/device-manager/device-manager.zig index a8f0897..ef06411 100644 --- a/system/services/device-manager/device-manager.zig +++ b/system/services/device-manager/device-manager.zig @@ -28,7 +28,7 @@ const registry = @import("device-registry"); const fs = @import("file-system"); // --- the device registry ------------------------------------------------------ -// Driver matching is data-driven and authoritative: /etc/devices.csv (parsed by +// Driver matching is data-driven and authoritative: /system/configuration/devices.csv (parsed by // the device-registry module) names, per bus, which driver binds a reported // device, the most-specific match winning. There is no compiled-in fallback — a // device no row matches goes unbound and is logged. This retired the hand-kept @@ -41,12 +41,12 @@ var registry_source: [8192]u8 = undefined; var registry_rules: [64]registry.Rule = undefined; var registry_count: usize = 0; -/// Read and parse /etc/devices.csv once at boot. The file lives in the initial +/// Read and parse /system/configuration/devices.csv once at boot. The file lives in the initial /// ramdisk, which the kernel serves directly — no filesystem service need be up /// (fat is spawned after the manager), so this is a plain fs.open + read. fn loadRegistry() void { - var file = fs.open("/etc/devices.csv", .{}) orelse { - _ = logging.write("/system/services/device-manager: /etc/devices.csv missing — nothing will match\n"); + var file = fs.open("/system/configuration/devices.csv", .{}) orelse { + _ = logging.write("/system/services/device-manager: /system/configuration/devices.csv missing — nothing will match\n"); return; }; defer file.close(); @@ -58,9 +58,9 @@ fn loadRegistry() void { } const result = registry.parse(registry_source[0..used], ®istry_rules); registry_count = result.count; - if (result.malformed != 0) std.log.info("/etc/devices.csv: {d} malformed line(s) skipped", .{result.malformed}); - if (result.truncated) _ = logging.write("/system/services/device-manager: /etc/devices.csv has more rules than the table holds\n"); - std.log.info("/etc/devices.csv: {d} rule(s) loaded", .{registry_count}); + if (result.malformed != 0) std.log.info("/system/configuration/devices.csv: {d} malformed line(s) skipped", .{result.malformed}); + if (result.truncated) _ = logging.write("/system/services/device-manager: /system/configuration/devices.csv has more rules than the table holds\n"); + std.log.info("/system/configuration/devices.csv: {d} rule(s) loaded", .{registry_count}); } /// Build a registry Identity from a bus driver's report: the bus it named, the @@ -443,7 +443,7 @@ fn onChildAdded(message: []const u8, reply: []u8, sender: u32) usize { if (!addChild(report.parent, report.bus_address, report.identity, report.device_id, sender)) status = -1; std.log.info("child added (device {d} port {d}, identity {d}) by {s}", .{ report.parent, report.bus_address, report.identity, driver.name() }); if (status == 0) publishEvent(message[0..device_manager_protocol.child_added_size]); - // Matching from reports (M19.3), now data-driven via the /etc/devices.csv + // Matching from reports (M19.3), now data-driven via the /system/configuration/devices.csv // registry: a registered child gets the most-specific driver its identity // matches, once — re-reports after a bus restart dedupe on the registered // id, exactly like the registrations do. @@ -451,7 +451,7 @@ fn onChildAdded(message: []const u8, reply: []u8, sender: u32) usize { const id = identityFromReport(report); if (registry.matchDriver(registry_rules[0..registry_count], id)) |match| { if (match.ambiguous) - std.log.info("/etc/devices.csv: multiple equally-specific rules match the device {s} reported; binding {s}", .{ driver.name(), match.driver }); + std.log.info("/system/configuration/devices.csv: multiple equally-specific rules match the device {s} reported; binding {s}", .{ driver.name(), match.driver }); if (id.bus == .acpi) { // An hid-matched driver (ps2-bus) is a singleton that finds its // own devices once spawned — spawn it once, no device assignment. diff --git a/system/services/fat/engine.zig b/system/services/fat/engine.zig index bae264a..de1723c 100644 --- a/system/services/fat/engine.zig +++ b/system/services/fat/engine.zig @@ -73,7 +73,7 @@ const entries_per_sector = sector_size / @sizeOf(on_disk.DirectoryEntry); // 16 // A small write-through cache of single-sector (metadata) accesses: FAT sectors, // directory sectors, and directory-entry writebacks. Its payoff is repeated scans // — resolving many paths under the same directory (a logging burst opening dozens -// of files under /var/log//) re-reads the same directory and FAT sectors, +// of files under /system/logs//) re-reads the same directory and FAT sectors, // which now come from RAM instead of a USB round trip each. Bulk file data (the // multi-sector run path) bypasses the cache — it is large and not re-read — and a // run write invalidates any overlapping cached sector to stay coherent. diff --git a/system/services/fat/fat.zig b/system/services/fat/fat.zig index 58cb1ab..eac1a41 100644 --- a/system/services/fat/fat.zig +++ b/system/services/fat/fat.zig @@ -1,8 +1,8 @@ //! system/services/fat — the FAT filesystem server. Spawned as a boot service, it //! opens the block device (a USB stick via usb-storage) under `.block`, mounts the //! FAT filesystem on it (the pure engine in engine.zig), and mounts itself into -//! the VFS at /mnt/usb. From then on the VFS forwards every open/read/write/ -//! status/readdir/close under /mnt/usb to this server, which serves the same +//! the VFS at /volumes/usb. From then on the VFS forwards every open/read/write/ +//! status/readdir/close under /volumes/usb to this server, which serves the same //! vfs-protocol as a backend — turning block reads into file reads. //! //! The block data path never crosses IPC: a DMA bounce buffer is handed to the @@ -22,7 +22,7 @@ const engine = @import("engine.zig"); const on_disk = @import("on-disk.zig"); const vfs_protocol = @import("vfs-protocol"); -const mount_point = "/mnt/usb"; +const mount_point = "/volumes/usb"; // The engine's BlockDevice, backed by the `.block` driver plus a DMA bounce // buffer the driver reads/writes by physical address. @@ -144,18 +144,25 @@ fn tryBringUp() void { }; std.log.info("mounted FAT ({s}, {d} clusters, partition lba {d})", .{ @tagName(filesystem.geometry.fat_type), filesystem.geometry.cluster_count, filesystem.base_lba }); - // Mount ourselves into the kernel VFS at /mnt/usb — and serve /var from the - // volume's /var subtree, so FHS paths (the logger's /var/log) stay decoupled - // from which volume carries them. + // Mount ourselves into the kernel VFS at /volumes/usb — and serve + // /system/configuration and /system/logs from the volume's identically-named + // subtrees (the boot volume is hierarchy-shaped, so rewrite == prefix), so + // hierarchy paths (the logger's /system/logs) stay decoupled from which + // volume carries them. if (file_system.mount(mount_point, endpointForMount())) { std.log.info("mounted {s}", .{mount_point}); } else { - _ = logging.write("/system/services/fat: could not mount /mnt/usb\n"); + _ = logging.write("/system/services/fat: could not mount /volumes/usb\n"); } - if (file_system.mountRewritten("/var", endpointForMount(), "/var")) { - std.log.info("mounted /var", .{}); + if (file_system.mountRewritten("/system/configuration", endpointForMount(), "/system/configuration")) { + std.log.info("mounted /system/configuration", .{}); } else { - _ = logging.write("/system/services/fat: could not mount /var\n"); + _ = logging.write("/system/services/fat: could not mount /system/configuration\n"); + } + if (file_system.mountRewritten("/system/logs", endpointForMount(), "/system/logs")) { + std.log.info("mounted /system/logs", .{}); + } else { + _ = logging.write("/system/services/fat: could not mount /system/logs\n"); } mounted = true; } diff --git a/system/services/init/init.zig b/system/services/init/init.zig index 480dd37..013dca5 100644 --- a/system/services/init/init.zig +++ b/system/services/init/init.zig @@ -29,17 +29,17 @@ const fs = @import("file-system"); const csv = @import("csv"); /// The system services init brings up at boot are init's policy, not the kernel's — -/// and that policy is now data: `/etc/init.csv` (see `loadServices`), read at +/// and that policy is now data: `/system/configuration/init.csv` (see `loadServices`), read at /// startup instead of a hardcoded list. Drivers are absent on purpose: the device /// manager owns those. /// -/// The most services `/etc/init.csv` can list, and the most argv entries (beyond the +/// The most services `/system/configuration/init.csv` can list, and the most argv entries (beyond the /// path) each may carry. Fixed caps because init parses the list into static storage — /// the freestanding, no-allocator counterpart to the device manager's registry table. const max_services = 16; const max_service_args = 4; -/// One service init starts, parsed from a row of `/etc/init.csv`: its binary path +/// One service init starts, parsed from a row of `/system/configuration/init.csv`: its binary path /// and argv, both slices into `init_csv` (held for the life of the process). const Service = struct { path: []const u8 = "", @@ -50,7 +50,7 @@ const Service = struct { } }; -/// The `/etc/init.csv` bytes, held because the parsed services slice into them. +/// The `/system/configuration/init.csv` bytes, held because the parsed services slice into them. var init_csv: [4096]u8 = undefined; var services: [max_services]Service = .{Service{}} ** max_services; var service_count: usize = 0; @@ -65,16 +65,16 @@ var restart_counts: [max_services]u32 = .{0} ** max_services; var shutting_down = false; var supervision_endpoint: ipc.Handle = 0; -/// Parse `/etc/init.csv` into `services`, in file order (startup order; shutdown is +/// Parse `/system/configuration/init.csv` into `services`, in file order (startup order; shutdown is /// the reverse). Each row is a binary path followed by its argv, comma-separated; /// `#` comments and blank lines are ignored. The file lives in the initial ramdisk, /// which the kernel serves directly, so init — PID 1, running before any filesystem /// service — reads it with a plain fs.open, the same mechanism the device manager -/// uses for /etc/devices.csv. A missing file means no services (the no-ramdisk +/// uses for /system/configuration/devices.csv. A missing file means no services (the no-ramdisk /// isolation test): loud, but not fatal. fn loadServices() void { - var file = fs.open("/etc/init.csv", .{}) orelse { - _ = logging.write("/system/services/init: /etc/init.csv missing — no services started\n"); + var file = fs.open("/system/configuration/init.csv", .{}) orelse { + _ = logging.write("/system/services/init: /system/configuration/init.csv missing — no services started\n"); return; }; defer file.close(); @@ -89,7 +89,7 @@ fn loadServices() void { const body = csv.stripComment(line); if (body.len == 0) continue; if (service_count >= services.len) { - _ = logging.write("/system/services/init: /etc/init.csv has more services than the table holds\n"); + _ = logging.write("/system/services/init: /system/configuration/init.csv has more services than the table holds\n"); break; } var it = csv.fields(body); @@ -137,7 +137,7 @@ pub fn main() void { // Load the service list, then bring each up supervised so init can stop them // cleanly. Best-effort and silent: each service announces its own readiness, - // and with no /etc/init.csv (an isolation test) the loop starts nothing. + // and with no /system/configuration/init.csv (an isolation test) the loop starts nothing. loadServices(); for (services[0..service_count], 0..) |*service, i| { if (process.spawnSupervised(service.path, service.arguments(), supervision_endpoint)) |id| child_ids[i] = id; diff --git a/system/services/logger/logger.zig b/system/services/logger/logger.zig index a8a3c4e..0495404 100644 --- a/system/services/logger/logger.zig +++ b/system/services/logger/logger.zig @@ -4,7 +4,7 @@ //! demultiplexes it into **one file per process** on the flash volume: //! //! //.log -//! e.g. /mnt/usb/var/log/2026-07-21T101530Z/system/services/fat.log +//! e.g. /volumes/usb/system/logs/2026-07-21T101530Z/system/services/fat.log //! //! The boot stamp is the wall-clock time of boot (from klog_status), so one //! boot session is one self-contained directory; the kernel's own records go to @@ -37,11 +37,11 @@ const time = @import("time"); const logging = @import("logging"); -/// Where log trees live: the FHS path. The kernel VFS routes /var to whatever -/// volume the fat server mounted there (today: the /var subtree of the USB -/// flash volume) — swapping the persistent medium later touches fat's two -/// mount calls, never this constant. -const base = "/var/log"; +/// Where log trees live: the hierarchy path. The kernel VFS routes /system/logs +/// to whatever volume the fat server mounted there (today: the /system/logs +/// subtree of the USB flash volume) — swapping the persistent medium later +/// touches fat's mount calls, never this constant. +const base = "/system/logs"; /// Drain cadence and the quiet period after which files are closed (flushed). const tick_ms = 250; @@ -128,7 +128,7 @@ fn onTerminate() void { fn tick() void { if (!storage_ready) { - // makePath doubles as the readiness probe: while /var is unmounted the + // makePath doubles as the readiness probe: while /system/logs is unmounted the // resolve fails fast (no storage round trip) and the ring buffers; the // first success creates the whole per-boot tree. if (!fs.makePath(boot_directory[0..boot_directory_len])) return; diff --git a/test/qemu_test.py b/test/qemu_test.py index 2efcccb..daa2bb5 100644 --- a/test/qemu_test.py +++ b/test/qemu_test.py @@ -172,7 +172,7 @@ CASES = [ "smp": 4, "timeout": 150, "qemu_extra": ["-device", "intel-iommu,intremap=off"], - "expect": r"(?s)(?=.*/system/kernel: iommu online)(?=.*fat: mounted /mnt/usb)(?=.*fat-test: ok)", + "expect": r"(?s)(?=.*/system/kernel: iommu online)(?=.*fat: mounted /volumes/usb)(?=.*fat-test: ok)", "fail": r"DANOS-TEST-RESULT: FAIL|DANOS-IOMMU-FAULT"}, # DMA + MSI under translation: interrupt-IN reports arrive through translated DMA and # the xHC's MSI/MSI-X still delivers (the 0xFEE00000 interrupt window bypasses second- @@ -208,7 +208,7 @@ CASES = [ "smp": 4, "timeout": 150, "qemu_extra": ["-device", "amd-iommu,dma-remap=on,intremap=off"], - "expect": r"(?s)(?=.*iommu online \(AMD-Vi\))(?=.*fat: mounted /mnt/usb)(?=.*fat-test: ok)", + "expect": r"(?s)(?=.*iommu online \(AMD-Vi\))(?=.*fat: mounted /volumes/usb)(?=.*fat-test: ok)", "fail": r"DANOS-TEST-RESULT: FAIL|DANOS-IOMMU-FAULT"}, # Port I/O grants: a claimed device's io_port resource lets a driver read/write its # ports (PS/2 status 0x64), gated by the claim; out-of-range/unclaimed is refused. @@ -623,13 +623,13 @@ CASES = [ "expect": r"usb-storage: ready[\s\S]*usb-storage: block 0 signature 0x55aa", "fail": r"DANOS-TEST-RESULT: FAIL"}, # FAT mount end to end: the fat server mounts the boot usb-storage device (the - # FAT32 image) into the VFS at /mnt/usb. A fat-test client then lists and reads + # FAT32 image) into the VFS at /volumes/usb. A fat-test client then lists and reads # through the mount — proof of the whole stack: block device -> FAT parse -> # VFS routing -> file read. {"name": "fat-mount", "smp": 4, "timeout": 150, - "expect": r"fat: mounted /mnt/usb[\s\S]*fat-test: ok", + "expect": r"fat: mounted /volumes/usb[\s\S]*fat-test: ok", "fail": r"DANOS-TEST-RESULT: FAIL"}, # Phase 2b: mkdir/unlink through the mount. Reuses the fat-mount build — the # fat-test client, after listing, makes a directory, writes+reads a file inside @@ -714,7 +714,7 @@ CASES = [ "smp": 4, "timeout": 150, "qmp_after": {"delay": 8, "command": "system_powerdown"}, - "expect": r"logger: logging to /var/log/\d{4}-\d{2}-\d{2}T\d{6}Z[\s\S]*" + "expect": r"logger: logging to /system/logs/\d{4}-\d{2}-\d{2}T\d{6}Z[\s\S]*" r"init: shutting down[\s\S]*" r"logger: flushed through sequence \d+[\s\S]*" r"power: entering S5", @@ -935,7 +935,7 @@ def run_case(arch, case): # The bootable FAT32 USB image the build produced (tools/make-fat-image.py), # presented to the guest as a usb-storage device (see qemu_args). # Boot a per-run COPY of the image: the guest MUTATES its boot volume (the - # fat tests create/delete files; the logger writes /var/log), and QEMU is + # fat tests create/delete files; the logger writes /system/logs), and QEMU is # hard-killed after a match — booting the build artifact in place let one # run's leftovers fail the next (a stale TESTDIR trips the mkdir-duplicate # refusal) and dirtied the build cache's own output. diff --git a/test/system/services/fat-test/fat-test.zig b/test/system/services/fat-test/fat-test.zig index 7a4dddd..b6d58a2 100644 --- a/test/system/services/fat-test/fat-test.zig +++ b/test/system/services/fat-test/fat-test.zig @@ -1,6 +1,6 @@ //! test/system/services/fat-test — a client that proves the FAT mount end to end: -//! it waits for the fat server to mount the USB volume at /mnt/usb, lists the -//! root directory through the VFS (which routes /mnt/usb to the fat backend), and +//! it waits for the fat server to mount the USB volume at /volumes/usb, lists the +//! root directory through the VFS (which routes /volumes/usb to the fat backend), and //! reads a known file off it. Shipped in the initial_ramdisk; the `fat-mount` //! kernel test spawns it alongside init. @@ -18,16 +18,16 @@ fn writeLine(comptime fmt: []const u8, arguments: anytype) void { pub fn main(init: process.Init) void { _ = init; - // Wait for /mnt/usb to be mounted — the fat server races us at boot (it must + // Wait for /volumes/usb to be mounted — the fat server races us at boot (it must // bring up the whole USB storage chain first). var opened: ?fs.Directory = null; var tries: u32 = 0; while (opened == null and tries < 1400) : (tries += 1) { - opened = fs.openDirectory("/mnt/usb"); + opened = fs.openDirectory("/volumes/usb"); if (opened == null) time.sleepMillis(50); } var dir = opened orelse { - _ = logging.write("fat-test: /mnt/usb never became available\n"); + _ = logging.write("fat-test: /volumes/usb never became available\n"); return; }; @@ -43,56 +43,56 @@ pub fn main(init: process.Init) void { // Read a known file off the boot volume through the mount (best effort): the // kernel image is an ELF, so its first bytes are the ELF magic. - if (fs.open("/mnt/usb/system/kernel", .{})) |opened_file| { + if (fs.open("/volumes/usb/system/kernel", .{})) |opened_file| { var file = opened_file; var magic: [4]u8 = undefined; const n = file.read(&magic) orelse 0; file.close(); if (n == 4 and magic[0] == 0x7F and magic[1] == 'E' and magic[2] == 'L' and magic[3] == 'F') { - _ = logging.write("fat-test: read /mnt/usb/system/kernel ELF magic ok\n"); + _ = logging.write("fat-test: read /volumes/usb/system/kernel ELF magic ok\n"); } else { - writeLine("fat-test: /mnt/usb/system/kernel read {d} bytes (not ELF magic)\n", .{n}); + writeLine("fat-test: /volumes/usb/system/kernel read {d} bytes (not ELF magic)\n", .{n}); } } // Exercise directory + file mutation through the mount: mkdir, create a file // inside it, read it back, then remove it — proof mkdir/unlink reach the engine. - if (fs.makeDirectory("/mnt/usb/TESTDIR")) { + if (fs.makeDirectory("/volumes/usb/TESTDIR")) { var wrote = false; - if (fs.open("/mnt/usb/TESTDIR/HELLO.TXT", .{ .create = true, .truncate = true })) |created| { + if (fs.open("/volumes/usb/TESTDIR/HELLO.TXT", .{ .create = true, .truncate = true })) |created| { var f = created; wrote = (f.writeAll("mutation-ok") orelse 0) == "mutation-ok".len; f.close(); } // The created file carries a real modification time (stamped from the RTC). var mtime_ok = false; - if (fs.attributes("/mnt/usb/TESTDIR/HELLO.TXT")) |attrs| { + if (fs.attributes("/volumes/usb/TESTDIR/HELLO.TXT")) |attrs| { writeLine("fat-test: mtime {d}\n", .{attrs.mtime}); mtime_ok = attrs.mtime > 1_577_836_800; // after 2020-01-01 } if (mtime_ok) _ = logging.write("fat-test: mtime ok\n"); // Rename it, then read from the new name and confirm the old name is gone. - const renamed = fs.rename("/mnt/usb/TESTDIR/HELLO.TXT", "/mnt/usb/TESTDIR/RENAMED.TXT"); - const old_gone = !fs.exists("/mnt/usb/TESTDIR/HELLO.TXT"); + const renamed = fs.rename("/volumes/usb/TESTDIR/HELLO.TXT", "/volumes/usb/TESTDIR/RENAMED.TXT"); + const old_gone = !fs.exists("/volumes/usb/TESTDIR/HELLO.TXT"); if (renamed and old_gone) _ = logging.write("fat-test: rename ok\n"); var readback = false; - if (fs.open("/mnt/usb/TESTDIR/RENAMED.TXT", .{})) |reopened| { + if (fs.open("/volumes/usb/TESTDIR/RENAMED.TXT", .{})) |reopened| { var f = reopened; var buf: [16]u8 = undefined; const got = f.read(&buf) orelse 0; f.close(); readback = std.mem.eql(u8, buf[0..got], "mutation-ok"); } - const removed = fs.remove("/mnt/usb/TESTDIR/RENAMED.TXT"); - const gone = !fs.exists("/mnt/usb/TESTDIR/RENAMED.TXT"); + const removed = fs.remove("/volumes/usb/TESTDIR/RENAMED.TXT"); + const gone = !fs.exists("/volumes/usb/TESTDIR/RENAMED.TXT"); if (wrote and mtime_ok and renamed and old_gone and readback and removed and gone) { _ = logging.write("fat-test: mutations ok\n"); } else { writeLine("fat-test: mutations FAILED (wrote={} mtime={} renamed={} oldgone={} read={} removed={} gone={})\n", .{ wrote, mtime_ok, renamed, old_gone, readback, removed, gone }); } } else { - _ = logging.write("fat-test: mkdir /mnt/usb/TESTDIR failed\n"); + _ = logging.write("fat-test: mkdir /volumes/usb/TESTDIR failed\n"); } if (count > 0) { diff --git a/test/system/services/vfs-test/vfs-test.zig b/test/system/services/vfs-test/vfs-test.zig index e31b6b7..3b89f94 100644 --- a/test/system/services/vfs-test/vfs-test.zig +++ b/test/system/services/vfs-test/vfs-test.zig @@ -77,7 +77,7 @@ fn park() void { var parked: ?fs.File = null; var tries: u32 = 0; while (parked == null and tries < 1000) : (tries += 1) { - parked = fs.open("/mnt/usb/parked", .{ .create = true }); + parked = fs.open("/volumes/usb/parked", .{ .create = true }); if (parked == null) time.sleepMillis(20); } if (parked == null) {