use super::table::FatKind;
use alloc::format;
pub const BOOT_SECTOR_SIZE: usize = 512;
const EXT_BOOT_SIGNATURE: u8 = 0x29;
#[derive(Debug, Clone)]
pub struct BootSector {
pub kind: FatKind,
pub bytes_per_sector: u16,
pub sectors_per_cluster: u8,
pub reserved_sector_count: u16,
pub num_fats: u8,
pub root_entry_count: u16,
pub media: u8,
pub sectors_per_track: u16,
pub num_heads: u16,
pub hidden_sectors: u32,
pub total_sectors: u32,
pub fat_size: u32,
pub root_cluster: u32,
pub fs_info_sector: u16,
pub backup_boot_sector: u16,
pub drive_number: u8,
pub volume_id: u32,
pub volume_label: [u8; 11],
}
impl BootSector {
pub fn defaults_for(kind: FatKind) -> Self {
let fat32 = kind == FatKind::Fat32;
Self {
kind,
bytes_per_sector: 512,
sectors_per_cluster: 1,
reserved_sector_count: if fat32 { 32 } else { 1 },
num_fats: 2,
root_entry_count: if fat32 { 0 } else { 512 },
media: 0xF8,
sectors_per_track: 32,
num_heads: 8,
hidden_sectors: 0,
total_sectors: 0,
fat_size: 0,
root_cluster: if fat32 { 2 } else { 0 },
fs_info_sector: if fat32 { 1 } else { 0 },
backup_boot_sector: if fat32 { 6 } else { 0 },
drive_number: 0x80,
volume_id: 0,
volume_label: *b"NO NAME ",
}
}
pub fn root_dir_sectors(&self) -> u32 {
let bytes = u32::from(self.root_entry_count) * 32;
bytes.div_ceil(u32::from(self.bytes_per_sector))
}
pub fn root_dir_start_sector(&self) -> u32 {
self.reserved_sector_count as u32 + self.num_fats as u32 * self.fat_size
}
pub fn data_start_sector(&self) -> u32 {
self.root_dir_start_sector() + self.root_dir_sectors()
}
pub fn cluster_count(&self) -> u32 {
let data_sectors = self.total_sectors.saturating_sub(self.data_start_sector());
data_sectors / self.sectors_per_cluster as u32
}
pub fn encode(&self) -> [u8; BOOT_SECTOR_SIZE] {
let fat32 = self.kind == FatKind::Fat32;
let mut b = [0u8; BOOT_SECTOR_SIZE];
b[0..3].copy_from_slice(&[0xEB, if fat32 { 0x58 } else { 0x3C }, 0x90]);
b[3..11].copy_from_slice(b"fstool ");
b[11..13].copy_from_slice(&self.bytes_per_sector.to_le_bytes());
b[13] = self.sectors_per_cluster;
b[14..16].copy_from_slice(&self.reserved_sector_count.to_le_bytes());
b[16] = self.num_fats;
b[17..19].copy_from_slice(&self.root_entry_count.to_le_bytes());
let use_16 = !fat32 && self.total_sectors < 0x1_0000;
if use_16 {
b[19..21].copy_from_slice(&(self.total_sectors as u16).to_le_bytes());
} else {
b[32..36].copy_from_slice(&self.total_sectors.to_le_bytes());
}
b[21] = self.media;
if !fat32 {
b[22..24].copy_from_slice(&(self.fat_size as u16).to_le_bytes());
}
b[24..26].copy_from_slice(&self.sectors_per_track.to_le_bytes());
b[26..28].copy_from_slice(&self.num_heads.to_le_bytes());
b[28..32].copy_from_slice(&self.hidden_sectors.to_le_bytes());
if fat32 {
b[36..40].copy_from_slice(&self.fat_size.to_le_bytes());
b[44..48].copy_from_slice(&self.root_cluster.to_le_bytes());
b[48..50].copy_from_slice(&self.fs_info_sector.to_le_bytes());
b[50..52].copy_from_slice(&self.backup_boot_sector.to_le_bytes());
b[64] = self.drive_number;
b[66] = EXT_BOOT_SIGNATURE;
b[67..71].copy_from_slice(&self.volume_id.to_le_bytes());
b[71..82].copy_from_slice(&self.volume_label);
b[82..90].copy_from_slice(self.kind.fs_type_label());
} else {
b[36] = self.drive_number;
b[38] = EXT_BOOT_SIGNATURE;
b[39..43].copy_from_slice(&self.volume_id.to_le_bytes());
b[43..54].copy_from_slice(&self.volume_label);
b[54..62].copy_from_slice(self.kind.fs_type_label());
}
b[510] = 0x55;
b[511] = 0xAA;
b
}
pub fn decode(b: &[u8; BOOT_SECTOR_SIZE]) -> crate::Result<Self> {
if b[510] != 0x55 || b[511] != 0xAA {
return Err(crate::Error::InvalidImage(
"fat: missing 0x55AA boot-sector signature".into(),
));
}
let bytes_per_sector = u16::from_le_bytes(b[11..13].try_into().unwrap());
if !(512..=4096).contains(&bytes_per_sector) || !bytes_per_sector.is_power_of_two() {
return Err(crate::Error::InvalidImage(format!(
"fat: bytes_per_sector must be a power of two in 512..=4096 (got \
{bytes_per_sector})"
)));
}
let sectors_per_cluster = b[13];
if sectors_per_cluster == 0
|| !sectors_per_cluster.is_power_of_two()
|| sectors_per_cluster > 128
{
return Err(crate::Error::InvalidImage(format!(
"fat: sectors_per_cluster must be a power of two in 1..=128 (got \
{sectors_per_cluster})"
)));
}
let reserved_sector_count = u16::from_le_bytes(b[14..16].try_into().unwrap());
if reserved_sector_count == 0 {
return Err(crate::Error::InvalidImage(
"fat: reserved_sector_count must be at least 1".into(),
));
}
let num_fats = b[16];
if num_fats == 0 || num_fats > 4 {
return Err(crate::Error::InvalidImage(format!(
"fat: num_fats must be in 1..=4 (got {num_fats})"
)));
}
let root_entry_count = u16::from_le_bytes(b[17..19].try_into().unwrap());
let total_16 = u16::from_le_bytes(b[19..21].try_into().unwrap());
let fat_size_16 = u16::from_le_bytes(b[22..24].try_into().unwrap());
let total_32 = u32::from_le_bytes(b[32..36].try_into().unwrap());
let fat_size_32 = u32::from_le_bytes(b[36..40].try_into().unwrap());
let total_sectors = if total_16 != 0 {
u32::from(total_16)
} else {
total_32
};
let fat_size = if fat_size_16 != 0 {
u32::from(fat_size_16)
} else {
fat_size_32
};
if fat_size == 0 || total_sectors == 0 {
return Err(crate::Error::InvalidImage(
"fat: boot sector declares a zero FAT size or volume size".into(),
));
}
let root_sectors = (u32::from(root_entry_count) * 32).div_ceil(u32::from(bytes_per_sector));
let data_start = u64::from(reserved_sector_count)
+ u64::from(num_fats) * u64::from(fat_size)
+ u64::from(root_sectors);
if data_start >= u64::from(total_sectors) {
return Err(crate::Error::InvalidImage(format!(
"fat: metadata ({data_start} sectors) overruns the volume of \
{total_sectors} sectors"
)));
}
let clusters =
((u64::from(total_sectors) - data_start) / u64::from(sectors_per_cluster)) as u32;
if clusters == 0 {
return Err(crate::Error::InvalidImage(
"fat: volume has no data clusters".into(),
));
}
let kind = FatKind::from_cluster_count(clusters);
if clusters > kind.max_clusters() {
return Err(crate::Error::InvalidImage(format!(
"{}: {clusters} data clusters exceeds the maximum {} for this entry width",
kind.as_str(),
kind.max_clusters()
)));
}
let fat_bytes = u64::from(fat_size) * u64::from(bytes_per_sector);
let need_fat_bytes = kind.fat_bytes(u64::from(clusters) + 2);
if fat_bytes < need_fat_bytes {
return Err(crate::Error::InvalidImage(format!(
"{}: FAT of {fat_bytes} bytes cannot map {clusters} clusters (needs \
{need_fat_bytes} bytes)",
kind.as_str()
)));
}
if kind == FatKind::Fat32 && root_entry_count != 0 {
return Err(crate::Error::InvalidImage(
"fat32: root_entry_count must be 0 on a FAT32 volume".into(),
));
}
if kind != FatKind::Fat32 && root_entry_count == 0 {
return Err(crate::Error::InvalidImage(format!(
"{}: root_entry_count must be non-zero",
kind.as_str()
)));
}
let mut volume_label = *b"NO NAME ";
let mut volume_id = 0u32;
let (root_cluster, fs_info_sector, backup_boot_sector, drive_number) =
if kind == FatKind::Fat32 {
if b[66] == EXT_BOOT_SIGNATURE {
volume_id = u32::from_le_bytes(b[67..71].try_into().unwrap());
volume_label.copy_from_slice(&b[71..82]);
}
(
u32::from_le_bytes(b[44..48].try_into().unwrap()),
u16::from_le_bytes(b[48..50].try_into().unwrap()),
u16::from_le_bytes(b[50..52].try_into().unwrap()),
b[64],
)
} else {
if b[38] == EXT_BOOT_SIGNATURE {
volume_id = u32::from_le_bytes(b[39..43].try_into().unwrap());
volume_label.copy_from_slice(&b[43..54]);
}
(0, 0, 0, b[36])
};
if kind == FatKind::Fat32 {
if fs_info_sector == 0 || fs_info_sector >= reserved_sector_count {
return Err(crate::Error::InvalidImage(format!(
"fat32: fs_info_sector {fs_info_sector} is outside the reserved region \
(1..{reserved_sector_count})"
)));
}
if backup_boot_sector != 0 {
let backup_end = u32::from(backup_boot_sector) + 1;
if backup_end >= u32::from(reserved_sector_count) {
return Err(crate::Error::InvalidImage(format!(
"fat32: backup_boot_sector {backup_boot_sector} (+1 for its FSInfo) is \
outside the reserved region (1..{reserved_sector_count})"
)));
}
if backup_boot_sector == fs_info_sector || backup_end == u32::from(fs_info_sector) {
return Err(crate::Error::InvalidImage(format!(
"fat32: backup boot region at sector {backup_boot_sector} overlaps \
fs_info_sector {fs_info_sector}"
)));
}
}
}
Ok(Self {
kind,
bytes_per_sector,
sectors_per_cluster,
reserved_sector_count,
num_fats,
root_entry_count,
media: b[21],
sectors_per_track: u16::from_le_bytes(b[24..26].try_into().unwrap()),
num_heads: u16::from_le_bytes(b[26..28].try_into().unwrap()),
hidden_sectors: u32::from_le_bytes(b[28..32].try_into().unwrap()),
total_sectors,
fat_size,
root_cluster,
fs_info_sector,
backup_boot_sector,
drive_number,
volume_id,
volume_label,
})
}
}
pub fn probe(b: &[u8; BOOT_SECTOR_SIZE]) -> Option<FatKind> {
if b[0] != 0xEB && b[0] != 0xE9 {
return None;
}
if b[21] < 0xF0 || (b[21] > 0xF0 && b[21] < 0xF8) {
return None;
}
BootSector::decode(b).ok().map(|bs| bs.kind)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fat32_roundtrip() {
let mut bs = BootSector::defaults_for(FatKind::Fat32);
bs.total_sectors = 131072;
bs.fat_size = 1009;
bs.volume_id = 0x1234_5678;
bs.volume_label = *b"REFVOL ";
let enc = bs.encode();
assert_eq!(&enc[82..87], b"FAT32");
let dec = BootSector::decode(&enc).unwrap();
assert_eq!(dec.kind, FatKind::Fat32);
assert_eq!(dec.total_sectors, 131072);
assert_eq!(dec.fat_size, 1009);
assert_eq!(dec.root_cluster, 2);
assert_eq!(dec.reserved_sector_count, 32);
assert_eq!(dec.num_fats, 2);
assert_eq!(dec.volume_id, 0x1234_5678);
assert_eq!(&dec.volume_label, b"REFVOL ");
}
#[test]
fn fat16_roundtrip() {
let mut bs = BootSector::defaults_for(FatKind::Fat16);
bs.total_sectors = 32768;
bs.fat_size = 128;
bs.volume_id = 0xDEAD_BEEF;
bs.volume_label = *b"SIXTEEN ";
let enc = bs.encode();
assert_eq!(&enc[54..59], b"FAT16");
assert_eq!(u16::from_le_bytes(enc[19..21].try_into().unwrap()), 32768);
assert_eq!(u32::from_le_bytes(enc[32..36].try_into().unwrap()), 0);
let dec = BootSector::decode(&enc).unwrap();
assert_eq!(dec.kind, FatKind::Fat16);
assert_eq!(dec.root_cluster, 0);
assert_eq!(dec.root_entry_count, 512);
assert_eq!(dec.total_sectors, 32768);
assert_eq!(dec.fat_size, 128);
assert_eq!(dec.volume_id, 0xDEAD_BEEF);
assert_eq!(&dec.volume_label, b"SIXTEEN ");
assert_eq!(dec.root_dir_sectors(), 32);
assert_eq!(dec.data_start_sector(), 1 + 256 + 32);
}
#[test]
fn fat12_floppy_roundtrip() {
let mut bs = BootSector::defaults_for(FatKind::Fat12);
bs.total_sectors = 2880;
bs.fat_size = 9;
bs.root_entry_count = 224;
bs.volume_label = *b"FLOPPY ";
let enc = bs.encode();
assert_eq!(&enc[54..59], b"FAT12");
let dec = BootSector::decode(&enc).unwrap();
assert_eq!(dec.kind, FatKind::Fat12);
assert_eq!(dec.root_entry_count, 224);
assert_eq!(dec.root_dir_sectors(), 14);
assert_eq!(dec.data_start_sector(), 1 + 18 + 14);
assert_eq!(dec.cluster_count(), 2880 - 33);
assert!(dec.cluster_count() < FatKind::Fat16.min_clusters());
}
#[test]
fn fs_type_string_does_not_decide_the_flavour() {
let mut bs = BootSector::defaults_for(FatKind::Fat16);
bs.total_sectors = 32768;
bs.fat_size = 128;
let mut enc = bs.encode();
enc[54..62].copy_from_slice(b"FAT12 ");
assert_eq!(BootSector::decode(&enc).unwrap().kind, FatKind::Fat16);
}
#[test]
fn data_start_and_cluster_count() {
let mut bs = BootSector::defaults_for(FatKind::Fat32);
bs.total_sectors = 131072;
bs.fat_size = 1009;
assert_eq!(bs.data_start_sector(), 2050);
assert_eq!(bs.cluster_count(), 129022);
}
#[test]
fn rejects_fat_too_small_to_map_every_cluster() {
let mut bs = BootSector::defaults_for(FatKind::Fat32);
bs.total_sectors = 131072;
bs.fat_size = 1009;
assert!(BootSector::decode(&bs.encode()).is_ok());
bs.fat_size = 100;
match BootSector::decode(&bs.encode()) {
Err(crate::Error::InvalidImage(msg)) => assert!(msg.contains("cannot map"), "{msg}"),
other => panic!("expected InvalidImage, got {other:?}"),
}
}
#[test]
fn fat32_fsinfo_and_backup_sectors_must_stay_in_the_reserved_region() {
let mut bs = BootSector::defaults_for(FatKind::Fat32);
bs.total_sectors = 131072;
bs.fat_size = 1009;
assert_eq!(bs.reserved_sector_count, 32);
let decode = |bs: &BootSector| BootSector::decode(&bs.encode());
assert!(decode(&bs).is_ok());
let mut bad = bs.clone();
bad.fs_info_sector = 0;
assert!(matches!(decode(&bad), Err(crate::Error::InvalidImage(_))));
bad.fs_info_sector = 32;
assert!(matches!(decode(&bad), Err(crate::Error::InvalidImage(_))));
let mut bad = bs.clone();
bad.backup_boot_sector = 31;
assert!(matches!(decode(&bad), Err(crate::Error::InvalidImage(_))));
bad.backup_boot_sector = 1;
assert!(matches!(decode(&bad), Err(crate::Error::InvalidImage(_))));
bad.fs_info_sector = 7;
bad.backup_boot_sector = 6;
assert!(matches!(decode(&bad), Err(crate::Error::InvalidImage(_))));
let mut none = bs.clone();
none.backup_boot_sector = 0;
assert_eq!(decode(&none).unwrap().backup_boot_sector, 0);
for near_max in [u16::MAX, u16::MAX - 1] {
let mut bad = bs.clone();
bad.backup_boot_sector = near_max;
assert!(
matches!(decode(&bad), Err(crate::Error::InvalidImage(_))),
"backup_boot_sector {near_max} must be rejected"
);
}
}
#[test]
fn rejects_more_clusters_than_the_entry_width_can_address() {
let mut bs = BootSector::defaults_for(FatKind::Fat32);
bs.sectors_per_cluster = 1;
bs.fat_size = 1;
bs.total_sectors = u32::MAX;
match BootSector::decode(&bs.encode()) {
Err(crate::Error::InvalidImage(msg)) => {
assert!(msg.contains("exceeds the maximum"), "{msg}")
}
other => panic!("expected InvalidImage, got {other:?}"),
}
let mut ok = BootSector::defaults_for(FatKind::Fat32);
ok.sectors_per_cluster = 8;
ok.fat_size = 1024;
ok.total_sectors = 1024 * 1024;
let decoded = BootSector::decode(&ok.encode()).unwrap();
assert!(decoded.cluster_count() <= decoded.kind.max_clusters());
}
#[test]
fn rejects_zero_data_clusters() {
let mut bs = BootSector::defaults_for(FatKind::Fat12);
bs.sectors_per_cluster = 8;
bs.fat_size = 1;
bs.total_sectors = 36;
match BootSector::decode(&bs.encode()) {
Err(crate::Error::InvalidImage(msg)) => {
assert!(msg.contains("no data clusters"), "{msg}")
}
other => panic!("expected InvalidImage, got {other:?}"),
}
}
#[test]
fn bad_signature_rejected() {
let buf = [0u8; BOOT_SECTOR_SIZE];
assert!(BootSector::decode(&buf).is_err());
}
#[test]
fn probe_identifies_each_flavour_and_rejects_noise() {
for (kind, total, fat_size, root) in [
(FatKind::Fat12, 2880u32, 9u32, 224u16),
(FatKind::Fat16, 32768, 128, 512),
(FatKind::Fat32, 131072, 1009, 0),
] {
let mut bs = BootSector::defaults_for(kind);
bs.total_sectors = total;
bs.fat_size = fat_size;
bs.root_entry_count = root;
assert_eq!(probe(&bs.encode()), Some(kind));
}
let mut mbr = [0u8; BOOT_SECTOR_SIZE];
mbr[510] = 0x55;
mbr[511] = 0xAA;
assert_eq!(probe(&mbr), None);
let mut bs = BootSector::defaults_for(FatKind::Fat16);
bs.total_sectors = 32768;
bs.fat_size = 128;
bs.media = 0x00;
assert_eq!(probe(&bs.encode()), None);
}
#[test]
fn signature_without_a_sane_bpb_is_rejected() {
let mut buf = [0u8; BOOT_SECTOR_SIZE];
buf[510] = 0x55;
buf[511] = 0xAA;
assert!(BootSector::decode(&buf).is_err(), "zero BPB");
buf[11..13].copy_from_slice(&512u16.to_le_bytes());
buf[13] = 3;
assert!(BootSector::decode(&buf).is_err(), "spc = 3");
}
}