diff --git a/Cargo.toml b/Cargo.toml index 58632039..879cbc03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,17 +22,23 @@ repository.workspace = true [package.metadata.docs.rs] all-features = true +[[example]] +name = "list" +required-features = ["alloc", "littlefs2-core/heapless07"] + [dependencies] bitflags = "2.9.0" delog = "0.1.0" -generic-array = "0.14" heapless = "0.7" littlefs2-core = { version = "0.1", path = "core" } littlefs2-sys = { version = "0.3.1", features = ["multiversion"] } +pin-project = "1.1.10" [dev-dependencies] ssmarshal = "1" serde = { version = "1.0", default-features = false, features = ["derive"] } +clap = { version = "4.5.31", features = ["derive"] } +hex = "0.4.3" # trybuild = "1" [features] diff --git a/examples/list.rs b/examples/list.rs index 06300945..94ca0b9a 100644 --- a/examples/list.rs +++ b/examples/list.rs @@ -1,11 +1,9 @@ use std::{ - env, fs::File, io::{Read as _, Seek as _, SeekFrom}, }; use littlefs2::{ - consts::{U1, U512}, driver::Storage, fs::{Allocation, FileType, Filesystem}, io::{Error, Result}, @@ -13,48 +11,81 @@ use littlefs2::{ path::{Path, PathBuf}, }; -const BLOCK_COUNT: usize = 128; -const BLOCK_SIZE: usize = 512; +use clap::Parser; + +#[derive(Parser)] +struct Args { + path: String, + block_size: usize, + #[arg(short, long)] + write_size: Option, + #[arg(short, long)] + read_size: Option, + #[arg(short, long)] + cache_size: Option, + #[arg(short, long)] + lookahead_size: Option, + #[arg(short, long)] + block_count: Option, + #[arg(short, long)] + show_hex: bool, +} + +const BLOCK_COUNT: usize = 288; +const BLOCK_SIZE: usize = 256; fn main() { - let path = env::args().nth(1).expect("missing argument"); - let file = File::open(&path).expect("failed to open file"); + let args = Args::parse(); + let file = File::open(&args.path).expect("failed to open file"); let metadata = file.metadata().expect("failed to query metadata"); - let expected_len = BLOCK_COUNT * BLOCK_SIZE; let actual_len = usize::try_from(metadata.len()).unwrap(); - assert_eq!(actual_len % BLOCK_COUNT, 0); + if let Some(block_count) = args.block_count { + assert_eq!(actual_len, args.block_size * block_count); + } + assert_eq!(actual_len % args.block_size, 0); + let block_count = actual_len / args.block_size; let mut s = FileStorage { file, len: actual_len, + read_size: args.read_size.unwrap_or(args.block_size), + write_size: args.write_size.unwrap_or(args.block_size), + cache_size: args.cache_size.unwrap_or(args.block_size), + lookahead_size: args.lookahead_size.unwrap_or(1), + block_count, + block_size: args.block_size, }; - let mut alloc = Allocation::new(); + let mut alloc = Allocation::new(&s); let fs = Filesystem::mount(&mut alloc, &mut s).expect("failed to mount filesystem"); let available_blocks = fs.available_blocks().unwrap(); - println!("expected_len: {expected_len}"); println!("actual_len: {actual_len}"); println!("available_blocks: {available_blocks}"); println!(); let path = PathBuf::new(); - list(&fs, &path); + list(&fs, &path, args.show_hex); } -fn list(fs: &dyn DynFilesystem, path: &Path) { +fn list(fs: &dyn DynFilesystem, path: &Path, show_hex: bool) { fs.read_dir_and_then(path, &mut |iter| { for entry in iter { let entry = entry.unwrap(); match entry.file_type() { - FileType::File => println!("F {}", entry.path()), + FileType::File => { + println!("F {}", entry.path()); + if show_hex { + let bytes: heapless::Vec = fs.read(entry.path()).unwrap(); + println!(" {}", hex::encode_upper(&bytes)); + } + } FileType::Dir => match entry.file_name().as_str() { "." => (), ".." => (), _ => { - println!("D {}", entry.path()); - list(fs, entry.path()); + list(fs, entry.path(), show_hex); } }, } @@ -67,16 +98,38 @@ fn list(fs: &dyn DynFilesystem, path: &Path) { struct FileStorage { file: File, len: usize, + read_size: usize, + write_size: usize, + block_size: usize, + block_count: usize, + cache_size: usize, + lookahead_size: usize, } impl Storage for FileStorage { - type CACHE_SIZE = U512; - type LOOKAHEAD_SIZE = U1; + type CACHE_BUFFER = Vec; + type LOOKAHEAD_BUFFER = Vec; - const READ_SIZE: usize = 16; - const WRITE_SIZE: usize = 512; - const BLOCK_SIZE: usize = BLOCK_SIZE; - const BLOCK_COUNT: usize = BLOCK_COUNT; + fn read_size(&self) -> usize { + self.read_size + } + fn write_size(&self) -> usize { + self.write_size + } + fn block_size(&self) -> usize { + self.block_size + } + fn block_count(&self) -> usize { + self.block_count + } + + fn cache_size(&self) -> usize { + self.cache_size + } + + fn lookahead_size(&self) -> usize { + self.lookahead_size + } fn read(&mut self, off: usize, buf: &mut [u8]) -> Result { assert!(off + buf.len() <= BLOCK_SIZE * BLOCK_COUNT); diff --git a/src/consts.rs b/src/consts.rs index 2abb971b..99ee0165 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,8 +1,5 @@ #![allow(non_camel_case_types)] -/// Re-export of `typenum::consts`. -pub use generic_array::typenum::consts::*; - pub const PATH_MAX: usize = littlefs2_core::PathBuf::MAX_SIZE; pub const PATH_MAX_PLUS_ONE: usize = littlefs2_core::PathBuf::MAX_SIZE_PLUS_ONE; pub const FILENAME_MAX_PLUS_ONE: u32 = 255 + 1; diff --git a/src/driver.rs b/src/driver.rs index 52c896c2..75a98715 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -1,9 +1,90 @@ //! The `Storage`, `Read`, `Write` and `Seek` driver. #![allow(non_camel_case_types)] -use generic_array::ArrayLength; +use crate::io::Error; -use crate::io::Result; +mod private { + pub struct NotEnoughCapacity; + pub trait Sealed { + /// Returns a buffer of bytes initialized and valid. If [`set_len`]() was called previously successfully, + /// its last call defines the minimum number of valid bytes + fn as_ptr(&self) -> *const u8; + /// Returns a buffer of bytes initialized and valid. If [`set_len`]() was called previously successfully, + /// its last call defines the minimum number of valid bytes + fn as_mut_ptr(&mut self) -> *mut u8; + + /// Current lenght, set by the last call to [`set_len`](Buffer::set_len) + fn current_len(&self) -> usize; + + /// Atempts to set the length of the buffer to `len` + /// + /// If succeeded, the buffer obtained through the pointer operation **must** be of at least `len` bytes + fn set_len(&mut self, len: usize) -> Result<(), NotEnoughCapacity>; + + // We could use a `Default` trait bound but it's not implemented for all array sizes + fn empty() -> Self; + } +} + +pub(crate) use private::Sealed; + +/// Safety: implemented only by `[u8; N]` and `Vec` if the alloc feature is enabled +pub unsafe trait Buffer: private::Sealed {} + +impl private::Sealed for [u8; N] { + fn as_ptr(&self) -> *const u8 { + <[u8]>::as_ptr(self) + } + + fn as_mut_ptr(&mut self) -> *mut u8 { + <[u8]>::as_mut_ptr(self) + } + + fn current_len(&self) -> usize { + N + } + + fn set_len(&mut self, len: usize) -> Result<(), private::NotEnoughCapacity> { + if len > N { + Err(private::NotEnoughCapacity) + } else { + Ok(()) + } + } + + fn empty() -> Self { + [0; N] + } +} + +unsafe impl Buffer for [u8; N] {} + +#[cfg(feature = "alloc")] +impl private::Sealed for alloc::vec::Vec { + fn as_ptr(&self) -> *const u8 { + <[u8]>::as_ptr(self) + } + + fn as_mut_ptr(&mut self) -> *mut u8 { + <[u8]>::as_mut_ptr(self) + } + + fn current_len(&self) -> usize { + self.len() + } + + fn set_len(&mut self, len: usize) -> Result<(), private::NotEnoughCapacity> { + self.resize(len, 0); + Ok(()) + } + + fn empty() -> Self { + Self::new() + } +} + +#[cfg(feature = "alloc")] +unsafe impl Buffer for alloc::vec::Vec {} /// Users of this library provide a "storage driver" by implementing this trait. /// @@ -12,44 +93,43 @@ use crate::io::Result; /// Do note that due to caches, files still must be synched. And unfortunately, /// this can't be automatically done in `drop`, since it needs mut refs to both /// filesystem and storage. -/// -/// The `*_SIZE` types must be `generic_array::typenume::consts` such as `U256`. -/// -/// Why? Currently, associated constants can not be used (as constants...) to define -/// arrays. This "will be fixed" as part of const generics. -/// Once that's done, we can get rid of `generic-array`s, and replace the -/// `*_SIZE` types with `usize`s. pub trait Storage { // /// Error type for user-provided read/write/erase methods // type Error = usize; /// Minimum size of block read in bytes. Not in superblock - const READ_SIZE: usize; + fn read_size(&self) -> usize; /// Minimum size of block write in bytes. Not in superblock - const WRITE_SIZE: usize; + fn write_size(&self) -> usize; /// Size of an erasable block in bytes, as unsigned typenum. /// Must be a multiple of both `READ_SIZE` and `WRITE_SIZE`. /// [At least 128](https://github.com/littlefs-project/littlefs/issues/264#issuecomment-519963153). Stored in superblock. - const BLOCK_SIZE: usize; + fn block_size(&self) -> usize; /// Number of erasable blocks. /// Hence storage capacity is `BLOCK_COUNT * BLOCK_SIZE` - const BLOCK_COUNT: usize; + fn block_count(&self) -> usize; /// Suggested values are 100-1000, higher is more performant but /// less wear-leveled. Default of -1 disables wear-leveling. /// Value zero is invalid, must be positive or -1. - const BLOCK_CYCLES: isize = -1; + fn block_cycles(&self) -> isize { + -1 + } /// littlefs uses a read cache, a write cache, and one cache per per file. - /// Must be a multiple of `READ_SIZE` and `WRITE_SIZE`. - /// Must be a factor of `BLOCK_SIZE`. - type CACHE_SIZE: ArrayLength; + type CACHE_BUFFER: Buffer; + + /// Must be a multiple of `read_size` and `write_size`. + /// Must be a factor of `block_size`. + fn cache_size(&self) -> usize; + /// Lookahead buffer used by littlefs + type LOOKAHEAD_BUFFER: Buffer; /// Size of the lookahead buffer used by littlefs, measured in multiples of 8 bytes. - type LOOKAHEAD_SIZE: ArrayLength; + fn lookahead_size(&self) -> usize; ///// Maximum length of a filename plus one. Stored in superblock. ///// Should default to 255+1, but associated type defaults don't exist currently. @@ -83,13 +163,13 @@ pub trait Storage { /// Read data from the storage device. /// Guaranteed to be called only with bufs of length a multiple of READ_SIZE. - fn read(&mut self, off: usize, buf: &mut [u8]) -> Result; + fn read(&mut self, off: usize, buf: &mut [u8]) -> Result; /// Write data to the storage device. /// Guaranteed to be called only with bufs of length a multiple of WRITE_SIZE. - fn write(&mut self, off: usize, data: &[u8]) -> Result; + fn write(&mut self, off: usize, data: &[u8]) -> Result; /// Erase data from the storage device. /// Guaranteed to be called only with bufs of length a multiple of BLOCK_SIZE. - fn erase(&mut self, off: usize, len: usize) -> Result; + fn erase(&mut self, off: usize, len: usize) -> Result; // /// Synchronize writes to the storage device. // fn sync(&mut self) -> Result; } diff --git a/src/fs.rs b/src/fs.rs index 5bbdaea1..5195478d 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -1,22 +1,21 @@ //! Experimental Filesystem version using closures. use core::ffi::{c_int, c_void}; +use core::marker::PhantomData; +use core::pin::{pin, Pin}; use core::ptr::addr_of; use core::ptr::addr_of_mut; use core::{ cell::{RefCell, UnsafeCell}, mem, slice, }; -use generic_array::typenum::marker_traits::Unsigned; use littlefs2_sys as ll; - -// so far, don't need `heapless-bytes`. -pub type Bytes = generic_array::GenericArray; +use pin_project::{pin_project, pinned_drop}; pub use littlefs2_core::{Attribute, DirEntry, FileOpenFlags, FileType, Metadata}; use crate::{ - driver, + driver::{self, Sealed}, io::{self, Error, OpenSeekFrom, Result}, path::{Path, PathBuf}, DISK_VERSION, @@ -44,41 +43,39 @@ pub fn u32_result(return_value: i32) -> Result { } struct Cache { - read: UnsafeCell>, - write: UnsafeCell>, - // lookahead: aligned::Aligned>, - lookahead: UnsafeCell>, + read: UnsafeCell, + write: UnsafeCell, + lookahead: UnsafeCell, } impl Cache { pub fn new() -> Self { Self { - read: Default::default(), - write: Default::default(), - lookahead: Default::default(), + read: UnsafeCell::new(S::CACHE_BUFFER::empty()), + write: UnsafeCell::new(S::CACHE_BUFFER::empty()), + lookahead: UnsafeCell::new(S::LOOKAHEAD_BUFFER::empty()), } } } -impl Default for Cache { - fn default() -> Self { - Self::new() - } -} - pub struct Allocation { cache: Cache, config: ll::lfs_config, state: ll::lfs_t, } -// pub fn check_storage_requirements( - -impl Default for Allocation { - fn default() -> Self { - Self::new() - } -} +/// # Safety +/// +/// All operations are done on `&mut Allocation, and the reference is held +/// during the entire lifetime of the filesystem, so once the reference is +/// available again, the filesystem is closed +unsafe impl Sync for Allocation {} +/// # Safety +/// +/// All operations are done on `&mut Allocation, and the reference is held +/// during the entire lifetime of the filesystem, so once the reference is +/// available again, the filesystem is closed +unsafe impl Send for Allocation {} #[derive(Default, Clone, Debug)] #[non_exhaustive] @@ -94,17 +91,18 @@ bitflags::bitflags! { } impl Allocation { - pub fn new() -> Self { - Self::with_config(Config::default()) + pub fn new(storage: &Storage) -> Self { + Self::with_config(storage, Config::default()) } - pub fn with_config(config: Config) -> Allocation { - let read_size: u32 = Storage::READ_SIZE as _; - let write_size: u32 = Storage::WRITE_SIZE as _; - let block_size: u32 = Storage::BLOCK_SIZE as _; - let cache_size: u32 = ::CACHE_SIZE::U32; - let lookahead_size: u32 = 8 * ::LOOKAHEAD_SIZE::U32; - let block_cycles: i32 = Storage::BLOCK_CYCLES as _; - let block_count: u32 = Storage::BLOCK_COUNT as _; + + pub fn with_config(storage: &Storage, config: Config) -> Allocation { + let read_size: u32 = storage.read_size() as _; + let write_size: u32 = storage.write_size() as _; + let block_size: u32 = storage.block_size() as _; + let cache_size: u32 = storage.cache_size() as _; + let lookahead_size: u32 = 8 * storage.lookahead_size() as u32; + let block_cycles: i32 = storage.block_cycles() as _; + let block_count: u32 = storage.block_count() as _; debug_assert!(block_cycles >= -1); debug_assert!(block_cycles != 0); @@ -201,6 +199,7 @@ impl Allocation { pub struct Filesystem<'a, Storage: driver::Storage> { alloc: RefCell<&'a mut Allocation>, storage: &'a mut Storage, + cache_size: usize, } fn metadata(info: ll::lfs_info) -> Metadata { @@ -220,9 +219,9 @@ struct RemoveDirAllProgress { skipped_any: bool, } -impl Filesystem<'_, Storage> { - pub fn allocate() -> Allocation { - Allocation::new() +impl<'a, Storage: driver::Storage> Filesystem<'a, Storage> { + pub fn allocate(storage: &Storage) -> Allocation { + Allocation::new(storage) } pub fn format(storage: &mut Storage) -> Result<()> { @@ -230,8 +229,8 @@ impl Filesystem<'_, Storage> { } pub fn format_with_config(storage: &mut Storage, config: Config) -> Result<()> { - let alloc = &mut Allocation::with_config(config); - let fs = Filesystem::new(alloc, storage); + let alloc = &mut Allocation::with_config(storage, config); + let fs = Filesystem::new(alloc, storage)?; let mut alloc = fs.alloc.borrow_mut(); let return_code = unsafe { ll::lfs_format(&mut alloc.state, &alloc.config) }; result_from((), return_code) @@ -244,7 +243,7 @@ impl Filesystem<'_, Storage> { // TODO: check if this is equivalent to `is_formatted`. pub fn is_mountable_with_config(storage: &mut Storage, config: Config) -> bool { - let alloc = &mut Allocation::with_config(config); + let alloc = &mut Allocation::with_config(storage, config); Filesystem::mount(alloc, storage).is_ok() } @@ -269,7 +268,7 @@ impl Filesystem<'_, Storage> { config: Config, f: impl FnOnce(&Filesystem<'_, Storage>) -> Result, ) -> Result { - let mut alloc = Allocation::with_config(config); + let mut alloc = Allocation::with_config(storage, config); let fs = Filesystem::mount(&mut alloc, storage)?; f(&fs) } @@ -290,12 +289,12 @@ impl Filesystem<'_, Storage> { /// Total number of blocks in the filesystem pub fn total_blocks(&self) -> usize { - Storage::BLOCK_COUNT + self.storage.block_count() } /// Total number of bytes in the filesystem pub fn total_space(&self) -> usize { - Storage::BLOCK_COUNT * Storage::BLOCK_SIZE + self.storage.block_count() * self.storage.block_size() } /// Available number of unused blocks in the filesystem @@ -320,7 +319,7 @@ impl Filesystem<'_, Storage> { /// Second, files may be inlined. pub fn available_space(&self) -> Result { self.available_blocks() - .map(|blocks| blocks * Storage::BLOCK_SIZE) + .map(|blocks| blocks * self.storage.block_size()) } /// Remove a file or directory. @@ -448,7 +447,7 @@ impl Filesystem<'_, Storage> { pub fn create_file_and_then( &self, path: &Path, - f: impl FnOnce(&File<'_, '_, Storage>) -> Result, + f: impl FnOnce(&File<'_, '_, '_, Storage>) -> Result, ) -> Result { File::create_and_then(self, path, f) } @@ -456,7 +455,7 @@ impl Filesystem<'_, Storage> { pub fn open_file_and_then( &self, path: &Path, - f: impl FnOnce(&File<'_, '_, Storage>) -> Result, + f: impl FnOnce(&File<'_, '_, '_, Storage>) -> Result, ) -> Result { File::open_and_then(self, path, f) } @@ -465,23 +464,23 @@ impl Filesystem<'_, Storage> { OpenOptions::new() } - pub fn open_file_with_options_and_then( - &self, + pub fn open_file_with_options_and_then<'b, R>( + &'b self, o: impl FnOnce(&mut OpenOptions) -> &OpenOptions, path: &Path, - f: impl FnOnce(&File<'_, '_, Storage>) -> Result, + f: impl FnOnce(&File<'a, 'b, '_, Storage>) -> Result, ) -> Result { let mut options = OpenOptions::new(); o(&mut options).open_and_then(self, path, f) } /// Read attribute. - pub fn attribute<'a>( + pub fn attribute<'b>( &self, path: &Path, id: u8, - buffer: &'a mut [u8], - ) -> Result>> { + buffer: &'b mut [u8], + ) -> Result>> { let n = u32::try_from(buffer.len()).unwrap_or(u32::MAX); let return_code = unsafe { @@ -562,7 +561,7 @@ impl Filesystem<'_, Storage> { let storage = unsafe { &mut *((*c).context as *mut Storage) }; debug_assert!(!c.is_null()); // let block_size = unsafe { c.read().block_size }; - let block_size = Storage::BLOCK_SIZE as u32; + let block_size = storage.block_size() as u32; let off = (block * block_size + off) as usize; let buf: &[u8] = unsafe { slice::from_raw_parts(buffer as *const u8, size as usize) }; @@ -574,9 +573,9 @@ impl Filesystem<'_, Storage> { extern "C" fn lfs_config_erase(c: *const ll::lfs_config, block: ll::lfs_block_t) -> c_int { // println!("in lfs_config_erase"); let storage = unsafe { &mut *((*c).context as *mut Storage) }; - let off = block as usize * Storage::BLOCK_SIZE; + let off = block as usize * storage.block_size(); - error_code_from(storage.erase(off, Storage::BLOCK_SIZE)) + error_code_from(storage.erase(off, storage.block_size())) } /// C callback interface used by LittleFS to sync data with the lower level interface below the @@ -588,36 +587,63 @@ impl Filesystem<'_, Storage> { } } +#[pin_project(PinnedDrop, !Unpin)] /// The state of a `File`. Pre-allocate with `File::allocate`. -pub struct FileAllocation { - cache: UnsafeCell>, +pub struct FileAllocation<'a, 'b, S: driver::Storage> { + cache: UnsafeCell, state: ll::lfs_file_t, config: ll::lfs_file_config, + // If filesystem is Some, the file was opened + filesystem: Option<&'b Filesystem<'a, S>>, } -impl Default for FileAllocation { - fn default() -> Self { - Self::new() +impl<'a, 'b, S: driver::Storage> FileAllocation<'a, 'b, S> { + pub fn new() -> Self { + Self { + cache: UnsafeCell::new(S::CACHE_BUFFER::empty()), + state: unsafe { mem::MaybeUninit::zeroed().assume_init() }, + config: unsafe { mem::MaybeUninit::zeroed().assume_init() }, + filesystem: None, + } } } -impl FileAllocation { - pub fn new() -> Self { - let cache_size: u32 = ::CACHE_SIZE::to_u32(); - debug_assert!(cache_size > 0); - unsafe { mem::MaybeUninit::zeroed().assume_init() } +impl<'a, 'b, S: driver::Storage> FileAllocation<'a, 'b, S> { + fn close(self: Pin<&mut Self>) -> Result<()> { + let this = self.project(); + let Some(fs) = this.filesystem.take() else { + return Ok(()); + }; + + let return_code = unsafe { + ll::lfs_file_close( + &mut fs.alloc.borrow_mut().state, + // We need to use addr_of_mut! here instead of & mut since + // the FFI stores a copy of a pointer to the field state, + // so we cannot assert unique mutable access. + addr_of_mut!(*this.state), + ) + }; + result_from((), return_code) } } -pub struct File<'a, 'b, S: driver::Storage> { +impl<'a, 'b, S: driver::Storage> Default for FileAllocation<'a, 'b, S> { + fn default() -> Self { + Self::new() + } +} + +pub struct File<'a, 'b, 'c, S: driver::Storage> { // We must store a raw pointer here since the FFI retains a copy of a pointer // to the field alloc.state, so we cannot assert unique mutable access. - alloc: RefCell<*mut FileAllocation>, + alloc: *mut FileAllocation<'a, 'b, S>, + phantom: PhantomData>>, fs: &'b Filesystem<'a, S>, } -impl<'a, 'b, Storage: driver::Storage> File<'a, 'b, Storage> { - pub fn allocate() -> FileAllocation { +impl<'a, 'b, 'c, Storage: driver::Storage> File<'a, 'b, 'c, Storage> { + pub fn allocate() -> FileAllocation<'a, 'b, Storage> { FileAllocation::new() } @@ -632,25 +658,25 @@ impl<'a, 'b, Storage: driver::Storage> File<'a, 'b, Storage> { OpenOptions::new() } - pub unsafe fn open( + pub fn open( fs: &'b Filesystem<'a, Storage>, - alloc: &'b mut FileAllocation, + alloc: Pin<&'c mut FileAllocation<'a, 'b, Storage>>, path: &Path, ) -> Result { OpenOptions::new().read(true).open(fs, alloc, path) } pub fn open_and_then( - fs: &Filesystem<'a, Storage>, + fs: &'b Filesystem<'a, Storage>, path: &Path, - f: impl FnOnce(&File<'_, '_, Storage>) -> Result, + f: impl FnOnce(&File<'a, 'b, '_, Storage>) -> Result, ) -> Result { OpenOptions::new().read(true).open_and_then(fs, path, f) } - pub unsafe fn create( + pub fn create( fs: &'b Filesystem<'a, Storage>, - alloc: &'b mut FileAllocation, + alloc: Pin<&'c mut FileAllocation<'a, 'b, Storage>>, path: &Path, ) -> Result { OpenOptions::new() @@ -661,9 +687,9 @@ impl<'a, 'b, Storage: driver::Storage> File<'a, 'b, Storage> { } pub fn create_and_then( - fs: &Filesystem<'a, Storage>, + fs: &'b Filesystem<'a, Storage>, path: &Path, - f: impl FnOnce(&File<'_, '_, Storage>) -> Result, + f: impl FnOnce(&File<'a, '_, '_, Storage>) -> Result, ) -> Result { OpenOptions::new() .write(true) @@ -673,7 +699,7 @@ impl<'a, 'b, Storage: driver::Storage> File<'a, 'b, Storage> { } // Safety-hatch to experiment with missing parts of API - pub unsafe fn borrow_filesystem<'c>(&'c mut self) -> &'c Filesystem<'a, Storage> { + pub unsafe fn borrow_filesystem(&mut self) -> &Filesystem<'a, Storage> { self.fs } @@ -681,15 +707,9 @@ impl<'a, 'b, Storage: driver::Storage> File<'a, 'b, Storage> { /// Not doing this is UB, which is why we have all the closure-based APIs. /// /// This must not be called twice. - pub unsafe fn close(self) -> Result<()> { - let return_code = ll::lfs_file_close( - &mut self.fs.alloc.borrow_mut().state, - // We need to use addr_of_mut! here instead of & mut since - // the FFI stores a copy of a pointer to the field state, - // so we cannot assert unique mutable access. - addr_of_mut!((*(*self.alloc.borrow_mut())).state), - ); - result_from((), return_code) + pub fn close(self) -> Result<()> { + // Safety: alloc was obtained pinned + unsafe { Pin::new_unchecked(&mut *self.alloc) }.close() } /// Synchronize file contents to storage. @@ -700,7 +720,7 @@ impl<'a, 'b, Storage: driver::Storage> File<'a, 'b, Storage> { // so we cannot assert unique mutable access. ll::lfs_file_sync( &mut self.fs.alloc.borrow_mut().state, - addr_of_mut!((*(*self.alloc.borrow_mut())).state), + addr_of_mut!((*self.alloc).state), ) }; result_from((), return_code) @@ -714,7 +734,7 @@ impl<'a, 'b, Storage: driver::Storage> File<'a, 'b, Storage> { // so we cannot assert unique mutable access. ll::lfs_file_size( &mut self.fs.alloc.borrow_mut().state, - addr_of_mut!((*(*self.alloc.borrow_mut())).state), + addr_of_mut!((*self.alloc).state), ) }; u32_result(return_code).map(|n| n as usize) @@ -736,7 +756,7 @@ impl<'a, 'b, Storage: driver::Storage> File<'a, 'b, Storage> { // so we cannot assert unique mutable access. ll::lfs_file_truncate( &mut self.fs.alloc.borrow_mut().state, - addr_of_mut!((*(*self.alloc.borrow_mut())).state), + addr_of_mut!((*self.alloc).state), size as u32, ) }; @@ -772,6 +792,13 @@ impl<'a, 'b, Storage: driver::Storage> File<'a, 'b, Storage> { } } +#[pinned_drop] +impl<'a, 'b, S: driver::Storage> PinnedDrop for FileAllocation<'a, 'b, S> { + fn drop(self: Pin<&mut Self>) { + self.close().ok(); + } +} + /// Options and flags which can be used to configure how a file is opened. /// /// This builder exposes the ability to configure how a File is opened and what operations @@ -800,26 +827,44 @@ impl OpenOptions { /// closing removes them from there /// - since littlefs is supposed to be *fail-safe*, we can't just close files in /// Drop and panic if something went wrong. - pub unsafe fn open<'a, 'b, S: driver::Storage>( + pub fn open<'a, 'b, 'c, S: driver::Storage>( &self, fs: &'b Filesystem<'a, S>, - alloc: &mut FileAllocation, + mut alloc: Pin<&'c mut FileAllocation<'a, 'b, S>>, path: &Path, - ) -> Result> { - alloc.config.buffer = alloc.cache.get() as *mut _; + ) -> Result> { + let alloc_proj = alloc.as_mut().project(); + alloc_proj + .cache + .get_mut() + .set_len(fs.cache_size) + .map_err(|_| { + error_now!("Buffer is not large enough for cache size of {cache_size}"); + Error::NO_MEMORY + })?; + alloc_proj.config.buffer = alloc_proj.cache.get_mut().as_mut_ptr() as *mut _; + // We need to use addr_of_mut! here instead of & mut since // the FFI stores a copy of a pointer to the field state, // so we cannot assert unique mutable access. - let return_code = ll::lfs_file_opencfg( - &mut fs.alloc.borrow_mut().state, - addr_of_mut!(alloc.state), - path.as_ptr(), - self.0.bits(), - addr_of!(alloc.config), - ); + let return_code = unsafe { + ll::lfs_file_opencfg( + &mut fs.alloc.borrow_mut().state, + addr_of_mut!(*alloc_proj.state), + path.as_ptr(), + self.0.bits(), + addr_of!(*alloc_proj.config), + ) + }; + + // The file was opened + if return_code >= 0 { + *alloc_proj.filesystem = Some(fs); + } let file = File { - alloc: RefCell::new(alloc), + alloc: unsafe { alloc.get_unchecked_mut() }, + phantom: PhantomData, fs, }; @@ -827,20 +872,21 @@ impl OpenOptions { } /// (Hopefully) safe abstraction around `open`. - pub fn open_and_then<'a, R, S: driver::Storage>( + pub fn open_and_then<'a, 'b, R, S: driver::Storage>( &self, - fs: &Filesystem<'a, S>, + fs: &'b Filesystem<'a, S>, path: &Path, - f: impl FnOnce(&File<'a, '_, S>) -> Result, + f: impl FnOnce(&File<'a, 'b, '_, S>) -> Result, ) -> Result { - let mut alloc = FileAllocation::new(); // lifetime 'c - let mut file = unsafe { self.open(fs, &mut alloc, path)? }; + let alloc = File::allocate(); + let alloc = pin!(alloc); + let mut file = self.open(fs, alloc, path)?; // Q: what is the actually correct behaviour? // E.g. if res is Ok but closing gives an error. // Or if closing fails because something is broken and // we'd already know that from an Err res. let res = f(&mut file); - unsafe { file.close()? }; + file.close()?; res } @@ -911,7 +957,7 @@ impl From for OpenOptions { } } -impl io::Read for File<'_, '_, S> { +impl io::Read for File<'_, '_, '_, S> { fn read(&self, buf: &mut [u8]) -> Result { let return_code = unsafe { // We need to use addr_of_mut! here instead of & mut since @@ -919,7 +965,7 @@ impl io::Read for File<'_, '_, S> { // so we cannot assert unique mutable access. ll::lfs_file_read( &mut self.fs.alloc.borrow_mut().state, - addr_of_mut!((*(*self.alloc.borrow_mut())).state), + addr_of_mut!((*self.alloc).state), buf.as_mut_ptr() as *mut c_void, buf.len() as u32, ) @@ -928,7 +974,7 @@ impl io::Read for File<'_, '_, S> { } } -impl io::Seek for File<'_, '_, S> { +impl io::Seek for File<'_, '_, '_, S> { fn seek(&self, pos: io::SeekFrom) -> Result { let return_code = unsafe { // We need to use addr_of_mut! here instead of & mut since @@ -936,7 +982,7 @@ impl io::Seek for File<'_, '_, S> { // so we cannot assert unique mutable access. ll::lfs_file_seek( &mut self.fs.alloc.borrow_mut().state, - addr_of_mut!((*(*self.alloc.borrow_mut())).state), + addr_of_mut!((*self.alloc).state), pos.off(), pos.whence(), ) @@ -945,7 +991,7 @@ impl io::Seek for File<'_, '_, S> { } } -impl io::Write for File<'_, '_, S> { +impl io::Write for File<'_, '_, '_, S> { fn write(&self, buf: &[u8]) -> Result { let return_code = unsafe { // We need to use addr_of_mut! here instead of & mut since @@ -953,7 +999,7 @@ impl io::Write for File<'_, '_, S> { // so we cannot assert unique mutable access. ll::lfs_file_write( &mut self.fs.alloc.borrow_mut().state, - addr_of_mut!((*(*self.alloc.borrow_mut())).state), + addr_of_mut!((*self.alloc).state), buf.as_ptr() as *const c_void, buf.len() as u32, ) @@ -966,31 +1012,58 @@ impl io::Write for File<'_, '_, S> { } } -pub struct ReadDirAllocation { +#[pin_project(PinnedDrop, !Unpin)] +pub struct ReadDirAllocation<'a, 'b, S: driver::Storage> { state: ll::lfs_dir_t, + fs: Option<&'b Filesystem<'a, S>>, +} + +#[pinned_drop] +impl PinnedDrop for ReadDirAllocation<'_, '_, S> { + fn drop(self: Pin<&mut Self>) { + self.close().ok(); + } } -impl Default for ReadDirAllocation { +impl Default for ReadDirAllocation<'_, '_, S> { fn default() -> Self { Self::new() } } -impl ReadDirAllocation { +impl ReadDirAllocation<'_, '_, S> { pub fn new() -> Self { - unsafe { mem::MaybeUninit::zeroed().assume_init() } + Self { + state: unsafe { mem::MaybeUninit::zeroed().assume_init() }, + fs: None, + } + } + + fn close(self: Pin<&mut Self>) -> Result<()> { + let this = self.project(); + + let Some(fs) = this.fs else { return Ok(()) }; + + let return_code = unsafe { + // We need to use addr_of_mut! here instead of & mut since + // the FFI stores a copy of a pointer to the field state, + // so we cannot assert unique mutable access. + ll::lfs_dir_close(&mut fs.alloc.borrow_mut().state, addr_of_mut!(*this.state)) + }; + result_from((), return_code) } } -pub struct ReadDir<'a, 'b, S: driver::Storage> { +pub struct ReadDir<'a, 'b, 'c, S: driver::Storage> { // We must store a raw pointer here since the FFI retains a copy of a pointer // to the field alloc.state, so we cannot assert unique mutable access. - alloc: RefCell<*mut ReadDirAllocation>, + alloc: *mut ReadDirAllocation<'a, 'b, S>, + phantom: PhantomData>>, fs: &'b Filesystem<'a, S>, path: &'b Path, } -impl Iterator for ReadDir<'_, '_, S> { +impl Iterator for ReadDir<'_, '_, '_, S> { type Item = Result; // remove this allowance again, once path overflow is properly handled @@ -1003,7 +1076,7 @@ impl Iterator for ReadDir<'_, '_, S> { let return_code = unsafe { ll::lfs_dir_read( &mut self.fs.alloc.borrow_mut().state, - addr_of_mut!((*(*self.alloc.borrow_mut())).state), + addr_of_mut!((*self.alloc).state), &mut info, ) }; @@ -1026,14 +1099,14 @@ impl Iterator for ReadDir<'_, '_, S> { } } -impl<'a, S: driver::Storage> ReadDir<'a, '_, S> { +impl<'a, S: driver::Storage> ReadDir<'a, '_, '_, S> { // Safety-hatch to experiment with missing parts of API pub unsafe fn borrow_filesystem<'b>(&'b mut self) -> &'b Filesystem<'a, S> { self.fs } } -impl ReadDir<'_, '_, S> { +impl ReadDir<'_, '_, '_, S> { // Again, not sure if this can be called twice // Update: This one seems to be safe to call multiple times, // it just goes through the "mlist" and removes itself. @@ -1042,28 +1115,21 @@ impl ReadDir<'_, '_, S> { // have an (unsafely genereated) ReadDir with that handle; on the other hand // as long as ReadDir is not Copy. pub fn close(self) -> Result<()> { - let return_code = unsafe { - // We need to use addr_of_mut! here instead of & mut since - // the FFI stores a copy of a pointer to the field state, - // so we cannot assert unique mutable access. - ll::lfs_dir_close( - &mut self.fs.alloc.borrow_mut().state, - addr_of_mut!((*(*self.alloc.borrow_mut())).state), - ) - }; - result_from((), return_code) + unsafe { Pin::new_unchecked(&mut *self.alloc) }.close() } } +impl ReadDir<'_, '_, '_, S> {} impl<'a, Storage: driver::Storage> Filesystem<'a, Storage> { pub fn read_dir_and_then( &self, path: &Path, // *not* &ReadDir, as Iterator takes &mut - f: impl FnOnce(&mut ReadDir<'_, '_, Storage>) -> Result, + f: impl FnOnce(&mut ReadDir<'_, '_, '_, Storage>) -> Result, ) -> Result { let mut alloc = ReadDirAllocation::new(); - let mut read_dir = unsafe { self.read_dir(&mut alloc, path)? }; + let alloc = pin!(alloc); + let mut read_dir = self.read_dir(alloc, path)?; let res = f(&mut read_dir); // unsafe { read_dir.close()? }; read_dir.close()?; @@ -1073,22 +1139,30 @@ impl<'a, Storage: driver::Storage> Filesystem<'a, Storage> { /// Returns a pseudo-iterator over the entries within a directory. /// /// This is unsafe since it can induce UB just like File::open. - pub unsafe fn read_dir<'b>( + pub fn read_dir<'b, 'c>( &'b self, - alloc: &'b mut ReadDirAllocation, + mut alloc: Pin<&'c mut ReadDirAllocation<'a, 'b, Storage>>, path: &'b Path, - ) -> Result> { + ) -> Result> { + let alloc_proj = alloc.as_mut().project(); // ll::lfs_dir_open stores a copy of the pointer to alloc.state, so // we must use addr_of_mut! here, since &mut alloc.state asserts unique // mutable access, and we need shared mutable access. - let return_code = ll::lfs_dir_open( - &mut self.alloc.borrow_mut().state, - addr_of_mut!(alloc.state), - path.as_ptr(), - ); + let return_code = unsafe { + ll::lfs_dir_open( + &mut self.alloc.borrow_mut().state, + addr_of_mut!(*alloc_proj.state), + path.as_ptr(), + ) + }; + + if return_code >= 0 { + *alloc_proj.fs = Some(self); + } let read_dir = ReadDir { - alloc: RefCell::new(alloc), + alloc: unsafe { alloc.get_unchecked_mut() }, + phantom: PhantomData, fs: self, path, }; @@ -1099,16 +1173,51 @@ impl<'a, Storage: driver::Storage> Filesystem<'a, Storage> { impl<'a, Storage: driver::Storage> Filesystem<'a, Storage> { pub fn mount(alloc: &'a mut Allocation, storage: &'a mut Storage) -> Result { - let fs = Self::new(alloc, storage); + let fs = Self::new(alloc, storage)?; fs.raw_mount()?; Ok(fs) } - fn set_alloc_config(alloc: &mut Allocation, storage: &mut Storage) { + fn set_alloc_config(alloc: &mut Allocation, storage: &mut Storage) -> Result { + let cache_size = storage.cache_size(); + let lookahead_size = storage.lookahead_size(); + + alloc + .cache + .read + .get_mut() + .set_len(cache_size) + .map_err(|_| { + error_now!("Buffer is not large enough for cache size of {cache_size}"); + Error::NO_MEMORY + })?; + alloc + .cache + .write + .get_mut() + .set_len(cache_size) + .map_err(|_| { + error_now!("Buffer is not large enough for cache size of {cache_size}"); + Error::NO_MEMORY + })?; + alloc + .cache + .lookahead + .get_mut() + .set_len(lookahead_size) + .map_err(|_| { + error_now!("Buffer is not large enough for lookahead size of {lookahead_size}"); + Error::NO_MEMORY + })?; + alloc.config.context = storage as *mut _ as *mut c_void; - alloc.config.read_buffer = alloc.cache.read.get() as *mut c_void; - alloc.config.prog_buffer = alloc.cache.write.get() as *mut c_void; - alloc.config.lookahead_buffer = alloc.cache.lookahead.get() as *mut c_void; + + alloc.config.read_buffer = alloc.cache.read.get_mut().as_mut_ptr() as *mut c_void; + alloc.config.prog_buffer = alloc.cache.write.get_mut().as_mut_ptr() as *mut c_void; + alloc.config.lookahead_buffer = alloc.cache.lookahead.get_mut().as_mut_ptr() as *mut c_void; + alloc.config.cache_size = cache_size as _; + alloc.config.lookahead_size = lookahead_size as _; + Ok(cache_size) } /// Mount the filesystem or, if that fails, call `f` with the mount error and the storage and then try again. @@ -1120,11 +1229,11 @@ impl<'a, Storage: driver::Storage> Filesystem<'a, Storage> { where F: FnOnce(Error, &mut Storage, &mut Allocation) -> Result<()>, { - let mut fs = Self::new(alloc, storage); + let mut fs = Self::new(alloc, storage)?; if let Err(err) = fs.raw_mount() { let alloc = fs.alloc.get_mut(); f(err, fs.storage, alloc)?; - Self::set_alloc_config(alloc, fs.storage); + Self::set_alloc_config(alloc, fs.storage)?; fs.raw_mount()?; } Ok(fs) @@ -1138,12 +1247,13 @@ impl<'a, Storage: driver::Storage> Filesystem<'a, Storage> { } // Not public, user should use `mount`, possibly after `format` - fn new(alloc: &'a mut Allocation, storage: &'a mut Storage) -> Self { - Self::set_alloc_config(alloc, storage); - Filesystem { + fn new(alloc: &'a mut Allocation, storage: &'a mut Storage) -> Result { + let cache_size = Self::set_alloc_config(alloc, storage)?; + Ok(Filesystem { + cache_size, alloc: RefCell::new(alloc), storage, - } + }) } /// Deconstruct `Filesystem`, intention is to allow access to @@ -1418,7 +1528,7 @@ mod tests { }) .unwrap(); - let mut alloc = Allocation::new(); + let mut alloc = Allocation::new(&test_storage); let fs = Filesystem::mount(&mut alloc, &mut test_storage).unwrap(); // fs.write(b"/z.txt\0".try_into().unwrap(), &jackson5).unwrap(); fs.write(path!("z.txt"), jackson5).unwrap(); @@ -1529,19 +1639,38 @@ mod tests { Ok(()) })?; - let mut a1 = File::allocate(); - let f1 = unsafe { File::create(fs, &mut a1, b"a.txt\0".try_into().unwrap())? }; + let a1 = File::allocate(); + let a1 = pin!(a1); + let f1 = File::create(fs, a1, b"a.txt\0".try_into().unwrap())?; f1.write(b"some text")?; - let mut a2 = File::allocate(); - let f2 = unsafe { File::create(fs, &mut a2, b"b.txt\0".try_into().unwrap())? }; + let a2 = File::allocate(); + let a2 = pin!(a2); + let f2 = File::create(fs, a2, b"b.txt\0".try_into().unwrap())?; f2.write(b"more text")?; - unsafe { f1.close()? }; // program hangs here - unsafe { f2.close()? }; // this statement is never reached + f1.close()?; // program hangs here + f2.close()?; // this statement is never reached Ok(()) }) .unwrap(); } + + #[allow(unreachable_code)] + fn _filesystem_is_sync() { + fn assert_is_sync(_: &T) -> R { + todo!() + } + fn assert_is_send(_: &T) -> R { + todo!() + } + + assert_is_sync::, ()>(todo!()); + assert_is_send::<&mut Allocation, ()>(todo!()); + assert_is_send::>, ()>(todo!()); + + let mut test_storage = TestStorage::new(); + Filesystem::mount_and_then(&mut test_storage, |fs| assert_is_send(fs)).unwrap() + } } diff --git a/src/lib.rs b/src/lib.rs index c7c5f92d..a4688c52 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -107,7 +107,7 @@ let mut storage = RamStorage::new(&mut ram); // must format before first mount Filesystem::format(&mut storage).unwrap(); // must allocate state statically before use -let mut alloc = Filesystem::allocate(); +let mut alloc = Filesystem::allocate(&storage); let mut fs = Filesystem::mount(&mut alloc, &mut storage).unwrap(); // may use common `OpenOptions` diff --git a/src/macros.rs b/src/macros.rs index c6e448e7..64220287 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -11,12 +11,10 @@ macro_rules! ram_storage { erase_value=$erase_value:expr, read_size=$read_size:expr, write_size=$write_size:expr, - cache_size_ty=$cache_size:path, + cache_size=$cache_size:expr, block_size=$block_size:expr, block_count=$block_count:expr, - lookahead_size_ty=$lookahead_size:path, - filename_max_plus_one_ty=$filename_max_plus_one:path, - path_max_plus_one_ty=$path_max_plus_one:path, + lookahead_size=$lookahead_size:expr, ) => { pub struct $Backend { @@ -43,15 +41,30 @@ macro_rules! ram_storage { } impl<'backend> $crate::driver::Storage for $Name<'backend> { - const READ_SIZE: usize = $read_size; - const WRITE_SIZE: usize = $write_size; - type CACHE_SIZE = $cache_size; - const BLOCK_SIZE: usize = $block_size; - const BLOCK_COUNT: usize = $block_count; - type LOOKAHEAD_SIZE = $lookahead_size; + fn read_size(&self) -> usize { + $read_size + } + fn write_size(&self) -> usize { + $write_size + } + fn block_size(&self) -> usize { + $block_size + } + fn cache_size(&self) -> usize { + $cache_size + } + type CACHE_BUFFER = [u8; $cache_size]; + fn block_count(&self) -> usize { + $block_count + } + + fn lookahead_size(&self) -> usize { + $lookahead_size + } + type LOOKAHEAD_BUFFER = [u8; $lookahead_size * 8]; fn read(&mut self, offset: usize, buf: &mut [u8]) -> $crate::io::Result { - let read_size: usize = Self::READ_SIZE; + let read_size: usize = self.read_size(); debug_assert!(offset % read_size == 0); debug_assert!(buf.len() % read_size == 0); for (from, to) in self.backend.buf[offset..].iter().zip(buf.iter_mut()) { @@ -61,7 +74,7 @@ macro_rules! ram_storage { } fn write(&mut self, offset: usize, data: &[u8]) -> $crate::io::Result { - let write_size: usize = Self::WRITE_SIZE; + let write_size: usize = self.write_size(); debug_assert!(offset % write_size == 0); debug_assert!(data.len() % write_size == 0); for (from, to) in data.iter().zip(self.backend.buf[offset..].iter_mut()) { @@ -71,7 +84,7 @@ macro_rules! ram_storage { } fn erase(&mut self, offset: usize, len: usize) -> $crate::io::Result { - let block_size: usize = Self::BLOCK_SIZE; + let block_size: usize = self.block_size(); debug_assert!(offset % block_size == 0); debug_assert!(len % block_size == 0); for byte in self.backend.buf[offset..offset + len].iter_mut() { @@ -88,12 +101,10 @@ macro_rules! ram_storage { erase_value = 0xff, read_size = 1, write_size = 1, - cache_size_ty = $crate::consts::U32, + cache_size = 32, block_size = 128, block_count = $bytes / 128, - lookahead_size_ty = $crate::consts::U1, - filename_max_plus_one_ty = $crate::consts::U256, - path_max_plus_one_ty = $crate::consts::U256, + lookahead_size = 1, ); }; (tiny) => { @@ -103,12 +114,10 @@ macro_rules! ram_storage { erase_value = 0xff, read_size = 32, write_size = 32, - cache_size_ty = $crate::consts::U32, + cache_size = 32, block_size = 128, block_count = 8, - lookahead_size_ty = $crate::consts::U1, - filename_max_plus_one_ty = $crate::consts::U256, - path_max_plus_one_ty = $crate::consts::U256, + lookahead_size = 1, ); }; (large) => { @@ -118,12 +127,10 @@ macro_rules! ram_storage { erase_value = 0xff, read_size = 32, write_size = 32, - cache_size_ty = $crate::consts::U32, + cache_size = 32, block_size = 256, block_count = 512, - lookahead_size_ty = $crate::consts::U4, - filename_max_plus_one_ty = $crate::consts::U256, - path_max_plus_one_ty = $crate::consts::U256, + lookahead_size = 4, ); }; } @@ -136,12 +143,10 @@ macro_rules! const_ram_storage { erase_value=$erase_value:expr, read_size=$read_size:expr, write_size=$write_size:expr, - cache_size_ty=$cache_size:path, + cache_size=$cache_size:expr, block_size=$block_size:expr, block_count=$block_count:expr, - lookahead_size_ty=$lookahead_size:path, - filename_max_plus_one_ty=$filename_max_plus_one:path, - path_max_plus_one_ty=$path_max_plus_one:path, + lookahead_size=$lookahead_size:expr, ) => { pub struct $Name { @@ -167,15 +172,32 @@ macro_rules! const_ram_storage { } impl $crate::driver::Storage for $Name { - const READ_SIZE: usize = $read_size; - const WRITE_SIZE: usize = $write_size; - type CACHE_SIZE = $cache_size; - const BLOCK_SIZE: usize = $block_size; - const BLOCK_COUNT: usize = $block_count; - type LOOKAHEAD_SIZE = $lookahead_size; + fn read_size(&self) -> usize { + $read_size + } + + fn write_size(&self) -> usize { + $write_size + } + + fn cache_size(&self) -> usize { + $cache_size + } + type CACHE_BUFFER = [u8; $cache_size]; + fn block_size(&self) -> usize { + $block_size + } + fn block_count(&self) -> usize { + $block_count + } + + fn lookahead_size(&self) -> usize { + $lookahead_size + } + type LOOKAHEAD_BUFFER = [u8; $lookahead_size * 8]; fn read(&mut self, offset: usize, buf: &mut [u8]) -> $crate::io::Result { - let read_size: usize = Self::READ_SIZE; + let read_size = self.read_size(); debug_assert!(offset % read_size == 0); debug_assert!(buf.len() % read_size == 0); for (from, to) in self.buf[offset..].iter().zip(buf.iter_mut()) { @@ -185,7 +207,7 @@ macro_rules! const_ram_storage { } fn write(&mut self, offset: usize, data: &[u8]) -> $crate::io::Result { - let write_size: usize = Self::WRITE_SIZE; + let write_size = self.write_size(); debug_assert!(offset % write_size == 0); debug_assert!(data.len() % write_size == 0); for (from, to) in data.iter().zip(self.buf[offset..].iter_mut()) { @@ -195,7 +217,7 @@ macro_rules! const_ram_storage { } fn erase(&mut self, offset: usize, len: usize) -> $crate::io::Result { - let block_size: usize = Self::BLOCK_SIZE; + let block_size: usize = self.block_size(); debug_assert!(offset % block_size == 0); debug_assert!(len % block_size == 0); for byte in self.buf[offset..offset + len].iter_mut() { @@ -211,12 +233,10 @@ macro_rules! const_ram_storage { erase_value = 0xff, read_size = 16, write_size = 512, - cache_size_ty = $crate::consts::U512, + cache_size = 512, block_size = 512, block_count = $bytes / 512, - lookahead_size_ty = $crate::consts::U1, - filename_max_plus_one_ty = $crate::consts::U256, - path_max_plus_one_ty = $crate::consts::U256, + lookahead_size = 1, ); }; } diff --git a/src/object_safe.rs b/src/object_safe.rs index fb102859..4ac4ab41 100644 --- a/src/object_safe.rs +++ b/src/object_safe.rs @@ -1,7 +1,5 @@ //! Object-safe traits for [`File`][], [`Filesystem`][] and [`Storage`][]. -use generic_array::typenum::Unsigned as _; - use crate::{ driver::Storage, fs::{Attribute, File, FileOpenFlags, Filesystem, Metadata}, @@ -19,7 +17,7 @@ pub type FilesystemCallback<'a, R = ()> = &'a mut dyn FnMut(&dyn DynFilesystem) pub type FilesystemCallbackOnce<'a, R = ()> = alloc::boxed::Box Result + 'a>; -impl DynFile for File<'_, '_, S> { +impl DynFile for File<'_, '_, '_, S> { fn sync(&self) -> Result<()> { File::sync(self) } @@ -179,31 +177,31 @@ pub trait DynStorage { impl DynStorage for S { fn read_size(&self) -> usize { - Self::READ_SIZE + ::read_size(self) } fn write_size(&self) -> usize { - Self::WRITE_SIZE + ::write_size(self) } fn block_size(&self) -> usize { - Self::BLOCK_SIZE + ::block_size(self) } fn block_count(&self) -> usize { - Self::BLOCK_COUNT + ::block_count(self) } fn block_cycles(&self) -> isize { - Self::BLOCK_CYCLES + ::block_cycles(self) } fn cache_size(&self) -> usize { - S::CACHE_SIZE::to_usize() + ::cache_size(self) } fn lookahead_size(&self) -> usize { - S::LOOKAHEAD_SIZE::to_usize() + ::lookahead_size(self) } fn read(&mut self, off: usize, buf: &mut [u8]) -> Result { diff --git a/src/tests.rs b/src/tests.rs index 2a0bf9c2..74097253 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,5 +1,4 @@ use core::convert::TryInto; -use generic_array::typenum::consts; use littlefs2_core::PathBuf; use crate::{ @@ -9,18 +8,19 @@ use crate::{ path, BACKEND_VERSION, DISK_VERSION, }; +const RAM_STORAGE_BLOCK_COUNT: usize = 32; +const LARGER_RAM_STORAGE_BLOCK_COUNT: usize = 64; + ram_storage!( name = OtherRamStorage, backend = OtherRam, erase_value = 0xff, read_size = 1, write_size = 32, - cache_size_ty = consts::U32, + cache_size = 32, block_size = 256, block_count = 512, - lookahead_size_ty = consts::U1, - filename_max_plus_one_ty = consts::U256, - path_max_plus_one_ty = consts::U256, + lookahead_size = 1, ); ram_storage!( @@ -29,12 +29,10 @@ ram_storage!( erase_value = 0xff, read_size = 20 * 5, write_size = 20 * 7, - cache_size_ty = consts::U700, + cache_size = 700, 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, + block_count = RAM_STORAGE_BLOCK_COUNT, + lookahead_size = 16, ); ram_storage!( @@ -43,12 +41,10 @@ ram_storage!( erase_value = 0xff, read_size = 20 * 5, write_size = 20 * 7, - cache_size_ty = consts::U700, + cache_size = 700, block_size = 20 * 35, - block_count = 64, - lookahead_size_ty = consts::U16, - filename_max_plus_one_ty = consts::U256, - path_max_plus_one_ty = consts::U256, + block_count = LARGER_RAM_STORAGE_BLOCK_COUNT, + lookahead_size = 16, ); #[test] @@ -61,7 +57,7 @@ fn version() { fn format() { let mut backend = OtherRam::default(); let mut storage = OtherRamStorage::new(&mut backend); - let mut alloc = Filesystem::allocate(); + let mut alloc = Filesystem::allocate(&storage); // should fail: FS is not formatted assert_eq!( @@ -93,7 +89,7 @@ fn borrow_fs_allocation() { let mut backend = OtherRam::default(); let mut storage = OtherRamStorage::new(&mut backend); - let mut alloc_fs = Filesystem::allocate(); + let mut alloc_fs = Filesystem::allocate(&storage); Filesystem::format(&mut storage).unwrap(); let _fs = Filesystem::mount(&mut alloc_fs, &mut storage).unwrap(); // previous `_fs` is fine as it's masked, due to NLL @@ -110,7 +106,7 @@ fn borrow_fs_allocation2() { let mut backend = OtherRam::default(); let mut storage = OtherRamStorage::new(&mut backend); - let mut alloc_fs = Filesystem::allocate(); + let mut alloc_fs = Filesystem::allocate(&storage); Filesystem::format(&mut storage).unwrap(); let _fs = Filesystem::mount(&mut alloc_fs, &mut storage).unwrap(); // previous `_fs` is fine as it's masked, due to NLL @@ -541,9 +537,9 @@ fn test_iter_dirs() { fn test_mount_or_else_clobber_alloc() { let mut backend = Ram::default(); let mut storage = RamStorage::new(&mut backend); - let alloc = &mut Allocation::new(); + let alloc = &mut Allocation::new(&storage); Filesystem::mount_or_else(alloc, &mut storage, |_, storage, alloc| { - *alloc = Allocation::new(); + *alloc = Allocation::new(storage); Filesystem::format(storage).unwrap(); Ok(()) }) @@ -566,7 +562,7 @@ fn test_mount_or_else_clobber_alloc() { fn shrinking() { let backend = &mut Ram::default(); let storage = &mut RamStorage::new(backend); - let alloc = &mut Allocation::new(); + let alloc = &mut Allocation::new(storage); Filesystem::format(storage).unwrap(); let fs = Filesystem::mount(alloc, storage).unwrap(); @@ -582,15 +578,18 @@ fn shrinking() { let larger_backend = &mut LargerRam::default(); larger_backend.buf[..backend.buf.len()].copy_from_slice(&backend.buf); let larger_storage = &mut LargerRamStorage::new(larger_backend); - let larger_alloc = &mut Allocation::new(); + let larger_alloc = &mut Allocation::new(larger_storage); assert!(matches!( Filesystem::mount(larger_alloc, larger_storage), Err(Error::INVALID) )); - let larger_alloc = &mut Allocation::with_config(crate::fs::Config { - mount_flags: MountFlags::DISABLE_BLOCK_COUNT_CHECK, - }); + let larger_alloc = &mut Allocation::with_config( + larger_storage, + crate::fs::Config { + mount_flags: MountFlags::DISABLE_BLOCK_COUNT_CHECK, + }, + ); let fs = Filesystem::mount(larger_alloc, larger_storage).unwrap(); assert_eq!(fs.read::<10>(path!("some-file")).unwrap(), &[42; 10]); @@ -599,14 +598,14 @@ fn shrinking() { &[42; 1024] ); - fs.grow(LargerRamStorage::BLOCK_COUNT).unwrap(); + fs.grow(LARGER_RAM_STORAGE_BLOCK_COUNT).unwrap(); assert_eq!(fs.read::<10>(path!("some-file")).unwrap(), &[42; 10]); assert_eq!( fs.read::<1024>(path!("some-large-file")).unwrap(), &[42; 1024] ); - fs.shrink(RamStorage::BLOCK_COUNT).unwrap(); + fs.shrink(RAM_STORAGE_BLOCK_COUNT).unwrap(); assert_eq!(fs.read::<10>(path!("some-file")).unwrap(), &[42; 10]); assert_eq!( fs.read::<1024>(path!("some-large-file")).unwrap(), @@ -618,7 +617,7 @@ fn shrinking() { fn shrinking_full() { let larger_backend = &mut LargerRam::default(); let larger_storage = &mut LargerRamStorage::new(larger_backend); - let larger_alloc = &mut Allocation::new(); + let larger_alloc = &mut Allocation::new(larger_storage); Filesystem::format(larger_storage).unwrap(); let fs = Filesystem::mount(larger_alloc, larger_storage).unwrap(); @@ -632,8 +631,11 @@ fn shrinking_full() { } } + let backend = &mut Ram::default(); + let storage = RamStorage::new(backend); + assert!(matches!( - fs.shrink(RamStorage::BLOCK_COUNT), + fs.shrink(storage.block_count()), Err(Error::DIR_NOT_EMPTY) )) } diff --git a/tests/test_serde.rs b/tests/test_serde.rs index de05c7f5..cbe7ddad 100644 --- a/tests/test_serde.rs +++ b/tests/test_serde.rs @@ -19,7 +19,7 @@ fn main() { let mut storage = RamStorage::new(&mut ram); Filesystem::format(&mut storage).unwrap(); - let mut alloc = Filesystem::allocate(); + let mut alloc = Filesystem::allocate(&storage); let fs = Filesystem::mount(&mut alloc, &mut storage).unwrap(); let entity = Entity::default();