exfat: cross-engine discrimination + the exfat-test fixture (S4 step 7)
Each engine now refuses the other's volume at mount(): the exFAT engine rejects a FAT boot sector (a non-zero byte where exFAT keeps MustBeZero), and the FAT engine rejects an exFAT one (MustBeZero reads as a zero bytes-per-sector), each mounting its own as a control. The two engines can never claim the same medium. exfat-test is the mount round-trip client, cloned from fat-test: it waits for /volumes/exfat-e0fa0001, reads the seeded HELLO.TXT, then exercises mkdir + write + rename + read-back + remove through the VFS and reports "exfat-test: ok". It ships as a lazy fixture in test builds (the exfat-volume QEMU case, step 8, drives it). build + zig build test + bounds green.
This commit is contained in:
@@ -333,6 +333,7 @@ pub fn build(b: *std.Build) void {
|
|||||||
if (test_case != null) for ([_][]const u8{
|
if (test_case != null) for ([_][]const u8{
|
||||||
"vfs-test", // the user-space VFS round-trip client
|
"vfs-test", // the user-space VFS round-trip client
|
||||||
"fat-test",
|
"fat-test",
|
||||||
|
"exfat-test", // the exFAT mount round-trip client (S4)
|
||||||
"badge-scope-test", // the guessable-id probe: a second process names the first's node and layer
|
"badge-scope-test", // the guessable-id probe: a second process names the first's node and layer
|
||||||
"shared-memory-server",
|
"shared-memory-server",
|
||||||
"shared-memory-client",
|
"shared-memory-client",
|
||||||
|
|||||||
@@ -65,6 +65,7 @@
|
|||||||
.@"virtio-gpu" = .{ .path = "system/drivers/virtio-gpu" },
|
.@"virtio-gpu" = .{ .path = "system/drivers/virtio-gpu" },
|
||||||
.@"vfs-test" = .{ .path = "test/system/services/vfs-test", .lazy = true },
|
.@"vfs-test" = .{ .path = "test/system/services/vfs-test", .lazy = true },
|
||||||
.@"fat-test" = .{ .path = "test/system/services/fat-test", .lazy = true },
|
.@"fat-test" = .{ .path = "test/system/services/fat-test", .lazy = true },
|
||||||
|
.@"exfat-test" = .{ .path = "test/system/services/exfat-test", .lazy = true },
|
||||||
.@"badge-scope-test" = .{ .path = "test/system/services/badge-scope-test", .lazy = true },
|
.@"badge-scope-test" = .{ .path = "test/system/services/badge-scope-test", .lazy = true },
|
||||||
.@"shared-memory-server" = .{ .path = "test/system/services/shared-memory-server", .lazy = true },
|
.@"shared-memory-server" = .{ .path = "test/system/services/shared-memory-server", .lazy = true },
|
||||||
.@"shared-memory-client" = .{ .path = "test/system/services/shared-memory-client", .lazy = true },
|
.@"shared-memory-client" = .{ .path = "test/system/services/shared-memory-client", .lazy = true },
|
||||||
|
|||||||
@@ -1276,3 +1276,21 @@ test "rename keeps the file's contents under the new name" {
|
|||||||
_ = fs.readFile(renamed, 0, &readback);
|
_ = fs.readFile(renamed, 0, &readback);
|
||||||
try std.testing.expectEqualSlices(u8, &payload, &readback);
|
try std.testing.expectEqualSlices(u8, &payload, &readback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "the exFAT engine rejects a FAT volume (mutual exclusion at mount)" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
const bytes = try allocator.alloc(u8, 128 * sector_size);
|
||||||
|
defer allocator.free(bytes);
|
||||||
|
@memset(bytes, 0);
|
||||||
|
// A FAT-shaped boot sector: an OEM name and a non-zero bytes-per-sector where
|
||||||
|
// exFAT keeps MustBeZero zero, plus the 0x55AA signature.
|
||||||
|
@memcpy(bytes[3..11], "MSWIN4.1");
|
||||||
|
std.mem.writeInt(u16, bytes[11..13], 512, .little); // FAT bytes_per_sector = exFAT MustBeZero
|
||||||
|
bytes[510] = 0x55;
|
||||||
|
bytes[511] = 0xAA;
|
||||||
|
var disk = RamDisk{ .bytes = bytes };
|
||||||
|
try std.testing.expect(FileSystem.mount(disk.device()) == null);
|
||||||
|
// Control: it mounts its own.
|
||||||
|
formatExfat(bytes);
|
||||||
|
try std.testing.expect(FileSystem.mount(disk.device()) != null);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1657,3 +1657,21 @@ test "short-name checksum matches the reference vector" {
|
|||||||
const c = FileSystem.shortChecksum("REDAME TXT".*);
|
const c = FileSystem.shortChecksum("REDAME TXT".*);
|
||||||
try std.testing.expect(a != c);
|
try std.testing.expect(a != c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "the FAT engine rejects an exFAT volume (mutual exclusion at mount)" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
const bytes = try allocator.alloc(u8, 5000 * sector_size);
|
||||||
|
defer allocator.free(bytes);
|
||||||
|
@memset(bytes, 0);
|
||||||
|
// An exFAT boot sector: the "EXFAT " name and 0x55AA, but MustBeZero (offset
|
||||||
|
// 11, where a FAT BPB keeps bytes-per-sector) stays zero — so this engine's
|
||||||
|
// geometryOf reads a zero bytes-per-sector and rejects it.
|
||||||
|
@memcpy(bytes[3..11], "EXFAT ");
|
||||||
|
bytes[on_disk.boot_signature_offset] = 0x55;
|
||||||
|
bytes[on_disk.boot_signature_offset + 1] = 0xAA;
|
||||||
|
var disk = RamDisk{ .bytes = bytes };
|
||||||
|
try std.testing.expect(FileSystem.mount(disk.device()) == null);
|
||||||
|
// Control: a real FAT16 mounts.
|
||||||
|
formatFat16(bytes);
|
||||||
|
try std.testing.expect(FileSystem.mount(disk.device()) != null);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
//! The exfat-test test fixture as a binary package (docs/build-packages-plan.md):
|
||||||
|
//! this file names the binary and EXACTLY the modules its source imports —
|
||||||
|
//! build-support resolves each name from the domains this zon declares.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const build_support = @import("build-support");
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
const exe = build_support.userBinary(b, .{
|
||||||
|
.name = "exfat-test",
|
||||||
|
.root_source_file = b.path("exfat-test.zig"),
|
||||||
|
.imports = &.{ "file-system", "logging", "process", "time" },
|
||||||
|
});
|
||||||
|
b.installArtifact(exe);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
.{
|
||||||
|
.name = .exfat_test,
|
||||||
|
.version = "0.0.0",
|
||||||
|
.fingerprint = 0x77b19e3f7ee43ce3, // Changing this has security and trust implications.
|
||||||
|
.minimum_zig_version = "0.16.0",
|
||||||
|
.dependencies = .{
|
||||||
|
// build-support supplies the shared recipe; kernel is implicit in
|
||||||
|
// every binary (the root shim + link script live there). The rest
|
||||||
|
// are exactly the homes of this binary's declared imports.
|
||||||
|
.@"build-support" = .{ .path = "../../../../build-support" },
|
||||||
|
.kernel = .{ .path = "../../../../library/kernel" },
|
||||||
|
},
|
||||||
|
.paths = .{""},
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
//! test/system/services/exfat-test — a client that proves the exFAT mount end to
|
||||||
|
//! end: it waits for the exfat server to mount the volume at /volumes/exfat-
|
||||||
|
//! e0fa0001, reads the seeded HELLO.TXT off it, and exercises mkdir / write /
|
||||||
|
//! read / rename / remove through the VFS (which routes the id-path to the exfat
|
||||||
|
//! backend). Shipped in the initial_ramdisk; the `exfat-volume` QEMU case spawns
|
||||||
|
//! it alongside init with an exFAT data device attached beside the FAT boot volume.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const fs = @import("file-system");
|
||||||
|
const process = @import("process");
|
||||||
|
const time = @import("time");
|
||||||
|
const logging = @import("logging");
|
||||||
|
|
||||||
|
// The exFAT data device's content id-path — its VolumeSerialNumber is 0xE0FA0001
|
||||||
|
// (the `exfat-volume` case passes --serial E0FA0001 to make-exfat-image.py).
|
||||||
|
const mount = "/volumes/exfat-e0fa0001";
|
||||||
|
|
||||||
|
fn writeLine(comptime fmt: []const u8, arguments: anytype) void {
|
||||||
|
var line: [128]u8 = undefined;
|
||||||
|
_ = logging.write(std.fmt.bufPrint(&line, fmt, arguments) catch return);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main(init: process.Init) void {
|
||||||
|
_ = init;
|
||||||
|
|
||||||
|
// Wait for the exfat server to bring up the USB storage chain and mount.
|
||||||
|
var opened: ?fs.Directory = null;
|
||||||
|
var tries: u32 = 0;
|
||||||
|
while (opened == null and tries < 1400) : (tries += 1) {
|
||||||
|
opened = fs.openDirectory(mount);
|
||||||
|
if (opened == null) time.sleepMillis(50);
|
||||||
|
}
|
||||||
|
var dir = opened orelse {
|
||||||
|
_ = logging.write("exfat-test: " ++ mount ++ " never became available\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
var count: u32 = 0;
|
||||||
|
var entry: fs.Entry = .{};
|
||||||
|
while (dir.next(&entry)) {
|
||||||
|
writeLine("exfat-test: entry '{s}' size={d}\n", .{ entry.name(), entry.size });
|
||||||
|
count += 1;
|
||||||
|
if (count > 32) break;
|
||||||
|
}
|
||||||
|
dir.close();
|
||||||
|
|
||||||
|
// Read the seeded HELLO.TXT (make-exfat-image.py writes "exfat hello danos\n").
|
||||||
|
var read_ok = false;
|
||||||
|
if (fs.open(mount ++ "/HELLO.TXT", .{})) |opened_file| {
|
||||||
|
var file = opened_file;
|
||||||
|
var buf: [32]u8 = undefined;
|
||||||
|
const n = file.read(&buf) orelse 0;
|
||||||
|
file.close();
|
||||||
|
read_ok = std.mem.startsWith(u8, buf[0..n], "exfat hello danos");
|
||||||
|
}
|
||||||
|
if (read_ok) _ = logging.write("exfat-test: read HELLO.TXT ok\n");
|
||||||
|
|
||||||
|
// Mutation through the mount: mkdir, create + write, rename, read back, remove
|
||||||
|
// — proof the write path reaches the engine over a real device.
|
||||||
|
var mut_ok = false;
|
||||||
|
if (fs.makeDirectory(mount ++ "/TESTDIR")) {
|
||||||
|
var wrote = false;
|
||||||
|
if (fs.open(mount ++ "/TESTDIR/W.TXT", .{ .create = true, .truncate = true })) |created| {
|
||||||
|
var f = created;
|
||||||
|
wrote = (f.writeAll("exfat-mutation-ok") orelse 0) == "exfat-mutation-ok".len;
|
||||||
|
f.close();
|
||||||
|
}
|
||||||
|
const renamed = fs.rename(mount ++ "/TESTDIR/W.TXT", mount ++ "/TESTDIR/R.TXT");
|
||||||
|
var readback = false;
|
||||||
|
if (fs.open(mount ++ "/TESTDIR/R.TXT", .{})) |reopened| {
|
||||||
|
var f = reopened;
|
||||||
|
var buf: [32]u8 = undefined;
|
||||||
|
const got = f.read(&buf) orelse 0;
|
||||||
|
f.close();
|
||||||
|
readback = std.mem.eql(u8, buf[0..got], "exfat-mutation-ok");
|
||||||
|
}
|
||||||
|
const removed = fs.remove(mount ++ "/TESTDIR/R.TXT");
|
||||||
|
mut_ok = wrote and renamed and readback and removed;
|
||||||
|
}
|
||||||
|
if (mut_ok) _ = logging.write("exfat-test: mutations ok\n");
|
||||||
|
|
||||||
|
if (read_ok and mut_ok) {
|
||||||
|
while (true) {
|
||||||
|
_ = logging.write("exfat-test: ok\n");
|
||||||
|
time.sleepMillis(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeLine("exfat-test: FAILED (read={} mutations={})\n", .{ read_ok, mut_ok });
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user