diff --git a/src/boot_sector.rs b/src/boot_sector.rs index 812222d..0636ad2 100644 --- a/src/boot_sector.rs +++ b/src/boot_sector.rs @@ -334,6 +334,10 @@ impl BiosParameterBlock { FsStatusFlags::decode(self.reserved_1) } + pub(crate) fn set_status_flags(&mut self, status_flags: FsStatusFlags) { + self.reserved_1 = status_flags.encode(); + } + pub(crate) fn is_fat32(&self) -> bool { // because this field must be zero on FAT32, and // because it must be non-zero on FAT12/FAT16, diff --git a/src/dir.rs b/src/dir.rs index 4f48583..767c73e 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -23,7 +23,7 @@ const LFN_PADDING: u16 = 0xFFFF; pub(crate) enum DirRawStream<'a, IO: ReadWriteSeek, TP, OCC> { File(File<'a, IO, TP, OCC>), - Root(DiskSlice, FsIoAdapter<'a, IO, TP, OCC>>), + Root(DiskSlice, IO::Error>), } impl DirRawStream<'_, IO, TP, OCC> { diff --git a/src/error.rs b/src/error.rs index 7a8bd9a..ecf1156 100644 --- a/src/error.rs +++ b/src/error.rs @@ -20,6 +20,8 @@ pub enum Error { DirectoryIsNotEmpty, /// File system internal structures are corrupted/invalid. CorruptedFileSystem, + /// File system is marked dirty. + DirtyFileSystem, /// There is not enough free space on the storage to finish the requested operation. NotEnoughSpace, /// The provided file name is either too long or empty. @@ -47,7 +49,7 @@ impl From> for std::io::Error { | Error::DirectoryIsNotEmpty => Self::new(std::io::ErrorKind::InvalidInput, error), Error::NotFound => Self::new(std::io::ErrorKind::NotFound, error), Error::AlreadyExists => Self::new(std::io::ErrorKind::AlreadyExists, error), - Error::CorruptedFileSystem => Self::new(std::io::ErrorKind::InvalidData, error), + Error::CorruptedFileSystem | Error::DirtyFileSystem => Self::new(std::io::ErrorKind::InvalidData, error), } } } @@ -66,6 +68,7 @@ impl core::fmt::Display for Error { Error::NotFound => write!(f, "No such file or directory"), Error::AlreadyExists => write!(f, "File or directory already exists"), Error::CorruptedFileSystem => write!(f, "Corrupted file system"), + Error::DirtyFileSystem => write!(f, "Dirty file system"), } } } diff --git a/src/fs.rs b/src/fs.rs index 996a68d..2efea04 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -16,9 +16,11 @@ use crate::error::Error; use crate::file::File; use crate::io::{self, IoBase, Read, ReadLeExt, Seek, SeekFrom, Write, WriteLeExt}; use crate::table::{ - alloc_cluster, count_free_clusters, format_fat, read_fat_flags, ClusterIterator, RESERVED_FAT_ENTRIES, + alloc_cluster, count_free_clusters, format_fat, read_fat_flags, write_fat_flags, ClusterIterator, + RESERVED_FAT_ENTRIES, }; use crate::time::{DefaultTimeProvider, TimeProvider}; +use crate::IoError; // FAT implementation based on: // http://wiki.osdev.org/FAT @@ -89,17 +91,17 @@ impl FsStatusFlags { /// /// Dirty flag means volume has been suddenly ejected from filesystem without unmounting. #[must_use] - pub fn dirty(&self) -> bool { + pub const fn dirty(&self) -> bool { self.dirty } /// Checks if the volume has the IO Error flag active. #[must_use] - pub fn io_error(&self) -> bool { + pub const fn io_error(&self) -> bool { self.io_error } - fn encode(self) -> u8 { + pub(crate) const fn encode(self) -> u8 { let mut res = 0_u8; if self.dirty { res |= 1; @@ -110,7 +112,7 @@ impl FsStatusFlags { res } - pub(crate) fn decode(flags: u8) -> Self { + pub(crate) const fn decode(flags: u8) -> Self { Self { dirty: flags & 1 != 0, io_error: flags & 2 != 0, @@ -238,6 +240,7 @@ impl FsInfoSector { #[derive(Copy, Clone, Debug, Default)] pub struct FsOptions { pub(crate) update_accessed_date: bool, + pub(crate) ignore_dirty_flag: bool, pub(crate) oem_cp_converter: OCC, pub(crate) time_provider: TP, } @@ -248,6 +251,7 @@ impl FsOptions { pub fn new() -> Self { Self { update_accessed_date: false, + ignore_dirty_flag: false, oem_cp_converter: LossyOemCpConverter::new(), time_provider: DefaultTimeProvider::new(), } @@ -262,19 +266,30 @@ impl FsOptions { self } + /// Ignore a dirty file system and clear the dirty flag when mounting it. + #[must_use] + pub fn ignore_dirty_flag(mut self, enabled: bool) -> Self { + self.ignore_dirty_flag = enabled; + self + } + /// Changes default OEM code page encoder-decoder. + #[must_use] pub fn oem_cp_converter(self, oem_cp_converter: OCC2) -> FsOptions { FsOptions:: { update_accessed_date: self.update_accessed_date, + ignore_dirty_flag: self.ignore_dirty_flag, oem_cp_converter, time_provider: self.time_provider, } } /// Changes default time provider. + #[must_use] pub fn time_provider(self, time_provider: TP2) -> FsOptions { FsOptions:: { update_accessed_date: self.update_accessed_date, + ignore_dirty_flag: self.ignore_dirty_flag, oem_cp_converter: self.oem_cp_converter, time_provider, } @@ -369,7 +384,7 @@ impl FileSystem { debug_assert!(disk.seek(SeekFrom::Current(0))? == 0); // read boot sector - let bpb = { + let mut bpb = { let boot = BootSector::deserialize(&mut disk)?; boot.validate()?; boot.bpb @@ -388,16 +403,40 @@ impl FileSystem { FsInfoSector::default() }; + let mut bpb_status_flags = bpb.status_flags(); + // if dirty flag is set completly ignore free_cluster_count in FSInfo - if bpb.status_flags().dirty { + if bpb_status_flags.dirty() { + if options.ignore_dirty_flag { + warn!("BPB is dirty, clearing dirty flag."); + bpb_status_flags.dirty = false; + Self::write_bpb_status_flags(&mut disk, fat_type, bpb_status_flags)?; + bpb.set_status_flags(bpb_status_flags); + } else { + return Err(Error::DirtyFileSystem); + } + fs_info.free_cluster_count = None; } // Validate the numbers stored in the free_cluster_count and next_free_cluster are within bounds for volume fs_info.validate_and_fix(total_clusters); - // return FileSystem struct - let status_flags = bpb.status_flags(); + let mut fat_status_flags = read_fat_flags(&mut fat_slice::<&mut IO, IO::Error, IO>(&mut disk, &bpb), fat_type)?; + if fat_status_flags.dirty() { + if options.ignore_dirty_flag { + warn!("FAT is dirty, clearing dirty flag."); + fat_status_flags.dirty = false; + write_fat_flags( + &mut fat_slice::<&mut IO, IO::Error, IO>(&mut disk, &bpb), + fat_type, + fat_status_flags, + )?; + } else { + return Err(Error::DirtyFileSystem); + } + } + trace!("FileSystem::new end"); Ok(Self { disk: RefCell::new(disk), @@ -408,7 +447,7 @@ impl FileSystem { root_dir_sectors, total_clusters, fs_info: RefCell::new(fs_info), - current_status_flags: Cell::new(status_flags), + current_status_flags: Cell::new(bpb_status_flags), }) } @@ -513,7 +552,7 @@ impl FileSystem { /// /// `Error::Io` will be returned if the underlying storage object returned an I/O error. pub fn read_status_flags(&self) -> Result> { - let bpb_status = self.bpb.status_flags(); + let bpb_status = self.current_status_flags.get(); let fat_status = read_fat_flags(&mut self.fat_slice(), self.fat_type)?; Ok(FsStatusFlags { dirty: bpb_status.dirty || fat_status.dirty, @@ -580,28 +619,39 @@ impl FileSystem { Ok(()) } - pub(crate) fn set_dirty_flag(&self, dirty: bool) -> Result<(), IO::Error> { - // Do not overwrite flags read from BPB on mount - let mut flags = self.bpb.status_flags(); - flags.dirty |= dirty; - // Check if flags has changed - let current_flags = self.current_status_flags.get(); - if flags == current_flags { - // Nothing to do - return Ok(()); - } - let encoded = flags.encode(); + fn write_bpb_status_flags( + disk: &mut IO, + fat_type: FatType, + status_flags: FsStatusFlags, + ) -> Result<(), Error> { + let encoded = status_flags.encode(); + // Note: only one field is written to avoid rewriting entire boot-sector which could be dangerous // Compute reserver_1 field offset and write new flags - let offset = if self.fat_type() == FatType::Fat32 { - 0x041 - } else { - 0x025 - }; - let mut disk = self.disk.borrow_mut(); + let offset = if fat_type == FatType::Fat32 { 0x041 } else { 0x025 }; + disk.seek(io::SeekFrom::Start(offset))?; disk.write_u8(encoded)?; - self.current_status_flags.set(flags); + + Ok(()) + } + + #[inline] + pub(crate) fn set_dirty_flag(&self, dirty: bool) -> Result<(), Error> { + let mut disk = self.disk.borrow_mut(); + + let mut status_flags = self.current_status_flags.get(); + + if status_flags.dirty == dirty { + // Dirty flag did not change. + return Ok(()); + } + + status_flags.dirty = dirty; + + Self::write_bpb_status_flags(&mut *disk, self.fat_type(), status_flags)?; + self.current_status_flags.set(status_flags); + Ok(()) } @@ -696,12 +746,12 @@ pub(crate) struct FsIoAdapter<'a, IO: ReadWriteSeek, TP, OCC> { } impl IoBase for FsIoAdapter<'_, IO, TP, OCC> { - type Error = IO::Error; + type Error = Error; } impl Read for FsIoAdapter<'_, IO, TP, OCC> { fn read(&mut self, buf: &mut [u8]) -> Result { - self.fs.disk.borrow_mut().read(buf) + Ok(self.fs.disk.borrow_mut().read(buf)?) } } @@ -715,13 +765,13 @@ impl Write for FsIoAdapter<'_, IO, TP, OCC> { } fn flush(&mut self) -> Result<(), Self::Error> { - self.fs.disk.borrow_mut().flush() + Ok(self.fs.disk.borrow_mut().flush()?) } } impl Seek for FsIoAdapter<'_, IO, TP, OCC> { fn seek(&mut self, pos: SeekFrom) -> Result { - self.fs.disk.borrow_mut().seek(pos) + Ok(self.fs.disk.borrow_mut().seek(pos)?) } } @@ -732,10 +782,7 @@ impl Clone for FsIoAdapter<'_, IO, TP, OCC> { } } -fn fat_slice>( - io: B, - bpb: &BiosParameterBlock, -) -> impl ReadWriteSeek> { +fn fat_slice, E, S: ReadWriteSeek>(io: B, bpb: &BiosParameterBlock) -> DiskSlice { let sectors_per_fat = bpb.sectors_per_fat(); let mirroring_enabled = bpb.mirroring_enabled(); let (fat_first_sector, mirrors) = if mirroring_enabled { @@ -748,16 +795,17 @@ fn fat_slice>( DiskSlice::from_sectors(fat_first_sector, sectors_per_fat, mirrors, bpb, io) } -pub(crate) struct DiskSlice { +pub(crate) struct DiskSlice { begin: u64, size: u64, offset: u64, mirrors: u8, inner: B, - phantom: PhantomData, + phantom_e: PhantomData, + phantom_s: PhantomData, } -impl, S: ReadWriteSeek> DiskSlice { +impl, E, S: ReadWriteSeek> DiskSlice { pub(crate) fn new(begin: u64, size: u64, mirrors: u8, inner: B) -> Self { Self { begin, @@ -765,7 +813,8 @@ impl, S: ReadWriteSeek> DiskSlice { mirrors, inner, offset: 0, - phantom: PhantomData, + phantom_e: PhantomData, + phantom_s: PhantomData, } } @@ -784,7 +833,7 @@ impl, S: ReadWriteSeek> DiskSlice { } // Note: derive cannot be used because of invalid bounds. See: https://github.com/rust-lang/rust/issues/26925 -impl Clone for DiskSlice { +impl Clone for DiskSlice { fn clone(&self) -> Self { Self { begin: self.begin, @@ -793,16 +842,26 @@ impl Clone for DiskSlice { mirrors: self.mirrors, inner: self.inner.clone(), // phantom is needed to add type bounds on the storage type - phantom: PhantomData, + phantom_e: PhantomData, + phantom_s: PhantomData, } } } -impl IoBase for DiskSlice { - type Error = Error; +impl IoBase for DiskSlice +where + E: IoError, +{ + type Error = Error; } -impl, S: Read + Seek> Read for DiskSlice { +impl Read for DiskSlice +where + B: BorrowMut, + E: IoError, + S: Read + Seek, + Error: From, +{ fn read(&mut self, buf: &mut [u8]) -> Result { let offset = self.begin + self.offset; let read_size = cmp::min(self.size - self.offset, buf.len() as u64) as usize; @@ -813,7 +872,13 @@ impl, S: Read + Seek> Read for DiskSlice { } } -impl, S: Write + Seek> Write for DiskSlice { +impl Write for DiskSlice +where + B: BorrowMut, + E: IoError, + S: Write + Seek, + Error: From, +{ fn write(&mut self, buf: &[u8]) -> Result { let offset = self.begin + self.offset; let write_size = cmp::min(self.size - self.offset, buf.len() as u64) as usize; @@ -835,7 +900,13 @@ impl, S: Write + Seek> Write for DiskSlice { } } -impl Seek for DiskSlice { +impl Seek for DiskSlice +where + B: BorrowMut, + E: IoError, + S: IoBase, + Error: From, +{ fn seek(&mut self, pos: SeekFrom) -> Result { let new_offset_opt: Option = match pos { SeekFrom::Current(x) => i64::try_from(self.offset) @@ -1167,7 +1238,7 @@ pub fn format_volume(storage: &mut S, options: FormatVolumeOpt storage.seek(SeekFrom::Start(fat_pos))?; write_zeros(storage, bpb.bytes_from_sectors(sectors_per_all_fats))?; { - let mut fat_slice = fat_slice::(storage, bpb); + let mut fat_slice = fat_slice::<&mut S, S::Error, S>(storage, bpb); let sectors_per_fat = bpb.sectors_per_fat(); let bytes_per_fat = bpb.bytes_from_sectors(sectors_per_fat); format_fat(&mut fat_slice, fat_type, bpb.media, bytes_per_fat, bpb.total_clusters())?; @@ -1181,7 +1252,7 @@ pub fn format_volume(storage: &mut S, options: FormatVolumeOpt write_zeros(storage, bpb.bytes_from_sectors(root_dir_sectors))?; if fat_type == FatType::Fat32 { let root_dir_first_cluster = { - let mut fat_slice = fat_slice::(storage, bpb); + let mut fat_slice = fat_slice::<&mut S, S::Error, S>(storage, bpb); alloc_cluster(&mut fat_slice, fat_type, None, None, 1)? }; assert!(root_dir_first_cluster == bpb.root_dir_first_cluster); diff --git a/src/table.rs b/src/table.rs index 3afbb63..5c66d94 100644 --- a/src/table.rs +++ b/src/table.rs @@ -152,6 +152,12 @@ where Ok(new_cluster) } +const FAT16_IO_ERROR_BIT: u32 = 1 << 14; +const FAT16_DIRTY_BIT: u32 = 1 << 15; + +const FAT32_IO_ERROR_BIT: u32 = 1 << 26; +const FAT32_DIRTY_BIT: u32 = 1 << 27; + pub(crate) fn read_fat_flags(fat: &mut S, fat_type: FatType) -> Result> where S: Read + Seek, @@ -166,17 +172,54 @@ where }; let dirty = match fat_type { FatType::Fat12 => false, - FatType::Fat16 => val & (1 << 15) == 0, - FatType::Fat32 => val & (1 << 27) == 0, + FatType::Fat16 => val & FAT16_DIRTY_BIT == 0, + FatType::Fat32 => val & FAT32_DIRTY_BIT == 0, }; let io_error = match fat_type { FatType::Fat12 => false, - FatType::Fat16 => val & (1 << 14) == 0, - FatType::Fat32 => val & (1 << 26) == 0, + FatType::Fat16 => val & FAT16_IO_ERROR_BIT == 0, + FatType::Fat32 => val & FAT32_IO_ERROR_BIT == 0, }; Ok(FsStatusFlags { dirty, io_error }) } +pub(crate) fn write_fat_flags(fat: &mut S, fat_type: FatType, fat_status: FsStatusFlags) -> Result<(), Error> +where + S: Read + Write + Seek, + E: IoError, + Error: From, +{ + match fat_type { + FatType::Fat12 => Ok(()), + FatType::Fat16 => { + let mut val = 0; + + if fat_status.dirty { + val |= FAT16_DIRTY_BIT; + } + + if fat_status.io_error { + val |= FAT16_IO_ERROR_BIT; + } + + Fat16::set(fat, 1, FatValue::Data(!val)) + } + FatType::Fat32 => { + let mut val = 0; + + if fat_status.dirty { + val |= FAT32_DIRTY_BIT; + } + + if fat_status.io_error { + val |= FAT32_IO_ERROR_BIT; + } + + Fat32::set(fat, 1, FatValue::Data(!val)) + } + } +} + pub(crate) fn count_free_clusters(fat: &mut S, fat_type: FatType, total_clusters: u32) -> Result> where S: Read + Seek, diff --git a/tests/write.rs b/tests/write.rs index 8fb55b9..bed8467 100644 --- a/tests/write.rs +++ b/tests/write.rs @@ -4,7 +4,7 @@ use std::io::prelude::*; use std::mem; use std::str; -use fatfs::{DefaultTimeProvider, FsOptions, LossyOemCpConverter, StdIoWrapper}; +use fatfs::{DefaultTimeProvider, Error, FsOptions, LossyOemCpConverter, StdIoWrapper}; use fscommon::BufStream; const FAT12_IMG: &str = "fat12.img"; @@ -27,16 +27,18 @@ fn call_with_tmp_img ()>(f: F, filename: &str, test_seq: u32) { fs::remove_file(tmp_path).unwrap(); } -fn open_filesystem_rw(tmp_path: &str) -> FileSystem { +fn open_filesystem_rw(tmp_path: &str, ignore_dirty_flag: bool) -> Result> { let file = fs::OpenOptions::new().read(true).write(true).open(&tmp_path).unwrap(); let buf_file = BufStream::new(file); - let options = FsOptions::new().update_accessed_date(true); - FileSystem::new(buf_file, options).unwrap() + let options = FsOptions::new() + .update_accessed_date(true) + .ignore_dirty_flag(ignore_dirty_flag); + FileSystem::new(buf_file, options) } fn call_with_fs ()>(f: F, filename: &str, test_seq: u32) { let callback = |tmp_path: &str| { - let fs = open_filesystem_rw(tmp_path); + let fs = open_filesystem_rw(tmp_path, false).unwrap(); f(fs); }; call_with_tmp_img(&callback, filename, test_seq); @@ -338,23 +340,26 @@ fn test_rename_file_fat32() { fn test_dirty_flag(tmp_path: &str) { // Open filesystem, make change, and forget it - should become dirty - let fs = open_filesystem_rw(tmp_path); + let fs = open_filesystem_rw(tmp_path, false).unwrap(); let status_flags = fs.read_status_flags().unwrap(); assert_eq!(status_flags.dirty(), false); assert_eq!(status_flags.io_error(), false); fs.root_dir().create_file("abc.txt").unwrap(); mem::forget(fs); // Check if volume is dirty now - let fs = open_filesystem_rw(tmp_path); - let status_flags = fs.read_status_flags().unwrap(); - assert_eq!(status_flags.dirty(), true); - assert_eq!(status_flags.io_error(), false); - fs.unmount().unwrap(); + let fs = open_filesystem_rw(tmp_path, false); + assert!(matches!(fs, Err(Error::DirtyFileSystem))); // Make sure remounting does not clear the dirty flag - let fs = open_filesystem_rw(tmp_path); + let fs = open_filesystem_rw(tmp_path, false); + assert!(matches!(fs, Err(Error::DirtyFileSystem))); + // Make sure clearing the dirty flag allows mounting to succeed + let fs = open_filesystem_rw(tmp_path, true).unwrap(); let status_flags = fs.read_status_flags().unwrap(); - assert_eq!(status_flags.dirty(), true); + assert_eq!(status_flags.dirty(), false); assert_eq!(status_flags.io_error(), false); + // Make sure the dirty flag is cleared after remounting. + drop(fs); + open_filesystem_rw(tmp_path, false).unwrap(); } #[test]