forked from M-Labs/rust-fatfs
Mark volume dirty on first write and not-dirty on unmount
This commit is contained in:
parent
1f2427d371
commit
dc1ad7d2f7
1
TODO.md
1
TODO.md
@ -1,6 +1,5 @@
|
|||||||
TODO
|
TODO
|
||||||
====
|
====
|
||||||
* marking volume dirty on first write and not-dirty on unmount
|
|
||||||
* support for a volume label file in the root directory
|
* support for a volume label file in the root directory
|
||||||
* format volume API
|
* format volume API
|
||||||
* add method for getting `DirEntry` from a path (possible names: metadata, lookup)
|
* add method for getting `DirEntry` from a path (possible names: metadata, lookup)
|
||||||
|
@ -229,6 +229,8 @@ impl<'a, T: ReadWriteSeek> Write for File<'a, T> {
|
|||||||
if write_size == 0 {
|
if write_size == 0 {
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
|
// Mark the volume 'dirty'
|
||||||
|
self.fs.set_dirty_flag(true)?;
|
||||||
// Get cluster for write possibly allocating new one
|
// Get cluster for write possibly allocating new one
|
||||||
let current_cluster = if self.offset % cluster_size == 0 {
|
let current_cluster = if self.offset % cluster_size == 0 {
|
||||||
// next cluster
|
// next cluster
|
||||||
|
58
src/fs.rs
58
src/fs.rs
@ -1,6 +1,6 @@
|
|||||||
#[cfg(all(not(feature = "std"), feature = "alloc"))]
|
#[cfg(all(not(feature = "std"), feature = "alloc"))]
|
||||||
use alloc::String;
|
use alloc::String;
|
||||||
use core::cell::RefCell;
|
use core::cell::{Cell, RefCell};
|
||||||
use core::char;
|
use core::char;
|
||||||
use core::cmp;
|
use core::cmp;
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
@ -66,6 +66,24 @@ impl FsStatusFlags {
|
|||||||
pub fn io_error(&self) -> bool {
|
pub fn io_error(&self) -> bool {
|
||||||
self.io_error
|
self.io_error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn encode(&self) -> u8 {
|
||||||
|
let mut res = 0u8;
|
||||||
|
if self.dirty {
|
||||||
|
res |= 1;
|
||||||
|
}
|
||||||
|
if self.io_error {
|
||||||
|
res |= 2;
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode(flags: u8) -> Self {
|
||||||
|
FsStatusFlags {
|
||||||
|
dirty: flags & 1 != 0,
|
||||||
|
io_error: flags & 2 != 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A sum of `Read` and `Seek` traits.
|
/// A sum of `Read` and `Seek` traits.
|
||||||
@ -178,10 +196,7 @@ impl BiosParameterBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn status_flags(&self) -> FsStatusFlags {
|
fn status_flags(&self) -> FsStatusFlags {
|
||||||
FsStatusFlags {
|
FsStatusFlags::decode(self.reserved_1)
|
||||||
dirty: self.reserved_1 & 1 != 0,
|
|
||||||
io_error: self.reserved_1 & 2 != 0,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,6 +389,7 @@ pub struct FileSystem<T: ReadWriteSeek> {
|
|||||||
root_dir_sectors: u32,
|
root_dir_sectors: u32,
|
||||||
total_clusters: u32,
|
total_clusters: u32,
|
||||||
fs_info: RefCell<FsInfoSector>,
|
fs_info: RefCell<FsInfoSector>,
|
||||||
|
current_status_flags: Cell<FsStatusFlags>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ReadWriteSeek> FileSystem<T> {
|
impl<T: ReadWriteSeek> FileSystem<T> {
|
||||||
@ -434,6 +450,7 @@ impl<T: ReadWriteSeek> FileSystem<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// return FileSystem struct
|
// return FileSystem struct
|
||||||
|
let status_flags = bpb.status_flags();
|
||||||
Ok(FileSystem {
|
Ok(FileSystem {
|
||||||
disk: RefCell::new(disk),
|
disk: RefCell::new(disk),
|
||||||
options,
|
options,
|
||||||
@ -443,6 +460,7 @@ impl<T: ReadWriteSeek> FileSystem<T> {
|
|||||||
root_dir_sectors,
|
root_dir_sectors,
|
||||||
total_clusters,
|
total_clusters,
|
||||||
fs_info: RefCell::new(fs_info),
|
fs_info: RefCell::new(fs_info),
|
||||||
|
current_status_flags: Cell::new(status_flags),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -605,6 +623,7 @@ impl<T: ReadWriteSeek> FileSystem<T> {
|
|||||||
|
|
||||||
fn unmount_internal(&self) -> io::Result<()> {
|
fn unmount_internal(&self) -> io::Result<()> {
|
||||||
self.flush_fs_info()?;
|
self.flush_fs_info()?;
|
||||||
|
self.set_dirty_flag(false)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -618,6 +637,27 @@ impl<T: ReadWriteSeek> FileSystem<T> {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_dirty_flag(&self, dirty: bool) -> io::Result<()> {
|
||||||
|
// 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();
|
||||||
|
// 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();
|
||||||
|
disk.seek(io::SeekFrom::Start(offset))?;
|
||||||
|
disk.write_u8(encoded)?;
|
||||||
|
self.current_status_flags.set(flags);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Drop` implementation tries to unmount the filesystem when dropping.
|
/// `Drop` implementation tries to unmount the filesystem when dropping.
|
||||||
@ -692,8 +732,14 @@ impl<'a, T: ReadWriteSeek> Write for DiskSlice<'a, T> {
|
|||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
let offset = self.begin + self.offset;
|
let offset = self.begin + self.offset;
|
||||||
let write_size = cmp::min((self.size - self.offset) as usize, buf.len());
|
let write_size = cmp::min((self.size - self.offset) as usize, buf.len());
|
||||||
for i in 0..self.mirrors {
|
if write_size == 0 {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
// Mark the volume 'dirty'
|
||||||
|
self.fs.set_dirty_flag(true)?;
|
||||||
|
// Write data
|
||||||
let mut disk = self.fs.disk.borrow_mut();
|
let mut disk = self.fs.disk.borrow_mut();
|
||||||
|
for i in 0..self.mirrors {
|
||||||
disk.seek(SeekFrom::Start(offset + i as u64 * self.size))?;
|
disk.seek(SeekFrom::Start(offset + i as u64 * self.size))?;
|
||||||
disk.write_all(&buf[..write_size])?;
|
disk.write_all(&buf[..write_size])?;
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ extern crate fscommon;
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
use std::mem;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
use fatfs::FsOptions;
|
use fatfs::FsOptions;
|
||||||
@ -20,20 +21,28 @@ const TEST_STR2: &str = "Rust is cool!\n";
|
|||||||
|
|
||||||
type FileSystem = fatfs::FileSystem<BufStream<fs::File>>;
|
type FileSystem = fatfs::FileSystem<BufStream<fs::File>>;
|
||||||
|
|
||||||
fn call_with_fs(f: &Fn(FileSystem) -> (), filename: &str, test_seq: u32) {
|
fn call_with_tmp_img(f: &Fn(&str) -> (), filename: &str, test_seq: u32) {
|
||||||
let _ = env_logger::try_init();
|
let _ = env_logger::try_init();
|
||||||
let img_path = format!("{}/{}", IMG_DIR, filename);
|
let img_path = format!("{}/{}", IMG_DIR, filename);
|
||||||
let tmp_path = format!("{}/{}-{}", TMP_DIR, test_seq, filename);
|
let tmp_path = format!("{}/{}-{}", TMP_DIR, test_seq, filename);
|
||||||
fs::create_dir(TMP_DIR).ok();
|
fs::create_dir(TMP_DIR).ok();
|
||||||
fs::copy(&img_path, &tmp_path).unwrap();
|
fs::copy(&img_path, &tmp_path).unwrap();
|
||||||
{
|
f(tmp_path.as_str());
|
||||||
|
fs::remove_file(tmp_path).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_filesystem_rw(tmp_path: &str) -> FileSystem {
|
||||||
let file = fs::OpenOptions::new().read(true).write(true).open(&tmp_path).unwrap();
|
let file = fs::OpenOptions::new().read(true).write(true).open(&tmp_path).unwrap();
|
||||||
let buf_file = BufStream::new(file);
|
let buf_file = BufStream::new(file);
|
||||||
let options = FsOptions::new().update_accessed_date(true);
|
let options = FsOptions::new().update_accessed_date(true);
|
||||||
let fs = FileSystem::new(buf_file, options).unwrap();
|
FileSystem::new(buf_file, options).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call_with_fs(f: &Fn(FileSystem) -> (), filename: &str, test_seq: u32) {
|
||||||
|
call_with_tmp_img(&|tmp_path| {
|
||||||
|
let fs = open_filesystem_rw(tmp_path);
|
||||||
f(fs);
|
f(fs);
|
||||||
}
|
}, filename, test_seq);
|
||||||
fs::remove_file(tmp_path).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_write_short_file(fs: FileSystem) {
|
fn test_write_short_file(fs: FileSystem) {
|
||||||
@ -296,3 +305,27 @@ fn test_rename_file_fat16() {
|
|||||||
fn test_rename_file_fat32() {
|
fn test_rename_file_fat32() {
|
||||||
call_with_fs(&test_rename_file, FAT32_IMG, 6)
|
call_with_fs(&test_rename_file, FAT32_IMG, 6)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dirty_flag() {
|
||||||
|
call_with_tmp_img(&|tmp_path| {
|
||||||
|
// Open filesystem, make change, and forget it - should become dirty
|
||||||
|
let fs = open_filesystem_rw(tmp_path);
|
||||||
|
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();
|
||||||
|
// Make sure remounting does not clear the dirty flag
|
||||||
|
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);
|
||||||
|
}, FAT32_IMG, 7);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user