Skip to content

Commit

Permalink
Merge pull request #157 from rust-embedded-community/add-lfn-support
Browse files Browse the repository at this point in the history
Add LFN support when iterating directories
  • Loading branch information
eldruin authored Oct 29, 2024
2 parents 1b9fa5c + 837ae27 commit 52e0e4f
Show file tree
Hide file tree
Showing 14 changed files with 682 additions and 162 deletions.
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

0 comments on commit 52e0e4f

Please sign in to comment.