Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LFN support when iterating directories #157

Merged
merged 9 commits into from
Oct 29, 2024
Merged
10 changes: 8 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,21 @@ The format is based on [Keep a Changelog] and this project adheres to [Semantic

### Changed

- None
- __Breaking Change__: `VolumeManager` now uses interior-mutability (with a `RefCell`) and so most methods are now `&self`. This also makes it easier to open multiple `File`, `Directory` or `Volume` objects at once.
- __Breaking Change__: The `VolumeManager`, `File`, `Directory` and `Volume` no longer implement `Send` or `Sync.
- `VolumeManager` uses an interior block cache of 512 bytes, increasing its size by about 520 bytes but hugely reducing stack space required at run-time.
- __Breaking Change__: The `VolumeManager::device` method now takes a callback rather than giving you a reference to the underlying `BlockDevice`
- __Breaking Change__: `Error:LockError` variant added.
- __Breaking Change__: `SearchId` was renamed to `Handle`

### Added

- `File` now implements the `embedded-io` `Read`, `Write` and `Seek` traits.
- New `iterate_dir_lfn` method on `VolumeManager` and `Directory` - provides decoded Long File Names as `Option<&str>`

### Removed

- None
- __Breaking Change__: Removed the `reason: &str` argument from `BlockDevice`

## [Version 0.8.0] - 2024-07-12

Expand Down
21 changes: 15 additions & 6 deletions examples/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@

use std::{cell::RefCell, io::prelude::*};

use embedded_sdmmc::{Error as EsError, RawDirectory, RawVolume, ShortFileName, VolumeIdx};
use embedded_sdmmc::{
Error as EsError, LfnBuffer, RawDirectory, RawVolume, ShortFileName, VolumeIdx,
};

type VolumeManager = embedded_sdmmc::VolumeManager<LinuxBlockDevice, Clock, 8, 8, 4>;
type Directory<'a> = embedded_sdmmc::Directory<'a, LinuxBlockDevice, Clock, 8, 8, 4>;
Expand Down Expand Up @@ -229,17 +231,24 @@ impl Context {
fn dir(&self, path: &Path) -> Result<(), Error> {
println!("Directory listing of {:?}", path);
let dir = self.resolve_existing_directory(path)?;
dir.iterate_dir(|entry| {
if !entry.attributes.is_volume() && !entry.attributes.is_lfn() {
println!(
"{:12} {:9} {} {} {:08X?} {:?}",
let mut storage = [0u8; 128];
let mut lfn_buffer = LfnBuffer::new(&mut storage);
dir.iterate_dir_lfn(&mut lfn_buffer, |entry, lfn| {
if !entry.attributes.is_volume() {
print!(
"{:12} {:9} {} {} {:08X?} {:5?}",
entry.name,
entry.size,
entry.ctime,
entry.mtime,
entry.cluster,
entry.attributes
entry.attributes,
);
if let Some(lfn) = lfn {
println!(" {:?}", lfn);
} else {
println!();
}
}
})?;
Ok(())
Expand Down
69 changes: 51 additions & 18 deletions src/fat/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ mod test {
fn test_dir_entries() {
#[derive(Debug)]
enum Expected {
Lfn(bool, u8, [char; 13]),
Lfn(bool, u8, u8, [u16; 13]),
Short(DirEntry),
}
let raw_data = r#"
Expand All @@ -105,6 +105,7 @@ mod test {
422e0064007400620000000f0059ffffffffffffffffffffffff0000ffffffff B..d.t.b.....Y..................
01620063006d00320037000f0059300038002d0072007000690000002d006200 .b.c.m.2.7...Y0.8.-.r.p.i...-.b.
"#;

let results = [
Expected::Short(DirEntry {
name: unsafe {
Expand All @@ -123,9 +124,10 @@ mod test {
Expected::Lfn(
true,
1,
0x47,
[
'o', 'v', 'e', 'r', 'l', 'a', 'y', 's', '\u{0000}', '\u{ffff}', '\u{ffff}',
'\u{ffff}', '\u{ffff}',
'o' as u16, 'v' as u16, 'e' as u16, 'r' as u16, 'l' as u16, 'a' as u16,
'y' as u16, 's' as u16, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
],
),
Expected::Short(DirEntry {
Expand All @@ -141,16 +143,20 @@ mod test {
Expected::Lfn(
true,
2,
0x79,
[
'-', 'p', 'l', 'u', 's', '.', 'd', 't', 'b', '\u{0000}', '\u{ffff}',
'\u{ffff}', '\u{ffff}',
'-' as u16, 'p' as u16, 'l' as u16, 'u' as u16, 's' as u16, '.' as u16,
'd' as u16, 't' as u16, 'b' as u16, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF,
],
),
Expected::Lfn(
false,
1,
0x79,
[
'b', 'c', 'm', '2', '7', '0', '8', '-', 'r', 'p', 'i', '-', 'b',
'b' as u16, 'c' as u16, 'm' as u16, '2' as u16, '7' as u16, '0' as u16,
'8' as u16, '-' as u16, 'r' as u16, 'p' as u16, 'i' as u16, '-' as u16,
'b' as u16,
],
),
Expected::Short(DirEntry {
Expand All @@ -166,8 +172,11 @@ mod test {
Expected::Lfn(
true,
1,
0x12,
[
'C', 'O', 'P', 'Y', 'I', 'N', 'G', '.', 'l', 'i', 'n', 'u', 'x',
'C' as u16, 'O' as u16, 'P' as u16, 'Y' as u16, 'I' as u16, 'N' as u16,
'G' as u16, '.' as u16, 'l' as u16, 'i' as u16, 'n' as u16, 'u' as u16,
'x' as u16,
],
),
Expected::Short(DirEntry {
Expand All @@ -183,16 +192,31 @@ mod test {
Expected::Lfn(
true,
2,
0x67,
[
'c', 'o', 'm', '\u{0}', '\u{ffff}', '\u{ffff}', '\u{ffff}', '\u{ffff}',
'\u{ffff}', '\u{ffff}', '\u{ffff}', '\u{ffff}', '\u{ffff}',
'c' as u16,
'o' as u16,
'm' as u16,
'\u{0}' as u16,
0xFFFF,
0xFFFF,
0xFFFF,
0xFFFF,
0xFFFF,
0xFFFF,
0xFFFF,
0xFFFF,
0xFFFF,
],
),
Expected::Lfn(
false,
1,
0x67,
[
'L', 'I', 'C', 'E', 'N', 'C', 'E', '.', 'b', 'r', 'o', 'a', 'd',
'L' as u16, 'I' as u16, 'C' as u16, 'E' as u16, 'N' as u16, 'C' as u16,
'E' as u16, '.' as u16, 'b' as u16, 'r' as u16, 'o' as u16, 'a' as u16,
'd' as u16,
],
),
Expected::Short(DirEntry {
Expand All @@ -208,16 +232,20 @@ mod test {
Expected::Lfn(
true,
2,
0x19,
[
'-', 'b', '.', 'd', 't', 'b', '\u{0000}', '\u{ffff}', '\u{ffff}', '\u{ffff}',
'\u{ffff}', '\u{ffff}', '\u{ffff}',
'-' as u16, 'b' as u16, '.' as u16, 'd' as u16, 't' as u16, 'b' as u16, 0x0000,
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
],
),
Expected::Lfn(
false,
1,
0x19,
[
'b', 'c', 'm', '2', '7', '0', '9', '-', 'r', 'p', 'i', '-', '2',
'b' as u16, 'c' as u16, 'm' as u16, '2' as u16, '7' as u16, '0' as u16,
'9' as u16, '-' as u16, 'r' as u16, 'p' as u16, 'i' as u16, '-' as u16,
'2' as u16,
],
),
Expected::Short(DirEntry {
Expand All @@ -233,16 +261,20 @@ mod test {
Expected::Lfn(
true,
2,
0x59,
[
'.', 'd', 't', 'b', '\u{0000}', '\u{ffff}', '\u{ffff}', '\u{ffff}', '\u{ffff}',
'\u{ffff}', '\u{ffff}', '\u{ffff}', '\u{ffff}',
'.' as u16, 'd' as u16, 't' as u16, 'b' as u16, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF,
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
],
),
Expected::Lfn(
false,
1,
0x59,
[
'b', 'c', 'm', '2', '7', '0', '8', '-', 'r', 'p', 'i', '-', 'b',
'b' as u16, 'c' as u16, 'm' as u16, '2' as u16, '7' as u16, '0' as u16,
'8' as u16, '-' as u16, 'r' as u16, 'p' as u16, 'i' as u16, '-' as u16,
'b' as u16,
],
),
];
Expand All @@ -251,12 +283,13 @@ mod test {
for (part, expected) in data.chunks(OnDiskDirEntry::LEN).zip(results.iter()) {
let on_disk_entry = OnDiskDirEntry::new(part);
match expected {
Expected::Lfn(start, index, contents) if on_disk_entry.is_lfn() => {
let (calc_start, calc_index, calc_contents) =
Expected::Lfn(start, index, csum, contents) if on_disk_entry.is_lfn() => {
let (calc_start, calc_index, calc_csum, calc_contents) =
on_disk_entry.lfn_contents().unwrap();
assert_eq!(*start, calc_start);
assert_eq!(*index, calc_index);
assert_eq!(*contents, calc_contents);
assert_eq!(*csum, calc_csum);
}
Expected::Short(expected_entry) if !on_disk_entry.is_lfn() => {
let parsed_entry = on_disk_entry.get_entry(FatType::Fat32, BlockIdx(0), 0);
Expand Down
56 changes: 18 additions & 38 deletions src/fat/ondiskdirentry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,47 +78,27 @@ impl<'a> OnDiskDirEntry<'a> {
}

/// If this is an LFN, get the contents so we can re-assemble the filename.
pub fn lfn_contents(&self) -> Option<(bool, u8, [char; 13])> {
pub fn lfn_contents(&self) -> Option<(bool, u8, u8, [u16; 13])> {
if self.is_lfn() {
let mut buffer = [' '; 13];
let is_start = (self.data[0] & 0x40) != 0;
let sequence = self.data[0] & 0x1F;
// LFNs store UCS-2, so we can map from 16-bit char to 32-bit char without problem.
buffer[0] =
core::char::from_u32(u32::from(LittleEndian::read_u16(&self.data[1..=2]))).unwrap();
buffer[1] =
core::char::from_u32(u32::from(LittleEndian::read_u16(&self.data[3..=4]))).unwrap();
buffer[2] =
core::char::from_u32(u32::from(LittleEndian::read_u16(&self.data[5..=6]))).unwrap();
buffer[3] =
core::char::from_u32(u32::from(LittleEndian::read_u16(&self.data[7..=8]))).unwrap();
buffer[4] = core::char::from_u32(u32::from(LittleEndian::read_u16(&self.data[9..=10])))
.unwrap();
buffer[5] =
core::char::from_u32(u32::from(LittleEndian::read_u16(&self.data[14..=15])))
.unwrap();
buffer[6] =
core::char::from_u32(u32::from(LittleEndian::read_u16(&self.data[16..=17])))
.unwrap();
buffer[7] =
core::char::from_u32(u32::from(LittleEndian::read_u16(&self.data[18..=19])))
.unwrap();
buffer[8] =
core::char::from_u32(u32::from(LittleEndian::read_u16(&self.data[20..=21])))
.unwrap();
buffer[9] =
core::char::from_u32(u32::from(LittleEndian::read_u16(&self.data[22..=23])))
.unwrap();
buffer[10] =
core::char::from_u32(u32::from(LittleEndian::read_u16(&self.data[24..=25])))
.unwrap();
buffer[11] =
core::char::from_u32(u32::from(LittleEndian::read_u16(&self.data[28..=29])))
.unwrap();
buffer[12] =
core::char::from_u32(u32::from(LittleEndian::read_u16(&self.data[30..=31])))
.unwrap();
Some((is_start, sequence, buffer))
let csum = self.data[13];
let buffer = [
LittleEndian::read_u16(&self.data[1..=2]),
LittleEndian::read_u16(&self.data[3..=4]),
LittleEndian::read_u16(&self.data[5..=6]),
LittleEndian::read_u16(&self.data[7..=8]),
LittleEndian::read_u16(&self.data[9..=10]),
LittleEndian::read_u16(&self.data[14..=15]),
LittleEndian::read_u16(&self.data[16..=17]),
LittleEndian::read_u16(&self.data[18..=19]),
LittleEndian::read_u16(&self.data[20..=21]),
LittleEndian::read_u16(&self.data[22..=23]),
LittleEndian::read_u16(&self.data[24..=25]),
LittleEndian::read_u16(&self.data[28..=29]),
LittleEndian::read_u16(&self.data[30..=31]),
];
Some((is_start, sequence, csum, buffer))
} else {
None
}
Expand Down
Loading