Skip to content

Commit 94eb91f

Browse files
authored
Merge pull request #100 from rust-embedded-community/add-shell
Add a little shell example.
2 parents e3f807f + 52e77c9 commit 94eb91f

File tree

7 files changed

+279
-7
lines changed

7 files changed

+279
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2626

2727
### Added
2828

29-
- New examples, `append_file`, `create_file`, `delete_file`, `list_dir`
29+
- New examples, `append_file`, `create_file`, `delete_file`, `list_dir`, `shell`
3030
- New test cases `tests/directories.rs`, `tests/read_file.rs`
3131

3232
### Removed

examples/linux/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ impl BlockDevice for LinuxBlockDevice {
7474
}
7575
}
7676

77+
#[derive(Debug)]
7778
pub struct Clock;
7879

7980
impl TimeSource for Clock {

examples/shell.rs

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
//! A simple shell demo for embedded-sdmmc
2+
//!
3+
//! Presents a basic command prompt which implements some basic MS-DOS style shell commands.
4+
5+
use std::io::prelude::*;
6+
7+
use embedded_sdmmc::{Directory, Error, Volume, VolumeIdx, VolumeManager};
8+
9+
use crate::linux::{Clock, LinuxBlockDevice};
10+
11+
mod linux;
12+
13+
struct VolumeState {
14+
directory: Directory,
15+
volume: Volume,
16+
path: Vec<String>,
17+
}
18+
19+
struct Context {
20+
volume_mgr: VolumeManager<LinuxBlockDevice, Clock, 8, 8, 4>,
21+
volumes: [Option<VolumeState>; 4],
22+
current_volume: usize,
23+
}
24+
25+
impl Context {
26+
fn current_path(&self) -> Vec<String> {
27+
let Some(s) = &self.volumes[self.current_volume] else {
28+
return vec![];
29+
};
30+
s.path.clone()
31+
}
32+
33+
fn process_line(&mut self, line: &str) -> Result<(), Error<std::io::Error>> {
34+
if line == "help" {
35+
println!("Commands:");
36+
println!("\thelp -> this help text");
37+
println!("\t<volume>: -> change volume/partition");
38+
println!("\tdir -> do a directory listing");
39+
println!("\tstat -> print volume manager status");
40+
println!("\tcat <file> -> print a text file");
41+
println!("\thexdump <file> -> print a binary file");
42+
println!("\tcd .. -> go up a level");
43+
println!("\tcd <dir> -> change into <dir>");
44+
println!("\tquit -> exits the program");
45+
} else if line == "0:" {
46+
self.current_volume = 0;
47+
} else if line == "1:" {
48+
self.current_volume = 1;
49+
} else if line == "2:" {
50+
self.current_volume = 2;
51+
} else if line == "3:" {
52+
self.current_volume = 3;
53+
} else if line == "stat" {
54+
println!("Status:\n{:#?}", self.volume_mgr);
55+
} else if line == "dir" {
56+
let Some(s) = &self.volumes[self.current_volume] else {
57+
println!("That volume isn't available");
58+
return Ok(());
59+
};
60+
self.volume_mgr.iterate_dir(s.directory, |entry| {
61+
println!(
62+
"{:12} {:9} {} {:?}",
63+
entry.name, entry.size, entry.mtime, entry.attributes
64+
);
65+
})?;
66+
} else if let Some(arg) = line.strip_prefix("cd ") {
67+
let Some(s) = &mut self.volumes[self.current_volume] else {
68+
println!("This volume isn't available");
69+
return Ok(());
70+
};
71+
let d = self.volume_mgr.open_dir(s.directory, arg)?;
72+
self.volume_mgr.close_dir(s.directory)?;
73+
s.directory = d;
74+
if arg == ".." {
75+
s.path.pop();
76+
} else {
77+
s.path.push(arg.to_owned());
78+
}
79+
} else if let Some(arg) = line.strip_prefix("cat ") {
80+
let Some(s) = &mut self.volumes[self.current_volume] else {
81+
println!("This volume isn't available");
82+
return Ok(());
83+
};
84+
let f = self.volume_mgr.open_file_in_dir(
85+
s.directory,
86+
arg,
87+
embedded_sdmmc::Mode::ReadOnly,
88+
)?;
89+
let mut inner = || -> Result<(), Error<std::io::Error>> {
90+
let mut data = Vec::new();
91+
while !self.volume_mgr.file_eof(f)? {
92+
let mut buffer = vec![0u8; 65536];
93+
let n = self.volume_mgr.read(f, &mut buffer)?;
94+
// read n bytes
95+
data.extend_from_slice(&buffer[0..n]);
96+
println!("Read {} bytes, making {} total", n, data.len());
97+
}
98+
if let Ok(s) = std::str::from_utf8(&data) {
99+
println!("{}", s);
100+
} else {
101+
println!("I'm afraid that file isn't UTF-8 encoded");
102+
}
103+
Ok(())
104+
};
105+
let r = inner();
106+
self.volume_mgr.close_file(f)?;
107+
r?;
108+
} else if let Some(arg) = line.strip_prefix("hexdump ") {
109+
let Some(s) = &mut self.volumes[self.current_volume] else {
110+
println!("This volume isn't available");
111+
return Ok(());
112+
};
113+
let f = self.volume_mgr.open_file_in_dir(
114+
s.directory,
115+
arg,
116+
embedded_sdmmc::Mode::ReadOnly,
117+
)?;
118+
let mut inner = || -> Result<(), Error<std::io::Error>> {
119+
let mut data = Vec::new();
120+
while !self.volume_mgr.file_eof(f)? {
121+
let mut buffer = vec![0u8; 65536];
122+
let n = self.volume_mgr.read(f, &mut buffer)?;
123+
// read n bytes
124+
data.extend_from_slice(&buffer[0..n]);
125+
println!("Read {} bytes, making {} total", n, data.len());
126+
}
127+
for (idx, chunk) in data.chunks(16).enumerate() {
128+
print!("{:08x} | ", idx * 16);
129+
for b in chunk {
130+
print!("{:02x} ", b);
131+
}
132+
for _padding in 0..(16 - chunk.len()) {
133+
print!(" ");
134+
}
135+
print!("| ");
136+
for b in chunk {
137+
print!(
138+
"{}",
139+
if b.is_ascii_graphic() {
140+
*b as char
141+
} else {
142+
'.'
143+
}
144+
);
145+
}
146+
println!();
147+
}
148+
Ok(())
149+
};
150+
let r = inner();
151+
self.volume_mgr.close_file(f)?;
152+
r?;
153+
} else {
154+
println!("Unknown command {line:?} - try 'help' for help");
155+
}
156+
Ok(())
157+
}
158+
}
159+
160+
fn main() -> Result<(), Error<std::io::Error>> {
161+
env_logger::init();
162+
let mut args = std::env::args().skip(1);
163+
let filename = args.next().unwrap_or_else(|| "/dev/mmcblk0".into());
164+
let print_blocks = args.find(|x| x == "-v").map(|_| true).unwrap_or(false);
165+
println!("Opening '{filename}'...");
166+
let lbd = LinuxBlockDevice::new(filename, print_blocks).map_err(Error::DeviceError)?;
167+
let stdin = std::io::stdin();
168+
169+
let mut ctx = Context {
170+
volume_mgr: VolumeManager::new_with_limits(lbd, Clock, 100),
171+
volumes: [None, None, None, None],
172+
current_volume: 0,
173+
};
174+
175+
let mut current_volume = None;
176+
for volume_no in 0..4 {
177+
match ctx.volume_mgr.open_volume(VolumeIdx(volume_no)) {
178+
Ok(volume) => {
179+
println!("Volume # {}: found", volume_no,);
180+
match ctx.volume_mgr.open_root_dir(volume) {
181+
Ok(root_dir) => {
182+
ctx.volumes[volume_no] = Some(VolumeState {
183+
directory: root_dir,
184+
volume,
185+
path: vec![],
186+
});
187+
if current_volume.is_none() {
188+
current_volume = Some(volume_no);
189+
}
190+
}
191+
Err(e) => {
192+
println!("Failed to open root directory: {e:?}");
193+
ctx.volume_mgr.close_volume(volume).expect("close volume");
194+
}
195+
}
196+
}
197+
Err(e) => {
198+
println!("Failed to open volume {volume_no}: {e:?}");
199+
}
200+
}
201+
}
202+
203+
match current_volume {
204+
Some(n) => {
205+
// Default to the first valid partition
206+
ctx.current_volume = n;
207+
}
208+
None => {
209+
println!("No volumes found in file. Sorry.");
210+
return Ok(());
211+
}
212+
};
213+
214+
loop {
215+
print!("{}:/", ctx.current_volume);
216+
print!("{}", ctx.current_path().join("/"));
217+
print!("> ");
218+
std::io::stdout().flush().unwrap();
219+
let mut line = String::new();
220+
stdin.read_line(&mut line)?;
221+
let line = line.trim();
222+
if line == "quit" {
223+
break;
224+
} else if let Err(e) = ctx.process_line(line) {
225+
println!("Error: {:?}", e);
226+
}
227+
}
228+
229+
for (idx, s) in ctx.volumes.into_iter().enumerate() {
230+
if let Some(state) = s {
231+
println!("Closing current dir for {idx}...");
232+
let r = ctx.volume_mgr.close_dir(state.directory);
233+
if let Err(e) = r {
234+
println!("Error closing directory: {e:?}");
235+
}
236+
println!("Unmounting {idx}...");
237+
let r = ctx.volume_mgr.close_volume(state.volume);
238+
if let Err(e) = r {
239+
println!("Error closing volume: {e:?}");
240+
}
241+
}
242+
}
243+
244+
println!("Bye!");
245+
Ok(())
246+
}
247+
248+
// ****************************************************************************
249+
//
250+
// End Of File
251+
//
252+
// ****************************************************************************

src/fat/ondiskdirentry.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,17 +148,26 @@ impl<'a> OnDiskDirEntry<'a> {
148148
entry_block: BlockIdx,
149149
entry_offset: u32,
150150
) -> DirEntry {
151+
let attributes = Attributes::create_from_fat(self.raw_attr());
151152
let mut result = DirEntry {
152153
name: ShortFileName {
153154
contents: [0u8; 11],
154155
},
155156
mtime: Timestamp::from_fat(self.write_date(), self.write_time()),
156157
ctime: Timestamp::from_fat(self.create_date(), self.create_time()),
157-
attributes: Attributes::create_from_fat(self.raw_attr()),
158-
cluster: if fat_type == FatType::Fat32 {
159-
self.first_cluster_fat32()
160-
} else {
161-
self.first_cluster_fat16()
158+
attributes,
159+
cluster: {
160+
let cluster = if fat_type == FatType::Fat32 {
161+
self.first_cluster_fat32()
162+
} else {
163+
self.first_cluster_fat16()
164+
};
165+
if cluster == ClusterId::EMPTY && attributes.is_directory() {
166+
// FAT16/FAT32 uses a cluster ID of `0` in the ".." entry to mean 'root directory'
167+
ClusterId::ROOT_DIR
168+
} else {
169+
cluster
170+
}
162171
},
163172
size: self.file_size(),
164173
entry_block,

src/filesystem/filename.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ impl ShortFileName {
8585
let mut sfn = ShortFileName {
8686
contents: [b' '; Self::FILENAME_MAX_LEN],
8787
};
88+
89+
// Special case `..`, which means "parent directory".
90+
if name == ".." {
91+
return Ok(ShortFileName::parent_dir());
92+
}
93+
8894
let mut idx = 0;
8995
let mut seen_dot = false;
9096
for ch in name.bytes() {

src/filesystem/search_id.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub struct SearchId(pub(crate) u32);
1212
/// Well, it will wrap after `2**32` IDs. But most systems won't open that many
1313
/// files, and if they do, they are unlikely to hold one file open and then
1414
/// open/close `2**32 - 1` others.
15+
#[derive(Debug)]
1516
pub struct SearchIdGenerator {
1617
next_id: Wrapping<u32>,
1718
}

src/volume_mgr.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use heapless::Vec;
2020

2121
/// A `VolumeManager` wraps a block device and gives access to the FAT-formatted
2222
/// volumes within it.
23+
#[derive(Debug)]
2324
pub struct VolumeManager<
2425
D,
2526
T,
@@ -242,6 +243,8 @@ where
242243
}
243244
};
244245

246+
debug!("Found dir entry: {:?}", dir_entry);
247+
245248
if !dir_entry.attributes.is_directory() {
246249
return Err(Error::OpenedFileAsDir);
247250
}
@@ -482,7 +485,7 @@ where
482485

483486
// Check if it's open already
484487
if let Some(dir_entry) = &dir_entry {
485-
if self.file_is_open(volume_info.volume_id, &dir_entry) {
488+
if self.file_is_open(volume_info.volume_id, dir_entry) {
486489
return Err(Error::FileAlreadyOpen);
487490
}
488491
}

0 commit comments

Comments
 (0)