Volume format API

This commit is contained in:
Rafał Harabień 2018-10-31 22:01:05 +01:00 committed by Rafał
parent e45cfb5188
commit df81d3b9fd
3 changed files with 361 additions and 2 deletions

309
src/fs.rs
View File

@ -15,7 +15,7 @@ use byteorder_ext::{ReadBytesExt, WriteBytesExt};
use dir::{Dir, DirRawStream};
use dir_entry::DIR_ENTRY_SIZE;
use file::File;
use table::{alloc_cluster, count_free_clusters, read_fat_flags, ClusterIterator, RESERVED_FAT_ENTRIES};
use table::{alloc_cluster, count_free_clusters, read_fat_flags, format_fat, ClusterIterator, RESERVED_FAT_ENTRIES};
use time::{TimeProvider, DEFAULT_TIME_PROVIDER};
// FAT implementation based on:
@ -96,7 +96,7 @@ impl<T: Read + Write + Seek> ReadWriteSeek for T {}
#[allow(dead_code)]
#[derive(Default, Debug, Clone)]
struct BiosParameterBlock {
pub(crate) struct BiosParameterBlock {
bytes_per_sector: u16,
sectors_per_cluster: u8,
reserved_sectors: u16,
@ -176,6 +176,45 @@ impl BiosParameterBlock {
Ok(bpb)
}
fn serialize<T: Write>(&self, mut wrt: T) -> io::Result<()> {
wrt.write_u16::<LittleEndian>(self.bytes_per_sector)?;
wrt.write_u8(self.sectors_per_cluster)?;
wrt.write_u16::<LittleEndian>(self.reserved_sectors)?;
wrt.write_u8(self.fats)?;
wrt.write_u16::<LittleEndian>(self.root_entries)?;
wrt.write_u16::<LittleEndian>(self.total_sectors_16)?;
wrt.write_u8(self.media)?;
wrt.write_u16::<LittleEndian>(self.sectors_per_fat_16)?;
wrt.write_u16::<LittleEndian>(self.sectors_per_track)?;
wrt.write_u16::<LittleEndian>(self.heads)?;
wrt.write_u32::<LittleEndian>(self.hidden_sectors)?;
wrt.write_u32::<LittleEndian>(self.total_sectors_32)?;
if self.is_fat32() {
wrt.write_u32::<LittleEndian>(self.sectors_per_fat_32)?;
wrt.write_u16::<LittleEndian>(self.extended_flags)?;
wrt.write_u16::<LittleEndian>(self.fs_version)?;
wrt.write_u32::<LittleEndian>(self.root_dir_first_cluster)?;
wrt.write_u16::<LittleEndian>(self.fs_info_sector)?;
wrt.write_u16::<LittleEndian>(self.backup_boot_sector)?;
wrt.write_all(&self.reserved_0)?;
wrt.write_u8(self.drive_num)?;
wrt.write_u8(self.reserved_1)?;
wrt.write_u8(self.ext_sig)?; // 0x29
wrt.write_u32::<LittleEndian>(self.volume_id)?;
wrt.write_all(&self.volume_label)?;
wrt.write_all(&self.fs_type_label)?;
} else {
wrt.write_u8(self.drive_num)?;
wrt.write_u8(self.reserved_1)?;
wrt.write_u8(self.ext_sig)?; // 0x29
wrt.write_u32::<LittleEndian>(self.volume_id)?;
wrt.write_all(&self.volume_label)?;
wrt.write_all(&self.fs_type_label)?;
}
Ok(())
}
fn validate(&self) -> io::Result<()> {
// sanity checks
if self.bytes_per_sector.count_ones() != 1 {
@ -389,6 +428,20 @@ impl BootRecord {
Ok(boot)
}
fn serialize<T: Write>(&self, mut wrt: T) -> io::Result<()> {
wrt.write_all(&self.bootjmp)?;
wrt.write_all(&self.oem_name)?;
self.bpb.serialize(&mut wrt)?;
if self.bpb.is_fat32() {
wrt.write_all(&self.boot_code[0..420])?;
} else {
wrt.write_all(&self.boot_code[0..448])?;
}
wrt.write_all(&self.boot_sig)?;
Ok(())
}
fn validate(&self) -> io::Result<()> {
if self.boot_sig != [0x55, 0xAA] {
return Err(Error::new(ErrorKind::Other, "Invalid boot sector signature"));
@ -1018,3 +1071,255 @@ impl OemCpConverter for LossyOemCpConverter {
}
pub(crate) static LOSSY_OEM_CP_CONVERTER: LossyOemCpConverter = LossyOemCpConverter { _dummy: () };
#[derive(Default, Debug, Clone)]
pub struct FormatOptions {
pub bytes_per_sector: Option<u16>,
pub total_sectors: u32,
pub bytes_per_cluster: Option<u32>,
pub fat_type: Option<FatType>,
pub root_entries: Option<u16>,
pub media: Option<u8>,
pub sectors_per_track: Option<u16>,
pub heads: Option<u16>,
pub drive_num: Option<u8>,
pub volume_id: Option<u32>,
pub volume_label: Option<[u8; 11]>,
// force usage of Default trait by struct users
_end: [u8;0],
}
const KB: u32 = 1024;
const MB: u32 = KB * 1024;
fn determine_fat_type(total_bytes: u64) -> FatType {
if total_bytes < 4*MB as u64 {
FatType::Fat12
} else if total_bytes < 512*MB as u64 {
FatType::Fat16
} else {
FatType::Fat32
}
}
fn determine_bytes_per_cluster(total_bytes: u64, fat_type: FatType, bytes_per_sector: u16) -> u32 {
// TODO: test!
let min_cluster_size = bytes_per_sector;
let bytes_per_cluster = match fat_type {
FatType::Fat12 => (total_bytes as u32 / MB * KB) as u32,
FatType::Fat16 => (total_bytes / (32 * MB as u64) * KB as u64) as u32,
FatType::Fat32 => (total_bytes / (64 * MB as u64) * KB as u64) as u32,
};
const MAX_CLUSTER_SIZE: u32 = 32*KB;
cmp::min(cmp::max(bytes_per_cluster.next_power_of_two(), min_cluster_size as u32), MAX_CLUSTER_SIZE)
}
fn determine_sectors_per_fat(total_sectors: u32, reserved_sectors: u16, fats: u8, root_dir_sectors: u32,
sectors_per_cluster: u8, fat_entries_per_sector: u16, fat_type: FatType) -> u32 {
// FIXME: this is for FAT16/32
let tmp_val1 = total_sectors - (reserved_sectors as u32 + root_dir_sectors as u32);
let mut tmp_val2 = (256 * sectors_per_cluster as u32) + fats as u32;
if fat_type == FatType::Fat32 {
tmp_val2 = tmp_val2 / 2;
}
let sectors_per_fat = (tmp_val1 + (tmp_val2 - 1)) / tmp_val2;
// total_sectors = reserved_sectors + sectors_per_fat * fats + data_sectors
// sectors_per_fat >= data_sectors / sectors_per_cluster / fat_entries_per_sector
//
// sectors_per_fat >= (total_sectors - reserved_sectors - sectors_per_fat * fats) / sectors_per_cluster / fat_entries_per_sector
// sectors_per_fat + sectors_per_fat * fats / sectors_per_cluster / fat_entries_per_sector >= (total_sectors - reserved_sectors) / sectors_per_cluster / fat_entries_per_sector
// sectors_per_fat * (1 + fats / sectors_per_cluster / fat_entries_per_sector) >= (total_sectors - reserved_sectors) / sectors_per_cluster / fat_entries_per_sector
// sectors_per_fat >= (total_sectors - reserved_sectors) / sectors_per_cluster / fat_entries_per_sector / (1 + fats / sectors_per_cluster / fat_entries_per_sector)
// fat_entries_per_sector = bytes_per_sector / bytes_per_fat_entry = fat16: 512/2
sectors_per_fat
}
fn format_bpb(options: &FormatOptions) -> io::Result<(BiosParameterBlock, FatType)> {
// TODO: maybe total_sectors could be optional?
let bytes_per_sector = options.bytes_per_sector.unwrap_or(512);
let total_sectors = options.total_sectors;
let total_bytes = total_sectors as u64 * bytes_per_sector as u64;
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
let reserved_sectors: u16 = if fat_type == FatType::Fat32 { 4 } 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;
let fat_entries_per_sector = match fat_type {
FatType::Fat12 => bytes_per_sector * 8 / 12,
FatType::Fat16 => bytes_per_sector * 8 / 16,
FatType::Fat32 => bytes_per_sector * 8 / 32,
};
if total_sectors <= reserved_sectors as u32 + root_dir_sectors as u32 + 16 {
return Err(Error::new(ErrorKind::Other, "volume is too small",));
}
let sectors_per_fat = determine_sectors_per_fat(total_sectors, reserved_sectors, fats, root_dir_sectors,
sectors_per_cluster, fat_entries_per_sector, fat_type);
// drive_num should be 0 for floppy disks and 0x80 for hard disks - determine it using FAT type
let drive_num = options.drive_num.unwrap_or_else(|| if fat_type == FatType::Fat12 { 0 } else { 0x80 });
let reserved_0 = [0u8; 12];
let mut volume_label = [0u8; 11];
if let Some(volume_label_from_opts) = options.volume_label {
volume_label.copy_from_slice(&volume_label_from_opts);
} else {
volume_label.copy_from_slice("NO NAME ".as_bytes());
}
let mut fs_type_label = [0u8; 8];
let fs_type_label_str = match fat_type {
FatType::Fat12 => "FAT12 ",
FatType::Fat16 => "FAT16 ",
FatType::Fat32 => "FAT32 ",
};
fs_type_label.copy_from_slice(fs_type_label_str.as_bytes());
let bpb = BiosParameterBlock {
bytes_per_sector,
sectors_per_cluster,
reserved_sectors,
fats,
root_entries,
total_sectors_16: if total_sectors < 0x10000 { total_sectors as u16 } else { 0 },
media: options.media.unwrap_or(0xF8),
sectors_per_fat_16: if is_fat32 { 0 } else { sectors_per_fat as u16 },
sectors_per_track: options.sectors_per_track.unwrap_or(0x20),
heads: options.heads.unwrap_or(0x40),
hidden_sectors: 0,
total_sectors_32: if total_sectors >= 0x10000 { total_sectors } else { 0 },
// FAT32 fields start
sectors_per_fat_32: if is_fat32 { sectors_per_fat } else { 0 },
extended_flags: 0, // mirroring enabled
fs_version: 0,
root_dir_first_cluster: if is_fat32 { 2 } else { 0 },
fs_info_sector: if is_fat32 { 1 } else { 0 },
backup_boot_sector: if is_fat32 { 6 } else { 0 },
reserved_0,
// FAT32 fields end
drive_num,
reserved_1: 0,
ext_sig: 0x29,
volume_id: options.volume_id.unwrap_or(0x12345678), // TODO: random?
volume_label,
fs_type_label,
};
Ok((bpb, fat_type))
}
fn write_zeros<T: ReadWriteSeek>(mut disk: T, mut len: usize) -> io::Result<()> {
const ZEROS: [u8; 512] = [0u8; 512];
while len > 0 {
let write_size = cmp::min(len, ZEROS.len());
disk.write_all(&ZEROS[..write_size])?;
len -= write_size;
}
Ok(())
}
fn write_zeros_until_end_of_sector<T: ReadWriteSeek>(mut disk: T, bytes_per_sector: u16) -> io::Result<()> {
let pos = disk.seek(SeekFrom::Current(0))?;
let total_bytes_to_write = bytes_per_sector as usize - (pos % bytes_per_sector as u64) as usize;
if total_bytes_to_write != bytes_per_sector as usize {
write_zeros(disk, total_bytes_to_write)?;
}
Ok(())
}
fn format_boot_sector(options: &FormatOptions) -> io::Result<(BootRecord, FatType)> {
let mut boot: BootRecord = Default::default();
boot.bootjmp = [0xEB, 0x58, 0x90];
boot.oem_name.copy_from_slice("MSWIN4.1".as_bytes());
let (bpb, fat_type) = format_bpb(options)?;
boot.bpb = bpb;
// Boot code copied from boot sector initialized by mkfs.fat
let boot_code: [u8; 129] = [
0x0E, 0x1F, 0xBE, 0x77, 0x7C, 0xAC, 0x22, 0xC0, 0x74, 0x0B, 0x56, 0xB4, 0x0E, 0xBB, 0x07, 0x00,
0xCD, 0x10, 0x5E, 0xEB, 0xF0, 0x32, 0xE4, 0xCD, 0x16, 0xCD, 0x19, 0xEB, 0xFE, 0x54, 0x68, 0x69,
0x73, 0x20, 0x69, 0x73, 0x20, 0x6E, 0x6F, 0x74, 0x20, 0x61, 0x20, 0x62, 0x6F, 0x6F, 0x74, 0x61,
0x62, 0x6C, 0x65, 0x20, 0x64, 0x69, 0x73, 0x6B, 0x2E, 0x20, 0x20, 0x50, 0x6C, 0x65, 0x61, 0x73,
0x65, 0x20, 0x69, 0x6E, 0x73, 0x65, 0x72, 0x74, 0x20, 0x61, 0x20, 0x62, 0x6F, 0x6F, 0x74, 0x61,
0x62, 0x6C, 0x65, 0x20, 0x66, 0x6C, 0x6F, 0x70, 0x70, 0x79, 0x20, 0x61, 0x6E, 0x64, 0x0D, 0x0A,
0x70, 0x72, 0x65, 0x73, 0x73, 0x20, 0x61, 0x6E, 0x79, 0x20, 0x6B, 0x65, 0x79, 0x20, 0x74, 0x6F,
0x20, 0x74, 0x72, 0x79, 0x20, 0x61, 0x67, 0x61, 0x69, 0x6E, 0x20, 0x2E, 0x2E, 0x2E, 0x20, 0x0D,
0x0A];
boot.boot_code[..boot_code.len()].copy_from_slice(&boot_code);
boot.boot_sig = [0x55, 0xAA];
Ok((boot, fat_type))
}
// alternative names: create_filesystem, init_filesystem, prepare_fs
pub fn format_volume<T: ReadWriteSeek>(mut disk: T, options: FormatOptions) -> io::Result<()> {
let (boot, fat_type) = format_boot_sector(&options)?;
boot.serialize(&mut disk)?;
let bytes_per_sector = boot.bpb.bytes_per_sector;
write_zeros_until_end_of_sector(&mut disk, bytes_per_sector)?;
if boot.bpb.is_fat32() {
// FSInfo sector
let fs_info_sector = FsInfoSector {
free_cluster_count: None,
next_free_cluster: None,
dirty: false,
};
disk.seek(SeekFrom::Start(boot.bpb.fs_info_sector as u64 * bytes_per_sector as u64))?;
fs_info_sector.serialize(&mut disk)?;
write_zeros_until_end_of_sector(&mut disk, bytes_per_sector)?;
// backup boot sector
disk.seek(SeekFrom::Start(boot.bpb.backup_boot_sector as u64 * bytes_per_sector as u64))?;
boot.serialize(&mut disk)?;
write_zeros_until_end_of_sector(&mut disk, bytes_per_sector)?;
}
// FATs
let sectors_per_fat: u32 = boot.bpb.sectors_per_fat();
let bytes_per_fat: u32 = sectors_per_fat * bytes_per_sector as u32;
let reserved_sectors = boot.bpb.reserved_sectors;
let mut fat_pos = reserved_sectors as u64 * bytes_per_sector as u64;
for _ in 0..boot.bpb.fats {
disk.seek(SeekFrom::Start(fat_pos))?;
write_zeros(&mut disk, bytes_per_fat as usize)?;
disk.seek(SeekFrom::Start(fat_pos))?;
format_fat(&mut disk, fat_type, boot.bpb.media)?;
fat_pos += bytes_per_fat as u64;
// TODO: mark entries at the end of FAT as used (after FAT but before sector end)
// TODO: mark special entries 0x0FFFFFF0 - 0x0FFFFFFF as BAD if they exists on FAT32 volume
}
// Root directory
let root_dir_pos = fat_pos;
disk.seek(SeekFrom::Start(root_dir_pos))?;
let root_dir_sectors: u32 = boot.bpb.root_dir_sectors();
write_zeros(&mut disk, root_dir_sectors as usize * bytes_per_sector as usize)?;
if fat_type == FatType::Fat32 {
let root_dir_first_cluster = alloc_cluster(&mut disk, fat_type, None, None, 1)?;
assert!(root_dir_first_cluster == boot.bpb.root_dir_first_cluster);
let first_data_sector = reserved_sectors as u32 + sectors_per_fat + root_dir_sectors;
let sectors_per_cluster = boot.bpb.sectors_per_cluster;
let root_dir_first_sector =
((root_dir_first_cluster - RESERVED_FAT_ENTRIES) * sectors_per_cluster as u32) + first_data_sector;
let root_dir_pos = root_dir_first_sector as u64 * bytes_per_sector as u64;
disk.seek(SeekFrom::Start(root_dir_pos))?;
write_zeros(&mut disk, sectors_per_cluster as usize * bytes_per_sector as usize)?;
}
// TODO: create volume label dir entry if volume label is set
disk.seek(SeekFrom::Start(0))?;
Ok(())
}

View File

@ -109,6 +109,25 @@ pub(crate) fn read_fat_flags<T: ReadSeek>(fat: &mut T, fat_type: FatType) -> io:
Ok(FsStatusFlags { dirty, io_error })
}
pub(crate) fn format_fat<T: ReadWriteSeek>(fat: &mut T, fat_type: FatType, media: u8) -> io::Result<()> {
// Note: cannot use any seeking function here because fat parameter is not a disk slice
match fat_type {
FatType::Fat12 => {
fat.write_u8(media)?;
fat.write_u16::<LittleEndian>(0xFFFF)?;
},
FatType::Fat16 => {
fat.write_u16::<LittleEndian>(media as u16 | 0xFF00)?;
fat.write_u16::<LittleEndian>(0xFFFF)?;
},
FatType::Fat32 => {
fat.write_u32::<LittleEndian>(media as u32 | 0xFFFFF00)?;
fat.write_u32::<LittleEndian>(0xFFFFFFFF)?;
},
};
Ok(())
}
pub(crate) fn count_free_clusters<T: ReadSeek>(fat: &mut T, fat_type: FatType, total_clusters: u32) -> io::Result<u32> {
let end_cluster = total_clusters + RESERVED_FAT_ENTRIES;
match fat_type {

35
tests/format.rs Normal file
View File

@ -0,0 +1,35 @@
extern crate env_logger;
extern crate fatfs;
extern crate fscommon;
use std::io;
use std::io::prelude::*;
use fscommon::BufStream;
const KB: u32 = 1024;
const MB: u32 = KB * 1024;
const TEST_STR: &str = "Hi there Rust programmer!\n";
#[test]
fn test_format() {
let _ = env_logger::try_init();
let storage_vec: Vec<u8> = Vec::with_capacity((8 * MB) as usize);
let storage_cur = io::Cursor::new(storage_vec);
let mut buffered_stream = BufStream::new(storage_cur);
let mut opts: fatfs::FormatOptions = Default::default();
opts.total_sectors = 8 * MB / 512;
fatfs::format_volume(&mut buffered_stream, opts).expect("format volume");
let fs = fatfs::FileSystem::new(buffered_stream, fatfs::FsOptions::new()).expect("open fs");
let root_dir = fs.root_dir();
let entries = root_dir.iter().map(|r| r.unwrap()).collect::<Vec<_>>();
assert_eq!(entries.len(), 0);
let mut file = root_dir.create_file("short.txt").expect("create file");
file.truncate().expect("truncate file");
file.write_all(&TEST_STR.as_bytes()).expect("write file");
let entries = root_dir.iter().map(|r| r.unwrap()).collect::<Vec<_>>();
assert_eq!(entries.len(), 1);
}