diff --git a/core/src/lib.rs b/core/src/lib.rs index 439e5f038..7f1c14629 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -13,7 +13,10 @@ mod path; pub use fs::{Attribute, DirEntry, FileOpenFlags, FileType, Metadata}; pub use io::{Error, OpenSeekFrom, Read, Result, Seek, SeekFrom, Write}; -pub use object_safe::{DirEntriesCallback, DynFile, DynFilesystem, FileCallback, Predicate, Vec}; +pub use object_safe::{ + DirEntriesCallback, DirIterationTell, DirIterator, DynFile, DynFilesystem, FileCallback, + Predicate, Vec, +}; pub use path::{Ancestors, Iter, Path, PathBuf, PathError}; /// Creates a path from a string without a trailing null. diff --git a/core/src/object_safe.rs b/core/src/object_safe.rs index d4138d150..1ec779ab8 100644 --- a/core/src/object_safe.rs +++ b/core/src/object_safe.rs @@ -8,8 +8,7 @@ use crate::{ const _: Option<&dyn DynFile> = None; const _: Option<&dyn DynFilesystem> = None; -pub type DirEntriesCallback<'a, R = ()> = - &'a mut dyn FnMut(&mut dyn Iterator>) -> Result; +pub type DirEntriesCallback<'a, R = ()> = &'a mut dyn FnMut(&mut dyn DirIterator) -> Result; pub type FileCallback<'a, R = ()> = &'a mut dyn FnMut(&dyn DynFile) -> Result; pub type Predicate<'a> = &'a dyn Fn(&DirEntry) -> bool; @@ -87,6 +86,31 @@ impl dyn DynFile + '_ { } } +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct DirIterationTell { + #[doc(hidden)] + pub tell_result: u32, +} + +pub trait DirIterator: Iterator> { + /// Return the position of the directory + /// + /// The returned offset is only meant to be consumed by seek and may not make + /// sense, but does indicate the current position in the directory iteration. + /// + /// Returns the position of the directory, which can be returned to using [`seek`](Self::seek). + fn tell(&self) -> Result; + + /// Change the position of the directory + /// + /// The new off must be a value previous returned from [`tell`](Self::tell) and specifies + /// an absolute offset in the directory seek. + fn seek(&self, state: DirIterationTell) -> Result<()>; + + /// Change the position of the directory to the beginning of the directory + fn rewind(&self) -> Result<()>; +} + /// Object-safe trait for filesystems. /// /// The following methods cannot support generic return types in the callbacks: diff --git a/src/fs.rs b/src/fs.rs index 3f33d9585..f2d7accdb 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -8,12 +8,15 @@ use core::{ mem, slice, }; use generic_array::typenum::marker_traits::Unsigned; +use littlefs2_core::DirIterator; use littlefs2_sys as ll; // so far, don't need `heapless-bytes`. pub type Bytes = generic_array::GenericArray; -pub use littlefs2_core::{Attribute, DirEntry, FileOpenFlags, FileType, Metadata}; +pub use littlefs2_core::{ + Attribute, DirEntry, DirIterationTell, FileOpenFlags, FileType, Metadata, +}; use crate::{ driver, @@ -940,6 +943,65 @@ pub struct ReadDir<'a, 'b, S: driver::Storage> { path: &'b Path, } +impl DirIterator for ReadDir<'_, '_, S> { + /// Return the position of the directory + /// + /// The returned offset is only meant to be consumed by seek and may not make + /// sense, but does indicate the current position in the directory iteration. + /// + /// Returns the position of the directory, which can be returned to using [`seek`](Self::seek). + fn tell(&self) -> Result { + let value = unsafe { + ll::lfs_dir_tell( + &mut self.fs.alloc.borrow_mut().state, + &mut (**self.alloc.borrow_mut()).state, + ) + }; + if let Ok(value_positive) = value.try_into() { + Ok(DirIterationTell { + tell_result: value_positive, + }) + } else { + Err(result_from((), value as _).unwrap_err()) + } + } + + /// Change the position of the directory + /// + /// The new off must be a value previous returned from [`tell`](Self::tell) and specifies + /// an absolute offset in the directory seek. + fn seek(&self, state: DirIterationTell) -> Result<()> { + let value = unsafe { + ll::lfs_dir_seek( + &mut self.fs.alloc.borrow_mut().state, + &mut (**self.alloc.borrow_mut()).state, + state.tell_result, + ) + }; + if value < 0 { + Err(result_from((), value as _).unwrap_err()) + } else { + Ok(()) + } + } + + /// Change the position of the directory to the beginning of the directory + fn rewind(&self) -> Result<()> { + let res = unsafe { + ll::lfs_dir_rewind( + &mut self.fs.alloc.borrow_mut().state, + &mut (**self.alloc.borrow_mut()).state, + ) + }; + + if res < 0 { + Err(result_from((), res).unwrap_err()) + } else { + Ok(()) + } + } +} + impl Iterator for ReadDir<'_, '_, S> { type Item = Result; diff --git a/src/tests.rs b/src/tests.rs index 20bc09299..5debe312e 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -4,8 +4,11 @@ use generic_array::typenum::consts; use crate::{ fs::{Attribute, File, Filesystem}, io::{Error, OpenSeekFrom, Read, Result, SeekFrom}, - path, BACKEND_VERSION, DISK_VERSION, + path, + path::PathBuf, + BACKEND_VERSION, DISK_VERSION, }; +use littlefs2_core::DirIterator; ram_storage!( name = OtherRamStorage, @@ -492,31 +495,56 @@ fn test_iter_dirs() { file.set_len(37)?; fs.create_file_and_then(path!("/tmp/file.b"), |file| file.set_len(42)) })?; + let mut tells = Vec::new(); - fs.read_dir_and_then(path!("/tmp"), |dir| { + fs.read_dir_and_then(path!("/tmp"), |mut dir| { let mut found_files: usize = 0; let mut sizes = [0usize; 4]; + let mut i = 0; - for (i, entry) in dir.enumerate() { + tells.push(dir.tell()?); + while let Some(entry) = dir.next() { + tells.push(dir.tell()?); let entry = entry?; - // assert_eq!(entry.file_name(), match i { - // 0 => b".\0", - // 1 => b"..\0", - // 2 => b"file.a\0", - // 3 => b"file.b\0", - // _ => panic!("oh noes"), - // }); + let expected_name = match i { + 0 => PathBuf::from(path!(".")), + 1 => PathBuf::from(path!("..")), + 2 => PathBuf::from(path!("file.a")), + 3 => PathBuf::from(path!("file.b")), + _ => panic!("oh noes"), + }; + + assert_eq!(entry.file_name(), &*expected_name); sizes[i] = entry.metadata().len(); found_files += 1; + i += 1; } assert_eq!(sizes, [0, 0, 37, 42]); assert_eq!(found_files, 4); - + assert_eq!(tells.len(), 5); + + for (i, tell) in tells.iter().enumerate() { + dir.rewind().unwrap(); + let mut found_files: usize = 0; + let mut sizes = Vec::new(); + dir.seek(*tell)?; + + for entry in &mut dir { + let entry = entry?; + sizes.push(entry.metadata().len()); + found_files += 1; + } + + assert_eq!(sizes, [0, 0, 37, 42][i..]); + assert_eq!(found_files, 4 - i); + } Ok(()) }) + .unwrap(); + Ok(()) }) .unwrap(); } diff --git a/tests-old-fs/empty.bin b/tests-old-fs/empty.bin new file mode 100644 index 000000000..1eb401e04 Binary files /dev/null and b/tests-old-fs/empty.bin differ diff --git a/tests-old-fs/recurse.bin b/tests-old-fs/recurse.bin new file mode 100644 index 000000000..b1bfe814e Binary files /dev/null and b/tests-old-fs/recurse.bin differ diff --git a/tests-old-fs/root.bin b/tests-old-fs/root.bin new file mode 100644 index 000000000..6b7fad791 Binary files /dev/null and b/tests-old-fs/root.bin differ diff --git a/tests/create_old_fs.rs b/tests/create_old_fs.rs new file mode 100644 index 000000000..6e02d4ebf --- /dev/null +++ b/tests/create_old_fs.rs @@ -0,0 +1,185 @@ +use littlefs2::{ + consts, + fs::Filesystem, + path, + path::{Path, PathBuf}, + ram_storage, +}; + +ram_storage!( + name = RamStorage, + backend = Ram, + erase_value = 0xff, + read_size = 20 * 5, + write_size = 20 * 7, + cache_size_ty = consts::U700, + block_size = 20 * 35, + block_count = 32, + lookahead_size_ty = consts::U16, + filename_max_plus_one_ty = consts::U256, + path_max_plus_one_ty = consts::U256, +); + +struct FileTest { + name: &'static Path, + content: &'static [u8], +} + +struct DirTest { + files: &'static [FileTest], + sub_dirs: &'static [(&'static Path, DirTest)], +} + +struct FsTest { + root: DirTest, + name: &'static str, +} + +const EMPTY_DIR: FsTest = FsTest { + name: "empty.bin", + root: DirTest { + files: &[], + sub_dirs: &[], + }, +}; + +const ROOT_FULL: FsTest = FsTest { + name: "root.bin", + root: DirTest { + files: &[ + FileTest { + name: path!("test_file.txt"), + content: b"Test content - test_file.txt", + }, + FileTest { + name: path!("test_file2.txt"), + content: b"Test content - test_file2.txt", + }, + FileTest { + name: path!("test_file3.txt"), + content: b"Test content - test_file3.txt", + }, + ], + sub_dirs: &[], + }, +}; + +const RECURSE: FsTest = FsTest { + name: "recurse.bin", + root: DirTest { + files: ROOT_FULL.root.files, + sub_dirs: &[ + ( + path!("root1"), + DirTest { + files: &[ + FileTest { + name: path!("test_sub_file.txt"), + content: b"Test content - test_sub_file.txt", + }, + FileTest { + name: path!("test_sub_file2.txt"), + content: b"Test content - test_sub_file2.txt", + }, + ], + sub_dirs: &[( + path!("sub-dir"), + DirTest { + files: &[ + FileTest { + name: path!("test_sub_sub_file.txt"), + content: b"Test content - test_sub_sub_file.txt", + }, + FileTest { + name: path!("test_sub_sub_file2.txt"), + content: b"Test content - test_sub_sub_file2.txt", + }, + ], + sub_dirs: &[], + }, + )], + }, + ), + ( + path!("root2"), + DirTest { + files: &[], + sub_dirs: &[], + }, + ), + ], + }, +}; + +const ALL: &[FsTest] = &[EMPTY_DIR, ROOT_FULL, RECURSE]; + +fn write_dir(fs: &Filesystem, dir: &DirTest, current_dir: PathBuf) { + println!("Writing current_dir: {current_dir}"); + for f in dir.files { + let mut buf = current_dir.clone(); + buf.push(f.name); + println!( + "Writing {}, ({})", + f.name, + std::str::from_utf8(f.content).unwrap() + ); + fs.write(&buf, f.content).unwrap(); + } + + for (name, d) in dir.sub_dirs { + let mut buf = current_dir.clone(); + buf.push(name); + fs.create_dir(&buf).unwrap(); + write_dir(fs, d, buf); + } +} + +fn read_dir(fs: &Filesystem, dir: &DirTest, current_dir: PathBuf) { + println!("Reading current_dir: {current_dir}"); + for f in dir.files { + let mut buf = current_dir.clone(); + buf.push(f.name); + dbg!(&buf); + let read = fs.read::<1024>(&buf).unwrap(); + assert_eq!(std::str::from_utf8(&read), std::str::from_utf8(f.content)); + } + + for (name, d) in dir.sub_dirs { + let mut buf = current_dir.clone(); + buf.push(name); + read_dir(fs, d, buf); + } +} + +#[test] +#[ignore] +fn create() { + for fs_test in ALL { + println!("Got to test: {}", fs_test.name); + let mut backend = Ram::default(); + let mut storage = RamStorage::new(&mut backend); + Filesystem::format(&mut storage).unwrap(); + Filesystem::mount_and_then(&mut storage, |fs| { + write_dir(fs, &fs_test.root, PathBuf::new()); + Ok(()) + }) + .unwrap(); + std::fs::write(format!("tests-old-fs/{}", fs_test.name), backend.buf).unwrap(); + } +} + +#[test] +fn read() { + for fs_test in ALL { + println!("Got to test: {}", fs_test.name); + let mut backend = Ram::default(); + let buf = std::fs::read(format!("tests-old-fs/{}", fs_test.name)).unwrap(); + backend.buf.copy_from_slice(&buf); + let mut storage = RamStorage::new(&mut backend); + Filesystem::mount_and_then(&mut storage, |fs| { + read_dir(fs, &fs_test.root, PathBuf::new()); + Ok(()) + }) + .unwrap(); + } +}