//! Partition-table parsing, the policy the storage architecture places above the //! block driver and below the filesystem (docs/file-system-development/ //! storage-architecture.md): read the medium, decide what block sub-ranges are //! volumes, and read each volume's content identity. The block DRIVER never does //! this — it clamps ranges it is told about; this is what tells it the numbers. //! //! Reads happen through a `SectorReader` (not one preloaded block-0 slice) so the //! parser can reach GPT metadata at LBA 1, the entry array beyond it, and each //! partition's VBR on demand. The identity it returns is a tagged `Identity`: the //! `key` is the id (the mount path is derived from it — a stable, unique, //! content-derived handle), and `label` is display metadata (the FAT volume label //! or the GPT partition name), never part of the id. Today's rung is MBR/bare-FAT; //! GPT (rung 1) and the FAT serial (rung 3) slot in without changing the shape. const std = @import("std"); /// A single 512-byte sector's worth of bytes. The parser assumes 512-byte /// logical sectors (4Kn media is a separate concern, noted in the plan). pub const sector_bytes = 512; /// bound: bytes of a volume's display label the parser records (a GPT partition /// name is 36 UTF-16 units; a FAT volume label is 11 bytes; 36 covers both) /// decided-by: hardware /// protects: the Identity.label buffer /// at-limit: degrade - a longer name is truncated to this many ASCII bytes /// observed-by: a volume whose displayed label is clipped pub const label_maximum = 36; /// Which rung of the identity ladder produced this identity. The rung tags the /// `key` namespace so a FAT serial and an MBR signature that happen to share bits /// 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 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 /// mount path is derived from and the mount map keys on. `label` is DISPLAY /// metadata (FAT volume label / GPT partition name), exposed to a UI but never /// part of the path; two volumes with the same label but different keys are /// different volumes. Derived from the medium, never from a port. pub const Identity = struct { rung: Rung, key: u128 = 0, label: [label_maximum]u8 = [_]u8{0} ** label_maximum, label_len: u8 = 0, pub fn labelSlice(self: *const Identity) []const u8 { return self.label[0..self.label_len]; } /// Identity equality is the ID (rung + key) only — the label is display /// metadata and does not enter it. Same rung + same key means the same /// volume (the dd-cloned-media case the duplicate policy is for). pub fn eql(a: Identity, b: Identity) bool { return a.rung == b.rung and a.key == b.key; } }; /// Which filesystem a volume's content is — the key `filesystems.csv` maps to a /// 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; } }; /// One volume the parser found on the device: the block sub-range it occupies, /// its content identity, and which filesystem its content is (the signature the /// `filesystems.csv` map keys on to pick the service binary). pub const Volume = struct { base_lba: u64, block_count: u64, identity: Identity, signature: FilesystemKind = .fat, }; /// Read sectors on demand. `context` + `readFn` mirror the FAT engine's /// `BlockDevice` vtable; `readFn` returns false past the end of the device or on /// an I/O error, which the parser treats as "no volume". pub const SectorReader = struct { context: *anyopaque, readFn: *const fn (context: *anyopaque, lba: u64, buffer: *[sector_bytes]u8) bool, pub fn read(self: SectorReader, lba: u64, buffer: *[sector_bytes]u8) bool { return self.readFn(self.context, lba, buffer); } }; /// The MBR disk signature (offset 440, 4 bytes LE) — a 32-bit id written at /// partition time. Weak (dd-cloned disks share it) but on the medium, and the /// last rung of the identity ladder; the fuller rungs (GPT GUID, FAT serial) /// take precedence when present. fn diskSignature(block0: []const u8) u32 { if (block0.len < 444) return 0; return std.mem.readInt(u32, block0[440..444], .little); } /// The rung-4 identity of the volume at partition `index`: the disk signature /// paired with the index, so two partitions of one disk stay distinct. For a /// bare FAT (no table) the index is 0. Carries no label. fn mbrIdentity(block0: []const u8, index: u8) Identity { return .{ .rung = .mbr_index, .key = (@as(u128, diskSignature(block0)) << 8) | index }; } /// Whether a block looks like a boot sector / partition table (the 0x55AA boot /// signature). A bare FAT also carries it, so the caller distinguishes by whether /// any partition entry is non-empty. fn hasBootSignature(block0: []const u8) bool { return block0.len >= 512 and block0[510] == 0x55 and block0[511] == 0xAA; } /// GPT header signature at LBA 1. const gpt_signature = "EFI PART"; /// bound: GPT partition entries scanned before the prober gives up /// decided-by: ours /// protects: the entry-array scan loop from an untrusted num_partition_entries /// at-limit: degrade - stop scanning; a device whose usable entry sits past the /// cap is treated as having no GPT volume (real tables carry <=128 entries) /// observed-by: the gpt-entry-past-device host test const gpt_entry_scan_maximum = 128; /// Reflected CRC-32 (polynomial 0xEDB88320) — the ISO-HDLC variant GPT uses for /// its header checksum. Inlined so the parser and the host fixtures compute it /// the same way and never drift onto a magic constant. fn crc32(bytes: []const u8) u32 { var c: u32 = 0xFFFFFFFF; for (bytes) |b| { c ^= b; var k: u8 = 0; while (k < 8) : (k += 1) { c = if (c & 1 != 0) (c >> 1) ^ 0xEDB88320 else c >> 1; } } return c ^ 0xFFFFFFFF; } /// A GPT disk carries a protective MBR: a boot-signed block 0 with a partition /// entry of type 0xEE. Its presence routes probing to the GPT (authoritative). fn isProtectiveMbr(block0: []const u8) bool { if (!hasBootSignature(block0)) return false; var index: usize = 0; while (index < 4) : (index += 1) { if (block0[446 + index * 16 + 4] == 0xEE) return true; } return false; } /// Copy the GPT partition name (36 UTF-16LE units, the 72 bytes at entry+56) /// into the identity's display label as ASCII, dropping non-ASCII units. fn setLabelFromUtf16(id: *Identity, name_bytes: []const u8) void { var out: usize = 0; var i: usize = 0; while (i + 1 < name_bytes.len and out < label_maximum) : (i += 2) { const unit = std.mem.readInt(u16, name_bytes[i..][0..2], .little); if (unit == 0) break; if (unit < 0x80) { id.label[out] = @intCast(unit); out += 1; } } id.label_len = @intCast(out); } /// Append every valid GPT volume to `out` (up to `out.len`), returning the count /// (0 if LBA 1 is not a valid GPT header). The header CRC-32 and the per-entry /// overflow-safe range check are the confinement-safety guards the driver's clamp /// rests on — the invariant documented for MBR, extended to untrusted GPT /// metadata. The entry-array CRC is deferred (correctness-only; the range check /// carries safety). fn gptAllVolumes(reader: SectorReader, device_blocks: u64, out: []Volume) usize { var header: [sector_bytes]u8 = undefined; if (!reader.read(1, &header)) return 0; if (!std.mem.eql(u8, header[0..8], gpt_signature)) return 0; const header_size = std.mem.readInt(u32, header[12..16], .little); if (header_size < 92 or header_size > sector_bytes) return 0; const stored_crc = std.mem.readInt(u32, header[16..20], .little); var check: [sector_bytes]u8 = undefined; @memcpy(check[0..header_size], header[0..header_size]); @memset(check[16..20], 0); if (crc32(check[0..header_size]) != stored_crc) return 0; const entry_lba = std.mem.readInt(u64, header[72..80], .little); const num_entries = std.mem.readInt(u32, header[80..84], .little); const entry_size = std.mem.readInt(u32, header[84..88], .little); if (entry_size != 128 and entry_size != 256 and entry_size != 512) return 0; if (entry_lba == 0 or entry_lba >= device_blocks) return 0; const scan = @min(num_entries, gpt_entry_scan_maximum); var sector_buf: [sector_bytes]u8 = undefined; var loaded: u64 = std.math.maxInt(u64); var count: usize = 0; var i: u32 = 0; while (i < scan and count < out.len) : (i += 1) { const abs = @as(u64, i) * entry_size; const lba = entry_lba + abs / sector_bytes; const off = @as(usize, @intCast(abs % sector_bytes)); if (lba != loaded) { if (!reader.read(lba, §or_buf)) break; // return what we have loaded = lba; } const entry = sector_buf[off..][0..128]; // the fields we read live in the first 128 bytes var type_nonzero = false; for (entry[0..16]) |b| { if (b != 0) { type_nonzero = true; break; } } if (!type_nonzero) continue; const start = std.mem.readInt(u64, entry[32..40], .little); const end = std.mem.readInt(u64, entry[40..48], .little); // inclusive last LBA // Untrusted range from removable media: overflow-safe validation. Reject a // partition that starts at 0, is reversed, or ends outside the device; only // then is start + count <= device_blocks guaranteed for the driver's clamp. 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, .signature = signatureAt(reader, start) }; count += 1; } return count; } /// Trim trailing spaces (FAT labels are space-padded) and copy into the display /// label, clamped to label_maximum. fn setFatLabel(id: *Identity, label: []const u8) void { var end: usize = label.len; while (end > 0 and label[end - 1] == ' ') : (end -= 1) {} const n = @min(end, label_maximum); @memcpy(id.label[0..n], label[0..n]); id.label_len = @intCast(n); } /// The FAT volume serial (BS_VolID) + label (BS_VolLab) read from the VBR at /// `start_lba` — rung 3, stronger than the MBR disk signature. Null if the /// sector is not an extended FAT boot record (no 0x55AA, or no 0x28/0x29 /// extended boot signature). FAT32 is distinguished by fat_size_16 == 0; the /// serial and label live at different EBR offsets for FAT12/16 vs FAT32 (the /// offsets are cross-checked against system/services/fat/on-disk.zig). fn fatIdentity(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; const is_fat32 = std.mem.readInt(u16, vbr[22..24], .little) == 0; const sig_off: usize = if (is_fat32) 66 else 38; if (vbr[sig_off] != 0x28 and vbr[sig_off] != 0x29) return null; const id_off: usize = if (is_fat32) 67 else 39; const label_off: usize = if (is_fat32) 71 else 43; var id = Identity{ .rung = .fat_serial, .key = std.mem.readInt(u32, vbr[id_off..][0..4], .little) }; setFatLabel(&id, vbr[label_off..][0..11]); 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 /// final. Otherwise every fitting MBR entry is a volume; a boot signature with no /// partition entries is a bare FAT spanning the whole device. Each volume's /// [start, count) is validated overflow-safe (the confinement invariant the /// driver's clamp rests on), and each prefers its FAT serial identity over the /// disk signature. pub fn allVolumes(reader: SectorReader, device_blocks: u64, out: []Volume) usize { var block0: [sector_bytes]u8 = undefined; if (!reader.read(0, &block0)) return 0; if (!hasBootSignature(&block0)) return 0; if (isProtectiveMbr(&block0)) return gptAllVolumes(reader, device_blocks, out); var count: usize = 0; var index: u8 = 0; while (index < 4 and count < out.len) : (index += 1) { const entry = block0[446 + @as(usize, index) * 16 ..][0..16]; const kind = entry[4]; const start = std.mem.readInt(u32, entry[8..12], .little); const size = std.mem.readInt(u32, entry[12..16], .little); if (kind == 0 or start == 0 or size == 0) continue; // These bytes come off an untrusted removable medium. A partition that // does not fit inside the device is not a partition — skip it. This is // where the driver's confinement-safety invariant is established: the // clamp's overflow-safety rests on base + count staying inside the // 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; 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 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; } /// firstVolume is allVolumes into a one-element buffer. const one_volume_slot = 1; /// The first volume on the device, or null — the single-volume case of /// `allVolumes`, kept for callers that want just one. pub fn firstVolume(reader: SectorReader, device_blocks: u64) ?Volume { var one: [one_volume_slot]Volume = undefined; return if (allVolumes(reader, device_blocks, &one) > 0) one[0] else null; } /// A read-only RAM disk over a byte slice of sectors, for the host tests. const RamDisk = struct { sectors: []const u8, fn readFn(context: *anyopaque, lba: u64, buffer: *[sector_bytes]u8) bool { const self: *const RamDisk = @ptrCast(@alignCast(context)); const off = lba * sector_bytes; if (off + sector_bytes > self.sectors.len) return false; @memcpy(buffer, self.sectors[off..][0..sector_bytes]); return true; } fn reader(self: *const RamDisk) SectorReader { return .{ .context = @constCast(self), .readFn = readFn }; } }; test "an MBR with one partition yields its range and a distinct identity" { var block0 = [_]u8{0} ** 512; block0[510] = 0x55; block0[511] = 0xAA; std.mem.writeInt(u32, block0[440..444], 0xDEADBEEF, .little); // partition 0: type 0x0c (FAT32 LBA), start 2048, size 100000 block0[446 + 4] = 0x0c; std.mem.writeInt(u32, block0[446 + 8 ..][0..4], 2048, .little); std.mem.writeInt(u32, block0[446 + 12 ..][0..4], 100000, .little); const disk = RamDisk{ .sectors = &block0 }; const v = firstVolume(disk.reader(), 200000).?; try std.testing.expectEqual(@as(u64, 2048), v.base_lba); try std.testing.expectEqual(@as(u64, 100000), v.block_count); try std.testing.expectEqual(Rung.mbr_index, v.identity.rung); try std.testing.expectEqual((@as(u128, 0xDEADBEEF) << 8) | 0, v.identity.key); } test "a boot signature with no partitions is a bare FAT over the whole device" { var block0 = [_]u8{0} ** 512; block0[510] = 0x55; block0[511] = 0xAA; const disk = RamDisk{ .sectors = &block0 }; const v = firstVolume(disk.reader(), 65536).?; try std.testing.expectEqual(@as(u64, 0), v.base_lba); try std.testing.expectEqual(@as(u64, 65536), v.block_count); } test "no boot signature is no volume" { const block0 = [_]u8{0} ** 512; 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; block0[510] = 0x55; block0[511] = 0xAA; // partition 0: start 0xFFFFFF00, size 0x400 — far past a 200000-block device. block0[446 + 4] = 0x0c; std.mem.writeInt(u32, block0[446 + 8 ..][0..4], 0xFFFFFF00, .little); std.mem.writeInt(u32, block0[446 + 12 ..][0..4], 0x400, .little); // partition 1: start 2048, size 1000 — fits. block0[462 + 4] = 0x0c; std.mem.writeInt(u32, block0[462 + 8 ..][0..4], 2048, .little); std.mem.writeInt(u32, block0[462 + 12 ..][0..4], 1000, .little); const disk = RamDisk{ .sectors = &block0 }; const v = firstVolume(disk.reader(), 200000).?; try std.testing.expectEqual(@as(u64, 2048), v.base_lba); // the fitting one, not the overflowing one try std.testing.expectEqual(@as(u64, 1000), v.block_count); } /// A single 128-byte GPT partition entry for the tests. fn gptEntry(type_nonzero: bool, unique_guid: u128, start: u64, end: u64) [128]u8 { var e = [_]u8{0} ** 128; if (type_nonzero) e[0] = 0x01; // any non-zero byte makes the type GUID non-zero std.mem.writeInt(u128, e[16..32], unique_guid, .little); std.mem.writeInt(u64, e[32..40], start, .little); std.mem.writeInt(u64, e[40..48], end, .little); return e; } /// Lay out a disk with `entry_size`-spaced GPT entries: protective MBR (LBA 0), /// GPT header with a correct CRC (LBA 1), the entry array (LBA 2+). fn buildGptDiskSized(disk: []u8, entries: []const [128]u8, entry_size: u32) void { @memset(disk, 0); disk[510] = 0x55; disk[511] = 0xAA; disk[446 + 4] = 0xEE; // protective entry type std.mem.writeInt(u32, disk[446 + 8 ..][0..4], 1, .little); std.mem.writeInt(u32, disk[446 + 12 ..][0..4], 0xFFFFFFFF, .little); const h = disk[sector_bytes..][0..sector_bytes]; @memcpy(h[0..8], gpt_signature); std.mem.writeInt(u32, h[12..16], 92, .little); // header_size std.mem.writeInt(u64, h[72..80], 2, .little); // partition_entry_lba std.mem.writeInt(u32, h[80..84], @intCast(entries.len), .little); std.mem.writeInt(u32, h[84..88], entry_size, .little); // size_of_partition_entry @memset(h[16..20], 0); std.mem.writeInt(u32, h[16..20], crc32(h[0..92]), .little); const step: usize = @intCast(entry_size); var i: usize = 0; while (i < entries.len) : (i += 1) { const abs = 2 * sector_bytes + i * step; @memcpy(disk[abs..][0..128], &entries[i]); } } /// The common 128-byte-entry case. fn buildGptDisk(disk: []u8, entries: []const [128]u8) void { buildGptDiskSized(disk, entries, 128); } test "a GPT disk yields the partition GUID as the identity id" { var disk = [_]u8{0} ** (4 * sector_bytes); const guid: u128 = 0x112233445566778899AABBCCDDEEFF00; const entries = [_][128]u8{gptEntry(true, guid, 2048, 4095)}; buildGptDisk(&disk, &entries); const rd = RamDisk{ .sectors = &disk }; const v = firstVolume(rd.reader(), 200000).?; try std.testing.expectEqual(@as(u64, 2048), v.base_lba); try std.testing.expectEqual(@as(u64, 2048), v.block_count); // 4095 - 2048 + 1 try std.testing.expectEqual(Rung.gpt_guid, v.identity.rung); try std.testing.expectEqual(guid, v.identity.key); } test "a GPT entry past the device is skipped; an all-out-of-range table is no volume" { var disk = [_]u8{0} ** (4 * sector_bytes); const entries = [_][128]u8{ gptEntry(true, 0xAAA, 2048, 999999), // ends past a 200000-block device gptEntry(true, 0xBBB, 4096, 8191), // fits }; buildGptDisk(&disk, &entries); const rd = RamDisk{ .sectors = &disk }; const v = firstVolume(rd.reader(), 200000).?; try std.testing.expectEqual(@as(u64, 4096), v.base_lba); // the fitting one, not the overflowing one try std.testing.expectEqual(@as(u128, 0xBBB), v.identity.key); var solo_disk = [_]u8{0} ** (4 * sector_bytes); const solo = [_][128]u8{gptEntry(true, 0xAAA, 2048, 999999)}; buildGptDisk(&solo_disk, &solo); const rd2 = RamDisk{ .sectors = &solo_disk }; try std.testing.expect(firstVolume(rd2.reader(), 200000) == null); } test "a protective MBR with a broken GPT header is not a volume" { var disk = [_]u8{0} ** (4 * sector_bytes); const entries = [_][128]u8{gptEntry(true, 0xCCC, 2048, 4095)}; buildGptDisk(&disk, &entries); disk[sector_bytes] = 'X'; // wreck the 'EFI PART' signature const rd = RamDisk{ .sectors = &disk }; try std.testing.expect(firstVolume(rd.reader(), 200000) == null); var bad_crc = [_]u8{0} ** (4 * sector_bytes); buildGptDisk(&bad_crc, &entries); bad_crc[sector_bytes + 16] ^= 0xFF; // corrupt a header-CRC byte const rd2 = RamDisk{ .sectors = &bad_crc }; try std.testing.expect(firstVolume(rd2.reader(), 200000) == null); } test "a bare FAT32 reports its volume serial and label as the identity" { var block0 = [_]u8{0} ** 512; block0[510] = 0x55; block0[511] = 0xAA; std.mem.writeInt(u16, block0[22..24], 0, .little); // fat_size_16 == 0 → FAT32 block0[66] = 0x29; // FAT32 extended boot signature std.mem.writeInt(u32, block0[67..71], 0x12345678, .little); // BS_VolID @memcpy(block0[71..82], "DANOS "); // BS_VolLab, space-padded to 11 const disk = RamDisk{ .sectors = &block0 }; const v = firstVolume(disk.reader(), 65536).?; try std.testing.expectEqual(@as(u64, 0), v.base_lba); try std.testing.expectEqual(Rung.fat_serial, v.identity.rung); try std.testing.expectEqual(@as(u128, 0x12345678), v.identity.key); try std.testing.expectEqualStrings("DANOS", v.identity.labelSlice()); } test "an MBR FAT partition prefers the volume serial; a non-FAT partition keeps rung 4" { var disk = [_]u8{0} ** (3 * 512); disk[510] = 0x55; disk[511] = 0xAA; std.mem.writeInt(u32, disk[440..444], 0xDEADBEEF, .little); disk[446 + 4] = 0x0c; // FAT32-LBA partition std.mem.writeInt(u32, disk[446 + 8 ..][0..4], 1, .little); // start LBA 1 std.mem.writeInt(u32, disk[446 + 12 ..][0..4], 2, .little); // size 2 const vbr = disk[512..][0..512]; // a FAT16 VBR at the partition start vbr[510] = 0x55; vbr[511] = 0xAA; std.mem.writeInt(u16, vbr[22..24], 0x0080, .little); // fat_size_16 != 0 → FAT16 vbr[38] = 0x29; // FAT12/16 extended boot signature std.mem.writeInt(u32, vbr[39..43], 0xCAFEBABE, .little); @memcpy(vbr[43..54], "MYVOL "); const rd = RamDisk{ .sectors = &disk }; const v = firstVolume(rd.reader(), 200000).?; try std.testing.expectEqual(@as(u64, 1), v.base_lba); try std.testing.expectEqual(Rung.fat_serial, v.identity.rung); try std.testing.expectEqual(@as(u128, 0xCAFEBABE), v.identity.key); try std.testing.expectEqualStrings("MYVOL", v.identity.labelSlice()); // A partition whose VBR is not an extended FAT falls back to the rung-4 id. var plain = [_]u8{0} ** (3 * 512); @memcpy(plain[0..512], disk[0..512]); // same MBR; LBA 1 left blank const rd2 = RamDisk{ .sectors = &plain }; const v2 = firstVolume(rd2.reader(), 200000).?; try std.testing.expectEqual(Rung.mbr_index, v2.identity.rung); try std.testing.expectEqual((@as(u128, 0xDEADBEEF) << 8) | 0, v2.identity.key); } test "GPT with 256-byte entries reads the non-128 offset arithmetic correctly" { // With entry_size 256, entry 1 lands at offset 256 of the same sector (LBA 2). // Put the only valid entry at index 1 so the off = (i*entry_size) % 512 path // (256, not 0) is exercised — the sharp edge the 128-byte tests never hit. var disk = [_]u8{0} ** (5 * sector_bytes); const entries = [_][128]u8{ gptEntry(false, 0, 0, 0), // index 0: unused (type GUID zero) gptEntry(true, 0xF00D, 4096, 8191), // index 1: at offset 256 }; buildGptDiskSized(&disk, &entries, 256); const rd = RamDisk{ .sectors = &disk }; const v = firstVolume(rd.reader(), 200000).?; try std.testing.expectEqual(@as(u64, 4096), v.base_lba); try std.testing.expectEqual(Rung.gpt_guid, v.identity.rung); try std.testing.expectEqual(@as(u128, 0xF00D), v.identity.key); } // A fixture-sized volume buffer for the multi-volume tests, named so the bounds // gate (which flags literal array lengths) stays quiet: a test input. const test_volume_slots = 4; test "allVolumes returns every fitting MBR partition with distinct identities" { var block0 = [_]u8{0} ** 512; block0[510] = 0x55; block0[511] = 0xAA; std.mem.writeInt(u32, block0[440..444], 0xDEADBEEF, .little); // partition 0: start 2048, size 1000 block0[446 + 4] = 0x0c; std.mem.writeInt(u32, block0[446 + 8 ..][0..4], 2048, .little); std.mem.writeInt(u32, block0[446 + 12 ..][0..4], 1000, .little); // partition 1: start 4096, size 2000 block0[462 + 4] = 0x0c; std.mem.writeInt(u32, block0[462 + 8 ..][0..4], 4096, .little); std.mem.writeInt(u32, block0[462 + 12 ..][0..4], 2000, .little); const disk = RamDisk{ .sectors = &block0 }; var vols: [test_volume_slots]Volume = undefined; const n = allVolumes(disk.reader(), 200000, &vols); try std.testing.expectEqual(@as(usize, 2), n); // both partitions, not just the first try std.testing.expectEqual(@as(u64, 2048), vols[0].base_lba); try std.testing.expectEqual(@as(u64, 4096), vols[1].base_lba); // distinct rung-4 identities (no FAT VBR at those LBAs): index 0 vs 1. try std.testing.expectEqual((@as(u128, 0xDEADBEEF) << 8) | 0, vols[0].identity.key); try std.testing.expectEqual((@as(u128, 0xDEADBEEF) << 8) | 1, vols[1].identity.key); // firstVolume (the 1-buffer case) still returns just the first. try std.testing.expectEqual(@as(u64, 2048), firstVolume(disk.reader(), 200000).?.base_lba); }