From 1a8cafd396cf580408d448f233632bd48341df97 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 23 Sep 2023 21:50:56 +0100 Subject: [PATCH 1/5] Add a little shell example. You can walk around a disk image and do directory listings. I had to make some more things 'Debug' so I could print the filesystem state. Also our handling of ".." was not right so I fixed that. --- examples/linux/mod.rs | 1 + examples/shell.rs | 151 ++++++++++++++++++++++++++++++++++++ src/fat/ondiskdirentry.rs | 19 +++-- src/filesystem/filename.rs | 6 ++ src/filesystem/search_id.rs | 1 + src/volume_mgr.rs | 5 +- 6 files changed, 177 insertions(+), 6 deletions(-) create mode 100644 examples/shell.rs diff --git a/examples/linux/mod.rs b/examples/linux/mod.rs index 1a1b190..5abb99f 100644 --- a/examples/linux/mod.rs +++ b/examples/linux/mod.rs @@ -74,6 +74,7 @@ impl BlockDevice for LinuxBlockDevice { } } +#[derive(Debug)] pub struct Clock; impl TimeSource for Clock { diff --git a/examples/shell.rs b/examples/shell.rs new file mode 100644 index 0000000..4612bae --- /dev/null +++ b/examples/shell.rs @@ -0,0 +1,151 @@ +//! A simple shell demo for embedded-sdmmc +//! +//! Presents a basic command prompt which implements some basic MS-DOS style shell commands. + +use std::io::prelude::*; + +use embedded_sdmmc::{Directory, Error, Volume, VolumeIdx, VolumeManager}; + +use crate::linux::{Clock, LinuxBlockDevice}; + +mod linux; + +struct State { + directory: Directory, + volume: Volume, +} + +fn main() -> Result<(), Error> { + env_logger::init(); + let mut args = std::env::args().skip(1); + let filename = args.next().unwrap_or_else(|| "/dev/mmcblk0".into()); + let print_blocks = args.find(|x| x == "-v").map(|_| true).unwrap_or(false); + println!("Opening '{filename}'..."); + let lbd = LinuxBlockDevice::new(filename, print_blocks).map_err(Error::DeviceError)?; + let mut volume_mgr: VolumeManager = + VolumeManager::new_with_limits(lbd, Clock, 100); + let stdin = std::io::stdin(); + let mut volumes: [Option; 4] = [None, None, None, None]; + + let mut current_volume = None; + for volume_no in 0..4 { + match volume_mgr.open_volume(VolumeIdx(volume_no)) { + Ok(volume) => { + println!("Volume # {}: found", volume_no,); + match volume_mgr.open_root_dir(volume) { + Ok(root_dir) => { + volumes[volume_no] = Some(State { + directory: root_dir, + volume, + }); + if current_volume.is_none() { + current_volume = Some(volume_no); + } + } + Err(e) => { + println!("Failed to open root directory: {e:?}"); + volume_mgr.close_volume(volume).expect("close volume"); + } + } + } + Err(e) => { + println!("Failed to open volume {volume_no}: {e:?}"); + } + } + } + + let Some(mut current_volume) = current_volume else { + println!("No volumes found in file. Sorry."); + return Ok(()); + }; + + loop { + print!("{}:> ", current_volume); + std::io::stdout().flush().unwrap(); + let mut line = String::new(); + stdin.read_line(&mut line)?; + let line = line.trim(); + log::info!("Got command: {line:?}"); + if line == "quit" { + break; + } else if line == "help" { + println!("Commands:"); + println!("\thelp -> this help text"); + println!("\t: -> change volume/partition"); + println!("\tdir -> do a directory listing"); + println!("\tquit -> exits the program"); + } else if line == "0:" { + current_volume = 0; + } else if line == "1:" { + current_volume = 1; + } else if line == "2:" { + current_volume = 2; + } else if line == "3:" { + current_volume = 3; + } else if line == "stat" { + println!("Status:\n{volume_mgr:#?}"); + } else if line == "dir" { + let Some(s) = &volumes[current_volume] else { + println!("That volume isn't available"); + continue; + }; + let r = volume_mgr.iterate_dir(s.directory, |entry| { + println!( + "{:12} {:9} {} {}", + entry.name, + entry.size, + entry.mtime, + if entry.attributes.is_directory() { + "" + } else { + "" + } + ); + }); + handle("iterating directory", r); + } else if let Some(arg) = line.strip_prefix("cd ") { + let Some(s) = &mut volumes[current_volume] else { + println!("This volume isn't available"); + continue; + }; + match volume_mgr.open_dir(s.directory, arg) { + Ok(d) => { + let r = volume_mgr.close_dir(s.directory); + handle("closing old directory", r); + s.directory = d; + } + Err(e) => { + handle("changing directory", Err(e)); + } + } + } else { + println!("Unknown command {line:?} - try 'help' for help"); + } + } + + for (idx, s) in volumes.into_iter().enumerate() { + if let Some(state) = s { + println!("Unmounting {idx}..."); + let r = volume_mgr.close_dir(state.directory); + handle("closing directory", r); + let r = volume_mgr.close_volume(state.volume); + handle("closing volume", r); + println!("Unmounted {idx}!"); + } + } + + println!("Bye!"); + Ok(()) +} + +fn handle(operation: &str, r: Result<(), Error>) { + if let Err(e) = r { + println!("Error {operation}: {e:?}"); + } +} + +// **************************************************************************** +// +// End Of File +// +// **************************************************************************** diff --git a/src/fat/ondiskdirentry.rs b/src/fat/ondiskdirentry.rs index 8ce3f63..14be74c 100644 --- a/src/fat/ondiskdirentry.rs +++ b/src/fat/ondiskdirentry.rs @@ -148,17 +148,26 @@ impl<'a> OnDiskDirEntry<'a> { entry_block: BlockIdx, entry_offset: u32, ) -> DirEntry { + let attributes = Attributes::create_from_fat(self.raw_attr()); let mut result = DirEntry { name: ShortFileName { contents: [0u8; 11], }, mtime: Timestamp::from_fat(self.write_date(), self.write_time()), ctime: Timestamp::from_fat(self.create_date(), self.create_time()), - attributes: Attributes::create_from_fat(self.raw_attr()), - cluster: if fat_type == FatType::Fat32 { - self.first_cluster_fat32() - } else { - self.first_cluster_fat16() + attributes, + cluster: { + let cluster = if fat_type == FatType::Fat32 { + self.first_cluster_fat32() + } else { + self.first_cluster_fat16() + }; + if cluster == ClusterId::EMPTY && attributes.is_directory() { + // FAT16/FAT32 uses a cluster ID of `0` in the ".." entry to mean 'root directory' + ClusterId::ROOT_DIR + } else { + cluster + } }, size: self.file_size(), entry_block, diff --git a/src/filesystem/filename.rs b/src/filesystem/filename.rs index 096e99c..120eaa5 100644 --- a/src/filesystem/filename.rs +++ b/src/filesystem/filename.rs @@ -85,6 +85,12 @@ impl ShortFileName { let mut sfn = ShortFileName { contents: [b' '; Self::FILENAME_MAX_LEN], }; + + // Special case `..`, which means "parent directory". + if name == ".." { + return Ok(ShortFileName::parent_dir()); + } + let mut idx = 0; let mut seen_dot = false; for ch in name.bytes() { diff --git a/src/filesystem/search_id.rs b/src/filesystem/search_id.rs index f4be611..30c1018 100644 --- a/src/filesystem/search_id.rs +++ b/src/filesystem/search_id.rs @@ -12,6 +12,7 @@ pub struct SearchId(pub(crate) u32); /// Well, it will wrap after `2**32` IDs. But most systems won't open that many /// files, and if they do, they are unlikely to hold one file open and then /// open/close `2**32 - 1` others. +#[derive(Debug)] pub struct SearchIdGenerator { next_id: Wrapping, } diff --git a/src/volume_mgr.rs b/src/volume_mgr.rs index 4cafafa..5abfcbb 100644 --- a/src/volume_mgr.rs +++ b/src/volume_mgr.rs @@ -20,6 +20,7 @@ use heapless::Vec; /// A `VolumeManager` wraps a block device and gives access to the FAT-formatted /// volumes within it. +#[derive(Debug)] pub struct VolumeManager< D, T, @@ -242,6 +243,8 @@ where } }; + debug!("Found dir entry: {:?}", dir_entry); + if !dir_entry.attributes.is_directory() { return Err(Error::OpenedFileAsDir); } @@ -482,7 +485,7 @@ where // Check if it's open already if let Some(dir_entry) = &dir_entry { - if self.file_is_open(volume_info.volume_id, &dir_entry) { + if self.file_is_open(volume_info.volume_id, dir_entry) { return Err(Error::FileAlreadyOpen); } } From 9b5ddf76a26bbf2a8c818d74810ee9d4a4ef02e7 Mon Sep 17 00:00:00 2001 From: Jonathan Pallant Date: Tue, 3 Oct 2023 21:19:36 +0100 Subject: [PATCH 2/5] Remove log line from shell example. Fixes the build. --- examples/shell.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/shell.rs b/examples/shell.rs index 4612bae..8ecf75e 100644 --- a/examples/shell.rs +++ b/examples/shell.rs @@ -65,7 +65,6 @@ fn main() -> Result<(), Error> { let mut line = String::new(); stdin.read_line(&mut line)?; let line = line.trim(); - log::info!("Got command: {line:?}"); if line == "quit" { break; } else if line == "help" { From 0578d4e5d649ae60ec3dac6c6930e3843fbedf9d Mon Sep 17 00:00:00 2001 From: Jonathan Pallant Date: Tue, 3 Oct 2023 21:22:23 +0100 Subject: [PATCH 3/5] Update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9bcb2d..a8aa658 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added -- New examples, `append_file`, `create_file`, `delete_file`, `list_dir` +- New examples, `append_file`, `create_file`, `delete_file`, `list_dir`, `shell` - New test cases `tests/directories.rs`, `tests/read_file.rs` ### Removed From a041eee5648e425c9fb52fc161a72cd2495288b6 Mon Sep 17 00:00:00 2001 From: Jonathan Pallant Date: Tue, 3 Oct 2023 22:10:22 +0100 Subject: [PATCH 4/5] Added hexdump and cat. Also moved the code into a method so we can use ?. Although ? is a pain when your objects cannot safely be dropped. --- examples/shell.rs | 261 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 185 insertions(+), 76 deletions(-) diff --git a/examples/shell.rs b/examples/shell.rs index 8ecf75e..8ec1d81 100644 --- a/examples/shell.rs +++ b/examples/shell.rs @@ -10,9 +10,158 @@ use crate::linux::{Clock, LinuxBlockDevice}; mod linux; -struct State { +struct VolumeState { directory: Directory, volume: Volume, + path: Vec, +} + +struct Context { + volume_mgr: VolumeManager, + volumes: [Option; 4], + current_volume: usize, +} + +impl Context { + fn current_path(&self) -> Vec { + let Some(s) = &self.volumes[self.current_volume] else { + return vec![]; + }; + s.path.clone() + } + + 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!("\tquit -> exits the program"); + } else if line == "0:" { + self.current_volume = 0; + } else if line == "1:" { + self.current_volume = 1; + } else if line == "2:" { + 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} {} {}", + entry.name, + entry.size, + entry.mtime, + if entry.attributes.is_directory() { + "" + } else { + "" + } + ); + })?; + } else if let Some(arg) = line.strip_prefix("cd ") { + 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 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()); + } + 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?; + } 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()); + } + 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(()) + }; + let r = inner(); + self.volume_mgr.close_file(f)?; + r?; + } else { + println!("Unknown command {line:?} - try 'help' for help"); + } + Ok(()) + } } fn main() -> Result<(), Error> { @@ -22,21 +171,25 @@ fn main() -> Result<(), Error> { let print_blocks = args.find(|x| x == "-v").map(|_| true).unwrap_or(false); println!("Opening '{filename}'..."); let lbd = LinuxBlockDevice::new(filename, print_blocks).map_err(Error::DeviceError)?; - let mut volume_mgr: VolumeManager = - VolumeManager::new_with_limits(lbd, Clock, 100); let stdin = std::io::stdin(); - let mut volumes: [Option; 4] = [None, None, None, None]; + + let mut ctx = Context { + volume_mgr: VolumeManager::new_with_limits(lbd, Clock, 100), + volumes: [None, None, None, None], + current_volume: 0, + }; let mut current_volume = None; for volume_no in 0..4 { - match volume_mgr.open_volume(VolumeIdx(volume_no)) { + match ctx.volume_mgr.open_volume(VolumeIdx(volume_no)) { Ok(volume) => { println!("Volume # {}: found", volume_no,); - match volume_mgr.open_root_dir(volume) { + match ctx.volume_mgr.open_root_dir(volume) { Ok(root_dir) => { - volumes[volume_no] = Some(State { + ctx.volumes[volume_no] = Some(VolumeState { directory: root_dir, volume, + path: vec![], }); if current_volume.is_none() { current_volume = Some(volume_no); @@ -44,7 +197,7 @@ fn main() -> Result<(), Error> { } Err(e) => { println!("Failed to open root directory: {e:?}"); - volume_mgr.close_volume(volume).expect("close volume"); + ctx.volume_mgr.close_volume(volume).expect("close volume"); } } } @@ -54,82 +207,44 @@ fn main() -> Result<(), Error> { } } - let Some(mut current_volume) = current_volume else { - println!("No volumes found in file. Sorry."); - return Ok(()); + match current_volume { + Some(n) => { + // Default to the first valid partition + ctx.current_volume = n; + } + None => { + println!("No volumes found in file. Sorry."); + return Ok(()); + } }; loop { - print!("{}:> ", current_volume); + print!("{}:/", ctx.current_volume); + print!("{}", ctx.current_path().join("/")); + print!("> "); std::io::stdout().flush().unwrap(); let mut line = String::new(); stdin.read_line(&mut line)?; let line = line.trim(); if line == "quit" { break; - } else if line == "help" { - println!("Commands:"); - println!("\thelp -> this help text"); - println!("\t: -> change volume/partition"); - println!("\tdir -> do a directory listing"); - println!("\tquit -> exits the program"); - } else if line == "0:" { - current_volume = 0; - } else if line == "1:" { - current_volume = 1; - } else if line == "2:" { - current_volume = 2; - } else if line == "3:" { - current_volume = 3; - } else if line == "stat" { - println!("Status:\n{volume_mgr:#?}"); - } else if line == "dir" { - let Some(s) = &volumes[current_volume] else { - println!("That volume isn't available"); - continue; - }; - let r = volume_mgr.iterate_dir(s.directory, |entry| { - println!( - "{:12} {:9} {} {}", - entry.name, - entry.size, - entry.mtime, - if entry.attributes.is_directory() { - "" - } else { - "" - } - ); - }); - handle("iterating directory", r); - } else if let Some(arg) = line.strip_prefix("cd ") { - let Some(s) = &mut volumes[current_volume] else { - println!("This volume isn't available"); - continue; - }; - match volume_mgr.open_dir(s.directory, arg) { - Ok(d) => { - let r = volume_mgr.close_dir(s.directory); - handle("closing old directory", r); - s.directory = d; - } - Err(e) => { - handle("changing directory", Err(e)); - } - } - } else { - println!("Unknown command {line:?} - try 'help' for help"); + } else if let Err(e) = ctx.process_line(line) { + println!("Error: {:?}", e); } } - for (idx, s) in volumes.into_iter().enumerate() { + 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 = volume_mgr.close_dir(state.directory); - handle("closing directory", r); - let r = volume_mgr.close_volume(state.volume); - handle("closing volume", r); - println!("Unmounted {idx}!"); + let r = ctx.volume_mgr.close_volume(state.volume); + if let Err(e) = r { + println!("Error closing volume: {e:?}"); + } } } @@ -137,12 +252,6 @@ fn main() -> Result<(), Error> { Ok(()) } -fn handle(operation: &str, r: Result<(), Error>) { - if let Err(e) = r { - println!("Error {operation}: {e:?}"); - } -} - // **************************************************************************** // // End Of File From 52e77c9cb3ee5b2a7677ae1056df5f6dd09fb207 Mon Sep 17 00:00:00 2001 From: Jonathan Pallant Date: Sat, 7 Oct 2023 20:49:28 +0100 Subject: [PATCH 5/5] Shell prints all attributes in DIR. --- examples/shell.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/examples/shell.rs b/examples/shell.rs index 8ec1d81..35e9e13 100644 --- a/examples/shell.rs +++ b/examples/shell.rs @@ -59,15 +59,8 @@ impl Context { }; self.volume_mgr.iterate_dir(s.directory, |entry| { println!( - "{:12} {:9} {} {}", - entry.name, - entry.size, - entry.mtime, - if entry.attributes.is_directory() { - "" - } else { - "" - } + "{:12} {:9} {} {:?}", + entry.name, entry.size, entry.mtime, entry.attributes ); })?; } else if let Some(arg) = line.strip_prefix("cd ") {