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:
@@ -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 })
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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/<stamp>/) re-reads the same directory and FAT sectors,
|
||||
// of files under /system/logs/<stamp>/) 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.
|
||||
|
||||
+17
-10
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//! demultiplexes it into **one file per process** on the flash volume:
|
||||
//!
|
||||
//! <base>/<boot-stamp>/<binary-path>.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;
|
||||
|
||||
Reference in New Issue
Block a user