From a1716e07638f63de9f581159b9c3f3c34615ed4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szczepan=20Cie=C5=9Blik?= Date: Sun, 20 Oct 2024 17:10:55 +0200 Subject: [PATCH 1/2] Add LFN support --- src/fat/volume.rs | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/src/fat/volume.rs b/src/fat/volume.rs index 8f44c6d..8adc378 100644 --- a/src/fat/volume.rs +++ b/src/fat/volume.rs @@ -11,6 +11,7 @@ use crate::{ }; use byteorder::{ByteOrder, LittleEndian}; use core::convert::TryFrom; +use heapless::String; use super::BlockCache; @@ -532,6 +533,10 @@ impl FatVolume { }; let mut blocks = [Block::new()]; let mut block_cache = BlockCache::empty(); + + let mut lfn_buffer = [[' '; 13]; 8]; + let mut lfn_pointer = 0; + while let Some(cluster) = current_cluster { let block_idx = self.cluster_to_block(cluster); for block in block_idx.range(BlockCount(u32::from(self.blocks_per_cluster))) { @@ -545,11 +550,33 @@ impl FatVolume { if dir_entry.is_end() { // Can quit early return Ok(()); - } else if dir_entry.is_valid() && !dir_entry.is_lfn() { - // Safe, since Block::LEN always fits on a u32 - let start = u32::try_from(start).unwrap(); - let entry = dir_entry.get_entry(FatType::Fat32, block, start); - func(&entry); + } else if dir_entry.is_valid() { + if dir_entry.is_lfn() { + if let Some((_, _, data)) = dir_entry.lfn_contents() { + lfn_buffer[lfn_pointer] = data.clone(); + lfn_pointer += 1; + } + } else { + if lfn_pointer > 0 { + let mut filename: String<255> = String::new(); + while lfn_pointer > 0 { + lfn_pointer -= 1; + for i in 0..13 { + // This will only happen on last chunk after last + // character, so simple break is enough + if lfn_buffer[lfn_pointer][i] == '\0' { + break; + } else { + filename.push(lfn_buffer[lfn_pointer][i]); + } + } + } + } + // Safe, since Block::LEN always fits on a u32 + let start = u32::try_from(start).unwrap(); + let entry = dir_entry.get_entry(FatType::Fat32, block, start); + func(&entry); + } } } } From f3e7aebd1e7bb521dae2059f6469afd56d3f491e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szczepan=20Cie=C5=9Blik?= Date: Tue, 22 Oct 2024 22:24:35 +0200 Subject: [PATCH 2/2] Improve entries reading --- src/fat/mod.rs | 3 +- src/fat/ondiskdirentry.rs | 5 ++-- src/fat/volume.rs | 61 +++++++++++++++++++++++++++------------ 3 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/fat/mod.rs b/src/fat/mod.rs index 504f67f..cd113a2 100644 --- a/src/fat/mod.rs +++ b/src/fat/mod.rs @@ -252,11 +252,12 @@ mod test { 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) = + let (calc_start, calc_index, calc_contents, _) = on_disk_entry.lfn_contents().unwrap(); assert_eq!(*start, calc_start); assert_eq!(*index, calc_index); assert_eq!(*contents, calc_contents); + // TODO: Check checksum } Expected::Short(expected_entry) if !on_disk_entry.is_lfn() => { let parsed_entry = on_disk_entry.get_entry(FatType::Fat32, BlockIdx(0), 0); diff --git a/src/fat/ondiskdirentry.rs b/src/fat/ondiskdirentry.rs index 49b8bb2..ccab32c 100644 --- a/src/fat/ondiskdirentry.rs +++ b/src/fat/ondiskdirentry.rs @@ -78,11 +78,12 @@ 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, [char; 13], u8)> { if self.is_lfn() { let mut buffer = [' '; 13]; let is_start = (self.data[0] & 0x40) != 0; let sequence = self.data[0] & 0x1F; + let checksum = self.data[13]; // 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(); @@ -118,7 +119,7 @@ impl<'a> OnDiskDirEntry<'a> { buffer[12] = core::char::from_u32(u32::from(LittleEndian::read_u16(&self.data[30..=31]))) .unwrap(); - Some((is_start, sequence, buffer)) + Some((is_start, sequence, buffer, checksum)) } else { None } diff --git a/src/fat/volume.rs b/src/fat/volume.rs index 9576d7b..7791ff7 100644 --- a/src/fat/volume.rs +++ b/src/fat/volume.rs @@ -12,7 +12,7 @@ use crate::{ }; use byteorder::{ByteOrder, LittleEndian}; use core::convert::TryFrom; -use heapless::String; +use heapless::{String, Vec}; /// An MS-DOS 11 character volume label. /// @@ -615,8 +615,9 @@ impl FatVolume { _ => Some(dir_info.cluster), }; - let mut lfn_buffer = [[' '; 13]; 8]; - let mut lfn_pointer = 0; + let mut lfn_stack = Vec::, 8>::new(); + let mut current_checksum: Option = None; + let mut current_sequence: Option = None; while let Some(cluster) = current_cluster { let start_block_idx = self.cluster_to_block(cluster); @@ -630,26 +631,50 @@ impl FatVolume { return Ok(()); } else if dir_entry.is_valid() { if dir_entry.is_lfn() { - if let Some((_, _, data)) = dir_entry.lfn_contents() { - lfn_buffer[lfn_pointer] = data.clone(); - lfn_pointer += 1; + if let Some((is_start, sequence, data, checksum)) = + dir_entry.lfn_contents() + { + // Case for malformed LFN entries, if they ale placed before + // actual entries + let checksum_ok = match current_checksum { + Some(c) => c == checksum, + None => true, + }; + let sequence_ok = match current_sequence { + Some(s) => sequence == s - 1 && sequence < 8, + None => true, + }; + if is_start || !checksum_ok || !sequence_ok { + lfn_stack.clear(); + current_checksum = None; + current_sequence = None; + } else { + current_checksum = Some(checksum); + current_sequence = Some(sequence); + } + + let mut name_chunk: String<32> = String::new(); + for i in 0..13 { + if data[i] == '\0' { + break; + } + name_chunk.push(data[i]).unwrap(); + } + lfn_stack.push(name_chunk).unwrap(); } } else { - if lfn_pointer > 0 { + if !lfn_stack.is_empty() { let mut filename: String<255> = String::new(); - while lfn_pointer > 0 { - lfn_pointer -= 1; - for i in 0..13 { - // This will only happen on last chunk after last - // character, so simple break is enough - if lfn_buffer[lfn_pointer][i] == '\0' { - break; - } else { - filename.push(lfn_buffer[lfn_pointer][i]); - } - } + lfn_stack.reverse(); + for name_chunk in lfn_stack.iter() { + filename.push_str(name_chunk).unwrap(); } } + + lfn_stack.clear(); + current_checksum = None; + current_sequence = None; + // TODO calculate checksum and compare with shorf file name // Safe, since Block::LEN always fits on a u32 let start = (i * OnDiskDirEntry::LEN) as u32; let entry = dir_entry.get_entry(FatType::Fat32, block_idx, start);