From d41d41de3059e2d5d675a2a2b4443c8c5fadaf3e Mon Sep 17 00:00:00 2001 From: Jonathan Pallant Date: Sat, 7 Oct 2023 20:42:38 +0100 Subject: [PATCH 01/36] Add Raw/Smart Handles for Volume/Directory/File Raw Handles work like the handles we had before. Smart Handles now hold an &mut VolumeManager. This means they can have methods, and they also have a Drop implementation. However, the &mut reference means you are much more limited in how you hold them and how many you can hold at a time. It's OK for opening a single file but more than that you you want to use Raw Handles and sort the Drop stuff out yourself. All the examples are updated to use Smart Handles. --- README.md | 24 ++---- examples/append_file.rs | 10 +-- examples/create_file.rs | 10 +-- examples/delete_file.rs | 7 +- examples/list_dir.rs | 17 ++-- examples/read_file.rs | 14 ++- examples/readme_test.rs | 19 ++--- examples/shell.rs | 151 +++++++++++++++------------------ src/filesystem/directory.rs | 154 ++++++++++++++++++++++++++++++++- src/filesystem/files.rs | 165 +++++++++++++++++++++++++++++++----- src/filesystem/mod.rs | 4 +- src/lib.rs | 123 +++++++++++++++++++++++---- src/volume_mgr.rs | 115 +++++++++++++++---------- tests/directories.rs | 106 ++++++++++++++++------- tests/open_files.rs | 94 +++++++++++++++----- tests/read_file.rs | 8 +- tests/volume.rs | 72 ++++++++-------- tests/write_file.rs | 4 +- 18 files changed, 776 insertions(+), 321 deletions(-) diff --git a/README.md b/README.md index 81bdfc2..242333b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ designed for readability and simplicity over performance. You will need something that implements the `BlockDevice` trait, which can read and write the 512-byte blocks (or sectors) from your card. If you were to implement this over USB Mass Storage, there's no reason this crate couldn't work with a USB Thumb Drive, but we only supply a `BlockDevice` suitable for reading SD and SDHC cards over SPI. ```rust -// Build an SD Card interface out of an SPI device, a chip-select pin and a delay object +// Build an SD Card interface out of an SPI device, a chip-select pin and the delay object let sdcard = embedded_sdmmc::SdCard::new(sdmmc_spi, sdmmc_cs, delay); // Get the card size (this also triggers card initialisation because it's not been done yet) println!("Card size is {} bytes", sdcard.num_bytes()?); @@ -20,29 +20,21 @@ println!("Card size is {} bytes", sdcard.num_bytes()?); let mut 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. -// It doesn't hold a reference to the Volume Manager and so must be passed back -// to every Volume Manager API call. This makes it easier to handle multiple -// volumes in parallel. -let volume0 = volume_mgr.get_volume(embedded_sdmmc::VolumeIdx(0))?; +let mut volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?; println!("Volume 0: {:?}", volume0); -// Open the root directory (passing in the volume we're using). -let root_dir = volume_mgr.open_root_dir(&volume0)?; +// Open the root directory (mutably borrows from the volume). +let mut root_dir = volume0.open_root_dir()?; // Open a file called "MY_FILE.TXT" in the root directory -let my_file = volume_mgr.open_file_in_dir( - root_dir, - "MY_FILE.TXT", - embedded_sdmmc::Mode::ReadOnly, -)?; +// This mutably borrows the directory. +let mut my_file = root_dir.open_file_in_dir("MY_FILE.TXT", embedded_sdmmc::Mode::ReadOnly)?; // Print the contents of the file -while !volume_manager.file_eof(my_file).unwrap() { +while !my_file.is_eof() { let mut buffer = [0u8; 32]; - let num_read = volume_mgr.read(&volume0, &mut my_file, &mut buffer)?; + let num_read = my_file.read(&mut buffer)?; for b in &buffer[0..num_read] { print!("{}", *b as char); } } -volume_mgr.close_file(my_file)?; -volume_mgr.close_dir(root_dir)?; ``` ### Open directories and files diff --git a/examples/append_file.rs b/examples/append_file.rs index 608fa2c..f5a3bc4 100644 --- a/examples/append_file.rs +++ b/examples/append_file.rs @@ -32,13 +32,11 @@ fn main() -> Result<(), embedded_sdmmc::Error> { 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 volume = volume_mgr.open_volume(VolumeIdx(0))?; - let root_dir = volume_mgr.open_root_dir(volume)?; + let mut volume = volume_mgr.open_volume(VolumeIdx(0))?; + let mut root_dir = volume.open_root_dir()?; println!("\nCreating file {}...", FILE_TO_APPEND); - let f = volume_mgr.open_file_in_dir(root_dir, FILE_TO_APPEND, Mode::ReadWriteAppend)?; - volume_mgr.write(f, b"\r\n\r\nThis has been added to your file.\r\n")?; - volume_mgr.close_file(f)?; - volume_mgr.close_dir(root_dir)?; + let mut 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/create_file.rs b/examples/create_file.rs index 1d426eb..81263ce 100644 --- a/examples/create_file.rs +++ b/examples/create_file.rs @@ -32,16 +32,14 @@ fn main() -> Result<(), embedded_sdmmc::Error> { 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 volume = volume_mgr.open_volume(VolumeIdx(0))?; - let root_dir = volume_mgr.open_root_dir(volume)?; + let mut volume = volume_mgr.open_volume(VolumeIdx(0))?; + let mut 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 f = volume_mgr.open_file_in_dir(root_dir, FILE_TO_CREATE, Mode::ReadWriteCreate)?; - volume_mgr.write(f, b"Hello, this is a new file on disk\r\n")?; - volume_mgr.close_file(f)?; - volume_mgr.close_dir(root_dir)?; + let mut 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 8b7b1cb..743b2d5 100644 --- a/examples/delete_file.rs +++ b/examples/delete_file.rs @@ -35,12 +35,11 @@ fn main() -> Result<(), embedded_sdmmc::Error> { 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 volume = volume_mgr.open_volume(VolumeIdx(0))?; - let root_dir = volume_mgr.open_root_dir(volume)?; + let mut volume = volume_mgr.open_volume(VolumeIdx(0))?; + let mut root_dir = volume.open_root_dir()?; println!("Deleting file {}...", FILE_TO_DELETE); - volume_mgr.delete_file_in_dir(root_dir, FILE_TO_DELETE)?; + root_dir.delete_file_in_dir(FILE_TO_DELETE)?; println!("Deleted!"); - volume_mgr.close_dir(root_dir)?; Ok(()) } diff --git a/examples/list_dir.rs b/examples/list_dir.rs index 7bcfeb2..51fa68e 100644 --- a/examples/list_dir.rs +++ b/examples/list_dir.rs @@ -49,10 +49,9 @@ fn main() -> Result<(), Error> { 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 volume = volume_mgr.open_volume(VolumeIdx(0))?; - let root_dir = volume_mgr.open_root_dir(volume)?; - list_dir(&mut volume_mgr, root_dir, "/")?; - volume_mgr.close_dir(root_dir)?; + let mut volume = volume_mgr.open_volume(VolumeIdx(0))?; + let root_dir = volume.open_root_dir()?; + list_dir(root_dir, "/")?; Ok(()) } @@ -60,13 +59,12 @@ fn main() -> Result<(), Error> { /// /// The path is for display purposes only. fn list_dir( - volume_mgr: &mut VolumeManager, - directory: Directory, + mut directory: Directory, path: &str, ) -> Result<(), Error> { println!("Listing {}", path); let mut children = Vec::new(); - volume_mgr.iterate_dir(directory, |entry| { + directory.iterate_dir(|entry| { println!( "{:12} {:9} {} {}", entry.name, @@ -87,14 +85,13 @@ fn list_dir( } })?; for child_name in children { - let child_dir = volume_mgr.open_dir(directory, &child_name)?; + let child_dir = directory.open_dir(&child_name)?; let child_path = if path == "/" { format!("/{}", child_name) } else { format!("{}/{}", path, child_name) }; - list_dir(volume_mgr, child_dir, &child_path)?; - volume_mgr.close_dir(child_dir)?; + list_dir(child_dir, &child_path)?; } Ok(()) } diff --git a/examples/read_file.rs b/examples/read_file.rs index a771f59..1a958c1 100644 --- a/examples/read_file.rs +++ b/examples/read_file.rs @@ -49,15 +49,14 @@ fn main() -> Result<(), embedded_sdmmc::Error> { 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 volume = volume_mgr.open_volume(VolumeIdx(0))?; - let root_dir = volume_mgr.open_root_dir(volume)?; + let mut volume = volume_mgr.open_volume(VolumeIdx(0))?; + let mut root_dir = volume.open_root_dir()?; println!("\nReading file {}...", FILE_TO_READ); - let f = volume_mgr.open_file_in_dir(root_dir, FILE_TO_READ, Mode::ReadOnly)?; - volume_mgr.close_dir(root_dir)?; - while !volume_mgr.file_eof(f)? { + let mut f = root_dir.open_file_in_dir(FILE_TO_READ, Mode::ReadOnly)?; + while !f.is_eof() { let mut buffer = [0u8; 16]; - let offset = volume_mgr.file_offset(f)?; - let mut len = volume_mgr.read(f, &mut buffer)?; + let offset = f.offset(); + let mut len = f.read(&mut buffer)?; print!("{:08x} {:02x?}", offset, &buffer[0..len]); while len < buffer.len() { print!(" "); @@ -74,7 +73,6 @@ fn main() -> Result<(), embedded_sdmmc::Error> { } println!("|"); } - volume_mgr.close_file(f)?; Ok(()) } diff --git a/examples/readme_test.rs b/examples/readme_test.rs index beba096..9fffca1 100644 --- a/examples/readme_test.rs +++ b/examples/readme_test.rs @@ -87,26 +87,21 @@ fn main() -> Result<(), Error> { let mut 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. - // It doesn't hold a reference to the Volume Manager and so must be passed back - // to every Volume Manager API call. This makes it easier to handle multiple - // volumes in parallel. - let volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?; + let mut volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?; println!("Volume 0: {:?}", volume0); - // Open the root directory (passing in the volume we're using). - let root_dir = volume_mgr.open_root_dir(volume0)?; + // Open the root directory (mutably borrows from the volume). + let mut root_dir = volume0.open_root_dir()?; // Open a file called "MY_FILE.TXT" in the root directory - let my_file = - volume_mgr.open_file_in_dir(root_dir, "MY_FILE.TXT", embedded_sdmmc::Mode::ReadOnly)?; + // This mutably borrows the directory. + let mut my_file = root_dir.open_file_in_dir("MY_FILE.TXT", embedded_sdmmc::Mode::ReadOnly)?; // Print the contents of the file - while !volume_mgr.file_eof(my_file).unwrap() { + while !my_file.is_eof() { let mut buffer = [0u8; 32]; - let num_read = volume_mgr.read(my_file, &mut buffer)?; + let num_read = my_file.read(&mut buffer)?; for b in &buffer[0..num_read] { print!("{}", *b as char); } } - volume_mgr.close_file(my_file)?; - volume_mgr.close_dir(root_dir)?; Ok(()) } diff --git a/examples/shell.rs b/examples/shell.rs index 35e9e13..ddea8de 100644 --- a/examples/shell.rs +++ b/examples/shell.rs @@ -4,15 +4,15 @@ use std::io::prelude::*; -use embedded_sdmmc::{Directory, Error, Volume, VolumeIdx, VolumeManager}; +use embedded_sdmmc::{Error, RawDirectory, RawVolume, VolumeIdx, VolumeManager}; use crate::linux::{Clock, LinuxBlockDevice}; mod linux; struct VolumeState { - directory: Directory, - volume: Volume, + directory: RawDirectory, + volume: RawVolume, path: Vec, } @@ -81,75 +81,61 @@ impl Context { println!("This volume isn't available"); return Ok(()); }; - let f = self.volume_mgr.open_file_in_dir( - s.directory, - arg, - embedded_sdmmc::Mode::ReadOnly, - )?; - let mut inner = || -> Result<(), Error> { - let mut data = Vec::new(); - while !self.volume_mgr.file_eof(f)? { - let mut buffer = vec![0u8; 65536]; - let n = self.volume_mgr.read(f, &mut buffer)?; - // read n bytes - data.extend_from_slice(&buffer[0..n]); - println!("Read {} bytes, making {} total", n, data.len()); - } - if let Ok(s) = std::str::from_utf8(&data) { - println!("{}", s); - } else { - println!("I'm afraid that file isn't UTF-8 encoded"); - } - Ok(()) - }; - let r = inner(); - self.volume_mgr.close_file(f)?; - r?; + let mut f = self + .volume_mgr + .open_file_in_dir(s.directory, arg, embedded_sdmmc::Mode::ReadOnly)? + .to_file(&mut self.volume_mgr); + let mut data = Vec::new(); + while !f.is_eof() { + let mut buffer = vec![0u8; 65536]; + let n = f.read(&mut buffer)?; + // read n bytes + data.extend_from_slice(&buffer[0..n]); + println!("Read {} bytes, making {} total", n, data.len()); + } + if let Ok(s) = std::str::from_utf8(&data) { + println!("{}", s); + } else { + println!("I'm afraid that file isn't UTF-8 encoded"); + } } else if let Some(arg) = line.strip_prefix("hexdump ") { let Some(s) = &mut self.volumes[self.current_volume] else { println!("This volume isn't available"); return Ok(()); }; - let f = self.volume_mgr.open_file_in_dir( - s.directory, - arg, - embedded_sdmmc::Mode::ReadOnly, - )?; - let mut inner = || -> Result<(), Error> { - let mut data = Vec::new(); - while !self.volume_mgr.file_eof(f)? { - let mut buffer = vec![0u8; 65536]; - let n = self.volume_mgr.read(f, &mut buffer)?; - // read n bytes - data.extend_from_slice(&buffer[0..n]); - println!("Read {} bytes, making {} total", n, data.len()); + let mut f = self + .volume_mgr + .open_file_in_dir(s.directory, arg, embedded_sdmmc::Mode::ReadOnly)? + .to_file(&mut self.volume_mgr); + let mut data = Vec::new(); + while !f.is_eof() { + let mut buffer = vec![0u8; 65536]; + let n = f.read(&mut buffer)?; + // read n bytes + data.extend_from_slice(&buffer[0..n]); + println!("Read {} bytes, making {} total", n, data.len()); + } + for (idx, chunk) in data.chunks(16).enumerate() { + print!("{:08x} | ", idx * 16); + for b in chunk { + print!("{:02x} ", b); } - for (idx, chunk) in data.chunks(16).enumerate() { - print!("{:08x} | ", idx * 16); - for b in chunk { - print!("{:02x} ", b); - } - for _padding in 0..(16 - chunk.len()) { - print!(" "); - } - print!("| "); - for b in chunk { - print!( - "{}", - if b.is_ascii_graphic() { - *b as char - } else { - '.' - } - ); - } - println!(); + for _padding in 0..(16 - chunk.len()) { + print!(" "); } - Ok(()) - }; - let r = inner(); - self.volume_mgr.close_file(f)?; - r?; + print!("| "); + for b in chunk { + print!( + "{}", + if b.is_ascii_graphic() { + *b as char + } else { + '.' + } + ); + } + println!(); + } } else { println!("Unknown command {line:?} - try 'help' for help"); } @@ -157,6 +143,24 @@ impl Context { } } +impl Drop for Context { + fn drop(&mut self) { + for v in self.volumes.iter_mut() { + if let Some(v) = v { + println!("Closing directory {:?}", v.directory); + self.volume_mgr + .close_dir(v.directory) + .expect("Closing directory"); + println!("Closing volume {:?}", v.volume); + self.volume_mgr + .close_volume(v.volume) + .expect("Closing volume"); + } + *v = None; + } + } +} + fn main() -> Result<(), Error> { env_logger::init(); let mut args = std::env::args().skip(1); @@ -174,7 +178,7 @@ fn main() -> Result<(), Error> { let mut current_volume = None; for volume_no in 0..4 { - match ctx.volume_mgr.open_volume(VolumeIdx(volume_no)) { + match ctx.volume_mgr.open_raw_volume(VolumeIdx(volume_no)) { Ok(volume) => { println!("Volume # {}: found", volume_no,); match ctx.volume_mgr.open_root_dir(volume) { @@ -226,21 +230,6 @@ fn main() -> Result<(), Error> { } } - for (idx, s) in ctx.volumes.into_iter().enumerate() { - if let Some(state) = s { - println!("Closing current dir for {idx}..."); - let r = ctx.volume_mgr.close_dir(state.directory); - if let Err(e) = r { - println!("Error closing directory: {e:?}"); - } - println!("Unmounting {idx}..."); - let r = ctx.volume_mgr.close_volume(state.volume); - if let Err(e) = r { - println!("Error closing volume: {e:?}"); - } - } - } - println!("Bye!"); Ok(()) } diff --git a/src/filesystem/directory.rs b/src/filesystem/directory.rs index 744e12d..9286055 100644 --- a/src/filesystem/directory.rs +++ b/src/filesystem/directory.rs @@ -3,7 +3,9 @@ use core::convert::TryFrom; use crate::blockdevice::BlockIdx; use crate::fat::{FatType, OnDiskDirEntry}; use crate::filesystem::{Attributes, ClusterId, SearchId, ShortFileName, Timestamp}; -use crate::Volume; +use crate::{Error, RawVolume, VolumeManager}; + +use super::ToShortFileName; /// Represents a directory entry, which tells you about /// other files and directories. @@ -46,16 +48,160 @@ 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 Directory(pub(crate) SearchId); +pub struct RawDirectory(pub(crate) SearchId); + +impl RawDirectory { + /// Convert a raw file into a droppable [`File`] + pub fn to_directory< + D, + T, + const MAX_DIRS: usize, + const MAX_FILES: usize, + const MAX_VOLUMES: usize, + >( + self, + volume_mgr: &mut VolumeManager, + ) -> Directory + where + D: crate::BlockDevice, + T: crate::TimeSource, + { + Directory::new(self, volume_mgr) + } +} + +/// Represents an open directory on disk. +/// +/// If you drop a value of this type, it closes the directory automatically. However, +/// it holds a mutable reference to its parent `VolumeManager`, which restricts +/// which operations you can perform. +#[cfg_attr(feature = "defmt-log", derive(defmt::Format))] +pub struct Directory< + 'a, + D, + T, + const MAX_DIRS: usize, + const MAX_FILES: usize, + const MAX_VOLUMES: usize, +> where + D: crate::BlockDevice, + T: crate::TimeSource, +{ + raw_directory: RawDirectory, + volume_mgr: &'a mut VolumeManager, +} + +impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> + Directory<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES> +where + D: crate::BlockDevice, + T: crate::TimeSource, +{ + /// Create a new `Directory` from a `RawDirectory` + pub fn new( + raw_directory: RawDirectory, + volume_mgr: &'a mut VolumeManager, + ) -> Directory<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES> { + Directory { + raw_directory, + volume_mgr, + } + } + + /// Open a directory. + /// + /// You can then read the directory entries with `iterate_dir` and `open_file_in_dir`. + pub fn open_dir( + &mut self, + name: N, + ) -> Result, Error> + where + N: ToShortFileName, + { + let d = self.volume_mgr.open_dir(self.raw_directory, name)?; + Ok(d.to_directory(self.volume_mgr)) + } + + /// Look in a directory for a named file. + pub fn find_directory_entry(&mut self, name: N) -> Result> + where + N: ToShortFileName, + { + self.volume_mgr + .find_directory_entry(self.raw_directory, name) + } + + /// Call a callback function for each directory entry in a directory. + pub fn iterate_dir(&mut self, func: F) -> Result<(), Error> + where + F: FnMut(&DirEntry), + { + self.volume_mgr.iterate_dir(self.raw_directory, func) + } + + /// Open a file with the given full path. A file can only be opened once. + pub fn open_file_in_dir( + &mut self, + name: N, + mode: crate::Mode, + ) -> Result, crate::Error> + where + N: super::ToShortFileName, + { + let f = self + .volume_mgr + .open_file_in_dir(self.raw_directory, name, mode)?; + Ok(f.to_file(self.volume_mgr)) + } + + /// Delete a closed file with the given filename, if it exists. + pub fn delete_file_in_dir(&mut self, name: N) -> Result<(), Error> + where + N: ToShortFileName, + { + self.volume_mgr.delete_file_in_dir(self.raw_directory, name) + } + + /// Convert back to a raw directory + pub fn to_raw_directory(self) -> RawDirectory { + let d = self.raw_directory; + core::mem::forget(self); + d + } +} + +impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> Drop + for Directory<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES> +where + D: crate::BlockDevice, + T: crate::TimeSource, +{ + fn drop(&mut self) { + self.volume_mgr + .close_dir(self.raw_directory) + .expect("Failed to close directory"); + } +} + +impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> + core::fmt::Debug for Directory<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES> +where + D: crate::BlockDevice, + T: crate::TimeSource, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Directory({})", self.raw_directory.0 .0) + } +} /// Holds information about an open file on disk #[cfg_attr(feature = "defmt-log", derive(defmt::Format))] #[derive(Debug, Clone)] pub(crate) struct DirectoryInfo { /// Unique ID for this directory. - pub(crate) directory_id: Directory, + pub(crate) directory_id: RawDirectory, /// The unique ID for the volume this directory is on - pub(crate) volume_id: Volume, + pub(crate) volume_id: RawVolume, /// The starting point of the directory listing. pub(crate) cluster: ClusterId, } diff --git a/src/filesystem/files.rs b/src/filesystem/files.rs index 6d66125..cbde1b0 100644 --- a/src/filesystem/files.rs +++ b/src/filesystem/files.rs @@ -1,6 +1,6 @@ use crate::{ filesystem::{ClusterId, DirEntry, SearchId}, - Volume, + RawVolume, VolumeManager, }; /// Represents an open file on disk. @@ -21,26 +21,133 @@ use crate::{ /// reason we did it this way. #[cfg_attr(feature = "defmt-log", derive(defmt::Format))] #[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct File(pub(crate) SearchId); +pub struct RawFile(pub(crate) SearchId); -/// Internal metadata about an open file +impl RawFile { + /// Convert a raw file into a droppable [`File`] + pub fn to_file( + self, + volume_mgr: &mut VolumeManager, + ) -> File + where + D: crate::BlockDevice, + T: crate::TimeSource, + { + File::new(self, volume_mgr) + } +} + +/// Represents an open file on disk. +/// +/// If you drop a value of this type, it closes the file automatically. However, +/// it holds a mutable reference to its parent `VolumeManager`, which restricts +/// which operations you can perform. #[cfg_attr(feature = "defmt-log", derive(defmt::Format))] -#[derive(Debug, Clone)] -pub(crate) struct FileInfo { - /// Unique ID for this file - pub(crate) file_id: File, - /// The unique ID for the volume this directory is on - pub(crate) volume_id: Volume, - /// The current cluster, and how many bytes that short-cuts us - pub(crate) current_cluster: (u32, ClusterId), - /// How far through the file we've read (in bytes). - pub(crate) current_offset: u32, - /// What mode the file was opened in - pub(crate) mode: Mode, - /// DirEntry of this file - pub(crate) entry: DirEntry, - /// Did we write to this file? - pub(crate) dirty: bool, +pub struct File<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> +where + D: crate::BlockDevice, + T: crate::TimeSource, +{ + raw_file: RawFile, + volume_mgr: &'a mut VolumeManager, +} + +impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> + File<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES> +where + D: crate::BlockDevice, + T: crate::TimeSource, +{ + /// Create a new `File` from a `RawFile` + pub fn new( + raw_file: RawFile, + volume_mgr: &'a mut VolumeManager, + ) -> File<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES> { + File { + raw_file, + volume_mgr, + } + } + + /// Read from the file + /// + /// Returns how many bytes were read, or an error. + pub fn read(&mut 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> { + self.volume_mgr.write(self.raw_file, buffer) + } + + /// Check if a file is at End Of File. + pub fn is_eof(&self) -> bool { + self.volume_mgr + .file_eof(self.raw_file) + .expect("Corrupt file ID") + } + + /// Seek a file with an offset from the current position. + pub fn seek_from_current(&mut 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> { + 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> { + self.volume_mgr.file_seek_from_end(self.raw_file, offset) + } + + /// Get the length of a file + pub fn length(&self) -> u32 { + self.volume_mgr + .file_length(self.raw_file) + .expect("Corrupt file ID") + } + + /// Get the current offset of a file + pub fn offset(&self) -> u32 { + self.volume_mgr + .file_offset(self.raw_file) + .expect("Corrupt file ID") + } + + /// Convert back to a raw file + pub fn to_raw_file(self) -> RawFile { + let f = self.raw_file; + core::mem::forget(self); + f + } +} + +impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> Drop + for File<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES> +where + D: crate::BlockDevice, + T: crate::TimeSource, +{ + fn drop(&mut self) { + self.volume_mgr + .close_file(self.raw_file) + .expect("Failed to close file"); + } +} + +impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> + core::fmt::Debug for File<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES> +where + D: crate::BlockDevice, + T: crate::TimeSource, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "File({})", self.raw_file.0 .0) + } } /// Errors related to file operations @@ -69,6 +176,26 @@ pub enum Mode { ReadWriteCreateOrAppend, } +/// Internal metadata about an open file +#[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, + /// The current cluster, and how many bytes that short-cuts us + pub(crate) current_cluster: (u32, ClusterId), + /// How far through the file we've read (in bytes). + pub(crate) current_offset: u32, + /// What mode the file was opened in + pub(crate) mode: Mode, + /// DirEntry of this file + pub(crate) entry: DirEntry, + /// Did we write to this file? + pub(crate) dirty: bool, +} + impl FileInfo { /// Are we at the end of the file? pub fn eof(&self) -> bool { diff --git a/src/filesystem/mod.rs b/src/filesystem/mod.rs index 69076f1..03baa67 100644 --- a/src/filesystem/mod.rs +++ b/src/filesystem/mod.rs @@ -16,9 +16,9 @@ mod timestamp; pub use self::attributes::Attributes; pub use self::cluster::ClusterId; -pub use self::directory::{DirEntry, Directory}; +pub use self::directory::{DirEntry, Directory, RawDirectory}; pub use self::filename::{FilenameError, ShortFileName, ToShortFileName}; -pub use self::files::{File, FileError, Mode}; +pub use self::files::{File, FileError, Mode, RawFile}; pub use self::search_id::{SearchId, SearchIdGenerator}; pub use self::timestamp::{TimeSource, Timestamp}; diff --git a/src/lib.rs b/src/lib.rs index 61070fc..26ad505 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,23 +50,19 @@ //! # let time_source = DummyTimeSource; //! # let delayer = DummyDelayer; //! let sdcard = embedded_sdmmc::SdCard::new(sdmmc_spi, sdmmc_cs, delayer); -//! println!("Card size {} bytes", sdcard.num_bytes()?); -//! let mut volume_mgr = VolumeManager::new(sdcard, time_source); -//! println!("Card size is still {} bytes", volume_mgr.device().num_bytes()?); -//! let volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?; +//! println!("Card size is {} bytes", sdcard.num_bytes()?); +//! let mut volume_mgr = embedded_sdmmc::VolumeManager::new(sdcard, time_source); +//! let mut volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?; //! println!("Volume 0: {:?}", volume0); -//! let root_dir = volume_mgr.open_root_dir(volume0)?; -//! let my_file = volume_mgr.open_file_in_dir( -//! root_dir, "MY_FILE.TXT", embedded_sdmmc::Mode::ReadOnly)?; -//! while !volume_mgr.file_eof(my_file).unwrap() { +//! let mut root_dir = volume0.open_root_dir()?; +//! let mut my_file = root_dir.open_file_in_dir("MY_FILE.TXT", embedded_sdmmc::Mode::ReadOnly)?; +//! while !my_file.is_eof() { //! let mut buffer = [0u8; 32]; -//! let num_read = volume_mgr.read(my_file, &mut buffer)?; +//! let num_read = my_file.read(&mut buffer)?; //! for b in &buffer[0..num_read] { //! print!("{}", *b as char); //! } //! } -//! volume_mgr.close_file(my_file)?; -//! volume_mgr.close_dir(root_dir)?; //! # Ok(()) //! # } //! ``` @@ -111,8 +107,8 @@ pub use crate::fat::FatVolume; #[doc(inline)] pub use crate::filesystem::{ - Attributes, ClusterId, DirEntry, Directory, File, FilenameError, Mode, ShortFileName, - TimeSource, Timestamp, MAX_FILE_SIZE, + Attributes, ClusterId, DirEntry, Directory, File, FilenameError, Mode, RawDirectory, RawFile, + ShortFileName, TimeSource, Timestamp, MAX_FILE_SIZE, }; use filesystem::DirectoryInfo; @@ -224,6 +220,8 @@ where BadBlockSize(u16), /// Bad offset given when seeking InvalidOffset, + /// Disk is full + DiskFull, } impl From for Error @@ -238,14 +236,109 @@ where /// Represents a partition with a filesystem within it. #[cfg_attr(feature = "defmt-log", derive(defmt::Format))] #[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct Volume(SearchId); +pub struct RawVolume(SearchId); + +impl RawVolume { + /// Convert a raw volume into a droppable [`Volume`] + pub fn to_volume< + D, + T, + const MAX_DIRS: usize, + const MAX_FILES: usize, + const MAX_VOLUMES: usize, + >( + self, + volume_mgr: &mut VolumeManager, + ) -> Volume + where + D: crate::BlockDevice, + T: crate::TimeSource, + { + Volume::new(self, volume_mgr) + } +} + +/// Represents an open volume on disk. +/// +/// If you drop a value of this type, it closes the volume automatically. However, +/// it holds a mutable reference to its parent `VolumeManager`, which restricts +/// which operations you can perform. +#[cfg_attr(feature = "defmt-log", derive(defmt::Format))] +pub struct Volume<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> +where + D: crate::BlockDevice, + T: crate::TimeSource, +{ + raw_volume: RawVolume, + volume_mgr: &'a mut VolumeManager, +} + +impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> + Volume<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES> +where + D: crate::BlockDevice, + T: crate::TimeSource, +{ + /// Create a new `Volume` from a `RawVolume` + pub fn new( + raw_volume: RawVolume, + volume_mgr: &'a mut VolumeManager, + ) -> Volume<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES> { + Volume { + raw_volume, + volume_mgr, + } + } + + /// Open the volume's root directory. + /// + /// 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, + ) -> Result, Error> { + let d = self.volume_mgr.open_root_dir(self.raw_volume)?; + Ok(d.to_directory(self.volume_mgr)) + } + + /// Convert back to a raw volume + pub fn to_raw_volume(self) -> RawVolume { + let v = self.raw_volume; + core::mem::forget(self); + v + } +} + +impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> Drop + for Volume<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES> +where + D: crate::BlockDevice, + T: crate::TimeSource, +{ + fn drop(&mut self) { + self.volume_mgr + .close_volume(self.raw_volume) + .expect("Failed to close volume"); + } +} + +impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> + core::fmt::Debug for Volume<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES> +where + D: crate::BlockDevice, + T: crate::TimeSource, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Volume({})", self.raw_volume.0 .0) + } +} /// Internal information about a Volume #[cfg_attr(feature = "defmt-log", derive(defmt::Format))] #[derive(Debug, PartialEq, Eq)] pub(crate) struct VolumeInfo { /// Search ID for this volume. - volume_id: Volume, + volume_id: RawVolume, /// TODO: some kind of index idx: VolumeIdx, /// What kind of volume this is diff --git a/src/volume_mgr.rs b/src/volume_mgr.rs index 5abfcbb..9e95d0f 100644 --- a/src/volume_mgr.rs +++ b/src/volume_mgr.rs @@ -8,12 +8,12 @@ use core::convert::TryFrom; use crate::fat::{self, BlockCache, RESERVED_ENTRIES}; use crate::filesystem::{ - Attributes, ClusterId, DirEntry, Directory, DirectoryInfo, File, FileInfo, Mode, + Attributes, ClusterId, DirEntry, DirectoryInfo, FileInfo, Mode, RawDirectory, RawFile, SearchIdGenerator, TimeSource, ToShortFileName, MAX_FILE_SIZE, }; use crate::{ - debug, Block, BlockCount, BlockDevice, BlockIdx, Error, Volume, VolumeIdx, VolumeInfo, - VolumeType, PARTITION_ID_FAT16, PARTITION_ID_FAT16_LBA, PARTITION_ID_FAT32_CHS_LBA, + debug, Block, BlockCount, BlockDevice, BlockIdx, Error, RawVolume, Volume, VolumeIdx, + VolumeInfo, VolumeType, PARTITION_ID_FAT16, PARTITION_ID_FAT16_LBA, PARTITION_ID_FAT32_CHS_LBA, PARTITION_ID_FAT32_LBA, }; use heapless::Vec; @@ -95,11 +95,26 @@ where &mut self.block_device } - /// Get a volume (or partition) based on entries in the Master Boot - /// Record. 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, volume_idx: VolumeIdx) -> Result> { + /// Get a volume (or partition) based on entries in the Master Boot Record. + /// + /// 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, + volume_idx: VolumeIdx, + ) -> Result, Error> { + let v = self.open_raw_volume(volume_idx)?; + Ok(v.to_volume(self)) + } + + /// Get a volume (or partition) based on entries in the Master Boot Record. + /// + /// 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. + /// + /// 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> { const PARTITION1_START: usize = 446; const PARTITION2_START: usize = PARTITION1_START + PARTITION_INFO_LENGTH; const PARTITION3_START: usize = PARTITION2_START + PARTITION_INFO_LENGTH; @@ -172,7 +187,7 @@ where | PARTITION_ID_FAT16_LBA | PARTITION_ID_FAT16 => { let volume = fat::parse_volume(&self.block_device, lba_start, num_blocks)?; - let id = Volume(self.id_generator.get()); + let id = RawVolume(self.id_generator.get()); let info = VolumeInfo { volume_id: id, idx: volume_idx, @@ -190,14 +205,14 @@ 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: Volume) -> Result> { + pub fn open_root_dir(&mut self, volume: RawVolume) -> Result> { for dir in self.open_dirs.iter() { if dir.cluster == ClusterId::ROOT_DIR && dir.volume_id == volume { return Err(Error::DirAlreadyOpen); } } - let directory_id = Directory(self.id_generator.get()); + let directory_id = RawDirectory(self.id_generator.get()); let dir_info = DirectoryInfo { volume_id: volume, cluster: ClusterId::ROOT_DIR, @@ -214,15 +229,11 @@ where /// Open a directory. /// /// You can then read the directory entries with `iterate_dir` and `open_file_in_dir`. - /// - /// TODO: Work out how to prevent damage occuring to the file system while - /// this directory handle is open. In particular, stop this directory - /// being unlinked. pub fn open_dir( &mut self, - parent_dir: Directory, + parent_dir: RawDirectory, name: N, - ) -> Result> + ) -> Result> where N: ToShortFileName, { @@ -259,7 +270,7 @@ where } // Remember this open directory. - let directory_id = Directory(self.id_generator.get()); + let directory_id = RawDirectory(self.id_generator.get()); let dir_info = DirectoryInfo { directory_id, volume_id: self.open_volumes[volume_idx].volume_id, @@ -275,7 +286,7 @@ 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: Directory) -> Result<(), Error> { + 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); @@ -288,7 +299,7 @@ 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: Volume) -> Result<(), Error> { + pub fn close_volume(&mut self, volume: RawVolume) -> Result<(), Error> { for f in self.open_files.iter() { if f.volume_id == volume { return Err(Error::VolumeStillInUse); @@ -310,7 +321,7 @@ where /// Look in a directory for a named file. pub fn find_directory_entry( &mut self, - directory: Directory, + directory: RawDirectory, name: N, ) -> Result> where @@ -327,7 +338,11 @@ where } /// Call a callback function for each directory entry in a directory. - pub fn iterate_dir(&mut self, directory: Directory, func: F) -> Result<(), Error> + pub fn iterate_dir( + &mut self, + directory: RawDirectory, + func: F, + ) -> Result<(), Error> where F: FnMut(&DirEntry), { @@ -348,10 +363,10 @@ where /// random numbers. unsafe fn open_dir_entry( &mut self, - volume: Volume, + volume: RawVolume, dir_entry: DirEntry, mode: Mode, - ) -> Result> { + ) -> Result> { // This check is load-bearing - we do an unchecked push later. if self.open_files.is_full() { return Err(Error::TooManyOpenFiles); @@ -371,7 +386,7 @@ where } let mode = solve_mode_variant(mode, true); - let file_id = File(self.id_generator.get()); + let file_id = RawFile(self.id_generator.get()); let file = match mode { Mode::ReadOnly => FileInfo { @@ -438,10 +453,10 @@ where /// Open a file with the given full path. A file can only be opened once. pub fn open_file_in_dir( &mut self, - directory: Directory, + directory: RawDirectory, name: N, mode: Mode, - ) -> Result> + ) -> Result> where N: ToShortFileName, { @@ -509,7 +524,7 @@ where )?, }; - let file_id = File(self.id_generator.get()); + let file_id = RawFile(self.id_generator.get()); let file = FileInfo { file_id, @@ -540,7 +555,7 @@ where /// Delete a closed file with the given filename, if it exists. pub fn delete_file_in_dir( &mut self, - directory: Directory, + directory: RawDirectory, name: N, ) -> Result<(), Error> where @@ -576,7 +591,7 @@ where /// Check if a file is open /// /// Returns `true` if it's open, `false`, otherwise. - fn file_is_open(&self, volume: Volume, dir_entry: &DirEntry) -> bool { + 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 @@ -589,7 +604,7 @@ where } /// Read from an open file. - pub fn read(&mut self, file: File, buffer: &mut [u8]) -> Result> { + 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)?; // Calculate which file block the current offset lies within @@ -626,7 +641,7 @@ where } /// Write to a open file. - pub fn write(&mut self, file: File, buffer: &[u8]) -> Result> { + pub fn write(&mut self, file: RawFile, buffer: &[u8]) -> Result<(), Error> { #[cfg(feature = "defmt-log")] debug!("write(file={:?}, buffer={:x}", file, buffer); @@ -700,7 +715,7 @@ where ) .is_err() { - return Ok(written); + return Err(Error::DiskFull); } debug!("Allocated new FAT cluster, finding offsets..."); let new_offset = self @@ -748,11 +763,11 @@ where } self.open_files[file_idx].entry.attributes.set_archive(true); self.open_files[file_idx].entry.mtime = self.time_source.get_timestamp(); - Ok(written) + Ok(()) } /// Close a file with the given full path. - pub fn close_file(&mut self, file: File) -> Result<(), Error> { + pub fn close_file(&mut self, file: RawFile) -> Result<(), Error> { let mut found_idx = None; for (idx, info) in self.open_files.iter().enumerate() { if file == info.file_id { @@ -795,13 +810,17 @@ where } /// Check if a file is at End Of File. - pub fn file_eof(&self, file: File) -> Result> { + pub fn file_eof(&self, file: RawFile) -> Result> { let file_idx = self.get_file_by_id(file)?; Ok(self.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: File, offset: u32) -> Result<(), Error> { + 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] .seek_from_start(offset) @@ -812,7 +831,7 @@ where /// Seek a file with an offset from the current position. pub fn file_seek_from_current( &mut self, - file: File, + file: RawFile, offset: i32, ) -> Result<(), Error> { let file_idx = self.get_file_by_id(file)?; @@ -823,7 +842,11 @@ where } /// Seek a file with an offset back from the end of the file. - pub fn file_seek_from_end(&mut self, file: File, offset: u32) -> Result<(), Error> { + 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] .seek_from_end(offset) @@ -832,18 +855,18 @@ where } /// Get the length of a file - pub fn file_length(&self, file: File) -> Result> { + pub fn file_length(&self, file: RawFile) -> Result> { let file_idx = self.get_file_by_id(file)?; Ok(self.open_files[file_idx].length()) } /// Get the current offset of a file - pub fn file_offset(&self, file: File) -> Result> { + 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) } - fn get_volume_by_id(&self, volume: Volume) -> Result> { + fn get_volume_by_id(&self, volume: RawVolume) -> Result> { for (idx, v) in self.open_volumes.iter().enumerate() { if v.volume_id == volume { return Ok(idx); @@ -852,7 +875,7 @@ where Err(Error::BadHandle) } - fn get_dir_by_id(&self, directory: Directory) -> Result> { + fn get_dir_by_id(&self, directory: RawDirectory) -> Result> { for (idx, d) in self.open_dirs.iter().enumerate() { if d.directory_id == directory { return Ok(idx); @@ -861,7 +884,7 @@ where Err(Error::BadHandle) } - fn get_file_by_id(&self, file: File) -> Result> { + fn get_file_by_id(&self, file: RawFile) -> Result> { for (idx, f) in self.open_files.iter().enumerate() { if f.file_id == file { return Ok(idx); @@ -1200,8 +1223,8 @@ mod tests { let mut c: VolumeManager = VolumeManager::new_with_limits(DummyBlockDevice, Clock, 0xAA00_0000); - let v = c.open_volume(VolumeIdx(0)).unwrap(); - let expected_id = Volume(SearchId(0xAA00_0000)); + let v = c.open_raw_volume(VolumeIdx(0)).unwrap(); + let expected_id = RawVolume(SearchId(0xAA00_0000)); assert_eq!(v, expected_id); assert_eq!( &c.open_volumes[0], diff --git a/tests/directories.rs b/tests/directories.rs index 12abb85..fee08f7 100644 --- a/tests/directories.rs +++ b/tests/directories.rs @@ -1,6 +1,6 @@ //! Directory related tests -use embedded_sdmmc::ShortFileName; +use embedded_sdmmc::{Mode, ShortFileName}; mod utils; @@ -41,7 +41,7 @@ fn fat16_root_directory_listing() { let mut volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); let fat16_volume = volume_mgr - .open_volume(embedded_sdmmc::VolumeIdx(0)) + .open_raw_volume(embedded_sdmmc::VolumeIdx(0)) .expect("open volume 0"); let root_dir = volume_mgr .open_root_dir(fat16_volume) @@ -103,7 +103,7 @@ fn fat16_sub_directory_listing() { let mut volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); let fat16_volume = volume_mgr - .open_volume(embedded_sdmmc::VolumeIdx(0)) + .open_raw_volume(embedded_sdmmc::VolumeIdx(0)) .expect("open volume 0"); let root_dir = volume_mgr .open_root_dir(fat16_volume) @@ -168,7 +168,7 @@ fn fat32_root_directory_listing() { let mut volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); let fat32_volume = volume_mgr - .open_volume(embedded_sdmmc::VolumeIdx(1)) + .open_raw_volume(embedded_sdmmc::VolumeIdx(1)) .expect("open volume 1"); let root_dir = volume_mgr .open_root_dir(fat32_volume) @@ -230,39 +230,39 @@ fn open_dir_twice() { let mut volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); let fat32_volume = volume_mgr - .open_volume(embedded_sdmmc::VolumeIdx(1)) + .open_raw_volume(embedded_sdmmc::VolumeIdx(1)) .expect("open volume 1"); let root_dir = volume_mgr .open_root_dir(fat32_volume) .expect("open root dir"); - let r = volume_mgr.open_root_dir(fat32_volume); - let Err(embedded_sdmmc::Error::DirAlreadyOpen) = r else { - panic!("Expected to fail opening the root dir twice: {r:?}"); - }; + assert!(matches!( + volume_mgr.open_root_dir(fat32_volume), + Err(embedded_sdmmc::Error::DirAlreadyOpen) + )); - let r = volume_mgr.open_dir(root_dir, "README.TXT"); - let Err(embedded_sdmmc::Error::OpenedFileAsDir) = r else { - panic!("Expected to fail opening file as dir: {r:?}"); - }; + assert!(matches!( + volume_mgr.open_dir(root_dir, "README.TXT"), + Err(embedded_sdmmc::Error::OpenedFileAsDir) + )); let test_dir = volume_mgr .open_dir(root_dir, "TEST") .expect("open test dir"); - let r = volume_mgr.open_dir(root_dir, "TEST"); - let Err(embedded_sdmmc::Error::DirAlreadyOpen) = r else { - panic!("Expected to fail opening the dir twice: {r:?}"); - }; + assert!(matches!( + volume_mgr.open_dir(root_dir, "TEST"), + Err(embedded_sdmmc::Error::DirAlreadyOpen) + )); volume_mgr.close_dir(root_dir).expect("close root dir"); volume_mgr.close_dir(test_dir).expect("close test dir"); - let r = volume_mgr.close_dir(test_dir); - let Err(embedded_sdmmc::Error::BadHandle) = r else { - panic!("Expected to fail closing the dir twice: {r:?}"); - }; + assert!(matches!( + volume_mgr.close_dir(test_dir), + Err(embedded_sdmmc::Error::BadHandle) + )); } #[test] @@ -278,15 +278,16 @@ fn open_too_many_dirs() { > = embedded_sdmmc::VolumeManager::new_with_limits(disk, time_source, 0x1000_0000); let fat32_volume = volume_mgr - .open_volume(embedded_sdmmc::VolumeIdx(1)) + .open_raw_volume(embedded_sdmmc::VolumeIdx(1)) .expect("open volume 1"); let root_dir = volume_mgr .open_root_dir(fat32_volume) .expect("open root dir"); - let Err(embedded_sdmmc::Error::TooManyOpenDirs) = volume_mgr.open_dir(root_dir, "TEST") else { - panic!("Expected to fail at opening too many dirs"); - }; + assert!(matches!( + volume_mgr.open_dir(root_dir, "TEST"), + Err(embedded_sdmmc::Error::TooManyOpenDirs) + )); } #[test] @@ -296,7 +297,7 @@ fn find_dir_entry() { let mut volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); let fat32_volume = volume_mgr - .open_volume(embedded_sdmmc::VolumeIdx(1)) + .open_raw_volume(embedded_sdmmc::VolumeIdx(1)) .expect("open volume 1"); let root_dir = volume_mgr @@ -313,10 +314,55 @@ fn find_dir_entry() { assert!(!dir_entry.attributes.is_system()); assert!(!dir_entry.attributes.is_volume()); - let r = volume_mgr.find_directory_entry(root_dir, "README.TXS"); - let Err(embedded_sdmmc::Error::FileNotFound) = r else { - panic!("Expected not to find file: {r:?}"); - }; + assert!(matches!( + volume_mgr.find_directory_entry(root_dir, "README.TXS"), + Err(embedded_sdmmc::Error::FileNotFound) + )); +} + +#[test] +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 fat32_volume = volume_mgr + .open_raw_volume(embedded_sdmmc::VolumeIdx(1)) + .expect("open volume 1"); + + let root_dir = volume_mgr + .open_root_dir(fat32_volume) + .expect("open root dir"); + + let file = volume_mgr + .open_file_in_dir(root_dir, "README.TXT", Mode::ReadOnly) + .unwrap(); + + assert!(matches!( + volume_mgr.delete_file_in_dir(root_dir, "README.TXT"), + Err(embedded_sdmmc::Error::FileAlreadyOpen) + )); + + assert!(matches!( + volume_mgr.delete_file_in_dir(root_dir, "README2.TXT"), + Err(embedded_sdmmc::Error::FileNotFound) + )); + + volume_mgr.close_file(file).unwrap(); + + volume_mgr + .delete_file_in_dir(root_dir, "README.TXT") + .unwrap(); + + assert!(matches!( + volume_mgr.delete_file_in_dir(root_dir, "README.TXT"), + Err(embedded_sdmmc::Error::FileNotFound) + )); + + assert!(matches!( + volume_mgr.open_file_in_dir(root_dir, "README.TXT", Mode::ReadOnly), + Err(embedded_sdmmc::Error::FileNotFound) + )); } // **************************************************************************** diff --git a/tests/open_files.rs b/tests/open_files.rs index 0e07d0c..9608a15 100644 --- a/tests/open_files.rs +++ b/tests/open_files.rs @@ -10,7 +10,9 @@ fn open_files() { let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap(); let mut volume_mgr: VolumeManager>, utils::TestTimeSource, 4, 2, 1> = VolumeManager::new_with_limits(disk, time_source, 0xAA00_0000); - let volume = volume_mgr.open_volume(VolumeIdx(0)).expect("open volume"); + let volume = volume_mgr + .open_raw_volume(VolumeIdx(0)) + .expect("open volume"); let root_dir = volume_mgr.open_root_dir(volume).expect("open root dir"); // Open with string @@ -18,10 +20,10 @@ fn open_files() { .open_file_in_dir(root_dir, "README.TXT", Mode::ReadWriteTruncate) .expect("open file"); - let r = volume_mgr.open_file_in_dir(root_dir, "README.TXT", Mode::ReadOnly); - let Err(Error::FileAlreadyOpen) = r else { - panic!("Expected to not open file twice: {r:?}"); - }; + assert!(matches!( + volume_mgr.open_file_in_dir(root_dir, "README.TXT", Mode::ReadOnly), + Err(Error::FileAlreadyOpen) + )); volume_mgr.close_file(f).expect("close file"); @@ -35,35 +37,38 @@ fn open_files() { .open_file_in_dir(root_dir, &dir_entry.name, Mode::ReadWriteCreateOrAppend) .expect("open file with dir entry"); - let r = volume_mgr.open_file_in_dir(root_dir, &dir_entry.name, Mode::ReadOnly); - let Err(Error::FileAlreadyOpen) = r else { - panic!("Expected to not open file twice: {r:?}"); - }; + assert!(matches!( + volume_mgr.open_file_in_dir(root_dir, &dir_entry.name, Mode::ReadOnly), + Err(Error::FileAlreadyOpen) + )); // Can still spot duplicates even if name given the other way around - let r = volume_mgr.open_file_in_dir(root_dir, "README.TXT", Mode::ReadOnly); - let Err(Error::FileAlreadyOpen) = r else { - panic!("Expected to not open file twice: {r:?}"); - }; + + assert!(matches!( + volume_mgr.open_file_in_dir(root_dir, "README.TXT", Mode::ReadOnly), + Err(Error::FileAlreadyOpen) + )); let f2 = volume_mgr .open_file_in_dir(root_dir, "64MB.DAT", Mode::ReadWriteTruncate) .expect("open file"); // Hit file limit - let r = volume_mgr.open_file_in_dir(root_dir, "EMPTY.DAT", Mode::ReadOnly); - let Err(Error::TooManyOpenFiles) = r else { - panic!("Expected to run out of file handles: {r:?}"); - }; + + assert!(matches!( + volume_mgr.open_file_in_dir(root_dir, "EMPTY.DAT", Mode::ReadOnly), + Err(Error::TooManyOpenFiles) + )); volume_mgr.close_file(f).expect("close file"); volume_mgr.close_file(f2).expect("close file"); // File not found - let r = volume_mgr.open_file_in_dir(root_dir, "README.TXS", Mode::ReadOnly); - let Err(Error::FileNotFound) = r else { - panic!("Expected to not open missing file: {r:?}"); - }; + + assert!(matches!( + volume_mgr.open_file_in_dir(root_dir, "README.TXS", Mode::ReadOnly), + Err(Error::FileNotFound) + )); // Create a new file let f3 = volume_mgr @@ -86,6 +91,53 @@ fn open_files() { volume_mgr.close_volume(volume).expect("close volume"); } +#[test] +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> = + 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 + .open_file_in_dir("README.TXT", Mode::ReadOnly) + .expect("open file"); + + let mut buffer = [0u8; 512]; + let len = f.read(&mut buffer).expect("read from file"); + // See directory listing in utils.rs, to see that README.TXT is 258 bytes long + assert_eq!(len, 258); + assert_eq!(f.length(), 258); + f.seek_from_current(0).unwrap(); + assert_eq!(f.is_eof(), true); + assert_eq!(f.offset(), 258); + assert!(matches!(f.seek_from_current(1), Err(Error::InvalidOffset))); + f.seek_from_current(-258).unwrap(); + assert_eq!(f.is_eof(), false); + assert_eq!(f.offset(), 0); + f.seek_from_current(10).unwrap(); + assert_eq!(f.is_eof(), false); + assert_eq!(f.offset(), 10); + f.seek_from_end(0).unwrap(); + assert_eq!(f.is_eof(), true); + assert_eq!(f.offset(), 258); + assert!(matches!( + f.seek_from_current(-259), + Err(Error::InvalidOffset) + )); + f.seek_from_start(25).unwrap(); + assert_eq!(f.is_eof(), false); + assert_eq!(f.offset(), 25); + + drop(f); + + let Err(Error::FileAlreadyExists) = + root_dir.open_file_in_dir("README.TXT", Mode::ReadWriteCreate) + else { + panic!("Expected to file to exist"); + }; +} + // **************************************************************************** // // End Of File diff --git a/tests/read_file.rs b/tests/read_file.rs index 0a6bffb..91121b3 100644 --- a/tests/read_file.rs +++ b/tests/read_file.rs @@ -12,7 +12,7 @@ fn read_file_512_blocks() { let mut volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); let fat16_volume = volume_mgr - .open_volume(embedded_sdmmc::VolumeIdx(0)) + .open_raw_volume(embedded_sdmmc::VolumeIdx(0)) .expect("open volume 0"); let root_dir = volume_mgr .open_root_dir(fat16_volume) @@ -52,7 +52,7 @@ fn read_file_all() { let mut volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); let fat16_volume = volume_mgr - .open_volume(embedded_sdmmc::VolumeIdx(0)) + .open_raw_volume(embedded_sdmmc::VolumeIdx(0)) .expect("open volume 0"); let root_dir = volume_mgr .open_root_dir(fat16_volume) @@ -84,7 +84,7 @@ fn read_file_prime_blocks() { let mut volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); let fat16_volume = volume_mgr - .open_volume(embedded_sdmmc::VolumeIdx(0)) + .open_raw_volume(embedded_sdmmc::VolumeIdx(0)) .expect("open volume 0"); let root_dir = volume_mgr .open_root_dir(fat16_volume) @@ -125,7 +125,7 @@ fn read_file_backwards() { let mut volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); let fat16_volume = volume_mgr - .open_volume(embedded_sdmmc::VolumeIdx(0)) + .open_raw_volume(embedded_sdmmc::VolumeIdx(0)) .expect("open volume 0"); let root_dir = volume_mgr .open_root_dir(fat16_volume) diff --git a/tests/volume.rs b/tests/volume.rs index a1a0455..633a8d2 100644 --- a/tests/volume.rs +++ b/tests/volume.rs @@ -16,60 +16,60 @@ fn open_all_volumes() { // Open Volume 0 let fat16_volume = volume_mgr - .open_volume(embedded_sdmmc::VolumeIdx(0)) + .open_raw_volume(embedded_sdmmc::VolumeIdx(0)) .expect("open volume 0"); // Fail to Open Volume 0 again - let r = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0)); - let Err(embedded_sdmmc::Error::VolumeAlreadyOpen) = r else { - panic!("Should have failed to open volume {:?}", r); - }; + assert!(matches!( + volume_mgr.open_raw_volume(embedded_sdmmc::VolumeIdx(0)), + Err(embedded_sdmmc::Error::VolumeAlreadyOpen) + )); volume_mgr.close_volume(fat16_volume).expect("close fat16"); // Open Volume 1 let fat32_volume = volume_mgr - .open_volume(embedded_sdmmc::VolumeIdx(1)) + .open_raw_volume(embedded_sdmmc::VolumeIdx(1)) .expect("open volume 1"); // Fail to Volume 1 again - let r = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(1)); - let Err(embedded_sdmmc::Error::VolumeAlreadyOpen) = r else { - panic!("Should have failed to open volume {:?}", r); - }; + assert!(matches!( + volume_mgr.open_raw_volume(embedded_sdmmc::VolumeIdx(1)), + Err(embedded_sdmmc::Error::VolumeAlreadyOpen) + )); // Open Volume 0 again let fat16_volume = volume_mgr - .open_volume(embedded_sdmmc::VolumeIdx(0)) + .open_raw_volume(embedded_sdmmc::VolumeIdx(0)) .expect("open volume 0"); // Open any volume - too many volumes (0 and 1 are open) - let r = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0)); - let Err(embedded_sdmmc::Error::TooManyOpenVolumes) = r else { - panic!("Should have failed to open volume {:?}", r); - }; + assert!(matches!( + volume_mgr.open_raw_volume(embedded_sdmmc::VolumeIdx(0)), + Err(embedded_sdmmc::Error::TooManyOpenVolumes) + )); volume_mgr.close_volume(fat16_volume).expect("close fat16"); volume_mgr.close_volume(fat32_volume).expect("close fat32"); // This isn't a valid volume - let r = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(2)); - let Err(embedded_sdmmc::Error::FormatError(_e)) = r else { - panic!("Should have failed to open volume {:?}", r); - }; + assert!(matches!( + volume_mgr.open_raw_volume(embedded_sdmmc::VolumeIdx(2)), + Err(embedded_sdmmc::Error::FormatError(_e)) + )); // This isn't a valid volume - let r = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(9)); - let Err(embedded_sdmmc::Error::NoSuchVolume) = r else { - panic!("Should have failed to open volume {:?}", r); - }; + assert!(matches!( + volume_mgr.open_raw_volume(embedded_sdmmc::VolumeIdx(9)), + Err(embedded_sdmmc::Error::NoSuchVolume) + )); let _root_dir = volume_mgr.open_root_dir(fat32_volume).expect("Open dir"); - let r = volume_mgr.close_volume(fat32_volume); - let Err(embedded_sdmmc::Error::VolumeStillInUse) = r else { - panic!("Should have failed to close volume {:?}", r); - }; + assert!(matches!( + volume_mgr.close_volume(fat32_volume), + Err(embedded_sdmmc::Error::VolumeStillInUse) + )); } #[test] @@ -79,15 +79,15 @@ fn close_volume_too_early() { let mut volume_mgr = embedded_sdmmc::VolumeManager::new(disk, time_source); let volume = volume_mgr - .open_volume(embedded_sdmmc::VolumeIdx(0)) + .open_raw_volume(embedded_sdmmc::VolumeIdx(0)) .expect("open volume 0"); let root_dir = volume_mgr.open_root_dir(volume).expect("open root dir"); // Dir open - let r = volume_mgr.close_volume(volume); - let Err(embedded_sdmmc::Error::VolumeStillInUse) = r else { - panic!("Expected failure to close volume: {r:?}"); - }; + assert!(matches!( + volume_mgr.close_volume(volume), + Err(embedded_sdmmc::Error::VolumeStillInUse) + )); let _test_file = volume_mgr .open_file_in_dir(root_dir, "64MB.DAT", embedded_sdmmc::Mode::ReadOnly) @@ -96,10 +96,10 @@ fn close_volume_too_early() { volume_mgr.close_dir(root_dir).unwrap(); // File open, not dir open - let r = volume_mgr.close_volume(volume); - let Err(embedded_sdmmc::Error::VolumeStillInUse) = r else { - panic!("Expected failure to close volume: {r:?}"); - }; + assert!(matches!( + volume_mgr.close_volume(volume), + Err(embedded_sdmmc::Error::VolumeStillInUse) + )); } // **************************************************************************** diff --git a/tests/write_file.rs b/tests/write_file.rs index 0594509..5fc91c6 100644 --- a/tests/write_file.rs +++ b/tests/write_file.rs @@ -10,7 +10,9 @@ fn append_file() { let disk = utils::make_block_device(utils::DISK_SOURCE).unwrap(); let mut volume_mgr: VolumeManager>, utils::TestTimeSource, 4, 2, 1> = VolumeManager::new_with_limits(disk, time_source, 0xAA00_0000); - let volume = volume_mgr.open_volume(VolumeIdx(0)).expect("open volume"); + let volume = volume_mgr + .open_raw_volume(VolumeIdx(0)) + .expect("open volume"); let root_dir = volume_mgr.open_root_dir(volume).expect("open root dir"); // Open with string From 06af669af89c32ac6cde64856cb22d7bef45612c Mon Sep 17 00:00:00 2001 From: Jonathan Pallant Date: Sat, 7 Oct 2023 20:45:19 +0100 Subject: [PATCH 02/36] Switch from sha256 to sha2. Brings in fewer dependencies. --- Cargo.toml | 2 +- tests/read_file.rs | 30 ++++++++++++++++++++---------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8bd40d4..ee15e12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ log = {version = "0.4", default-features = false, optional = true} env_logger = "0.9" hex-literal = "0.3" flate2 = "1.0" -sha256 = "1.4" +sha2 = "0.10" chrono = "0.4" [features] diff --git a/tests/read_file.rs b/tests/read_file.rs index 91121b3..b0e7628 100644 --- a/tests/read_file.rs +++ b/tests/read_file.rs @@ -1,9 +1,11 @@ //! Reading related tests +use sha2::Digest; + mod utils; -static TEST_DAT_SHA256_SUM: &str = - "59e3468e3bef8bfe37e60a8221a1896e105b80a61a23637612ac8cd24ca04a75"; +static TEST_DAT_SHA256_SUM: &[u8] = + b"\x59\xe3\x46\x8e\x3b\xef\x8b\xfe\x37\xe6\x0a\x82\x21\xa1\x89\x6e\x10\x5b\x80\xa6\x1a\x23\x63\x76\x12\xac\x8c\xd2\x4c\xa0\x4a\x75"; #[test] fn read_file_512_blocks() { @@ -41,8 +43,10 @@ fn read_file_512_blocks() { contents.extend(&buffer[0..len]); } - let hash = sha256::digest(contents); - assert_eq!(&hash, TEST_DAT_SHA256_SUM); + let mut hasher = sha2::Sha256::new(); + hasher.update(contents); + let hash = hasher.finalize(); + assert_eq!(&hash[..], TEST_DAT_SHA256_SUM); } #[test] @@ -73,8 +77,10 @@ fn read_file_all() { panic!("Failed to read all of TEST.DAT"); } - let hash = sha256::digest(&contents[0..3500]); - assert_eq!(&hash, TEST_DAT_SHA256_SUM); + let mut hasher = sha2::Sha256::new(); + hasher.update(&contents[0..3500]); + let hash = hasher.finalize(); + assert_eq!(&hash[..], TEST_DAT_SHA256_SUM); } #[test] @@ -114,8 +120,10 @@ fn read_file_prime_blocks() { contents.extend(&buffer[0..len]); } - let hash = sha256::digest(contents); - assert_eq!(&hash, TEST_DAT_SHA256_SUM); + let mut hasher = sha2::Sha256::new(); + hasher.update(&contents[0..3500]); + let hash = hasher.finalize(); + assert_eq!(&hash[..], TEST_DAT_SHA256_SUM); } #[test] @@ -166,8 +174,10 @@ fn read_file_backwards() { let flat: Vec = contents.iter().flatten().copied().collect(); - let hash = sha256::digest(flat); - assert_eq!(&hash, TEST_DAT_SHA256_SUM); + let mut hasher = sha2::Sha256::new(); + hasher.update(flat); + let hash = hasher.finalize(); + assert_eq!(&hash[..], TEST_DAT_SHA256_SUM); } // **************************************************************************** From 6c3fa0a7c13f72c62cf5747b590cdfb7b88c1766 Mon Sep 17 00:00:00 2001 From: Jonathan Pallant Date: Sat, 7 Oct 2023 21:15:25 +0100 Subject: [PATCH 03/36] Fix defmt logging. --- Cargo.toml | 3 ++- src/filesystem/directory.rs | 13 ++++++++++++- src/filesystem/files.rs | 13 ++++++++++++- src/lib.rs | 13 ++++++++++++- 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ee15e12..e94d79e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,4 +26,5 @@ chrono = "0.4" [features] default = ["log"] -defmt-log = ["defmt"] +defmt-log = ["dep:defmt"] +log = ["dep:log"] diff --git a/src/filesystem/directory.rs b/src/filesystem/directory.rs index 9286055..b271471 100644 --- a/src/filesystem/directory.rs +++ b/src/filesystem/directory.rs @@ -75,7 +75,6 @@ impl RawDirectory { /// If you drop a value of this type, it closes the directory automatically. However, /// it holds a mutable reference to its parent `VolumeManager`, which restricts /// which operations you can perform. -#[cfg_attr(feature = "defmt-log", derive(defmt::Format))] pub struct Directory< 'a, D, @@ -194,6 +193,18 @@ where } } +#[cfg(feature = "defmt-log")] +impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> + defmt::Format for Directory<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES> +where + D: crate::BlockDevice, + T: crate::TimeSource, +{ + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "Directory({})", self.raw_directory.0 .0) + } +} + /// Holds information about an open file on disk #[cfg_attr(feature = "defmt-log", derive(defmt::Format))] #[derive(Debug, Clone)] diff --git a/src/filesystem/files.rs b/src/filesystem/files.rs index cbde1b0..31fc966 100644 --- a/src/filesystem/files.rs +++ b/src/filesystem/files.rs @@ -42,7 +42,6 @@ impl RawFile { /// If you drop a value of this type, it closes the file automatically. However, /// it holds a mutable reference to its parent `VolumeManager`, which restricts /// which operations you can perform. -#[cfg_attr(feature = "defmt-log", derive(defmt::Format))] pub struct File<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> where D: crate::BlockDevice, @@ -150,6 +149,18 @@ where } } +#[cfg(feature = "defmt-log")] +impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> + defmt::Format for File<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES> +where + D: crate::BlockDevice, + T: crate::TimeSource, +{ + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "File({})", self.raw_file.0 .0) + } +} + /// Errors related to file operations #[cfg_attr(feature = "defmt-log", derive(defmt::Format))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/src/lib.rs b/src/lib.rs index 26ad505..7aa9622 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -263,7 +263,6 @@ impl RawVolume { /// If you drop a value of this type, it closes the volume automatically. However, /// it holds a mutable reference to its parent `VolumeManager`, which restricts /// which operations you can perform. -#[cfg_attr(feature = "defmt-log", derive(defmt::Format))] pub struct Volume<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> where D: crate::BlockDevice, @@ -333,6 +332,18 @@ where } } +#[cfg(feature = "defmt-log")] +impl<'a, D, T, const MAX_DIRS: usize, const MAX_FILES: usize, const MAX_VOLUMES: usize> + defmt::Format for Volume<'a, D, T, MAX_DIRS, MAX_FILES, MAX_VOLUMES> +where + D: crate::BlockDevice, + T: crate::TimeSource, +{ + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "Volume({})", self.raw_volume.0 .0) + } +} + /// Internal information about a Volume #[cfg_attr(feature = "defmt-log", derive(defmt::Format))] #[derive(Debug, PartialEq, Eq)] From d18fefab006d0ed641fb2c4c669a813c9d4382d2 Mon Sep 17 00:00:00 2001 From: Simon Struck Date: Mon, 23 Oct 2023 00:12:06 +0200 Subject: [PATCH 04/36] Update embedded hal --- Cargo.toml | 6 ++--- examples/readme_test.rs | 8 +++---- src/lib.rs | 8 +++---- src/sdcard/mod.rs | 53 +++++++++++++++++------------------------ 4 files changed, 33 insertions(+), 42 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e94d79e..a58d83f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,13 +13,13 @@ version = "0.6.0" [dependencies] byteorder = {version = "1", default-features = false} defmt = {version = "0.3", optional = true} -embedded-hal = "0.2.3" +embedded-hal = "1.0.0-rc.1" heapless = "0.7" log = {version = "0.4", default-features = false, optional = true} [dev-dependencies] -env_logger = "0.9" -hex-literal = "0.3" +env_logger = "0.10.0" +hex-literal = "0.4.1" flate2 = "1.0" sha2 = "0.10" chrono = "0.4" diff --git a/examples/readme_test.rs b/examples/readme_test.rs index 9fffca1..6ddf58f 100644 --- a/examples/readme_test.rs +++ b/examples/readme_test.rs @@ -5,14 +5,14 @@ struct FakeSpi(); -impl embedded_hal::blocking::spi::Transfer for FakeSpi { +impl embedded_hal::spi::SpiDevice for FakeSpi { type Error = core::convert::Infallible; fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { Ok(words) } } -impl embedded_hal::blocking::spi::Write for FakeSpi { +impl embedded_hal::spi::SpiDevice for FakeSpi { type Error = core::convert::Infallible; fn write(&mut self, _words: &[u8]) -> Result<(), Self::Error> { Ok(()) @@ -21,7 +21,7 @@ impl embedded_hal::blocking::spi::Write for FakeSpi { struct FakeCs(); -impl embedded_hal::digital::v2::OutputPin for FakeCs { +impl embedded_hal::digital::OutputPin for FakeCs { type Error = core::convert::Infallible; fn set_low(&mut self) -> Result<(), Self::Error> { Ok(()) @@ -34,7 +34,7 @@ impl embedded_hal::digital::v2::OutputPin for FakeCs { struct FakeDelayer(); -impl embedded_hal::blocking::delay::DelayUs for FakeDelayer { +impl embedded_hal::delay::DelayUs for FakeDelayer { fn delay_us(&mut self, us: u8) { std::thread::sleep(std::time::Duration::from_micros(u64::from(us))); } diff --git a/src/lib.rs b/src/lib.rs index 7aa9622..6c0225f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,15 +22,15 @@ //! # struct DummyUart; //! # struct DummyTimeSource; //! # struct DummyDelayer; -//! # impl embedded_hal::blocking::spi::Transfer for DummySpi { +//! # impl embedded_hal::spi::SpiDevice for DummySpi { //! # type Error = (); //! # fn transfer<'w>(&mut self, data: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { Ok(&[0]) } //! # } -//! # impl embedded_hal::blocking::spi::Write for DummySpi { +//! # impl embedded_hal::spi::SpiDevice for DummySpi { //! # type Error = (); //! # fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> { Ok(()) } //! # } -//! # impl embedded_hal::digital::v2::OutputPin for DummyCsPin { +//! # impl embedded_hal::digital::OutputPin for DummyCsPin { //! # type Error = (); //! # fn set_low(&mut self) -> Result<(), ()> { Ok(()) } //! # fn set_high(&mut self) -> Result<(), ()> { Ok(()) } @@ -38,7 +38,7 @@ //! # impl embedded_sdmmc::TimeSource for DummyTimeSource { //! # fn get_timestamp(&self) -> embedded_sdmmc::Timestamp { embedded_sdmmc::Timestamp::from_fat(0, 0) } //! # } -//! # impl embedded_hal::blocking::delay::DelayUs for DummyDelayer { +//! # impl embedded_hal::delay::DelayUs for DummyDelayer { //! # fn delay_us(&mut self, us: u8) {} //! # } //! # impl std::fmt::Write for DummyUart { fn write_str(&mut self, s: &str) -> std::fmt::Result { Ok(()) } } diff --git a/src/sdcard/mod.rs b/src/sdcard/mod.rs index 517e19c..a678d68 100644 --- a/src/sdcard/mod.rs +++ b/src/sdcard/mod.rs @@ -30,22 +30,18 @@ use crate::{debug, warn}; /// All the APIs take `&self` - mutability is handled using an inner `RefCell`. pub struct SdCard where - SPI: embedded_hal::blocking::spi::Transfer + embedded_hal::blocking::spi::Write, - CS: embedded_hal::digital::v2::OutputPin, - >::Error: core::fmt::Debug, - >::Error: core::fmt::Debug, - DELAYER: embedded_hal::blocking::delay::DelayUs, + SPI: embedded_hal::spi::SpiDevice, + CS: embedded_hal::digital::OutputPin, + DELAYER: embedded_hal::delay::DelayUs, { inner: RefCell>, } impl SdCard where - SPI: embedded_hal::blocking::spi::Transfer + embedded_hal::blocking::spi::Write, - CS: embedded_hal::digital::v2::OutputPin, - >::Error: core::fmt::Debug, - >::Error: core::fmt::Debug, - DELAYER: embedded_hal::blocking::delay::DelayUs, + SPI: embedded_hal::spi::SpiDevice + embedded_hal::spi::SpiDevice, + CS: embedded_hal::digital::OutputPin, + DELAYER: embedded_hal::delay::DelayUs, { /// Create a new SD/MMC Card driver using a raw SPI interface. /// @@ -152,11 +148,9 @@ where impl BlockDevice for SdCard where - SPI: embedded_hal::blocking::spi::Transfer + embedded_hal::blocking::spi::Write, - CS: embedded_hal::digital::v2::OutputPin, - >::Error: core::fmt::Debug, - >::Error: core::fmt::Debug, - DELAYER: embedded_hal::blocking::delay::DelayUs, + SPI: embedded_hal::spi::SpiDevice + embedded_hal::spi::SpiDevice, + CS: embedded_hal::digital::OutputPin, + DELAYER: embedded_hal::delay::DelayUs, { type Error = Error; @@ -205,11 +199,9 @@ where /// All the APIs required `&mut self`. struct SdCardInner where - SPI: embedded_hal::blocking::spi::Transfer + embedded_hal::blocking::spi::Write, - CS: embedded_hal::digital::v2::OutputPin, - >::Error: core::fmt::Debug, - >::Error: core::fmt::Debug, - DELAYER: embedded_hal::blocking::delay::DelayUs, + SPI: embedded_hal::spi::SpiDevice + embedded_hal::spi::SpiDevice, + CS: embedded_hal::digital::OutputPin, + DELAYER: embedded_hal::delay::DelayUs, { spi: SPI, cs: CS, @@ -220,11 +212,9 @@ where impl SdCardInner where - SPI: embedded_hal::blocking::spi::Transfer + embedded_hal::blocking::spi::Write, - CS: embedded_hal::digital::v2::OutputPin, - >::Error: core::fmt::Debug, - >::Error: core::fmt::Debug, - DELAYER: embedded_hal::blocking::delay::DelayUs, + SPI: embedded_hal::spi::SpiDevice + embedded_hal::spi::SpiDevice, + CS: embedded_hal::digital::OutputPin, + DELAYER: embedded_hal::delay::DelayUs, { /// Read one or more blocks, starting at the given block index. fn read(&mut self, blocks: &mut [Block], start_block_idx: BlockIdx) -> Result<(), Error> { @@ -583,13 +573,14 @@ where /// Send one byte and receive one byte over the SPI bus. fn transfer_byte(&mut self, out: u8) -> Result { + let mut read_buf = [0u8;1]; self.spi - .transfer(&mut [out]) - .map(|b| b[0]) - .map_err(|_e| Error::Transport) + .transfer(&mut read_buf,&[out]) + .map_err(|_| Error::Transport)?; + Ok(read_buf[0]) } - /// Send mutiple bytes and ignore what comes back over the SPI bus. + /// Send multiple bytes and ignore what comes back over the SPI bus. fn write_bytes(&mut self, out: &[u8]) -> Result<(), Error> { self.spi.write(out).map_err(|_e| Error::Transport)?; Ok(()) @@ -597,7 +588,7 @@ where /// Send multiple bytes and replace them with what comes back over the SPI bus. fn transfer_bytes(&mut self, in_out: &mut [u8]) -> Result<(), Error> { - self.spi.transfer(in_out).map_err(|_e| Error::Transport)?; + self.spi.transfer_in_place(in_out).map_err(|_e| Error::Transport)?; Ok(()) } @@ -753,7 +744,7 @@ impl Delay { /// `Ok(())`. fn delay(&mut self, delayer: &mut T, err: Error) -> Result<(), Error> where - T: embedded_hal::blocking::delay::DelayUs, + T: embedded_hal::delay::DelayUs, { if self.retries_left == 0 { Err(err) From df3b86a96d3dc9e80deafc7f7fefc3dbe5e826c6 Mon Sep 17 00:00:00 2001 From: Simon Struck Date: Mon, 23 Oct 2023 00:41:01 +0200 Subject: [PATCH 05/36] Fix doctests --- src/lib.rs | 67 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6c0225f..c329898 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,16 +22,37 @@ //! # struct DummyUart; //! # struct DummyTimeSource; //! # struct DummyDelayer; -//! # impl embedded_hal::spi::SpiDevice for DummySpi { -//! # type Error = (); -//! # fn transfer<'w>(&mut self, data: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { Ok(&[0]) } +//! # use embedded_hal::spi::Operation; +//! # use embedded_hal::spi::ErrorType; +//! # impl ErrorType for DummySpi { +//! # type Error = (); //! # } -//! # impl embedded_hal::spi::SpiDevice for DummySpi { -//! # type Error = (); -//! # fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> { Ok(()) } +//! # impl embedded_hal::spi::SpiDevice for DummySpi { +//! # +//! # fn transaction(&mut self, operations: &mut [Operation<'_, u8>]) -> Result<(), Self::Error> { +//! # Ok(()) +//! # } +//! # +//! # fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { +//! # Ok(()) +//! # } +//! # +//! # fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> { +//! # Ok(()) +//! # } +//! # +//! # fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { +//! # Ok(()) +//! # } +//! # +//! # fn transfer_in_place(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { +//! # Ok(()) +//! # } +//! # } +//! # impl embedded_hal::digital::ErrorType for DummyCsPin { +//! # type Error = (); //! # } //! # impl embedded_hal::digital::OutputPin for DummyCsPin { -//! # type Error = (); //! # fn set_low(&mut self) -> Result<(), ()> { Ok(()) } //! # fn set_high(&mut self) -> Result<(), ()> { Ok(()) } //! # } @@ -41,7 +62,9 @@ //! # impl embedded_hal::delay::DelayUs for DummyDelayer { //! # fn delay_us(&mut self, us: u8) {} //! # } -//! # impl std::fmt::Write for DummyUart { fn write_str(&mut self, s: &str) -> std::fmt::Result { Ok(()) } } +//! # impl std::fmt::Write for DummyUart { +//! # fn write_str(&mut self, s: &str) -> std::fmt::Result { Ok(()) } +//! # } //! # use std::fmt::Write; //! # use embedded_sdmmc::VolumeManager; //! # fn main() -> Result<(), embedded_sdmmc::Error> { @@ -49,20 +72,20 @@ //! # let mut sdmmc_cs = DummyCsPin; //! # let time_source = DummyTimeSource; //! # let delayer = DummyDelayer; -//! let sdcard = embedded_sdmmc::SdCard::new(sdmmc_spi, sdmmc_cs, delayer); -//! println!("Card size is {} bytes", sdcard.num_bytes()?); -//! let mut volume_mgr = embedded_sdmmc::VolumeManager::new(sdcard, time_source); -//! let mut volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?; -//! println!("Volume 0: {:?}", volume0); -//! let mut root_dir = volume0.open_root_dir()?; -//! let mut my_file = root_dir.open_file_in_dir("MY_FILE.TXT", embedded_sdmmc::Mode::ReadOnly)?; -//! while !my_file.is_eof() { -//! let mut buffer = [0u8; 32]; -//! let num_read = my_file.read(&mut buffer)?; -//! for b in &buffer[0..num_read] { -//! print!("{}", *b as char); -//! } -//! } +//! # let sdcard = embedded_sdmmc::SdCard::new(sdmmc_spi, sdmmc_cs, delayer); +//! # println!("Card size is {} bytes", sdcard.num_bytes()?); +//! # let mut volume_mgr = embedded_sdmmc::VolumeManager::new(sdcard, time_source); +//! # let mut volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?; +//! # println!("Volume 0: {:?}", volume0); +//! # let mut root_dir = volume0.open_root_dir()?; +//! # let mut my_file = root_dir.open_file_in_dir("MY_FILE.TXT", embedded_sdmmc::Mode::ReadOnly)?; +//! # while !my_file.is_eof() { +//! # let mut buffer = [0u8; 32]; +//! # let num_read = my_file.read(&mut buffer)?; +//! # for b in &buffer[0..num_read] { +//! # print!("{}", *b as char); +//! # } +//! # } //! # Ok(()) //! # } //! ``` From 62fb7be314fd7977b20fc2ea6c8cfa0c454eb022 Mon Sep 17 00:00:00 2001 From: Simon Struck Date: Mon, 23 Oct 2023 03:38:17 +0200 Subject: [PATCH 06/36] Remove duplicate where clauses --- src/sdcard/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sdcard/mod.rs b/src/sdcard/mod.rs index a678d68..90d85f0 100644 --- a/src/sdcard/mod.rs +++ b/src/sdcard/mod.rs @@ -39,7 +39,7 @@ where impl SdCard where - SPI: embedded_hal::spi::SpiDevice + embedded_hal::spi::SpiDevice, + SPI: embedded_hal::spi::SpiDevice, CS: embedded_hal::digital::OutputPin, DELAYER: embedded_hal::delay::DelayUs, { @@ -148,7 +148,7 @@ where impl BlockDevice for SdCard where - SPI: embedded_hal::spi::SpiDevice + embedded_hal::spi::SpiDevice, + SPI: embedded_hal::spi::SpiDevice, CS: embedded_hal::digital::OutputPin, DELAYER: embedded_hal::delay::DelayUs, { @@ -199,7 +199,7 @@ where /// All the APIs required `&mut self`. struct SdCardInner where - SPI: embedded_hal::spi::SpiDevice + embedded_hal::spi::SpiDevice, + SPI: embedded_hal::spi::SpiDevice, CS: embedded_hal::digital::OutputPin, DELAYER: embedded_hal::delay::DelayUs, { @@ -212,7 +212,7 @@ where impl SdCardInner where - SPI: embedded_hal::spi::SpiDevice + embedded_hal::spi::SpiDevice, + SPI: embedded_hal::spi::SpiDevice, CS: embedded_hal::digital::OutputPin, DELAYER: embedded_hal::delay::DelayUs, { From db74a651eb0cff8fe25bfeb608a4ace6e50b42c5 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 28 Oct 2023 16:13:29 +0100 Subject: [PATCH 07/36] Update CHANGELOG --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f08a337..1268e08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ The format is based on [Keep a Changelog] and this project adheres to [Semantic ## [Unreleased] -* None +* `Volume`, `Directory` and `File` are now smart! They hold references to the thing they were made from, and will clean themselves up when dropped. The trade-off is you can can't open multiple volumes, directories or files at the same time. +* Renamed the old types to `RawVolume`, `RawDirectory` and `RawFile` ## [Version 0.6.0] - 2023-10-20 From 7fc0c6f145f6870b0dbb1f4938ec405bcc145e9a Mon Sep 17 00:00:00 2001 From: Simon Struck Date: Mon, 23 Oct 2023 00:12:06 +0200 Subject: [PATCH 08/36] Update embedded hal --- Cargo.toml | 6 ++--- examples/readme_test.rs | 8 +++---- src/lib.rs | 8 +++---- src/sdcard/mod.rs | 53 +++++++++++++++++------------------------ 4 files changed, 33 insertions(+), 42 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e94d79e..a58d83f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,13 +13,13 @@ version = "0.6.0" [dependencies] byteorder = {version = "1", default-features = false} defmt = {version = "0.3", optional = true} -embedded-hal = "0.2.3" +embedded-hal = "1.0.0-rc.1" heapless = "0.7" log = {version = "0.4", default-features = false, optional = true} [dev-dependencies] -env_logger = "0.9" -hex-literal = "0.3" +env_logger = "0.10.0" +hex-literal = "0.4.1" flate2 = "1.0" sha2 = "0.10" chrono = "0.4" diff --git a/examples/readme_test.rs b/examples/readme_test.rs index 9fffca1..6ddf58f 100644 --- a/examples/readme_test.rs +++ b/examples/readme_test.rs @@ -5,14 +5,14 @@ struct FakeSpi(); -impl embedded_hal::blocking::spi::Transfer for FakeSpi { +impl embedded_hal::spi::SpiDevice for FakeSpi { type Error = core::convert::Infallible; fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { Ok(words) } } -impl embedded_hal::blocking::spi::Write for FakeSpi { +impl embedded_hal::spi::SpiDevice for FakeSpi { type Error = core::convert::Infallible; fn write(&mut self, _words: &[u8]) -> Result<(), Self::Error> { Ok(()) @@ -21,7 +21,7 @@ impl embedded_hal::blocking::spi::Write for FakeSpi { struct FakeCs(); -impl embedded_hal::digital::v2::OutputPin for FakeCs { +impl embedded_hal::digital::OutputPin for FakeCs { type Error = core::convert::Infallible; fn set_low(&mut self) -> Result<(), Self::Error> { Ok(()) @@ -34,7 +34,7 @@ impl embedded_hal::digital::v2::OutputPin for FakeCs { struct FakeDelayer(); -impl embedded_hal::blocking::delay::DelayUs for FakeDelayer { +impl embedded_hal::delay::DelayUs for FakeDelayer { fn delay_us(&mut self, us: u8) { std::thread::sleep(std::time::Duration::from_micros(u64::from(us))); } diff --git a/src/lib.rs b/src/lib.rs index 7aa9622..6c0225f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,15 +22,15 @@ //! # struct DummyUart; //! # struct DummyTimeSource; //! # struct DummyDelayer; -//! # impl embedded_hal::blocking::spi::Transfer for DummySpi { +//! # impl embedded_hal::spi::SpiDevice for DummySpi { //! # type Error = (); //! # fn transfer<'w>(&mut self, data: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { Ok(&[0]) } //! # } -//! # impl embedded_hal::blocking::spi::Write for DummySpi { +//! # impl embedded_hal::spi::SpiDevice for DummySpi { //! # type Error = (); //! # fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> { Ok(()) } //! # } -//! # impl embedded_hal::digital::v2::OutputPin for DummyCsPin { +//! # impl embedded_hal::digital::OutputPin for DummyCsPin { //! # type Error = (); //! # fn set_low(&mut self) -> Result<(), ()> { Ok(()) } //! # fn set_high(&mut self) -> Result<(), ()> { Ok(()) } @@ -38,7 +38,7 @@ //! # impl embedded_sdmmc::TimeSource for DummyTimeSource { //! # fn get_timestamp(&self) -> embedded_sdmmc::Timestamp { embedded_sdmmc::Timestamp::from_fat(0, 0) } //! # } -//! # impl embedded_hal::blocking::delay::DelayUs for DummyDelayer { +//! # impl embedded_hal::delay::DelayUs for DummyDelayer { //! # fn delay_us(&mut self, us: u8) {} //! # } //! # impl std::fmt::Write for DummyUart { fn write_str(&mut self, s: &str) -> std::fmt::Result { Ok(()) } } diff --git a/src/sdcard/mod.rs b/src/sdcard/mod.rs index 517e19c..a678d68 100644 --- a/src/sdcard/mod.rs +++ b/src/sdcard/mod.rs @@ -30,22 +30,18 @@ use crate::{debug, warn}; /// All the APIs take `&self` - mutability is handled using an inner `RefCell`. pub struct SdCard where - SPI: embedded_hal::blocking::spi::Transfer + embedded_hal::blocking::spi::Write, - CS: embedded_hal::digital::v2::OutputPin, - >::Error: core::fmt::Debug, - >::Error: core::fmt::Debug, - DELAYER: embedded_hal::blocking::delay::DelayUs, + SPI: embedded_hal::spi::SpiDevice, + CS: embedded_hal::digital::OutputPin, + DELAYER: embedded_hal::delay::DelayUs, { inner: RefCell>, } impl SdCard where - SPI: embedded_hal::blocking::spi::Transfer + embedded_hal::blocking::spi::Write, - CS: embedded_hal::digital::v2::OutputPin, - >::Error: core::fmt::Debug, - >::Error: core::fmt::Debug, - DELAYER: embedded_hal::blocking::delay::DelayUs, + SPI: embedded_hal::spi::SpiDevice + embedded_hal::spi::SpiDevice, + CS: embedded_hal::digital::OutputPin, + DELAYER: embedded_hal::delay::DelayUs, { /// Create a new SD/MMC Card driver using a raw SPI interface. /// @@ -152,11 +148,9 @@ where impl BlockDevice for SdCard where - SPI: embedded_hal::blocking::spi::Transfer + embedded_hal::blocking::spi::Write, - CS: embedded_hal::digital::v2::OutputPin, - >::Error: core::fmt::Debug, - >::Error: core::fmt::Debug, - DELAYER: embedded_hal::blocking::delay::DelayUs, + SPI: embedded_hal::spi::SpiDevice + embedded_hal::spi::SpiDevice, + CS: embedded_hal::digital::OutputPin, + DELAYER: embedded_hal::delay::DelayUs, { type Error = Error; @@ -205,11 +199,9 @@ where /// All the APIs required `&mut self`. struct SdCardInner where - SPI: embedded_hal::blocking::spi::Transfer + embedded_hal::blocking::spi::Write, - CS: embedded_hal::digital::v2::OutputPin, - >::Error: core::fmt::Debug, - >::Error: core::fmt::Debug, - DELAYER: embedded_hal::blocking::delay::DelayUs, + SPI: embedded_hal::spi::SpiDevice + embedded_hal::spi::SpiDevice, + CS: embedded_hal::digital::OutputPin, + DELAYER: embedded_hal::delay::DelayUs, { spi: SPI, cs: CS, @@ -220,11 +212,9 @@ where impl SdCardInner where - SPI: embedded_hal::blocking::spi::Transfer + embedded_hal::blocking::spi::Write, - CS: embedded_hal::digital::v2::OutputPin, - >::Error: core::fmt::Debug, - >::Error: core::fmt::Debug, - DELAYER: embedded_hal::blocking::delay::DelayUs, + SPI: embedded_hal::spi::SpiDevice + embedded_hal::spi::SpiDevice, + CS: embedded_hal::digital::OutputPin, + DELAYER: embedded_hal::delay::DelayUs, { /// Read one or more blocks, starting at the given block index. fn read(&mut self, blocks: &mut [Block], start_block_idx: BlockIdx) -> Result<(), Error> { @@ -583,13 +573,14 @@ where /// Send one byte and receive one byte over the SPI bus. fn transfer_byte(&mut self, out: u8) -> Result { + let mut read_buf = [0u8;1]; self.spi - .transfer(&mut [out]) - .map(|b| b[0]) - .map_err(|_e| Error::Transport) + .transfer(&mut read_buf,&[out]) + .map_err(|_| Error::Transport)?; + Ok(read_buf[0]) } - /// Send mutiple bytes and ignore what comes back over the SPI bus. + /// Send multiple bytes and ignore what comes back over the SPI bus. fn write_bytes(&mut self, out: &[u8]) -> Result<(), Error> { self.spi.write(out).map_err(|_e| Error::Transport)?; Ok(()) @@ -597,7 +588,7 @@ where /// Send multiple bytes and replace them with what comes back over the SPI bus. fn transfer_bytes(&mut self, in_out: &mut [u8]) -> Result<(), Error> { - self.spi.transfer(in_out).map_err(|_e| Error::Transport)?; + self.spi.transfer_in_place(in_out).map_err(|_e| Error::Transport)?; Ok(()) } @@ -753,7 +744,7 @@ impl Delay { /// `Ok(())`. fn delay(&mut self, delayer: &mut T, err: Error) -> Result<(), Error> where - T: embedded_hal::blocking::delay::DelayUs, + T: embedded_hal::delay::DelayUs, { if self.retries_left == 0 { Err(err) From a5eb76596cc025f1f1f120230cc17afd3006c37d Mon Sep 17 00:00:00 2001 From: Simon Struck Date: Mon, 23 Oct 2023 00:41:01 +0200 Subject: [PATCH 09/36] Fix doctests --- src/lib.rs | 67 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6c0225f..c329898 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,16 +22,37 @@ //! # struct DummyUart; //! # struct DummyTimeSource; //! # struct DummyDelayer; -//! # impl embedded_hal::spi::SpiDevice for DummySpi { -//! # type Error = (); -//! # fn transfer<'w>(&mut self, data: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { Ok(&[0]) } +//! # use embedded_hal::spi::Operation; +//! # use embedded_hal::spi::ErrorType; +//! # impl ErrorType for DummySpi { +//! # type Error = (); //! # } -//! # impl embedded_hal::spi::SpiDevice for DummySpi { -//! # type Error = (); -//! # fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> { Ok(()) } +//! # impl embedded_hal::spi::SpiDevice for DummySpi { +//! # +//! # fn transaction(&mut self, operations: &mut [Operation<'_, u8>]) -> Result<(), Self::Error> { +//! # Ok(()) +//! # } +//! # +//! # fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { +//! # Ok(()) +//! # } +//! # +//! # fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> { +//! # Ok(()) +//! # } +//! # +//! # fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { +//! # Ok(()) +//! # } +//! # +//! # fn transfer_in_place(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { +//! # Ok(()) +//! # } +//! # } +//! # impl embedded_hal::digital::ErrorType for DummyCsPin { +//! # type Error = (); //! # } //! # impl embedded_hal::digital::OutputPin for DummyCsPin { -//! # type Error = (); //! # fn set_low(&mut self) -> Result<(), ()> { Ok(()) } //! # fn set_high(&mut self) -> Result<(), ()> { Ok(()) } //! # } @@ -41,7 +62,9 @@ //! # impl embedded_hal::delay::DelayUs for DummyDelayer { //! # fn delay_us(&mut self, us: u8) {} //! # } -//! # impl std::fmt::Write for DummyUart { fn write_str(&mut self, s: &str) -> std::fmt::Result { Ok(()) } } +//! # impl std::fmt::Write for DummyUart { +//! # fn write_str(&mut self, s: &str) -> std::fmt::Result { Ok(()) } +//! # } //! # use std::fmt::Write; //! # use embedded_sdmmc::VolumeManager; //! # fn main() -> Result<(), embedded_sdmmc::Error> { @@ -49,20 +72,20 @@ //! # let mut sdmmc_cs = DummyCsPin; //! # let time_source = DummyTimeSource; //! # let delayer = DummyDelayer; -//! let sdcard = embedded_sdmmc::SdCard::new(sdmmc_spi, sdmmc_cs, delayer); -//! println!("Card size is {} bytes", sdcard.num_bytes()?); -//! let mut volume_mgr = embedded_sdmmc::VolumeManager::new(sdcard, time_source); -//! let mut volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?; -//! println!("Volume 0: {:?}", volume0); -//! let mut root_dir = volume0.open_root_dir()?; -//! let mut my_file = root_dir.open_file_in_dir("MY_FILE.TXT", embedded_sdmmc::Mode::ReadOnly)?; -//! while !my_file.is_eof() { -//! let mut buffer = [0u8; 32]; -//! let num_read = my_file.read(&mut buffer)?; -//! for b in &buffer[0..num_read] { -//! print!("{}", *b as char); -//! } -//! } +//! # let sdcard = embedded_sdmmc::SdCard::new(sdmmc_spi, sdmmc_cs, delayer); +//! # println!("Card size is {} bytes", sdcard.num_bytes()?); +//! # let mut volume_mgr = embedded_sdmmc::VolumeManager::new(sdcard, time_source); +//! # let mut volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?; +//! # println!("Volume 0: {:?}", volume0); +//! # let mut root_dir = volume0.open_root_dir()?; +//! # let mut my_file = root_dir.open_file_in_dir("MY_FILE.TXT", embedded_sdmmc::Mode::ReadOnly)?; +//! # while !my_file.is_eof() { +//! # let mut buffer = [0u8; 32]; +//! # let num_read = my_file.read(&mut buffer)?; +//! # for b in &buffer[0..num_read] { +//! # print!("{}", *b as char); +//! # } +//! # } //! # Ok(()) //! # } //! ``` From 69d80473adc0c84e1936d8e847df5a369608ca3e Mon Sep 17 00:00:00 2001 From: Simon Struck Date: Mon, 23 Oct 2023 03:38:17 +0200 Subject: [PATCH 10/36] Remove duplicate where clauses --- src/sdcard/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sdcard/mod.rs b/src/sdcard/mod.rs index a678d68..90d85f0 100644 --- a/src/sdcard/mod.rs +++ b/src/sdcard/mod.rs @@ -39,7 +39,7 @@ where impl SdCard where - SPI: embedded_hal::spi::SpiDevice + embedded_hal::spi::SpiDevice, + SPI: embedded_hal::spi::SpiDevice, CS: embedded_hal::digital::OutputPin, DELAYER: embedded_hal::delay::DelayUs, { @@ -148,7 +148,7 @@ where impl BlockDevice for SdCard where - SPI: embedded_hal::spi::SpiDevice + embedded_hal::spi::SpiDevice, + SPI: embedded_hal::spi::SpiDevice, CS: embedded_hal::digital::OutputPin, DELAYER: embedded_hal::delay::DelayUs, { @@ -199,7 +199,7 @@ where /// All the APIs required `&mut self`. struct SdCardInner where - SPI: embedded_hal::spi::SpiDevice + embedded_hal::spi::SpiDevice, + SPI: embedded_hal::spi::SpiDevice, CS: embedded_hal::digital::OutputPin, DELAYER: embedded_hal::delay::DelayUs, { @@ -212,7 +212,7 @@ where impl SdCardInner where - SPI: embedded_hal::spi::SpiDevice + embedded_hal::spi::SpiDevice, + SPI: embedded_hal::spi::SpiDevice, CS: embedded_hal::digital::OutputPin, DELAYER: embedded_hal::delay::DelayUs, { From 4d3aa31e4d40b057c86b7aaec802799de4ddbacf Mon Sep 17 00:00:00 2001 From: Gray Olson Date: Mon, 30 Oct 2023 10:12:41 +0100 Subject: [PATCH 11/36] Create and document DummyCsPin for use with e-h1.0 --- Cargo.toml | 1 + examples/readme_test.rs | 45 ++++++++++++++++++++++-------- src/sdcard/mod.rs | 61 ++++++++++++++++++++++++++++++++++++----- 3 files changed, 88 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a58d83f..83f1a7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ hex-literal = "0.4.1" flate2 = "1.0" sha2 = "0.10" chrono = "0.4" +embedded-hal-bus = "0.1.0-rc.1" [features] default = ["log"] diff --git a/examples/readme_test.rs b/examples/readme_test.rs index 6ddf58f..14b7030 100644 --- a/examples/readme_test.rs +++ b/examples/readme_test.rs @@ -3,26 +3,45 @@ //! We add enough stuff to make it compile, but it won't run because our fake //! SPI doesn't do any replies. -struct FakeSpi(); +use core::cell::RefCell; -impl embedded_hal::spi::SpiDevice for FakeSpi { +use embedded_sdmmc::sdcard::DummyCsPin; + +struct FakeSpiBus(); + +impl embedded_hal::spi::ErrorType for FakeSpiBus { type Error = core::convert::Infallible; - fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { - Ok(words) - } } -impl embedded_hal::spi::SpiDevice for FakeSpi { - type Error = core::convert::Infallible; - fn write(&mut self, _words: &[u8]) -> Result<(), Self::Error> { +impl embedded_hal::spi::SpiBus for FakeSpiBus { + fn read(&mut self, _: &mut [u8]) -> Result<(), Self::Error> { + Ok(()) + } + + fn write(&mut self, _: &[u8]) -> Result<(), Self::Error> { + Ok(()) + } + + fn transfer(&mut self, _: &mut [u8], _: &[u8]) -> Result<(), Self::Error> { + Ok(()) + } + + fn transfer_in_place(&mut self, _: &mut [u8]) -> Result<(), Self::Error> { + Ok(()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { Ok(()) } } struct FakeCs(); -impl embedded_hal::digital::OutputPin for FakeCs { +impl embedded_hal::digital::ErrorType for FakeCs { type Error = core::convert::Infallible; +} + +impl embedded_hal::digital::OutputPin for FakeCs { fn set_low(&mut self) -> Result<(), Self::Error> { Ok(()) } @@ -32,10 +51,11 @@ impl embedded_hal::digital::OutputPin for FakeCs { } } +#[derive(Clone, Copy)] struct FakeDelayer(); impl embedded_hal::delay::DelayUs for FakeDelayer { - fn delay_us(&mut self, us: u8) { + fn delay_us(&mut self, us: u32) { std::thread::sleep(std::time::Duration::from_micros(u64::from(us))); } } @@ -74,9 +94,10 @@ impl From for Error { } fn main() -> Result<(), Error> { - let sdmmc_spi = FakeSpi(); - let sdmmc_cs = FakeCs(); + let spi_bus = RefCell::new(FakeSpiBus()); let delay = FakeDelayer(); + let sdmmc_spi = embedded_hal_bus::spi::RefCellDevice::new(&spi_bus, DummyCsPin, delay); + let sdmmc_cs = FakeCs(); let time_source = FakeTimesource(); // Build an SD Card interface out of an SPI device, a chip-select pin and the delay object let sdcard = embedded_sdmmc::SdCard::new(sdmmc_spi, sdmmc_cs, delay); diff --git a/src/sdcard/mod.rs b/src/sdcard/mod.rs index 90d85f0..a343813 100644 --- a/src/sdcard/mod.rs +++ b/src/sdcard/mod.rs @@ -21,11 +21,50 @@ use crate::{debug, warn}; // Types and Implementations // **************************************************************************** +/// A dummy "CS pin" that does nothing when set high or low. +/// +/// Should be used when constructing an [`SpiDevice`] implementation for use with [`SdCard`]. +/// +/// Let the [`SpiDevice`] use this dummy CS pin that does not actually do anything, and pass the +/// card's real CS pin to [`SdCard`]'s constructor. This allows the driver to have more +/// fine-grained control of how the CS pin is managed than is allowed by default using the +/// [`SpiDevice`] trait, which is needed to implement the SD/MMC SPI communication spec correctly. +/// +/// If you're not sure how to get a [`SpiDevice`], you may use one of the implementations +/// in the [`embedded-hal-bus`] crate, providing a wrapped version of your platform's HAL-provided +/// [`SpiBus`] and [`DelayUs`] as well as our [`DummyCsPin`] in the constructor. +/// +/// [`SpiDevice`]: embedded_hal::spi::SpiDevice +/// [`SpiBus`]: embedded_hal::spi::SpiBus +/// [`DelayUs`]: embedded_hal::delay::DelayUs +/// [`embedded-hal-bus`]: https://docs.rs/embedded-hal-bus +pub struct DummyCsPin; + +impl embedded_hal::digital::ErrorType for DummyCsPin { + type Error = core::convert::Infallible; +} + +impl embedded_hal::digital::OutputPin for DummyCsPin { + #[inline(always)] + fn set_low(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + #[inline(always)] + fn set_high(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + /// Represents an SD Card on an SPI bus. /// -/// Built from an SPI peripheral and a Chip Select pin. We need Chip Select to -/// be separate so we can clock out some bytes without Chip Select asserted -/// (which "flushes the SD cards registers" according to the spec). +/// Built from an [`SpiDevice`] implementation and a Chip Select pin. +/// Unfortunately, We need control of the chip select pin separately from the [`SpiDevice`] +/// implementation so we can clock out some bytes without Chip Select asserted +/// (which is necessary to make the SD card actually release the Spi bus after performing +/// operations on it, according to the spec). To support this, we provide [`DummyCsPin`] +/// which should be provided to your chosen [`SpiDevice`] implementation rather than the card's +/// actual CS pin. Then provide the actual CS pin to [`SdCard`]'s constructor. /// /// All the APIs take `&self` - mutability is handled using an inner `RefCell`. pub struct SdCard @@ -45,6 +84,9 @@ where { /// Create a new SD/MMC Card driver using a raw SPI interface. /// + /// See the docs of the [`SdCard`] struct for more information about + /// how to construct the needed `SPI` and `CS` types. + /// /// The card will not be initialised at this time. Initialisation is /// deferred until a method is called on the object. /// @@ -55,6 +97,9 @@ where /// Construct a new SD/MMC Card driver, using a raw SPI interface and the given options. /// + /// See the docs of the [`SdCard`] struct for more information about + /// how to construct the needed `SPI` and `CS` types. + /// /// The card will not be initialised at this time. Initialisation is /// deferred until a method is called on the object. pub fn new_with_options( @@ -573,9 +618,9 @@ where /// Send one byte and receive one byte over the SPI bus. fn transfer_byte(&mut self, out: u8) -> Result { - let mut read_buf = [0u8;1]; + let mut read_buf = [0u8; 1]; self.spi - .transfer(&mut read_buf,&[out]) + .transfer(&mut read_buf, &[out]) .map_err(|_| Error::Transport)?; Ok(read_buf[0]) } @@ -588,7 +633,9 @@ where /// Send multiple bytes and replace them with what comes back over the SPI bus. fn transfer_bytes(&mut self, in_out: &mut [u8]) -> Result<(), Error> { - self.spi.transfer_in_place(in_out).map_err(|_e| Error::Transport)?; + self.spi + .transfer_in_place(in_out) + .map_err(|_e| Error::Transport)?; Ok(()) } @@ -680,7 +727,7 @@ pub enum CardType { /// Uses byte-addressing internally, so limited to 2GiB in size. SD2, /// An high-capacity 'SDHC' Card. - /// + /// /// Uses block-addressing internally to support capacities above 2GiB. SDHC, } From 5d31339856977a07b4875071b06a6314c114af86 Mon Sep 17 00:00:00 2001 From: Simon Struck Date: Mon, 30 Oct 2023 13:55:21 +0100 Subject: [PATCH 12/36] Fix doctest --- src/lib.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c329898..7b41145 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,8 +24,10 @@ //! # struct DummyDelayer; //! # use embedded_hal::spi::Operation; //! # use embedded_hal::spi::ErrorType; +//! # use core::convert::Infallible; +//! # use core::fmt; //! # impl ErrorType for DummySpi { -//! # type Error = (); +//! # type Error = Infallible; //! # } //! # impl embedded_hal::spi::SpiDevice for DummySpi { //! # @@ -50,22 +52,21 @@ //! # } //! # } //! # impl embedded_hal::digital::ErrorType for DummyCsPin { -//! # type Error = (); +//! # type Error = Infallible; //! # } //! # impl embedded_hal::digital::OutputPin for DummyCsPin { -//! # fn set_low(&mut self) -> Result<(), ()> { Ok(()) } -//! # fn set_high(&mut self) -> Result<(), ()> { Ok(()) } +//! # fn set_low(&mut self) -> Result<(), Self::Error> { Ok(()) } +//! # fn set_high(&mut self) -> Result<(), Self::Error> { Ok(()) } //! # } //! # impl embedded_sdmmc::TimeSource for DummyTimeSource { //! # fn get_timestamp(&self) -> embedded_sdmmc::Timestamp { embedded_sdmmc::Timestamp::from_fat(0, 0) } //! # } //! # impl embedded_hal::delay::DelayUs for DummyDelayer { -//! # fn delay_us(&mut self, us: u8) {} +//! # fn delay_us(&mut self, us: u32) {} //! # } -//! # impl std::fmt::Write for DummyUart { -//! # fn write_str(&mut self, s: &str) -> std::fmt::Result { Ok(()) } +//! # impl fmt::Write for DummyUart { +//! # fn write_str(&mut self, s: &str) -> fmt::Result { Ok(()) } //! # } -//! # use std::fmt::Write; //! # use embedded_sdmmc::VolumeManager; //! # fn main() -> Result<(), embedded_sdmmc::Error> { //! # let mut sdmmc_spi = DummySpi; @@ -74,7 +75,7 @@ //! # let delayer = DummyDelayer; //! # let sdcard = embedded_sdmmc::SdCard::new(sdmmc_spi, sdmmc_cs, delayer); //! # println!("Card size is {} bytes", sdcard.num_bytes()?); -//! # let mut volume_mgr = embedded_sdmmc::VolumeManager::new(sdcard, time_source); +//! # let mut volume_mgr = VolumeManager::new(sdcard, time_source); //! # let mut volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?; //! # println!("Volume 0: {:?}", volume0); //! # let mut root_dir = volume0.open_root_dir()?; From f2a6ae1666f9c1c7eb11587d86789911a06e1b93 Mon Sep 17 00:00:00 2001 From: Nereuxofficial <37740907+Nereuxofficial@users.noreply.github.com> Date: Wed, 8 Nov 2023 15:12:37 +0100 Subject: [PATCH 13/36] Linked no-std examples --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 242333b..c16a965 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,11 @@ let mut cont: VolumeManager<_, _, 6, 12, 4> = VolumeManager::new_with_limits(blo * Iterate sub-directories * Log over defmt or the common log interface (feature flags). +## No-std usage +This repository houses no examples for no-std usage, however you can check out the following examples: +- [Pi Pico](https://github.com/rp-rs/rp-hal-boards/blob/main/boards/rp-pico/examples/pico_spi_sd_card.rs) +- [STM32H7XX](https://github.com/stm32-rs/stm32h7xx-hal/blob/master/examples/sdmmc_fat.rs) + ## Todo List (PRs welcome!) * Create new dirs From d4195badfaa03c50c56ff7dac2fa00874087c00c Mon Sep 17 00:00:00 2001 From: Nereuxofficial <37740907+Nereuxofficial@users.noreply.github.com> Date: Wed, 8 Nov 2023 15:15:39 +0100 Subject: [PATCH 14/36] Added pygamer example --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c16a965..3ba2918 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ let mut cont: VolumeManager<_, _, 6, 12, 4> = VolumeManager::new_with_limits(blo This repository houses no examples for no-std usage, however you can check out the following examples: - [Pi Pico](https://github.com/rp-rs/rp-hal-boards/blob/main/boards/rp-pico/examples/pico_spi_sd_card.rs) - [STM32H7XX](https://github.com/stm32-rs/stm32h7xx-hal/blob/master/examples/sdmmc_fat.rs) +- [atsamd(pygamer)](https://github.com/atsamd-rs/atsamd/blob/master/boards/pygamer/examples/sd_card.rs) ## Todo List (PRs welcome!) From eb59b66f416505d5f4dc0cd3f727a42b4db96388 Mon Sep 17 00:00:00 2001 From: yanorei32 Date: Wed, 6 Dec 2023 02:09:04 +0900 Subject: [PATCH 15/36] Fix struct reference in documentation --- src/filesystem/directory.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filesystem/directory.rs b/src/filesystem/directory.rs index b271471..b6cd831 100644 --- a/src/filesystem/directory.rs +++ b/src/filesystem/directory.rs @@ -51,7 +51,7 @@ pub struct DirEntry { pub struct RawDirectory(pub(crate) SearchId); impl RawDirectory { - /// Convert a raw file into a droppable [`File`] + /// Convert a raw file into a droppable [`crate::filesystem::File`] pub fn to_directory< D, T, From 799b8036df94c8c063b2700db13ef308b1546e66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ray=20Yano=20=7C=20=E3=81=82=E3=81=AD=E3=81=A6=E3=81=82?= Date: Wed, 6 Dec 2023 13:32:12 +0900 Subject: [PATCH 16/36] Update src/filesystem/directory.rs Co-authored-by: Jonathan 'theJPster' Pallant --- src/filesystem/directory.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filesystem/directory.rs b/src/filesystem/directory.rs index b6cd831..684fc47 100644 --- a/src/filesystem/directory.rs +++ b/src/filesystem/directory.rs @@ -51,7 +51,7 @@ pub struct DirEntry { pub struct RawDirectory(pub(crate) SearchId); impl RawDirectory { - /// Convert a raw file into a droppable [`crate::filesystem::File`] + /// Convert a raw directory into a droppable [`Directory`] pub fn to_directory< D, T, From fec3af5e76e62c90cd33bc9e7c177b51bd844e65 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 17 Dec 2023 17:19:53 +0000 Subject: [PATCH 17/36] Add example which tries to totally fill the root directory. --- examples/big_dir.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 examples/big_dir.rs diff --git a/examples/big_dir.rs b/examples/big_dir.rs new file mode 100644 index 0000000..b355c3c --- /dev/null +++ b/examples/big_dir.rs @@ -0,0 +1,39 @@ +extern crate embedded_sdmmc; + +mod linux; +use linux::*; + +use embedded_sdmmc::{Error, VolumeManager}; + +fn main() -> Result<(), embedded_sdmmc::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(embedded_sdmmc::VolumeIdx(1)) + .unwrap(); + println!("Volume: {:?}", volume); + let mut 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 + .open_file_in_dir( + file_name.as_str(), + embedded_sdmmc::Mode::ReadWriteCreateOrTruncate, + ) + .unwrap(); + let buf = b"hello world, from rust"; + println!("writing to file"); + file.write(&buf[..]).unwrap(); + println!("closing file"); + drop(file); + } +} From 819a624488297ecbf7992c39753a6b7ccb0a307e Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 17 Dec 2023 17:21:45 +0000 Subject: [PATCH 18/36] Fix issue #74 Map ROOT_DIR (0xFFFF_FFFC) to an actual cluster number, so if you go off the end of the first cluster of the root directory on FAT32, the code no longer crashes trying to convert the ROOT_DIR magic cluster number into a disk offset. --- src/fat/volume.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/fat/volume.rs b/src/fat/volume.rs index c946ec8..b6dd510 100644 --- a/src/fat/volume.rs +++ b/src/fat/volume.rs @@ -179,6 +179,9 @@ impl FatVolume { where D: BlockDevice, { + if cluster.0 > (u32::MAX / 4) { + panic!("next_cluster called on invalid cluster {:x?}", cluster); + } match &self.fat_specific_info { FatSpecificInfo::Fat16(_fat16_info) => { let fat_offset = cluster.0 * 2; @@ -360,19 +363,24 @@ impl FatVolume { FatSpecificInfo::Fat32(fat32_info) => { // All directories on FAT32 have a cluster chain but the root // dir starts in a specified cluster. - let mut first_dir_block_num = match dir.cluster { - ClusterId::ROOT_DIR => self.cluster_to_block(fat32_info.first_root_dir_cluster), - _ => self.cluster_to_block(dir.cluster), + let mut current_cluster = match dir.cluster { + ClusterId::ROOT_DIR => Some(fat32_info.first_root_dir_cluster), + _ => Some(dir.cluster), }; - let mut current_cluster = 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) { + // Read a block of directory entries block_device .read(&mut blocks, block, "read_dir") .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; @@ -397,6 +405,8 @@ impl FatVolume { } } } + // 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) { @@ -412,6 +422,8 @@ impl FatVolume { _ => 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. Err(Error::NotEnoughSpace) } } From 2c557b06cf0c4d29cfdd2b264df307898f07da08 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 17 Dec 2023 20:32:59 +0000 Subject: [PATCH 19/36] Clippy lints for list_dir --- examples/list_dir.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/list_dir.rs b/examples/list_dir.rs index 51fa68e..18c121d 100644 --- a/examples/list_dir.rs +++ b/examples/list_dir.rs @@ -76,12 +76,11 @@ fn list_dir( "" } ); - if entry.attributes.is_directory() { - if entry.name != embedded_sdmmc::ShortFileName::parent_dir() - && entry.name != embedded_sdmmc::ShortFileName::this_dir() - { - children.push(entry.name.clone()); - } + if entry.attributes.is_directory() + && entry.name != embedded_sdmmc::ShortFileName::parent_dir() + && entry.name != embedded_sdmmc::ShortFileName::this_dir() + { + children.push(entry.name.clone()); } })?; for child_name in children { From 054af872b3bb51c40753d7e215ea1c9b8e1c811f Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 17 Dec 2023 20:45:45 +0000 Subject: [PATCH 20/36] Support making new directories. --- examples/shell.rs | 21 +++++- src/fat/volume.rs | 44 +++++++++-- src/lib.rs | 2 + src/volume_mgr.rs | 171 ++++++++++++++++++++++++++++++++++++------- tests/directories.rs | 113 ++++++++++++++++++++++++++++ tests/utils/mod.rs | 8 ++ 6 files changed, 322 insertions(+), 37 deletions(-) diff --git a/examples/shell.rs b/examples/shell.rs index ddea8de..433fb3a 100644 --- a/examples/shell.rs +++ b/examples/shell.rs @@ -41,6 +41,7 @@ impl Context { println!("\thexdump -> print a binary file"); println!("\tcd .. -> go up a level"); println!("\tcd -> change into "); + println!("\tmkdir -> create a directory called "); println!("\tquit -> exits the program"); } else if line == "0:" { self.current_volume = 0; @@ -59,11 +60,17 @@ impl Context { }; self.volume_mgr.iterate_dir(s.directory, |entry| { println!( - "{:12} {:9} {} {:?}", - entry.name, entry.size, entry.mtime, entry.attributes + "{:12} {:9} {} {} {:X?} {:?}", + entry.name, + entry.size, + entry.ctime, + entry.mtime, + entry.cluster, + entry.attributes ); })?; } else if let Some(arg) = line.strip_prefix("cd ") { + let arg = arg.trim(); let Some(s) = &mut self.volumes[self.current_volume] else { println!("This volume isn't available"); return Ok(()); @@ -77,6 +84,7 @@ impl Context { s.path.push(arg.to_owned()); } } else if let Some(arg) = line.strip_prefix("cat ") { + let arg = arg.trim(); let Some(s) = &mut self.volumes[self.current_volume] else { println!("This volume isn't available"); return Ok(()); @@ -99,6 +107,7 @@ impl Context { println!("I'm afraid that file isn't UTF-8 encoded"); } } else if let Some(arg) = line.strip_prefix("hexdump ") { + let arg = arg.trim(); let Some(s) = &mut self.volumes[self.current_volume] else { println!("This volume isn't available"); return Ok(()); @@ -136,6 +145,14 @@ impl Context { } println!(); } + } else if let Some(arg) = line.strip_prefix("mkdir ") { + let arg = arg.trim(); + let Some(s) = &mut self.volumes[self.current_volume] else { + println!("This volume isn't available"); + return Ok(()); + }; + // make the dir + self.volume_mgr.make_dir_in_dir(s.directory, arg)?; } else { println!("Unknown command {line:?} - try 'help' for help"); } diff --git a/src/fat/volume.rs b/src/fat/volume.rs index b6dd510..c41591f 100644 --- a/src/fat/volume.rs +++ b/src/fat/volume.rs @@ -278,7 +278,7 @@ impl FatVolume { &mut self, block_device: &D, time_source: &T, - dir: &DirectoryInfo, + dir_cluster: ClusterId, name: ShortFileName, attributes: Attributes, ) -> Result> @@ -292,12 +292,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_cluster); + let mut first_dir_block_num = match dir_cluster { ClusterId::ROOT_DIR => self.lba_start + fat16_info.first_root_dir_block, - _ => self.cluster_to_block(dir.cluster), + _ => self.cluster_to_block(dir_cluster), }; - let dir_size = match dir.cluster { + let dir_size = match dir_cluster { ClusterId::ROOT_DIR => { let len_bytes = u32::from(fat16_info.root_entries_count) * OnDiskDirEntry::LEN_U32; @@ -363,11 +363,11 @@ impl FatVolume { FatSpecificInfo::Fat32(fat32_info) => { // 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_cluster { ClusterId::ROOT_DIR => Some(fat32_info.first_root_dir_cluster), - _ => Some(dir.cluster), + _ => Some(dir_cluster), }; - let mut first_dir_block_num = self.cluster_to_block(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)); @@ -1006,6 +1006,34 @@ impl FatVolume { } Ok(()) } + + /// Writes a Directory Entry to the disk + pub(crate) fn write_entry_to_disk( + &self, + block_device: &D, + entry: &DirEntry, + ) -> Result<(), Error> + where + D: BlockDevice, + { + let fat_type = match self.fat_specific_info { + FatSpecificInfo::Fat16(_) => FatType::Fat16, + FatSpecificInfo::Fat32(_) => FatType::Fat32, + }; + let mut blocks = [Block::new()]; + block_device + .read(&mut blocks, entry.entry_block, "read") + .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)?; + Ok(()) + } } /// Load the boot parameter block from the start of the given partition and diff --git a/src/lib.rs b/src/lib.rs index 7aa9622..630d09f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -222,6 +222,8 @@ where InvalidOffset, /// Disk is full DiskFull, + /// A directory with that name already exists + DirAlreadyExists, } impl From for Error diff --git a/src/volume_mgr.rs b/src/volume_mgr.rs index 9e95d0f..95c09ed 100644 --- a/src/volume_mgr.rs +++ b/src/volume_mgr.rs @@ -5,7 +5,7 @@ use byteorder::{ByteOrder, LittleEndian}; use core::convert::TryFrom; -use crate::fat::{self, BlockCache, RESERVED_ENTRIES}; +use crate::fat::{self, BlockCache, FatType, OnDiskDirEntry, RESERVED_ENTRIES}; use crate::filesystem::{ Attributes, ClusterId, DirEntry, DirectoryInfo, FileInfo, Mode, RawDirectory, RawFile, @@ -432,8 +432,7 @@ where match &self.open_volumes[volume_idx].volume_type { VolumeType::Fat(fat) => { file.entry.mtime = self.time_source.get_timestamp(); - let fat_type = fat.get_fat_type(); - self.write_entry_to_disk(fat_type, &file.entry)?; + fat.write_entry_to_disk(&self.block_device, &file.entry)?; } }; @@ -518,7 +517,7 @@ where VolumeType::Fat(fat) => fat.write_new_directory_entry( &self.block_device, &self.time_source, - directory_info, + directory_info.cluster, sfn, att, )?, @@ -789,8 +788,7 @@ where // If you have a length, you must have a cluster assert!(file_info.entry.cluster.0 != 0); } - let fat_type = fat.get_fat_type(); - self.write_entry_to_disk(fat_type, &file_info.entry)?; + fat.write_entry_to_disk(&self.block_device, &file_info.entry)?; } }; } @@ -866,6 +864,146 @@ where Ok(self.open_files[file_idx].current_offset) } + /// Create a directory in a given directory. + pub fn make_dir_in_dir( + &mut self, + directory: RawDirectory, + name: N, + ) -> Result<(), Error> + where + N: ToShortFileName, + { + // This check is load-bearing - we do an unchecked push later. + if self.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 sfn = name.to_short_filename().map_err(Error::FilenameError)?; + + debug!("Creating directory '{}'", sfn); + debug!( + "Parent dir is in cluster {:?}", + parent_directory_info.cluster + ); + + // 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) + } + }; + + match maybe_dir_entry { + Ok(entry) if entry.attributes.is_directory() => { + return Err(Error::DirAlreadyExists); + } + Ok(_entry) => { + return Err(Error::FileAlreadyExists); + } + Err(Error::FileNotFound) => { + // perfect, let's make it + } + Err(e) => { + // Some other error - tell them about it + return Err(e); + } + }; + + let att = Attributes::create_from_fat(Attributes::DIRECTORY); + + // Need mutable access for this + match &mut self.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, + &self.time_source, + parent_directory_info.cluster, + sfn, + att, + )?; + if new_dir_entry_in_parent.cluster == ClusterId::EMPTY { + new_dir_entry_in_parent.cluster = + fat.alloc_cluster(&self.block_device, 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)?; + } + 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()]; + // make the "." entry + let dot_entry_in_child = DirEntry { + name: crate::ShortFileName::this_dir(), + mtime: now, + ctime: now, + attributes: att, + // point at ourselves + cluster: new_dir_entry_in_parent.cluster, + size: 0, + entry_block: new_dir_start_block, + entry_offset: 0, + }; + debug!("New dir has {:?}", dot_entry_in_child); + let mut offset = 0; + blocks[0][offset..offset + OnDiskDirEntry::LEN] + .copy_from_slice(&dot_entry_in_child.serialize(fat_type)[..]); + offset += OnDiskDirEntry::LEN; + // make the ".." entry + let dot_dot_entry_in_child = DirEntry { + name: crate::ShortFileName::parent_dir(), + mtime: now, + ctime: now, + attributes: att, + // point at our parent + cluster: match fat_type { + FatType::Fat16 => { + // On FAT16, indicate parent is root using Cluster(0) + if parent_directory_info.cluster == ClusterId::ROOT_DIR { + ClusterId::EMPTY + } else { + parent_directory_info.cluster + } + } + FatType::Fat32 => parent_directory_info.cluster, + }, + size: 0, + entry_block: new_dir_start_block, + entry_offset: OnDiskDirEntry::LEN_U32, + }; + debug!("New dir has {:?}", dot_dot_entry_in_child); + blocks[0][offset..offset + 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)?; + + // Now zero the rest of the cluster + for b in blocks[0].iter_mut() { + *b = 0; + } + for block 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)?; + } + } + }; + + Ok(()) + } + fn get_volume_by_id(&self, volume: RawVolume) -> Result> { for (idx, v) in self.open_volumes.iter().enumerate() { if v.volume_id == volume { @@ -928,27 +1066,6 @@ where let available = Block::LEN - block_offset; Ok((block_idx, block_offset, available)) } - - /// Writes a Directory Entry to the disk - fn write_entry_to_disk( - &self, - fat_type: fat::FatType, - entry: &DirEntry, - ) -> Result<(), Error> { - let mut blocks = [Block::new()]; - self.block_device - .read(&mut blocks, entry.entry_block, "read") - .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)[..]); - - self.block_device - .write(&blocks, entry.entry_block) - .map_err(Error::DeviceError)?; - Ok(()) - } } /// Transform mode variants (ReadWriteCreate_Or_Append) to simple modes ReadWriteAppend or diff --git a/tests/directories.rs b/tests/directories.rs index fee08f7..af1cf87 100644 --- a/tests/directories.rs +++ b/tests/directories.rs @@ -365,6 +365,119 @@ fn delete_file() { )); } +#[test] +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 fat32_volume = volume_mgr + .open_raw_volume(embedded_sdmmc::VolumeIdx(1)) + .expect("open volume 1"); + + let root_dir = volume_mgr + .open_root_dir(fat32_volume) + .expect("open root dir"); + + let test_dir_name = ShortFileName::create_from_str("12345678.ABC").unwrap(); + let test_file_name = ShortFileName::create_from_str("ABC.TXT").unwrap(); + + volume_mgr + .make_dir_in_dir(root_dir, &test_dir_name) + .unwrap(); + + let new_dir = volume_mgr.open_dir(root_dir, &test_dir_name).unwrap(); + + let mut has_this = false; + let mut has_parent = false; + volume_mgr + .iterate_dir(new_dir, |item| { + if item.name == ShortFileName::parent_dir() { + has_parent = true; + assert!(item.attributes.is_directory()); + assert_eq!(item.size, 0); + assert_eq!(item.mtime.to_string(), utils::get_time_source_string()); + assert_eq!(item.ctime.to_string(), utils::get_time_source_string()); + } else if item.name == ShortFileName::this_dir() { + has_this = true; + assert!(item.attributes.is_directory()); + assert_eq!(item.size, 0); + assert_eq!(item.mtime.to_string(), utils::get_time_source_string()); + assert_eq!(item.ctime.to_string(), utils::get_time_source_string()); + } else { + panic!("Unexpected item in new dir"); + } + }) + .unwrap(); + assert!(has_this); + assert!(has_parent); + + let new_file = volume_mgr + .open_file_in_dir( + new_dir, + &test_file_name, + embedded_sdmmc::Mode::ReadWriteCreate, + ) + .expect("open new file"); + volume_mgr + .write(new_file, b"Hello") + .expect("write to new file"); + volume_mgr.close_file(new_file).expect("close new file"); + + let mut has_this = false; + let mut has_parent = false; + let mut has_new_file = false; + volume_mgr + .iterate_dir(new_dir, |item| { + if item.name == ShortFileName::parent_dir() { + has_parent = true; + assert!(item.attributes.is_directory()); + assert_eq!(item.size, 0); + assert_eq!(item.mtime.to_string(), utils::get_time_source_string()); + assert_eq!(item.ctime.to_string(), utils::get_time_source_string()); + } else if item.name == ShortFileName::this_dir() { + has_this = true; + assert!(item.attributes.is_directory()); + assert_eq!(item.size, 0); + assert_eq!(item.mtime.to_string(), utils::get_time_source_string()); + assert_eq!(item.ctime.to_string(), utils::get_time_source_string()); + } else if item.name == test_file_name { + has_new_file = true; + // We wrote "Hello" to it + assert_eq!(item.size, 5); + assert!(!item.attributes.is_directory()); + assert_eq!(item.mtime.to_string(), utils::get_time_source_string()); + assert_eq!(item.ctime.to_string(), utils::get_time_source_string()); + } else { + panic!("Unexpected item in new dir"); + } + }) + .unwrap(); + assert!(has_this); + assert!(has_parent); + assert!(has_new_file); + + // Close the root dir and look again + volume_mgr.close_dir(root_dir).expect("close root"); + volume_mgr.close_dir(new_dir).expect("close new_dir"); + let root_dir = volume_mgr + .open_root_dir(fat32_volume) + .expect("open root dir"); + // Check we can't make it again now it exists + assert!(volume_mgr + .make_dir_in_dir(root_dir, &test_dir_name) + .is_err()); + let new_dir = volume_mgr + .open_dir(root_dir, &test_dir_name) + .expect("find new dir"); + let new_file = volume_mgr + .open_file_in_dir(new_dir, &test_file_name, embedded_sdmmc::Mode::ReadOnly) + .expect("re-open new file"); + volume_mgr.close_dir(root_dir).expect("close root"); + volume_mgr.close_dir(new_dir).expect("close new dir"); + volume_mgr.close_file(new_file).expect("close file"); +} + // **************************************************************************** // // End Of File diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs index b74db45..9976975 100644 --- a/tests/utils/mod.rs +++ b/tests/utils/mod.rs @@ -179,6 +179,14 @@ pub fn make_time_source() -> TestTimeSource { } } +/// Get the test time source time, as a string. +/// +/// We apply the FAT 2-second rounding here. +#[allow(unused)] +pub fn get_time_source_string() -> &'static str { + "2003-04-04 13:30:04" +} + // **************************************************************************** // // End Of File From 43f9281e3859d05d3f9ba0571685fbd136710d10 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 17 Dec 2023 20:55:05 +0000 Subject: [PATCH 21/36] Update CHANGELOG. --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1268e08..24357bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ The format is based on [Keep a Changelog] and this project adheres to [Semantic * `Volume`, `Directory` and `File` are now smart! They hold references to the thing they were made from, and will clean themselves up when dropped. The trade-off is you can can't open multiple volumes, directories or files at the same time. * Renamed the old types to `RawVolume`, `RawDirectory` and `RawFile` +* New method `make_dir_in_dir` +* Fixed long-standing bug that caused an integer overflow when a FAT32 directory + was longer than one cluster ([#74]) + +[#74]: https://github.com/rust-embedded-community/embedded-sdmmc-rs/issues/74 ## [Version 0.6.0] - 2023-10-20 From 5fe01bf23f628d9462d65954df2df4a1c8306800 Mon Sep 17 00:00:00 2001 From: jakezhu9 Date: Wed, 10 Jan 2024 14:58:04 +0800 Subject: [PATCH 22/36] lib: update dependency embedded-hal to 1.0.0 Signed-off-by: jakezhu9 --- Cargo.toml | 4 ++-- examples/readme_test.rs | 6 +++--- src/lib.rs | 33 +++++++++------------------------ src/sdcard/mod.rs | 18 +++++++++--------- 4 files changed, 23 insertions(+), 38 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 83f1a7a..20322ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ version = "0.6.0" [dependencies] byteorder = {version = "1", default-features = false} defmt = {version = "0.3", optional = true} -embedded-hal = "1.0.0-rc.1" +embedded-hal = "1.0.0" heapless = "0.7" log = {version = "0.4", default-features = false, optional = true} @@ -23,7 +23,7 @@ hex-literal = "0.4.1" flate2 = "1.0" sha2 = "0.10" chrono = "0.4" -embedded-hal-bus = "0.1.0-rc.1" +embedded-hal-bus = "0.1.0" [features] default = ["log"] diff --git a/examples/readme_test.rs b/examples/readme_test.rs index 14b7030..4c8ee0c 100644 --- a/examples/readme_test.rs +++ b/examples/readme_test.rs @@ -54,9 +54,9 @@ impl embedded_hal::digital::OutputPin for FakeCs { #[derive(Clone, Copy)] struct FakeDelayer(); -impl embedded_hal::delay::DelayUs for FakeDelayer { - fn delay_us(&mut self, us: u32) { - std::thread::sleep(std::time::Duration::from_micros(u64::from(us))); +impl embedded_hal::delay::DelayNs for FakeDelayer { + fn delay_ns(&mut self, ns: u32) { + std::thread::sleep(std::time::Duration::from_nanos(u64::from(ns))); } } diff --git a/src/lib.rs b/src/lib.rs index 7b41145..be78233 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,32 +27,17 @@ //! # use core::convert::Infallible; //! # use core::fmt; //! # impl ErrorType for DummySpi { -//! # type Error = Infallible; +//! # type Error = Infallible; //! # } //! # impl embedded_hal::spi::SpiDevice for DummySpi { -//! # -//! # fn transaction(&mut self, operations: &mut [Operation<'_, u8>]) -> Result<(), Self::Error> { -//! # Ok(()) -//! # } -//! # -//! # fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { -//! # Ok(()) -//! # } -//! # -//! # fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> { -//! # Ok(()) -//! # } -//! # -//! # fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { -//! # Ok(()) -//! # } -//! # -//! # fn transfer_in_place(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { -//! # Ok(()) -//! # } +//! # fn transaction(&mut self, operations: &mut [Operation<'_, u8>]) -> Result<(), Self::Error> { Ok(()) } +//! # fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { Ok(()) } +//! # fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> { Ok(()) } +//! # fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { Ok(()) } +//! # fn transfer_in_place(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { Ok(()) } //! # } //! # impl embedded_hal::digital::ErrorType for DummyCsPin { -//! # type Error = Infallible; +//! # type Error = Infallible; //! # } //! # impl embedded_hal::digital::OutputPin for DummyCsPin { //! # fn set_low(&mut self) -> Result<(), Self::Error> { Ok(()) } @@ -61,8 +46,8 @@ //! # impl embedded_sdmmc::TimeSource for DummyTimeSource { //! # fn get_timestamp(&self) -> embedded_sdmmc::Timestamp { embedded_sdmmc::Timestamp::from_fat(0, 0) } //! # } -//! # impl embedded_hal::delay::DelayUs for DummyDelayer { -//! # fn delay_us(&mut self, us: u32) {} +//! # impl embedded_hal::delay::DelayNs for DummyDelayer { +//! # fn delay_ns(&mut self, ns: u32) {} //! # } //! # impl fmt::Write for DummyUart { //! # fn write_str(&mut self, s: &str) -> fmt::Result { Ok(()) } diff --git a/src/sdcard/mod.rs b/src/sdcard/mod.rs index 89e0310..ea302e2 100644 --- a/src/sdcard/mod.rs +++ b/src/sdcard/mod.rs @@ -32,11 +32,11 @@ use crate::{debug, warn}; /// /// If you're not sure how to get a [`SpiDevice`], you may use one of the implementations /// in the [`embedded-hal-bus`] crate, providing a wrapped version of your platform's HAL-provided -/// [`SpiBus`] and [`DelayUs`] as well as our [`DummyCsPin`] in the constructor. +/// [`SpiBus`] and [`DelayNs`] as well as our [`DummyCsPin`] in the constructor. /// /// [`SpiDevice`]: embedded_hal::spi::SpiDevice /// [`SpiBus`]: embedded_hal::spi::SpiBus -/// [`DelayUs`]: embedded_hal::delay::DelayUs +/// [`DelayNs`]: embedded_hal::delay::DelayNs /// [`embedded-hal-bus`]: https://docs.rs/embedded-hal-bus pub struct DummyCsPin; @@ -71,7 +71,7 @@ pub struct SdCard where SPI: embedded_hal::spi::SpiDevice, CS: embedded_hal::digital::OutputPin, - DELAYER: embedded_hal::delay::DelayUs, + DELAYER: embedded_hal::delay::DelayNs, { inner: RefCell>, } @@ -80,7 +80,7 @@ impl SdCard where SPI: embedded_hal::spi::SpiDevice, CS: embedded_hal::digital::OutputPin, - DELAYER: embedded_hal::delay::DelayUs, + DELAYER: embedded_hal::delay::DelayNs, { /// Create a new SD/MMC Card driver using a raw SPI interface. /// @@ -195,7 +195,7 @@ impl BlockDevice for SdCard where SPI: embedded_hal::spi::SpiDevice, CS: embedded_hal::digital::OutputPin, - DELAYER: embedded_hal::delay::DelayUs, + DELAYER: embedded_hal::delay::DelayNs, { type Error = Error; @@ -246,7 +246,7 @@ struct SdCardInner where SPI: embedded_hal::spi::SpiDevice, CS: embedded_hal::digital::OutputPin, - DELAYER: embedded_hal::delay::DelayUs, + DELAYER: embedded_hal::delay::DelayNs, { spi: SPI, cs: CS, @@ -259,7 +259,7 @@ impl SdCardInner where SPI: embedded_hal::spi::SpiDevice, CS: embedded_hal::digital::OutputPin, - DELAYER: embedded_hal::delay::DelayUs, + DELAYER: embedded_hal::delay::DelayNs, { /// Read one or more blocks, starting at the given block index. fn read(&mut self, blocks: &mut [Block], start_block_idx: BlockIdx) -> Result<(), Error> { @@ -620,7 +620,7 @@ where fn transfer_byte(&mut self, out: u8) -> Result { let mut read_buf = [0u8; 1]; self.spi - .transfer(&mut read_buf,&[out]) + .transfer(&mut read_buf, &[out]) .map_err(|_| Error::Transport)?; Ok(read_buf[0]) } @@ -791,7 +791,7 @@ impl Delay { /// `Ok(())`. fn delay(&mut self, delayer: &mut T, err: Error) -> Result<(), Error> where - T: embedded_hal::delay::DelayUs, + T: embedded_hal::delay::DelayNs, { if self.retries_left == 0 { Err(err) From 13712926d315eb9a170c488a937e2cef99668b0d Mon Sep 17 00:00:00 2001 From: Simon Struck Date: Thu, 11 Jan 2024 19:59:50 +0100 Subject: [PATCH 23/36] Fix doc warnings --- src/sdcard/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sdcard/mod.rs b/src/sdcard/mod.rs index ea302e2..8f0dbb9 100644 --- a/src/sdcard/mod.rs +++ b/src/sdcard/mod.rs @@ -67,6 +67,8 @@ impl embedded_hal::digital::OutputPin for DummyCsPin { /// actual CS pin. Then provide the actual CS pin to [`SdCard`]'s constructor. /// /// All the APIs take `&self` - mutability is handled using an inner `RefCell`. +/// +/// [`SpiDevice`]: embedded_hal::spi::SpiDevice pub struct SdCard where SPI: embedded_hal::spi::SpiDevice, From 7e3390613a4743e0bf9cf13f94a3ec345b181dee Mon Sep 17 00:00:00 2001 From: Simon Struck Date: Thu, 11 Jan 2024 20:14:24 +0100 Subject: [PATCH 24/36] Annotate readme_test.rs to make section clearer --- examples/readme_test.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/readme_test.rs b/examples/readme_test.rs index 4c8ee0c..153b758 100644 --- a/examples/readme_test.rs +++ b/examples/readme_test.rs @@ -94,11 +94,14 @@ impl From for Error { } fn main() -> Result<(), Error> { + // BEGIN Fake stuff that will be replaced with real peripherals let spi_bus = RefCell::new(FakeSpiBus()); let delay = FakeDelayer(); let sdmmc_spi = embedded_hal_bus::spi::RefCellDevice::new(&spi_bus, DummyCsPin, delay); let sdmmc_cs = FakeCs(); let time_source = FakeTimesource(); + // END Fake stuff that will be replaced with real peripherals + // Build an SD Card interface out of an SPI device, a chip-select pin and the delay object let sdcard = embedded_sdmmc::SdCard::new(sdmmc_spi, sdmmc_cs, delay); // Get the card size (this also triggers card initialisation because it's not been done yet) From fe739b45ef5793827c2aaafccf82701058e996dc Mon Sep 17 00:00:00 2001 From: Simon Struck Date: Thu, 11 Jan 2024 20:14:58 +0100 Subject: [PATCH 25/36] Commented in doctest --- src/lib.rs | 114 ++++++++++++++++++++++++++--------------------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9247ea1..7cf671f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,63 +17,63 @@ //! suitable for reading SD and SDHC cards over SPI. //! //! ```rust,no_run -//! # struct DummySpi; -//! # struct DummyCsPin; -//! # struct DummyUart; -//! # struct DummyTimeSource; -//! # struct DummyDelayer; -//! # use embedded_hal::spi::Operation; -//! # use embedded_hal::spi::ErrorType; -//! # use core::convert::Infallible; -//! # use core::fmt; -//! # impl ErrorType for DummySpi { -//! # type Error = Infallible; -//! # } -//! # impl embedded_hal::spi::SpiDevice for DummySpi { -//! # fn transaction(&mut self, operations: &mut [Operation<'_, u8>]) -> Result<(), Self::Error> { Ok(()) } -//! # fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { Ok(()) } -//! # fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> { Ok(()) } -//! # fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { Ok(()) } -//! # fn transfer_in_place(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { Ok(()) } -//! # } -//! # impl embedded_hal::digital::ErrorType for DummyCsPin { -//! # type Error = Infallible; -//! # } -//! # impl embedded_hal::digital::OutputPin for DummyCsPin { -//! # fn set_low(&mut self) -> Result<(), Self::Error> { Ok(()) } -//! # fn set_high(&mut self) -> Result<(), Self::Error> { Ok(()) } -//! # } -//! # impl embedded_sdmmc::TimeSource for DummyTimeSource { -//! # fn get_timestamp(&self) -> embedded_sdmmc::Timestamp { embedded_sdmmc::Timestamp::from_fat(0, 0) } -//! # } -//! # impl embedded_hal::delay::DelayNs for DummyDelayer { -//! # fn delay_ns(&mut self, ns: u32) {} -//! # } -//! # impl fmt::Write for DummyUart { -//! # fn write_str(&mut self, s: &str) -> fmt::Result { Ok(()) } -//! # } -//! # use embedded_sdmmc::VolumeManager; -//! # fn main() -> Result<(), embedded_sdmmc::Error> { -//! # let mut sdmmc_spi = DummySpi; -//! # let mut sdmmc_cs = DummyCsPin; -//! # let time_source = DummyTimeSource; -//! # let delayer = DummyDelayer; -//! # let sdcard = embedded_sdmmc::SdCard::new(sdmmc_spi, sdmmc_cs, delayer); -//! # println!("Card size is {} bytes", sdcard.num_bytes()?); -//! # let mut volume_mgr = VolumeManager::new(sdcard, time_source); -//! # let mut volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?; -//! # println!("Volume 0: {:?}", volume0); -//! # let mut root_dir = volume0.open_root_dir()?; -//! # let mut my_file = root_dir.open_file_in_dir("MY_FILE.TXT", embedded_sdmmc::Mode::ReadOnly)?; -//! # while !my_file.is_eof() { -//! # let mut buffer = [0u8; 32]; -//! # let num_read = my_file.read(&mut buffer)?; -//! # for b in &buffer[0..num_read] { -//! # print!("{}", *b as char); -//! # } -//! # } -//! # Ok(()) -//! # } +//! struct DummySpi; +//! struct DummyCsPin; +//! struct DummyUart; +//! struct DummyTimeSource; +//! struct DummyDelayer; +//! use embedded_hal::spi::Operation; +//! use embedded_hal::spi::ErrorType; +//! use core::convert::Infallible; +//! use core::fmt; +//! impl ErrorType for DummySpi { +//! type Error = Infallible; +//! } +//! impl embedded_hal::spi::SpiDevice for DummySpi { +//! fn transaction(&mut self, operations: &mut [Operation<'_, u8>]) -> Result<(), Self::Error> { Ok(()) } +//! fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { Ok(()) } +//! fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> { Ok(()) } +//! fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { Ok(()) } +//! fn transfer_in_place(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { Ok(()) } +//! } +//! impl embedded_hal::digital::ErrorType for DummyCsPin { +//! type Error = Infallible; +//! } +//! impl embedded_hal::digital::OutputPin for DummyCsPin { +//! fn set_low(&mut self) -> Result<(), Self::Error> { Ok(()) } +//! fn set_high(&mut self) -> Result<(), Self::Error> { Ok(()) } +//! } +//! impl embedded_sdmmc::TimeSource for DummyTimeSource { +//! fn get_timestamp(&self) -> embedded_sdmmc::Timestamp { embedded_sdmmc::Timestamp::from_fat(0, 0) } +//! } +//! impl embedded_hal::delay::DelayNs for DummyDelayer { +//! fn delay_ns(&mut self, ns: u32) {} +//! } +//! impl fmt::Write for DummyUart { +//! fn write_str(&mut self, s: &str) -> fmt::Result { Ok(()) } +//! } +//! use embedded_sdmmc::VolumeManager; +//! fn main() -> Result<(), embedded_sdmmc::Error> { +//! let mut sdmmc_spi = DummySpi; +//! let mut sdmmc_cs = DummyCsPin; +//! let time_source = DummyTimeSource; +//! let delayer = DummyDelayer; +//! let sdcard = embedded_sdmmc::SdCard::new(sdmmc_spi, sdmmc_cs, delayer); +//! println!("Card size is {} bytes", sdcard.num_bytes()?); +//! let mut volume_mgr = VolumeManager::new(sdcard, time_source); +//! let mut volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?; +//! println!("Volume 0: {:?}", volume0); +//! let mut root_dir = volume0.open_root_dir()?; +//! let mut my_file = root_dir.open_file_in_dir("MY_FILE.TXT", embedded_sdmmc::Mode::ReadOnly)?; +//! while !my_file.is_eof() { +//! let mut buffer = [0u8; 32]; +//! let num_read = my_file.read(&mut buffer)?; +//! for b in &buffer[0..num_read] { +//! print!("{}", *b as char); +//! } +//! } +//! Ok(()) +//! } //! ``` //! //! ## Features From 4c31f43db99bf0ce9e6185d268b7242a1b9cc9bf Mon Sep 17 00:00:00 2001 From: Simon Struck Date: Thu, 11 Jan 2024 20:19:37 +0100 Subject: [PATCH 26/36] Improve formatting of doctest --- src/lib.rs | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7cf671f..643a4c6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,7 @@ //! use embedded_hal::spi::ErrorType; //! use core::convert::Infallible; //! use core::fmt; +//! //! impl ErrorType for DummySpi { //! type Error = Infallible; //! } @@ -53,26 +54,27 @@ //! fn write_str(&mut self, s: &str) -> fmt::Result { Ok(()) } //! } //! use embedded_sdmmc::VolumeManager; +//! //! fn main() -> Result<(), embedded_sdmmc::Error> { -//! let mut sdmmc_spi = DummySpi; -//! let mut sdmmc_cs = DummyCsPin; -//! let time_source = DummyTimeSource; -//! let delayer = DummyDelayer; -//! let sdcard = embedded_sdmmc::SdCard::new(sdmmc_spi, sdmmc_cs, delayer); -//! println!("Card size is {} bytes", sdcard.num_bytes()?); -//! let mut volume_mgr = VolumeManager::new(sdcard, time_source); -//! let mut volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?; -//! println!("Volume 0: {:?}", volume0); -//! let mut root_dir = volume0.open_root_dir()?; -//! let mut my_file = root_dir.open_file_in_dir("MY_FILE.TXT", embedded_sdmmc::Mode::ReadOnly)?; -//! while !my_file.is_eof() { -//! let mut buffer = [0u8; 32]; -//! let num_read = my_file.read(&mut buffer)?; -//! for b in &buffer[0..num_read] { -//! print!("{}", *b as char); +//! let mut sdmmc_spi = DummySpi; +//! let mut sdmmc_cs = DummyCsPin; +//! let time_source = DummyTimeSource; +//! let delayer = DummyDelayer; +//! let sdcard = embedded_sdmmc::SdCard::new(sdmmc_spi, sdmmc_cs, delayer); +//! println!("Card size is {} bytes", sdcard.num_bytes()?); +//! let mut volume_mgr = VolumeManager::new(sdcard, time_source); +//! let mut volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?; +//! println!("Volume 0: {:?}", volume0); +//! let mut root_dir = volume0.open_root_dir()?; +//! let mut my_file = root_dir.open_file_in_dir("MY_FILE.TXT", embedded_sdmmc::Mode::ReadOnly)?; +//! while !my_file.is_eof() { +//! let mut buffer = [0u8; 32]; +//! let num_read = my_file.read(&mut buffer)?; +//! for b in &buffer[0..num_read] { +//! print!("{}", *b as char); +//! } //! } -//! } -//! Ok(()) +//! Ok(()) //! } //! ``` //! From 959b670dd35aec05a074217ec120fbc74153573b Mon Sep 17 00:00:00 2001 From: "Jonathan Pallant (Ferrous Systems)" Date: Fri, 12 Jan 2024 09:37:07 +0000 Subject: [PATCH 27/36] Clean up example code. If we put it in a function we need much less boiler plate to make it compile. --- src/lib.rs | 60 ++++++++++++------------------------------------------ 1 file changed, 13 insertions(+), 47 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 643a4c6..11db6e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,57 +16,23 @@ //! couldn't work with a USB Thumb Drive, but we only supply a `BlockDevice` //! suitable for reading SD and SDHC cards over SPI. //! -//! ```rust,no_run -//! struct DummySpi; -//! struct DummyCsPin; -//! struct DummyUart; -//! struct DummyTimeSource; -//! struct DummyDelayer; -//! use embedded_hal::spi::Operation; -//! use embedded_hal::spi::ErrorType; -//! use core::convert::Infallible; -//! use core::fmt; +//! ```rust +//! use embedded_sdmmc::{Error, Mode, SdCard, SdCardError, TimeSource, VolumeIdx, VolumeManager}; //! -//! impl ErrorType for DummySpi { -//! type Error = Infallible; -//! } -//! impl embedded_hal::spi::SpiDevice for DummySpi { -//! fn transaction(&mut self, operations: &mut [Operation<'_, u8>]) -> Result<(), Self::Error> { Ok(()) } -//! fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { Ok(()) } -//! fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> { Ok(()) } -//! fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { Ok(()) } -//! fn transfer_in_place(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { Ok(()) } -//! } -//! impl embedded_hal::digital::ErrorType for DummyCsPin { -//! type Error = Infallible; -//! } -//! impl embedded_hal::digital::OutputPin for DummyCsPin { -//! fn set_low(&mut self) -> Result<(), Self::Error> { Ok(()) } -//! fn set_high(&mut self) -> Result<(), Self::Error> { Ok(()) } -//! } -//! impl embedded_sdmmc::TimeSource for DummyTimeSource { -//! fn get_timestamp(&self) -> embedded_sdmmc::Timestamp { embedded_sdmmc::Timestamp::from_fat(0, 0) } -//! } -//! impl embedded_hal::delay::DelayNs for DummyDelayer { -//! fn delay_ns(&mut self, ns: u32) {} -//! } -//! impl fmt::Write for DummyUart { -//! fn write_str(&mut self, s: &str) -> fmt::Result { Ok(()) } -//! } -//! use embedded_sdmmc::VolumeManager; -//! -//! fn main() -> Result<(), embedded_sdmmc::Error> { -//! let mut sdmmc_spi = DummySpi; -//! let mut sdmmc_cs = DummyCsPin; -//! let time_source = DummyTimeSource; -//! let delayer = DummyDelayer; -//! let sdcard = embedded_sdmmc::SdCard::new(sdmmc_spi, sdmmc_cs, delayer); +//! fn example(spi: S, cs: CS, delay: D, ts: T) -> Result<(), Error> +//! where +//! S: embedded_hal::spi::SpiDevice, +//! CS: embedded_hal::digital::OutputPin, +//! D: embedded_hal::delay::DelayNs, +//! T: TimeSource, +//! { +//! let sdcard = SdCard::new(spi, cs, delay); //! println!("Card size is {} bytes", sdcard.num_bytes()?); -//! let mut volume_mgr = VolumeManager::new(sdcard, time_source); -//! let mut volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?; +//! let mut volume_mgr = VolumeManager::new(sdcard, ts); +//! let mut volume0 = volume_mgr.open_volume(VolumeIdx(0))?; //! println!("Volume 0: {:?}", volume0); //! let mut root_dir = volume0.open_root_dir()?; -//! let mut my_file = root_dir.open_file_in_dir("MY_FILE.TXT", embedded_sdmmc::Mode::ReadOnly)?; +//! 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]; //! let num_read = my_file.read(&mut buffer)?; From d8bcdf71f538b7ca05a100933e8decb5b5861d6e Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Tue, 19 Dec 2023 14:58:31 +0000 Subject: [PATCH 28/36] Rename FileNotFound to NotFound Sometimes you get this error if a *directory* was not found, so it was confusing. --- src/fat/volume.rs | 18 +++++++++--------- src/lib.rs | 4 ++-- src/volume_mgr.rs | 4 ++-- tests/directories.rs | 8 ++++---- tests/open_files.rs | 2 +- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/fat/volume.rs b/src/fat/volume.rs index c41591f..05ffc0e 100644 --- a/src/fat/volume.rs +++ b/src/fat/volume.rs @@ -575,7 +575,7 @@ impl FatVolume { match_name, block, ) { - Err(Error::FileNotFound) => continue, + Err(Error::NotFound) => continue, x => return x, } } @@ -592,7 +592,7 @@ impl FatVolume { current_cluster = None; } } - Err(Error::FileNotFound) + Err(Error::NotFound) } FatSpecificInfo::Fat32(fat32_info) => { let mut current_cluster = match dir.cluster { @@ -609,7 +609,7 @@ impl FatVolume { match_name, block, ) { - Err(Error::FileNotFound) => continue, + Err(Error::NotFound) => continue, x => return x, } } @@ -619,7 +619,7 @@ impl FatVolume { _ => None, } } - Err(Error::FileNotFound) + Err(Error::NotFound) } } } @@ -653,7 +653,7 @@ impl FatVolume { return Ok(dir_entry.get_entry(fat_type, block, start)); } } - Err(Error::FileNotFound) + Err(Error::NotFound) } /// Delete an entry from the given directory @@ -691,7 +691,7 @@ impl FatVolume { // 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) { - Err(Error::FileNotFound) => { + Err(Error::NotFound) => { // Carry on } x => { @@ -731,7 +731,7 @@ impl FatVolume { 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) { - Err(Error::FileNotFound) => { + Err(Error::NotFound) => { // Carry on continue; } @@ -755,7 +755,7 @@ impl FatVolume { } // If we get here we never found the right entry in any of the // blocks that made up the directory - Err(Error::FileNotFound) + Err(Error::NotFound) } /// Deletes a directory entry from a block of directory entries. @@ -790,7 +790,7 @@ impl FatVolume { .map_err(Error::DeviceError); } } - Err(Error::FileNotFound) + Err(Error::NotFound) } /// Finds the next free cluster after the start_cluster and before end_cluster diff --git a/src/lib.rs b/src/lib.rs index 11db6e2..ebf9a4d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -159,8 +159,8 @@ where TooManyOpenFiles, /// Bad handle given BadHandle, - /// That file doesn't exist - FileNotFound, + /// That file or directory doesn't exist + NotFound, /// You can't open a file twice or delete an open file FileAlreadyOpen, /// You can't open a directory twice diff --git a/src/volume_mgr.rs b/src/volume_mgr.rs index 95c09ed..06a61af 100644 --- a/src/volume_mgr.rs +++ b/src/volume_mgr.rs @@ -493,7 +493,7 @@ where } _ => { // We are opening a non-existant file, and that's not OK. - return Err(Error::FileNotFound); + return Err(Error::NotFound); } }; @@ -905,7 +905,7 @@ where Ok(_entry) => { return Err(Error::FileAlreadyExists); } - Err(Error::FileNotFound) => { + Err(Error::NotFound) => { // perfect, let's make it } Err(e) => { diff --git a/tests/directories.rs b/tests/directories.rs index af1cf87..2c0bc22 100644 --- a/tests/directories.rs +++ b/tests/directories.rs @@ -316,7 +316,7 @@ fn find_dir_entry() { assert!(matches!( volume_mgr.find_directory_entry(root_dir, "README.TXS"), - Err(embedded_sdmmc::Error::FileNotFound) + Err(embedded_sdmmc::Error::NotFound) )); } @@ -345,7 +345,7 @@ fn delete_file() { assert!(matches!( volume_mgr.delete_file_in_dir(root_dir, "README2.TXT"), - Err(embedded_sdmmc::Error::FileNotFound) + Err(embedded_sdmmc::Error::NotFound) )); volume_mgr.close_file(file).unwrap(); @@ -356,12 +356,12 @@ fn delete_file() { assert!(matches!( volume_mgr.delete_file_in_dir(root_dir, "README.TXT"), - Err(embedded_sdmmc::Error::FileNotFound) + Err(embedded_sdmmc::Error::NotFound) )); assert!(matches!( volume_mgr.open_file_in_dir(root_dir, "README.TXT", Mode::ReadOnly), - Err(embedded_sdmmc::Error::FileNotFound) + Err(embedded_sdmmc::Error::NotFound) )); } diff --git a/tests/open_files.rs b/tests/open_files.rs index 9608a15..dcdaadf 100644 --- a/tests/open_files.rs +++ b/tests/open_files.rs @@ -67,7 +67,7 @@ fn open_files() { assert!(matches!( volume_mgr.open_file_in_dir(root_dir, "README.TXS", Mode::ReadOnly), - Err(Error::FileNotFound) + Err(Error::NotFound) )); // Create a new file From b173f7bf2b1592fbe87bf5dcf9e30a12be46a048 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Tue, 19 Dec 2023 15:01:06 +0000 Subject: [PATCH 29/36] Tools for relative path handling. * Converting "" to a ShortFileName, gives you ShortFileName::this_dir() or ".<10 spaces>", which is the special entry at the top of every directory. * You can open the same directory multiple times. This is required if you want to walk a path, but also hold open a directory in that path. * Lets you open "." and get a duplicate handle to a directory * Lets you mutate a Directory object to point at a child, without having to make a new Directory object and drop the old one. --- src/filesystem/directory.rs | 21 +++++++++++++++++++ src/filesystem/filename.rs | 14 ++++++++++++- src/volume_mgr.rs | 42 ++++++++++++++++++++++--------------- tests/directories.rs | 14 ++++++------- 4 files changed, 65 insertions(+), 26 deletions(-) diff --git a/src/filesystem/directory.rs b/src/filesystem/directory.rs index 684fc47..f0a7bb0 100644 --- a/src/filesystem/directory.rs +++ b/src/filesystem/directory.rs @@ -121,6 +121,19 @@ where Ok(d.to_directory(self.volume_mgr)) } + /// Change to a directory, mutating this object. + /// + /// You can then read the directory entries with `iterate_dir` and `open_file_in_dir`. + pub fn change_dir(&mut self, name: N) -> Result<(), Error> + where + N: ToShortFileName, + { + let d = self.volume_mgr.open_dir(self.raw_directory, name)?; + self.volume_mgr.close_dir(self.raw_directory).unwrap(); + self.raw_directory = d; + Ok(()) + } + /// Look in a directory for a named file. pub fn find_directory_entry(&mut self, name: N) -> Result> where @@ -161,6 +174,14 @@ where self.volume_mgr.delete_file_in_dir(self.raw_directory, name) } + /// Make a directory inside this directory + pub fn make_dir_in_dir(&mut self, name: N) -> Result<(), Error> + where + N: ToShortFileName, + { + self.volume_mgr.make_dir_in_dir(self.raw_directory, name) + } + /// Convert back to a raw directory pub fn to_raw_directory(self) -> RawDirectory { let d = self.raw_directory; diff --git a/src/filesystem/filename.rs b/src/filesystem/filename.rs index 120eaa5..a8327ea 100644 --- a/src/filesystem/filename.rs +++ b/src/filesystem/filename.rs @@ -91,6 +91,11 @@ impl ShortFileName { return Ok(ShortFileName::parent_dir()); } + // Special case `.` (or blank), which means "this directory". + if name.is_empty() || name == "." { + return Ok(ShortFileName::this_dir()); + } + let mut idx = 0; let mut seen_dot = false; for ch in name.bytes() { @@ -318,9 +323,16 @@ mod test { assert_eq!(sfn, ShortFileName::create_from_str("1.C").unwrap()); } + #[test] + fn filename_empty() { + assert_eq!( + ShortFileName::create_from_str("").unwrap(), + ShortFileName::this_dir() + ); + } + #[test] fn filename_bad() { - assert!(ShortFileName::create_from_str("").is_err()); assert!(ShortFileName::create_from_str(" ").is_err()); assert!(ShortFileName::create_from_str("123456789").is_err()); assert!(ShortFileName::create_from_str("12345678.ABCD").is_err()); diff --git a/src/volume_mgr.rs b/src/volume_mgr.rs index 06a61af..54d1a5f 100644 --- a/src/volume_mgr.rs +++ b/src/volume_mgr.rs @@ -12,9 +12,9 @@ use crate::filesystem::{ SearchIdGenerator, TimeSource, ToShortFileName, MAX_FILE_SIZE, }; use crate::{ - debug, Block, BlockCount, BlockDevice, BlockIdx, Error, RawVolume, Volume, VolumeIdx, - VolumeInfo, VolumeType, PARTITION_ID_FAT16, PARTITION_ID_FAT16_LBA, PARTITION_ID_FAT32_CHS_LBA, - PARTITION_ID_FAT32_LBA, + debug, Block, 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; @@ -206,11 +206,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, volume: RawVolume) -> Result> { - for dir in self.open_dirs.iter() { - if dir.cluster == ClusterId::ROOT_DIR && dir.volume_id == volume { - return Err(Error::DirAlreadyOpen); - } - } + // Opening a root directory twice is OK let directory_id = RawDirectory(self.id_generator.get()); let dir_info = DirectoryInfo { @@ -229,6 +225,8 @@ where /// Open a directory. /// /// You can then read the directory entries with `iterate_dir` and `open_file_in_dir`. + /// + /// Passing "." as the name results in opening the `parent_dir` a second time. pub fn open_dir( &mut self, parent_dir: RawDirectory, @@ -245,9 +243,25 @@ where 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 short_file_name = name.to_short_filename().map_err(Error::FilenameError)?; + let parent_dir_info = &self.open_dirs[parent_dir_idx]; // Open the directory - let parent_dir_info = &self.open_dirs[parent_dir_idx]; + if short_file_name == ShortFileName::this_dir() { + // short-cut (root dir doesn't have ".") + let directory_id = RawDirectory(self.id_generator.get()); + let dir_info = DirectoryInfo { + directory_id, + volume_id: self.open_volumes[volume_idx].volume_id, + cluster: parent_dir_info.cluster, + }; + + self.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)? @@ -260,14 +274,8 @@ where return Err(Error::OpenedFileAsDir); } - // Check it's not already open - for d in self.open_dirs.iter() { - if d.volume_id == self.open_volumes[volume_idx].volume_id - && d.cluster == dir_entry.cluster - { - return Err(Error::DirAlreadyOpen); - } - } + // We don't check if the directory is already open - directories hold + // no cached state and so opening a directory twice is allowable. // Remember this open directory. let directory_id = RawDirectory(self.id_generator.get()); diff --git a/tests/directories.rs b/tests/directories.rs index 2c0bc22..df9154f 100644 --- a/tests/directories.rs +++ b/tests/directories.rs @@ -237,10 +237,9 @@ fn open_dir_twice() { .open_root_dir(fat32_volume) .expect("open root dir"); - assert!(matches!( - volume_mgr.open_root_dir(fat32_volume), - Err(embedded_sdmmc::Error::DirAlreadyOpen) - )); + let root_dir2 = volume_mgr + .open_root_dir(fat32_volume) + .expect("open it again"); assert!(matches!( volume_mgr.open_dir(root_dir, "README.TXT"), @@ -251,13 +250,12 @@ fn open_dir_twice() { .open_dir(root_dir, "TEST") .expect("open test dir"); - assert!(matches!( - volume_mgr.open_dir(root_dir, "TEST"), - Err(embedded_sdmmc::Error::DirAlreadyOpen) - )); + let test_dir2 = volume_mgr.open_dir(root_dir, "TEST").unwrap(); volume_mgr.close_dir(root_dir).expect("close root dir"); volume_mgr.close_dir(test_dir).expect("close test dir"); + volume_mgr.close_dir(test_dir2).expect("close test dir"); + volume_mgr.close_dir(root_dir2).expect("close test dir"); assert!(matches!( volume_mgr.close_dir(test_dir), From fdd59dcb91326214d2648eba79406a1ca3e771b5 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Tue, 19 Dec 2023 15:07:32 +0000 Subject: [PATCH 30/36] Clean up shell. Each command is now a function. --- examples/shell.rs | 272 ++++++++++++++++++++++++++-------------------- 1 file changed, 157 insertions(+), 115 deletions(-) diff --git a/examples/shell.rs b/examples/shell.rs index 433fb3a..adec9c4 100644 --- a/examples/shell.rs +++ b/examples/shell.rs @@ -4,10 +4,12 @@ use std::io::prelude::*; -use embedded_sdmmc::{Error, RawDirectory, RawVolume, VolumeIdx, VolumeManager}; +use embedded_sdmmc::{Error as EsError, RawDirectory, RawVolume, VolumeIdx, VolumeManager}; use crate::linux::{Clock, LinuxBlockDevice}; +type Error = EsError; + mod linux; struct VolumeState { @@ -30,19 +32,149 @@ impl Context { s.path.clone() } - fn process_line(&mut self, line: &str) -> Result<(), Error> { + /// Print some help text + fn help(&mut self) -> Result<(), Error> { + println!("Commands:"); + println!("\thelp -> this help text"); + println!("\t: -> change volume/partition"); + println!("\tstat -> print volume manager status"); + println!("\tdir [] -> do a directory listing"); + println!("\tcd .. -> go up a level"); + println!("\tcd -> change into directory "); + println!("\tcat -> print a text file"); + println!("\thexdump -> print a binary file"); + println!("\tmkdir -> create an empty directory"); + println!("\tquit -> exits the program"); + println!(); + println!("Paths can be:"); + println!(); + println!("\t* Bare names, like `FILE.DAT`"); + println!("\t* Relative, like `../SOMEDIR/FILE.DAT` or `./FILE.DAT`"); + println!("\t* Absolute, like `1:/SOMEDIR/FILE.DAT`"); + Ok(()) + } + + /// Print volume manager status + fn stat(&mut self) -> Result<(), Error> { + println!("Status:\n{:#?}", self.volume_mgr); + Ok(()) + } + + /// Print a directory listing + fn dir(&mut self) -> Result<(), Error> { + let Some(s) = &self.volumes[self.current_volume] else { + println!("That volume isn't available"); + return Ok(()); + }; + self.volume_mgr.iterate_dir(s.directory, |entry| { + println!( + "{:12} {:9} {} {} {:X?} {:?}", + entry.name, entry.size, entry.ctime, entry.mtime, entry.cluster, entry.attributes + ); + })?; + Ok(()) + } + + /// Change into + /// + /// An arg of `..` goes up one level + fn cd(&mut self, filename: &str) -> Result<(), Error> { + let Some(s) = &mut self.volumes[self.current_volume] else { + println!("This volume isn't available"); + return Ok(()); + }; + let d = self.volume_mgr.open_dir(s.directory, filename)?; + self.volume_mgr.close_dir(s.directory)?; + s.directory = d; + if filename == ".." { + s.path.pop(); + } else { + s.path.push(filename.to_owned()); + } + Ok(()) + } + + /// print a text file + fn cat(&mut self, filename: &str) -> Result<(), Error> { + let Some(s) = &mut self.volumes[self.current_volume] else { + println!("This volume isn't available"); + return Ok(()); + }; + let mut f = self + .volume_mgr + .open_file_in_dir(s.directory, filename, embedded_sdmmc::Mode::ReadOnly)? + .to_file(&mut self.volume_mgr); + let mut data = Vec::new(); + while !f.is_eof() { + let mut buffer = vec![0u8; 65536]; + let n = f.read(&mut buffer)?; + // read n bytes + data.extend_from_slice(&buffer[0..n]); + println!("Read {} bytes, making {} total", n, data.len()); + } + if let Ok(s) = std::str::from_utf8(&data) { + println!("{}", s); + } else { + println!("I'm afraid that file isn't UTF-8 encoded"); + } + Ok(()) + } + + /// print a binary file + fn hexdump(&mut self, filename: &str) -> Result<(), Error> { + let Some(s) = &mut self.volumes[self.current_volume] else { + println!("This volume isn't available"); + return Ok(()); + }; + let mut f = self + .volume_mgr + .open_file_in_dir(s.directory, filename, embedded_sdmmc::Mode::ReadOnly)? + .to_file(&mut self.volume_mgr); + let mut data = Vec::new(); + while !f.is_eof() { + let mut buffer = vec![0u8; 65536]; + let n = f.read(&mut buffer)?; + // read n bytes + data.extend_from_slice(&buffer[0..n]); + println!("Read {} bytes, making {} total", n, data.len()); + } + for (idx, chunk) in data.chunks(16).enumerate() { + print!("{:08x} | ", idx * 16); + for b in chunk { + print!("{:02x} ", b); + } + for _padding in 0..(16 - chunk.len()) { + print!(" "); + } + print!("| "); + for b in chunk { + print!( + "{}", + if b.is_ascii_graphic() { + *b as char + } else { + '.' + } + ); + } + println!(); + } + Ok(()) + } + + /// create a directory + fn mkdir(&mut self, dir_name: &str) -> Result<(), Error> { + let Some(s) = &mut self.volumes[self.current_volume] else { + println!("This volume isn't available"); + return Ok(()); + }; + // make the dir + self.volume_mgr.make_dir_in_dir(s.directory, dir_name) + } + + fn process_line(&mut self, line: &str) -> Result<(), Error> { if line == "help" { - println!("Commands:"); - println!("\thelp -> this help text"); - println!("\t: -> change volume/partition"); - println!("\tdir -> do a directory listing"); - println!("\tstat -> print volume manager status"); - println!("\tcat -> print a text file"); - println!("\thexdump -> print a binary file"); - println!("\tcd .. -> go up a level"); - println!("\tcd -> change into "); - println!("\tmkdir -> create a directory called "); - println!("\tquit -> exits the program"); + self.help()?; } else if line == "0:" { self.current_volume = 0; } else if line == "1:" { @@ -51,108 +183,18 @@ impl Context { self.current_volume = 2; } else if line == "3:" { self.current_volume = 3; - } else if line == "stat" { - println!("Status:\n{:#?}", self.volume_mgr); } else if line == "dir" { - let Some(s) = &self.volumes[self.current_volume] else { - println!("That volume isn't available"); - return Ok(()); - }; - self.volume_mgr.iterate_dir(s.directory, |entry| { - println!( - "{:12} {:9} {} {} {:X?} {:?}", - entry.name, - entry.size, - entry.ctime, - entry.mtime, - entry.cluster, - entry.attributes - ); - })?; - } else if let Some(arg) = line.strip_prefix("cd ") { - let arg = arg.trim(); - let Some(s) = &mut self.volumes[self.current_volume] else { - println!("This volume isn't available"); - return Ok(()); - }; - let d = self.volume_mgr.open_dir(s.directory, arg)?; - self.volume_mgr.close_dir(s.directory)?; - s.directory = d; - if arg == ".." { - s.path.pop(); - } else { - s.path.push(arg.to_owned()); - } - } else if let Some(arg) = line.strip_prefix("cat ") { - let arg = arg.trim(); - let Some(s) = &mut self.volumes[self.current_volume] else { - println!("This volume isn't available"); - return Ok(()); - }; - let mut f = self - .volume_mgr - .open_file_in_dir(s.directory, arg, embedded_sdmmc::Mode::ReadOnly)? - .to_file(&mut self.volume_mgr); - let mut data = Vec::new(); - while !f.is_eof() { - let mut buffer = vec![0u8; 65536]; - let n = f.read(&mut buffer)?; - // read n bytes - data.extend_from_slice(&buffer[0..n]); - println!("Read {} bytes, making {} total", n, data.len()); - } - if let Ok(s) = std::str::from_utf8(&data) { - println!("{}", s); - } else { - println!("I'm afraid that file isn't UTF-8 encoded"); - } - } else if let Some(arg) = line.strip_prefix("hexdump ") { - let arg = arg.trim(); - let Some(s) = &mut self.volumes[self.current_volume] else { - println!("This volume isn't available"); - return Ok(()); - }; - let mut f = self - .volume_mgr - .open_file_in_dir(s.directory, arg, embedded_sdmmc::Mode::ReadOnly)? - .to_file(&mut self.volume_mgr); - let mut data = Vec::new(); - while !f.is_eof() { - let mut buffer = vec![0u8; 65536]; - let n = f.read(&mut buffer)?; - // read n bytes - data.extend_from_slice(&buffer[0..n]); - println!("Read {} bytes, making {} total", n, data.len()); - } - for (idx, chunk) in data.chunks(16).enumerate() { - print!("{:08x} | ", idx * 16); - for b in chunk { - print!("{:02x} ", b); - } - for _padding in 0..(16 - chunk.len()) { - print!(" "); - } - print!("| "); - for b in chunk { - print!( - "{}", - if b.is_ascii_graphic() { - *b as char - } else { - '.' - } - ); - } - println!(); - } - } else if let Some(arg) = line.strip_prefix("mkdir ") { - let arg = arg.trim(); - let Some(s) = &mut self.volumes[self.current_volume] else { - println!("This volume isn't available"); - return Ok(()); - }; - // make the dir - self.volume_mgr.make_dir_in_dir(s.directory, arg)?; + self.dir()?; + } else if line == "stat" { + self.stat()?; + } else if let Some(dirname) = line.strip_prefix("cd ") { + self.cd(dirname.trim())?; + } else if let Some(filename) = line.strip_prefix("cat ") { + self.cat(filename.trim())?; + } else if let Some(filename) = line.strip_prefix("hexdump ") { + self.hexdump(filename.trim())?; + } else if let Some(dirname) = line.strip_prefix("mkdir ") { + self.mkdir(dirname.trim())?; } else { println!("Unknown command {line:?} - try 'help' for help"); } @@ -178,7 +220,7 @@ impl Drop for Context { } } -fn main() -> Result<(), Error> { +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()); From 2b07077dae65ae30d770ca71efdb46539c2fbc9d Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Tue, 19 Dec 2023 15:10:45 +0000 Subject: [PATCH 31/36] Shell supports relative paths. Handle relative paths in "mkdir", "dir", "cat" and "hexdump". Not currently supported in "cd". --- examples/shell.rs | 135 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 105 insertions(+), 30 deletions(-) diff --git a/examples/shell.rs b/examples/shell.rs index adec9c4..bd5b276 100644 --- a/examples/shell.rs +++ b/examples/shell.rs @@ -61,12 +61,11 @@ impl Context { } /// Print a directory listing - fn dir(&mut self) -> Result<(), Error> { - let Some(s) = &self.volumes[self.current_volume] else { - println!("That volume isn't available"); - return Ok(()); - }; - self.volume_mgr.iterate_dir(s.directory, |entry| { + fn dir(&mut self, path: &str) -> 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} {} {} {:X?} {:?}", entry.name, entry.size, entry.ctime, entry.mtime, entry.cluster, entry.attributes @@ -84,7 +83,9 @@ impl Context { return Ok(()); }; let d = self.volume_mgr.open_dir(s.directory, filename)?; - self.volume_mgr.close_dir(s.directory)?; + self.volume_mgr + .close_dir(s.directory) + .expect("close open dir"); s.directory = d; if filename == ".." { s.path.pop(); @@ -96,14 +97,9 @@ impl Context { /// print a text file fn cat(&mut self, filename: &str) -> Result<(), Error> { - let Some(s) = &mut self.volumes[self.current_volume] else { - println!("This volume isn't available"); - return Ok(()); - }; - let mut f = self - .volume_mgr - .open_file_in_dir(s.directory, filename, embedded_sdmmc::Mode::ReadOnly)? - .to_file(&mut self.volume_mgr); + 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 mut data = Vec::new(); while !f.is_eof() { let mut buffer = vec![0u8; 65536]; @@ -122,14 +118,9 @@ impl Context { /// print a binary file fn hexdump(&mut self, filename: &str) -> Result<(), Error> { - let Some(s) = &mut self.volumes[self.current_volume] else { - println!("This volume isn't available"); - return Ok(()); - }; - let mut f = self - .volume_mgr - .open_file_in_dir(s.directory, filename, embedded_sdmmc::Mode::ReadOnly)? - .to_file(&mut self.volume_mgr); + 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 mut data = Vec::new(); while !f.is_eof() { let mut buffer = vec![0u8; 65536]; @@ -164,12 +155,9 @@ impl Context { /// create a directory fn mkdir(&mut self, dir_name: &str) -> Result<(), Error> { - let Some(s) = &mut self.volumes[self.current_volume] else { - println!("This volume isn't available"); - return Ok(()); - }; - // make the dir - self.volume_mgr.make_dir_in_dir(s.directory, dir_name) + let (dir, filename) = self.resolve_filename(dir_name)?; + let mut dir = dir.to_directory(&mut self.volume_mgr); + dir.make_dir_in_dir(filename) } fn process_line(&mut self, line: &str) -> Result<(), Error> { @@ -184,7 +172,9 @@ impl Context { } else if line == "3:" { self.current_volume = 3; } else if line == "dir" { - self.dir()?; + self.dir(".")?; + } else if let Some(dirname) = line.strip_prefix("dir ") { + self.dir(dirname.trim())?; } else if line == "stat" { self.stat()?; } else if let Some(dirname) = line.strip_prefix("cd ") { @@ -200,6 +190,91 @@ impl Context { } Ok(()) } + + /// Resolves an existing directory. + /// + /// Converts a string path into a directory handle. + /// + /// * Bare names (no leading `.`, `/` or `N:/`) are mapped to the current + /// directory in the current volume. + /// * Relative names, like `../SOMEDIR` or `./SOMEDIR`, traverse + /// starting at the current volume and directory. + /// * Absolute, like `1:/SOMEDIR/OTHERDIR` start at the given volume. + fn resolve_existing_directory(&mut self, full_path: &str) -> 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()) + } + + /// Resolves a filename. + /// + /// Converts a string path into a directory handle and a name within that + /// directory (that may or may not exist). + /// + /// * Bare names (no leading `.`, `/` or `N:/`) are mapped to the current + /// directory in the current volume. + /// * Relative names, like `../SOMEDIR/SOMEFILE` or `./SOMEDIR/SOMEFILE`, traverse + /// starting at the current volume and directory. + /// * Absolute, like `1:/SOMEDIR/SOMEFILE` start at the given volume. + fn resolve_filename<'path>( + &mut self, + full_path: &'path str, + ) -> Result<(RawDirectory, &'path str), Error> { + let mut volume_idx = self.current_volume; + let mut path_fragments = if full_path.is_empty() { "." } else { full_path }; + let mut is_absolute = false; + if let Some((given_volume_idx, remainder)) = + Self::is_absolute(full_path, VolumeIdx(self.current_volume)) + { + volume_idx = given_volume_idx.0; + path_fragments = remainder; + is_absolute = true; + } + let Some(s) = &mut self.volumes[volume_idx] else { + return Err(Error::NoSuchVolume); + }; + let mut work_dir = if is_absolute { + // relative to root + self.volume_mgr + .open_root_dir(s.volume)? + .to_directory(&mut self.volume_mgr) + } else { + // relative to CWD + self.volume_mgr + .open_dir(s.directory, ".")? + .to_directory(&mut self.volume_mgr) + }; + + let mut path_iter = path_fragments.split('/').peekable(); + let mut last_piece = "."; + while let Some(fragment) = path_iter.next() { + if path_iter.peek().is_none() { + // this is the last piece + last_piece = fragment; + break; + } + work_dir.change_dir(fragment)?; + } + + Ok((work_dir.to_raw_directory(), last_piece)) + } + + /// Is this an absolute path? + fn is_absolute(path: &str, current_volume: VolumeIdx) -> Option<(VolumeIdx, &str)> { + if let Some(remainder) = path.strip_prefix("0:/") { + Some((VolumeIdx(0), remainder)) + } else if let Some(remainder) = path.strip_prefix("1:/") { + Some((VolumeIdx(1), remainder)) + } else if let Some(remainder) = path.strip_prefix("2:/") { + Some((VolumeIdx(2), remainder)) + } else if let Some(remainder) = path.strip_prefix("3:/") { + Some((VolumeIdx(3), remainder)) + } else { + path.strip_prefix('/') + .map(|remainder| (current_volume, remainder)) + } + } } impl Drop for Context { From d68a73f18bde76f8d24750d526b8b8942e371cf0 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Tue, 19 Dec 2023 21:51:35 +0000 Subject: [PATCH 32/36] cd now has path support. Also volumes are letters. --- examples/shell.rs | 304 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 234 insertions(+), 70 deletions(-) diff --git a/examples/shell.rs b/examples/shell.rs index bd5b276..c7fd10a 100644 --- a/examples/shell.rs +++ b/examples/shell.rs @@ -1,6 +1,73 @@ //! A simple shell demo for embedded-sdmmc //! -//! Presents a basic command prompt which implements some basic MS-DOS style shell commands. +//! Presents a basic command prompt which implements some basic MS-DOS style +//! shell commands. +//! +//! Note that `embedded_sdmmc` itself does not care about 'paths' - only +//! accessing files and directories on on disk, relative to some previously +//! opened directory. A 'path' is an operating-system level construct, and can +//! vary greatly (see MS-DOS paths vs POSIX paths). This example, however, +//! implements an MS-DOS style Path API over the top of embedded-sdmmc. Feel +//! free to copy it if it suits your particular application. +//! +//! The four primary partitions are scanned on the given disk image on start-up. +//! Any valid FAT16 or FAT32 volumes are mounted, and given volume labels from +//! `A:` to `C:`, like MS-DOS. Also like MS-DOS, file and directory names use +//! the `8.3` format, like `FILENAME.TXT`. Long filenames are not supported. +//! +//! Unlike MS-DOS, this application uses the POSIX `/` as the directory +//! separator. +//! +//! Every volume has its own *current working directory*. The shell has one +//! *current volume* selected but it remembers the *current working directory* +//! for the unselected volumes. +//! +//! A path comprises: +//! +//! * An optional volume specifier, like `A:` +//! * If the volume specifier is not given, the current volume is used. +//! * An optional `/` to indicate this is an absolute path, not a relative path +//! * If this is a relative path, traversal starts at the Current Working +//! Directory for the volume +//! * An optional sequence of directory names, each followed by a `/` +//! * An optional final filename +//! * If this is missing, then `.` is the default (which selects the +//! containing directory) +//! +//! An *expanded path* has all optional components, and works independently of +//! whichever volume is currently selected, or the current working directory +//! within that volume. The empty path (`""`) is invalid, but commands may +//! assume that in the absence of a path argument they are to use the current +//! working directory on the current volume. +//! +//! As an example, imagine that volume `A:` is the current volume, and we have +//! these current working directories: +//! +//! * `A:` has a CWD of `/CATS` +//! * `B:` has a CWD of `/DOGS` +//! +//! The following path expansions would occur: +//! +//! | Given Path | Volume | Absolute | Directory Names | Final Filename | Expanded Path | +//! | --------------------------- | ------- | -------- | ------------------ | -------------- | ------------------------------ | +//! | `NAMES.CSV` | Current | No | `[]` | `NAMES.CSV` | `A:/CATS/NAMES.CSV` | +//! | `./NAMES.CSV` | Current | No | `[.]` | `NAMES.CSV` | `A:/CATS/NAMES.CSV` | +//! | `BACKUP.000/` | Current | No | `[BACKUP.000]` | None | `A:/CATS/BACKUP.000/.` | +//! | `BACKUP.000/NAMES.CSV` | Current | No | `[BACKUP.000]` | `NAMES.CSV` | `A:/CATS/BACKUP.000/NAMES.CSV` | +//! | `/BACKUP.000/NAMES.CSV` | Current | Yes | `[BACKUP.000]` | `NAMES.CSV` | `A:/BACKUP.000/NAMES.CSV` | +//! | `../BACKUP.000/NAMES.CSV` | Current | No | `[.., BACKUP.000]` | `NAMES.CSV` | `A:/BACKUP.000/NAMES.CSV` | +//! | `A:NAMES.CSV` | `A:` | No | `[]` | `NAMES.CSV` | `A:/CATS/NAMES.CSV` | +//! | `A:./NAMES.CSV` | `A:` | No | `[.]` | `NAMES.CSV` | `A:/CATS/NAMES.CSV` | +//! | `A:BACKUP.000/` | `A:` | No | `[BACKUP.000]` | None | `A:/CATS/BACKUP.000/.` | +//! | `A:BACKUP.000/NAMES.CSV` | `A:` | No | `[BACKUP.000]` | `NAMES.CSV` | `A:/CATS/BACKUP.000/NAMES.CSV` | +//! | `A:/BACKUP.000/NAMES.CSV` | `A:` | Yes | `[BACKUP.000]` | `NAMES.CSV` | `A:/BACKUP.000/NAMES.CSV` | +//! | `A:../BACKUP.000/NAMES.CSV` | `A:` | No | `[.., BACKUP.000]` | `NAMES.CSV` | `A:/BACKUP.000/NAMES.CSV` | +//! | `B:NAMES.CSV` | `B:` | No | `[]` | `NAMES.CSV` | `B:/DOGS/NAMES.CSV` | +//! | `B:./NAMES.CSV` | `B:` | No | `[.]` | `NAMES.CSV` | `B:/DOGS/NAMES.CSV` | +//! | `B:BACKUP.000/` | `B:` | No | `[BACKUP.000]` | None | `B:/DOGS/BACKUP.000/.` | +//! | `B:BACKUP.000/NAMES.CSV` | `B:` | No | `[BACKUP.000]` | `NAMES.CSV` | `B:/DOGS/BACKUP.000/NAMES.CSV` | +//! | `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::*; @@ -12,6 +79,100 @@ type Error = EsError; mod linux; +/// Represents a path on a volume within `embedded_sdmmc`. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +struct Path(str); + +impl std::ops::Deref for Path { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Path { + /// Create a new Path from a string slice. + /// + /// The `Path` borrows the string slice. No validation is performed on the + /// path. + fn new + ?Sized>(s: &S) -> &Path { + unsafe { &*(s.as_ref() as *const str as *const Path) } + } + + /// Does this path specify a volume? + fn volume(&self) -> Option { + let mut char_iter = self.chars(); + match (char_iter.next(), char_iter.next()) { + (Some(volume), Some(':')) => Some(volume), + _ => None, + } + } + + /// Is this an absolute path? + fn is_absolute(&self) -> bool { + let tail = self.without_volume(); + tail.starts_with('/') + } + + /// Iterate through the directory components. + /// + /// This will exclude the final path component (i.e. it will not include the + /// 'basename'). + fn iterate_dirs(&self) -> impl Iterator { + let path = self.without_volume(); + let path = path.strip_prefix('/').unwrap_or(path); + if let Some((directories, _basename)) = path.rsplit_once('/') { + directories.split('/') + } else { + "".split('/') + } + } + + /// Iterate through all the components. + /// + /// This will include the final path component (i.e. it will include the + /// 'basename'). + fn iterate_components(&self) -> impl Iterator { + let path = self.without_volume(); + let path = path.strip_prefix('/').unwrap_or(path); + path.split('/') + } + + /// Get the final component of this path (the 'basename'). + fn basename(&self) -> Option<&str> { + if let Some((_, basename)) = self.rsplit_once('/') { + if basename.is_empty() { + None + } else { + Some(basename) + } + } else { + let path = self.without_volume(); + Some(path) + } + } + + /// Return this [`Path`], but without a leading volume. + fn without_volume(&self) -> &Path { + if let Some((volume, tail)) = self.split_once(':') { + // only support single char drive letters + if volume.chars().count() == 1 { + return Path::new(tail); + } + } + self + } +} + +impl PartialEq for Path { + fn eq(&self, other: &str) -> bool { + let s: &str = self; + s == other + } +} + struct VolumeState { directory: RawDirectory, volume: RawVolume, @@ -61,42 +222,52 @@ impl Context { } /// Print a directory listing - fn dir(&mut self, path: &str) -> Result<(), Error> { + fn dir(&mut 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} {} {} {:X?} {:?}", + "{:12} {:9} {} {} {:08X?} {:?}", entry.name, entry.size, entry.ctime, entry.mtime, entry.cluster, entry.attributes ); })?; Ok(()) } - /// Change into + /// Change into `` /// - /// An arg of `..` goes up one level - fn cd(&mut self, filename: &str) -> Result<(), Error> { - let Some(s) = &mut self.volumes[self.current_volume] else { - println!("This volume isn't available"); - return Ok(()); + /// * An arg of `..` goes up one level + /// * A relative arg like `../FOO` goes up a level and then into the `FOO` + /// sub-folder, starting from the current directory on the current volume + /// * An absolute path like `1:/FOO` changes the CWD on Volume 1 to path + /// `/FOO` + fn cd(&mut 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"); + return Err(Error::NoSuchVolume); }; - let d = self.volume_mgr.open_dir(s.directory, filename)?; self.volume_mgr .close_dir(s.directory) .expect("close open dir"); s.directory = d; - if filename == ".." { - s.path.pop(); - } else { - s.path.push(filename.to_owned()); + 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 { + s.path.push(fragment.to_owned()); + } } Ok(()) } /// print a text file - fn cat(&mut self, filename: &str) -> Result<(), Error> { + fn cat(&mut 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)?; @@ -117,7 +288,7 @@ impl Context { } /// print a binary file - fn hexdump(&mut self, filename: &str) -> Result<(), Error> { + fn hexdump(&mut 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)?; @@ -154,7 +325,7 @@ impl Context { } /// create a directory - fn mkdir(&mut self, dir_name: &str) -> Result<(), Error> { + fn mkdir(&mut 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) @@ -163,28 +334,28 @@ impl Context { fn process_line(&mut self, line: &str) -> Result<(), Error> { if line == "help" { self.help()?; - } else if line == "0:" { + } else if line == "A:" || line == "a:" { self.current_volume = 0; - } else if line == "1:" { + } else if line == "B:" || line == "b:" { self.current_volume = 1; - } else if line == "2:" { + } else if line == "C:" || line == "c:" { self.current_volume = 2; - } else if line == "3:" { + } else if line == "D:" || line == "d:" { self.current_volume = 3; } else if line == "dir" { - self.dir(".")?; - } else if let Some(dirname) = line.strip_prefix("dir ") { - self.dir(dirname.trim())?; + self.dir(Path::new("."))?; + } else if let Some(path) = line.strip_prefix("dir ") { + self.dir(Path::new(path.trim()))?; } else if line == "stat" { self.stat()?; - } else if let Some(dirname) = line.strip_prefix("cd ") { - self.cd(dirname.trim())?; - } else if let Some(filename) = line.strip_prefix("cat ") { - self.cat(filename.trim())?; - } else if let Some(filename) = line.strip_prefix("hexdump ") { - self.hexdump(filename.trim())?; - } else if let Some(dirname) = line.strip_prefix("mkdir ") { - self.mkdir(dirname.trim())?; + } else if let Some(path) = line.strip_prefix("cd ") { + self.cd(Path::new(path.trim()))?; + } else if let Some(path) = line.strip_prefix("cat ") { + self.cat(Path::new(path.trim()))?; + } else if let Some(path) = line.strip_prefix("hexdump ") { + self.hexdump(Path::new(path.trim()))?; + } else if let Some(path) = line.strip_prefix("mkdir ") { + self.mkdir(Path::new(path.trim()))?; } else { println!("Unknown command {line:?} - try 'help' for help"); } @@ -200,13 +371,25 @@ impl Context { /// * Relative names, like `../SOMEDIR` or `./SOMEDIR`, traverse /// starting at the current volume and directory. /// * Absolute, like `1:/SOMEDIR/OTHERDIR` start at the given volume. - fn resolve_existing_directory(&mut self, full_path: &str) -> Result { + 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()) } + /// Either get the volume from the path, or pick the current volume. + fn resolve_volume(&self, path: &Path) -> Result { + match path.volume() { + None => Ok(self.current_volume), + Some('A' | 'a') => Ok(0), + Some('B' | 'b') => Ok(1), + Some('C' | 'c') => Ok(2), + Some('D' | 'd') => Ok(3), + Some(_) => Err(Error::NoSuchVolume), + } + } + /// Resolves a filename. /// /// Converts a string path into a directory handle and a name within that @@ -219,22 +402,13 @@ impl Context { /// * Absolute, like `1:/SOMEDIR/SOMEFILE` start at the given volume. fn resolve_filename<'path>( &mut self, - full_path: &'path str, + full_path: &'path Path, ) -> Result<(RawDirectory, &'path str), Error> { - let mut volume_idx = self.current_volume; - let mut path_fragments = if full_path.is_empty() { "." } else { full_path }; - let mut is_absolute = false; - if let Some((given_volume_idx, remainder)) = - Self::is_absolute(full_path, VolumeIdx(self.current_volume)) - { - volume_idx = given_volume_idx.0; - path_fragments = remainder; - is_absolute = true; - } + let volume_idx = self.resolve_volume(full_path)?; let Some(s) = &mut self.volumes[volume_idx] else { return Err(Error::NoSuchVolume); }; - let mut work_dir = if is_absolute { + let mut work_dir = if full_path.is_absolute() { // relative to root self.volume_mgr .open_root_dir(s.volume)? @@ -246,33 +420,23 @@ impl Context { .to_directory(&mut self.volume_mgr) }; - let mut path_iter = path_fragments.split('/').peekable(); - let mut last_piece = "."; - while let Some(fragment) = path_iter.next() { - if path_iter.peek().is_none() { - // this is the last piece - last_piece = fragment; - break; - } + for fragment in full_path.iterate_dirs() { work_dir.change_dir(fragment)?; } - - Ok((work_dir.to_raw_directory(), last_piece)) + Ok(( + work_dir.to_raw_directory(), + full_path.basename().unwrap_or("."), + )) } - /// Is this an absolute path? - fn is_absolute(path: &str, current_volume: VolumeIdx) -> Option<(VolumeIdx, &str)> { - if let Some(remainder) = path.strip_prefix("0:/") { - Some((VolumeIdx(0), remainder)) - } else if let Some(remainder) = path.strip_prefix("1:/") { - Some((VolumeIdx(1), remainder)) - } else if let Some(remainder) = path.strip_prefix("2:/") { - Some((VolumeIdx(2), remainder)) - } else if let Some(remainder) = path.strip_prefix("3:/") { - Some((VolumeIdx(3), remainder)) - } else { - path.strip_prefix('/') - .map(|remainder| (current_volume, remainder)) + /// Convert a volume index to a letter + fn volume_to_letter(volume: usize) -> char { + match volume { + 0 => 'A', + 1 => 'B', + 2 => 'C', + 3 => 'D', + _ => panic!("Invalid volume ID"), } } } @@ -314,7 +478,7 @@ 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", volume_no,); + println!("Volume # {}: found", Context::volume_to_letter(volume_no)); match ctx.volume_mgr.open_root_dir(volume) { Ok(root_dir) => { ctx.volumes[volume_no] = Some(VolumeState { @@ -350,7 +514,7 @@ fn main() -> Result<(), Error> { }; loop { - print!("{}:/", ctx.current_volume); + print!("{}:/", Context::volume_to_letter(ctx.current_volume)); print!("{}", ctx.current_path().join("/")); print!("> "); std::io::stdout().flush().unwrap(); From 69c48fd712d69e303c2923b022caea8366a54602 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Tue, 19 Dec 2023 22:14:21 +0000 Subject: [PATCH 33/36] Add tree command. --- examples/shell.rs | 57 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/examples/shell.rs b/examples/shell.rs index c7fd10a..751ce53 100644 --- a/examples/shell.rs +++ b/examples/shell.rs @@ -71,7 +71,9 @@ use std::io::prelude::*; -use embedded_sdmmc::{Error as EsError, RawDirectory, RawVolume, VolumeIdx, VolumeManager}; +use embedded_sdmmc::{ + Error as EsError, RawDirectory, RawVolume, ShortFileName, VolumeIdx, VolumeManager, +}; use crate::linux::{Clock, LinuxBlockDevice}; @@ -200,6 +202,7 @@ impl Context { println!("\t: -> change volume/partition"); println!("\tstat -> print volume manager status"); println!("\tdir [] -> do a directory listing"); + println!("\ttree [] -> do a recursive directory listing"); println!("\tcd .. -> go up a level"); println!("\tcd -> change into directory "); println!("\tcat -> print a text file"); @@ -235,6 +238,54 @@ impl Context { Ok(()) } + /// Print a recursive directory listing for the given path + fn tree(&mut 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) + } + + /// 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); + let mut children = Vec::new(); + dir.iterate_dir(|entry| { + println!( + "{:12} {:9} {} {} {:08X?} {:?}", + entry.name, entry.size, entry.ctime, entry.mtime, entry.cluster, entry.attributes + ); + if entry.attributes.is_directory() + && entry.name != ShortFileName::this_dir() + && entry.name != ShortFileName::parent_dir() + { + 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); + 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(()) + } + /// Change into `` /// /// * An arg of `..` goes up one level @@ -346,6 +397,10 @@ impl Context { self.dir(Path::new("."))?; } else if let Some(path) = line.strip_prefix("dir ") { self.dir(Path::new(path.trim()))?; + } else if line == "tree" { + self.tree(Path::new("."))?; + } else if let Some(path) = line.strip_prefix("tree ") { + self.tree(Path::new(path.trim()))?; } else if line == "stat" { self.stat()?; } else if let Some(path) = line.strip_prefix("cd ") { From 2fab5492646ef605201d22f5b9fbc2d489761cac Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Wed, 20 Dec 2023 16:47:23 +0000 Subject: [PATCH 34/36] Fix example volume names. --- examples/shell.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/shell.rs b/examples/shell.rs index 751ce53..0e41ce1 100644 --- a/examples/shell.rs +++ b/examples/shell.rs @@ -12,7 +12,7 @@ //! //! The four primary partitions are scanned on the given disk image on start-up. //! Any valid FAT16 or FAT32 volumes are mounted, and given volume labels from -//! `A:` to `C:`, like MS-DOS. Also like MS-DOS, file and directory names use +//! `A:` to `D:`, like MS-DOS. Also like MS-DOS, file and directory names use //! the `8.3` format, like `FILENAME.TXT`. Long filenames are not supported. //! //! Unlike MS-DOS, this application uses the POSIX `/` as the directory @@ -214,7 +214,7 @@ impl Context { println!(); println!("\t* Bare names, like `FILE.DAT`"); println!("\t* Relative, like `../SOMEDIR/FILE.DAT` or `./FILE.DAT`"); - println!("\t* Absolute, like `1:/SOMEDIR/FILE.DAT`"); + println!("\t* Absolute, like `B:/SOMEDIR/FILE.DAT`"); Ok(()) } @@ -291,7 +291,7 @@ impl Context { /// * An arg of `..` goes up one level /// * A relative arg like `../FOO` goes up a level and then into the `FOO` /// sub-folder, starting from the current directory on the current volume - /// * An absolute path like `1:/FOO` changes the CWD on Volume 1 to path + /// * An absolute path like `B:/FOO` changes the CWD on Volume 1 to path /// `/FOO` fn cd(&mut self, full_path: &Path) -> Result<(), Error> { let volume_idx = self.resolve_volume(full_path)?; @@ -425,7 +425,7 @@ impl Context { /// directory in the current volume. /// * Relative names, like `../SOMEDIR` or `./SOMEDIR`, traverse /// starting at the current volume and directory. - /// * Absolute, like `1:/SOMEDIR/OTHERDIR` start at the given volume. + /// * 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); @@ -454,7 +454,7 @@ impl Context { /// directory in the current volume. /// * Relative names, like `../SOMEDIR/SOMEFILE` or `./SOMEDIR/SOMEFILE`, traverse /// starting at the current volume and directory. - /// * Absolute, like `1:/SOMEDIR/SOMEFILE` start at the given volume. + /// * Absolute, like `B:/SOMEDIR/SOMEFILE` start at the given volume. fn resolve_filename<'path>( &mut self, full_path: &'path Path, From 8f0e37c659a52077264f1204c94d87422e93956e Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Wed, 20 Dec 2023 16:50:52 +0000 Subject: [PATCH 35/36] Update CHANGELOG --- CHANGELOG.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24357bc..5fe70e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,12 @@ The format is based on [Keep a Changelog] and this project adheres to [Semantic * `Volume`, `Directory` and `File` are now smart! They hold references to the thing they were made from, and will clean themselves up when dropped. The trade-off is you can can't open multiple volumes, directories or files at the same time. * Renamed the old types to `RawVolume`, `RawDirectory` and `RawFile` * New method `make_dir_in_dir` -* Fixed long-standing bug that caused an integer overflow when a FAT32 directory - was longer than one cluster ([#74]) +* Fixed long-standing bug that caused an integer overflow when a FAT32 directory was longer than one cluster ([#74]) +* Updated 'shell' example to support `mkdir`, `tree` and relative/absolute paths +* Renamed `Error::FileNotFound` to `Error::NotFound` +* New API `change_dir` which changes a directory to point to some child directory (or the parent) without opening a new directory. +* Empty strings and `"."` convert to `ShortFileName::this_dir()` +* You can now open directories multiple times without error [#74]: https://github.com/rust-embedded-community/embedded-sdmmc-rs/issues/74 From afdcec7c3758c58e29491798bc28b49019f7f26a Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 4 Feb 2024 18:07:10 +0000 Subject: [PATCH 36/36] Updated to 0.7.0 --- CHANGELOG.md | 37 +++++++++++++++++++++++++++---------- Cargo.toml | 8 ++++---- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fe70e5..edb029f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,17 +6,33 @@ The format is based on [Keep a Changelog] and this project adheres to [Semantic ## [Unreleased] -* `Volume`, `Directory` and `File` are now smart! They hold references to the thing they were made from, and will clean themselves up when dropped. The trade-off is you can can't open multiple volumes, directories or files at the same time. -* Renamed the old types to `RawVolume`, `RawDirectory` and `RawFile` -* New method `make_dir_in_dir` -* Fixed long-standing bug that caused an integer overflow when a FAT32 directory was longer than one cluster ([#74]) -* Updated 'shell' example to support `mkdir`, `tree` and relative/absolute paths -* Renamed `Error::FileNotFound` to `Error::NotFound` -* New API `change_dir` which changes a directory to point to some child directory (or the parent) without opening a new directory. -* Empty strings and `"."` convert to `ShortFileName::this_dir()` -* You can now open directories multiple times without error +* None + +## [Version 0.7.0] - 2024-02-04 + +## Changed + +- __Breaking Change__: `Volume`, `Directory` and `File` are now smart! They hold references to the thing they were made from, and will clean themselves up when dropped. The trade-off is you can can't open multiple volumes, directories or files at the same time. +- __Breaking Change__: Renamed the old types to `RawVolume`, `RawDirectory` and `RawFile` +- __Breaking Change__: Renamed `Error::FileNotFound` to `Error::NotFound` +- Fixed long-standing bug that caused an integer overflow when a FAT32 directory was longer than one cluster ([#74]) +- You can now open directories multiple times without error +- Updated to [embedded-hal] 1.0 + +## Added + +- `RawVolume`, `RawDirectory` and `RawFile` types (like the old `Volume`, `Directory` and `File` types) +- New method `make_dir_in_dir` +- Empty strings and `"."` convert to `ShortFileName::this_dir()` +- New API `change_dir` which changes a directory to point to some child directory (or the parent) without opening a new directory. +- Updated 'shell' example to support `mkdir`, `tree` and relative/absolute paths + +## Removed + +* None [#74]: https://github.com/rust-embedded-community/embedded-sdmmc-rs/issues/74 +[embedded-hal]: https://crates.io/crates/embedded-hal ## [Version 0.6.0] - 2023-10-20 @@ -121,7 +137,8 @@ The format is based on [Keep a Changelog] and this project adheres to [Semantic [Keep a Changelog]: http://keepachangelog.com/en/1.0.0/ [Semantic Versioning]: http://semver.org/spec/v2.0.0.html -[Unreleased]: https://github.com/rust-embedded-community/embedded-sdmmc-rs/compare/v0.6.0...develop +[Unreleased]: https://github.com/rust-embedded-community/embedded-sdmmc-rs/compare/v0.7.0...develop +[Version 0.7.0]: https://github.com/rust-embedded-community/embedded-sdmmc-rs/compare/v0.7.0...v0.6.0 [Version 0.6.0]: https://github.com/rust-embedded-community/embedded-sdmmc-rs/compare/v0.6.0...v0.5.0 [Version 0.5.0]: https://github.com/rust-embedded-community/embedded-sdmmc-rs/compare/v0.5.0...v0.4.0 [Version 0.4.0]: https://github.com/rust-embedded-community/embedded-sdmmc-rs/compare/v0.4.0...v0.3.0 diff --git a/Cargo.toml b/Cargo.toml index 20322ce..f64a194 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" name = "embedded-sdmmc" readme = "README.md" repository = "https://github.com/rust-embedded-community/embedded-sdmmc-rs" -version = "0.6.0" +version = "0.7.0" [dependencies] byteorder = {version = "1", default-features = false} @@ -18,12 +18,12 @@ heapless = "0.7" log = {version = "0.4", default-features = false, optional = true} [dev-dependencies] +chrono = "0.4" +embedded-hal-bus = "0.1.0" env_logger = "0.10.0" -hex-literal = "0.4.1" flate2 = "1.0" +hex-literal = "0.4.1" sha2 = "0.10" -chrono = "0.4" -embedded-hal-bus = "0.1.0" [features] default = ["log"]