fs_mac/hfs_boot.rs
1//! Classic-HFS boot-block helpers shared by the CLI and GUI "make bootable"
2//! paths.
3//!
4//! A classic-HFS volume boots only if sector 0 holds valid **boot blocks** —
5//! the 1024-byte (sectors 0–1) region beginning with the `'LK'` (`0x4C4B`)
6//! signature, containing the boot loader the Mac ROM runs before it even
7//! looks at the blessed System Folder. A bare data volume (e.g. a flat HFS
8//! image freshly built, or infinite-mac's "Infinite HD" data disk) has a
9//! zeroed sector 0 and will not boot no matter which folder is blessed.
10//!
11//! Boot-block code is coupled to the System version, so we never synthesize
12//! it. Instead we **copy** the region verbatim from a donor volume that
13//! already boots that System (e.g. the matching stock infinite-mac
14//! `System x.y HD.dsk`). These helpers are the read + validate primitives;
15//! the write side is [`disk_core::filesystem::EditableFilesystem::write_boot_blocks`].
16//!
17//! Everything here is **partition-offset relative**: the boot region is read
18//! from / written to the start of the *HFS volume*, which is byte 0 for a flat
19//! HFV and the `Apple_HFS` partition start for a full (APM) disk. So the same
20//! code makes a flat HFV and the HFS partition of a full disk bootable — the
21//! DDR / partition map / drivers ahead of an APM HFS partition are never
22//! touched.
23
24use std::io::{Read, Seek, SeekFrom};
25
26use disk_core::filesystem::FilesystemError;
27
28/// Classic-HFS boot-block signature: `'LK'` (`0x4C4B`) at the first two bytes
29/// of sector 0 of a bootable volume.
30pub const HFS_BOOT_SIGNATURE: u16 = 0x4C4B;
31
32/// Size of the HFS boot-block region: sectors 0–1, two 512-byte sectors.
33pub const HFS_BOOT_REGION_LEN: usize = 1024;
34
35/// Whether the volume at `offset` begins with the `'LK'` boot-block
36/// signature. A `false` here means the volume has no boot loader; blessing a
37/// System Folder on it is not enough to make it boot.
38pub fn has_boot_blocks<R: Read + Seek>(
39 reader: &mut R,
40 offset: u64,
41) -> Result<bool, FilesystemError> {
42 reader.seek(SeekFrom::Start(offset))?;
43 let mut sig = [0u8; 2];
44 reader.read_exact(&mut sig)?;
45 Ok(u16::from_be_bytes(sig) == HFS_BOOT_SIGNATURE)
46}
47
48/// A donor's boot-block region plus where it was read from, for confirmation
49/// messages.
50#[derive(Debug, Clone)]
51pub struct DonorBootBlocks {
52 /// The validated 1024-byte boot-block region, ready to stage / write.
53 pub blocks: Box<[u8; HFS_BOOT_REGION_LEN]>,
54 /// Byte offset within the donor image the region was read from (0 for a
55 /// flat HFS superfloppy, the Apple_HFS partition start for an APM disk).
56 pub offset: u64,
57}
58
59/// Read and validate the 1024-byte boot-block region of a donor volume whose
60/// HFS volume starts at `offset`. Errors with [`FilesystemError::InvalidData`]
61/// if the region lacks the `'LK'` signature — a donor that isn't itself
62/// bootable has nothing useful to copy.
63pub fn read_donor_boot_blocks<R: Read + Seek>(
64 reader: &mut R,
65 offset: u64,
66) -> Result<Box<[u8; HFS_BOOT_REGION_LEN]>, FilesystemError> {
67 reader.seek(SeekFrom::Start(offset))?;
68 let mut buf = Box::new([0u8; HFS_BOOT_REGION_LEN]);
69 reader.read_exact(buf.as_mut_slice())?;
70 if u16::from_be_bytes([buf[0], buf[1]]) != HFS_BOOT_SIGNATURE {
71 return Err(FilesystemError::InvalidData(
72 "donor volume has no HFS boot blocks ('LK' signature absent at sector 0); \
73 pick a bootable classic-HFS disk to copy them from"
74 .to_string(),
75 ));
76 }
77 Ok(buf)
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use crate::hfs::create_blank_hfs;
84 use std::io::Cursor;
85
86 const MIB: u64 = 1024 * 1024;
87
88 /// A blank HFS volume from `create_blank_hfs` has zeroed boot blocks, so
89 /// it reads as "not bootable" and is rejected as a donor.
90 #[test]
91 fn blank_volume_has_no_boot_blocks() {
92 let img = create_blank_hfs(8 * MIB, 4096, "Blank").unwrap();
93 let mut cur = Cursor::new(img);
94 assert!(!has_boot_blocks(&mut cur, 0).unwrap());
95 let err = read_donor_boot_blocks(&mut cur, 0).unwrap_err();
96 assert!(err.to_string().contains("no HFS boot blocks"));
97 }
98
99 /// Stamping the `'LK'` signature flips the detection and lets the region
100 /// be read as a donor.
101 #[test]
102 fn lk_signature_makes_a_valid_donor() {
103 let mut img = create_blank_hfs(8 * MIB, 4096, "Booty").unwrap();
104 // Write a recognizable boot-block region: 'LK' + a marker byte.
105 img[0] = 0x4C;
106 img[1] = 0x4B;
107 img[2] = 0x60; // typical bbID branch byte on real boot blocks
108 let mut cur = Cursor::new(&mut img);
109 assert!(has_boot_blocks(&mut cur, 0).unwrap());
110
111 let donor = read_donor_boot_blocks(&mut cur, 0).unwrap();
112 assert_eq!(&donor[0..3], &[0x4C, 0x4B, 0x60]);
113 assert_eq!(donor.len(), HFS_BOOT_REGION_LEN);
114 }
115}