init: data-driven boot service list via /etc/init.csv
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
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
//! (e.g. "PNP0303") with the triple left blank. `driver` is a full ramdisk path.
|
||||
|
||||
const std = @import("std");
|
||||
const csv = @import("csv");
|
||||
|
||||
/// Which bus a rule or a reported device belongs to. `unknown` is what an
|
||||
/// unrecognised `bus` token parses to — such a rule never matches (its bus
|
||||
@@ -168,12 +169,6 @@ pub const ParseResult = struct {
|
||||
truncated: bool,
|
||||
};
|
||||
|
||||
/// Strip a trailing `#` comment and surrounding whitespace from one raw line.
|
||||
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");
|
||||
}
|
||||
|
||||
/// Parse one hex field into `T`, honouring `*`/empty as a wildcard (`null`) and
|
||||
/// an optional `0x` prefix. Returns an error only for a genuinely unparsable
|
||||
/// non-wildcard token, so the caller can mark the whole line malformed.
|
||||
@@ -197,36 +192,36 @@ fn parseStringField(field: []const u8) ?[]const u8 {
|
||||
/// Classify and (if a rule) parse one line. Split out from `parse` so it can be
|
||||
/// unit-tested directly. `line` is the raw line including no newline.
|
||||
fn parseLine(line: []const u8) Line {
|
||||
const body = stripComment(line);
|
||||
const body = csv.stripComment(line);
|
||||
if (body.len == 0) return .ignorable;
|
||||
|
||||
// Nine comma-separated fields: bus, base, class, prog_if, vendor, device,
|
||||
// subsystem, hid, driver.
|
||||
var fields: [9][]const u8 = undefined;
|
||||
// Nine comma-separated fields (csv.fields trims each): bus, base, class,
|
||||
// prog_if, vendor, device, subsystem, hid, driver.
|
||||
var cols: [9][]const u8 = undefined;
|
||||
var count: usize = 0;
|
||||
var it = std.mem.splitScalar(u8, body, ',');
|
||||
var it = csv.fields(body);
|
||||
while (it.next()) |field| {
|
||||
if (count >= fields.len) return .malformed; // too many columns
|
||||
fields[count] = field;
|
||||
if (count >= cols.len) return .malformed; // too many columns
|
||||
cols[count] = field;
|
||||
count += 1;
|
||||
}
|
||||
if (count != fields.len) return .malformed; // too few columns
|
||||
if (count != cols.len) return .malformed; // too few columns
|
||||
|
||||
const bus = Bus.fromToken(std.mem.trim(u8, fields[0], " \t"));
|
||||
const bus = Bus.fromToken(cols[0]);
|
||||
if (bus == .unknown) return .malformed;
|
||||
|
||||
const driver = std.mem.trim(u8, fields[8], " \t");
|
||||
const driver = cols[8];
|
||||
if (driver.len == 0) return .malformed;
|
||||
|
||||
return .{ .rule = .{
|
||||
.bus = bus,
|
||||
.base = parseHexField(u8, fields[1]) catch return .malformed,
|
||||
.subclass = parseHexField(u8, fields[2]) catch return .malformed,
|
||||
.prog_if = parseHexField(u8, fields[3]) catch return .malformed,
|
||||
.vendor = parseHexField(u16, fields[4]) catch return .malformed,
|
||||
.device = parseHexField(u16, fields[5]) catch return .malformed,
|
||||
.subsystem = parseHexField(u32, fields[6]) catch return .malformed,
|
||||
.hid = parseStringField(fields[7]),
|
||||
.base = parseHexField(u8, cols[1]) catch return .malformed,
|
||||
.subclass = parseHexField(u8, cols[2]) catch return .malformed,
|
||||
.prog_if = parseHexField(u8, cols[3]) catch return .malformed,
|
||||
.vendor = parseHexField(u16, cols[4]) catch return .malformed,
|
||||
.device = parseHexField(u16, cols[5]) catch return .malformed,
|
||||
.subsystem = parseHexField(u32, cols[6]) catch return .malformed,
|
||||
.hid = parseStringField(cols[7]),
|
||||
.driver = driver,
|
||||
} };
|
||||
}
|
||||
@@ -262,13 +257,13 @@ const testing = std.testing;
|
||||
// the specific one must win. And a plain VGA adapter still falls to the generic
|
||||
// rule. This is the whole point of widening the ABI to carry vendor/device.
|
||||
test "virtio device rule beats the generic display rule" {
|
||||
const csv =
|
||||
const text =
|
||||
\\# bus, base, class, prog_if, vendor, device, subsystem, hid, driver
|
||||
\\pci, 03, 00, 00, *, *, *, *, /system/drivers/display
|
||||
\\pci, 03, 80, *, 1AF4, 1050, *, *, /system/drivers/virtio-gpu
|
||||
;
|
||||
var rules: [8]Rule = undefined;
|
||||
const parsed = parse(csv, &rules);
|
||||
const parsed = parse(text, &rules);
|
||||
try testing.expectEqual(@as(usize, 2), parsed.count);
|
||||
try testing.expectEqual(@as(usize, 0), parsed.malformed);
|
||||
|
||||
@@ -289,9 +284,9 @@ test "virtio device rule beats the generic display rule" {
|
||||
}
|
||||
|
||||
test "no matching row leaves the device unbound" {
|
||||
const csv = "pci, 0C, 03, 30, *, *, *, *, /system/drivers/usb-xhci-bus\n";
|
||||
const text = "pci, 0C, 03, 30, *, *, *, *, /system/drivers/usb-xhci-bus\n";
|
||||
var rules: [8]Rule = undefined;
|
||||
const parsed = parse(csv, &rules);
|
||||
const parsed = parse(text, &rules);
|
||||
try testing.expectEqual(@as(usize, 1), parsed.count);
|
||||
|
||||
// An AHCI controller (mass storage / SATA / AHCI) has no row — unbound.
|
||||
@@ -302,12 +297,12 @@ test "no matching row leaves the device unbound" {
|
||||
}
|
||||
|
||||
test "acpi rows match on hid" {
|
||||
const csv =
|
||||
const text =
|
||||
\\acpi, *, *, *, *, *, *, PNP0303, /system/drivers/ps2-bus
|
||||
\\acpi, *, *, *, *, *, *, PNP0F13, /system/drivers/ps2-bus
|
||||
;
|
||||
var rules: [8]Rule = undefined;
|
||||
const parsed = parse(csv, &rules);
|
||||
const parsed = parse(text, &rules);
|
||||
try testing.expectEqual(@as(usize, 2), parsed.count);
|
||||
|
||||
const keyboard = matchDriver(rules[0..parsed.count], .{ .bus = .acpi, .hid = "PNP0303" }).?;
|
||||
@@ -317,12 +312,12 @@ test "acpi rows match on hid" {
|
||||
}
|
||||
|
||||
test "equally specific rules flag ambiguity" {
|
||||
const csv =
|
||||
const text =
|
||||
\\pci, 03, 00, 00, *, *, *, *, /system/drivers/display-a
|
||||
\\pci, 03, 00, 00, *, *, *, *, /system/drivers/display-b
|
||||
;
|
||||
var rules: [8]Rule = undefined;
|
||||
const parsed = parse(csv, &rules);
|
||||
const parsed = parse(text, &rules);
|
||||
const hit = matchDriver(rules[0..parsed.count], .{
|
||||
.bus = .pci, .base = 0x03, .subclass = 0x00, .prog_if = 0x00,
|
||||
}).?;
|
||||
@@ -331,7 +326,7 @@ test "equally specific rules flag ambiguity" {
|
||||
}
|
||||
|
||||
test "comments, blanks, and malformed lines" {
|
||||
const csv =
|
||||
const text =
|
||||
\\# a header comment
|
||||
\\
|
||||
\\pci, 0C, 03, 30, *, *, *, *, /system/drivers/usb-xhci-bus # trailing comment
|
||||
@@ -340,7 +335,7 @@ test "comments, blanks, and malformed lines" {
|
||||
\\bogus-bus, *, *, *, *, *, *, *, /system/drivers/x
|
||||
;
|
||||
var rules: [8]Rule = undefined;
|
||||
const parsed = parse(csv, &rules);
|
||||
const parsed = parse(text, &rules);
|
||||
try testing.expectEqual(@as(usize, 1), parsed.count); // only the xhci row is valid
|
||||
try testing.expectEqual(@as(usize, 3), parsed.malformed); // bad hex, empty driver, bad bus
|
||||
try testing.expectEqualStrings("/system/drivers/usb-xhci-bus", rules[0].driver);
|
||||
|
||||
Reference in New Issue
Block a user