//! 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 block 0, 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. //! //! Today: MBR (the four-entry table at offset 446) plus the bare-FAT case (a boot //! sector right at LBA 0). GPT is the next entry in the identity ladder and slots //! in here without touching anything above or below. const std = @import("std"); /// One volume the parser found on the device: the block sub-range it occupies /// and a content identity stable for the volume's life (the mount map keys on /// it; the boot volume is recorded by it). `identity` is derived from the medium, /// never from a port — a moved drive keeps it. pub const Volume = struct { base_lba: u64, block_count: u64, identity: u64, }; /// 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 /// simplest rung of the identity ladder; the fuller rungs (GPT partition GUID, /// FAT volume serial) refine `identityOf` without changing the shape. fn diskSignature(block0: []const u8) u32 { if (block0.len < 444) return 0; return std.mem.readInt(u32, block0[440..444], .little); } /// The identity of the volume at partition index `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. fn identityOf(block0: []const u8, index: u8) u64 { return (@as(u64, diskSignature(block0)) << 8) | index; } /// Whether block 0 looks like a 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; } /// The first volume on a device whose block 0 is `block0` and whose whole-device /// size is `device_blocks`, or null if none is found. An MBR with a non-empty /// entry yields that partition's [start, size); otherwise a boot signature with /// no partitions is treated as a bare FAT spanning the whole device. pub fn firstVolume(block0: []const u8, device_blocks: u64) ?Volume { if (!hasBootSignature(block0)) return null; var index: u8 = 0; while (index < 4) : (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; return .{ .base_lba = start, .block_count = size, .identity = identityOf(block0, index) }; } // No partition entries: a bare FAT spanning the device. return .{ .base_lba = 0, .block_count = device_blocks, .identity = identityOf(block0, 0) }; } 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 v = firstVolume(&block0, 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((@as(u64, 0xDEADBEEF) << 8) | 0, v.identity); } 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 v = firstVolume(&block0, 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; try std.testing.expect(firstVolume(&block0, 65536) == null); }