use crate::{ByteSource, Error, Result};
pub(crate) const BOOT_SECTOR_SIZE: usize = 512;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FatType {
Fat12,
Fat16,
Fat32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FatBootSector {
pub fat_type: FatType,
pub bytes_per_sector: u16,
pub sectors_per_cluster: u8,
pub reserved_sectors: u16,
pub fat_count: u8,
pub root_entry_count: u16,
pub total_sectors: u32,
pub sectors_per_fat: u32,
pub root_cluster: u32,
pub media_descriptor: u8,
}
impl FatBootSector {
pub fn read(source: &dyn ByteSource) -> Result<Self> {
let bytes = source.read_bytes_at(0, BOOT_SECTOR_SIZE)?;
Self::from_bytes(&bytes)
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
let sector: &[u8; BOOT_SECTOR_SIZE] = bytes
.try_into()
.map_err(|_| Error::invalid_format("fat boot sector must be exactly 512 bytes"))?;
Self::from_sector(sector)
}
pub fn from_sector(sector: &[u8; BOOT_SECTOR_SIZE]) -> Result<Self> {
if !has_valid_boot_jump(sector) {
return Err(Error::invalid_format(
"fat boot sector has an invalid jump instruction".to_string(),
));
}
if !has_boot_signature(sector) {
return Err(Error::invalid_format(
"fat boot sector is missing the 0x55aa signature".to_string(),
));
}
let bytes_per_sector = le_u16(§or[11..13]);
if !valid_bytes_per_sector(bytes_per_sector) {
return Err(Error::invalid_format(format!(
"unsupported fat bytes-per-sector value: {bytes_per_sector}"
)));
}
let sectors_per_cluster = sector[13];
if !valid_sectors_per_cluster(sectors_per_cluster) {
return Err(Error::invalid_format(format!(
"unsupported fat sectors-per-cluster value: {sectors_per_cluster}"
)));
}
let reserved_sectors = le_u16(§or[14..16]);
if reserved_sectors == 0 {
return Err(Error::invalid_format(
"fat reserved sector count must be non-zero".to_string(),
));
}
let fat_count = sector[16];
if fat_count == 0 {
return Err(Error::invalid_format(
"fat must contain at least one FAT table".to_string(),
));
}
let root_entry_count = le_u16(§or[17..19]);
let total_sectors_16 = u32::from(le_u16(§or[19..21]));
let total_sectors_32 = le_u32(§or[32..36]);
let total_sectors = if total_sectors_16 != 0 {
total_sectors_16
} else {
total_sectors_32
};
if total_sectors == 0 {
return Err(Error::invalid_format(
"fat total sector count must be non-zero".to_string(),
));
}
let media_descriptor = sector[21];
if !matches!(media_descriptor, 0xF0 | 0xF8..=0xFF) {
return Err(Error::invalid_format(format!(
"unsupported fat media descriptor: 0x{media_descriptor:02x}"
)));
}
let sectors_per_fat_16 = u32::from(le_u16(§or[22..24]));
let sectors_per_fat_32 = le_u32(§or[36..40]);
let sectors_per_fat = if sectors_per_fat_16 != 0 {
sectors_per_fat_16
} else {
sectors_per_fat_32
};
if sectors_per_fat == 0 {
return Err(Error::invalid_format(
"fat sectors-per-fat must be non-zero".to_string(),
));
}
let root_dir_sectors = div_ceil_u32(
u32::from(root_entry_count)
.checked_mul(32)
.ok_or_else(|| Error::invalid_range("fat root directory size overflow"))?,
u32::from(bytes_per_sector),
);
let data_sectors = total_sectors
.checked_sub(
u32::from(reserved_sectors)
.checked_add(
u32::from(fat_count)
.checked_mul(sectors_per_fat)
.ok_or_else(|| Error::invalid_range("fat table area size overflow"))?,
)
.and_then(|value| value.checked_add(root_dir_sectors))
.ok_or_else(|| Error::invalid_range("fat layout size overflow"))?,
)
.ok_or_else(|| Error::invalid_format("fat data area is out of bounds"))?;
let cluster_count = data_sectors / u32::from(sectors_per_cluster);
let fat_type = if cluster_count < 4_085 {
FatType::Fat12
} else if cluster_count < 65_525 {
FatType::Fat16
} else {
FatType::Fat32
};
let root_cluster = if fat_type == FatType::Fat32 {
let root_cluster = le_u32(§or[44..48]);
if root_entry_count != 0 || sectors_per_fat_16 != 0 || sectors_per_fat_32 == 0 {
return Err(Error::invalid_format(
"fat32 requires a zero root-entry count and 32-bit sectors-per-fat".to_string(),
));
}
if root_cluster < 2 {
return Err(Error::invalid_format(
"fat32 root cluster must be at least 2".to_string(),
));
}
root_cluster
} else {
if root_entry_count == 0 || sectors_per_fat_16 == 0 {
return Err(Error::invalid_format(
"fat12/16 require a fixed root directory and 16-bit sectors-per-fat".to_string(),
));
}
0
};
Ok(Self {
fat_type,
bytes_per_sector,
sectors_per_cluster,
reserved_sectors,
fat_count,
root_entry_count,
total_sectors,
sectors_per_fat,
root_cluster,
media_descriptor,
})
}
pub fn cluster_size(&self) -> Result<u64> {
u64::from(self.bytes_per_sector)
.checked_mul(u64::from(self.sectors_per_cluster))
.ok_or_else(|| Error::invalid_range("fat cluster size overflow"))
}
pub fn root_dir_sectors(&self) -> u32 {
div_ceil_u32(
u32::from(self.root_entry_count) * 32,
u32::from(self.bytes_per_sector),
)
}
pub fn fat_offset(&self, index: u8) -> Result<u64> {
let sector_offset = u64::from(self.reserved_sectors)
.checked_add(
u64::from(index)
.checked_mul(u64::from(self.sectors_per_fat))
.ok_or_else(|| Error::invalid_range("fat table offset overflow"))?,
)
.ok_or_else(|| Error::invalid_range("fat table offset overflow"))?;
sector_offset
.checked_mul(u64::from(self.bytes_per_sector))
.ok_or_else(|| Error::invalid_range("fat table byte offset overflow"))
}
pub fn fat_size_bytes(&self) -> Result<usize> {
usize::try_from(
u64::from(self.sectors_per_fat)
.checked_mul(u64::from(self.bytes_per_sector))
.ok_or_else(|| Error::invalid_range("fat table byte size overflow"))?,
)
.map_err(|_| Error::invalid_range("fat table is too large to map"))
}
pub fn root_dir_offset(&self) -> Result<u64> {
let sector_offset = u64::from(self.reserved_sectors)
.checked_add(
u64::from(self.fat_count)
.checked_mul(u64::from(self.sectors_per_fat))
.ok_or_else(|| Error::invalid_range("fat root directory offset overflow"))?,
)
.ok_or_else(|| Error::invalid_range("fat root directory offset overflow"))?;
sector_offset
.checked_mul(u64::from(self.bytes_per_sector))
.ok_or_else(|| Error::invalid_range("fat root directory offset overflow"))
}
pub fn root_dir_size_bytes(&self) -> Result<usize> {
usize::try_from(
u64::from(self.root_dir_sectors())
.checked_mul(u64::from(self.bytes_per_sector))
.ok_or_else(|| Error::invalid_range("fat root directory size overflow"))?,
)
.map_err(|_| Error::invalid_range("fat root directory is too large to map"))
}
pub fn data_offset(&self) -> Result<u64> {
let data_sector = u64::from(self.reserved_sectors)
.checked_add(
u64::from(self.fat_count)
.checked_mul(u64::from(self.sectors_per_fat))
.ok_or_else(|| Error::invalid_range("fat data offset overflow"))?,
)
.and_then(|value| value.checked_add(u64::from(self.root_dir_sectors())))
.ok_or_else(|| Error::invalid_range("fat data offset overflow"))?;
data_sector
.checked_mul(u64::from(self.bytes_per_sector))
.ok_or_else(|| Error::invalid_range("fat data offset overflow"))
}
pub fn cluster_offset(&self, cluster: u32) -> Result<u64> {
if cluster < 2 {
return Err(Error::invalid_format(format!(
"fat cluster numbers start at 2, got {cluster}"
)));
}
self
.data_offset()?
.checked_add(
u64::from(cluster - 2)
.checked_mul(self.cluster_size()?)
.ok_or_else(|| Error::invalid_range("fat cluster offset overflow"))?,
)
.ok_or_else(|| Error::invalid_range("fat cluster offset overflow"))
}
}
pub(crate) fn has_valid_boot_jump(sector: &[u8; BOOT_SECTOR_SIZE]) -> bool {
matches!(sector[0], 0xE9) || (sector[0] == 0xEB && sector[2] == 0x90)
}
pub(crate) fn has_boot_signature(sector: &[u8; BOOT_SECTOR_SIZE]) -> bool {
sector[510] == 0x55 && sector[511] == 0xAA
}
pub(crate) fn valid_bytes_per_sector(value: u16) -> bool {
matches!(value, 512 | 1024 | 2048 | 4096)
}
pub(crate) fn valid_sectors_per_cluster(value: u8) -> bool {
value != 0 && value.is_power_of_two()
}
fn div_ceil_u32(value: u32, divisor: u32) -> u32 {
value.div_ceil(divisor)
}
fn le_u16(bytes: &[u8]) -> u16 {
let mut raw = [0u8; 2];
raw.copy_from_slice(bytes);
u16::from_le_bytes(raw)
}
fn le_u32(bytes: &[u8]) -> u32 {
let mut raw = [0u8; 4];
raw.copy_from_slice(bytes);
u32::from_le_bytes(raw)
}
#[cfg(test)]
mod tests {
use std::path::Path;
use super::*;
fn fixture_path(relative: &str) -> std::path::PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("formats")
.join("fat")
.join("libfsfat")
.join(relative)
}
fn build_fat32_boot_sector() -> [u8; BOOT_SECTOR_SIZE] {
let mut sector = [0u8; BOOT_SECTOR_SIZE];
sector[0] = 0xEB;
sector[1] = 0x58;
sector[2] = 0x90;
sector[3..11].copy_from_slice(b"MSWIN4.1");
sector[11..13].copy_from_slice(&512u16.to_le_bytes());
sector[13] = 8;
sector[14..16].copy_from_slice(&32u16.to_le_bytes());
sector[16] = 2;
sector[21] = 0xF8;
sector[32..36].copy_from_slice(&1_048_576u32.to_le_bytes());
sector[36..40].copy_from_slice(&1_024u32.to_le_bytes());
sector[44..48].copy_from_slice(&2u32.to_le_bytes());
sector[510] = 0x55;
sector[511] = 0xAA;
sector
}
#[test]
fn parses_valid_fat32_boot_sector() {
let boot_sector = FatBootSector::from_sector(&build_fat32_boot_sector()).unwrap();
assert_eq!(boot_sector.fat_type, FatType::Fat32);
assert_eq!(boot_sector.cluster_size().unwrap(), 4096);
assert_eq!(boot_sector.root_cluster, 2);
}
#[test]
fn rejects_ntfs_geometry_with_a_fat_hint() {
let mut sector = build_fat32_boot_sector();
sector[3..11].copy_from_slice(b"NTFS ");
sector[14..16].copy_from_slice(&0u16.to_le_bytes());
let error = FatBootSector::from_sector(§or).unwrap_err();
assert!(matches!(error, Error::InvalidFormat(_)));
}
#[test]
fn parses_libfsfat_boot_sector_fixture() {
let bytes = std::fs::read(fixture_path("boot_sector.1")).unwrap();
let boot_sector = FatBootSector::from_bytes(&bytes).unwrap();
assert_eq!(boot_sector.fat_type, FatType::Fat12);
assert_eq!(boot_sector.bytes_per_sector, 512);
assert_eq!(boot_sector.sectors_per_cluster, 4);
assert_eq!(boot_sector.reserved_sectors, 6);
assert_eq!(boot_sector.fat_count, 2);
assert_eq!(boot_sector.root_entry_count, 512);
assert_eq!(boot_sector.total_sectors, 6016);
assert_eq!(boot_sector.sectors_per_fat, 5);
assert_eq!(boot_sector.media_descriptor, 0xF8);
}
}