volume-manager: recognize exFAT and route it by content (S4 step 6)
partition.zig gains an exFAT VBR recognizer: the "EXFAT " name (where a FAT BPB keeps its OEM string, so the two never collide) plus the 0x55AA signature, with VolumeSerialNumber (offset 100) as a new exfat_serial identity rung. A `recognize` helper tries exFAT, then FAT, then the MBR disk-signature fallback, and sets each volume's FilesystemKind — so allVolumes/firstVolume and the GPT path all tag a volume with the engine its content needs. volume-map renders exfat-<serial> as the id-path, and filesystems.csv adds the exfat -> /system/services/exfat row: an exFAT stick now spawns the exFAT service, at its own content id-path. Host tests: a bare exFAT volume recognized with its serial; a FAT VBR still recognized as fat (the discrimination); the exfat-<serial> id render. build + zig build test + bounds green.
This commit is contained in:
@@ -31,10 +31,11 @@ pub const label_maximum = 36;
|
||||
/// stay distinct, and it drives how the mount path is rendered from the id.
|
||||
pub const Rung = enum(u8) {
|
||||
gpt_guid = 1,
|
||||
filesystem_uuid = 2, // reserved: no non-FAT engine reads a superblock UUID yet
|
||||
filesystem_uuid = 2, // reserved: no engine reads a superblock UUID yet
|
||||
fat_serial = 3,
|
||||
mbr_index = 4,
|
||||
anonymous = 5,
|
||||
exfat_serial = 6, // exFAT's VolumeSerialNumber — content-strong like fat_serial
|
||||
};
|
||||
|
||||
/// A volume's content identity. `key` is the ID — the stable, unique handle the
|
||||
@@ -61,15 +62,16 @@ pub const Identity = struct {
|
||||
};
|
||||
|
||||
/// Which filesystem a volume's content is — the key `filesystems.csv` maps to a
|
||||
/// service binary. Today only FAT is recognized (S4 adds exFAT with a real VBR
|
||||
/// recognizer); until then every probed volume is `.fat`, matching the volume
|
||||
/// manager's historical hand-off of everything to the FAT service.
|
||||
/// service binary. FAT and exFAT are recognized by their VBRs; content that is
|
||||
/// neither falls back to `.fat`, the volume manager's historical hand-off.
|
||||
pub const FilesystemKind = enum {
|
||||
fat,
|
||||
exfat,
|
||||
unknown,
|
||||
|
||||
pub fn fromToken(token: []const u8) FilesystemKind {
|
||||
if (std.mem.eql(u8, token, "fat")) return .fat;
|
||||
if (std.mem.eql(u8, token, "exfat")) return .exfat;
|
||||
return .unknown;
|
||||
}
|
||||
};
|
||||
@@ -226,7 +228,7 @@ fn gptAllVolumes(reader: SectorReader, device_blocks: u64, out: []Volume) usize
|
||||
if (start == 0 or end < start or end >= device_blocks) continue;
|
||||
var id = Identity{ .rung = .gpt_guid, .key = std.mem.readInt(u128, entry[16..32], .little) };
|
||||
setLabelFromUtf16(&id, entry[56..128]);
|
||||
out[count] = .{ .base_lba = start, .block_count = end - start + 1, .identity = id };
|
||||
out[count] = .{ .base_lba = start, .block_count = end - start + 1, .identity = id, .signature = signatureAt(reader, start) };
|
||||
count += 1;
|
||||
}
|
||||
return count;
|
||||
@@ -262,6 +264,36 @@ fn fatIdentity(reader: SectorReader, start_lba: u64) ?Identity {
|
||||
return id;
|
||||
}
|
||||
|
||||
/// The exFAT VolumeSerialNumber (offset 100) read from the Main Boot Sector at
|
||||
/// `start_lba` — its content identity, rung `exfat_serial`. Null unless the sector
|
||||
/// is an exFAT VBR (the "EXFAT " name at offset 3 + the 0x55AA signature; the
|
||||
/// name is where a FAT BPB keeps its OEM string, so the two never collide). The
|
||||
/// label lives in a root-directory entry, not the VBR, so it is left empty here.
|
||||
fn exfatIdentity(reader: SectorReader, start_lba: u64) ?Identity {
|
||||
var vbr: [sector_bytes]u8 = undefined;
|
||||
if (!reader.read(start_lba, &vbr)) return null;
|
||||
if (vbr[510] != 0x55 or vbr[511] != 0xAA) return null;
|
||||
if (!std.mem.eql(u8, vbr[3..11], "EXFAT ")) return null;
|
||||
return .{ .rung = .exfat_serial, .key = std.mem.readInt(u32, vbr[100..104], .little) };
|
||||
}
|
||||
|
||||
const Recognized = struct { identity: Identity, signature: FilesystemKind };
|
||||
|
||||
/// Recognize the filesystem at `start_lba` by its VBR: exFAT first (its serial and
|
||||
/// the `.exfat` signature), else FAT (its serial), else unknown content that keeps
|
||||
/// the MBR disk-signature identity and the historical `.fat` hand-off.
|
||||
fn recognize(reader: SectorReader, start_lba: u64, block0: *const [sector_bytes]u8, index: u8) Recognized {
|
||||
if (exfatIdentity(reader, start_lba)) |id| return .{ .identity = id, .signature = .exfat };
|
||||
if (fatIdentity(reader, start_lba)) |id| return .{ .identity = id, .signature = .fat };
|
||||
return .{ .identity = mbrIdentity(block0, index), .signature = .fat };
|
||||
}
|
||||
|
||||
/// The filesystem signature at `start_lba` when the identity is decided elsewhere
|
||||
/// (a GPT partition keeps its GUID identity but still needs its content's kind).
|
||||
fn signatureAt(reader: SectorReader, start_lba: u64) FilesystemKind {
|
||||
return if (exfatIdentity(reader, start_lba) != null) .exfat else .fat;
|
||||
}
|
||||
|
||||
/// Append every volume on the device `reader` addresses, whose whole-device size
|
||||
/// is `device_blocks`, to `out` (up to `out.len`), returning the count. A GPT
|
||||
/// disk (protective MBR) is enumerated by GPT, authoritatively — a zero count is
|
||||
@@ -290,12 +322,14 @@ pub fn allVolumes(reader: SectorReader, device_blocks: u64, out: []Volume) usize
|
||||
// device (usb-storage.zig resolveTransfer), which only holds because the
|
||||
// range handed down is validated here. The subtraction cannot overflow.
|
||||
if (start > device_blocks or device_blocks - start < size) continue;
|
||||
out[count] = .{ .base_lba = start, .block_count = size, .identity = fatIdentity(reader, start) orelse mbrIdentity(&block0, index) };
|
||||
const found = recognize(reader, start, &block0, index);
|
||||
out[count] = .{ .base_lba = start, .block_count = size, .identity = found.identity, .signature = found.signature };
|
||||
count += 1;
|
||||
}
|
||||
if (count == 0 and out.len > 0) {
|
||||
// No partition entries: a bare FAT spanning the device.
|
||||
out[0] = .{ .base_lba = 0, .block_count = device_blocks, .identity = fatIdentity(reader, 0) orelse mbrIdentity(&block0, 0) };
|
||||
// No partition entries: a bare FAT or exFAT spanning the device.
|
||||
const found = recognize(reader, 0, &block0, 0);
|
||||
out[0] = .{ .base_lba = 0, .block_count = device_blocks, .identity = found.identity, .signature = found.signature };
|
||||
return 1;
|
||||
}
|
||||
return count;
|
||||
@@ -360,6 +394,31 @@ test "no boot signature is no volume" {
|
||||
const disk = RamDisk{ .sectors = &block0 };
|
||||
try std.testing.expect(firstVolume(disk.reader(), 65536) == null);
|
||||
}
|
||||
test "a bare exFAT volume is recognized by its VBR, with its serial as the id" {
|
||||
var block0 = [_]u8{0} ** 512;
|
||||
@memcpy(block0[3..11], "EXFAT ");
|
||||
block0[510] = 0x55;
|
||||
block0[511] = 0xAA;
|
||||
std.mem.writeInt(u32, block0[100..104], 0xDA7A0001, .little); // VolumeSerialNumber
|
||||
const disk = RamDisk{ .sectors = &block0 };
|
||||
const v = firstVolume(disk.reader(), 65536).?;
|
||||
try std.testing.expectEqual(FilesystemKind.exfat, v.signature);
|
||||
try std.testing.expectEqual(Rung.exfat_serial, v.identity.rung);
|
||||
try std.testing.expectEqual(@as(u128, 0xDA7A0001), v.identity.key);
|
||||
}
|
||||
test "a FAT VBR is recognized as fat, not exfat — the signatures never collide" {
|
||||
var block0 = [_]u8{0} ** 512;
|
||||
@memcpy(block0[3..11], "MSWIN4.1"); // a FAT OEM name, not "EXFAT "
|
||||
block0[510] = 0x55;
|
||||
block0[511] = 0xAA;
|
||||
std.mem.writeInt(u16, block0[22..24], 16, .little); // fat_size_16 != 0 -> FAT16 shape
|
||||
block0[38] = 0x29; // extended boot signature
|
||||
std.mem.writeInt(u32, block0[39..43], 0x12345678, .little); // volume id
|
||||
const disk = RamDisk{ .sectors = &block0 };
|
||||
const v = firstVolume(disk.reader(), 65536).?;
|
||||
try std.testing.expectEqual(FilesystemKind.fat, v.signature);
|
||||
try std.testing.expectEqual(Rung.fat_serial, v.identity.rung);
|
||||
}
|
||||
|
||||
test "a partition that runs past the device is skipped, not trusted" {
|
||||
var block0 = [_]u8{0} ** 512;
|
||||
|
||||
Reference in New Issue
Block a user