Refactor FAT type determination, rename and add option for format_volume

* FAT type should be properly selected even if custom cluster size is used.
* root_entries has been renamed to max_root_dir_entries in FormatVolumeOptions
* added option FormatVolumeOptions::fats
This commit is contained in:
Rafał Harabień 2018-12-29 19:37:46 +01:00
parent 131f047c91
commit dc128b7308
3 changed files with 163 additions and 72 deletions

View File

@ -1,4 +1,6 @@
use core::cmp; use core::cmp;
use core::u8;
use core::u16;
use io; use io;
use io::prelude::*; use io::prelude::*;
use io::{Error, ErrorKind}; use io::{Error, ErrorKind};
@ -427,7 +429,8 @@ impl Default for BootSector {
} }
} }
pub(crate) fn determine_fat_type(total_bytes: u64) -> FatType { pub(crate) fn estimate_fat_type(total_bytes: u64) -> FatType {
// Used only to select cluster size if FAT type has not been overriden in options
if total_bytes < 4 * MB { if total_bytes < 4 * MB {
FatType::Fat12 FatType::Fat12
} else if total_bytes < 512 * MB { } else if total_bytes < 512 * MB {
@ -437,7 +440,8 @@ pub(crate) fn determine_fat_type(total_bytes: u64) -> FatType {
} }
} }
fn determine_bytes_per_cluster(total_bytes: u64, fat_type: FatType, bytes_per_sector: u16) -> u32 { fn determine_bytes_per_cluster(total_bytes: u64, bytes_per_sector: u16, fat_type: Option<FatType>) -> u32 {
let fat_type = fat_type.unwrap_or_else(|| estimate_fat_type(total_bytes));
let bytes_per_cluster = match fat_type { let bytes_per_cluster = match fat_type {
FatType::Fat12 => (total_bytes.next_power_of_two() / MB * 512) as u32, FatType::Fat12 => (total_bytes.next_power_of_two() / MB * 512) as u32,
FatType::Fat16 => { FatType::Fat16 => {
@ -520,29 +524,16 @@ fn determine_sectors_per_fat(
sectors_per_fat as u32 sectors_per_fat as u32
} }
fn format_bpb(options: &FormatVolumeOptions, total_sectors: u32, bytes_per_sector: u16) -> io::Result<(BiosParameterBlock, FatType)> { fn try_fs_geometry(total_sectors: u32, bytes_per_sector: u16, sectors_per_cluster: u8, fat_type: FatType,
let total_bytes = u64::from(total_sectors) * u64::from(bytes_per_sector); root_dir_sectors: u32, fats: u8) -> io::Result<(u16, u32)> {
// FIXME: determine FAT type from cluster size because it can be modified by user
let fat_type = options.fat_type.unwrap_or_else(|| determine_fat_type(total_bytes));
let bytes_per_cluster = options
.bytes_per_cluster
.unwrap_or_else(|| determine_bytes_per_cluster(total_bytes, fat_type, bytes_per_sector));
let sectors_per_cluster = (bytes_per_cluster / bytes_per_sector as u32) as u8;
// Note: most of implementations use 32 reserved sectors for FAT32 but it's wasting of space // Note: most of implementations use 32 reserved sectors for FAT32 but it's wasting of space
// This implementation uses only 8. This is enough to fit in two boot sectors (main and backup) with additional // This implementation uses only 8. This is enough to fit in two boot sectors (main and backup) with additional
// bootstrap code and one FSInfo sector. It also makes FAT alligned to 4096 which is a nice number. // bootstrap code and one FSInfo sector. It also makes FAT alligned to 4096 which is a nice number.
let reserved_sectors: u16 = if fat_type == FatType::Fat32 { 8 } else { 1 }; let reserved_sectors: u16 = if fat_type == FatType::Fat32 { 8 } else { 1 };
let fats = 2u8;
let is_fat32 = fat_type == FatType::Fat32;
let root_entries = if is_fat32 { 0 } else { options.root_entries.unwrap_or(512) };
let root_dir_bytes = root_entries as u32 * DIR_ENTRY_SIZE as u32;
let root_dir_sectors = (root_dir_bytes + bytes_per_sector as u32 - 1) / bytes_per_sector as u32;
// Check if volume has enough space to accomodate reserved sectors, FAT, root directory and some data space // Check if volume has enough space to accomodate reserved sectors, FAT, root directory and some data space
// Having less than 8 sectors for FAT and data would make a little sense // Having less than 8 sectors for FAT and data would make a little sense
if total_sectors <= reserved_sectors as u32 + root_dir_sectors as u32 + 8 { if total_sectors <= u32::from(reserved_sectors) + u32::from(root_dir_sectors) + 8 {
return Err(Error::new(ErrorKind::Other, "Volume is too small")); return Err(Error::new(ErrorKind::Other, "Volume is too small"));
} }
@ -557,6 +548,63 @@ fn format_bpb(options: &FormatVolumeOptions, total_sectors: u32, bytes_per_secto
fats, fats,
); );
let data_sectors = total_sectors - u32::from(reserved_sectors) - u32::from(root_dir_sectors)
- sectors_per_fat * u32::from(fats);
let total_clusters = data_sectors / u32::from(sectors_per_cluster);
if fat_type != FatType::from_clusters(total_clusters) {
return Err(Error::new(ErrorKind::Other, "Invalid FAT type"));
}
debug_assert!(total_clusters >= fat_type.min_clusters());
if total_clusters > fat_type.max_clusters() {
// Note: it can happen for FAT32
return Err(Error::new(ErrorKind::Other, "Too many clusters"));
}
return Ok((reserved_sectors, sectors_per_fat));
}
fn determine_root_dir_sectors(root_dir_entries: u16, bytes_per_sector: u16, fat_type: FatType) -> u32 {
if fat_type == FatType::Fat32 {
0
} else {
let root_dir_bytes = u32::from(root_dir_entries) * DIR_ENTRY_SIZE as u32;
(root_dir_bytes + u32::from(bytes_per_sector) - 1) / u32::from(bytes_per_sector)
}
}
fn determine_fs_geometry(total_sectors: u32, bytes_per_sector: u16, sectors_per_cluster: u8,
root_dir_entries: u16, fats: u8
) -> io::Result<(FatType, u16, u32)> {
for &fat_type in &[FatType::Fat32, FatType::Fat16, FatType::Fat12] {
let root_dir_sectors = determine_root_dir_sectors(root_dir_entries, bytes_per_sector, fat_type);
let result = try_fs_geometry(total_sectors, bytes_per_sector, sectors_per_cluster, fat_type, root_dir_sectors, fats);
if result.is_ok() {
let (reserved_sectors, sectors_per_fat) = result.unwrap(); // SAFE: used is_ok() before
return Ok((fat_type, reserved_sectors, sectors_per_fat));
}
}
return Err(Error::new(ErrorKind::Other, "Cannot select FAT type - unfortunate disk size"));
}
fn format_bpb(options: &FormatVolumeOptions, total_sectors: u32, bytes_per_sector: u16) -> io::Result<(BiosParameterBlock, FatType)> {
let bytes_per_cluster = options
.bytes_per_cluster
.unwrap_or_else(|| {
let total_bytes = u64::from(total_sectors) * u64::from(bytes_per_sector);
determine_bytes_per_cluster(total_bytes, bytes_per_sector, options.fat_type)
});
let sectors_per_cluster = bytes_per_cluster / u32::from(bytes_per_sector);
assert!(sectors_per_cluster <= u32::from(u8::MAX));
let sectors_per_cluster = sectors_per_cluster as u8;
let fats = options.fats.unwrap_or(2u8);
let root_dir_entries = options.max_root_dir_entries.unwrap_or(512);
let (fat_type, reserved_sectors, sectors_per_fat) =
determine_fs_geometry(total_sectors, bytes_per_sector, sectors_per_cluster, root_dir_entries, fats)?;
// drive_num should be 0 for floppy disks and 0x80 for hard disks - determine it using FAT type // drive_num should be 0 for floppy disks and 0x80 for hard disks - determine it using FAT type
let drive_num = options let drive_num = options
.drive_num .drive_num
@ -583,15 +631,22 @@ fn format_bpb(options: &FormatVolumeOptions, total_sectors: u32, bytes_per_secto
fs_type_label.copy_from_slice(fs_type_label_str); fs_type_label.copy_from_slice(fs_type_label_str);
// create Bios Parameter Block struct // create Bios Parameter Block struct
let is_fat32 = fat_type == FatType::Fat32;
let sectors_per_fat_16 = if is_fat32 {
0
} else {
debug_assert!(sectors_per_fat <= u32::from(u16::MAX));
sectors_per_fat as u16
};
let bpb = BiosParameterBlock { let bpb = BiosParameterBlock {
bytes_per_sector, bytes_per_sector,
sectors_per_cluster, sectors_per_cluster,
reserved_sectors, reserved_sectors,
fats, fats,
root_entries, root_entries: if is_fat32 { 0 } else { root_dir_entries },
total_sectors_16: if total_sectors < 0x10000 { total_sectors as u16 } else { 0 }, total_sectors_16: if total_sectors < 0x10000 { total_sectors as u16 } else { 0 },
media: options.media.unwrap_or(0xF8), media: options.media.unwrap_or(0xF8),
sectors_per_fat_16: if is_fat32 { 0 } else { sectors_per_fat as u16 }, sectors_per_fat_16,
sectors_per_track: options.sectors_per_track.unwrap_or(0x20), sectors_per_track: options.sectors_per_track.unwrap_or(0x20),
heads: options.heads.unwrap_or(0x40), heads: options.heads.unwrap_or(0x40),
hidden_sectors: 0, hidden_sectors: 0,
@ -647,11 +702,11 @@ pub(crate) fn format_boot_sector(options: &FormatVolumeOptions, total_sectors: u
// fix offsets in bootjmp and boot code for non-FAT32 filesystems (bootcode is on a different offset) // fix offsets in bootjmp and boot code for non-FAT32 filesystems (bootcode is on a different offset)
if fat_type != FatType::Fat32 { if fat_type != FatType::Fat32 {
// offset of boot code // offset of boot code
let boot_code_offset = 0x36 + 8; let boot_code_offset: u8 = 0x36 + 8;
boot.bootjmp[1] = (boot_code_offset - 2) as u8; boot.bootjmp[1] = boot_code_offset - 2;
// offset of message // offset of message
const MESSAGE_OFFSET: u32 = 29; const MESSAGE_OFFSET: u16 = 29;
let message_offset_in_sector = boot_code_offset + MESSAGE_OFFSET + 0x7c00; let message_offset_in_sector = u16::from(boot_code_offset) + MESSAGE_OFFSET + 0x7c00;
boot.boot_code[3] = (message_offset_in_sector & 0xff) as u8; boot.boot_code[3] = (message_offset_in_sector & 0xff) as u8;
boot.boot_code[4] = (message_offset_in_sector >> 8) as u8; boot.boot_code[4] = (message_offset_in_sector >> 8) as u8;
} }
@ -664,49 +719,49 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn test_determine_fat_type() { fn test_estimate_fat_type() {
assert_eq!(determine_fat_type(3 * MB), FatType::Fat12); assert_eq!(estimate_fat_type(3 * MB), FatType::Fat12);
assert_eq!(determine_fat_type(4 * MB), FatType::Fat16); assert_eq!(estimate_fat_type(4 * MB), FatType::Fat16);
assert_eq!(determine_fat_type(511 * MB), FatType::Fat16); assert_eq!(estimate_fat_type(511 * MB), FatType::Fat16);
assert_eq!(determine_fat_type(512 * MB), FatType::Fat32); assert_eq!(estimate_fat_type(512 * MB), FatType::Fat32);
} }
#[test] #[test]
fn test_determine_bytes_per_cluster_fat12() { fn test_determine_bytes_per_cluster_fat12() {
assert_eq!(determine_bytes_per_cluster(1 * MB + 0, FatType::Fat12, 512), 512); assert_eq!(determine_bytes_per_cluster(1 * MB + 0, 512, Some(FatType::Fat12)), 512);
assert_eq!(determine_bytes_per_cluster(1 * MB + 1, FatType::Fat12, 512), 1024); assert_eq!(determine_bytes_per_cluster(1 * MB + 1, 512, Some(FatType::Fat12)), 1024);
assert_eq!(determine_bytes_per_cluster(1 * MB, FatType::Fat12, 4096), 4096); assert_eq!(determine_bytes_per_cluster(1 * MB, 4096, Some(FatType::Fat12)), 4096);
} }
#[test] #[test]
fn test_determine_bytes_per_cluster_fat16() { fn test_determine_bytes_per_cluster_fat16() {
assert_eq!(determine_bytes_per_cluster(1 * MB, FatType::Fat16, 512), 1 * KB as u32); assert_eq!(determine_bytes_per_cluster(1 * MB, 512, Some(FatType::Fat16)), 1 * KB as u32);
assert_eq!(determine_bytes_per_cluster(1 * MB, FatType::Fat16, 4 * KB as u16), 4 * KB as u32); assert_eq!(determine_bytes_per_cluster(1 * MB, 4 * KB as u16, Some(FatType::Fat16)), 4 * KB as u32);
assert_eq!(determine_bytes_per_cluster(16 * MB + 0, FatType::Fat16, 512), 1 * KB as u32); assert_eq!(determine_bytes_per_cluster(16 * MB + 0, 512, Some(FatType::Fat16)), 1 * KB as u32);
assert_eq!(determine_bytes_per_cluster(16 * MB + 1, FatType::Fat16, 512), 2 * KB as u32); assert_eq!(determine_bytes_per_cluster(16 * MB + 1, 512, Some(FatType::Fat16)), 2 * KB as u32);
assert_eq!(determine_bytes_per_cluster(128 * MB + 0, FatType::Fat16, 512), 2 * KB as u32); assert_eq!(determine_bytes_per_cluster(128 * MB + 0, 512, Some(FatType::Fat16)), 2 * KB as u32);
assert_eq!(determine_bytes_per_cluster(128 * MB + 1, FatType::Fat16, 512), 4 * KB as u32); assert_eq!(determine_bytes_per_cluster(128 * MB + 1, 512, Some(FatType::Fat16)), 4 * KB as u32);
assert_eq!(determine_bytes_per_cluster(256 * MB + 0, FatType::Fat16, 512), 4 * KB as u32); assert_eq!(determine_bytes_per_cluster(256 * MB + 0, 512, Some(FatType::Fat16)), 4 * KB as u32);
assert_eq!(determine_bytes_per_cluster(256 * MB + 1, FatType::Fat16, 512), 8 * KB as u32); assert_eq!(determine_bytes_per_cluster(256 * MB + 1, 512, Some(FatType::Fat16)), 8 * KB as u32);
assert_eq!(determine_bytes_per_cluster(512 * MB + 0, FatType::Fat16, 512), 8 * KB as u32); assert_eq!(determine_bytes_per_cluster(512 * MB + 0, 512, Some(FatType::Fat16)), 8 * KB as u32);
assert_eq!(determine_bytes_per_cluster(512 * MB + 1, FatType::Fat16, 512), 16 * KB as u32); assert_eq!(determine_bytes_per_cluster(512 * MB + 1, 512, Some(FatType::Fat16)), 16 * KB as u32);
assert_eq!(determine_bytes_per_cluster(1024 * MB + 0, FatType::Fat16, 512), 16 * KB as u32); assert_eq!(determine_bytes_per_cluster(1024 * MB + 0, 512, Some(FatType::Fat16)), 16 * KB as u32);
assert_eq!(determine_bytes_per_cluster(1024 * MB + 1, FatType::Fat16, 512), 32 * KB as u32); assert_eq!(determine_bytes_per_cluster(1024 * MB + 1, 512, Some(FatType::Fat16)), 32 * KB as u32);
assert_eq!(determine_bytes_per_cluster(99999 * MB, FatType::Fat16, 512), 32 * KB as u32); assert_eq!(determine_bytes_per_cluster(99999 * MB, 512, Some(FatType::Fat16)), 32 * KB as u32);
} }
#[test] #[test]
fn test_determine_bytes_per_cluster_fat32() { fn test_determine_bytes_per_cluster_fat32() {
assert_eq!(determine_bytes_per_cluster(260 * MB as u64, FatType::Fat32, 512), 512); assert_eq!(determine_bytes_per_cluster(260 * MB as u64, 512, Some(FatType::Fat32)), 512);
assert_eq!(determine_bytes_per_cluster(260 * MB as u64, FatType::Fat32, 4 * KB as u16), 4 * KB as u32); assert_eq!(determine_bytes_per_cluster(260 * MB as u64, 4 * KB as u16, Some(FatType::Fat32)), 4 * KB as u32);
assert_eq!(determine_bytes_per_cluster(260 * MB as u64 + 1, FatType::Fat32, 512), 4 * KB as u32); assert_eq!(determine_bytes_per_cluster(260 * MB as u64 + 1, 512, Some(FatType::Fat32)), 4 * KB as u32);
assert_eq!(determine_bytes_per_cluster(8 * GB as u64, FatType::Fat32, 512), 4 * KB as u32); assert_eq!(determine_bytes_per_cluster(8 * GB as u64, 512, Some(FatType::Fat32)), 4 * KB as u32);
assert_eq!(determine_bytes_per_cluster(8 * GB as u64 + 1, FatType::Fat32, 512), 8 * KB as u32); assert_eq!(determine_bytes_per_cluster(8 * GB as u64 + 1, 512, Some(FatType::Fat32)), 8 * KB as u32);
assert_eq!(determine_bytes_per_cluster(16 * GB as u64 + 0, FatType::Fat32, 512), 8 * KB as u32); assert_eq!(determine_bytes_per_cluster(16 * GB as u64 + 0, 512, Some(FatType::Fat32)), 8 * KB as u32);
assert_eq!(determine_bytes_per_cluster(16 * GB as u64 + 1, FatType::Fat32, 512), 16 * KB as u32); assert_eq!(determine_bytes_per_cluster(16 * GB as u64 + 1, 512, Some(FatType::Fat32)), 16 * KB as u32);
assert_eq!(determine_bytes_per_cluster(32 * GB as u64, FatType::Fat32, 512), 16 * KB as u32); assert_eq!(determine_bytes_per_cluster(32 * GB as u64, 512, Some(FatType::Fat32)), 16 * KB as u32);
assert_eq!(determine_bytes_per_cluster(32 * GB as u64 + 1, FatType::Fat32, 512), 32 * KB as u32); assert_eq!(determine_bytes_per_cluster(32 * GB as u64 + 1, 512, Some(FatType::Fat32)), 32 * KB as u32);
assert_eq!(determine_bytes_per_cluster(999 * GB as u64, FatType::Fat32, 512), 32 * KB as u32); assert_eq!(determine_bytes_per_cluster(999 * GB as u64, 512, Some(FatType::Fat32)), 32 * KB as u32);
} }
fn test_determine_sectors_per_fat_single( fn test_determine_sectors_per_fat_single(
@ -810,16 +865,21 @@ mod tests {
} }
#[test] #[test]
fn test_format_boot_sector_large_partition() { fn test_format_boot_sector() {
let _ = env_logger::try_init(); let _ = env_logger::try_init();
let bytes_per_sector = 512; let bytes_per_sector = 512u16;
let bytes_per_cluster = 4 * 4096; // test all partition sizes from 1MB to 2TB (u32::MAX sectors is 2TB - 1 for 512 byte sectors)
let total_sectors = core::u32::MAX; let mut total_sectors_vec = Vec::new();
let (boot, _) = format_boot_sector(&FormatVolumeOptions::new() let mut size = 1 * MB;
.total_sectors(total_sectors) while size < 2048 * GB {
.bytes_per_sector(bytes_per_sector) total_sectors_vec.push((size / u64::from(bytes_per_sector)) as u32);
.bytes_per_cluster(bytes_per_cluster), total_sectors, bytes_per_sector) size = size + size / 7;
}
total_sectors_vec.push(core::u32::MAX);
for total_sectors in total_sectors_vec {
let (boot, _) = format_boot_sector(&FormatVolumeOptions::new(), total_sectors, bytes_per_sector)
.expect("format_boot_sector"); .expect("format_boot_sector");
boot.validate().expect("validate"); boot.validate().expect("validate");
} }
} }
}

View File

@ -37,10 +37,14 @@ pub enum FatType {
} }
impl FatType { impl FatType {
const FAT16_MIN_CLUSTERS: u32 = 4085;
const FAT32_MIN_CLUSTERS: u32 = 65525;
const FAT32_MAX_CLUSTERS: u32 = 0x0FFF_FFF4;
pub(crate) fn from_clusters(total_clusters: u32) -> FatType { pub(crate) fn from_clusters(total_clusters: u32) -> FatType {
if total_clusters < 4085 { if total_clusters < Self::FAT16_MIN_CLUSTERS {
FatType::Fat12 FatType::Fat12
} else if total_clusters < 65525 { } else if total_clusters < Self::FAT32_MIN_CLUSTERS {
FatType::Fat16 FatType::Fat16
} else { } else {
FatType::Fat32 FatType::Fat32
@ -54,6 +58,22 @@ impl FatType {
&FatType::Fat32 => 32, &FatType::Fat32 => 32,
} }
} }
pub(crate) fn min_clusters(&self) -> u32 {
match self {
&FatType::Fat12 => 0,
&FatType::Fat16 => Self::FAT16_MIN_CLUSTERS,
&FatType::Fat32 => Self::FAT32_MIN_CLUSTERS,
}
}
pub(crate) fn max_clusters(&self) -> u32 {
match self {
&FatType::Fat12 => Self::FAT16_MIN_CLUSTERS - 1,
&FatType::Fat16 => Self::FAT32_MIN_CLUSTERS - 1,
&FatType::Fat32 => Self::FAT32_MAX_CLUSTERS,
}
}
} }
/// A FAT volume status flags retrived from the Boot Sector and the allocation table second entry. /// A FAT volume status flags retrived from the Boot Sector and the allocation table second entry.
@ -794,7 +814,8 @@ pub struct FormatVolumeOptions {
pub(crate) total_sectors: Option<u32>, pub(crate) total_sectors: Option<u32>,
pub(crate) bytes_per_cluster: Option<u32>, pub(crate) bytes_per_cluster: Option<u32>,
pub(crate) fat_type: Option<FatType>, pub(crate) fat_type: Option<FatType>,
pub(crate) root_entries: Option<u16>, pub(crate) max_root_dir_entries: Option<u16>,
pub(crate) fats: Option<u8>,
pub(crate) media: Option<u8>, pub(crate) media: Option<u8>,
pub(crate) sectors_per_track: Option<u16>, pub(crate) sectors_per_track: Option<u16>,
pub(crate) heads: Option<u16>, pub(crate) heads: Option<u16>,
@ -857,8 +878,18 @@ impl FormatVolumeOptions {
/// Total root directory size should be dividable by sectors size so keep it a multiple of 16 (for default sector /// Total root directory size should be dividable by sectors size so keep it a multiple of 16 (for default sector
/// size). /// size).
/// Default is `512`. /// Default is `512`.
pub fn root_entries(mut self, root_entries: u16) -> Self { pub fn max_root_dir_entries(mut self, max_root_dir_entries: u16) -> Self {
self.root_entries = Some(root_entries); self.max_root_dir_entries = Some(max_root_dir_entries);
self
}
/// Set number of File Allocation Tables
///
/// The only allowed values are `1` and `2`. If `2` is used FAT is mirrored.
/// Default is `2`.
pub fn fats(mut self, fats: u8) -> Self {
assert!(fats >= 1 && fats <= 2);
self.fats = Some(fats);
self self
} }

View File

@ -80,9 +80,9 @@ fn test_format_1mb() {
} }
#[test] #[test]
fn test_format_8mb() { fn test_format_8mb_1fat() {
let total_bytes = 8 * MB; let total_bytes = 8 * MB;
let opts = fatfs::FormatVolumeOptions::new(); let opts = fatfs::FormatVolumeOptions::new().fats(1);
let fs = test_format_fs(opts, total_bytes); let fs = test_format_fs(opts, total_bytes);
assert_eq!(fs.fat_type(), fatfs::FatType::Fat16); assert_eq!(fs.fat_type(), fatfs::FatType::Fat16);
} }