diff --git a/README.md b/README.md index 41985d2..eace6f8 100644 --- a/README.md +++ b/README.md @@ -17,16 +17,16 @@ let sdcard = embedded_sdmmc::SdCard::new(sdmmc_spi, delay); println!("Card size is {} bytes", sdcard.num_bytes()?); // Now let's look for volumes (also known as partitions) on our block device. // To do this we need a Volume Manager. It will take ownership of the block device. -let mut volume_mgr = embedded_sdmmc::VolumeManager::new(sdcard, time_source); +let volume_mgr = embedded_sdmmc::VolumeManager::new(sdcard, time_source); // Try and access Volume 0 (i.e. the first partition). // The volume object holds information about the filesystem on that volume. -let mut volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?; +let volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?; println!("Volume 0: {:?}", volume0); // Open the root directory (mutably borrows from the volume). -let mut root_dir = volume0.open_root_dir()?; +let root_dir = volume0.open_root_dir()?; // Open a file called "MY_FILE.TXT" in the root directory // This mutably borrows the directory. -let mut my_file = root_dir.open_file_in_dir("MY_FILE.TXT", embedded_sdmmc::Mode::ReadOnly)?; +let my_file = root_dir.open_file_in_dir("MY_FILE.TXT", embedded_sdmmc::Mode::ReadOnly)?; // Print the contents of the file, assuming it's in ISO-8859-1 encoding while !my_file.is_eof() { let mut buffer = [0u8; 32]; @@ -43,7 +43,7 @@ By default the `VolumeManager` will initialize with a maximum number of `4` open ```rust // Create a volume manager with a maximum of 6 open directories, 12 open files, and 4 volumes (or partitions) -let mut cont: VolumeManager<_, _, 6, 12, 4> = VolumeManager::new_with_limits(block, time_source); +let cont: VolumeManager<_, _, 6, 12, 4> = VolumeManager::new_with_limits(block, time_source); ``` ## Supported features diff --git a/examples/append_file.rs b/examples/append_file.rs index f5a3bc4..2c7dd8e 100644 --- a/examples/append_file.rs +++ b/examples/append_file.rs @@ -22,7 +22,9 @@ use linux::*; const FILE_TO_APPEND: &str = "README.TXT"; -use embedded_sdmmc::{Error, Mode, VolumeIdx, VolumeManager}; +use embedded_sdmmc::{Error, Mode, VolumeIdx}; + +type VolumeManager = embedded_sdmmc::VolumeManager; fn main() -> Result<(), embedded_sdmmc::Error> { env_logger::init(); @@ -30,12 +32,11 @@ fn main() -> Result<(), embedded_sdmmc::Error> { let filename = args.next().unwrap_or_else(|| "/dev/mmcblk0".into()); let print_blocks = args.find(|x| x == "-v").map(|_| true).unwrap_or(false); let lbd = LinuxBlockDevice::new(filename, print_blocks).map_err(Error::DeviceError)?; - let mut volume_mgr: VolumeManager = - VolumeManager::new_with_limits(lbd, Clock, 0xAA00_0000); - let mut volume = volume_mgr.open_volume(VolumeIdx(0))?; - let mut root_dir = volume.open_root_dir()?; + let volume_mgr: VolumeManager = VolumeManager::new_with_limits(lbd, Clock, 0xAA00_0000); + let volume = volume_mgr.open_volume(VolumeIdx(0))?; + let root_dir = volume.open_root_dir()?; println!("\nCreating file {}...", FILE_TO_APPEND); - let mut f = root_dir.open_file_in_dir(FILE_TO_APPEND, Mode::ReadWriteAppend)?; + let f = root_dir.open_file_in_dir(FILE_TO_APPEND, Mode::ReadWriteAppend)?; f.write(b"\r\n\r\nThis has been added to your file.\r\n")?; Ok(()) } diff --git a/examples/big_dir.rs b/examples/big_dir.rs index b355c3c..a017026 100644 --- a/examples/big_dir.rs +++ b/examples/big_dir.rs @@ -3,7 +3,9 @@ extern crate embedded_sdmmc; mod linux; use linux::*; -use embedded_sdmmc::{Error, VolumeManager}; +use embedded_sdmmc::Error; + +type VolumeManager = embedded_sdmmc::VolumeManager; fn main() -> Result<(), embedded_sdmmc::Error> { env_logger::init(); @@ -11,20 +13,19 @@ fn main() -> Result<(), embedded_sdmmc::Error> { let filename = args.next().unwrap_or_else(|| "/dev/mmcblk0".into()); let print_blocks = args.find(|x| x == "-v").map(|_| true).unwrap_or(false); let lbd = LinuxBlockDevice::new(filename, print_blocks).map_err(Error::DeviceError)?; - let mut volume_mgr: VolumeManager = - VolumeManager::new_with_limits(lbd, Clock, 0xAA00_0000); - let mut volume = volume_mgr + let volume_mgr: VolumeManager = VolumeManager::new_with_limits(lbd, Clock, 0xAA00_0000); + let volume = volume_mgr .open_volume(embedded_sdmmc::VolumeIdx(1)) .unwrap(); println!("Volume: {:?}", volume); - let mut root_dir = volume.open_root_dir().unwrap(); + let root_dir = volume.open_root_dir().unwrap(); let mut file_num = 0; loop { file_num += 1; let file_name = format!("{}.da", file_num); println!("opening file {file_name} for writing"); - let mut file = root_dir + let file = root_dir .open_file_in_dir( file_name.as_str(), embedded_sdmmc::Mode::ReadWriteCreateOrTruncate, diff --git a/examples/create_file.rs b/examples/create_file.rs index 81263ce..cc8b193 100644 --- a/examples/create_file.rs +++ b/examples/create_file.rs @@ -22,7 +22,9 @@ use linux::*; const FILE_TO_CREATE: &str = "CREATE.TXT"; -use embedded_sdmmc::{Error, Mode, VolumeIdx, VolumeManager}; +use embedded_sdmmc::{Error, Mode, VolumeIdx}; + +type VolumeManager = embedded_sdmmc::VolumeManager; fn main() -> Result<(), embedded_sdmmc::Error> { env_logger::init(); @@ -30,15 +32,14 @@ fn main() -> Result<(), embedded_sdmmc::Error> { let filename = args.next().unwrap_or_else(|| "/dev/mmcblk0".into()); let print_blocks = args.find(|x| x == "-v").map(|_| true).unwrap_or(false); let lbd = LinuxBlockDevice::new(filename, print_blocks).map_err(Error::DeviceError)?; - let mut volume_mgr: VolumeManager = - VolumeManager::new_with_limits(lbd, Clock, 0xAA00_0000); - let mut volume = volume_mgr.open_volume(VolumeIdx(0))?; - let mut root_dir = volume.open_root_dir()?; + let volume_mgr: VolumeManager = VolumeManager::new_with_limits(lbd, Clock, 0xAA00_0000); + let volume = volume_mgr.open_volume(VolumeIdx(0))?; + let root_dir = volume.open_root_dir()?; println!("\nCreating file {}...", FILE_TO_CREATE); // This will panic if the file already exists: use ReadWriteCreateOrAppend // or ReadWriteCreateOrTruncate instead if you want to modify an existing // file. - let mut f = root_dir.open_file_in_dir(FILE_TO_CREATE, Mode::ReadWriteCreate)?; + let f = root_dir.open_file_in_dir(FILE_TO_CREATE, Mode::ReadWriteCreate)?; f.write(b"Hello, this is a new file on disk\r\n")?; Ok(()) } diff --git a/examples/delete_file.rs b/examples/delete_file.rs index 743b2d5..4d88213 100644 --- a/examples/delete_file.rs +++ b/examples/delete_file.rs @@ -25,7 +25,9 @@ use linux::*; const FILE_TO_DELETE: &str = "README.TXT"; -use embedded_sdmmc::{Error, VolumeIdx, VolumeManager}; +use embedded_sdmmc::{Error, VolumeIdx}; + +type VolumeManager = embedded_sdmmc::VolumeManager; fn main() -> Result<(), embedded_sdmmc::Error> { env_logger::init(); @@ -33,10 +35,9 @@ fn main() -> Result<(), embedded_sdmmc::Error> { let filename = args.next().unwrap_or_else(|| "/dev/mmcblk0".into()); let print_blocks = args.find(|x| x == "-v").map(|_| true).unwrap_or(false); let lbd = LinuxBlockDevice::new(filename, print_blocks).map_err(Error::DeviceError)?; - let mut volume_mgr: VolumeManager = - VolumeManager::new_with_limits(lbd, Clock, 0xAA00_0000); - let mut volume = volume_mgr.open_volume(VolumeIdx(0))?; - let mut root_dir = volume.open_root_dir()?; + let volume_mgr: VolumeManager = VolumeManager::new_with_limits(lbd, Clock, 0xAA00_0000); + let volume = volume_mgr.open_volume(VolumeIdx(0))?; + let root_dir = volume.open_root_dir()?; println!("Deleting file {}...", FILE_TO_DELETE); root_dir.delete_file_in_dir(FILE_TO_DELETE)?; println!("Deleted!"); diff --git a/examples/linux/mod.rs b/examples/linux/mod.rs index 5abb99f..6eefe23 100644 --- a/examples/linux/mod.rs +++ b/examples/linux/mod.rs @@ -34,22 +34,14 @@ impl LinuxBlockDevice { impl BlockDevice for LinuxBlockDevice { type Error = std::io::Error; - fn read( - &self, - blocks: &mut [Block], - start_block_idx: BlockIdx, - reason: &str, - ) -> Result<(), Self::Error> { + fn read(&self, blocks: &mut [Block], start_block_idx: BlockIdx) -> Result<(), Self::Error> { self.file .borrow_mut() .seek(SeekFrom::Start(start_block_idx.into_bytes()))?; for block in blocks.iter_mut() { self.file.borrow_mut().read_exact(&mut block.contents)?; if self.print_blocks { - println!( - "Read block ({}) {:?}: {:?}", - reason, start_block_idx, &block - ); + println!("Read block {:?}: {:?}", start_block_idx, &block); } } Ok(()) diff --git a/examples/list_dir.rs b/examples/list_dir.rs index 18c121d..0057849 100644 --- a/examples/list_dir.rs +++ b/examples/list_dir.rs @@ -32,24 +32,25 @@ //! $ cargo run --example list_dir -- ./disk.img //! ``` -extern crate embedded_sdmmc; - mod linux; use linux::*; -use embedded_sdmmc::{Directory, VolumeIdx, VolumeManager}; +use embedded_sdmmc::{ShortFileName, VolumeIdx}; type Error = embedded_sdmmc::Error; +type Directory<'a> = embedded_sdmmc::Directory<'a, LinuxBlockDevice, Clock, 8, 4, 4>; +type VolumeManager = embedded_sdmmc::VolumeManager; + fn main() -> Result<(), Error> { env_logger::init(); let mut args = std::env::args().skip(1); let filename = args.next().unwrap_or_else(|| "/dev/mmcblk0".into()); let print_blocks = args.find(|x| x == "-v").map(|_| true).unwrap_or(false); + let lbd = LinuxBlockDevice::new(filename, print_blocks).map_err(Error::DeviceError)?; - let mut volume_mgr: VolumeManager = - VolumeManager::new_with_limits(lbd, Clock, 0xAA00_0000); - let mut volume = volume_mgr.open_volume(VolumeIdx(0))?; + let volume_mgr: VolumeManager = VolumeManager::new_with_limits(lbd, Clock, 0xAA00_0000); + let volume = volume_mgr.open_volume(VolumeIdx(0))?; let root_dir = volume.open_root_dir()?; list_dir(root_dir, "/")?; Ok(()) @@ -58,10 +59,7 @@ fn main() -> Result<(), Error> { /// Recursively print a directory listing for the open directory given. /// /// The path is for display purposes only. -fn list_dir( - mut directory: Directory, - path: &str, -) -> Result<(), Error> { +fn list_dir(directory: Directory<'_>, path: &str) -> Result<(), Error> { println!("Listing {}", path); let mut children = Vec::new(); directory.iterate_dir(|entry| { @@ -77,8 +75,8 @@ fn list_dir( } ); if entry.attributes.is_directory() - && entry.name != embedded_sdmmc::ShortFileName::parent_dir() - && entry.name != embedded_sdmmc::ShortFileName::this_dir() + && entry.name != ShortFileName::parent_dir() + && entry.name != ShortFileName::this_dir() { children.push(entry.name.clone()); } diff --git a/examples/read_file.rs b/examples/read_file.rs index 1a958c1..e8d900c 100644 --- a/examples/read_file.rs +++ b/examples/read_file.rs @@ -39,7 +39,9 @@ use linux::*; const FILE_TO_READ: &str = "README.TXT"; -use embedded_sdmmc::{Error, Mode, VolumeIdx, VolumeManager}; +use embedded_sdmmc::{Error, Mode, VolumeIdx}; + +type VolumeManager = embedded_sdmmc::VolumeManager; fn main() -> Result<(), embedded_sdmmc::Error> { env_logger::init(); @@ -47,12 +49,14 @@ fn main() -> Result<(), embedded_sdmmc::Error> { let filename = args.next().unwrap_or_else(|| "/dev/mmcblk0".into()); let print_blocks = args.find(|x| x == "-v").map(|_| true).unwrap_or(false); let lbd = LinuxBlockDevice::new(filename, print_blocks).map_err(Error::DeviceError)?; - let mut volume_mgr: VolumeManager = - VolumeManager::new_with_limits(lbd, Clock, 0xAA00_0000); - let mut volume = volume_mgr.open_volume(VolumeIdx(0))?; - let mut root_dir = volume.open_root_dir()?; + let volume_mgr: VolumeManager = VolumeManager::new_with_limits(lbd, Clock, 0xAA00_0000); + let volume = volume_mgr.open_volume(VolumeIdx(0))?; + let root_dir = volume.open_root_dir()?; println!("\nReading file {}...", FILE_TO_READ); - let mut f = root_dir.open_file_in_dir(FILE_TO_READ, Mode::ReadOnly)?; + let f = root_dir.open_file_in_dir(FILE_TO_READ, Mode::ReadOnly)?; + // Proves we can open two files at once now (or try to - this file doesn't exist) + let f2 = root_dir.open_file_in_dir("MISSING.DAT", Mode::ReadOnly); + assert!(f2.is_err()); while !f.is_eof() { let mut buffer = [0u8; 16]; let offset = f.offset(); diff --git a/examples/readme_test.rs b/examples/readme_test.rs index 7ae7384..fd86780 100644 --- a/examples/readme_test.rs +++ b/examples/readme_test.rs @@ -125,16 +125,16 @@ fn main() -> Result<(), Error> { println!("Card size is {} bytes", sdcard.num_bytes()?); // Now let's look for volumes (also known as partitions) on our block device. // To do this we need a Volume Manager. It will take ownership of the block device. - let mut volume_mgr = embedded_sdmmc::VolumeManager::new(sdcard, time_source); + let volume_mgr = embedded_sdmmc::VolumeManager::new(sdcard, time_source); // Try and access Volume 0 (i.e. the first partition). // The volume object holds information about the filesystem on that volume. - let mut volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?; + let volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?; println!("Volume 0: {:?}", volume0); // Open the root directory (mutably borrows from the volume). - let mut root_dir = volume0.open_root_dir()?; + let root_dir = volume0.open_root_dir()?; // Open a file called "MY_FILE.TXT" in the root directory // This mutably borrows the directory. - let mut my_file = root_dir.open_file_in_dir("MY_FILE.TXT", embedded_sdmmc::Mode::ReadOnly)?; + let my_file = root_dir.open_file_in_dir("MY_FILE.TXT", embedded_sdmmc::Mode::ReadOnly)?; // Print the contents of the file, assuming it's in ISO-8859-1 encoding while !my_file.is_eof() { let mut buffer = [0u8; 32]; diff --git a/examples/shell.rs b/examples/shell.rs index 0e41ce1..341bb51 100644 --- a/examples/shell.rs +++ b/examples/shell.rs @@ -69,11 +69,12 @@ //! | `B:/BACKUP.000/NAMES.CSV` | `B:` | Yes | `[BACKUP.000]` | `NAMES.CSV` | `B:/BACKUP.000/NAMES.CSV` | //! | `B:../BACKUP.000/NAMES.CSV` | `B:` | No | `[.., BACKUP.000]` | `NAMES.CSV` | `B:/BACKUP.000/NAMES.CSV` | -use std::io::prelude::*; +use std::{cell::RefCell, io::prelude::*}; -use embedded_sdmmc::{ - Error as EsError, RawDirectory, RawVolume, ShortFileName, VolumeIdx, VolumeManager, -}; +use embedded_sdmmc::{Error as EsError, RawDirectory, RawVolume, ShortFileName, VolumeIdx}; + +type VolumeManager = embedded_sdmmc::VolumeManager; +type Directory<'a> = embedded_sdmmc::Directory<'a, LinuxBlockDevice, Clock, 8, 8, 4>; use crate::linux::{Clock, LinuxBlockDevice}; @@ -182,21 +183,21 @@ struct VolumeState { } struct Context { - volume_mgr: VolumeManager, - volumes: [Option; 4], + volume_mgr: VolumeManager, + volumes: RefCell<[Option; 4]>, current_volume: usize, } impl Context { fn current_path(&self) -> Vec { - let Some(s) = &self.volumes[self.current_volume] else { + let Some(s) = &self.volumes.borrow()[self.current_volume] else { return vec![]; }; s.path.clone() } /// Print some help text - fn help(&mut self) -> Result<(), Error> { + fn help(&self) -> Result<(), Error> { println!("Commands:"); println!("\thelp -> this help text"); println!("\t: -> change volume/partition"); @@ -219,38 +220,43 @@ impl Context { } /// Print volume manager status - fn stat(&mut self) -> Result<(), Error> { + fn stat(&self) -> Result<(), Error> { println!("Status:\n{:#?}", self.volume_mgr); Ok(()) } /// Print a directory listing - fn dir(&mut self, path: &Path) -> Result<(), Error> { + fn dir(&self, path: &Path) -> Result<(), Error> { println!("Directory listing of {:?}", path); let dir = self.resolve_existing_directory(path)?; - let mut dir = dir.to_directory(&mut self.volume_mgr); dir.iterate_dir(|entry| { - println!( - "{:12} {:9} {} {} {:08X?} {:?}", - entry.name, entry.size, entry.ctime, entry.mtime, entry.cluster, entry.attributes - ); + if !entry.attributes.is_volume() && !entry.attributes.is_lfn() { + println!( + "{:12} {:9} {} {} {:08X?} {:?}", + entry.name, + entry.size, + entry.ctime, + entry.mtime, + entry.cluster, + entry.attributes + ); + } })?; Ok(()) } /// Print a recursive directory listing for the given path - fn tree(&mut self, path: &Path) -> Result<(), Error> { + fn tree(&self, path: &Path) -> Result<(), Error> { println!("Directory listing of {:?}", path); let dir = self.resolve_existing_directory(path)?; // tree_dir will close this directory, always - self.tree_dir(dir) + Self::tree_dir(dir) } /// Print a recursive directory listing for the given open directory. /// /// Will close the given directory. - fn tree_dir(&mut self, dir: RawDirectory) -> Result<(), Error> { - let mut dir = dir.to_directory(&mut self.volume_mgr); + fn tree_dir(dir: Directory) -> Result<(), Error> { let mut children = Vec::new(); dir.iterate_dir(|entry| { println!( @@ -264,25 +270,12 @@ impl Context { children.push(entry.name.clone()); } })?; - // Be sure to close this, no matter what happens - let dir = dir.to_raw_directory(); for child in children { println!("Entering {}", child); - let child_dir = match self.volume_mgr.open_dir(dir, &child) { - Ok(child_dir) => child_dir, - Err(e) => { - self.volume_mgr.close_dir(dir).expect("close open dir"); - return Err(e); - } - }; - let result = self.tree_dir(child_dir); + let child_dir = dir.open_dir(&child)?; + Self::tree_dir(child_dir)?; println!("Returning from {}", child); - if let Err(e) = result { - self.volume_mgr.close_dir(dir).expect("close open dir"); - return Err(e); - } } - self.volume_mgr.close_dir(dir).expect("close open dir"); Ok(()) } @@ -293,23 +286,25 @@ impl Context { /// sub-folder, starting from the current directory on the current volume /// * An absolute path like `B:/FOO` changes the CWD on Volume 1 to path /// `/FOO` - fn cd(&mut self, full_path: &Path) -> Result<(), Error> { + fn cd(&self, full_path: &Path) -> Result<(), Error> { let volume_idx = self.resolve_volume(full_path)?; - let d = self.resolve_existing_directory(full_path)?; - let Some(s) = &mut self.volumes[volume_idx] else { - self.volume_mgr.close_dir(d).expect("close open dir"); + let (mut d, fragment) = self.resolve_filename(full_path)?; + d.change_dir(fragment)?; + let Some(s) = &mut self.volumes.borrow_mut()[volume_idx] else { return Err(Error::NoSuchVolume); }; self.volume_mgr .close_dir(s.directory) .expect("close open dir"); - s.directory = d; + s.directory = d.to_raw_directory(); if full_path.is_absolute() { s.path.clear(); } for fragment in full_path.iterate_components().filter(|s| !s.is_empty()) { if fragment == ".." { s.path.pop(); + } else if fragment == "." { + // do nothing } else { s.path.push(fragment.to_owned()); } @@ -318,10 +313,9 @@ impl Context { } /// print a text file - fn cat(&mut self, filename: &Path) -> Result<(), Error> { + fn cat(&self, filename: &Path) -> Result<(), Error> { let (dir, filename) = self.resolve_filename(filename)?; - let mut dir = dir.to_directory(&mut self.volume_mgr); - let mut f = dir.open_file_in_dir(filename, embedded_sdmmc::Mode::ReadOnly)?; + let f = dir.open_file_in_dir(filename, embedded_sdmmc::Mode::ReadOnly)?; let mut data = Vec::new(); while !f.is_eof() { let mut buffer = vec![0u8; 65536]; @@ -339,10 +333,9 @@ impl Context { } /// print a binary file - fn hexdump(&mut self, filename: &Path) -> Result<(), Error> { + fn hexdump(&self, filename: &Path) -> Result<(), Error> { let (dir, filename) = self.resolve_filename(filename)?; - let mut dir = dir.to_directory(&mut self.volume_mgr); - let mut f = dir.open_file_in_dir(filename, embedded_sdmmc::Mode::ReadOnly)?; + let f = dir.open_file_in_dir(filename, embedded_sdmmc::Mode::ReadOnly)?; let mut data = Vec::new(); while !f.is_eof() { let mut buffer = vec![0u8; 65536]; @@ -376,9 +369,8 @@ impl Context { } /// create a directory - fn mkdir(&mut self, dir_name: &Path) -> Result<(), Error> { + fn mkdir(&self, dir_name: &Path) -> Result<(), Error> { let (dir, filename) = self.resolve_filename(dir_name)?; - let mut dir = dir.to_directory(&mut self.volume_mgr); dir.make_dir_in_dir(filename) } @@ -426,11 +418,10 @@ impl Context { /// * Relative names, like `../SOMEDIR` or `./SOMEDIR`, traverse /// starting at the current volume and directory. /// * Absolute, like `B:/SOMEDIR/OTHERDIR` start at the given volume. - fn resolve_existing_directory(&mut self, full_path: &Path) -> Result { - let (dir, fragment) = self.resolve_filename(full_path)?; - let mut work_dir = dir.to_directory(&mut self.volume_mgr); - work_dir.change_dir(fragment)?; - Ok(work_dir.to_raw_directory()) + fn resolve_existing_directory<'a>(&'a self, full_path: &Path) -> Result, Error> { + let (mut dir, fragment) = self.resolve_filename(full_path)?; + dir.change_dir(fragment)?; + Ok(dir) } /// Either get the volume from the path, or pick the current volume. @@ -455,33 +446,30 @@ impl Context { /// * Relative names, like `../SOMEDIR/SOMEFILE` or `./SOMEDIR/SOMEFILE`, traverse /// starting at the current volume and directory. /// * Absolute, like `B:/SOMEDIR/SOMEFILE` start at the given volume. - fn resolve_filename<'path>( - &mut self, + fn resolve_filename<'a, 'path>( + &'a self, full_path: &'path Path, - ) -> Result<(RawDirectory, &'path str), Error> { + ) -> Result<(Directory<'a>, &'path str), Error> { let volume_idx = self.resolve_volume(full_path)?; - let Some(s) = &mut self.volumes[volume_idx] else { + let Some(s) = &self.volumes.borrow()[volume_idx] else { return Err(Error::NoSuchVolume); }; let mut work_dir = if full_path.is_absolute() { // relative to root self.volume_mgr .open_root_dir(s.volume)? - .to_directory(&mut self.volume_mgr) + .to_directory(&self.volume_mgr) } else { // relative to CWD self.volume_mgr .open_dir(s.directory, ".")? - .to_directory(&mut self.volume_mgr) + .to_directory(&self.volume_mgr) }; for fragment in full_path.iterate_dirs() { work_dir.change_dir(fragment)?; } - Ok(( - work_dir.to_raw_directory(), - full_path.basename().unwrap_or("."), - )) + Ok((work_dir, full_path.basename().unwrap_or("."))) } /// Convert a volume index to a letter @@ -498,7 +486,7 @@ impl Context { impl Drop for Context { fn drop(&mut self) { - for v in self.volumes.iter_mut() { + for v in self.volumes.borrow_mut().iter_mut() { if let Some(v) = v { println!("Closing directory {:?}", v.directory); self.volume_mgr @@ -525,7 +513,7 @@ fn main() -> Result<(), Error> { let mut ctx = Context { volume_mgr: VolumeManager::new_with_limits(lbd, Clock, 100), - volumes: [None, None, None, None], + volumes: RefCell::new([None, None, None, None]), current_volume: 0, }; @@ -533,10 +521,14 @@ fn main() -> Result<(), Error> { for volume_no in 0..4 { match ctx.volume_mgr.open_raw_volume(VolumeIdx(volume_no)) { Ok(volume) => { - println!("Volume # {}: found", Context::volume_to_letter(volume_no)); + println!( + "Volume # {}: found, label: {:?}", + Context::volume_to_letter(volume_no), + ctx.volume_mgr.get_root_volume_label(volume)? + ); match ctx.volume_mgr.open_root_dir(volume) { Ok(root_dir) => { - ctx.volumes[volume_no] = Some(VolumeState { + ctx.volumes.borrow_mut()[volume_no] = Some(VolumeState { directory: root_dir, volume, path: vec![], diff --git a/src/blockdevice.rs b/src/blockdevice.rs index 4564457..618eda2 100644 --- a/src/blockdevice.rs +++ b/src/blockdevice.rs @@ -16,45 +16,6 @@ pub struct Block { pub contents: [u8; Block::LEN], } -/// The linear numeric address of a block (or sector). -/// -/// The first block on a disk gets `BlockIdx(0)` (which usually contains the -/// Master Boot Record). -#[cfg_attr(feature = "defmt-log", derive(defmt::Format))] -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct BlockIdx(pub u32); - -/// The a number of blocks (or sectors). -/// -/// Add this to a `BlockIdx` to get an actual address on disk. -#[cfg_attr(feature = "defmt-log", derive(defmt::Format))] -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct BlockCount(pub u32); - -/// An iterator returned from `Block::range`. -pub struct BlockIter { - inclusive_end: BlockIdx, - current: BlockIdx, -} - -/// A block device - a device which can read and write blocks (or -/// sectors). Only supports devices which are <= 2 TiB in size. -pub trait BlockDevice { - /// The errors that the `BlockDevice` can return. Must be debug formattable. - type Error: core::fmt::Debug; - /// Read one or more blocks, starting at the given block index. - fn read( - &self, - blocks: &mut [Block], - start_block_idx: BlockIdx, - reason: &str, - ) -> Result<(), Self::Error>; - /// Write one or more blocks, starting at the given block index. - fn write(&self, blocks: &[Block], start_block_idx: BlockIdx) -> Result<(), Self::Error>; - /// Determine how many blocks this device can hold. - fn num_blocks(&self) -> Result; -} - impl Block { /// All our blocks are a fixed length of 512 bytes. We do not support /// 'Advanced Format' Hard Drives with 4 KiB blocks, nor weird old @@ -72,64 +33,6 @@ impl Block { } } -impl Default for Block { - fn default() -> Self { - Self::new() - } -} - -impl core::ops::Add for BlockIdx { - type Output = BlockIdx; - fn add(self, rhs: BlockCount) -> BlockIdx { - BlockIdx(self.0 + rhs.0) - } -} - -impl core::ops::AddAssign for BlockIdx { - fn add_assign(&mut self, rhs: BlockCount) { - self.0 += rhs.0 - } -} - -impl core::ops::Add for BlockCount { - type Output = BlockCount; - fn add(self, rhs: BlockCount) -> BlockCount { - BlockCount(self.0 + rhs.0) - } -} - -impl core::ops::AddAssign for BlockCount { - fn add_assign(&mut self, rhs: BlockCount) { - self.0 += rhs.0 - } -} - -impl core::ops::Sub for BlockIdx { - type Output = BlockIdx; - fn sub(self, rhs: BlockCount) -> BlockIdx { - BlockIdx(self.0 - rhs.0) - } -} - -impl core::ops::SubAssign for BlockIdx { - fn sub_assign(&mut self, rhs: BlockCount) { - self.0 -= rhs.0 - } -} - -impl core::ops::Sub for BlockCount { - type Output = BlockCount; - fn sub(self, rhs: BlockCount) -> BlockCount { - BlockCount(self.0 - rhs.0) - } -} - -impl core::ops::SubAssign for BlockCount { - fn sub_assign(&mut self, rhs: BlockCount) { - self.0 -= rhs.0 - } -} - impl core::ops::Deref for Block { type Target = [u8; 512]; fn deref(&self) -> &[u8; 512] { @@ -164,6 +67,107 @@ impl core::fmt::Debug for Block { } } +impl Default for Block { + fn default() -> Self { + Self::new() + } +} + +/// A block device - a device which can read and write blocks (or +/// sectors). Only supports devices which are <= 2 TiB in size. +pub trait BlockDevice { + /// The errors that the `BlockDevice` can return. Must be debug formattable. + type Error: core::fmt::Debug; + /// Read one or more blocks, starting at the given block index. + fn read(&self, blocks: &mut [Block], start_block_idx: BlockIdx) -> Result<(), Self::Error>; + /// Write one or more blocks, starting at the given block index. + fn write(&self, blocks: &[Block], start_block_idx: BlockIdx) -> Result<(), Self::Error>; + /// Determine how many blocks this device can hold. + fn num_blocks(&self) -> Result; +} + +/// A caching layer for block devices +/// +/// Caches a single block. +#[derive(Debug)] +pub struct BlockCache { + block_device: D, + block: [Block; 1], + block_idx: Option, +} + +impl BlockCache +where + D: BlockDevice, +{ + /// Create a new block cache + pub fn new(block_device: D) -> BlockCache { + BlockCache { + block_device, + block: [Block::new()], + block_idx: None, + } + } + + /// Read a block, and return a reference to it. + pub fn read(&mut self, block_idx: BlockIdx) -> Result<&Block, D::Error> { + if self.block_idx != Some(block_idx) { + self.block_idx = None; + self.block_device.read(&mut self.block, block_idx)?; + self.block_idx = Some(block_idx); + } + Ok(&self.block[0]) + } + + /// Read a block, and return a reference to it. + pub fn read_mut(&mut self, block_idx: BlockIdx) -> Result<&mut Block, D::Error> { + if self.block_idx != Some(block_idx) { + self.block_idx = None; + self.block_device.read(&mut self.block, block_idx)?; + self.block_idx = Some(block_idx); + } + Ok(&mut self.block[0]) + } + + /// Write back a block you read with [`Self::read_mut`] and then modified. + pub fn write_back(&mut self) -> Result<(), D::Error> { + self.block_device.write( + &self.block, + self.block_idx.expect("write_back with no read"), + ) + } + + /// Access a blank sector + pub fn blank_mut(&mut self, block_idx: BlockIdx) -> &mut Block { + self.block_idx = Some(block_idx); + for b in self.block[0].iter_mut() { + *b = 0; + } + &mut self.block[0] + } + + /// Access the block device + pub fn block_device(&mut self) -> &mut D { + // invalidate the cache + self.block_idx = None; + // give them the block device + &mut self.block_device + } + + /// Get the block device back + pub fn free(self) -> D { + self.block_device + } +} + +/// The linear numeric address of a block (or sector). +/// +/// The first block on a disk gets `BlockIdx(0)` (which usually contains the +/// Master Boot Record). +#[cfg_attr(feature = "defmt-log", derive(defmt::Format))] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct BlockIdx(pub u32); + impl BlockIdx { /// Convert a block index into a 64-bit byte offset from the start of the /// volume. Useful if your underlying block device actually works in @@ -179,6 +183,65 @@ impl BlockIdx { } } +impl core::ops::Add for BlockIdx { + type Output = BlockIdx; + fn add(self, rhs: BlockCount) -> BlockIdx { + BlockIdx(self.0 + rhs.0) + } +} + +impl core::ops::AddAssign for BlockIdx { + fn add_assign(&mut self, rhs: BlockCount) { + self.0 += rhs.0 + } +} + +impl core::ops::Sub for BlockIdx { + type Output = BlockIdx; + fn sub(self, rhs: BlockCount) -> BlockIdx { + BlockIdx(self.0 - rhs.0) + } +} + +impl core::ops::SubAssign for BlockIdx { + fn sub_assign(&mut self, rhs: BlockCount) { + self.0 -= rhs.0 + } +} + +/// The a number of blocks (or sectors). +/// +/// Add this to a `BlockIdx` to get an actual address on disk. +#[cfg_attr(feature = "defmt-log", derive(defmt::Format))] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct BlockCount(pub u32); + +impl core::ops::Add for BlockCount { + type Output = BlockCount; + fn add(self, rhs: BlockCount) -> BlockCount { + BlockCount(self.0 + rhs.0) + } +} + +impl core::ops::AddAssign for BlockCount { + fn add_assign(&mut self, rhs: BlockCount) { + self.0 += rhs.0 + } +} + +impl core::ops::Sub for BlockCount { + type Output = BlockCount; + fn sub(self, rhs: BlockCount) -> BlockCount { + BlockCount(self.0 - rhs.0) + } +} + +impl core::ops::SubAssign for BlockCount { + fn sub_assign(&mut self, rhs: BlockCount) { + self.0 -= rhs.0 + } +} + impl BlockCount { /// How many blocks are required to hold this many bytes. /// @@ -205,6 +268,12 @@ impl BlockCount { } } +/// An iterator returned from `Block::range`. +pub struct BlockIter { + inclusive_end: BlockIdx, + current: BlockIdx, +} + impl BlockIter { /// Create a new `BlockIter`, from the given start block, through (and /// including) the given end block. diff --git a/src/fat/bpb.rs b/src/fat/bpb.rs index f06f23e..c7e83b6 100644 --- a/src/fat/bpb.rs +++ b/src/fat/bpb.rs @@ -85,12 +85,13 @@ impl<'a> Bpb<'a> { // FAT16/FAT32 functions /// Get the Volume Label string for this volume - pub fn volume_label(&self) -> &[u8] { - if self.fat_type != FatType::Fat32 { - &self.data[43..=53] - } else { - &self.data[71..=81] + pub fn volume_label(&self) -> [u8; 11] { + let mut result = [0u8; 11]; + match self.fat_type { + FatType::Fat16 => result.copy_from_slice(&self.data[43..=53]), + FatType::Fat32 => result.copy_from_slice(&self.data[71..=81]), } + result } // FAT32 only functions @@ -98,10 +99,9 @@ impl<'a> Bpb<'a> { /// On a FAT32 volume, return the free block count from the Info Block. On /// a FAT16 volume, returns None. pub fn fs_info_block(&self) -> Option { - if self.fat_type != FatType::Fat32 { - None - } else { - Some(BlockCount(u32::from(self.fs_info()))) + match self.fat_type { + FatType::Fat16 => None, + FatType::Fat32 => Some(BlockCount(u32::from(self.fs_info()))), } } diff --git a/src/fat/mod.rs b/src/fat/mod.rs index 35641cb..504f67f 100644 --- a/src/fat/mod.rs +++ b/src/fat/mod.rs @@ -14,36 +14,6 @@ pub enum FatType { Fat32, } -pub(crate) struct BlockCache { - block: Block, - idx: Option, -} -impl BlockCache { - pub fn empty() -> Self { - BlockCache { - block: Block::new(), - idx: None, - } - } - pub(crate) fn read( - &mut self, - block_device: &D, - block_idx: BlockIdx, - reason: &str, - ) -> Result<&Block, Error> - where - D: BlockDevice, - { - if Some(block_idx) != self.idx { - self.idx = Some(block_idx); - block_device - .read(core::slice::from_mut(&mut self.block), block_idx, reason) - .map_err(Error::DeviceError)?; - } - Ok(&self.block) - } -} - mod bpb; mod info; mod ondiskdirentry; @@ -54,8 +24,6 @@ pub use info::{Fat16Info, Fat32Info, FatSpecificInfo, InfoSector}; pub use ondiskdirentry::OnDiskDirEntry; pub use volume::{parse_volume, FatVolume, VolumeName}; -use crate::{Block, BlockDevice, BlockIdx, Error}; - // **************************************************************************** // // Unit Tests @@ -139,7 +107,11 @@ mod test { "#; let results = [ Expected::Short(DirEntry { - name: ShortFileName::create_from_str_mixed_case("boot").unwrap(), + name: unsafe { + VolumeName::create_from_str("boot") + .unwrap() + .to_short_filename() + }, mtime: Timestamp::from_calendar(2015, 11, 21, 19, 35, 18).unwrap(), ctime: Timestamp::from_calendar(2015, 11, 21, 19, 35, 18).unwrap(), attributes: Attributes::create_from_fat(Attributes::VOLUME), @@ -349,7 +321,7 @@ mod test { assert_eq!(bpb.fat_size16(), 32); assert_eq!(bpb.total_blocks32(), 122_880); assert_eq!(bpb.footer(), 0xAA55); - assert_eq!(bpb.volume_label(), b"boot "); + assert_eq!(bpb.volume_label(), *b"boot "); assert_eq!(bpb.fat_size(), 32); assert_eq!(bpb.total_blocks(), 122_880); assert_eq!(bpb.fat_type, FatType::Fat16); diff --git a/src/fat/volume.rs b/src/fat/volume.rs index 8adc378..4713c42 100644 --- a/src/fat/volume.rs +++ b/src/fat/volume.rs @@ -6,35 +6,129 @@ use crate::{ Bpb, Fat16Info, Fat32Info, FatSpecificInfo, FatType, InfoSector, OnDiskDirEntry, RESERVED_ENTRIES, }, - trace, warn, Attributes, Block, BlockCount, BlockDevice, BlockIdx, ClusterId, DirEntry, - DirectoryInfo, Error, ShortFileName, TimeSource, VolumeType, + filesystem::FilenameError, + trace, warn, Attributes, Block, BlockCache, BlockCount, BlockDevice, BlockIdx, ClusterId, + DirEntry, DirectoryInfo, Error, ShortFileName, TimeSource, VolumeType, }; use byteorder::{ByteOrder, LittleEndian}; use core::convert::TryFrom; use heapless::String; -use super::BlockCache; - -/// The name given to a particular FAT formatted volume. +/// An MS-DOS 11 character volume label. +/// +/// ISO-8859-1 encoding is assumed. Trailing spaces are trimmed. Reserved +/// characters are not allowed. There is no file extension, unlike with a +/// filename. +/// +/// Volume labels can be found in the BIOS Parameter Block, and in a root +/// directory entry with the 'Volume Label' bit set. Both places should have the +/// same contents, but they can get out of sync. +/// +/// MS-DOS FDISK would show you the one in the BPB, but DIR would show you the +/// one in the root directory. #[cfg_attr(feature = "defmt-log", derive(defmt::Format))] -#[derive(Clone, PartialEq, Eq)] +#[derive(PartialEq, Eq, Clone)] pub struct VolumeName { - data: [u8; 11], + pub(crate) contents: [u8; Self::TOTAL_LEN], } impl VolumeName { - /// Create a new VolumeName - pub fn new(data: [u8; 11]) -> VolumeName { - VolumeName { data } + const TOTAL_LEN: usize = 11; + + /// Get name + pub fn name(&self) -> &[u8] { + self.contents.trim_ascii_end() + } + + /// Create a new MS-DOS volume label. + pub fn create_from_str(name: &str) -> Result { + let mut sfn = VolumeName { + contents: [b' '; Self::TOTAL_LEN], + }; + + let mut idx = 0; + for ch in name.chars() { + match ch { + // Microsoft say these are the invalid characters + '\u{0000}'..='\u{001F}' + | '"' + | '*' + | '+' + | ',' + | '/' + | ':' + | ';' + | '<' + | '=' + | '>' + | '?' + | '[' + | '\\' + | ']' + | '.' + | '|' => { + return Err(FilenameError::InvalidCharacter); + } + x if x > '\u{00FF}' => { + // We only handle ISO-8859-1 which is Unicode Code Points + // \U+0000 to \U+00FF. This is above that. + return Err(FilenameError::InvalidCharacter); + } + _ => { + let b = ch as u8; + if idx < Self::TOTAL_LEN { + sfn.contents[idx] = b; + } else { + return Err(FilenameError::NameTooLong); + } + idx += 1; + } + } + } + if idx == 0 { + return Err(FilenameError::FilenameEmpty); + } + Ok(sfn) + } + + /// Convert to a Short File Name + /// + /// # Safety + /// + /// Volume Labels can contain things that Short File Names cannot, so only + /// do this conversion if you are creating the name of a directory entry + /// with the 'Volume Label' attribute. + pub unsafe fn to_short_filename(self) -> ShortFileName { + ShortFileName { + contents: self.contents, + } } } -impl core::fmt::Debug for VolumeName { - fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { - match core::str::from_utf8(&self.data) { - Ok(s) => write!(fmt, "{:?}", s), - Err(_e) => write!(fmt, "{:?}", &self.data), +impl core::fmt::Display for VolumeName { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + let mut printed = 0; + for &c in self.name().iter() { + // converting a byte to a codepoint means you are assuming + // ISO-8859-1 encoding, because that's how Unicode was designed. + write!(f, "{}", c as char)?; + printed += 1; + } + if let Some(mut width) = f.width() { + if width > printed { + width -= printed; + for _ in 0..width { + write!(f, "{}", f.fill())?; + } + } } + Ok(()) + } +} + +impl core::fmt::Debug for VolumeName { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "VolumeName(\"{}\")", self) } } @@ -68,7 +162,10 @@ pub struct FatVolume { impl FatVolume { /// Write a new entry in the FAT - pub fn update_info_sector(&mut self, block_device: &D) -> Result<(), Error> + pub fn update_info_sector( + &mut self, + block_cache: &mut BlockCache, + ) -> Result<(), Error> where D: BlockDevice, { @@ -80,20 +177,18 @@ impl FatVolume { if self.free_clusters_count.is_none() && self.next_free_cluster.is_none() { return Ok(()); } - let mut blocks = [Block::new()]; - block_device - .read(&mut blocks, fat32_info.info_location, "read_info_sector") + trace!("Reading info sector"); + let block = block_cache + .read_mut(fat32_info.info_location) .map_err(Error::DeviceError)?; - let block = &mut blocks[0]; if let Some(count) = self.free_clusters_count { block[488..492].copy_from_slice(&count.to_le_bytes()); } if let Some(next_free_cluster) = self.next_free_cluster { block[492..496].copy_from_slice(&next_free_cluster.0.to_le_bytes()); } - block_device - .write(&blocks, fat32_info.info_location) - .map_err(Error::DeviceError)?; + trace!("Writing info sector"); + block_cache.write_back()?; } } Ok(()) @@ -110,22 +205,22 @@ impl FatVolume { /// Write a new entry in the FAT fn update_fat( &mut self, - block_device: &D, + block_cache: &mut BlockCache, cluster: ClusterId, new_value: ClusterId, ) -> Result<(), Error> where D: BlockDevice, { - let mut blocks = [Block::new()]; let this_fat_block_num; match &self.fat_specific_info { FatSpecificInfo::Fat16(_fat16_info) => { let fat_offset = cluster.0 * 2; this_fat_block_num = self.lba_start + self.fat_start.offset_bytes(fat_offset); let this_fat_ent_offset = (fat_offset % Block::LEN_U32) as usize; - block_device - .read(&mut blocks, this_fat_block_num, "read_fat") + trace!("Reading FAT for update"); + let block = block_cache + .read_mut(this_fat_block_num) .map_err(Error::DeviceError)?; // See let entry = match new_value { @@ -136,7 +231,7 @@ impl FatVolume { _ => new_value.0 as u16, }; LittleEndian::write_u16( - &mut blocks[0][this_fat_ent_offset..=this_fat_ent_offset + 1], + &mut block[this_fat_ent_offset..=this_fat_ent_offset + 1], entry, ); } @@ -145,8 +240,9 @@ impl FatVolume { let fat_offset = cluster.0 * 4; this_fat_block_num = self.lba_start + self.fat_start.offset_bytes(fat_offset); let this_fat_ent_offset = (fat_offset % Block::LEN_U32) as usize; - block_device - .read(&mut blocks, this_fat_block_num, "read_fat") + trace!("Reading FAT for update"); + let block = block_cache + .read_mut(this_fat_block_num) .map_err(Error::DeviceError)?; let entry = match new_value { ClusterId::INVALID => 0x0FFF_FFF6, @@ -154,28 +250,25 @@ impl FatVolume { ClusterId::EMPTY => 0x0000_0000, _ => new_value.0, }; - let existing = LittleEndian::read_u32( - &blocks[0][this_fat_ent_offset..=this_fat_ent_offset + 3], - ); + let existing = + LittleEndian::read_u32(&block[this_fat_ent_offset..=this_fat_ent_offset + 3]); let new = (existing & 0xF000_0000) | (entry & 0x0FFF_FFFF); LittleEndian::write_u32( - &mut blocks[0][this_fat_ent_offset..=this_fat_ent_offset + 3], + &mut block[this_fat_ent_offset..=this_fat_ent_offset + 3], new, ); } } - block_device - .write(&blocks, this_fat_block_num) - .map_err(Error::DeviceError)?; + trace!("Updating FAT"); + block_cache.write_back()?; Ok(()) } /// Look in the FAT to see which cluster comes next. pub(crate) fn next_cluster( &self, - block_device: &D, + block_cache: &mut BlockCache, cluster: ClusterId, - fat_block_cache: &mut BlockCache, ) -> Result> where D: BlockDevice, @@ -188,8 +281,8 @@ impl FatVolume { let fat_offset = cluster.0 * 2; let this_fat_block_num = self.lba_start + self.fat_start.offset_bytes(fat_offset); let this_fat_ent_offset = (fat_offset % Block::LEN_U32) as usize; - let block = - fat_block_cache.read(block_device, this_fat_block_num, "next_cluster")?; + trace!("Walking FAT"); + let block = block_cache.read(this_fat_block_num)?; let fat_entry = LittleEndian::read_u16(&block[this_fat_ent_offset..=this_fat_ent_offset + 1]); match fat_entry { @@ -211,8 +304,8 @@ impl FatVolume { let fat_offset = cluster.0 * 4; let this_fat_block_num = self.lba_start + self.fat_start.offset_bytes(fat_offset); let this_fat_ent_offset = (fat_offset % Block::LEN_U32) as usize; - let block = - fat_block_cache.read(block_device, this_fat_block_num, "next_cluster")?; + trace!("Walking FAT"); + let block = block_cache.read(this_fat_block_num)?; let fat_entry = LittleEndian::read_u32(&block[this_fat_ent_offset..=this_fat_ent_offset + 3]) & 0x0FFF_FFFF; @@ -277,7 +370,7 @@ impl FatVolume { /// needed pub(crate) fn write_new_directory_entry( &mut self, - block_device: &D, + block_cache: &mut BlockCache, time_source: &T, dir_cluster: ClusterId, name: ShortFileName, @@ -308,17 +401,16 @@ impl FatVolume { }; // Walk the directory - let mut blocks = [Block::new()]; while let Some(cluster) = current_cluster { - for block in first_dir_block_num.range(dir_size) { - block_device - .read(&mut blocks, block, "read_dir") + for block_idx in first_dir_block_num.range(dir_size) { + trace!("Reading directory"); + let block = block_cache + .read_mut(block_idx) .map_err(Error::DeviceError)?; - let entries_per_block = Block::LEN / OnDiskDirEntry::LEN; - for entry in 0..entries_per_block { - let start = entry * OnDiskDirEntry::LEN; - let end = (entry + 1) * OnDiskDirEntry::LEN; - let dir_entry = OnDiskDirEntry::new(&blocks[0][start..end]); + for (i, dir_entry_bytes) in + block.chunks_exact_mut(OnDiskDirEntry::LEN).enumerate() + { + let dir_entry = OnDiskDirEntry::new(dir_entry_bytes); // 0x00 or 0xE5 represents a free entry if !dir_entry.is_valid() { let ctime = time_source.get_timestamp(); @@ -327,34 +419,30 @@ impl FatVolume { attributes, ClusterId::EMPTY, ctime, - block, - start as u32, + block_idx, + (i * OnDiskDirEntry::LEN) as u32, ); - blocks[0][start..start + 32] + dir_entry_bytes .copy_from_slice(&entry.serialize(FatType::Fat16)[..]); - block_device - .write(&blocks, block) - .map_err(Error::DeviceError)?; + trace!("Updating directory"); + block_cache.write_back()?; return Ok(entry); } } } if cluster != ClusterId::ROOT_DIR { - let mut block_cache = BlockCache::empty(); - current_cluster = - match self.next_cluster(block_device, cluster, &mut block_cache) { - Ok(n) => { - first_dir_block_num = self.cluster_to_block(n); - Some(n) - } - Err(Error::EndOfFile) => { - let c = - self.alloc_cluster(block_device, Some(cluster), true)?; - first_dir_block_num = self.cluster_to_block(c); - Some(c) - } - _ => None, - }; + current_cluster = match self.next_cluster(block_cache, cluster) { + Ok(n) => { + first_dir_block_num = self.cluster_to_block(n); + Some(n) + } + Err(Error::EndOfFile) => { + let c = self.alloc_cluster(block_cache, Some(cluster), true)?; + first_dir_block_num = self.cluster_to_block(c); + Some(c) + } + _ => None, + }; } else { current_cluster = None; } @@ -369,23 +457,23 @@ impl FatVolume { _ => Some(dir_cluster), }; let mut first_dir_block_num = self.cluster_to_block(dir_cluster); - let mut blocks = [Block::new()]; let dir_size = BlockCount(u32::from(self.blocks_per_cluster)); // Walk the cluster chain until we run out of clusters while let Some(cluster) = current_cluster { // Loop through the blocks in the cluster - for block in first_dir_block_num.range(dir_size) { + for block_idx in first_dir_block_num.range(dir_size) { // Read a block of directory entries - block_device - .read(&mut blocks, block, "read_dir") + trace!("Reading directory"); + let block = block_cache + .read_mut(block_idx) .map_err(Error::DeviceError)?; // Are any entries in the block we just loaded blank? If so // we can use them. - for entry in 0..Block::LEN / OnDiskDirEntry::LEN { - let start = entry * OnDiskDirEntry::LEN; - let end = (entry + 1) * OnDiskDirEntry::LEN; - let dir_entry = OnDiskDirEntry::new(&blocks[0][start..end]); + for (i, dir_entry_bytes) in + block.chunks_exact_mut(OnDiskDirEntry::LEN).enumerate() + { + let dir_entry = OnDiskDirEntry::new(dir_entry_bytes); // 0x00 or 0xE5 represents a free entry if !dir_entry.is_valid() { let ctime = time_source.get_timestamp(); @@ -394,34 +482,31 @@ impl FatVolume { attributes, ClusterId(0), ctime, - block, - start as u32, + block_idx, + (i * OnDiskDirEntry::LEN) as u32, ); - blocks[0][start..start + 32] + dir_entry_bytes .copy_from_slice(&entry.serialize(FatType::Fat32)[..]); - block_device - .write(&blocks, block) - .map_err(Error::DeviceError)?; + trace!("Updating directory"); + block_cache.write_back()?; return Ok(entry); } } } // Well none of the blocks in that cluster had any space in // them, let's fetch another one. - let mut block_cache = BlockCache::empty(); - current_cluster = - match self.next_cluster(block_device, cluster, &mut block_cache) { - Ok(n) => { - first_dir_block_num = self.cluster_to_block(n); - Some(n) - } - Err(Error::EndOfFile) => { - let c = self.alloc_cluster(block_device, Some(cluster), true)?; - first_dir_block_num = self.cluster_to_block(c); - Some(c) - } - _ => None, - }; + current_cluster = match self.next_cluster(block_cache, cluster) { + Ok(n) => { + first_dir_block_num = self.cluster_to_block(n); + Some(n) + } + Err(Error::EndOfFile) => { + let c = self.alloc_cluster(block_cache, Some(cluster), true)?; + first_dir_block_num = self.cluster_to_block(c); + Some(c) + } + _ => None, + }; } // We ran out of clusters in the chain, and apparently we weren't // able to make the chain longer, so the disk must be full. @@ -434,8 +519,8 @@ impl FatVolume { /// Useful for performing directory listings. pub(crate) fn iterate_dir( &self, - block_device: &D, - dir: &DirectoryInfo, + block_cache: &mut BlockCache, + dir_info: &DirectoryInfo, func: F, ) -> Result<(), Error> where @@ -444,19 +529,19 @@ impl FatVolume { { match &self.fat_specific_info { FatSpecificInfo::Fat16(fat16_info) => { - self.iterate_fat16(dir, fat16_info, block_device, func) + self.iterate_fat16(dir_info, fat16_info, block_cache, func) } FatSpecificInfo::Fat32(fat32_info) => { - self.iterate_fat32(dir, fat32_info, block_device, func) + self.iterate_fat32(dir_info, fat32_info, block_cache, func) } } } fn iterate_fat16( &self, - dir: &DirectoryInfo, + dir_info: &DirectoryInfo, fat16_info: &Fat16Info, - block_device: &D, + block_cache: &mut BlockCache, mut func: F, ) -> Result<(), Error> where @@ -467,12 +552,12 @@ impl FatVolume { // a specially reserved space on disk (see // `first_root_dir_block`). Other directories can have any size // as they are made of regular clusters. - let mut current_cluster = Some(dir.cluster); - let mut first_dir_block_num = match dir.cluster { + let mut current_cluster = Some(dir_info.cluster); + let mut first_dir_block_num = match dir_info.cluster { ClusterId::ROOT_DIR => self.lba_start + fat16_info.first_root_dir_block, - _ => self.cluster_to_block(dir.cluster), + _ => self.cluster_to_block(dir_info.cluster), }; - let dir_size = match dir.cluster { + let dir_size = match dir_info.cluster { ClusterId::ROOT_DIR => { let len_bytes = u32::from(fat16_info.root_entries_count) * OnDiskDirEntry::LEN_U32; BlockCount::from_bytes(len_bytes) @@ -480,27 +565,25 @@ impl FatVolume { _ => BlockCount(u32::from(self.blocks_per_cluster)), }; - let mut block_cache = BlockCache::empty(); while let Some(cluster) = current_cluster { for block_idx in first_dir_block_num.range(dir_size) { - let block = block_cache.read(block_device, block_idx, "read_dir")?; - for entry in 0..Block::LEN / OnDiskDirEntry::LEN { - let start = entry * OnDiskDirEntry::LEN; - let end = (entry + 1) * OnDiskDirEntry::LEN; - let dir_entry = OnDiskDirEntry::new(&block[start..end]); + trace!("Reading FAT"); + let block = block_cache.read(block_idx)?; + for (i, dir_entry_bytes) in block.chunks_exact(OnDiskDirEntry::LEN).enumerate() { + let dir_entry = OnDiskDirEntry::new(dir_entry_bytes); if dir_entry.is_end() { // Can quit early return Ok(()); } else if dir_entry.is_valid() && !dir_entry.is_lfn() { // Safe, since Block::LEN always fits on a u32 - let start = u32::try_from(start).unwrap(); + let start = (i * OnDiskDirEntry::LEN) as u32; let entry = dir_entry.get_entry(FatType::Fat16, block_idx, start); func(&entry); } } } if cluster != ClusterId::ROOT_DIR { - current_cluster = match self.next_cluster(block_device, cluster, &mut block_cache) { + current_cluster = match self.next_cluster(block_cache, cluster) { Ok(n) => { first_dir_block_num = self.cluster_to_block(n); Some(n) @@ -516,9 +599,9 @@ impl FatVolume { fn iterate_fat32( &self, - dir: &DirectoryInfo, + dir_info: &DirectoryInfo, fat32_info: &Fat32Info, - block_device: &D, + block_cache: &mut BlockCache, mut func: F, ) -> Result<(), Error> where @@ -527,26 +610,21 @@ impl FatVolume { { // All directories on FAT32 have a cluster chain but the root // dir starts in a specified cluster. - let mut current_cluster = match dir.cluster { + let mut current_cluster = match dir_info.cluster { ClusterId::ROOT_DIR => Some(fat32_info.first_root_dir_cluster), - _ => Some(dir.cluster), + _ => Some(dir_info.cluster), }; - let mut blocks = [Block::new()]; - let mut block_cache = BlockCache::empty(); let mut lfn_buffer = [[' '; 13]; 8]; let mut lfn_pointer = 0; while let Some(cluster) = current_cluster { - let block_idx = self.cluster_to_block(cluster); - for block in block_idx.range(BlockCount(u32::from(self.blocks_per_cluster))) { - block_device - .read(&mut blocks, block, "read_dir") - .map_err(Error::DeviceError)?; - for entry in 0..Block::LEN / OnDiskDirEntry::LEN { - let start = entry * OnDiskDirEntry::LEN; - let end = (entry + 1) * OnDiskDirEntry::LEN; - let dir_entry = OnDiskDirEntry::new(&blocks[0][start..end]); + let start_block_idx = self.cluster_to_block(cluster); + for block_idx in start_block_idx.range(BlockCount(u32::from(self.blocks_per_cluster))) { + trace!("Reading FAT"); + let block = block_cache.read(block_idx).map_err(Error::DeviceError)?; + for (i, dir_entry_bytes) in block.chunks_exact(OnDiskDirEntry::LEN).enumerate() { + let dir_entry = OnDiskDirEntry::new(dir_entry_bytes); if dir_entry.is_end() { // Can quit early return Ok(()); @@ -573,14 +651,14 @@ impl FatVolume { } } // Safe, since Block::LEN always fits on a u32 - let start = u32::try_from(start).unwrap(); + let start = (i * OnDiskDirEntry::LEN) as u32; let entry = dir_entry.get_entry(FatType::Fat32, block, start); func(&entry); } } } } - current_cluster = match self.next_cluster(block_device, cluster, &mut block_cache) { + current_cluster = match self.next_cluster(block_cache, cluster) { Ok(n) => Some(n), _ => None, }; @@ -591,8 +669,8 @@ impl FatVolume { /// Get an entry from the given directory pub(crate) fn find_directory_entry( &self, - block_device: &D, - dir: &DirectoryInfo, + block_cache: &mut BlockCache, + dir_info: &DirectoryInfo, match_name: &ShortFileName, ) -> Result> where @@ -604,12 +682,12 @@ impl FatVolume { // a specially reserved space on disk (see // `first_root_dir_block`). Other directories can have any size // as they are made of regular clusters. - let mut current_cluster = Some(dir.cluster); - let mut first_dir_block_num = match dir.cluster { + let mut current_cluster = Some(dir_info.cluster); + let mut first_dir_block_num = match dir_info.cluster { ClusterId::ROOT_DIR => self.lba_start + fat16_info.first_root_dir_block, - _ => self.cluster_to_block(dir.cluster), + _ => self.cluster_to_block(dir_info.cluster), }; - let dir_size = match dir.cluster { + let dir_size = match dir_info.cluster { ClusterId::ROOT_DIR => { let len_bytes = u32::from(fat16_info.root_entries_count) * OnDiskDirEntry::LEN_U32; @@ -618,11 +696,10 @@ impl FatVolume { _ => BlockCount(u32::from(self.blocks_per_cluster)), }; - let mut block_cache = BlockCache::empty(); while let Some(cluster) = current_cluster { for block in first_dir_block_num.range(dir_size) { match self.find_entry_in_block( - block_device, + block_cache, FatType::Fat16, match_name, block, @@ -632,14 +709,13 @@ impl FatVolume { } } if cluster != ClusterId::ROOT_DIR { - current_cluster = - match self.next_cluster(block_device, cluster, &mut block_cache) { - Ok(n) => { - first_dir_block_num = self.cluster_to_block(n); - Some(n) - } - _ => None, - }; + current_cluster = match self.next_cluster(block_cache, cluster) { + Ok(n) => { + first_dir_block_num = self.cluster_to_block(n); + Some(n) + } + _ => None, + }; } else { current_cluster = None; } @@ -647,16 +723,15 @@ impl FatVolume { Err(Error::NotFound) } FatSpecificInfo::Fat32(fat32_info) => { - let mut current_cluster = match dir.cluster { + let mut current_cluster = match dir_info.cluster { ClusterId::ROOT_DIR => Some(fat32_info.first_root_dir_cluster), - _ => Some(dir.cluster), + _ => Some(dir_info.cluster), }; - let mut block_cache = BlockCache::empty(); while let Some(cluster) = current_cluster { let block_idx = self.cluster_to_block(cluster); for block in block_idx.range(BlockCount(u32::from(self.blocks_per_cluster))) { match self.find_entry_in_block( - block_device, + block_cache, FatType::Fat32, match_name, block, @@ -665,11 +740,10 @@ impl FatVolume { x => return x, } } - current_cluster = - match self.next_cluster(block_device, cluster, &mut block_cache) { - Ok(n) => Some(n), - _ => None, - } + current_cluster = match self.next_cluster(block_cache, cluster) { + Ok(n) => Some(n), + _ => None, + } } Err(Error::NotFound) } @@ -679,30 +753,26 @@ impl FatVolume { /// Finds an entry in a given block of directory entries. fn find_entry_in_block( &self, - block_device: &D, + block_cache: &mut BlockCache, fat_type: FatType, match_name: &ShortFileName, - block: BlockIdx, + block_idx: BlockIdx, ) -> Result> where D: BlockDevice, { - let mut blocks = [Block::new()]; - block_device - .read(&mut blocks, block, "read_dir") - .map_err(Error::DeviceError)?; - for entry in 0..Block::LEN / OnDiskDirEntry::LEN { - let start = entry * OnDiskDirEntry::LEN; - let end = (entry + 1) * OnDiskDirEntry::LEN; - let dir_entry = OnDiskDirEntry::new(&blocks[0][start..end]); + trace!("Reading directory"); + let block = block_cache.read(block_idx).map_err(Error::DeviceError)?; + for (i, dir_entry_bytes) in block.chunks_exact(OnDiskDirEntry::LEN).enumerate() { + let dir_entry = OnDiskDirEntry::new(dir_entry_bytes); if dir_entry.is_end() { // Can quit early break; } else if dir_entry.matches(match_name) { // Found it - // Safe, since Block::LEN always fits on a u32 - let start = u32::try_from(start).unwrap(); - return Ok(dir_entry.get_entry(fat_type, block, start)); + // Block::LEN always fits on a u32 + let start = (i * OnDiskDirEntry::LEN) as u32; + return Ok(dir_entry.get_entry(fat_type, block_idx, start)); } } Err(Error::NotFound) @@ -711,8 +781,8 @@ impl FatVolume { /// Delete an entry from the given directory pub(crate) fn delete_directory_entry( &self, - block_device: &D, - dir: &DirectoryInfo, + block_cache: &mut BlockCache, + dir_info: &DirectoryInfo, match_name: &ShortFileName, ) -> Result<(), Error> where @@ -724,12 +794,12 @@ impl FatVolume { // a specially reserved space on disk (see // `first_root_dir_block`). Other directories can have any size // as they are made of regular clusters. - let mut current_cluster = Some(dir.cluster); - let mut first_dir_block_num = match dir.cluster { + let mut current_cluster = Some(dir_info.cluster); + let mut first_dir_block_num = match dir_info.cluster { ClusterId::ROOT_DIR => self.lba_start + fat16_info.first_root_dir_block, - _ => self.cluster_to_block(dir.cluster), + _ => self.cluster_to_block(dir_info.cluster), }; - let dir_size = match dir.cluster { + let dir_size = match dir_info.cluster { ClusterId::ROOT_DIR => { let len_bytes = u32::from(fat16_info.root_entries_count) * OnDiskDirEntry::LEN_U32; @@ -741,8 +811,8 @@ impl FatVolume { // Walk the directory while let Some(cluster) = current_cluster { // Scan the cluster / root dir a block at a time - for block in first_dir_block_num.range(dir_size) { - match self.delete_entry_in_block(block_device, match_name, block) { + for block_idx in first_dir_block_num.range(dir_size) { + match self.delete_entry_in_block(block_cache, match_name, block_idx) { Err(Error::NotFound) => { // Carry on } @@ -755,15 +825,13 @@ impl FatVolume { } // if it's not the root dir, find the next cluster so we can keep looking if cluster != ClusterId::ROOT_DIR { - let mut block_cache = BlockCache::empty(); - current_cluster = - match self.next_cluster(block_device, cluster, &mut block_cache) { - Ok(n) => { - first_dir_block_num = self.cluster_to_block(n); - Some(n) - } - _ => None, - }; + current_cluster = match self.next_cluster(block_cache, cluster) { + Ok(n) => { + first_dir_block_num = self.cluster_to_block(n); + Some(n) + } + _ => None, + }; } else { current_cluster = None; } @@ -773,16 +841,18 @@ impl FatVolume { FatSpecificInfo::Fat32(fat32_info) => { // Root directories on FAT32 start at a specified cluster, but // they can have any length. - let mut current_cluster = match dir.cluster { + let mut current_cluster = match dir_info.cluster { ClusterId::ROOT_DIR => Some(fat32_info.first_root_dir_cluster), - _ => Some(dir.cluster), + _ => Some(dir_info.cluster), }; // Walk the directory while let Some(cluster) = current_cluster { // Scan the cluster a block at a time - let block_idx = self.cluster_to_block(cluster); - for block in block_idx.range(BlockCount(u32::from(self.blocks_per_cluster))) { - match self.delete_entry_in_block(block_device, match_name, block) { + let start_block_idx = self.cluster_to_block(cluster); + for block_idx in + start_block_idx.range(BlockCount(u32::from(self.blocks_per_cluster))) + { + match self.delete_entry_in_block(block_cache, match_name, block_idx) { Err(Error::NotFound) => { // Carry on continue; @@ -795,12 +865,10 @@ impl FatVolume { } } // Find the next cluster - let mut block_cache = BlockCache::empty(); - current_cluster = - match self.next_cluster(block_device, cluster, &mut block_cache) { - Ok(n) => Some(n), - _ => None, - } + current_cluster = match self.next_cluster(block_cache, cluster) { + Ok(n) => Some(n), + _ => None, + } } // Ok, give up } @@ -816,30 +884,28 @@ impl FatVolume { /// to a special value. fn delete_entry_in_block( &self, - block_device: &D, + block_cache: &mut BlockCache, match_name: &ShortFileName, - block: BlockIdx, + block_idx: BlockIdx, ) -> Result<(), Error> where D: BlockDevice, { - let mut blocks = [Block::new()]; - block_device - .read(&mut blocks, block, "read_dir") + trace!("Reading directory"); + let block = block_cache + .read_mut(block_idx) .map_err(Error::DeviceError)?; - for entry in 0..Block::LEN / OnDiskDirEntry::LEN { - let start = entry * OnDiskDirEntry::LEN; - let end = (entry + 1) * OnDiskDirEntry::LEN; - let dir_entry = OnDiskDirEntry::new(&blocks[0][start..end]); + for (i, dir_entry_bytes) in block.chunks_exact_mut(OnDiskDirEntry::LEN).enumerate() { + let dir_entry = OnDiskDirEntry::new(dir_entry_bytes); if dir_entry.is_end() { // Can quit early break; } else if dir_entry.matches(match_name) { - let mut blocks = blocks; - blocks[0].contents[start] = 0xE5; - return block_device - .write(&blocks, block) - .map_err(Error::DeviceError); + let start = i * OnDiskDirEntry::LEN; + // set first byte to the 'unused' marker + block[start] = 0xE5; + trace!("Updating directory"); + return block_cache.write_back().map_err(Error::DeviceError); } } Err(Error::NotFound) @@ -848,14 +914,13 @@ impl FatVolume { /// Finds the next free cluster after the start_cluster and before end_cluster pub(crate) fn find_next_free_cluster( &self, - block_device: &D, + block_cache: &mut BlockCache, start_cluster: ClusterId, end_cluster: ClusterId, ) -> Result> where D: BlockDevice, { - let mut blocks = [Block::new()]; let mut current_cluster = start_cluster; match &self.fat_specific_info { FatSpecificInfo::Fat16(_fat16_info) => { @@ -873,13 +938,12 @@ impl FatVolume { let mut this_fat_ent_offset = usize::try_from(fat_offset % Block::LEN_U32) .map_err(|_| Error::ConversionError)?; trace!("Reading block {:?}", this_fat_block_num); - block_device - .read(&mut blocks, this_fat_block_num, "next_cluster") + let block = block_cache + .read(this_fat_block_num) .map_err(Error::DeviceError)?; - while this_fat_ent_offset <= Block::LEN - 2 { let fat_entry = LittleEndian::read_u16( - &blocks[0][this_fat_ent_offset..=this_fat_ent_offset + 1], + &block[this_fat_ent_offset..=this_fat_ent_offset + 1], ); if fat_entry == 0 { return Ok(current_cluster); @@ -904,13 +968,12 @@ impl FatVolume { let mut this_fat_ent_offset = usize::try_from(fat_offset % Block::LEN_U32) .map_err(|_| Error::ConversionError)?; trace!("Reading block {:?}", this_fat_block_num); - block_device - .read(&mut blocks, this_fat_block_num, "next_cluster") + let block = block_cache + .read(this_fat_block_num) .map_err(Error::DeviceError)?; - while this_fat_ent_offset <= Block::LEN - 4 { let fat_entry = LittleEndian::read_u32( - &blocks[0][this_fat_ent_offset..=this_fat_ent_offset + 3], + &block[this_fat_ent_offset..=this_fat_ent_offset + 3], ) & 0x0FFF_FFFF; if fat_entry == 0 { return Ok(current_cluster); @@ -928,7 +991,7 @@ impl FatVolume { /// Tries to allocate a cluster pub(crate) fn alloc_cluster( &mut self, - block_device: &D, + block_cache: &mut BlockCache, prev_cluster: Option, zero: bool, ) -> Result> @@ -946,31 +1009,27 @@ impl FatVolume { start_cluster, end_cluster ); - let new_cluster = - match self.find_next_free_cluster(block_device, start_cluster, end_cluster) { - Ok(cluster) => cluster, - Err(_) if start_cluster.0 > RESERVED_ENTRIES => { - debug!( - "Retrying, finding next free between {:?}..={:?}", - ClusterId(RESERVED_ENTRIES), - end_cluster - ); - self.find_next_free_cluster( - block_device, - ClusterId(RESERVED_ENTRIES), - end_cluster, - )? - } - Err(e) => return Err(e), - }; - self.update_fat(block_device, new_cluster, ClusterId::END_OF_FILE)?; + let new_cluster = match self.find_next_free_cluster(block_cache, start_cluster, end_cluster) + { + Ok(cluster) => cluster, + Err(_) if start_cluster.0 > RESERVED_ENTRIES => { + debug!( + "Retrying, finding next free between {:?}..={:?}", + ClusterId(RESERVED_ENTRIES), + end_cluster + ); + self.find_next_free_cluster(block_cache, ClusterId(RESERVED_ENTRIES), end_cluster)? + } + Err(e) => return Err(e), + }; + self.update_fat(block_cache, new_cluster, ClusterId::END_OF_FILE)?; if let Some(cluster) = prev_cluster { trace!( "Updating old cluster {:?} to {:?} in FAT", cluster, new_cluster ); - self.update_fat(block_device, cluster, new_cluster)?; + self.update_fat(block_cache, cluster, new_cluster)?; } trace!( "Finding next free between {:?}..={:?}", @@ -978,11 +1037,11 @@ impl FatVolume { end_cluster ); self.next_free_cluster = - match self.find_next_free_cluster(block_device, new_cluster, end_cluster) { + match self.find_next_free_cluster(block_cache, new_cluster, end_cluster) { Ok(cluster) => Some(cluster), Err(_) if new_cluster.0 > RESERVED_ENTRIES => { match self.find_next_free_cluster( - block_device, + block_cache, ClusterId(RESERVED_ENTRIES), end_cluster, ) { @@ -998,11 +1057,13 @@ impl FatVolume { }; if zero { let blocks = [Block::new()]; - let first_block = self.cluster_to_block(new_cluster); + let start_block_idx = self.cluster_to_block(new_cluster); let num_blocks = BlockCount(u32::from(self.blocks_per_cluster)); - for block in first_block.range(num_blocks) { - block_device - .write(&blocks, block) + for block_idx in start_block_idx.range(num_blocks) { + trace!("Zeroing cluster"); + block_cache + .block_device() + .write(&blocks, block_idx) .map_err(Error::DeviceError)?; } } @@ -1013,7 +1074,7 @@ impl FatVolume { /// Marks the input cluster as an EOF and all the subsequent clusters in the chain as free pub(crate) fn truncate_cluster_chain( &mut self, - block_device: &D, + block_cache: &mut BlockCache, cluster: ClusterId, ) -> Result<(), Error> where @@ -1024,8 +1085,7 @@ impl FatVolume { return Ok(()); } let mut next = { - let mut block_cache = BlockCache::empty(); - match self.next_cluster(block_device, cluster, &mut block_cache) { + match self.next_cluster(block_cache, cluster) { Ok(n) => n, Err(Error::EndOfFile) => return Ok(()), Err(e) => return Err(e), @@ -1038,16 +1098,15 @@ impl FatVolume { } else { self.next_free_cluster = Some(next); } - self.update_fat(block_device, cluster, ClusterId::END_OF_FILE)?; + self.update_fat(block_cache, cluster, ClusterId::END_OF_FILE)?; loop { - let mut block_cache = BlockCache::empty(); - match self.next_cluster(block_device, next, &mut block_cache) { + match self.next_cluster(block_cache, next) { Ok(n) => { - self.update_fat(block_device, next, ClusterId::EMPTY)?; + self.update_fat(block_cache, next, ClusterId::EMPTY)?; next = n; } Err(Error::EndOfFile) => { - self.update_fat(block_device, next, ClusterId::EMPTY)?; + self.update_fat(block_cache, next, ClusterId::EMPTY)?; break; } Err(e) => return Err(e), @@ -1062,7 +1121,7 @@ impl FatVolume { /// Writes a Directory Entry to the disk pub(crate) fn write_entry_to_disk( &self, - block_device: &D, + block_cache: &mut BlockCache, entry: &DirEntry, ) -> Result<(), Error> where @@ -1072,18 +1131,16 @@ impl FatVolume { FatSpecificInfo::Fat16(_) => FatType::Fat16, FatSpecificInfo::Fat32(_) => FatType::Fat32, }; - let mut blocks = [Block::new()]; - block_device - .read(&mut blocks, entry.entry_block, "read") + trace!("Reading directory for update"); + let block = block_cache + .read_mut(entry.entry_block) .map_err(Error::DeviceError)?; - let block = &mut blocks[0]; let start = usize::try_from(entry.entry_offset).map_err(|_| Error::ConversionError)?; block[start..start + 32].copy_from_slice(&entry.serialize(fat_type)[..]); - block_device - .write(&blocks, entry.entry_block) - .map_err(Error::DeviceError)?; + trace!("Updating directory"); + block_cache.write_back().map_err(Error::DeviceError)?; Ok(()) } } @@ -1091,7 +1148,7 @@ impl FatVolume { /// Load the boot parameter block from the start of the given partition and /// determine if the partition contains a valid FAT16 or FAT32 file system. pub fn parse_volume( - block_device: &D, + block_cache: &mut BlockCache, lba_start: BlockIdx, num_blocks: BlockCount, ) -> Result> @@ -1099,11 +1156,8 @@ where D: BlockDevice, D::Error: core::fmt::Debug, { - let mut blocks = [Block::new()]; - block_device - .read(&mut blocks, lba_start, "read_bpb") - .map_err(Error::DeviceError)?; - let block = &blocks[0]; + trace!("Reading BPB"); + let block = block_cache.read(lba_start).map_err(Error::DeviceError)?; let bpb = Bpb::create_from_bytes(block).map_err(Error::FormatError)?; match bpb.fat_type { FatType::Fat16 => { @@ -1118,10 +1172,12 @@ where let first_root_dir_block = fat_start + BlockCount(u32::from(bpb.num_fats()) * bpb.fat_size()); let first_data_block = first_root_dir_block + BlockCount(root_dir_blocks); - let mut volume = FatVolume { + let volume = FatVolume { lba_start, num_blocks, - name: VolumeName { data: [0u8; 11] }, + name: VolumeName { + contents: bpb.volume_label(), + }, blocks_per_cluster: bpb.blocks_per_cluster(), first_data_block: (first_data_block), fat_start: BlockCount(u32::from(bpb.reserved_block_count())), @@ -1133,7 +1189,6 @@ where first_root_dir_block, }), }; - volume.name.data[..].copy_from_slice(bpb.volume_label()); Ok(VolumeType::Fat(volume)) } FatType::Fat32 => { @@ -1143,39 +1198,52 @@ where // Safe to unwrap since this is a Fat32 Type let info_location = bpb.fs_info_block().unwrap(); - let mut info_blocks = [Block::new()]; - block_device - .read( - &mut info_blocks, - lba_start + info_location, - "read_info_sector", - ) - .map_err(Error::DeviceError)?; - let info_block = &info_blocks[0]; - let info_sector = - InfoSector::create_from_bytes(info_block).map_err(Error::FormatError)?; - let mut volume = FatVolume { lba_start, num_blocks, - name: VolumeName { data: [0u8; 11] }, + name: VolumeName { + contents: bpb.volume_label(), + }, blocks_per_cluster: bpb.blocks_per_cluster(), first_data_block: BlockCount(first_data_block), fat_start: BlockCount(u32::from(bpb.reserved_block_count())), - free_clusters_count: info_sector.free_clusters_count(), - next_free_cluster: info_sector.next_free_cluster(), + free_clusters_count: None, + next_free_cluster: None, cluster_count: bpb.total_clusters(), fat_specific_info: FatSpecificInfo::Fat32(Fat32Info { info_location: lba_start + info_location, first_root_dir_cluster: ClusterId(bpb.first_root_dir_cluster()), }), }; - volume.name.data[..].copy_from_slice(bpb.volume_label()); + + // Now we don't need the BPB, update the volume with data from the info sector + trace!("Reading info block"); + let info_block = block_cache + .read(lba_start + info_location) + .map_err(Error::DeviceError)?; + let info_sector = + InfoSector::create_from_bytes(info_block).map_err(Error::FormatError)?; + volume.free_clusters_count = info_sector.free_clusters_count(); + volume.next_free_cluster = info_sector.next_free_cluster(); + Ok(VolumeType::Fat(volume)) } } } +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn volume_name() { + let sfn = VolumeName { + contents: *b"Hello \xA399 ", + }; + assert_eq!(sfn, VolumeName::create_from_str("Hello £99").unwrap()) + } +} + // **************************************************************************** // // End Of File diff --git a/src/filesystem/cluster.rs b/src/filesystem/cluster.rs index 34d8590..bcf6eb0 100644 --- a/src/filesystem/cluster.rs +++ b/src/filesystem/cluster.rs @@ -3,7 +3,7 @@ /// A cluster is a consecutive group of blocks. Each cluster has a a numeric ID. /// Some numeric IDs are reserved for special purposes. #[cfg_attr(feature = "defmt-log", derive(defmt::Format))] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)] +#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd)] pub struct ClusterId(pub(crate) u32); impl ClusterId { @@ -33,6 +33,34 @@ impl core::ops::AddAssign for ClusterId { } } +impl core::fmt::Debug for ClusterId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "ClusterId(")?; + match *self { + Self::INVALID => { + write!(f, "INVALID")?; + } + Self::BAD => { + write!(f, "BAD")?; + } + Self::EMPTY => { + write!(f, "EMPTY")?; + } + Self::ROOT_DIR => { + write!(f, "ROOT_DIR")?; + } + Self::END_OF_FILE => { + write!(f, "END_OF_FILE")?; + } + ClusterId(value) => { + write!(f, "{:#08x}", value)?; + } + } + write!(f, ")")?; + Ok(()) + } +} + // **************************************************************************** // // End Of File diff --git a/src/filesystem/directory.rs b/src/filesystem/directory.rs index b965995..5cfc5f0 100644 --- a/src/filesystem/directory.rs +++ b/src/filesystem/directory.rs @@ -1,8 +1,6 @@ -use core::convert::TryFrom; - use crate::blockdevice::BlockIdx; use crate::fat::{FatType, OnDiskDirEntry}; -use crate::filesystem::{Attributes, ClusterId, SearchId, ShortFileName, Timestamp}; +use crate::filesystem::{Attributes, ClusterId, Handle, ShortFileName, Timestamp}; use crate::{Error, RawVolume, VolumeManager}; use super::ToShortFileName; @@ -47,7 +45,7 @@ pub struct DirEntry { /// and there's a reason we did it this way. #[cfg_attr(feature = "defmt-log", derive(defmt::Format))] #[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct RawDirectory(pub(crate) SearchId); +pub struct RawDirectory(pub(crate) Handle); impl RawDirectory { /// Convert a raw directory into a droppable [`Directory`] @@ -59,7 +57,7 @@ impl RawDirectory { const MAX_VOLUMES: usize, >( self, - volume_mgr: &mut VolumeManager, + volume_mgr: &VolumeManager, ) -> Directory where D: crate::BlockDevice, @@ -89,7 +87,7 @@ pub struct Directory< T: crate::TimeSource, { raw_directory: RawDirectory, - volume_mgr: &'a mut VolumeManager, + volume_mgr: &'a VolumeManager, } impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> @@ -101,7 +99,7 @@ where /// Create a new `Directory` from a `RawDirectory` pub fn new( raw_directory: RawDirectory, - volume_mgr: &'a mut VolumeManager, + volume_mgr: &'a VolumeManager, ) -> Directory<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES> { Directory { raw_directory, @@ -113,7 +111,7 @@ where /// /// You can then read the directory entries with `iterate_dir` and `open_file_in_dir`. pub fn open_dir( - &mut self, + &self, name: N, ) -> Result, Error> where @@ -137,7 +135,7 @@ where } /// Look in a directory for a named file. - pub fn find_directory_entry(&mut self, name: N) -> Result> + pub fn find_directory_entry(&self, name: N) -> Result> where N: ToShortFileName, { @@ -146,7 +144,15 @@ where } /// Call a callback function for each directory entry in a directory. - pub fn iterate_dir(&mut self, func: F) -> Result<(), Error> + /// + ///
+ /// + /// Do not attempt to call any methods on the VolumeManager or any of its + /// handles from inside the callback. You will get a lock error because the + /// object is already locked in order to do the iteration. + /// + ///
+ pub fn iterate_dir(&self, func: F) -> Result<(), Error> where F: FnMut(&DirEntry), { @@ -155,7 +161,7 @@ where /// Open a file with the given full path. A file can only be opened once. pub fn open_file_in_dir( - &mut self, + &self, name: N, mode: crate::Mode, ) -> Result, crate::Error> @@ -169,7 +175,7 @@ where } /// Delete a closed file with the given filename, if it exists. - pub fn delete_file_in_dir(&mut self, name: N) -> Result<(), Error> + pub fn delete_file_in_dir(&self, name: N) -> Result<(), Error> where N: ToShortFileName, { @@ -177,7 +183,7 @@ where } /// Make a directory inside this directory - pub fn make_dir_in_dir(&mut self, name: N) -> Result<(), Error> + pub fn make_dir_in_dir(&self, name: N) -> Result<(), Error> where N: ToShortFileName, { @@ -240,10 +246,10 @@ where #[cfg_attr(feature = "defmt-log", derive(defmt::Format))] #[derive(Debug, Clone)] pub(crate) struct DirectoryInfo { - /// Unique ID for this directory. - pub(crate) directory_id: RawDirectory, - /// The unique ID for the volume this directory is on - pub(crate) volume_id: RawVolume, + /// The handle for this directory. + pub(crate) raw_directory: RawDirectory, + /// The handle for the volume this directory is on + pub(crate) raw_volume: RawVolume, /// The starting point of the directory listing. pub(crate) cluster: ClusterId, } @@ -262,16 +268,12 @@ impl DirEntry { [0u8; 2] } else { // Safe due to the AND operation - u16::try_from((cluster_number >> 16) & 0x0000_FFFF) - .unwrap() - .to_le_bytes() + (((cluster_number >> 16) & 0x0000_FFFF) as u16).to_le_bytes() }; data[20..22].copy_from_slice(&cluster_hi[..]); data[22..26].copy_from_slice(&self.mtime.serialize_to_fat()[..]); // Safe due to the AND operation - let cluster_lo = u16::try_from(cluster_number & 0x0000_FFFF) - .unwrap() - .to_le_bytes(); + let cluster_lo = ((cluster_number & 0x0000_FFFF) as u16).to_le_bytes(); data[26..28].copy_from_slice(&cluster_lo[..]); data[28..32].copy_from_slice(&self.size.to_le_bytes()[..]); data diff --git a/src/filesystem/filename.rs b/src/filesystem/filename.rs index 4cb763f..a8b34e4 100644 --- a/src/filesystem/filename.rs +++ b/src/filesystem/filename.rs @@ -1,5 +1,7 @@ //! Filename related types +use crate::fat::VolumeName; + /// Various filename related errors that can occur. #[cfg_attr(feature = "defmt-log", derive(defmt::Format))] #[derive(Debug, Clone)] @@ -40,17 +42,19 @@ impl ToShortFileName for &str { } } -/// An MS-DOS 8.3 filename. 7-bit ASCII only. All lower-case is converted to -/// upper-case by default. +/// An MS-DOS 8.3 filename. +/// +/// ISO-8859-1 encoding is assumed. All lower-case is converted to upper-case by +/// default. #[cfg_attr(feature = "defmt-log", derive(defmt::Format))] #[derive(PartialEq, Eq, Clone)] pub struct ShortFileName { - pub(crate) contents: [u8; 11], + pub(crate) contents: [u8; Self::TOTAL_LEN], } impl ShortFileName { - const FILENAME_BASE_MAX_LEN: usize = 8; - const FILENAME_MAX_LEN: usize = 11; + const BASE_LEN: usize = 8; + const TOTAL_LEN: usize = 11; /// Get a short file name containing "..", which means "parent directory". pub const fn parent_dir() -> Self { @@ -68,22 +72,24 @@ impl ShortFileName { /// Get base name (without extension) of the file. pub fn base_name(&self) -> &[u8] { - Self::bytes_before_space(&self.contents[..Self::FILENAME_BASE_MAX_LEN]) + Self::bytes_before_space(&self.contents[..Self::BASE_LEN]) } /// Get extension of the file (without base name). pub fn extension(&self) -> &[u8] { - Self::bytes_before_space(&self.contents[Self::FILENAME_BASE_MAX_LEN..]) + Self::bytes_before_space(&self.contents[Self::BASE_LEN..]) } fn bytes_before_space(bytes: &[u8]) -> &[u8] { - bytes.split(|b| *b == b' ').next().unwrap_or(&bytes[0..0]) + bytes.split(|b| *b == b' ').next().unwrap_or(&[]) } /// Create a new MS-DOS 8.3 space-padded file name as stored in the directory entry. + /// + /// The output uses ISO-8859-1 encoding. pub fn create_from_str(name: &str) -> Result { let mut sfn = ShortFileName { - contents: [b' '; Self::FILENAME_MAX_LEN], + contents: [b' '; Self::TOTAL_LEN], }; // Special case `..`, which means "parent directory". @@ -98,47 +104,52 @@ impl ShortFileName { let mut idx = 0; let mut seen_dot = false; - for ch in name.bytes() { + for ch in name.chars() { match ch { // Microsoft say these are the invalid characters - 0x00..=0x1F - | 0x20 - | 0x22 - | 0x2A - | 0x2B - | 0x2C - | 0x2F - | 0x3A - | 0x3B - | 0x3C - | 0x3D - | 0x3E - | 0x3F - | 0x5B - | 0x5C - | 0x5D - | 0x7C => { + '\u{0000}'..='\u{001F}' + | '"' + | '*' + | '+' + | ',' + | '/' + | ':' + | ';' + | '<' + | '=' + | '>' + | '?' + | '[' + | '\\' + | ']' + | ' ' + | '|' => { + return Err(FilenameError::InvalidCharacter); + } + x if x > '\u{00FF}' => { + // We only handle ISO-8859-1 which is Unicode Code Points + // \U+0000 to \U+00FF. This is above that. return Err(FilenameError::InvalidCharacter); } - // Denotes the start of the file extension - b'.' => { - if (1..=Self::FILENAME_BASE_MAX_LEN).contains(&idx) { - idx = Self::FILENAME_BASE_MAX_LEN; + '.' => { + // Denotes the start of the file extension + if (1..=Self::BASE_LEN).contains(&idx) { + idx = Self::BASE_LEN; seen_dot = true; } else { return Err(FilenameError::MisplacedPeriod); } } _ => { - let ch = ch.to_ascii_uppercase(); + let b = ch.to_ascii_uppercase() as u8; if seen_dot { - if (Self::FILENAME_BASE_MAX_LEN..Self::FILENAME_MAX_LEN).contains(&idx) { - sfn.contents[idx] = ch; + if (Self::BASE_LEN..Self::TOTAL_LEN).contains(&idx) { + sfn.contents[idx] = b; } else { return Err(FilenameError::NameTooLong); } - } else if idx < Self::FILENAME_BASE_MAX_LEN { - sfn.contents[idx] = ch; + } else if idx < Self::BASE_LEN { + sfn.contents[idx] = b; } else { return Err(FilenameError::NameTooLong); } @@ -152,65 +163,17 @@ impl ShortFileName { Ok(sfn) } - /// Create a new MS-DOS 8.3 space-padded file name as stored in the directory entry. - /// Use this for volume labels with mixed case. - pub fn create_from_str_mixed_case(name: &str) -> Result { - let mut sfn = ShortFileName { - contents: [b' '; Self::FILENAME_MAX_LEN], - }; - let mut idx = 0; - let mut seen_dot = false; - for ch in name.bytes() { - match ch { - // Microsoft say these are the invalid characters - 0x00..=0x1F - | 0x20 - | 0x22 - | 0x2A - | 0x2B - | 0x2C - | 0x2F - | 0x3A - | 0x3B - | 0x3C - | 0x3D - | 0x3E - | 0x3F - | 0x5B - | 0x5C - | 0x5D - | 0x7C => { - return Err(FilenameError::InvalidCharacter); - } - // Denotes the start of the file extension - b'.' => { - if (1..=Self::FILENAME_BASE_MAX_LEN).contains(&idx) { - idx = Self::FILENAME_BASE_MAX_LEN; - seen_dot = true; - } else { - return Err(FilenameError::MisplacedPeriod); - } - } - _ => { - if seen_dot { - if (Self::FILENAME_BASE_MAX_LEN..Self::FILENAME_MAX_LEN).contains(&idx) { - sfn.contents[idx] = ch; - } else { - return Err(FilenameError::NameTooLong); - } - } else if idx < Self::FILENAME_BASE_MAX_LEN { - sfn.contents[idx] = ch; - } else { - return Err(FilenameError::NameTooLong); - } - idx += 1; - } - } + /// Convert a Short File Name to a Volume Label. + /// + /// # Safety + /// + /// Volume Labels can contain things that Short File Names cannot, so only + /// do this conversion if you have the name of a directory entry with the + /// 'Volume Label' attribute. + pub unsafe fn to_volume_label(self) -> VolumeName { + VolumeName { + contents: self.contents, } - if idx == 0 { - return Err(FilenameError::FilenameEmpty); - } - Ok(sfn) } } @@ -219,10 +182,12 @@ impl core::fmt::Display for ShortFileName { let mut printed = 0; for (i, &c) in self.contents.iter().enumerate() { if c != b' ' { - if i == Self::FILENAME_BASE_MAX_LEN { + if i == Self::BASE_LEN { write!(f, ".")?; printed += 1; } + // converting a byte to a codepoint means you are assuming + // ISO-8859-1 encoding, because that's how Unicode was designed. write!(f, "{}", c as char)?; printed += 1; } diff --git a/src/filesystem/files.rs b/src/filesystem/files.rs index 1b13e09..870d85d 100644 --- a/src/filesystem/files.rs +++ b/src/filesystem/files.rs @@ -1,6 +1,6 @@ use super::TimeSource; use crate::{ - filesystem::{ClusterId, DirEntry, SearchId}, + filesystem::{ClusterId, DirEntry, Handle}, BlockDevice, Error, RawVolume, VolumeManager, }; use embedded_io::{ErrorType, Read, Seek, SeekFrom, Write}; @@ -23,13 +23,13 @@ use embedded_io::{ErrorType, Read, Seek, SeekFrom, Write}; /// reason we did it this way. #[cfg_attr(feature = "defmt-log", derive(defmt::Format))] #[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct RawFile(pub(crate) SearchId); +pub struct RawFile(pub(crate) Handle); impl RawFile { /// Convert a raw file into a droppable [`File`] pub fn to_file( self, - volume_mgr: &mut VolumeManager, + volume_mgr: &VolumeManager, ) -> File where D: crate::BlockDevice, @@ -53,7 +53,7 @@ where T: crate::TimeSource, { raw_file: RawFile, - volume_mgr: &'a mut VolumeManager, + volume_mgr: &'a VolumeManager, } impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> @@ -65,7 +65,7 @@ where /// Create a new `File` from a `RawFile` pub fn new( raw_file: RawFile, - volume_mgr: &'a mut VolumeManager, + volume_mgr: &'a VolumeManager, ) -> File<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES> { File { raw_file, @@ -76,12 +76,12 @@ where /// Read from the file /// /// Returns how many bytes were read, or an error. - pub fn read(&mut self, buffer: &mut [u8]) -> Result> { + pub fn read(&self, buffer: &mut [u8]) -> Result> { self.volume_mgr.read(self.raw_file, buffer) } /// Write to the file - pub fn write(&mut self, buffer: &[u8]) -> Result<(), crate::Error> { + pub fn write(&self, buffer: &[u8]) -> Result<(), crate::Error> { self.volume_mgr.write(self.raw_file, buffer) } @@ -93,18 +93,18 @@ where } /// Seek a file with an offset from the current position. - pub fn seek_from_current(&mut self, offset: i32) -> Result<(), crate::Error> { + pub fn seek_from_current(&self, offset: i32) -> Result<(), crate::Error> { self.volume_mgr .file_seek_from_current(self.raw_file, offset) } /// Seek a file with an offset from the start of the file. - pub fn seek_from_start(&mut self, offset: u32) -> Result<(), crate::Error> { + pub fn seek_from_start(&self, offset: u32) -> Result<(), crate::Error> { self.volume_mgr.file_seek_from_start(self.raw_file, offset) } /// Seek a file with an offset back from the end of the file. - pub fn seek_from_end(&mut self, offset: u32) -> Result<(), crate::Error> { + pub fn seek_from_end(&self, offset: u32) -> Result<(), crate::Error> { self.volume_mgr.file_seek_from_end(self.raw_file, offset) } @@ -130,7 +130,7 @@ where } /// Flush any written data by updating the directory entry. - pub fn flush(&mut self) -> Result<(), Error> { + pub fn flush(&self) -> Result<(), Error> { self.volume_mgr.flush_file(self.raw_file) } @@ -213,7 +213,7 @@ impl< } fn flush(&mut self) -> Result<(), Self::Error> { - self.flush() + Self::flush(self) } } @@ -283,10 +283,10 @@ pub enum Mode { #[cfg_attr(feature = "defmt-log", derive(defmt::Format))] #[derive(Debug, Clone)] pub(crate) struct FileInfo { - /// Unique ID for this file - pub(crate) file_id: RawFile, - /// The unique ID for the volume this directory is on - pub(crate) volume_id: RawVolume, + /// Handle for this file + pub(crate) raw_file: RawFile, + /// The handle for the volume this directory is on + pub(crate) raw_volume: RawVolume, /// The last cluster we accessed, and how many bytes that short-cuts us. /// /// This saves us walking from the very start of the FAT chain when we move diff --git a/src/filesystem/search_id.rs b/src/filesystem/handles.rs similarity index 56% rename from src/filesystem/search_id.rs rename to src/filesystem/handles.rs index 30c1018..dd37903 100644 --- a/src/filesystem/search_id.rs +++ b/src/filesystem/handles.rs @@ -1,11 +1,19 @@ +//! Contains the Handles and the HandleGenerator. + use core::num::Wrapping; -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt-log", derive(defmt::Format))] -/// Unique ID used to search for files and directories in the open Volume/File/Directory lists -pub struct SearchId(pub(crate) u32); +/// Unique ID used to identify things in the open Volume/File/Directory lists +pub struct Handle(pub(crate) u32); + +impl core::fmt::Debug for Handle { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#08x}", self.0) + } +} -/// A Search ID generator. +/// A Handle Generator. /// /// This object will always return a different ID. /// @@ -13,23 +21,23 @@ pub struct SearchId(pub(crate) u32); /// files, and if they do, they are unlikely to hold one file open and then /// open/close `2**32 - 1` others. #[derive(Debug)] -pub struct SearchIdGenerator { +pub struct HandleGenerator { next_id: Wrapping, } -impl SearchIdGenerator { - /// Create a new generator of Search IDs. +impl HandleGenerator { + /// Create a new generator of Handles. pub const fn new(offset: u32) -> Self { Self { next_id: Wrapping(offset), } } - /// Generate a new, unique [`SearchId`]. - pub fn get(&mut self) -> SearchId { + /// Generate a new, unique [`Handle`]. + pub fn generate(&mut self) -> Handle { let id = self.next_id; self.next_id += 1; - SearchId(id.0) + Handle(id.0) } } diff --git a/src/filesystem/mod.rs b/src/filesystem/mod.rs index 03baa67..92c94a1 100644 --- a/src/filesystem/mod.rs +++ b/src/filesystem/mod.rs @@ -4,14 +4,14 @@ //! most (if not all) supported filesystems. /// Maximum file size supported by this library -pub const MAX_FILE_SIZE: u32 = core::u32::MAX; +pub const MAX_FILE_SIZE: u32 = u32::MAX; mod attributes; mod cluster; mod directory; mod filename; mod files; -mod search_id; +mod handles; mod timestamp; pub use self::attributes::Attributes; @@ -19,7 +19,7 @@ pub use self::cluster::ClusterId; pub use self::directory::{DirEntry, Directory, RawDirectory}; pub use self::filename::{FilenameError, ShortFileName, ToShortFileName}; pub use self::files::{File, FileError, Mode, RawFile}; -pub use self::search_id::{SearchId, SearchIdGenerator}; +pub use self::handles::{Handle, HandleGenerator}; pub use self::timestamp::{TimeSource, Timestamp}; pub(crate) use self::directory::DirectoryInfo; diff --git a/src/lib.rs b/src/lib.rs index f510571..bdd5630 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,10 +27,10 @@ //! { //! let sdcard = SdCard::new(spi, delay); //! println!("Card size is {} bytes", sdcard.num_bytes()?); -//! let mut volume_mgr = VolumeManager::new(sdcard, ts); -//! let mut volume0 = volume_mgr.open_volume(VolumeIdx(0))?; +//! let volume_mgr = VolumeManager::new(sdcard, ts); +//! let volume0 = volume_mgr.open_volume(VolumeIdx(0))?; //! println!("Volume 0: {:?}", volume0); -//! let mut root_dir = volume0.open_root_dir()?; +//! let root_dir = volume0.open_root_dir()?; //! let mut my_file = root_dir.open_file_in_dir("MY_FILE.TXT", Mode::ReadOnly)?; //! while !my_file.is_eof() { //! let mut buffer = [0u8; 32]; @@ -47,8 +47,8 @@ //! //! * `log`: Enabled by default. Generates log messages using the `log` crate. //! * `defmt-log`: By turning off the default features and enabling the -//! `defmt-log` feature you can configure this crate to log messages over defmt -//! instead. +//! `defmt-log` feature you can configure this crate to log messages over defmt +//! instead. //! //! You cannot enable both the `log` feature and the `defmt-log` feature. @@ -75,13 +75,13 @@ pub mod sdcard; use core::fmt::Debug; use embedded_io::ErrorKind; -use filesystem::SearchId; +use filesystem::Handle; #[doc(inline)] -pub use crate::blockdevice::{Block, BlockCount, BlockDevice, BlockIdx}; +pub use crate::blockdevice::{Block, BlockCache, BlockCount, BlockDevice, BlockIdx}; #[doc(inline)] -pub use crate::fat::FatVolume; +pub use crate::fat::{FatVolume, VolumeName}; #[doc(inline)] pub use crate::filesystem::{ @@ -202,6 +202,11 @@ where DiskFull, /// A directory with that name already exists DirAlreadyExists, + /// The filesystem tried to gain a lock whilst already locked. + /// + /// This is either a bug in the filesystem, or you tried to access the + /// filesystem API from inside a directory iterator (that isn't allowed). + LockError, } impl embedded_io::Error for Error { @@ -216,7 +221,8 @@ impl embedded_io::Error for Error { | Error::EndOfFile | Error::DiskFull | Error::NotEnoughSpace - | Error::AllocationError => ErrorKind::Other, + | Error::AllocationError + | Error::LockError => ErrorKind::Other, Error::NoSuchVolume | Error::FilenameError(_) | Error::BadHandle @@ -247,10 +253,19 @@ where } } -/// A partition with a filesystem within it. +/// A handle to a volume. +/// +/// A volume is a partition with a filesystem within it. +/// +/// Do NOT drop this object! It doesn't hold a reference to the Volume Manager +/// it was created from and the VolumeManager will think you still have the +/// volume open if you just drop it, and it won't let you open the file again. +/// +/// Instead you must pass it to [`crate::VolumeManager::close_volume`] to close +/// it cleanly. #[cfg_attr(feature = "defmt-log", derive(defmt::Format))] #[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct RawVolume(SearchId); +pub struct RawVolume(Handle); impl RawVolume { /// Convert a raw volume into a droppable [`Volume`] @@ -262,7 +277,7 @@ impl RawVolume { const MAX_VOLUMES: usize, >( self, - volume_mgr: &mut VolumeManager, + volume_mgr: &VolumeManager, ) -> Volume where D: crate::BlockDevice, @@ -272,7 +287,7 @@ impl RawVolume { } } -/// An open volume on disk, which closes on drop. +/// A handle for an open volume on disk, which closes on drop. /// /// In contrast to a `RawVolume`, a `Volume` holds a mutable reference to its /// parent `VolumeManager`, which restricts which operations you can perform. @@ -286,7 +301,7 @@ where T: crate::TimeSource, { raw_volume: RawVolume, - volume_mgr: &'a mut VolumeManager, + volume_mgr: &'a VolumeManager, } impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> @@ -298,7 +313,7 @@ where /// Create a new `Volume` from a `RawVolume` pub fn new( raw_volume: RawVolume, - volume_mgr: &'a mut VolumeManager, + volume_mgr: &'a VolumeManager, ) -> Volume<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES> { Volume { raw_volume, @@ -311,7 +326,7 @@ where /// You can then read the directory entries with `iterate_dir`, or you can /// use `open_file_in_dir`. pub fn open_root_dir( - &mut self, + &self, ) -> Result, Error> { let d = self.volume_mgr.open_root_dir(self.raw_volume)?; Ok(d.to_directory(self.volume_mgr)) @@ -373,9 +388,9 @@ where #[cfg_attr(feature = "defmt-log", derive(defmt::Format))] #[derive(Debug, PartialEq, Eq)] pub(crate) struct VolumeInfo { - /// Search ID for this volume. - volume_id: RawVolume, - /// TODO: some kind of index + /// Handle for this volume. + raw_volume: RawVolume, + /// Which volume (i.e. partition) we opened on the disk idx: VolumeIdx, /// What kind of volume this is volume_type: VolumeType, diff --git a/src/sdcard/mod.rs b/src/sdcard/mod.rs index 6593043..5c3c038 100644 --- a/src/sdcard/mod.rs +++ b/src/sdcard/mod.rs @@ -162,19 +162,9 @@ where /// Read one or more blocks, starting at the given block index. /// /// This will trigger card (re-)initialisation. - fn read( - &self, - blocks: &mut [Block], - start_block_idx: BlockIdx, - _reason: &str, - ) -> Result<(), Self::Error> { + fn read(&self, blocks: &mut [Block], start_block_idx: BlockIdx) -> Result<(), Self::Error> { let mut inner = self.inner.borrow_mut(); - debug!( - "Read {} blocks @ {} for {}", - blocks.len(), - start_block_idx.0, - _reason - ); + debug!("Read {} blocks @ {}", blocks.len(), start_block_idx.0,); inner.check_init()?; inner.read(blocks, start_block_idx) } diff --git a/src/volume_mgr.rs b/src/volume_mgr.rs index 98b7781..75140d0 100644 --- a/src/volume_mgr.rs +++ b/src/volume_mgr.rs @@ -2,21 +2,23 @@ //! //! The volume manager handles partitions and open files on a block device. -use byteorder::{ByteOrder, LittleEndian}; +use core::cell::RefCell; use core::convert::TryFrom; +use core::ops::DerefMut; -use crate::fat::{self, BlockCache, FatType, OnDiskDirEntry, RESERVED_ENTRIES}; +use byteorder::{ByteOrder, LittleEndian}; +use heapless::Vec; -use crate::filesystem::{ - Attributes, ClusterId, DirEntry, DirectoryInfo, FileInfo, Mode, RawDirectory, RawFile, - SearchIdGenerator, TimeSource, ToShortFileName, MAX_FILE_SIZE, -}; use crate::{ - debug, Block, BlockCount, BlockDevice, BlockIdx, Error, RawVolume, ShortFileName, Volume, - VolumeIdx, VolumeInfo, VolumeType, PARTITION_ID_FAT16, PARTITION_ID_FAT16_LBA, + debug, fat, + filesystem::{ + Attributes, ClusterId, DirEntry, DirectoryInfo, FileInfo, HandleGenerator, Mode, + RawDirectory, RawFile, TimeSource, ToShortFileName, MAX_FILE_SIZE, + }, + trace, Block, BlockCache, BlockCount, BlockDevice, BlockIdx, Error, RawVolume, ShortFileName, + Volume, VolumeIdx, VolumeInfo, VolumeType, PARTITION_ID_FAT16, PARTITION_ID_FAT16_LBA, PARTITION_ID_FAT32_CHS_LBA, PARTITION_ID_FAT32_LBA, }; -use heapless::Vec; /// Wraps a block device and gives access to the FAT-formatted volumes within /// it. @@ -35,12 +37,8 @@ pub struct VolumeManager< T: TimeSource, ::Error: core::fmt::Debug, { - pub(crate) block_device: D, - pub(crate) time_source: T, - id_generator: SearchIdGenerator, - open_volumes: Vec, - open_dirs: Vec, - open_files: Vec, + time_source: T, + data: RefCell>, } impl VolumeManager @@ -84,18 +82,25 @@ where ) -> VolumeManager { debug!("Creating new embedded-sdmmc::VolumeManager"); VolumeManager { - block_device, time_source, - id_generator: SearchIdGenerator::new(id_offset), - open_volumes: Vec::new(), - open_dirs: Vec::new(), - open_files: Vec::new(), + data: RefCell::new(VolumeManagerData { + block_cache: BlockCache::new(block_device), + id_generator: HandleGenerator::new(id_offset), + open_volumes: Vec::new(), + open_dirs: Vec::new(), + open_files: Vec::new(), + }), } } /// Temporarily get access to the underlying block device. - pub fn device(&mut self) -> &mut D { - &mut self.block_device + pub fn device(&self, f: F) -> T + where + F: FnOnce(&mut D) -> T, + { + let mut data = self.data.borrow_mut(); + let result = f(data.block_cache.block_device()); + result } /// Get a volume (or partition) based on entries in the Master Boot Record. @@ -103,7 +108,7 @@ where /// We do not support GUID Partition Table disks. Nor do we support any /// concept of drive letters - that is for a higher layer to handle. pub fn open_volume( - &mut self, + &self, volume_idx: VolumeIdx, ) -> Result, Error> { let v = self.open_raw_volume(volume_idx)?; @@ -117,7 +122,7 @@ where /// /// This function gives you a `RawVolume` and you must close the volume by /// calling `VolumeManager::close_volume`. - pub fn open_raw_volume(&mut self, volume_idx: VolumeIdx) -> Result> { + pub fn open_raw_volume(&self, volume_idx: VolumeIdx) -> Result> { const PARTITION1_START: usize = 446; const PARTITION2_START: usize = PARTITION1_START + PARTITION_INFO_LENGTH; const PARTITION3_START: usize = PARTITION2_START + PARTITION_INFO_LENGTH; @@ -130,22 +135,24 @@ where const PARTITION_INFO_LBA_START_INDEX: usize = 8; const PARTITION_INFO_NUM_BLOCKS_INDEX: usize = 12; - if self.open_volumes.is_full() { + let mut data = self.data.try_borrow_mut().map_err(|_| Error::LockError)?; + + if data.open_volumes.is_full() { return Err(Error::TooManyOpenVolumes); } - for v in self.open_volumes.iter() { + for v in data.open_volumes.iter() { if v.idx == volume_idx { return Err(Error::VolumeAlreadyOpen); } } let (part_type, lba_start, num_blocks) = { - let mut blocks = [Block::new()]; - self.block_device - .read(&mut blocks, BlockIdx(0), "read_mbr") + trace!("Reading partition table"); + let block = data + .block_cache + .read(BlockIdx(0)) .map_err(Error::DeviceError)?; - let block = &blocks[0]; // We only support Master Boot Record (MBR) partitioned cards, not // GUID Partition Table (GPT) if LittleEndian::read_u16(&block[FOOTER_START..FOOTER_START + 2]) != FOOTER_VALUE { @@ -189,15 +196,15 @@ where | PARTITION_ID_FAT32_LBA | PARTITION_ID_FAT16_LBA | PARTITION_ID_FAT16 => { - let volume = fat::parse_volume(&self.block_device, lba_start, num_blocks)?; - let id = RawVolume(self.id_generator.get()); + let volume = fat::parse_volume(&mut data.block_cache, lba_start, num_blocks)?; + let id = RawVolume(data.id_generator.generate()); let info = VolumeInfo { - volume_id: id, + raw_volume: id, idx: volume_idx, volume_type: volume, }; // We already checked for space - self.open_volumes.push(info).unwrap(); + data.open_volumes.push(info).unwrap(); Ok(id) } _ => Err(Error::FormatError("Partition type not supported")), @@ -208,20 +215,25 @@ where /// /// You can then read the directory entries with `iterate_dir`, or you can /// use `open_file_in_dir`. - pub fn open_root_dir(&mut self, volume: RawVolume) -> Result> { + pub fn open_root_dir(&self, volume: RawVolume) -> Result> { + debug!("Opening root on {:?}", volume); + // Opening a root directory twice is OK + let mut data = self.data.try_borrow_mut().map_err(|_| Error::LockError)?; - let directory_id = RawDirectory(self.id_generator.get()); + let directory_id = RawDirectory(data.id_generator.generate()); let dir_info = DirectoryInfo { - volume_id: volume, + raw_volume: volume, cluster: ClusterId::ROOT_DIR, - directory_id, + raw_directory: directory_id, }; - self.open_dirs + data.open_dirs .push(dir_info) .map_err(|_| Error::TooManyOpenDirs)?; + debug!("Opened root on {:?}, got {:?}", volume, directory_id); + Ok(directory_id) } @@ -231,44 +243,51 @@ where /// /// Passing "." as the name results in opening the `parent_dir` a second time. pub fn open_dir( - &mut self, + &self, parent_dir: RawDirectory, name: N, ) -> Result> where N: ToShortFileName, { - if self.open_dirs.is_full() { + let mut data = self.data.try_borrow_mut().map_err(|_| Error::LockError)?; + let data = data.deref_mut(); + + if data.open_dirs.is_full() { return Err(Error::TooManyOpenDirs); } // Find dir by ID - let parent_dir_idx = self.get_dir_by_id(parent_dir)?; - let volume_idx = self.get_volume_by_id(self.open_dirs[parent_dir_idx].volume_id)?; + let parent_dir_idx = data.get_dir_by_id(parent_dir)?; + let volume_idx = data.get_volume_by_id(data.open_dirs[parent_dir_idx].raw_volume)?; let short_file_name = name.to_short_filename().map_err(Error::FilenameError)?; - let parent_dir_info = &self.open_dirs[parent_dir_idx]; // Open the directory + + // Should we short-cut? (root dir doesn't have ".") if short_file_name == ShortFileName::this_dir() { - // short-cut (root dir doesn't have ".") - let directory_id = RawDirectory(self.id_generator.get()); + let directory_id = RawDirectory(data.id_generator.generate()); let dir_info = DirectoryInfo { - directory_id, - volume_id: self.open_volumes[volume_idx].volume_id, - cluster: parent_dir_info.cluster, + raw_directory: directory_id, + raw_volume: data.open_volumes[volume_idx].raw_volume, + cluster: data.open_dirs[parent_dir_idx].cluster, }; - self.open_dirs + data.open_dirs .push(dir_info) .map_err(|_| Error::TooManyOpenDirs)?; return Ok(directory_id); } - let dir_entry = match &self.open_volumes[volume_idx].volume_type { - VolumeType::Fat(fat) => { - fat.find_directory_entry(&self.block_device, parent_dir_info, &short_file_name)? - } + // ok we'll actually look for the directory then + + let dir_entry = match &data.open_volumes[volume_idx].volume_type { + VolumeType::Fat(fat) => fat.find_directory_entry( + &mut data.block_cache, + &data.open_dirs[parent_dir_idx], + &short_file_name, + )?, }; debug!("Found dir entry: {:?}", dir_entry); @@ -281,14 +300,14 @@ where // no cached state and so opening a directory twice is allowable. // Remember this open directory. - let directory_id = RawDirectory(self.id_generator.get()); + let directory_id = RawDirectory(data.id_generator.generate()); let dir_info = DirectoryInfo { - directory_id, - volume_id: self.open_volumes[volume_idx].volume_id, + raw_directory: directory_id, + raw_volume: data.open_volumes[volume_idx].raw_volume, cluster: dir_entry.cluster, }; - self.open_dirs + data.open_dirs .push(dir_info) .map_err(|_| Error::TooManyOpenDirs)?; @@ -297,10 +316,13 @@ where /// Close a directory. You cannot perform operations on an open directory /// and so must close it if you want to do something with it. - pub fn close_dir(&mut self, directory: RawDirectory) -> Result<(), Error> { - for (idx, info) in self.open_dirs.iter().enumerate() { - if directory == info.directory_id { - self.open_dirs.swap_remove(idx); + pub fn close_dir(&self, directory: RawDirectory) -> Result<(), Error> { + debug!("Closing {:?}", directory); + let mut data = self.data.try_borrow_mut().map_err(|_| Error::LockError)?; + + for (idx, info) in data.open_dirs.iter().enumerate() { + if directory == info.raw_directory { + data.open_dirs.swap_remove(idx); return Ok(()); } } @@ -310,159 +332,82 @@ where /// Close a volume /// /// You can't close it if there are any files or directories open on it. - pub fn close_volume(&mut self, volume: RawVolume) -> Result<(), Error> { - for f in self.open_files.iter() { - if f.volume_id == volume { + pub fn close_volume(&self, volume: RawVolume) -> Result<(), Error> { + let mut data = self.data.try_borrow_mut().map_err(|_| Error::LockError)?; + + for f in data.open_files.iter() { + if f.raw_volume == volume { return Err(Error::VolumeStillInUse); } } - for d in self.open_dirs.iter() { - if d.volume_id == volume { + for d in data.open_dirs.iter() { + if d.raw_volume == volume { return Err(Error::VolumeStillInUse); } } - let volume_idx = self.get_volume_by_id(volume)?; - self.open_volumes.swap_remove(volume_idx); + let volume_idx = data.get_volume_by_id(volume)?; + + data.open_volumes.swap_remove(volume_idx); Ok(()) } /// Look in a directory for a named file. pub fn find_directory_entry( - &mut self, + &self, directory: RawDirectory, name: N, ) -> Result> where N: ToShortFileName, { - let directory_idx = self.get_dir_by_id(directory)?; - let volume_idx = self.get_volume_by_id(self.open_dirs[directory_idx].volume_id)?; - match &self.open_volumes[volume_idx].volume_type { + let mut data = self.data.try_borrow_mut().map_err(|_| Error::LockError)?; + let data = data.deref_mut(); + + let directory_idx = data.get_dir_by_id(directory)?; + let volume_idx = data.get_volume_by_id(data.open_dirs[directory_idx].raw_volume)?; + match &data.open_volumes[volume_idx].volume_type { VolumeType::Fat(fat) => { let sfn = name.to_short_filename().map_err(Error::FilenameError)?; - fat.find_directory_entry(&self.block_device, &self.open_dirs[directory_idx], &sfn) + fat.find_directory_entry( + &mut data.block_cache, + &data.open_dirs[directory_idx], + &sfn, + ) } } } /// Call a callback function for each directory entry in a directory. - pub fn iterate_dir( - &mut self, - directory: RawDirectory, - func: F, - ) -> Result<(), Error> + /// + ///
+ /// + /// Do not attempt to call any methods on the VolumeManager or any of its + /// handles from inside the callback. You will get a lock error because the + /// object is already locked in order to do the iteration. + /// + ///
+ pub fn iterate_dir(&self, directory: RawDirectory, func: F) -> Result<(), Error> where F: FnMut(&DirEntry), { - let directory_idx = self.get_dir_by_id(directory)?; - let volume_idx = self.get_volume_by_id(self.open_dirs[directory_idx].volume_id)?; - match &self.open_volumes[volume_idx].volume_type { - VolumeType::Fat(fat) => { - fat.iterate_dir(&self.block_device, &self.open_dirs[directory_idx], func) - } - } - } - - /// Open a file from a DirEntry. This is obtained by calling iterate_dir. - /// - /// # Safety - /// - /// The DirEntry must be a valid DirEntry read from disk, and not just - /// random numbers. - unsafe fn open_dir_entry( - &mut self, - volume: RawVolume, - dir_entry: DirEntry, - mode: Mode, - ) -> Result> { - // This check is load-bearing - we do an unchecked push later. - if self.open_files.is_full() { - return Err(Error::TooManyOpenFiles); - } + let mut data = self.data.try_borrow_mut().map_err(|_| Error::LockError)?; + let data = data.deref_mut(); - if dir_entry.attributes.is_read_only() && mode != Mode::ReadOnly { - return Err(Error::ReadOnly); - } - - if dir_entry.attributes.is_directory() { - return Err(Error::OpenedDirAsFile); - } - - // Check it's not already open - if self.file_is_open(volume, &dir_entry) { - return Err(Error::FileAlreadyOpen); - } - - let mode = solve_mode_variant(mode, true); - let file_id = RawFile(self.id_generator.get()); - - let file = match mode { - Mode::ReadOnly => FileInfo { - file_id, - volume_id: volume, - current_cluster: (0, dir_entry.cluster), - current_offset: 0, - mode, - entry: dir_entry, - dirty: false, - }, - Mode::ReadWriteAppend => { - let mut file = FileInfo { - file_id, - volume_id: volume, - current_cluster: (0, dir_entry.cluster), - current_offset: 0, - mode, - entry: dir_entry, - dirty: false, - }; - // seek_from_end with 0 can't fail - file.seek_from_end(0).ok(); - file - } - Mode::ReadWriteTruncate => { - let mut file = FileInfo { - file_id, - volume_id: volume, - current_cluster: (0, dir_entry.cluster), - current_offset: 0, - mode, - entry: dir_entry, - dirty: false, - }; - let volume_idx = self.get_volume_by_id(volume)?; - match &mut self.open_volumes[volume_idx].volume_type { - VolumeType::Fat(fat) => { - fat.truncate_cluster_chain(&self.block_device, file.entry.cluster)? - } - }; - file.update_length(0); - match &self.open_volumes[volume_idx].volume_type { - VolumeType::Fat(fat) => { - file.entry.mtime = self.time_source.get_timestamp(); - fat.write_entry_to_disk(&self.block_device, &file.entry)?; - } - }; - - file + let directory_idx = data.get_dir_by_id(directory)?; + let volume_idx = data.get_volume_by_id(data.open_dirs[directory_idx].raw_volume)?; + match &data.open_volumes[volume_idx].volume_type { + VolumeType::Fat(fat) => { + fat.iterate_dir(&mut data.block_cache, &data.open_dirs[directory_idx], func) } - _ => return Err(Error::Unsupported), - }; - - // Remember this open file - can't be full as we checked already - unsafe { - self.open_files.push_unchecked(file); } - - Ok(file_id) } /// Open a file with the given full path. A file can only be opened once. pub fn open_file_in_dir( - &mut self, + &self, directory: RawDirectory, name: N, mode: Mode, @@ -470,22 +415,26 @@ where where N: ToShortFileName, { + let mut data = self.data.try_borrow_mut().map_err(|_| Error::LockError)?; + let data = data.deref_mut(); + // This check is load-bearing - we do an unchecked push later. - if self.open_files.is_full() { + if data.open_files.is_full() { return Err(Error::TooManyOpenFiles); } - let directory_idx = self.get_dir_by_id(directory)?; - let directory_info = &self.open_dirs[directory_idx]; - let volume_id = self.open_dirs[directory_idx].volume_id; - let volume_idx = self.get_volume_by_id(volume_id)?; - let volume_info = &self.open_volumes[volume_idx]; + let directory_idx = data.get_dir_by_id(directory)?; + let volume_id = data.open_dirs[directory_idx].raw_volume; + let volume_idx = data.get_volume_by_id(volume_id)?; + let volume_info = &data.open_volumes[volume_idx]; let sfn = name.to_short_filename().map_err(Error::FilenameError)?; let dir_entry = match &volume_info.volume_type { - VolumeType::Fat(fat) => { - fat.find_directory_entry(&self.block_device, directory_info, &sfn) - } + VolumeType::Fat(fat) => fat.find_directory_entry( + &mut data.block_cache, + &data.open_dirs[directory_idx], + &sfn, + ), }; let dir_entry = match dir_entry { @@ -510,7 +459,7 @@ where // Check if it's open already if let Some(dir_entry) = &dir_entry { - if self.file_is_open(volume_info.volume_id, dir_entry) { + if data.file_is_open(volume_info.raw_volume, dir_entry) { return Err(Error::FileAlreadyOpen); } } @@ -522,23 +471,24 @@ where if dir_entry.is_some() { return Err(Error::FileAlreadyExists); } + let cluster = data.open_dirs[directory_idx].cluster; let att = Attributes::create_from_fat(0); - let volume_idx = self.get_volume_by_id(volume_id)?; - let entry = match &mut self.open_volumes[volume_idx].volume_type { + let volume_idx = data.get_volume_by_id(volume_id)?; + let entry = match &mut data.open_volumes[volume_idx].volume_type { VolumeType::Fat(fat) => fat.write_new_directory_entry( - &self.block_device, + &mut data.block_cache, &self.time_source, - directory_info.cluster, + cluster, sfn, att, )?, }; - let file_id = RawFile(self.id_generator.get()); + let file_id = RawFile(data.id_generator.generate()); let file = FileInfo { - file_id, - volume_id, + raw_file: file_id, + raw_volume: volume_id, current_cluster: (0, entry.cluster), current_offset: 0, mode, @@ -548,7 +498,7 @@ where // Remember this open file - can't be full as we checked already unsafe { - self.open_files.push_unchecked(file); + data.open_files.push_unchecked(file); } Ok(file_id) @@ -556,95 +506,205 @@ where _ => { // Safe to unwrap, since we actually have an entry if we got here let dir_entry = dir_entry.unwrap(); - // Safety: We read this dir entry off disk and didn't change it - unsafe { self.open_dir_entry(volume_id, dir_entry, mode) } + + if dir_entry.attributes.is_read_only() && mode != Mode::ReadOnly { + return Err(Error::ReadOnly); + } + + if dir_entry.attributes.is_directory() { + return Err(Error::OpenedDirAsFile); + } + + // Check it's not already open + if data.file_is_open(volume_id, &dir_entry) { + return Err(Error::FileAlreadyOpen); + } + + let mode = solve_mode_variant(mode, true); + let raw_file = RawFile(data.id_generator.generate()); + + let file = match mode { + Mode::ReadOnly => FileInfo { + raw_file, + raw_volume: volume_id, + current_cluster: (0, dir_entry.cluster), + current_offset: 0, + mode, + entry: dir_entry, + dirty: false, + }, + Mode::ReadWriteAppend => { + let mut file = FileInfo { + raw_file, + raw_volume: volume_id, + current_cluster: (0, dir_entry.cluster), + current_offset: 0, + mode, + entry: dir_entry, + dirty: false, + }; + // seek_from_end with 0 can't fail + file.seek_from_end(0).ok(); + file + } + Mode::ReadWriteTruncate => { + let mut file = FileInfo { + raw_file, + raw_volume: volume_id, + current_cluster: (0, dir_entry.cluster), + current_offset: 0, + mode, + entry: dir_entry, + dirty: false, + }; + match &mut data.open_volumes[volume_idx].volume_type { + VolumeType::Fat(fat) => fat.truncate_cluster_chain( + &mut data.block_cache, + file.entry.cluster, + )?, + }; + file.update_length(0); + match &data.open_volumes[volume_idx].volume_type { + VolumeType::Fat(fat) => { + file.entry.mtime = self.time_source.get_timestamp(); + fat.write_entry_to_disk(&mut data.block_cache, &file.entry)?; + } + }; + + file + } + _ => return Err(Error::Unsupported), + }; + + // Remember this open file - can't be full as we checked already + unsafe { + data.open_files.push_unchecked(file); + } + + Ok(raw_file) } } } /// Delete a closed file with the given filename, if it exists. pub fn delete_file_in_dir( - &mut self, + &self, directory: RawDirectory, name: N, ) -> Result<(), Error> where N: ToShortFileName, { - let dir_idx = self.get_dir_by_id(directory)?; - let dir_info = &self.open_dirs[dir_idx]; - let volume_idx = self.get_volume_by_id(dir_info.volume_id)?; + let mut data = self.data.try_borrow_mut().map_err(|_| Error::LockError)?; + let data = data.deref_mut(); + + let dir_idx = data.get_dir_by_id(directory)?; + let dir_info = &data.open_dirs[dir_idx]; + let volume_idx = data.get_volume_by_id(dir_info.raw_volume)?; let sfn = name.to_short_filename().map_err(Error::FilenameError)?; - let dir_entry = match &self.open_volumes[volume_idx].volume_type { - VolumeType::Fat(fat) => fat.find_directory_entry(&self.block_device, dir_info, &sfn), + let dir_entry = match &data.open_volumes[volume_idx].volume_type { + VolumeType::Fat(fat) => fat.find_directory_entry(&mut data.block_cache, dir_info, &sfn), }?; if dir_entry.attributes.is_directory() { return Err(Error::DeleteDirAsFile); } - if self.file_is_open(dir_info.volume_id, &dir_entry) { + if data.file_is_open(dir_info.raw_volume, &dir_entry) { return Err(Error::FileAlreadyOpen); } - let volume_idx = self.get_volume_by_id(dir_info.volume_id)?; - match &self.open_volumes[volume_idx].volume_type { + let volume_idx = data.get_volume_by_id(dir_info.raw_volume)?; + match &data.open_volumes[volume_idx].volume_type { VolumeType::Fat(fat) => { - fat.delete_directory_entry(&self.block_device, dir_info, &sfn)? + fat.delete_directory_entry(&mut data.block_cache, dir_info, &sfn)? } } Ok(()) } - /// Check if a file is open + /// Get the volume label /// - /// Returns `true` if it's open, `false`, otherwise. - fn file_is_open(&self, volume: RawVolume, dir_entry: &DirEntry) -> bool { - for f in self.open_files.iter() { - if f.volume_id == volume - && f.entry.entry_block == dir_entry.entry_block - && f.entry.entry_offset == dir_entry.entry_offset - { - return true; + /// Will look in the BPB for a volume label, and if nothing is found, will + /// search the root directory for a volume label. + pub fn get_root_volume_label( + &self, + raw_volume: RawVolume, + ) -> Result, Error> { + debug!("Reading volume label for {:?}", raw_volume); + // prefer the one in the BPB - it's easier to get + let data = self.data.try_borrow().map_err(|_| Error::LockError)?; + let volume_idx = data.get_volume_by_id(raw_volume)?; + match &data.open_volumes[volume_idx].volume_type { + VolumeType::Fat(fat) => { + if !fat.name.name().is_empty() { + debug!( + "Got volume label {:?} for {:?} from BPB", + fat.name, raw_volume + ); + return Ok(Some(fat.name.clone())); + } } } - false + drop(data); + + // Nothing in the BPB, let's do it the slow way + let root_dir = self.open_root_dir(raw_volume)?.to_directory(self); + let mut maybe_volume_name = None; + root_dir.iterate_dir(|de| { + if maybe_volume_name.is_none() + && de.attributes == Attributes::create_from_fat(Attributes::VOLUME) + { + maybe_volume_name = Some(unsafe { de.name.clone().to_volume_label() }) + } + })?; + + debug!( + "Got volume label {:?} for {:?} from root", + maybe_volume_name, raw_volume + ); + + Ok(maybe_volume_name) } /// Read from an open file. - pub fn read(&mut self, file: RawFile, buffer: &mut [u8]) -> Result> { - let file_idx = self.get_file_by_id(file)?; - let volume_idx = self.get_volume_by_id(self.open_files[file_idx].volume_id)?; + pub fn read(&self, file: RawFile, buffer: &mut [u8]) -> Result> { + let mut data = self.data.try_borrow_mut().map_err(|_| Error::LockError)?; + let data = data.deref_mut(); + + let file_idx = data.get_file_by_id(file)?; + let volume_idx = data.get_volume_by_id(data.open_files[file_idx].raw_volume)?; + // Calculate which file block the current offset lies within // While there is more to read, read the block and copy in to the buffer. // If we need to find the next cluster, walk the FAT. let mut space = buffer.len(); let mut read = 0; - while space > 0 && !self.open_files[file_idx].eof() { - let mut current_cluster = self.open_files[file_idx].current_cluster; - let (block_idx, block_offset, block_avail) = self.find_data_on_disk( + while space > 0 && !data.open_files[file_idx].eof() { + let mut current_cluster = data.open_files[file_idx].current_cluster; + let (block_idx, block_offset, block_avail) = data.find_data_on_disk( volume_idx, &mut current_cluster, - self.open_files[file_idx].entry.cluster, - self.open_files[file_idx].current_offset, + data.open_files[file_idx].entry.cluster, + data.open_files[file_idx].current_offset, )?; - self.open_files[file_idx].current_cluster = current_cluster; - let mut blocks = [Block::new()]; - self.block_device - .read(&mut blocks, block_idx, "read") + data.open_files[file_idx].current_cluster = current_cluster; + trace!("Reading file ID {:?}", file); + let block = data + .block_cache + .read(block_idx) .map_err(Error::DeviceError)?; - let block = &blocks[0]; let to_copy = block_avail .min(space) - .min(self.open_files[file_idx].left() as usize); + .min(data.open_files[file_idx].left() as usize); assert!(to_copy != 0); buffer[read..read + to_copy] .copy_from_slice(&block[block_offset..block_offset + to_copy]); read += to_copy; space -= to_copy; - self.open_files[file_idx] + data.open_files[file_idx] .seek_from_current(to_copy as i32) .unwrap(); } @@ -652,63 +712,66 @@ where } /// Write to a open file. - pub fn write(&mut self, file: RawFile, buffer: &[u8]) -> Result<(), Error> { + pub fn write(&self, file: RawFile, buffer: &[u8]) -> Result<(), Error> { #[cfg(feature = "defmt-log")] debug!("write(file={:?}, buffer={:x}", file, buffer); #[cfg(feature = "log")] debug!("write(file={:?}, buffer={:x?}", file, buffer); + let mut data = self.data.try_borrow_mut().map_err(|_| Error::LockError)?; + let data = data.deref_mut(); + // Clone this so we can touch our other structures. Need to ensure we // write it back at the end. - let file_idx = self.get_file_by_id(file)?; - let volume_idx = self.get_volume_by_id(self.open_files[file_idx].volume_id)?; + let file_idx = data.get_file_by_id(file)?; + let volume_idx = data.get_volume_by_id(data.open_files[file_idx].raw_volume)?; - if self.open_files[file_idx].mode == Mode::ReadOnly { + if data.open_files[file_idx].mode == Mode::ReadOnly { return Err(Error::ReadOnly); } - self.open_files[file_idx].dirty = true; + data.open_files[file_idx].dirty = true; - if self.open_files[file_idx].entry.cluster.0 < RESERVED_ENTRIES { + if data.open_files[file_idx].entry.cluster.0 < fat::RESERVED_ENTRIES { // file doesn't have a valid allocated cluster (possible zero-length file), allocate one - self.open_files[file_idx].entry.cluster = - match self.open_volumes[volume_idx].volume_type { + data.open_files[file_idx].entry.cluster = + match data.open_volumes[volume_idx].volume_type { VolumeType::Fat(ref mut fat) => { - fat.alloc_cluster(&self.block_device, None, false)? + fat.alloc_cluster(&mut data.block_cache, None, false)? } }; debug!( "Alloc first cluster {:?}", - self.open_files[file_idx].entry.cluster + data.open_files[file_idx].entry.cluster ); } // Clone this so we can touch our other structures. - let volume_idx = self.get_volume_by_id(self.open_files[file_idx].volume_id)?; + let volume_idx = data.get_volume_by_id(data.open_files[file_idx].raw_volume)?; - if (self.open_files[file_idx].current_cluster.1) < self.open_files[file_idx].entry.cluster { + if (data.open_files[file_idx].current_cluster.1) < data.open_files[file_idx].entry.cluster { debug!("Rewinding to start"); - self.open_files[file_idx].current_cluster = - (0, self.open_files[file_idx].entry.cluster); + data.open_files[file_idx].current_cluster = + (0, data.open_files[file_idx].entry.cluster); } let bytes_until_max = - usize::try_from(MAX_FILE_SIZE - self.open_files[file_idx].current_offset) + usize::try_from(MAX_FILE_SIZE - data.open_files[file_idx].current_offset) .map_err(|_| Error::ConversionError)?; let bytes_to_write = core::cmp::min(buffer.len(), bytes_until_max); let mut written = 0; while written < bytes_to_write { - let mut current_cluster = self.open_files[file_idx].current_cluster; + let mut current_cluster = data.open_files[file_idx].current_cluster; debug!( "Have written bytes {}/{}, finding cluster {:?}", written, bytes_to_write, current_cluster ); - let current_offset = self.open_files[file_idx].current_offset; - let (block_idx, block_offset, block_avail) = match self.find_data_on_disk( + let current_offset = data.open_files[file_idx].current_offset; + let (block_idx, block_offset, block_avail) = match data.find_data_on_disk( volume_idx, &mut current_cluster, - self.open_files[file_idx].entry.cluster, + data.open_files[file_idx].entry.cluster, current_offset, ) { Ok(vars) => { @@ -720,21 +783,25 @@ where } Err(Error::EndOfFile) => { debug!("Extending file"); - match self.open_volumes[volume_idx].volume_type { + match data.open_volumes[volume_idx].volume_type { VolumeType::Fat(ref mut fat) => { if fat - .alloc_cluster(&self.block_device, Some(current_cluster.1), false) + .alloc_cluster( + &mut data.block_cache, + Some(current_cluster.1), + false, + ) .is_err() { return Err(Error::DiskFull); } debug!("Allocated new FAT cluster, finding offsets..."); - let new_offset = self + let new_offset = data .find_data_on_disk( volume_idx, &mut current_cluster, - self.open_files[file_idx].entry.cluster, - self.open_files[file_idx].current_offset, + data.open_files[file_idx].entry.cluster, + data.open_files[file_idx].current_offset, ) .map_err(|_| Error::AllocationError)?; debug!("New offset {:?}", new_offset); @@ -744,68 +811,69 @@ where } Err(e) => return Err(e), }; - let mut blocks = [Block::new()]; let to_copy = core::cmp::min(block_avail, bytes_to_write - written); - if block_offset != 0 { - debug!("Partial block write"); - self.block_device - .read(&mut blocks, block_idx, "read") - .map_err(Error::DeviceError)?; - } - let block = &mut blocks[0]; + let block = if block_offset != 0 { + debug!("Reading for partial block write"); + data.block_cache + .read_mut(block_idx) + .map_err(Error::DeviceError)? + } else { + data.block_cache.blank_mut(block_idx) + }; block[block_offset..block_offset + to_copy] .copy_from_slice(&buffer[written..written + to_copy]); debug!("Writing block {:?}", block_idx); - self.block_device - .write(&blocks, block_idx) - .map_err(Error::DeviceError)?; + data.block_cache.write_back()?; written += to_copy; - self.open_files[file_idx].current_cluster = current_cluster; + data.open_files[file_idx].current_cluster = current_cluster; let to_copy = to_copy as u32; - let new_offset = self.open_files[file_idx].current_offset + to_copy; - if new_offset > self.open_files[file_idx].entry.size { + let new_offset = data.open_files[file_idx].current_offset + to_copy; + if new_offset > data.open_files[file_idx].entry.size { // We made it longer - self.open_files[file_idx].update_length(new_offset); + data.open_files[file_idx].update_length(new_offset); } - self.open_files[file_idx] + data.open_files[file_idx] .seek_from_start(new_offset) .unwrap(); // Entry update deferred to file close, for performance. } - self.open_files[file_idx].entry.attributes.set_archive(true); - self.open_files[file_idx].entry.mtime = self.time_source.get_timestamp(); + data.open_files[file_idx].entry.attributes.set_archive(true); + data.open_files[file_idx].entry.mtime = self.time_source.get_timestamp(); Ok(()) } /// Close a file with the given raw file handle. - pub fn close_file(&mut self, file: RawFile) -> Result<(), Error> { + pub fn close_file(&self, file: RawFile) -> Result<(), Error> { let flush_result = self.flush_file(file); - let file_idx = self.get_file_by_id(file)?; - self.open_files.swap_remove(file_idx); + let mut data = self.data.try_borrow_mut().map_err(|_| Error::LockError)?; + let file_idx = data.get_file_by_id(file)?; + data.open_files.swap_remove(file_idx); flush_result } /// Flush (update the entry) for a file with the given raw file handle. - pub fn flush_file(&mut self, file: RawFile) -> Result<(), Error> { - let file_info = self - .open_files - .iter() - .find(|info| info.file_id == file) - .ok_or(Error::BadHandle)?; - - if file_info.dirty { - let volume_idx = self.get_volume_by_id(file_info.volume_id)?; - match self.open_volumes[volume_idx].volume_type { - VolumeType::Fat(ref mut fat) => { + pub fn flush_file(&self, file: RawFile) -> Result<(), Error> { + let mut data = self.data.try_borrow_mut().map_err(|_| Error::LockError)?; + let data = data.deref_mut(); + + let file_id = data.get_file_by_id(file)?; + + if data.open_files[file_id].dirty { + let volume_idx = data.get_volume_by_id(data.open_files[file_id].raw_volume)?; + match &mut data.open_volumes[volume_idx].volume_type { + VolumeType::Fat(fat) => { debug!("Updating FAT info sector"); - fat.update_info_sector(&self.block_device)?; - debug!("Updating dir entry {:?}", file_info.entry); - if file_info.entry.size != 0 { + fat.update_info_sector(&mut data.block_cache)?; + debug!("Updating dir entry {:?}", data.open_files[file_id].entry); + if data.open_files[file_id].entry.size != 0 { // If you have a length, you must have a cluster - assert!(file_info.entry.cluster.0 != 0); + assert!(data.open_files[file_id].entry.cluster.0 != 0); } - fat.write_entry_to_disk(&self.block_device, &file_info.entry)?; + fat.write_entry_to_disk( + &mut data.block_cache, + &data.open_files[file_id].entry, + )?; } }; } @@ -814,28 +882,28 @@ where /// Check if any files or folders are open. pub fn has_open_handles(&self) -> bool { - !(self.open_dirs.is_empty() || self.open_files.is_empty()) + let data = self.data.borrow(); + !(data.open_dirs.is_empty() || data.open_files.is_empty()) } /// Consume self and return BlockDevice and TimeSource pub fn free(self) -> (D, T) { - (self.block_device, self.time_source) + let data = self.data.into_inner(); + (data.block_cache.free(), self.time_source) } /// Check if a file is at End Of File. pub fn file_eof(&self, file: RawFile) -> Result> { - let file_idx = self.get_file_by_id(file)?; - Ok(self.open_files[file_idx].eof()) + let data = self.data.try_borrow().map_err(|_| Error::LockError)?; + let file_idx = data.get_file_by_id(file)?; + Ok(data.open_files[file_idx].eof()) } /// Seek a file with an offset from the start of the file. - pub fn file_seek_from_start( - &mut self, - file: RawFile, - offset: u32, - ) -> Result<(), Error> { - let file_idx = self.get_file_by_id(file)?; - self.open_files[file_idx] + pub fn file_seek_from_start(&self, file: RawFile, offset: u32) -> Result<(), Error> { + let mut data = self.data.try_borrow_mut().map_err(|_| Error::LockError)?; + let file_idx = data.get_file_by_id(file)?; + data.open_files[file_idx] .seek_from_start(offset) .map_err(|_| Error::InvalidOffset)?; Ok(()) @@ -843,25 +911,23 @@ where /// Seek a file with an offset from the current position. pub fn file_seek_from_current( - &mut self, + &self, file: RawFile, offset: i32, ) -> Result<(), Error> { - let file_idx = self.get_file_by_id(file)?; - self.open_files[file_idx] + let mut data = self.data.try_borrow_mut().map_err(|_| Error::LockError)?; + let file_idx = data.get_file_by_id(file)?; + data.open_files[file_idx] .seek_from_current(offset) .map_err(|_| Error::InvalidOffset)?; Ok(()) } /// Seek a file with an offset back from the end of the file. - pub fn file_seek_from_end( - &mut self, - file: RawFile, - offset: u32, - ) -> Result<(), Error> { - let file_idx = self.get_file_by_id(file)?; - self.open_files[file_idx] + pub fn file_seek_from_end(&self, file: RawFile, offset: u32) -> Result<(), Error> { + let mut data = self.data.try_borrow_mut().map_err(|_| Error::LockError)?; + let file_idx = data.get_file_by_id(file)?; + data.open_files[file_idx] .seek_from_end(offset) .map_err(|_| Error::InvalidOffset)?; Ok(()) @@ -869,35 +935,40 @@ where /// Get the length of a file pub fn file_length(&self, file: RawFile) -> Result> { - let file_idx = self.get_file_by_id(file)?; - Ok(self.open_files[file_idx].length()) + let data = self.data.try_borrow().map_err(|_| Error::LockError)?; + let file_idx = data.get_file_by_id(file)?; + Ok(data.open_files[file_idx].length()) } /// Get the current offset of a file pub fn file_offset(&self, file: RawFile) -> Result> { - let file_idx = self.get_file_by_id(file)?; - Ok(self.open_files[file_idx].current_offset) + let data = self.data.try_borrow().map_err(|_| Error::LockError)?; + let file_idx = data.get_file_by_id(file)?; + Ok(data.open_files[file_idx].current_offset) } /// Create a directory in a given directory. pub fn make_dir_in_dir( - &mut self, + &self, directory: RawDirectory, name: N, ) -> Result<(), Error> where N: ToShortFileName, { + let mut data = self.data.try_borrow_mut().map_err(|_| Error::LockError)?; + let data = data.deref_mut(); + // This check is load-bearing - we do an unchecked push later. - if self.open_dirs.is_full() { + if data.open_dirs.is_full() { return Err(Error::TooManyOpenDirs); } - let parent_directory_idx = self.get_dir_by_id(directory)?; - let parent_directory_info = &self.open_dirs[parent_directory_idx]; - let volume_id = self.open_dirs[parent_directory_idx].volume_id; - let volume_idx = self.get_volume_by_id(volume_id)?; - let volume_info = &self.open_volumes[volume_idx]; + let parent_directory_idx = data.get_dir_by_id(directory)?; + let parent_directory_info = &data.open_dirs[parent_directory_idx]; + let volume_id = data.open_dirs[parent_directory_idx].raw_volume; + let volume_idx = data.get_volume_by_id(volume_id)?; + let volume_info = &data.open_volumes[volume_idx]; let sfn = name.to_short_filename().map_err(Error::FilenameError)?; debug!("Creating directory '{}'", sfn); @@ -909,7 +980,7 @@ where // Does an entry exist with this name? let maybe_dir_entry = match &volume_info.volume_type { VolumeType::Fat(fat) => { - fat.find_directory_entry(&self.block_device, parent_directory_info, &sfn) + fat.find_directory_entry(&mut data.block_cache, parent_directory_info, &sfn) } }; @@ -932,11 +1003,11 @@ where let att = Attributes::create_from_fat(Attributes::DIRECTORY); // Need mutable access for this - match &mut self.open_volumes[volume_idx].volume_type { + match &mut data.open_volumes[volume_idx].volume_type { VolumeType::Fat(fat) => { debug!("Making dir entry"); let mut new_dir_entry_in_parent = fat.write_new_directory_entry( - &self.block_device, + &mut data.block_cache, &self.time_source, parent_directory_info.cluster, sfn, @@ -944,16 +1015,16 @@ where )?; if new_dir_entry_in_parent.cluster == ClusterId::EMPTY { new_dir_entry_in_parent.cluster = - fat.alloc_cluster(&self.block_device, None, false)?; + fat.alloc_cluster(&mut data.block_cache, None, false)?; // update the parent dir with the cluster of the new dir - fat.write_entry_to_disk(&self.block_device, &new_dir_entry_in_parent)?; + fat.write_entry_to_disk(&mut data.block_cache, &new_dir_entry_in_parent)?; } let new_dir_start_block = fat.cluster_to_block(new_dir_entry_in_parent.cluster); debug!("Made new dir entry {:?}", new_dir_entry_in_parent); let now = self.time_source.get_timestamp(); let fat_type = fat.get_fat_type(); // A blank block - let mut blocks = [Block::new()]; + let block = data.block_cache.blank_mut(new_dir_start_block); // make the "." entry let dot_entry_in_child = DirEntry { name: crate::ShortFileName::this_dir(), @@ -968,9 +1039,9 @@ where }; debug!("New dir has {:?}", dot_entry_in_child); let mut offset = 0; - blocks[0][offset..offset + OnDiskDirEntry::LEN] + block[offset..offset + fat::OnDiskDirEntry::LEN] .copy_from_slice(&dot_entry_in_child.serialize(fat_type)[..]); - offset += OnDiskDirEntry::LEN; + offset += fat::OnDiskDirEntry::LEN; // make the ".." entry let dot_dot_entry_in_child = DirEntry { name: crate::ShortFileName::parent_dir(), @@ -979,7 +1050,7 @@ where attributes: att, // point at our parent cluster: match fat_type { - FatType::Fat16 => { + fat::FatType::Fat16 => { // On FAT16, indicate parent is root using Cluster(0) if parent_directory_info.cluster == ClusterId::ROOT_DIR { ClusterId::EMPTY @@ -987,59 +1058,102 @@ where parent_directory_info.cluster } } - FatType::Fat32 => parent_directory_info.cluster, + fat::FatType::Fat32 => parent_directory_info.cluster, }, size: 0, entry_block: new_dir_start_block, - entry_offset: OnDiskDirEntry::LEN_U32, + entry_offset: fat::OnDiskDirEntry::LEN_U32, }; debug!("New dir has {:?}", dot_dot_entry_in_child); - blocks[0][offset..offset + OnDiskDirEntry::LEN] + block[offset..offset + fat::OnDiskDirEntry::LEN] .copy_from_slice(&dot_dot_entry_in_child.serialize(fat_type)[..]); - self.block_device - .write(&blocks, new_dir_start_block) - .map_err(Error::DeviceError)?; + data.block_cache.write_back()?; - // Now zero the rest of the cluster - for b in blocks[0].iter_mut() { - *b = 0; - } - for block in new_dir_start_block + for block_idx in new_dir_start_block .range(BlockCount(u32::from(fat.blocks_per_cluster))) .skip(1) { - self.block_device - .write(&blocks, block) - .map_err(Error::DeviceError)?; + let _block = data.block_cache.blank_mut(block_idx); + data.block_cache.write_back()?; } } }; Ok(()) } +} + +/// The mutable data the VolumeManager needs to hold +/// +/// Kept separate so its easier to wrap it in a RefCell +#[derive(Debug)] + +struct VolumeManagerData< + D, + const MAX_DIRS: usize = 4, + const MAX_FILES: usize = 4, + const MAX_VOLUMES: usize = 1, +> where + D: BlockDevice, +{ + id_generator: HandleGenerator, + block_cache: BlockCache, + open_volumes: Vec, + open_dirs: Vec, + open_files: Vec, +} + +impl + VolumeManagerData +where + D: BlockDevice, +{ + /// Check if a file is open + /// + /// Returns `true` if it's open, `false`, otherwise. + fn file_is_open(&self, raw_volume: RawVolume, dir_entry: &DirEntry) -> bool { + for f in self.open_files.iter() { + if f.raw_volume == raw_volume + && f.entry.entry_block == dir_entry.entry_block + && f.entry.entry_offset == dir_entry.entry_offset + { + return true; + } + } + false + } - fn get_volume_by_id(&self, volume: RawVolume) -> Result> { + fn get_volume_by_id(&self, raw_volume: RawVolume) -> Result> + where + E: core::fmt::Debug, + { for (idx, v) in self.open_volumes.iter().enumerate() { - if v.volume_id == volume { + if v.raw_volume == raw_volume { return Ok(idx); } } Err(Error::BadHandle) } - fn get_dir_by_id(&self, directory: RawDirectory) -> Result> { + fn get_dir_by_id(&self, raw_directory: RawDirectory) -> Result> + where + E: core::fmt::Debug, + { for (idx, d) in self.open_dirs.iter().enumerate() { - if d.directory_id == directory { + if d.raw_directory == raw_directory { return Ok(idx); } } Err(Error::BadHandle) } - fn get_file_by_id(&self, file: RawFile) -> Result> { + fn get_file_by_id(&self, raw_file: RawFile) -> Result> + where + E: core::fmt::Debug, + { for (idx, f) in self.open_files.iter().enumerate() { - if f.file_id == file { + if f.raw_file == raw_file { return Ok(idx); } } @@ -1056,12 +1170,15 @@ where /// * the byte offset into that block for the data we want, and /// * how many bytes remain in that block. fn find_data_on_disk( - &self, + &mut self, volume_idx: usize, start: &mut (u32, ClusterId), file_start: ClusterId, desired_offset: u32, - ) -> Result<(BlockIdx, usize, usize), Error> { + ) -> Result<(BlockIdx, usize, usize), Error> + where + D: BlockDevice, + { let bytes_per_cluster = match &self.open_volumes[volume_idx].volume_type { VolumeType::Fat(fat) => fat.bytes_per_cluster(), }; @@ -1076,12 +1193,9 @@ where let offset_from_cluster = desired_offset - start.0; // walk through the FAT chain let num_clusters = offset_from_cluster / bytes_per_cluster; - let mut block_cache = BlockCache::empty(); for _ in 0..num_clusters { start.1 = match &self.open_volumes[volume_idx].volume_type { - VolumeType::Fat(fat) => { - fat.next_cluster(&self.block_device, start.1, &mut block_cache)? - } + VolumeType::Fat(fat) => fat.next_cluster(&mut self.block_cache, start.1)?, }; start.0 += bytes_per_cluster; } @@ -1127,7 +1241,7 @@ fn solve_mode_variant(mode: Mode, dir_entry_is_some: bool) -> Mode { #[cfg(test)] mod tests { use super::*; - use crate::filesystem::SearchId; + use crate::filesystem::Handle; use crate::Timestamp; struct DummyBlockDevice; @@ -1157,12 +1271,7 @@ mod tests { type Error = Error; /// Read one or more blocks, starting at the given block index. - fn read( - &self, - blocks: &mut [Block], - start_block_idx: BlockIdx, - _reason: &str, - ) -> Result<(), Self::Error> { + fn read(&self, blocks: &mut [Block], start_block_idx: BlockIdx) -> Result<(), Self::Error> { // Actual blocks taken from an SD card, except I've changed the start and length of partition 0. static BLOCKS: [Block; 3] = [ Block { @@ -1367,16 +1476,16 @@ mod tests { #[test] fn partition0() { - let mut c: VolumeManager = + let c: VolumeManager = VolumeManager::new_with_limits(DummyBlockDevice, Clock, 0xAA00_0000); let v = c.open_raw_volume(VolumeIdx(0)).unwrap(); - let expected_id = RawVolume(SearchId(0xAA00_0000)); + let expected_id = RawVolume(Handle(0xAA00_0000)); assert_eq!(v, expected_id); assert_eq!( - &c.open_volumes[0], + &c.data.borrow().open_volumes[0], &VolumeInfo { - volume_id: expected_id, + raw_volume: expected_id, idx: VolumeIdx(0), volume_type: VolumeType::Fat(crate::FatVolume { lba_start: BlockIdx(1), @@ -1384,7 +1493,7 @@ mod tests { blocks_per_cluster: 8, first_data_block: BlockCount(15136), fat_start: BlockCount(32), - name: fat::VolumeName::new(*b"Pictures "), + name: fat::VolumeName::create_from_str("Pictures").unwrap(), free_clusters_count: None, next_free_cluster: None, cluster_count: 965_788, diff --git a/tests/directories.rs b/tests/directories.rs index df9154f..1acfb37 100644 --- a/tests/directories.rs +++ b/tests/directories.rs @@ -38,7 +38,7 @@ impl PartialEq for ExpectedDirEntry { fn fat16_root_directory_listing() { let time_source = utils::make_time_source(); let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap(); - let mut volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); + let volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); let fat16_volume = volume_mgr .open_raw_volume(embedded_sdmmc::VolumeIdx(0)) @@ -100,7 +100,7 @@ fn fat16_root_directory_listing() { fn fat16_sub_directory_listing() { let time_source = utils::make_time_source(); let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap(); - let mut volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); + let volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); let fat16_volume = volume_mgr .open_raw_volume(embedded_sdmmc::VolumeIdx(0)) @@ -165,7 +165,7 @@ fn fat16_sub_directory_listing() { fn fat32_root_directory_listing() { let time_source = utils::make_time_source(); let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap(); - let mut volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); + let volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); let fat32_volume = volume_mgr .open_raw_volume(embedded_sdmmc::VolumeIdx(1)) @@ -227,7 +227,7 @@ fn fat32_root_directory_listing() { fn open_dir_twice() { let time_source = utils::make_time_source(); let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap(); - let mut volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); + let volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); let fat32_volume = volume_mgr .open_raw_volume(embedded_sdmmc::VolumeIdx(1)) @@ -267,7 +267,7 @@ fn open_dir_twice() { fn open_too_many_dirs() { let time_source = utils::make_time_source(); let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap(); - let mut volume_mgr: embedded_sdmmc::VolumeManager< + let volume_mgr: embedded_sdmmc::VolumeManager< utils::RamDisk>, utils::TestTimeSource, 1, @@ -292,7 +292,7 @@ fn open_too_many_dirs() { fn find_dir_entry() { let time_source = utils::make_time_source(); let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap(); - let mut volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); + let volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); let fat32_volume = volume_mgr .open_raw_volume(embedded_sdmmc::VolumeIdx(1)) @@ -322,7 +322,7 @@ fn find_dir_entry() { fn delete_file() { let time_source = utils::make_time_source(); let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap(); - let mut volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); + let volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); let fat32_volume = volume_mgr .open_raw_volume(embedded_sdmmc::VolumeIdx(1)) @@ -367,7 +367,7 @@ fn delete_file() { fn make_directory() { let time_source = utils::make_time_source(); let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap(); - let mut volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); + let volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); let fat32_volume = volume_mgr .open_raw_volume(embedded_sdmmc::VolumeIdx(1)) diff --git a/tests/open_files.rs b/tests/open_files.rs index 10e2181..6b927bc 100644 --- a/tests/open_files.rs +++ b/tests/open_files.rs @@ -8,7 +8,7 @@ mod utils; fn open_files() { let time_source = utils::make_time_source(); let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap(); - let mut volume_mgr: VolumeManager>, utils::TestTimeSource, 4, 2, 1> = + let volume_mgr: VolumeManager>, utils::TestTimeSource, 4, 2, 1> = VolumeManager::new_with_limits(disk, time_source, 0xAA00_0000); let volume = volume_mgr .open_raw_volume(VolumeIdx(0)) @@ -95,11 +95,11 @@ fn open_files() { fn open_non_raw() { let time_source = utils::make_time_source(); let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap(); - let mut volume_mgr: VolumeManager>, utils::TestTimeSource, 4, 2, 1> = + let volume_mgr: VolumeManager>, utils::TestTimeSource, 4, 2, 1> = VolumeManager::new_with_limits(disk, time_source, 0xAA00_0000); - let mut volume = volume_mgr.open_volume(VolumeIdx(0)).expect("open volume"); - let mut root_dir = volume.open_root_dir().expect("open root dir"); - let mut f = root_dir + let volume = volume_mgr.open_volume(VolumeIdx(0)).expect("open volume"); + let root_dir = volume.open_root_dir().expect("open root dir"); + let f = root_dir .open_file_in_dir("README.TXT", Mode::ReadOnly) .expect("open file"); diff --git a/tests/read_file.rs b/tests/read_file.rs index b7c80c9..904964f 100644 --- a/tests/read_file.rs +++ b/tests/read_file.rs @@ -11,7 +11,7 @@ static TEST_DAT_SHA256_SUM: &[u8] = fn read_file_512_blocks() { let time_source = utils::make_time_source(); let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap(); - let mut volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); + let volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); let fat16_volume = volume_mgr .open_raw_volume(embedded_sdmmc::VolumeIdx(0)) @@ -53,7 +53,7 @@ fn read_file_512_blocks() { fn read_file_all() { let time_source = utils::make_time_source(); let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap(); - let mut volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); + let volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); let fat16_volume = volume_mgr .open_raw_volume(embedded_sdmmc::VolumeIdx(0)) @@ -87,7 +87,7 @@ fn read_file_all() { fn read_file_prime_blocks() { let time_source = utils::make_time_source(); let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap(); - let mut volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); + let volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); let fat16_volume = volume_mgr .open_raw_volume(embedded_sdmmc::VolumeIdx(0)) @@ -130,7 +130,7 @@ fn read_file_prime_blocks() { fn read_file_backwards() { let time_source = utils::make_time_source(); let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap(); - let mut volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); + let volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); let fat16_volume = volume_mgr .open_raw_volume(embedded_sdmmc::VolumeIdx(0)) @@ -188,13 +188,13 @@ fn read_file_backwards() { fn read_file_with_odd_seek() { let time_source = utils::make_time_source(); let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap(); - let mut volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); + let volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); - let mut volume = volume_mgr + let volume = volume_mgr .open_volume(embedded_sdmmc::VolumeIdx(0)) .unwrap(); - let mut root_dir = volume.open_root_dir().unwrap(); - let mut f = root_dir + let root_dir = volume.open_root_dir().unwrap(); + let f = root_dir .open_file_in_dir("64MB.DAT", embedded_sdmmc::Mode::ReadOnly) .unwrap(); f.seek_from_start(0x2c).unwrap(); diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs index 22ed2ea..3f89a0e 100644 --- a/tests/utils/mod.rs +++ b/tests/utils/mod.rs @@ -85,12 +85,7 @@ where { type Error = Error; - fn read( - &self, - blocks: &mut [Block], - start_block_idx: BlockIdx, - _reason: &str, - ) -> Result<(), Self::Error> { + fn read(&self, blocks: &mut [Block], start_block_idx: BlockIdx) -> Result<(), Self::Error> { let borrow = self.contents.borrow(); let contents: &[u8] = borrow.as_ref(); let mut block_idx = start_block_idx; diff --git a/tests/volume.rs b/tests/volume.rs index 633a8d2..c2ea49b 100644 --- a/tests/volume.rs +++ b/tests/volume.rs @@ -6,7 +6,7 @@ mod utils; fn open_all_volumes() { let time_source = utils::make_time_source(); let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap(); - let mut volume_mgr: embedded_sdmmc::VolumeManager< + let volume_mgr: embedded_sdmmc::VolumeManager< utils::RamDisk>, utils::TestTimeSource, 4, @@ -58,6 +58,12 @@ fn open_all_volumes() { Err(embedded_sdmmc::Error::FormatError(_e)) )); + // This isn't a valid volume + assert!(matches!( + volume_mgr.open_raw_volume(embedded_sdmmc::VolumeIdx(3)), + Err(embedded_sdmmc::Error::FormatError(_e)) + )); + // This isn't a valid volume assert!(matches!( volume_mgr.open_raw_volume(embedded_sdmmc::VolumeIdx(9)), @@ -76,7 +82,7 @@ fn open_all_volumes() { fn close_volume_too_early() { let time_source = utils::make_time_source(); let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap(); - let mut volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); + let volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); let volume = volume_mgr .open_raw_volume(embedded_sdmmc::VolumeIdx(0)) diff --git a/tests/write_file.rs b/tests/write_file.rs index 97e7bcd..af17814 100644 --- a/tests/write_file.rs +++ b/tests/write_file.rs @@ -8,7 +8,7 @@ mod utils; fn append_file() { let time_source = utils::make_time_source(); let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap(); - let mut volume_mgr: VolumeManager>, utils::TestTimeSource, 4, 2, 1> = + let volume_mgr: VolumeManager>, utils::TestTimeSource, 4, 2, 1> = VolumeManager::new_with_limits(disk, time_source, 0xAA00_0000); let volume = volume_mgr .open_raw_volume(VolumeIdx(0)) @@ -59,7 +59,7 @@ fn append_file() { fn flush_file() { let time_source = utils::make_time_source(); let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap(); - let mut volume_mgr: VolumeManager>, utils::TestTimeSource, 4, 2, 1> = + let volume_mgr: VolumeManager>, utils::TestTimeSource, 4, 2, 1> = VolumeManager::new_with_limits(disk, time_source, 0xAA00_0000); let volume = volume_mgr .open_raw_volume(VolumeIdx(0))