xtask/tests/image_formats.rs
//! The three image formats, read back out of the bytes they produce.
//!
//! Every assertion here parses the output rather than checking a length or a
//! return value. A cpio writer that produced 110 bytes of ASCII and got the
//! field order wrong has the right size and the wrong archive; a FAT image with
//! a plausible BPB and a directory entry in the wrong sector mounts as empty.
//!
//! The ISO is booted by `cargo xtask iso --prove`, which is the only assertion
//! that covers all three at once — but a boot failure says "no bootable device"
//! and nothing else, so the field-level checks are what make it debuggable.
use xtask::image_formats::{
cpio_newc, eltorito_catalog, fat16, iso9660_eltorito, CpioEntry, FatFile, MODE_CHAR, MODE_DIR,
MODE_FILE,
};
// ── cpio ────────────────────────────────────────────────────────────────────
/// Parse one `newc` header at `at`, returning its fields and where the next
/// record starts. Written here rather than in the producer so the test is not
/// reading the producer's own idea of the layout.
fn parse_newc(archive: &[u8], at: usize) -> (String, u32, u32, u32, u32, usize) {
assert_eq!(
&archive[at..at + 6],
b"070701",
"a newc record must start with the magic 070701"
);
let field = |i: usize| -> u32 {
let s = std::str::from_utf8(&archive[at + 6 + i * 8..at + 6 + i * 8 + 8]).unwrap();
u32::from_str_radix(s, 16).unwrap()
};
let mode = field(1);
let filesize = field(6);
let rdevmajor = field(9);
let rdevminor = field(10);
let namesize = field(11) as usize;
let name_at = at + 110;
let name = String::from_utf8_lossy(&archive[name_at..name_at + namesize - 1]).into_owned();
let after_name = (name_at + namesize).next_multiple_of(4);
let next = (after_name + filesize as usize).next_multiple_of(4);
(name, mode, filesize, rdevmajor, rdevminor, next)
}
#[test]
fn a_cpio_archive_round_trips_every_field_the_kernel_reads() {
let archive = cpio_newc(&[
CpioEntry::file("init", 0o755, b"ELF...".to_vec()),
CpioEntry::dir("dev", 0o755),
CpioEntry::chardev("dev/console", 0o600, 5, 1),
]);
let (name, mode, size, _, _, next) = parse_newc(&archive, 0);
assert_eq!(
name, "init",
"the first member must not carry a leading slash"
);
assert_eq!(
mode,
MODE_FILE | 0o755,
"/init must be a regular file the kernel can execute"
);
assert_eq!(size, 6);
let (name, mode, size, _, _, next) = parse_newc(&archive, next);
assert_eq!(name, "dev");
assert_eq!(mode, MODE_DIR | 0o755);
assert_eq!(size, 0, "a directory record carries no data");
let (name, mode, size, major, minor, next) = parse_newc(&archive, next);
assert_eq!(name, "dev/console");
assert_eq!(mode, MODE_CHAR | 0o600);
assert_eq!(size, 0);
// The whole point of the entry. Without 5:1 the kernel cannot open a
// console for init and fds 0..2 stay closed.
assert_eq!(
(major, minor),
(5, 1),
"/dev/console is character device 5:1"
);
let (name, _, _, _, _, _) = parse_newc(&archive, next);
assert_eq!(name, "TRAILER!!!", "the archive must end with a trailer");
}
#[test]
fn the_payload_of_a_cpio_member_is_where_the_header_says_it_is() {
// The failure this catches: padding the header and the data with one rule
// instead of two. The first member is then perfect and the second is
// shifted, which reads as a corrupt archive with no clue where.
let archive = cpio_newc(&[
CpioEntry::file("a", 0o644, b"first".to_vec()),
CpioEntry::file("bb", 0o644, b"second".to_vec()),
]);
let (_, _, size, _, _, next) = parse_newc(&archive, 0);
let data_at = (110 + 2usize).next_multiple_of(4);
assert_eq!(&archive[data_at..data_at + size as usize], b"first");
let (name, _, size2, _, _, _) = parse_newc(&archive, next);
assert_eq!(name, "bb");
let data2_at = (next + 110 + 3).next_multiple_of(4);
assert_eq!(&archive[data2_at..data2_at + size2 as usize], b"second");
}
#[test]
fn the_archive_is_deterministic() {
let build = || {
cpio_newc(&[
CpioEntry::file("init", 0o755, vec![7; 100]),
CpioEntry::dir("data", 0o755),
])
};
assert_eq!(build(), build(), "two runs must produce the same bytes");
}
// ── FAT16 ───────────────────────────────────────────────────────────────────
struct Bpb {
bytes_per_sector: usize,
sectors_per_cluster: usize,
reserved: usize,
num_fats: usize,
root_entries: usize,
sectors_per_fat: usize,
total_sectors: usize,
}
fn parse_bpb(img: &[u8]) -> Bpb {
let u16le = |at: usize| u16::from_le_bytes([img[at], img[at + 1]]) as usize;
let total16 = u16le(19);
let total32 = u32::from_le_bytes([img[32], img[33], img[34], img[35]]) as usize;
Bpb {
bytes_per_sector: u16le(11),
sectors_per_cluster: img[13] as usize,
reserved: u16le(14),
num_fats: img[16] as usize,
root_entries: u16le(17),
sectors_per_fat: u16le(22),
total_sectors: if total16 != 0 { total16 } else { total32 },
}
}
/// Find an 8.3 entry in a 32-byte-record directory area, returning
/// `(first_cluster, size, attributes)`.
fn find_entry(dir: &[u8], name: &str) -> Option<(u16, u32, u8)> {
let mut field = [b' '; 11];
let (base, ext) = match name.rsplit_once('.') {
Some((b, x)) => (b, x),
None => (name, ""),
};
for (i, b) in base.bytes().take(8).enumerate() {
field[i] = b.to_ascii_uppercase();
}
for (i, b) in ext.bytes().take(3).enumerate() {
field[8 + i] = b.to_ascii_uppercase();
}
for rec in dir.chunks_exact(32) {
if rec[0] == 0 {
break;
}
if rec[0..11] == field {
return Some((
u16::from_le_bytes([rec[26], rec[27]]),
u32::from_le_bytes([rec[28], rec[29], rec[30], rec[31]]),
rec[11],
));
}
}
None
}
#[test]
fn a_fat16_volume_is_actually_fat16_and_the_files_are_where_the_directory_says() {
let payload: Vec<u8> = (0..70_000u32).map(|i| (i % 251) as u8).collect();
let img = fat16(
&[
FatFile {
path: "EFI/BOOT/BOOTX64.EFI".into(),
bytes: b"MZ this is the loader".to_vec(),
},
FatFile {
path: "vmlinuz".into(),
bytes: payload.clone(),
},
FatFile {
path: "korp/cmdline".into(),
bytes: b"console=ttyS0".to_vec(),
},
],
"GUNNAR",
)
.expect("a legal FAT16 volume");
let bpb = parse_bpb(&img);
assert_eq!(bpb.bytes_per_sector, 512);
assert_eq!(img[510], 0x55);
assert_eq!(img[511], 0xAA, "the boot sector signature is missing");
assert_eq!(&img[54..62], b"FAT16 ");
assert_eq!(
img.len(),
bpb.total_sectors * bpb.bytes_per_sector,
"the image is not the size its own BPB declares"
);
// **The FAT type is derived, not declared.** `"FAT16 "` at offset 0x36 is
// documentation; a driver computes the type from the cluster count, and a
// volume under 4085 clusters is FAT12 however it is labelled. This is the
// assertion the label cannot make.
let root_sectors = (bpb.root_entries * 32) / bpb.bytes_per_sector;
let data_start = bpb.reserved + bpb.num_fats * bpb.sectors_per_fat + root_sectors;
let clusters = (bpb.total_sectors - data_start) / bpb.sectors_per_cluster;
assert!(
(4085..=65_524).contains(&clusters),
"{clusters} clusters is not FAT16: under 4085 a driver reads the FAT as 12-bit and \
over 65524 as FAT32, and the label at 0x36 changes neither"
);
let root_at = data_start.saturating_sub(root_sectors) * bpb.bytes_per_sector;
let root = &img[root_at..root_at + bpb.root_entries * 32];
// The kernel, in the root, with the size the file actually has.
let (cluster, size, attr) = find_entry(root, "vmlinuz").expect("vmlinuz in the root directory");
assert_eq!(size as usize, payload.len());
assert_eq!(attr & 0x10, 0, "vmlinuz must not be marked as a directory");
let at = (data_start + (cluster as usize - 2) * bpb.sectors_per_cluster) * bpb.bytes_per_sector;
assert_eq!(
&img[at..at + payload.len()],
&payload[..],
"the bytes at the cluster the directory entry points to are not the file"
);
// EFI/ is a directory, BOOT/ is inside it, and BOOTX64.EFI is inside that.
// Reached by walking, because a loader that wrote every entry into the root
// and every directory record correctly would pass a flat check.
let (efi_cluster, _, attr) = find_entry(root, "EFI").expect("EFI/ in the root");
assert_eq!(attr & 0x10, 0x10, "EFI must be marked as a directory");
let efi_at =
(data_start + (efi_cluster as usize - 2) * bpb.sectors_per_cluster) * bpb.bytes_per_sector;
let efi = &img[efi_at..efi_at + bpb.sectors_per_cluster * bpb.bytes_per_sector];
assert_eq!(
&efi[0..11],
b". ",
"a subdirectory's first entry must be `.`"
);
assert_eq!(
&efi[32..43],
b".. ",
"a subdirectory's second entry must be `..`"
);
let (boot_cluster, _, _) = find_entry(efi, "BOOT").expect("BOOT/ inside EFI/");
let boot_at =
(data_start + (boot_cluster as usize - 2) * bpb.sectors_per_cluster) * bpb.bytes_per_sector;
let boot = &img[boot_at..boot_at + bpb.sectors_per_cluster * bpb.bytes_per_sector];
let (loader_cluster, loader_size, _) =
find_entry(boot, "BOOTX64.EFI").expect("BOOTX64.EFI inside EFI/BOOT/");
let loader_at = (data_start + (loader_cluster as usize - 2) * bpb.sectors_per_cluster)
* bpb.bytes_per_sector;
assert_eq!(
&img[loader_at..loader_at + loader_size as usize],
b"MZ this is the loader",
"the firmware would load these bytes as EFI/BOOT/BOOTX64.EFI"
);
// Both FAT copies must agree; a driver is entitled to read either.
let fat0 = bpb.reserved * bpb.bytes_per_sector;
let fat1 = (bpb.reserved + bpb.sectors_per_fat) * bpb.bytes_per_sector;
let len = bpb.sectors_per_fat * bpb.bytes_per_sector;
assert_eq!(
&img[fat0..fat0 + len],
&img[fat1..fat1 + len],
"the two FAT copies differ"
);
assert_eq!(
u16::from_le_bytes([img[fat0], img[fat0 + 1]]),
0xFFF8,
"FAT entry 0 must be the media descriptor"
);
}
#[test]
fn a_file_spanning_several_clusters_has_a_chain_that_ends() {
// 70 000 bytes at any legal cluster size is more than one cluster, so this
// exercises the chain rather than the single-cluster case every small file
// takes.
let payload: Vec<u8> = (0..70_000u32).map(|i| (i % 97) as u8).collect();
let img = fat16(
&[FatFile {
path: "vmlinuz".into(),
bytes: payload.clone(),
}],
"GUNNAR",
)
.expect("a legal volume");
let bpb = parse_bpb(&img);
let root_sectors = (bpb.root_entries * 32) / bpb.bytes_per_sector;
let data_start = bpb.reserved + bpb.num_fats * bpb.sectors_per_fat + root_sectors;
let root_at = (bpb.reserved + bpb.num_fats * bpb.sectors_per_fat) * bpb.bytes_per_sector;
let root = &img[root_at..root_at + bpb.root_entries * 32];
let (first, size, _) = find_entry(root, "vmlinuz").expect("vmlinuz");
assert_eq!(size as usize, payload.len());
let fat_at = bpb.reserved * bpb.bytes_per_sector;
let entry = |c: u16| {
u16::from_le_bytes([
img[fat_at + c as usize * 2],
img[fat_at + c as usize * 2 + 1],
])
};
let mut chain = vec![first];
let mut c = first;
// A cycle here would hang a real FAT driver, so the walk is bounded and the
// bound is itself an assertion.
for _ in 0..1000 {
let n = entry(c);
if n >= 0xFFF8 {
break;
}
assert!(n >= 2, "cluster {c} points at a reserved entry {n}");
chain.push(n);
c = n;
}
assert_eq!(
entry(c),
0xFFFF,
"the last cluster of the chain must be an end-of-chain marker"
);
let cluster_bytes = bpb.sectors_per_cluster * bpb.bytes_per_sector;
assert_eq!(
chain.len(),
payload.len().div_ceil(cluster_bytes),
"the chain is not as long as the file needs"
);
// And the bytes really are in those clusters, in order.
let mut got = Vec::new();
for c in &chain {
let at = (data_start + (*c as usize - 2) * bpb.sectors_per_cluster) * bpb.bytes_per_sector;
got.extend_from_slice(&img[at..at + cluster_bytes]);
}
assert_eq!(&got[..payload.len()], &payload[..]);
}
// ── ISO-9660 + El Torito ────────────────────────────────────────────────────
#[test]
fn the_iso_carries_the_three_volume_descriptors_and_a_boot_catalog() {
let esp = vec![0xABu8; 4096];
let iso = iso9660_eltorito("GUNNAR_APPLIANCE", "EFI.IMG", &esp).expect("an ISO");
// The system area is 32 KiB and must be untouched: it is where an isohybrid
// MBR or GPT would go, and writing into it is how an ISO stops being
// convertible to a disk image later.
assert!(
iso[..16 * 2048].iter().all(|b| *b == 0),
"the 32 KiB system area is not empty"
);
let pvd = &iso[16 * 2048..17 * 2048];
assert_eq!(pvd[0], 1, "sector 16 must be the primary volume descriptor");
assert_eq!(&pvd[1..6], b"CD001");
assert_eq!(&pvd[40..56], b"GUNNAR_APPLIANCE");
// Both-endian: a reader is entitled to consult either half.
let space_le = u32::from_le_bytes(pvd[80..84].try_into().unwrap());
let space_be = u32::from_be_bytes(pvd[84..88].try_into().unwrap());
assert_eq!(space_le, space_be, "the both-endian halves disagree");
assert_eq!(
space_le as usize * 2048,
iso.len(),
"the volume space size does not describe the file"
);
let br = &iso[17 * 2048..18 * 2048];
assert_eq!(
br[0], 0,
"sector 17 must be a boot record volume descriptor"
);
assert_eq!(&br[7..30], b"EL TORITO SPECIFICATION");
let catalog_lba = u32::from_le_bytes(br[71..75].try_into().unwrap());
let term = &iso[18 * 2048..19 * 2048];
assert_eq!(term[0], 255, "the descriptor set must be terminated");
// The catalog, found by following the pointer rather than by knowing where
// it was put.
let at = catalog_lba as usize * 2048;
let cat = &iso[at..at + 64];
assert_eq!(cat[0], 0x01, "the validation entry header id must be 1");
assert_eq!(cat[1], 0xEF, "the platform id must be EFI");
assert_eq!((cat[30], cat[31]), (0x55, 0xAA));
let mut sum: u16 = 0;
for i in (0..32).step_by(2) {
sum = sum.wrapping_add(u16::from_le_bytes([cat[i], cat[i + 1]]));
}
assert_eq!(
sum, 0,
"the validation entry checksum is wrong, and firmware ignores a bad catalog SILENTLY"
);
assert_eq!(cat[32], 0x88, "the boot entry must be marked bootable");
assert_eq!(cat[33], 0x00, "no emulation");
let sectors = u16::from_le_bytes([cat[38], cat[39]]) as usize;
let boot_lba = u32::from_le_bytes(cat[40..44].try_into().unwrap()) as usize;
// The trap this exists for: `sector_count` is 512-byte sectors, and the ISO
// block is 2048. Getting it wrong exposes a partition four times too small
// and the failure reads as "no bootable device".
assert_eq!(
sectors * 512,
esp.len(),
"sector_count does not cover the whole boot image"
);
assert_eq!(
&iso[boot_lba * 2048..boot_lba * 2048 + esp.len()],
&esp[..],
"the LBA in the catalog does not point at the boot image"
);
}
#[test]
fn the_boot_image_is_also_a_file_in_the_root_directory() {
let esp = vec![0x5Au8; 8192];
let iso = iso9660_eltorito("GUNNAR_APPLIANCE", "EFI.IMG", &esp).expect("an ISO");
let pvd = &iso[16 * 2048..17 * 2048];
// The root directory record lives inside the PVD at offset 156.
let root_lba = u32::from_le_bytes(pvd[158..162].try_into().unwrap()) as usize;
let dir = &iso[root_lba * 2048..(root_lba + 1) * 2048];
// Walk the records: `.`, `..`, then the file.
let mut at = 0usize;
let mut names = Vec::new();
while dir[at] != 0 {
let len = dir[at] as usize;
let id_len = dir[at + 32] as usize;
names.push(String::from_utf8_lossy(&dir[at + 33..at + 33 + id_len]).into_owned());
at += len;
}
assert_eq!(
names.len(),
3,
"expected `.`, `..` and one file, got {names:?}"
);
assert_eq!(
names[2], "EFI.IMG;1",
"an ISO-9660 file identifier carries its version suffix"
);
}
#[test]
fn an_oversized_boot_image_is_refused_rather_than_silently_truncated() {
// 34 MiB: past what a u16 count of 512-byte sectors can describe. The
// firmware would expose a partition that stops in the middle of the FAT and
// report "no bootable device", so this has to be a build failure.
let huge = vec![0u8; 34 * 1024 * 1024];
let err = iso9660_eltorito("GUNNAR", "EFI.IMG", &huge)
.expect_err("an ISO that cannot describe its own boot image must not be produced");
assert!(
err.contains("sector_count"),
"the error must name the field that cannot hold the value: {err}"
);
}
#[test]
fn the_catalog_checksum_is_recomputed_and_not_copied() {
// Two different boot images must produce two different, individually valid
// catalogs. A checksum computed once and pasted passes the first and fails
// the second.
for len in [2048usize, 1_048_576, 16_777_216] {
let cat = eltorito_catalog(23, len);
let mut sum: u16 = 0;
for i in (0..32).step_by(2) {
sum = sum.wrapping_add(u16::from_le_bytes([cat[i], cat[i + 1]]));
}
assert_eq!(
sum, 0,
"the validation checksum is wrong for a {len}-byte image"
);
assert_eq!(
u16::from_le_bytes([cat[38], cat[39]]) as usize * 512,
len.next_multiple_of(512),
"sector_count must round up, or the last partial sector is unreachable"
);
}
}