//! filesystem-map — parse `/system/configuration/filesystems.csv` into //! content-signature → service-binary rules, and pick the binary for a probed //! volume's signature. The data-driven replacement for the volume manager's //! hardcoded `filesystem_binary` const: a signature no row matches goes unserved //! (logged), never guessed — the same discipline the device registry uses. //! //! Pure logic: no hardware, no syscalls, no allocator. The `binary` slice points //! into the CSV source, which the manager holds in a static buffer for the life //! of the process (zero-copy), so the source must outlive the rules. //! //! Format: one rule per line, two comma-separated fields, `#` comments (whole- //! line or trailing), blank lines ignored: //! //! signature, binary //! //! `signature` is a filesystem token (`fat`; `exfat` lands with S4); `binary` is //! a full ramdisk path. const std = @import("std"); const csv = @import("csv"); const partition = @import("partition.zig"); /// One parsed row: a content signature and the service binary that serves it. pub const Rule = struct { kind: partition.FilesystemKind, binary: []const u8, }; /// How many rules landed, how many non-blank lines were malformed (for the /// manager to log), and whether there were more rules than the buffer could hold. pub const ParseResult = struct { count: usize, malformed: usize, truncated: bool }; const Line = union(enum) { rule: Rule, ignorable, malformed }; fn parseLine(line: []const u8) Line { const body = csv.stripComment(line); if (body.len == 0) return .ignorable; var it = csv.fields(body); const sig = it.next() orelse return .malformed; const binary = it.next() orelse return .malformed; if (it.next() != null) return .malformed; // too many columns if (binary.len == 0) return .malformed; const kind = partition.FilesystemKind.fromToken(sig); if (kind == .unknown) return .malformed; // an unrecognised signature token return .{ .rule = .{ .kind = kind, .binary = binary } }; } /// Parse a whole `filesystems.csv` into `out_rules`. The `binary` slices 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 }; var lines = std.mem.splitScalar(u8, source, '\n'); while (lines.next()) |line| { switch (parseLine(line)) { .ignorable => {}, .malformed => result.malformed += 1, .rule => |rule| { if (result.count >= out_rules.len) { result.truncated = true; continue; } out_rules[result.count] = rule; result.count += 1; }, } } return result; } /// The service binary for a probed volume's signature — the first matching row, /// or null (the volume goes unserved, like a device no registry row matches). pub fn match(rules: []const Rule, kind: partition.FilesystemKind) ?[]const u8 { for (rules) |rule| { if (rule.kind == kind) return rule.binary; } return null; } // --- tests ------------------------------------------------------------------- const testing = std.testing; // A fixture-sized rule buffer for the tests, named so the bounds gate (which // flags literal array lengths) stays quiet: this is a test input, not a runtime // ceiling — the real one is maximum_filesystem_rules in the volume manager. const test_rule_slots = 4; test "a fat signature maps to its binary; an unmatched signature is null" { const text = \\# signature, binary \\fat, /system/services/fat ; var rules: [test_rule_slots]Rule = undefined; const parsed = parse(text, &rules); try testing.expectEqual(@as(usize, 1), parsed.count); try testing.expectEqual(@as(usize, 0), parsed.malformed); try testing.expectEqualStrings("/system/services/fat", match(rules[0..parsed.count], .fat).?); try testing.expect(match(rules[0..parsed.count], .unknown) == null); } test "the binary is chosen by content, not hardcoded" { // Point the fat row at a different binary and confirm that binary is chosen — // a constant could not satisfy this, which is the whole point of the map. const text = "fat, /system/services/other-fat\n"; var rules: [test_rule_slots]Rule = undefined; const parsed = parse(text, &rules); try testing.expectEqualStrings("/system/services/other-fat", match(rules[0..parsed.count], .fat).?); } test "malformed rows are counted, not bound" { const text = \\fat, /system/services/fat \\bogusfs, /system/services/x \\fat, \\fat, /a, /b ; var rules: [test_rule_slots]Rule = undefined; const parsed = parse(text, &rules); try testing.expectEqual(@as(usize, 1), parsed.count); // only the first fat row try testing.expectEqual(@as(usize, 3), parsed.malformed); // bad token, empty binary, too many columns }