only expose regular DirEntries, long filename one's are internal

This commit is contained in:
Moritz Gmeiner 2025-07-27 14:38:17 +02:00
commit e4f693ed3e
3 changed files with 128 additions and 53 deletions

View file

@ -45,8 +45,9 @@ impl Display for Attr {
} }
} }
/// represents an entry in a diectory
#[derive(Debug)] #[derive(Debug)]
pub struct RegularDirEntry { pub struct DirEntry {
name: [u8; 11], name: [u8; 11],
attr: Attr, attr: Attr,
@ -66,7 +67,7 @@ pub struct RegularDirEntry {
long_name: Option<String>, long_name: Option<String>,
} }
impl Display for RegularDirEntry { impl Display for DirEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut name = self.name_string().unwrap_or_else(|| "<unknown>".to_owned()); let mut name = self.name_string().unwrap_or_else(|| "<unknown>".to_owned());
@ -87,8 +88,8 @@ impl Display for RegularDirEntry {
} }
} }
impl RegularDirEntry { impl DirEntry {
pub fn load(bytes: &[u8]) -> anyhow::Result<RegularDirEntry> { pub fn load(bytes: &[u8]) -> anyhow::Result<DirEntry> {
assert_eq!(bytes.len(), 32); assert_eq!(bytes.len(), 32);
let name = bytes[..11].try_into().unwrap(); let name = bytes[..11].try_into().unwrap();
@ -129,7 +130,7 @@ impl RegularDirEntry {
) )
} }
Ok(RegularDirEntry { Ok(DirEntry {
name, name,
attr, attr,
create_time_tenths, create_time_tenths,
@ -273,7 +274,10 @@ impl RegularDirEntry {
} }
} }
pub struct LongNameDirEntry { /// long filename entry in a directory
///
/// this should not be exposed to end users, only for internal consumption in the DirIter
struct LongNameDirEntry {
ordinal: u8, ordinal: u8,
is_last: bool, is_last: bool,
name: [u16; 13], name: [u16; 13],
@ -330,7 +334,7 @@ impl LongNameDirEntry {
self.ordinal self.ordinal
} }
pub fn is_first(&self) -> bool { pub fn is_last(&self) -> bool {
self.is_last self.is_last
} }
@ -343,21 +347,25 @@ impl LongNameDirEntry {
} }
} }
pub enum DirEntry { /// wraps both Regular DirEntry and LongNameDirEntry
Regular(RegularDirEntry), ///
/// should not be exposed publicly, end users only see DirEntries
/// just for making the bytes -> DirEntry loading a bit easier
enum DirEntryWrapper {
Regular(DirEntry),
LongName(LongNameDirEntry), LongName(LongNameDirEntry),
} }
impl DirEntry { impl DirEntryWrapper {
pub fn load(bytes: &[u8]) -> anyhow::Result<DirEntry> { pub fn load(bytes: &[u8]) -> anyhow::Result<DirEntryWrapper> {
assert_eq!(bytes.len(), 32); assert_eq!(bytes.len(), 32);
let attr = Attr::from_bits_truncate(bytes[11]); let attr = Attr::from_bits_truncate(bytes[11]);
let dir_entry = if attr == Attr::LongName { let dir_entry = if attr == Attr::LongName {
DirEntry::LongName(LongNameDirEntry::load(bytes)?) DirEntryWrapper::LongName(LongNameDirEntry::load(bytes)?)
} else { } else {
DirEntry::Regular(RegularDirEntry::load(bytes)?) DirEntryWrapper::Regular(DirEntry::load(bytes)?)
}; };
Ok(dir_entry) Ok(dir_entry)
@ -379,7 +387,7 @@ impl LongFilenameBuf {
} }
pub fn next(&mut self, dir_entry: LongNameDirEntry) -> anyhow::Result<()> { pub fn next(&mut self, dir_entry: LongNameDirEntry) -> anyhow::Result<()> {
if dir_entry.is_last { if dir_entry.is_last() {
// first/lasts entry // first/lasts entry
let mut name = dir_entry.name(); let mut name = dir_entry.name();
@ -470,51 +478,53 @@ impl<R: Read> DirIter<R> {
long_filename_buf: Default::default(), long_filename_buf: Default::default(),
} }
} }
}
impl<R: Read> Iterator for DirIter<R> { /// inner function for iterator
type Item = RegularDirEntry; fn next_impl(&mut self) -> anyhow::Result<Option<DirEntry>> {
fn next(&mut self) -> Option<Self::Item> {
let mut chunk = [0; 32]; let mut chunk = [0; 32];
self.reader.read_exact(&mut chunk).ok()?; if self.reader.read_exact(&mut chunk).is_err() {
// reading failed; nothing we can do here
return Ok(None);
}
// let Ok(dir_entry) = DirEntry::load(&chunk) else { // let Ok(dir_entry) = DirEntry::load(&chunk) else {
// return self.next(); // return self.next();
// }; // };
let dir_entry = match DirEntry::load(&chunk) { let dir_entry = DirEntryWrapper::load(&chunk)
Ok(dir_entry) => dir_entry, .map_err(|e| anyhow::anyhow!("failed to load dir entry: {e}"))?;
Err(e) => {
// if loading fails: print error and try next entry
eprintln!("failed to load dir entry: {e}");
return self.next();
}
};
let mut dir_entry = match dir_entry { let mut dir_entry = match dir_entry {
DirEntry::Regular(dir_entry) => dir_entry, DirEntryWrapper::Regular(dir_entry) => dir_entry,
DirEntry::LongName(long_name) => { DirEntryWrapper::LongName(long_name) => {
if let Err(e) = self.long_filename_buf.next(long_name) { self.long_filename_buf.next(long_name).map_err(|e| {
eprintln!("invalid long filename entry: {}", e); self.long_filename_buf.reset();
} anyhow::anyhow!("invalid long filename entry: {e}")
})?;
// simply skip long name entries for now return self.next_impl();
return self.next();
} }
}; };
if dir_entry.is_sentinel() { if dir_entry.is_sentinel() {
return None; return Ok(None);
} }
if dir_entry.is_empty() { if dir_entry.is_empty() {
return self.next(); return self.next_impl();
} }
match self.long_filename_buf.get_buf(dir_entry.checksum()) { match self
Ok(Some(iter)) => { .long_filename_buf
.get_buf(dir_entry.checksum())
.map_err(|e| {
anyhow::anyhow!(
"failed to get long filename for {}: {}",
dir_entry.name_string().as_deref().unwrap_or("<invalid>"),
e
)
})? {
Some(iter) => {
// attach long filename to dir_entry // attach long filename to dir_entry
let long_filename: String = let long_filename: String =
@ -522,18 +532,83 @@ impl<R: Read> Iterator for DirIter<R> {
dir_entry.set_long_name(long_filename); dir_entry.set_long_name(long_filename);
} }
Ok(None) => {} // no long filename -> do nothing None => {} // no long filename -> do nothing
Err(e) => {
eprintln!(
"failed to get long filename for {}: {}",
dir_entry.name_string().as_deref().unwrap_or("<invalid>"),
e
);
}
} }
self.long_filename_buf.reset(); self.long_filename_buf.reset();
Some(dir_entry) Ok(Some(dir_entry))
}
}
impl<R: Read> Iterator for DirIter<R> {
type Item = DirEntry;
fn next(&mut self) -> Option<Self::Item> {
match self.next_impl() {
Ok(x) => x,
Err(e) => {
eprintln!("{}", e);
self.next()
}
}
// let mut chunk = [0; 32];
// self.reader.read_exact(&mut chunk).ok()?;
// let dir_entry = match DirEntryWrapper::load(&chunk) {
// Ok(dir_entry) => dir_entry,
// Err(e) => {
// // if loading fails: print error and try next entry
// eprintln!("failed to load dir entry: {e}");
// return self.next();
// }
// };
// let mut dir_entry = match dir_entry {
// DirEntryWrapper::Regular(dir_entry) => dir_entry,
// DirEntryWrapper::LongName(long_name) => {
// if let Err(e) = self.long_filename_buf.next(long_name) {
// self.long_filename_buf.reset();
//
// eprintln!("invalid long filename entry: {}", e);
// }
// return self.next();
// }
// };
// if dir_entry.is_sentinel() {
// return None;
// }
// if dir_entry.is_empty() {
// return self.next();
// }
// match self.long_filename_buf.get_buf(dir_entry.checksum()) {
// Ok(Some(iter)) => {
// // attach long filename to dir_entry
// let long_filename: String =
// char::decode_utf16(iter).filter_map(|x| x.ok()).collect();
// dir_entry.set_long_name(long_filename);
// }
// Ok(None) => {} // no long filename -> do nothing
// Err(e) => {
// eprintln!(
// "failed to get long filename for {}: {}",
// dir_entry.name_string().as_deref().unwrap_or("<invalid>"),
// e
// );
// }
// }
// self.long_filename_buf.reset();
// Some(dir_entry)
} }
} }

View file

@ -2,7 +2,7 @@ use std::cell::RefCell;
use std::io::{Read, Seek, SeekFrom, Write}; use std::io::{Read, Seek, SeekFrom, Write};
use std::rc::Rc; use std::rc::Rc;
use crate::dir::{DirIter, RegularDirEntry}; use crate::dir::{DirEntry, DirIter};
use crate::fat::{FatError, Fatty}; use crate::fat::{FatError, Fatty};
use crate::subslice::{SubSlice, SubSliceMut}; use crate::subslice::{SubSlice, SubSliceMut};
@ -224,7 +224,7 @@ impl<S: SliceLike> FatFs<S> {
Ok(data) Ok(data)
} }
pub fn root_dir_iter(&self) -> Box<dyn Iterator<Item = RegularDirEntry> + '_> { pub fn root_dir_iter(&self) -> Box<dyn Iterator<Item = DirEntry> + '_> {
// TODO: maybe wrap this in another RootDirIter enum, so we don't have to Box<dyn> // TODO: maybe wrap this in another RootDirIter enum, so we don't have to Box<dyn>
if let Some(root_dir_offset) = self.root_dir_offset { if let Some(root_dir_offset) = self.root_dir_offset {

View file

@ -1,4 +1,4 @@
use fat_bits::dir::{DirIter, RegularDirEntry}; use fat_bits::dir::{DirIter, DirEntry};
use fat_bits::fat::Fatty as _; use fat_bits::fat::Fatty as _;
use fat_bits::{FatFs, SliceLike}; use fat_bits::{FatFs, SliceLike};
@ -50,7 +50,7 @@ fn tree<S: SliceLike>(fat_fs: &FatFs<S>, show_hidden: bool) {
fn tree_impl<S: SliceLike>( fn tree_impl<S: SliceLike>(
fat_fs: &FatFs<S>, fat_fs: &FatFs<S>,
iter: impl Iterator<Item = RegularDirEntry>, iter: impl Iterator<Item = DirEntry>,
show_hidden: bool, show_hidden: bool,
indent: u32, indent: u32,
) { ) {