diff --git a/.gitignore b/.gitignore index 6936990..21eb078 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target **/*.rs.bk Cargo.lock +disk.img diff --git a/examples/delete_test.rs b/examples/delete_test.rs new file mode 100644 index 0000000..ae48c14 --- /dev/null +++ b/examples/delete_test.rs @@ -0,0 +1,163 @@ +//! # Tests the Embedded SDMMC Library +//! ```bash +//! $ cargo run --example delete_test -- /dev/mmcblk0 +//! $ cargo run --example delete_test -- /dev/sda +//! ``` +//! +//! If you pass a block device it should be unmounted. No testing has been +//! performed with Windows raw block devices - please report back if you try +//! this! There is a gzipped example disk image which you can gunzip and test +//! with if you don't have a suitable block device. +//! +//! ```bash +//! zcat ./disk.img.gz > ./disk.img +//! $ cargo run --example delete_test -- ./disk.img +//! ``` + +extern crate embedded_sdmmc; + +const FILE_TO_DELETE: &'static str = "DELETE.TXT"; + +use embedded_sdmmc::{ + Block, BlockCount, BlockDevice, BlockIdx, Controller, Error, Mode, TimeSource, Timestamp, + VolumeIdx, +}; +use std::cell::RefCell; +use std::fs::{File, OpenOptions}; +use std::io::prelude::*; +use std::io::SeekFrom; +use std::path::Path; + +#[derive(Debug)] +struct LinuxBlockDevice { + file: RefCell, + print_blocks: bool, +} + +impl LinuxBlockDevice { + fn new

(device_name: P, print_blocks: bool) -> Result + where + P: AsRef, + { + Ok(LinuxBlockDevice { + file: RefCell::new( + OpenOptions::new() + .read(true) + .write(true) + .open(device_name)?, + ), + print_blocks, + }) + } +} + +impl BlockDevice for LinuxBlockDevice { + type Error = std::io::Error; + + fn read( + &self, + blocks: &mut [Block], + start_block_idx: BlockIdx, + reason: &str, + ) -> 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 + ); + } + } + Ok(()) + } + + fn write(&self, blocks: &[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() { + self.file.borrow_mut().write_all(&block.contents)?; + if self.print_blocks { + println!("Wrote: {:?}", &block); + } + } + Ok(()) + } + + fn num_blocks(&self) -> Result { + let num_blocks = self.file.borrow().metadata().unwrap().len() / 512; + Ok(BlockCount(num_blocks as u32)) + } +} + +struct Clock; + +impl TimeSource for Clock { + fn get_timestamp(&self) -> Timestamp { + Timestamp { + year_since_1970: 0, + zero_indexed_month: 0, + zero_indexed_day: 0, + hours: 0, + minutes: 0, + seconds: 0, + } + } +} + +fn main() { + 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) + .unwrap(); + println!("lbd: {:?}", lbd); + let mut controller = Controller::new(lbd, Clock); + for volume_idx in 0..=3 { + let volume = controller.get_volume(VolumeIdx(volume_idx)); + println!("volume {}: {:#?}", volume_idx, volume); + if let Ok(mut volume) = volume { + let root_dir = controller.open_root_dir(&volume).unwrap(); + println!("\tListing root directory:"); + controller + .iterate_dir(&volume, &root_dir, |x| { + println!("\t\tFound: {:?}", x); + }) + .unwrap(); + println!("\nCreating file {}...", FILE_TO_DELETE); + // This will panic if the file already exists, use ReadWriteCreateOrAppend or + // ReadWriteCreateOrTruncate instead + let mut f = controller + .open_file_in_dir( + &mut volume, + &root_dir, + FILE_TO_DELETE, + Mode::ReadWriteCreate, + ) + .unwrap(); + + println!("\tFinding {}...", FILE_TO_DELETE); + println!( + "\tFound {}?: {:?}", + FILE_TO_DELETE, + controller.find_directory_entry(&volume, &root_dir, FILE_TO_DELETE) + ); + controller.close_file(&volume, f).unwrap(); + + controller.delete_file_in_dir(&volume, &root_dir, FILE_TO_DELETE).unwrap(); + + println!("\tFinding {}...", FILE_TO_DELETE); + println!( + "\tFound {}?: {:?}", + FILE_TO_DELETE, + controller.find_directory_entry(&volume, &root_dir, FILE_TO_DELETE) + ); + } + } +} diff --git a/src/fat.rs b/src/fat.rs index 92f8c27..231365a 100644 --- a/src/fat.rs +++ b/src/fat.rs @@ -1003,6 +1003,117 @@ impl FatVolume { } } + /// Delete an entry from the given directory + pub(crate) fn delete_directory_entry( + &self, + controller: &mut Controller, + dir: &Directory, + name: &str, + ) -> Result<(), Error> + where + D: BlockDevice, + T: TimeSource, + { + let match_name = ShortFileName::create_from_str(name).map_err(Error::FilenameError)?; + let mut blocks = [Block::new()]; + match &self.fat_specific_info { + FatSpecificInfo::Fat16(fat16_info) => { + let mut current_cluster = Some(dir.cluster); + let mut first_dir_block_num = match dir.cluster { + Cluster::ROOT_DIR => self.lba_start + fat16_info.first_root_dir_block, + _ => self.cluster_to_block(dir.cluster), + }; + let dir_size = match dir.cluster { + Cluster::ROOT_DIR => BlockCount( + ((u32::from(fat16_info.root_entries_count) * 32) + (Block::LEN as u32 - 1)) + / Block::LEN as u32, + ), + _ => BlockCount(u32::from(self.blocks_per_cluster)), + }; + + while let Some(cluster) = current_cluster { + for block in first_dir_block_num.range(dir_size) { + controller + .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]); + if dir_entry.is_end() { + // Can quit early + return Err(Error::FileNotFound); + } else if dir_entry.matches(&match_name) { + // let mut blockWrite = &blocks[0][start..end]; + // blockWrite[0] = 0xE5; + let mut blocks = [Block::new()]; + blocks[0].contents[0] = 0xE5; + controller + .block_device + .write(&blocks, block) + .map_err(Error::DeviceError)?; + return Ok(()); + } + } + } + if cluster != Cluster::ROOT_DIR { + current_cluster = match self.next_cluster(controller, cluster) { + Ok(n) => { + first_dir_block_num = self.cluster_to_block(n); + Some(n) + } + _ => None, + }; + } else { + current_cluster = None; + } + } + Err(Error::FileNotFound) + } + FatSpecificInfo::Fat32(fat32_info) => { + let match_name = + ShortFileName::create_from_str(name).map_err(Error::FilenameError)?; + let mut current_cluster = match dir.cluster { + Cluster::ROOT_DIR => Some(fat32_info.first_root_dir_cluster), + _ => Some(dir.cluster), + }; + let mut blocks = [Block::new()]; + 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))) { + controller + .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]); + if dir_entry.is_end() { + // Can quit early + return Err(Error::FileNotFound); + } else if dir_entry.matches(&match_name) { + let mut blocks = blocks; + blocks[0].contents[start] = 0xE5; + controller + .block_device + .write(&blocks, block) + .map_err(Error::DeviceError)?; + return Ok(()); + } + } + } + current_cluster = match self.next_cluster(controller, cluster) { + Ok(n) => Some(n), + _ => None, + } + } + Err(Error::FileNotFound) + } + } + } + /// Finds the next free cluster after the start_cluster and before end_cluster pub(crate) fn find_next_free_cluster( &self, diff --git a/src/lib.rs b/src/lib.rs index e9f35a4..ffc2c81 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -119,6 +119,10 @@ where DirAlreadyOpen, /// You can't open a directory as a file OpenedDirAsFile, + /// You can't delete a directory as a file + DeleteDirAsFile, + /// You can't delete an open file + FileIsOpen, /// We can't do that yet Unsupported, /// Tried to read beyond end of file @@ -585,6 +589,42 @@ where Ok(file) } + /// Delete a closed file with the given full path, if exists. + pub fn delete_file_in_dir( + &mut self, + volume: &Volume, + dir: &Directory, + name: &str, + ) -> Result<(), Error> { + debug!( + "delete_file(volume={:?}, dir={:?}, filename={:?}", + volume, dir, name + ); + let dir_entry = match &volume.volume_type { + VolumeType::Fat(fat) => fat.find_directory_entry(self, dir, name), + }; + + let dir_entry = match dir_entry { + Ok(entry) => entry, + _ => return Err(Error::FileNotFound), + }; + + if dir_entry.attributes.is_directory() { + return Err(Error::DeleteDirAsFile); + } + + let target = (volume.idx, dir_entry.cluster); + for d in self.open_files.iter_mut() { + if *d == target { + return Err(Error::FileIsOpen); + } + } + + match &volume.volume_type { + VolumeType::Fat(fat) => return fat.delete_directory_entry(self, dir, name), + }; + } + /// Read from an open file. pub fn read( &mut self,