Replace init's hardcoded boot_services array with an authoritative, human-readable service list read at boot, mirroring /etc/devices.csv. Each row is a service binary path followed by its argv; startup order is file order, shutdown the reverse. There is no hardcoded fallback — a missing file starts nothing (the no-ramdisk isolation behavior). - library/csv: shared CSV helpers (comment strip, field iteration) with unit tests; device-registry is refactored onto them so both /etc/*.csv files parse through one place. - init reads /etc/init.csv into fixed-max static tables (the same pattern as the device registry) and passes each row's argv straight to spawnSupervised. This also makes boot-time modes (e.g. device-manager test-usb-restart) expressible as data rather than hardcoded. - Diagnose mode (-Ddiagnose omits the display stack) becomes build-time file selection between etc/init.csv and etc/init-diagnose.csv, so init carries no comptime service logic; the diagnose build option is dropped from init. - build.zig: csv module wired; /etc/init.csv bundled into the initrd; the device-registry tests move to a dedicated block since they now import csv. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJqSiLLchDUUCoXn5jsiwd
57 lines
2.4 KiB
Zig
57 lines
2.4 KiB
Zig
//! Minimal CSV helpers shared by the `/etc/*.csv` config files — the device
|
|
//! registry (`/etc/devices.csv`) and the init service list (`/etc/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.
|
|
|
|
const std = @import("std");
|
|
|
|
/// Strip a trailing `#` comment and surrounding whitespace from one raw line.
|
|
/// A blank or comment-only line returns "" (length 0) — the caller's skip signal.
|
|
pub fn stripComment(raw: []const u8) []const u8 {
|
|
const body = if (std.mem.indexOfScalar(u8, raw, '#')) |hash| raw[0..hash] else raw;
|
|
return std.mem.trim(u8, body, " \t\r\n");
|
|
}
|
|
|
|
/// Iterate the comma-separated fields of a line body, each trimmed of spaces and
|
|
/// tabs. Build it from a `stripComment`ed body.
|
|
pub const Fields = struct {
|
|
inner: std.mem.SplitIterator(u8, .scalar),
|
|
|
|
/// The next field, trimmed, or null when the row is exhausted.
|
|
pub fn next(self: *Fields) ?[]const u8 {
|
|
const field = self.inner.next() orelse return null;
|
|
return std.mem.trim(u8, field, " \t");
|
|
}
|
|
};
|
|
|
|
pub fn fields(body: []const u8) Fields {
|
|
return .{ .inner = std.mem.splitScalar(u8, body, ',') };
|
|
}
|
|
|
|
// --- tests -------------------------------------------------------------------
|
|
|
|
const testing = std.testing;
|
|
|
|
test "stripComment trims and drops comments" {
|
|
try testing.expectEqualStrings("a, b", stripComment(" a, b # trailing\r\n"));
|
|
try testing.expectEqualStrings("", stripComment(" # whole-line comment"));
|
|
try testing.expectEqualStrings("", stripComment(" \t "));
|
|
try testing.expectEqualStrings("x", stripComment("x"));
|
|
}
|
|
|
|
test "fields splits and trims each column" {
|
|
var it = fields(stripComment("pci, 03 , 80 , /system/drivers/x # note"));
|
|
try testing.expectEqualStrings("pci", it.next().?);
|
|
try testing.expectEqualStrings("03", it.next().?);
|
|
try testing.expectEqualStrings("80", it.next().?);
|
|
try testing.expectEqualStrings("/system/drivers/x", it.next().?);
|
|
try testing.expect(it.next() == null);
|
|
}
|
|
|
|
test "a single field yields one column then null" {
|
|
var it = fields(stripComment("/system/services/input"));
|
|
try testing.expectEqualStrings("/system/services/input", it.next().?);
|
|
try testing.expect(it.next() == null);
|
|
}
|