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{
|
||||
"vfs-test", // the user-space VFS round-trip client
|
||||
"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
|
||||
"shared-memory-server",
|
||||
"shared-memory-client",
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
.@"virtio-gpu" = .{ .path = "system/drivers/virtio-gpu" },
|
||||
.@"vfs-test" = .{ .path = "test/system/services/vfs-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 },
|
||||
.@"shared-memory-server" = .{ .path = "test/system/services/shared-memory-server", .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);
|
||||
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".*);
|
||||
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