forked from M-Labs/rust-fatfs
fix: Rework FAT size calculation for volume formatting
New formula gives nearly perfect results (according to tests) and is better than formula from specification. Also all math transformations are provided in comments so it is no longer a blindly copied formula from spec but a Math supported formula. Old formula was not taking into account sector size (it resulted in too big FATs for sector size different than 512) and had rounding issues for FAT12 and FAT32.
This commit is contained in:
parent
a88d751c47
commit
f2863e8f46
@ -10,6 +10,7 @@ use dir_entry::DIR_ENTRY_SIZE;
|
|||||||
use fs::{FatType, FormatVolumeOptions, FsStatusFlags};
|
use fs::{FatType, FormatVolumeOptions, FsStatusFlags};
|
||||||
use table::RESERVED_FAT_ENTRIES;
|
use table::RESERVED_FAT_ENTRIES;
|
||||||
|
|
||||||
|
const BITS_PER_BYTE: u32 = 8;
|
||||||
const KB: u64 = 1024;
|
const KB: u64 = 1024;
|
||||||
const MB: u64 = KB * 1024;
|
const MB: u64 = KB * 1024;
|
||||||
const GB: u64 = MB * 1024;
|
const GB: u64 = MB * 1024;
|
||||||
@ -201,6 +202,10 @@ impl BiosParameterBlock {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (u32::from(self.root_entries) * DIR_ENTRY_SIZE as u32) % u32::from(self.bytes_per_sector) != 0 {
|
||||||
|
warn!("Root entries should fill sectors fully");
|
||||||
|
}
|
||||||
|
|
||||||
if is_fat32 && self.total_sectors_16 != 0 {
|
if is_fat32 && self.total_sectors_16 != 0 {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
ErrorKind::Other,
|
ErrorKind::Other,
|
||||||
@ -242,7 +247,7 @@ impl BiosParameterBlock {
|
|||||||
let bits_per_fat_entry = fat_type.bits_per_fat_entry();
|
let bits_per_fat_entry = fat_type.bits_per_fat_entry();
|
||||||
let total_fat_entries = self.sectors_per_fat() * self.bytes_per_sector as u32 * 8 / bits_per_fat_entry as u32;
|
let total_fat_entries = self.sectors_per_fat() * self.bytes_per_sector as u32 * 8 / bits_per_fat_entry as u32;
|
||||||
if total_fat_entries - RESERVED_FAT_ENTRIES < total_clusters {
|
if total_fat_entries - RESERVED_FAT_ENTRIES < total_clusters {
|
||||||
warn!("FAT is too small to compared to total number of clusters");
|
warn!("FAT is too small compared to total number of clusters");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -443,25 +448,61 @@ fn determine_bytes_per_cluster(total_bytes: u64, fat_type: FatType, bytes_per_se
|
|||||||
|
|
||||||
fn determine_sectors_per_fat(
|
fn determine_sectors_per_fat(
|
||||||
total_sectors: u32,
|
total_sectors: u32,
|
||||||
reserved_sectors: u16,
|
bytes_per_sector: u16,
|
||||||
fats: u8,
|
|
||||||
root_dir_sectors: u32,
|
|
||||||
sectors_per_cluster: u8,
|
sectors_per_cluster: u8,
|
||||||
fat_type: FatType,
|
fat_type: FatType,
|
||||||
|
reserved_sectors: u16,
|
||||||
|
root_dir_sectors: u32,
|
||||||
|
fats: u8,
|
||||||
) -> u32 {
|
) -> u32 {
|
||||||
// TODO: check if this calculation is always correct (especially for FAT12)
|
//
|
||||||
let tmp_val1 = total_sectors - (reserved_sectors as u32 + root_dir_sectors as u32);
|
// FAT size formula transformations:
|
||||||
let mut tmp_val2 = (256 * sectors_per_cluster as u32) + fats as u32;
|
//
|
||||||
if fat_type == FatType::Fat32 {
|
// Initial basic formula:
|
||||||
tmp_val2 = tmp_val2 / 2;
|
// size of FAT in bits >= (total number of clusters + 2) * bits per FAT entry
|
||||||
} else if fat_type == FatType::Fat12 {
|
//
|
||||||
tmp_val2 = tmp_val2 / 3 * 4
|
// Note: when computing number of clusters from number of sectors rounding down is used because partial clusters
|
||||||
}
|
// are not allowed
|
||||||
(tmp_val1 + (tmp_val2 - 1)) / tmp_val2
|
// Note: in those transformations '/' is a floating-point division (not a rounding towards zero division)
|
||||||
|
//
|
||||||
|
// data_sectors = total_sectors - reserved_sectors - fats * sectors_per_fat - root_dir_sectors
|
||||||
|
// total_clusters = floor(data_sectors / sectors_per_cluster)
|
||||||
|
// bits_per_sector = bytes_per_sector * 8
|
||||||
|
// sectors_per_fat * bits_per_sector >= (total_clusters + 2) * bits_per_fat_entry
|
||||||
|
// sectors_per_fat * bits_per_sector >= (floor(data_sectors / sectors_per_cluster) + 2) * bits_per_fat_entry
|
||||||
|
//
|
||||||
|
// Note: omitting the floor function can cause the FAT to be bigger by 1 entry - negligible
|
||||||
|
//
|
||||||
|
// sectors_per_fat * bits_per_sector >= (data_sectors / sectors_per_cluster + 2) * bits_per_fat_entry
|
||||||
|
// t0 = total_sectors - reserved_sectors - root_dir_sectors
|
||||||
|
// sectors_per_fat * bits_per_sector >= ((t0 - fats * sectors_per_fat) / sectors_per_cluster + 2) * bits_per_fat_entry
|
||||||
|
// sectors_per_fat * bits_per_sector / bits_per_fat_entry >= (t0 - fats * sectors_per_fat) / sectors_per_cluster + 2
|
||||||
|
// sectors_per_fat * bits_per_sector / bits_per_fat_entry >= t0 / sectors_per_cluster + 2 - fats * sectors_per_fat / sectors_per_cluster
|
||||||
|
// sectors_per_fat * bits_per_sector / bits_per_fat_entry + fats * sectors_per_fat / sectors_per_cluster >= t0 / sectors_per_cluster + 2
|
||||||
|
// sectors_per_fat * (bits_per_sector / bits_per_fat_entry + fats / sectors_per_cluster) >= t0 / sectors_per_cluster + 2
|
||||||
|
// sectors_per_fat >= (t0 / sectors_per_cluster + 2) / (bits_per_sector / bits_per_fat_entry + fats / sectors_per_cluster)
|
||||||
|
//
|
||||||
|
// Note: MS specification omits the constant 2 in calculations. This library is taking a better approach...
|
||||||
|
//
|
||||||
|
// sectors_per_fat >= ((t0 + 2 * sectors_per_cluster) / sectors_per_cluster) / (bits_per_sector / bits_per_fat_entry + fats / sectors_per_cluster)
|
||||||
|
// sectors_per_fat >= (t0 + 2 * sectors_per_cluster) / (sectors_per_cluster * bits_per_sector / bits_per_fat_entry + fats)
|
||||||
|
//
|
||||||
|
// Note: compared to MS formula this one can suffer from an overflow problem if u32 type is used
|
||||||
|
//
|
||||||
|
// When converting formula to integer types round towards a bigger FAT:
|
||||||
|
// * first division towards infinity
|
||||||
|
// * second division towards zero (it is in a denominator of the first division)
|
||||||
|
|
||||||
|
let t0: u32 = total_sectors - u32::from(reserved_sectors) - root_dir_sectors;
|
||||||
|
let t1: u64 = u64::from(t0) + u64::from(2 * u32::from(sectors_per_cluster));
|
||||||
|
let bits_per_cluster = u32::from(sectors_per_cluster) * u32::from(bytes_per_sector) * BITS_PER_BYTE;
|
||||||
|
let t2 = u64::from(bits_per_cluster / u32::from(fat_type.bits_per_fat_entry()) + u32::from(fats));
|
||||||
|
let sectors_per_fat = (t1 + t2 - 1) / t2;
|
||||||
|
// Note: casting is safe here because number of sectors per FAT cannot be bigger than total sectors number
|
||||||
|
sectors_per_fat as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_bpb(options: &FormatVolumeOptions) -> io::Result<(BiosParameterBlock, FatType)> {
|
fn format_bpb(options: &FormatVolumeOptions) -> io::Result<(BiosParameterBlock, FatType)> {
|
||||||
// TODO: maybe total_sectors could be optional?
|
|
||||||
let bytes_per_sector = options.bytes_per_sector;
|
let bytes_per_sector = options.bytes_per_sector;
|
||||||
let total_sectors = options.total_sectors;
|
let total_sectors = options.total_sectors;
|
||||||
let total_bytes = total_sectors as u64 * bytes_per_sector as u64;
|
let total_bytes = total_sectors as u64 * bytes_per_sector as u64;
|
||||||
@ -490,11 +531,12 @@ fn format_bpb(options: &FormatVolumeOptions) -> io::Result<(BiosParameterBlock,
|
|||||||
// calculate File Allocation Table size
|
// calculate File Allocation Table size
|
||||||
let sectors_per_fat = determine_sectors_per_fat(
|
let sectors_per_fat = determine_sectors_per_fat(
|
||||||
total_sectors,
|
total_sectors,
|
||||||
reserved_sectors,
|
bytes_per_sector,
|
||||||
fats,
|
|
||||||
root_dir_sectors,
|
|
||||||
sectors_per_cluster,
|
sectors_per_cluster,
|
||||||
fat_type,
|
fat_type,
|
||||||
|
reserved_sectors,
|
||||||
|
root_dir_sectors,
|
||||||
|
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
|
||||||
@ -649,9 +691,93 @@ mod tests {
|
|||||||
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, FatType::Fat32, 512), 32 * KB as u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
fn test_determine_sectors_per_fat_single(
|
||||||
fn test_determine_sectors_per_fat() {
|
total_bytes: u64,
|
||||||
assert_eq!(determine_sectors_per_fat(1 * MB as u32 / 512, 1, 2, 32, 1, FatType::Fat12), 6);
|
bytes_per_sector: u16,
|
||||||
|
bytes_per_cluster: u32,
|
||||||
|
fat_type: FatType,
|
||||||
|
reserved_sectors: u16,
|
||||||
|
fats: u8,
|
||||||
|
root_dir_entries: u32,
|
||||||
|
) {
|
||||||
|
let total_sectors = total_bytes / u64::from(bytes_per_sector);
|
||||||
|
debug_assert!(total_sectors < u64::from(core::u32::MAX));
|
||||||
|
let total_sectors = total_sectors as u32;
|
||||||
|
|
||||||
|
let sectors_per_cluster = (bytes_per_cluster / u32::from(bytes_per_sector)) as u8;
|
||||||
|
let root_dir_size = root_dir_entries * DIR_ENTRY_SIZE as u32;
|
||||||
|
let root_dir_sectors = (root_dir_size + u32::from(bytes_per_sector) - 1) / u32::from(bytes_per_sector);
|
||||||
|
let sectors_per_fat = determine_sectors_per_fat(
|
||||||
|
total_sectors,
|
||||||
|
bytes_per_sector,
|
||||||
|
sectors_per_cluster,
|
||||||
|
fat_type,
|
||||||
|
reserved_sectors,
|
||||||
|
root_dir_sectors,
|
||||||
|
fats,
|
||||||
|
);
|
||||||
|
|
||||||
|
let sectors_per_all_fats = u32::from(fats) * sectors_per_fat;
|
||||||
|
let total_data_sectors = total_sectors - u32::from(reserved_sectors) - sectors_per_all_fats - root_dir_sectors;
|
||||||
|
let total_clusters = total_data_sectors / u32::from(sectors_per_cluster);
|
||||||
|
if FatType::from_clusters(total_clusters) != fat_type {
|
||||||
|
// Skip impossible FAT configurations
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let bits_per_sector = u32::from(bytes_per_sector) * BITS_PER_BYTE;
|
||||||
|
let bits_per_fat = u64::from(sectors_per_fat) * u64::from(bits_per_sector);
|
||||||
|
let total_fat_entries = (bits_per_fat / u64::from(fat_type.bits_per_fat_entry())) as u32;
|
||||||
|
let fat_clusters = total_fat_entries - RESERVED_FAT_ENTRIES;
|
||||||
|
// Note: fat_entries_per_sector is rounded down for FAT12
|
||||||
|
let fat_entries_per_sector = u32::from(bits_per_sector) / fat_type.bits_per_fat_entry();
|
||||||
|
let desc = format!("total_clusters {}, fat_clusters {}, total_sectors {}, bytes/sector {}, sectors/cluster {}, fat_type {:?}, reserved_sectors {}, root_dir_sectors {}, sectors_per_fat {}",
|
||||||
|
total_clusters, fat_clusters, total_sectors, bytes_per_sector, sectors_per_cluster, fat_type, reserved_sectors, root_dir_sectors, sectors_per_fat);
|
||||||
|
assert!(fat_clusters >= total_clusters, "Too small FAT: {}", desc);
|
||||||
|
assert!(fat_clusters <= total_clusters + 2 * fat_entries_per_sector, "Too big FAT: {}", desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_determine_sectors_per_fat_for_multiple_sizes(
|
||||||
|
bytes_per_sector: u16,
|
||||||
|
fat_type: FatType,
|
||||||
|
reserved_sectors: u16,
|
||||||
|
fats: u8,
|
||||||
|
root_dir_entries: u32,
|
||||||
|
) {
|
||||||
|
let mut bytes_per_cluster = u32::from(bytes_per_sector);
|
||||||
|
while bytes_per_cluster <= 64 * KB as u32 {
|
||||||
|
let mut size = 1 * MB;
|
||||||
|
while size <= 2048 * GB {
|
||||||
|
test_determine_sectors_per_fat_single(
|
||||||
|
size,
|
||||||
|
bytes_per_sector,
|
||||||
|
bytes_per_cluster,
|
||||||
|
fat_type,
|
||||||
|
reserved_sectors,
|
||||||
|
fats,
|
||||||
|
root_dir_entries,
|
||||||
|
);
|
||||||
|
size = size + size / 7;
|
||||||
|
}
|
||||||
|
bytes_per_cluster *= 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_determine_sectors_per_fat() {
|
||||||
|
let _ = env_logger::try_init();
|
||||||
|
|
||||||
|
test_determine_sectors_per_fat_for_multiple_sizes(512, FatType::Fat12, 1, 2, 512);
|
||||||
|
test_determine_sectors_per_fat_for_multiple_sizes(512, FatType::Fat12, 1, 1, 512);
|
||||||
|
test_determine_sectors_per_fat_for_multiple_sizes(512, FatType::Fat12, 1, 2, 8192);
|
||||||
|
test_determine_sectors_per_fat_for_multiple_sizes(4096, FatType::Fat12, 1, 2, 512);
|
||||||
|
|
||||||
|
test_determine_sectors_per_fat_for_multiple_sizes(512, FatType::Fat16, 1, 2, 512);
|
||||||
|
test_determine_sectors_per_fat_for_multiple_sizes(512, FatType::Fat16, 1, 1, 512);
|
||||||
|
test_determine_sectors_per_fat_for_multiple_sizes(512, FatType::Fat16, 1, 2, 8192);
|
||||||
|
test_determine_sectors_per_fat_for_multiple_sizes(4096, FatType::Fat16, 1, 2, 512);
|
||||||
|
|
||||||
|
test_determine_sectors_per_fat_for_multiple_sizes(512, FatType::Fat32, 32, 2, 0);
|
||||||
|
test_determine_sectors_per_fat_for_multiple_sizes(512, FatType::Fat32, 32, 1, 0);
|
||||||
|
test_determine_sectors_per_fat_for_multiple_sizes(4096, FatType::Fat32, 32, 2, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user