fat: mount at the id-path from argv; migrate /volumes/usb -> id-path (S2)
The flip that makes the mount path the volume's content id. fat retires its hardcoded fat_mounts: it reads its mount path from argv[2] (the volume manager hands it the id-path, e.g. /volumes/fat-12345678, from the FAT serial), mounts its volume root there, and installs the /system/configuration + /system/logs FHS rewrites so /system/logs persistence stays decoupled from which volume backs it. The rewrites are unconditional this increment (the single volume IS the boot volume); S3 makes them content-conditional across N volumes. Every /volumes/usb reference migrates to /volumes/fat-12345678 in one commit — the fat-test, badge-scope-test, and vfs-test fixtures and the four QEMU regexes — plus a new volume-identity-name case asserting the id-path mount and the /system/logs rewrite. Discrimination: the regexes now require /volumes/fat-12345678, which the old hardcoded fat never emitted (it mounted /volumes/usb). Full suite 128/128.
This commit is contained in:
+33
-11
@@ -64,15 +64,24 @@ var filesystem: engine.FileSystem = undefined;
|
||||
/// the right volume's channel.
|
||||
var my_volume_id: u64 = 0;
|
||||
|
||||
/// The prefixes this volume installs: /volumes/usb from the volume root, plus
|
||||
/// the two hierarchy subtrees the boot volume carries (rewrite == prefix), so
|
||||
/// hierarchy paths (the logger's /system/logs) stay decoupled from which volume
|
||||
/// backs them.
|
||||
const fat_mounts = [_]harness.MountSpec{
|
||||
.{ .prefix = "/volumes/usb" },
|
||||
.{ .prefix = "/system/configuration", .rewrite = "/system/configuration" },
|
||||
.{ .prefix = "/system/logs", .rewrite = "/system/logs" },
|
||||
};
|
||||
/// The volume's own mount path, handed in as argv[2] by the volume manager: the
|
||||
/// volume's content id-path (e.g. /volumes/fat-12345678). Defaults to
|
||||
/// /volumes/usb only for a bare launch with no argument; the manager always
|
||||
/// passes it. The slice points into the entry block, valid for the process life.
|
||||
var volume_mount_prefix: []const u8 = "/volumes/usb";
|
||||
|
||||
/// The mounts this volume installs: its own root, plus — only if it is the boot
|
||||
/// volume (it resolves /system/configuration) — the two FHS rewrites, so the
|
||||
/// logger's /system/logs stays decoupled from which volume backs it. Boot-volume
|
||||
/// detection is by content, so it works no matter which volume carries /system.
|
||||
/// bound: mounts one volume installs (its root + the two boot rewrites)
|
||||
/// decided-by: ours
|
||||
/// protects: the mount_specs array
|
||||
/// at-limit: truncate - unreachable today (fixed at 3); more configured mounts
|
||||
/// would need this raised, a deliberate change
|
||||
/// observed-by: a mount silently missing from the harness's mount log
|
||||
const maximum_mounts_per_volume = 4;
|
||||
var mount_specs: [maximum_mounts_per_volume]harness.MountSpec = undefined;
|
||||
|
||||
/// Get this volume's block channel from the volume manager (establishment by
|
||||
/// lineage, communication.md "Establishment: two planes" — `block` is not a
|
||||
@@ -164,14 +173,27 @@ fn fatBringUp(endpoint: ipc.Handle) ?Harness.Volume {
|
||||
};
|
||||
std.log.info("mounted FAT ({s}, {d} clusters, partition lba {d})", .{ @tagName(filesystem.geometry.fat_type), filesystem.geometry.cluster_count, filesystem.base_lba });
|
||||
|
||||
return .{ .engine = &filesystem, .mounts = &fat_mounts, .flush = flushIfDirty };
|
||||
// The volume mounts at its id-path (argv[2]), plus the two FHS rewrites so
|
||||
// hierarchy paths (the logger's /system/logs) stay decoupled from which
|
||||
// volume backs them. This single-volume increment's one volume IS the boot
|
||||
// volume, so it installs both unconditionally; S3 (multi-volume) makes the
|
||||
// rewrites content-conditional — installed only by whichever volume carries
|
||||
// the system, decided by content, not order.
|
||||
mount_specs[0] = .{ .prefix = volume_mount_prefix };
|
||||
mount_specs[1] = .{ .prefix = "/system/configuration", .rewrite = "/system/configuration" };
|
||||
mount_specs[2] = .{ .prefix = "/system/logs", .rewrite = "/system/logs" };
|
||||
return .{ .engine = &filesystem, .mounts = mount_specs[0..3], .flush = flushIfDirty };
|
||||
}
|
||||
|
||||
pub fn main(init: process.Init) void {
|
||||
// The volume manager spawns this process with its volume id as argv[1].
|
||||
// The volume manager spawns this process with its volume id as argv[1] and
|
||||
// the volume's mount path (its id-path) as argv[2].
|
||||
if (init.arguments.get(1)) |id| {
|
||||
my_volume_id = std.fmt.parseInt(u64, id, 10) catch 0;
|
||||
}
|
||||
if (init.arguments.get(2)) |prefix| {
|
||||
volume_mount_prefix = prefix;
|
||||
}
|
||||
_ = logging.write("/system/services/fat: starting, waiting for a block device\n");
|
||||
Harness.run(.{ .bringUp = fatBringUp });
|
||||
}
|
||||
|
||||
+18
-5
@@ -179,7 +179,7 @@ CASES = [
|
||||
"smp": 4,
|
||||
"timeout": 150,
|
||||
"qemu_extra": ["-device", "intel-iommu,intremap=off"],
|
||||
"expect": r"(?s)(?=.*/system/kernel: iommu online)(?=.*fat: mounted /volumes/usb)(?=.*fat-test: ok)",
|
||||
"expect": r"(?s)(?=.*/system/kernel: iommu online)(?=.*fat: mounted /volumes/fat-12345678)(?=.*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-
|
||||
@@ -215,7 +215,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 /volumes/usb)(?=.*fat-test: ok)",
|
||||
"expect": r"(?s)(?=.*iommu online \(AMD-Vi\))(?=.*fat: mounted /volumes/fat-12345678)(?=.*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.
|
||||
@@ -749,13 +749,26 @@ 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 /volumes/usb. A fat-test client then lists and reads
|
||||
# FAT32 image) into the VFS at /volumes/fat-12345678. 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 /volumes/usb[\s\S]*fat-test: ok",
|
||||
"expect": r"fat: mounted /volumes/fat-12345678[\s\S]*fat-test: ok",
|
||||
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
||||
# The id-path naming (S2, storage-stack-plan.md). The boot volume mounts at
|
||||
# its CONTENT-derived id-path (/volumes/fat-12345678, from the FAT32 serial
|
||||
# 0x12345678) — never a port name — and keeps its FHS rewrites so /system/logs
|
||||
# persistence still rides the volume. Discrimination: before S2's flip fat
|
||||
# hardcoded /volumes/usb, so the id-path mount line never appears. (Making the
|
||||
# rewrites content-conditional on which volume carries the system is S3.)
|
||||
{"name": "volume-identity-name",
|
||||
"build_case": "fat-mount",
|
||||
"smp": 4,
|
||||
"timeout": 150,
|
||||
"expect": r"(?s)fat: mounted /volumes/fat-12345678"
|
||||
r"[\s\S]*fat: mounted /system/logs",
|
||||
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
||||
# The removal lifecycle (V4, docs/volume-manager-plan.md): pull the boot stick
|
||||
# mid-run. device_del the usb-storage device -> the bus reports the port empty
|
||||
@@ -780,7 +793,7 @@ CASES = [
|
||||
"qmp_sequence": [
|
||||
{"delay": 8, "command": "device_del", "arguments": {"id": "bootstorage"}},
|
||||
],
|
||||
"expect": r"(?s)fat: mounted /volumes/usb"
|
||||
"expect": r"(?s)fat: mounted /volumes/fat-12345678"
|
||||
r"[\s\S]*volume-manager: storage for volume \d+ removed; unmounting",
|
||||
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
||||
# Volume-manager discovery + probe (V3a, docs/volume-manager-plan.md). Reuses
|
||||
|
||||
@@ -38,7 +38,7 @@ const time = @import("time");
|
||||
/// A scratch file on the volume, so the node the intruder tries to write through
|
||||
/// is one nothing else reads. (A foreign write that *succeeded* would prove the
|
||||
/// bug — it must not also damage the boot volume proving it.)
|
||||
const held_path = "/volumes/usb/BADGE.TXT";
|
||||
const held_path = "/volumes/fat-12345678/BADGE.TXT";
|
||||
const held_contents = "held";
|
||||
|
||||
fn line(comptime format: []const u8, arguments: anytype) void {
|
||||
@@ -46,12 +46,12 @@ fn line(comptime format: []const u8, arguments: anytype) void {
|
||||
_ = logging.write(std.fmt.bufPrint(&buffer, format, arguments) catch return);
|
||||
}
|
||||
|
||||
/// The fat server mounts /volumes/usb only after the whole USB storage chain is
|
||||
/// The fat server mounts /volumes/fat-12345678 only after the whole USB storage chain is
|
||||
/// up, and both instances race it.
|
||||
fn waitForVolume() bool {
|
||||
var tries: u32 = 0;
|
||||
while (tries < 1400) : (tries += 1) {
|
||||
if (fs.openDirectory("/volumes/usb")) |opened| {
|
||||
if (fs.openDirectory("/volumes/fat-12345678")) |opened| {
|
||||
var directory = opened;
|
||||
directory.close();
|
||||
return true;
|
||||
@@ -79,7 +79,7 @@ pub fn main(init: process.Init) void {
|
||||
|
||||
fn own() void {
|
||||
if (!waitForVolume()) {
|
||||
_ = logging.write("badge-scope-test: FAILED (/volumes/usb never became available)\n");
|
||||
_ = logging.write("badge-scope-test: FAILED (/volumes/fat-12345678 never became available)\n");
|
||||
return;
|
||||
}
|
||||
var held = fs.open(held_path, .{ .create = true, .truncate = true }) orelse {
|
||||
@@ -142,7 +142,7 @@ fn own() void {
|
||||
|
||||
fn intrude(foreign_node: u64, foreign_layer: u32) void {
|
||||
if (!waitForVolume()) {
|
||||
_ = logging.write("badge-scope-test: FAILED (/volumes/usb never became available)\n");
|
||||
_ = logging.write("badge-scope-test: FAILED (/volumes/fat-12345678 never became available)\n");
|
||||
return;
|
||||
}
|
||||
const node_verdict = probeNode(foreign_node);
|
||||
|
||||
@@ -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 /volumes/usb, lists the
|
||||
//! root directory through the VFS (which routes /volumes/usb to the fat backend), and
|
||||
//! it waits for the fat server to mount the USB volume at /volumes/fat-12345678, lists the
|
||||
//! root directory through the VFS (which routes /volumes/fat-12345678 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 /volumes/usb to be mounted — the fat server races us at boot (it must
|
||||
// Wait for /volumes/fat-12345678 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("/volumes/usb");
|
||||
opened = fs.openDirectory("/volumes/fat-12345678");
|
||||
if (opened == null) time.sleepMillis(50);
|
||||
}
|
||||
var dir = opened orelse {
|
||||
_ = logging.write("fat-test: /volumes/usb never became available\n");
|
||||
_ = logging.write("fat-test: /volumes/fat-12345678 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("/volumes/usb/system/kernel", .{})) |opened_file| {
|
||||
if (fs.open("/volumes/fat-12345678/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 /volumes/usb/system/kernel ELF magic ok\n");
|
||||
_ = logging.write("fat-test: read /volumes/fat-12345678/system/kernel ELF magic ok\n");
|
||||
} else {
|
||||
writeLine("fat-test: /volumes/usb/system/kernel read {d} bytes (not ELF magic)\n", .{n});
|
||||
writeLine("fat-test: /volumes/fat-12345678/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("/volumes/usb/TESTDIR")) {
|
||||
if (fs.makeDirectory("/volumes/fat-12345678/TESTDIR")) {
|
||||
var wrote = false;
|
||||
if (fs.open("/volumes/usb/TESTDIR/HELLO.TXT", .{ .create = true, .truncate = true })) |created| {
|
||||
if (fs.open("/volumes/fat-12345678/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("/volumes/usb/TESTDIR/HELLO.TXT")) |attrs| {
|
||||
if (fs.attributes("/volumes/fat-12345678/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("/volumes/usb/TESTDIR/HELLO.TXT", "/volumes/usb/TESTDIR/RENAMED.TXT");
|
||||
const old_gone = !fs.exists("/volumes/usb/TESTDIR/HELLO.TXT");
|
||||
const renamed = fs.rename("/volumes/fat-12345678/TESTDIR/HELLO.TXT", "/volumes/fat-12345678/TESTDIR/RENAMED.TXT");
|
||||
const old_gone = !fs.exists("/volumes/fat-12345678/TESTDIR/HELLO.TXT");
|
||||
if (renamed and old_gone) _ = logging.write("fat-test: rename ok\n");
|
||||
var readback = false;
|
||||
if (fs.open("/volumes/usb/TESTDIR/RENAMED.TXT", .{})) |reopened| {
|
||||
if (fs.open("/volumes/fat-12345678/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("/volumes/usb/TESTDIR/RENAMED.TXT");
|
||||
const gone = !fs.exists("/volumes/usb/TESTDIR/RENAMED.TXT");
|
||||
const removed = fs.remove("/volumes/fat-12345678/TESTDIR/RENAMED.TXT");
|
||||
const gone = !fs.exists("/volumes/fat-12345678/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 /volumes/usb/TESTDIR failed\n");
|
||||
_ = logging.write("fat-test: mkdir /volumes/fat-12345678/TESTDIR failed\n");
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
|
||||
@@ -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("/volumes/usb/parked", .{ .create = true });
|
||||
parked = fs.open("/volumes/fat-12345678/parked", .{ .create = true });
|
||||
if (parked == null) time.sleepMillis(20);
|
||||
}
|
||||
if (parked == null) {
|
||||
@@ -92,16 +92,16 @@ fn park() void {
|
||||
// "parked" marker, which fails the vfs-client-death case: before the
|
||||
// ownership gate existed, any process could unmount any prefix, and this
|
||||
// fixture would have deleted the volume out from under the whole boot.
|
||||
if (fs.fsUnmount("/volumes/usb")) {
|
||||
if (fs.fsUnmount("/volumes/fat-12345678")) {
|
||||
_ = logging.write("vfstest: foreign unmount was ALLOWED\n");
|
||||
return;
|
||||
}
|
||||
if (fs.open("/volumes/usb/parked", .{})) |resolved| {
|
||||
if (fs.open("/volumes/fat-12345678/parked", .{})) |resolved| {
|
||||
var verification = resolved;
|
||||
verification.close(); // the park below must be the client's ONLY open
|
||||
// handle — the kernel test string-matches "released 1 handle(s)".
|
||||
} else {
|
||||
_ = logging.write("vfstest: /volumes/usb gone after refused unmount\n");
|
||||
_ = logging.write("vfstest: /volumes/fat-12345678 gone after refused unmount\n");
|
||||
return;
|
||||
}
|
||||
_ = logging.write("vfstest: foreign unmount refused\n");
|
||||
|
||||
Reference in New Issue
Block a user