From 45d8faacd44b32abc5f23aae61d89a01fda3ce05 Mon Sep 17 00:00:00 2001 From: Nicolas Stalder Date: Mon, 13 Apr 2020 00:02:35 +0200 Subject: [PATCH] WIP: Remove UB by using closures --- Cargo.toml | 13 +- src/driver.rs | 2 + src/fs.rs | 179 ++++++-- src/fsc.rs | 1159 +++++++++++++++++++++++++++++++++++++++++++++++++ src/io.rs | 46 +- src/lib.rs | 2 + src/macros.rs | 101 ++++- src/path.rs | 40 +- 8 files changed, 1487 insertions(+), 55 deletions(-) create mode 100644 src/fsc.rs diff --git a/Cargo.toml b/Cargo.toml index a7e57e5a..1898f50e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "littlefs2" description = "Idiomatic Rust API for littlefs" -version = "0.1.0-alpha.0" +version = "0.1.0-alpha.1" authors = ["Nicolas Stalder ", "Brandon Edens "] edition = "2018" license = "Apache-2.0 OR MIT" @@ -10,13 +10,14 @@ categories = ["embedded", "filesystem", "no-std"] repository = "https://github.com/nickray/littlefs2" [dependencies] -# aligned = "0.3.2" +aligned = { version = "0.3.2", optional = true } bitflags = "1.0.4" # cortex-m-semihosting = "0.3.5" # cstr_core = "0.1.2" cty = "0.2.1" generic-array = "0.13.2" # heapless = "0.5.1" +heapless-bytes = { path = "../heapless-bytes", optional = true } # Listed as regular dependency behind feature flag, # since dev-dependencies cannot be optional, and we @@ -24,12 +25,12 @@ generic-array = "0.13.2" # and test the `no_std` situation mainly. # Run UI tests with `cargo test --features ui-tests`. trybuild = { version = "1.0", optional = true } +ufmt = "0.1.0" [dependencies.littlefs2-sys] -version = "0.1.2" +version = "0.1.3" # git = "https://github.com/nickray/littlefs2-sys" # branch = "main" -# path = "../littlefs2-sys" [dev-dependencies] desse = "0.2.1" @@ -42,6 +43,10 @@ serde = { version = "1.0", default-features = false, features = ["derive"] } # serde-json-core = { version = "0.1.0" } [features] +# default = ["closures"] +# use experimental closure-based API +closures = ["aligned", "heapless-bytes"] +dir-entry-path = [] # enable assertions in backend C code ll-assertions = ["littlefs2-sys/assertions"] # enable trace in backend C code diff --git a/src/driver.rs b/src/driver.rs index 00a98eba..4b527853 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -1,4 +1,5 @@ //! The `Storage`, `Read`, `Write` and `Seek` driver. +#![allow(non_camel_case_types)] use generic_array::ArrayLength; use littlefs2_sys as ll; @@ -56,6 +57,7 @@ pub trait Storage { /// Hence, we further restrict `LOOKAHEAD_SIZE` to be a multiple of 32. /// Our LOOKAHEADWORDS_SIZE is this multiple. type LOOKAHEADWORDS_SIZE: ArrayLength; + // type LOOKAHEAD_SIZE: ArrayLength; /// Maximum length of a filename plus one. Stored in superblock. /// Should default to 255+1, but associated type defaults don't exist currently. diff --git a/src/fs.rs b/src/fs.rs index 601ba795..dbe688b3 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -16,6 +16,7 @@ use crate::{ self, Error, Result, + SeekFrom, }, path::{ Filename, @@ -27,7 +28,7 @@ use crate::{ // use aligned::{A4, Aligned}; use bitflags::bitflags; -use littlefs2_sys as ll; +pub use littlefs2_sys as ll; use generic_array::{ GenericArray, @@ -86,6 +87,15 @@ where Storage: 'alloc, { + pub fn within( + &mut self, + f: impl FnOnce(&mut Filesystem<'_, Storage>, &mut Storage) -> io::Result, + ) + -> Result + { + f(&mut Filesystem { alloc: self.alloc }, self.storage) + } + pub fn mount( alloc: &'alloc mut FilesystemAllocation, storage: &'storage mut Storage, @@ -173,6 +183,95 @@ where Error::result_from(return_code).map(|_| info.into()) } + /// Returns a pseudo-iterator over the entries within a directory. + pub fn read_dir( + &mut self, + path: impl Into>, + ) -> + Result> + { + let mut read_dir = ReadDir { + state: unsafe { mem::MaybeUninit::zeroed().assume_init() }, + _storage: PhantomData, + }; + + let return_code = unsafe { + ll::lfs_dir_open( + &mut self.alloc.state, + &mut read_dir.state, + &path.into() as *const _ as *const cty::c_char, + ) + }; + + Error::result_from(return_code).map(|_| read_dir) + } + + + /// Read attribute. + pub fn attribute( + &mut self, + path: impl Into>, + id: u8, + ) -> + Result>> + { + let mut attribute = Attribute::new(id); + let attr_max = ::ATTRBYTES_MAX::to_u32(); + + let return_code = unsafe { ll::lfs_getattr( + &mut self.alloc.state, + &path.into() as *const _ as *const cty::c_char, + id, + &mut attribute.data as *mut _ as *mut cty::c_void, + attr_max, + ) }; + + if return_code >= 0 { + attribute.size = cmp::min(attr_max, return_code as u32) as usize; + return Ok(Some(attribute)); + } + if return_code == ll::lfs_error_LFS_ERR_NOATTR { + return Ok(None) + } + + Error::result_from(return_code)?; + // TODO: get rid of this + unreachable!(); + } + + /// Remove attribute. + pub fn remove_attribute( + &mut self, + path: impl Into>, + id: u8, + ) -> Result<()> { + let return_code = unsafe { ll::lfs_removeattr( + &mut self.alloc.state, + &path.into() as *const _ as *const cty::c_char, + id, + ) }; + Error::result_from(return_code) + } + + /// Set attribute. + pub fn set_attribute( + &mut self, + path: impl Into>, + attribute: &Attribute + ) -> + Result<()> + { + let return_code = unsafe { ll::lfs_setattr( + &mut self.alloc.state, + &path.into() as *const _ as *const cty::c_char, + attribute.id, + &attribute.data as *const _ as *const cty::c_void, + attribute.size as u32, + ) }; + + Error::result_from(return_code) + } + } @@ -186,9 +285,9 @@ where 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::to_u32(); + let cache_size: u32 = ::CACHE_SIZE::U32; let lookahead_size: u32 = - 32 * ::LOOKAHEADWORDS_SIZE::to_u32(); + 32 * ::LOOKAHEADWORDS_SIZE::U32; let block_cycles: i32 = Storage::BLOCK_CYCLES as _; let block_count: u32 = Storage::BLOCK_COUNT as _; @@ -1012,6 +1111,7 @@ impl OpenOptions { S: driver::Storage, { // fs.alloc.config.context = storage as *mut _ as *mut cty::c_void; + // fs_with.alloc.config.context = fs_with.storage as *mut _ as *mut cty::c_void; alloc.config.buffer = alloc.cache.as_mut_slice() as *mut _ as *mut cty::c_void; let return_code = unsafe { ll::lfs_file_opencfg( @@ -1028,36 +1128,6 @@ impl OpenOptions { } } -/** Enumeration of possible methods to seek within an I/O object. - -Use the [`Seek`](../io/trait.Seek.html) trait. -*/ -#[derive(Clone,Copy,Debug,Eq,PartialEq)] -pub enum SeekFrom { - Start(u32), - End(i32), - Current(i32), -} - -impl SeekFrom { - pub(crate) fn off(self) -> i32 { - match self { - SeekFrom::Start(u) => u as i32, - SeekFrom::End(i) => i, - SeekFrom::Current(i) => i, - } - } - - pub(crate) fn whence(self) -> i32 { - match self { - SeekFrom::Start(_) => 0, - SeekFrom::End(_) => 2, - SeekFrom::Current(_) => 1, - } - } -} - - // /// The state of a `Dir`. Pre-allocate with `File::allocate()`. // pub struct DirAllocation // { @@ -1117,6 +1187,13 @@ where fs_with: &'fs mut FilesystemWith<'fsalloc, 'storage, S>, } +impl Drop for FileWith<'_, '_, '_, '_, S> { + fn drop(&mut self) { + self.nonconsuming_close_for_drop().expect("could not close FileWith"); + } +} + + impl<'falloc, 'fs, 'fsalloc, 'storage, S> FileWith<'falloc, 'fs, 'fsalloc, 'storage, S> where S: driver::Storage, @@ -1165,9 +1242,20 @@ where Error::result_from(return_code) } + pub fn nonconsuming_close_for_drop(&mut self) -> + Result<()> + { + // fs.alloc.config.context = storage as *mut _ as *mut cty::c_void; + let return_code = unsafe { ll::lfs_file_close( + &mut self.fs_with.alloc.state, + &mut self.alloc.state, + ) }; + Error::result_from(return_code) + } + /// Synchronize file contents to storage. pub fn sync(&mut self) -> Result<()> { - // assert!(self.fs_with.alloc.config.context == self.fs_with.storage as *mut _ as *mut cty::c_void); + assert!(self.fs_with.alloc.config.context == self.fs_with.storage as *mut _ as *mut cty::c_void); // fs.alloc.config.context = storage as *mut _ as *mut cty::c_void; let return_code = unsafe { ll::lfs_file_sync( &mut self.fs_with.alloc.state, @@ -1235,17 +1323,20 @@ where let cache_size: u32 = ::CACHE_SIZE::to_u32(); debug_assert!(cache_size > 0); - let config = ll::lfs_file_config { - buffer: core::ptr::null_mut(), - attrs: core::ptr::null_mut(), - attr_count: 0, - }; + // let config = ll::lfs_file_config { + // buffer: core::ptr::null_mut(), + // attrs: core::ptr::null_mut(), + // attr_count: 0, + // }; - FileAllocation { - cache: Default::default(), - state: unsafe { mem::MaybeUninit::zeroed().assume_init() }, - config, - } + // FileAllocation { + // cache: Default::default(), + // state: unsafe { mem::MaybeUninit::zeroed().assume_init() }, + // config, + // } + + // does not help with our variation on https://github.com/ARMmbed/littlefs/issues/145 + unsafe { mem::MaybeUninit::zeroed().assume_init() } } pub fn open<'fsalloc: 'falloc>( diff --git a/src/fsc.rs b/src/fsc.rs new file mode 100644 index 00000000..21b0ccde --- /dev/null +++ b/src/fsc.rs @@ -0,0 +1,1159 @@ +//! Experimental Filesystem version using closures. + +use core::{cmp, mem, slice}; + +use bitflags::bitflags; +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 crate::{ + io::{ + self, + Error, + Result, + SeekFrom, + }, + path::{ + Filename, + Path, + }, + driver, +}; + +struct Cache { + read: Bytes, + write: Bytes, + // lookahead: aligned::Aligned>, + lookahead: generic_array::GenericArray, +} + +impl Cache { + pub fn new() -> Self { + Self { + read: Default::default(), + write: Default::default(), + // lookahead: aligned::Aligned(Default::default()), + lookahead: Default::default(), + } + } +} + +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 Allocation { + + pub fn new() -> 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 = + 32 * ::LOOKAHEADWORDS_SIZE::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); + debug_assert!(block_count > 0); + + debug_assert!(read_size > 0); + debug_assert!(write_size > 0); + // https://github.com/ARMmbed/littlefs/issues/264 + // Technically, 104 is enough. + debug_assert!(block_size >= 128); + debug_assert!(cache_size > 0); + debug_assert!(lookahead_size > 0); + + // cache must be multiple of read + debug_assert!(read_size <= cache_size); + debug_assert!(cache_size % read_size == 0); + + // cache must be multiple of write + debug_assert!(write_size <= cache_size); + debug_assert!(cache_size % write_size == 0); + + // block must be multiple of cache + debug_assert!(cache_size <= block_size); + debug_assert!(block_size % cache_size == 0); + + let cache = Cache::new(); + + let filename_max_plus_one: u32 = + ::FILENAME_MAX_PLUS_ONE::to_u32(); + debug_assert!(filename_max_plus_one > 1); + debug_assert!(filename_max_plus_one <= 1_022+1); + // limitation of ll-bindings + debug_assert!(filename_max_plus_one == 255+1); + let path_max_plus_one: u32 = ::PATH_MAX_PLUS_ONE::to_u32(); + // TODO: any upper limit? + debug_assert!(path_max_plus_one >= filename_max_plus_one); + let file_max = Storage::FILEBYTES_MAX as u32; + assert!(file_max > 0); + assert!(file_max <= 2_147_483_647); + // limitation of ll-bindings + assert!(file_max == 2_147_483_647); + let attr_max: u32 = ::ATTRBYTES_MAX::to_u32(); + assert!(attr_max > 0); + assert!(attr_max <= 1_022); + // limitation of ll-bindings + assert!(attr_max == 1_022); + + let config = ll::lfs_config { + context: core::ptr::null_mut(), + read: Some(>::lfs_config_read), + prog: Some(>::lfs_config_prog), + erase: Some(>::lfs_config_erase), + sync: Some(>::lfs_config_sync), + // read: None, + // prog: None, + // erase: None, + // sync: None, + read_size, + prog_size: write_size, + block_size, + block_count, + block_cycles, + cache_size, + lookahead_size, + + read_buffer: core::ptr::null_mut(), + prog_buffer: core::ptr::null_mut(), + lookahead_buffer: core::ptr::null_mut(), + + name_max: filename_max_plus_one.wrapping_sub(1), + file_max, + attr_max: attr_max, + }; + + Self { + cache, + state: unsafe { mem::MaybeUninit::zeroed().assume_init() }, + config, + } + } + +} + +// pub struct Filesystem<'alloc, 'storage, Storage: driver::Storage> { +// pub(crate) alloc: &'alloc mut Allocation, +// pub(crate) storage: &'storage mut Storage, +// } + +// one lifetime is simpler than two... hopefully should be enough +// also consider "erasing" the lifetime completely +pub struct Filesystem<'a, Storage: driver::Storage> { + alloc: &'a mut Allocation, + storage: &'a mut Storage, +} + +// pub struct Filesystem { +// pub(crate) alloc: &'static mut Allocation, +// pub(crate) storage: &'static mut Storage, +// } + +/// Regular file vs directory +#[derive(Clone,Copy,Debug,Eq,Hash,PartialEq)] +pub enum FileType { + File, + Dir, +} + +impl FileType { + #[allow(clippy::all)] // following `std::fs` + pub fn is_dir(&self) -> bool { + *self == FileType::Dir + } + + #[allow(clippy::all)] // following `std::fs` + pub fn is_file(&self) -> bool { + *self == FileType::File + } +} + +/// File type (regular vs directory) and size of a file. +#[derive(Clone,Debug,Eq,PartialEq)] +pub struct Metadata { + file_type: FileType, + size: usize, +} + +impl Metadata +{ + pub fn file_type(&self) -> FileType { + self.file_type + } + + pub fn is_dir(&self) -> bool { + self.file_type().is_dir() + } + + pub fn is_file(&self) -> bool { + self.file_type().is_file() + } + + pub fn len(&self) -> usize { + self.size + } + + pub fn is_empty(&self) -> bool { + self.size == 0 + } +} + +impl From for Metadata +{ + fn from(info: ll::lfs_info) -> Self { + let file_type = match info.type_ as u32 { + ll::lfs_type_LFS_TYPE_DIR => FileType::Dir, + ll::lfs_type_LFS_TYPE_REG => FileType::File, + _ => { unreachable!(); } + }; + + Metadata { + file_type, + size: info.size as usize, + } + } +} + +impl Filesystem<'_, Storage> { + + pub fn allocate() -> Allocation { + Allocation::new() + } + + pub fn format(storage: &mut Storage) -> Result<()> { + + let alloc = &mut Allocation::new(); + let fs = Filesystem::new(alloc, storage); + let return_code = unsafe { ll::lfs_format(&mut fs.alloc.state, &fs.alloc.config) }; + Error::result_from(return_code) + } + + // TODO: check if this is equivalent to `is_formatted`. + pub fn is_mountable(storage: &mut Storage) -> bool { + let alloc = &mut Allocation::new(); + match Filesystem::mount(alloc, storage) { + Ok(_) => true, + _ => false, + } + } + + // Can BorrowMut be implemented "unsafely" instead? + // This is intended to be a second option, besides `into_inner`, to + // get access to the Flash peripheral in Storage. + pub unsafe fn borrow_storage_mut(&mut self) -> &mut Storage { + &mut self.storage + } + + /// This API avoids the need for using `Allocation`. + pub fn mount_and_then( + storage: &mut Storage, + f: impl FnOnce(&mut Filesystem<'_, Storage>) -> Result, + ) -> Result { + + let mut alloc = Allocation::new(); + let mut fs = Filesystem::mount(&mut alloc, storage)?; + f(&mut fs) + } + + /// Total number of blocks in the filesystem + pub fn total_blocks(&self) -> usize { + Storage::BLOCK_COUNT + } + + /// Total number of bytes in the filesystem + pub fn total_space(&self) -> usize { + Storage::BLOCK_COUNT * Storage::BLOCK_SIZE + } + + /// Available number of unused blocks in the filesystem + /// + /// Upstream littlefs documentation notes (on its "current size" function): + /// "Result is best effort. If files share COW structures, the returned size may be larger + /// than the filesystem actually is." + /// + /// So it would seem that there are *at least* the number of blocks returned + /// by this method available, at any given time. + pub fn available_blocks(&mut self) -> Result { + let return_code = unsafe { ll::lfs_fs_size( &mut self.alloc.state) }; + Error::usize_result_from(return_code).map(|blocks| self.total_blocks() - blocks) + } + + /// Available number of unused bytes in the filesystem + /// + /// This is a lower bound, more may be available. First, more blocks may be available as + /// explained in [`available_blocks`](struct.Filesystem.html#method.available_blocks). + /// Second, files may be inlined. + pub fn available_space(&mut self) -> Result { + self.available_blocks().map(|blocks| blocks * Storage::BLOCK_SIZE) + } + + /// Remove a file or directory. + pub fn remove(&mut self, path: impl Into>) -> Result<()> { + let return_code = unsafe { ll::lfs_remove( + &mut self.alloc.state, + &path.into() as *const _ as *const cty::c_char, + ) }; + Error::result_from(return_code) + } + + /// Rename or move a file or directory. + pub fn rename( + &mut self, + from: impl Into>, + to: impl Into>, + ) -> Result<()> { + let return_code = unsafe { ll::lfs_rename( + &mut self.alloc.state, + &from.into() as *const _ as *const cty::c_char, + &to.into() as *const _ as *const cty::c_char, + ) }; + Error::result_from(return_code) + } + + /// Given a path, query the filesystem to get information about a file or directory. + /// + /// To read user attributes, use + /// [`Filesystem::attribute`](struct.Filesystem.html#method.attribute) + pub fn metadata(&mut self, path: impl Into>) -> Result { + + // do *not* not call assume_init here and pass into the unsafe block. + // strange things happen ;) + + // TODO: Check we don't have UB here *too*. + // I think it's fine, as we immediately copy out the data + // to our own structure. + let mut info: ll::lfs_info = unsafe { mem::MaybeUninit::zeroed().assume_init() }; + let return_code = unsafe { + ll::lfs_stat( + &mut self.alloc.state, + &path.into() as *const _ as *const cty::c_char, + &mut info, + ) + }; + + Error::result_from(return_code).map(|_| info.into()) + } + + /// Read attribute. + pub fn attribute( + &mut self, + path: impl Into>, + id: u8, + ) -> + Result>> + { + let mut attribute = Attribute::new(id); + let attr_max = ::ATTRBYTES_MAX::to_u32(); + + let return_code = unsafe { ll::lfs_getattr( + &mut self.alloc.state, + &path.into() as *const _ as *const cty::c_char, + id, + &mut attribute.data as *mut _ as *mut cty::c_void, + attr_max, + ) }; + + if return_code >= 0 { + attribute.size = cmp::min(attr_max, return_code as u32) as usize; + return Ok(Some(attribute)); + } + if return_code == ll::lfs_error_LFS_ERR_NOATTR { + return Ok(None) + } + + Error::result_from(return_code)?; + // TODO: get rid of this + unreachable!(); + } + + /// Remove attribute. + pub fn remove_attribute( + &mut self, + path: impl Into>, + id: u8, + ) -> Result<()> { + let return_code = unsafe { ll::lfs_removeattr( + &mut self.alloc.state, + &path.into() as *const _ as *const cty::c_char, + id, + ) }; + Error::result_from(return_code) + } + + /// Set attribute. + pub fn set_attribute( + &mut self, + path: impl Into>, + attribute: &Attribute + ) -> + Result<()> + { + let return_code = unsafe { ll::lfs_setattr( + &mut self.alloc.state, + &path.into() as *const _ as *const cty::c_char, + attribute.id, + &attribute.data as *const _ as *const cty::c_void, + attribute.size as u32, + ) }; + + Error::result_from(return_code) + } + + + /// C callback interface used by LittleFS to read data with the lower level system below the + /// filesystem. + extern "C" fn lfs_config_read( + c: *const ll::lfs_config, + block: ll::lfs_block_t, + off: ll::lfs_off_t, + buffer: *mut cty::c_void, + size: ll::lfs_size_t, + ) -> cty::c_int { + // println!("in lfs_config_read for {} bytes", size); + let storage = unsafe { &mut *((*c).context as *mut Storage) }; + debug_assert!(!c.is_null()); + let block_size = unsafe { c.read().block_size }; + let off = (block * block_size + off) as usize; + let buf: &mut [u8] = unsafe { slice::from_raw_parts_mut(buffer as *mut u8, size as usize) }; + + // TODO + storage.read(off, buf).unwrap(); + 0 + } + + /// C callback interface used by LittleFS to program data with the lower level system below the + /// filesystem. + extern "C" fn lfs_config_prog( + c: *const ll::lfs_config, + block: ll::lfs_block_t, + off: ll::lfs_off_t, + buffer: *const cty::c_void, + size: ll::lfs_size_t, + ) -> cty::c_int { + // println!("in lfs_config_prog"); + 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 off = (block * block_size + off) as usize; + let buf: &[u8] = unsafe { slice::from_raw_parts(buffer as *const u8, size as usize) }; + + // TODO + storage.write(off, buf).unwrap(); + 0 + } + + /// C callback interface used by LittleFS to erase data with the lower level system below the + /// filesystem. + extern "C" fn lfs_config_erase( + c: *const ll::lfs_config, + block: ll::lfs_block_t, + ) -> cty::c_int { + // println!("in lfs_config_erase"); + let storage = unsafe { &mut *((*c).context as *mut Storage) }; + let off = block as usize * Storage::BLOCK_SIZE as usize; + + // TODO + storage.erase(off, Storage::BLOCK_SIZE as usize).unwrap(); + 0 + } + + /// C callback interface used by LittleFS to sync data with the lower level interface below the + /// filesystem. Note that this function currently does nothing. + extern "C" fn lfs_config_sync(_c: *const ll::lfs_config) -> i32 { + // println!("in lfs_config_sync"); + // Do nothing; we presume that data is synchronized. + 0 + } + +} + +#[derive(Clone,Debug,Eq,PartialEq)] +/// Custom user attribute that can be set on files and directories. +/// +/// Consists of an numerical identifier between 0 and 255, and arbitrary +/// binary data up to size `ATTRBYTES_MAX`. +/// +/// Use [`Filesystem::attribute`](struct.Filesystem.html#method.attribute), +/// [`Filesystem::set_attribute`](struct.Filesystem.html#method.set_attribute), and +/// [`Filesystem::clear_attribute`](struct.Filesystem.html#method.clear_attribute). +pub struct Attribute { + id: u8, + data: Bytes, + size: usize, +} + +impl Attribute { + pub fn new(id: u8) -> Self { + Attribute { + id, + data: Default::default(), + size: 0, + } + } + + pub fn id(&self) -> u8 { + self.id + } + + pub fn data(&self) -> &[u8] { + let attr_max = ::ATTRBYTES_MAX::to_usize(); + let len = cmp::min(attr_max, self.size); + &self.data[..len] + } + + pub fn set_data(&mut self, data: &[u8]) { + let attr_max = ::ATTRBYTES_MAX::to_usize(); + let len = cmp::min(attr_max, data.len()); + self.data[..len].copy_from_slice(&data[..len]); + self.size = len; + for entry in self.data[len..].iter_mut() { + *entry = 0; + } + } +} + +bitflags! { + /// Definition of file open flags which can be mixed and matched as appropriate. These definitions + /// are reminiscent of the ones defined by POSIX. + struct FileOpenFlags: u32 { + /// Open file in read only mode. + const READ = 0x1; + /// Open file in write only mode. + const WRITE = 0x2; + /// Open file for reading and writing. + const READWRITE = Self::READ.bits | Self::WRITE.bits; + /// Create the file if it does not exist. + const CREATE = 0x0100; + /// Fail if creating a file that already exists. + /// TODO: Good name for this + const EXCL = 0x0200; + /// Truncate the file if it already exists. + const TRUNCATE = 0x0400; + /// Open the file in append only mode. + const APPEND = 0x0800; + } +} + +/// 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 +/// are permitted on the open file. The File::open and File::create methods are aliases +/// for commonly used options using this builder. +/// +/// Consider `File::with_options()` to avoid having to `use` OpenOptions. +#[derive(Clone,Debug,Eq,PartialEq)] +pub struct OpenOptions (FileOpenFlags); + +impl Default for OpenOptions { + fn default() -> Self { + Self::new() + } +} + +impl OpenOptions { + pub fn new() -> Self { + OpenOptions(FileOpenFlags::empty()) + } + + pub fn read(&mut self, read: bool) -> &mut Self { + if read { + self.0.insert(FileOpenFlags::READ) + } else { + self.0.remove(FileOpenFlags::READ) + }; self + } + + pub fn write(&mut self, write: bool) -> &mut Self { + if write { + self.0.insert(FileOpenFlags::WRITE) + } else { + self.0.remove(FileOpenFlags::WRITE) + }; self + } + + pub fn append(&mut self, append: bool) -> &mut Self { + if append { + self.0.insert(FileOpenFlags::APPEND) + } else { + self.0.remove(FileOpenFlags::APPEND) + }; self + } + + pub fn create(&mut self, create: bool) -> &mut Self { + if create { + self.0.insert(FileOpenFlags::CREATE) + } else { + self.0.remove(FileOpenFlags::CREATE) + }; self + } + + pub fn create_new(&mut self, create_new: bool) -> &mut Self { + if create_new { + self.0.insert(FileOpenFlags::EXCL); + self.0.insert(FileOpenFlags::CREATE); + } else { + self.0.remove(FileOpenFlags::EXCL); + self.0.remove(FileOpenFlags::CREATE); + }; self + } + + pub fn truncate(&mut self, truncate: bool) -> &mut Self { + if truncate { + self.0.insert(FileOpenFlags::TRUNCATE) + } else { + self.0.remove(FileOpenFlags::TRUNCATE) + }; self + } + + /// Open the file with the options previously specified, keeping references. + /// + /// unsafe since UB can arise if files are not closed (see below). + /// + /// The alternative method `open_and_then` is suggested. + /// + /// Note that: + /// - files *must* be closed before going out of scope (they are stored in a linked list), + /// 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>( + &self, + fs: &'a mut Filesystem<'a, S>, + alloc: &'b mut FileAllocation, + path: impl Into>, + ) -> + Result> + { + alloc.config.buffer = &mut alloc.cache as *mut _ as *mut cty::c_void; + + let return_code = ll::lfs_file_opencfg( + &mut fs.alloc.state, + &mut alloc.state, + &path.into() as *const _ as *const cty::c_char, + self.0.bits() as i32, + &alloc.config, + ); + + let file_with = File { alloc, fs }; + + Error::result_from(return_code).map(|_| file_with) + } + + /// (Hopefully) safe abstraction around `open`. + pub fn open_and_then<'a, R, S: driver::Storage>( + &self, + fs: &'a mut Filesystem<'a, S>, + path: impl Into>, + f: impl FnOnce(&mut File<'_, '_, S>) -> Result, + ) + -> Result + { + let mut alloc = FileAllocation::new(); + let mut file = unsafe { self.open(fs, &mut alloc, path)? }; + let res = f(&mut file); + unsafe { file.close()? }; + res + } + + pub fn with_options() -> OpenOptions { + OpenOptions::new() + } + +} + + +/// The state of a `File`. Pre-allocate with `File::allocate`. +pub struct FileAllocation +{ + cache: Bytes, + state: ll::lfs_file_t, + config: ll::lfs_file_config, +} + +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() } + } +} + +pub struct File<'a: 'b, 'b, S: driver::Storage> +{ + alloc: &'b mut FileAllocation, + fs: &'a mut Filesystem<'a, S>, +} + +impl<'a, 'b, Storage: driver::Storage> File<'a, 'b, Storage> +{ + pub unsafe fn open( + fs: &'a mut Filesystem<'a, Storage>, + alloc: &'b mut FileAllocation, + path: impl Into>, + ) -> + Result + { + OpenOptions::new() + .read(true) + .open(fs, alloc, path) + } + + pub fn open_and_then( + fs: &'a mut Filesystem<'a, Storage>, + path: impl Into>, + f: impl FnOnce(&mut File<'_, '_, Storage>) -> Result, + ) -> + Result + { + OpenOptions::new() + .read(true) + .open_and_then(fs, path, f) + } + + pub unsafe fn create( + fs: &'a mut Filesystem<'a, Storage>, + alloc: &'b mut FileAllocation, + path: impl Into>, + ) -> + Result + { + OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(fs, alloc, path) + } + + pub fn create_and_then( + fs: &'a mut Filesystem<'a, Storage>, + path: impl Into>, + f: impl FnOnce(&mut File<'_, '_, Storage>) -> Result, + ) -> + Result + { + OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open_and_then(fs, path, f) + } + + // Safety-hatch to experiment with missing parts of API + pub unsafe fn borrow_filesystem<'c>(&'c mut self) -> &'c mut Filesystem<'a, Storage> { + &mut self.fs + } + + /// Sync the file and drop it from the internal linked list. + /// Not doing this is UB, which is why we have all the closure-based APIs. + /// + /// TODO: check if this can be closed >1 times, if so make it safe + pub unsafe fn close(self) -> Result<()> + { + let return_code = ll::lfs_file_close( + &mut self.fs.alloc.state, + &mut self.alloc.state, + ); + Error::result_from(return_code) + } + + /// Synchronize file contents to storage. + pub fn sync(&mut self) -> Result<()> { + let return_code = unsafe { ll::lfs_file_sync( + &mut self.fs.alloc.state, + &mut self.alloc.state, + ) }; + Error::result_from(return_code) + } + + /// Size of the file in bytes. + pub fn len(&mut self) -> Result { + let return_code = unsafe { ll::lfs_file_size( + &mut self.fs.alloc.state, + &mut self.alloc.state + ) }; + Error::usize_result_from(return_code) + } + + /// Truncates or extends the underlying file, updating the size of this file to become size. + /// + /// If the size is less than the current file's size, then the file will be shrunk. If it is + /// greater than the current file's size, then the file will be extended to size and have all + /// of the intermediate data filled in with 0s. + pub fn set_len(&mut self, size: usize) -> Result<()> { + let return_code = unsafe { ll::lfs_file_truncate( + &mut self.fs.alloc.state, + &mut self.alloc.state, + size as u32, + ) }; + Error::result_from(return_code) + } + +} + +impl io::ReadWith for File<'_, '_, S> +{ + fn read(&mut self, buf: &mut [u8]) -> Result { + let return_code = unsafe { ll::lfs_file_read( + &mut self.fs.alloc.state, + &mut self.alloc.state, + buf.as_mut_ptr() as *mut cty::c_void, + buf.len() as u32, + ) }; + Error::usize_result_from(return_code) + } +} + +impl io::SeekWith for File<'_, '_, S> +{ + fn seek(&mut self, pos: SeekFrom) -> Result { + let return_code = unsafe { ll::lfs_file_seek( + &mut self.fs.alloc.state, + &mut self.alloc.state, + pos.off(), + pos.whence(), + ) }; + Error::usize_result_from(return_code) + } +} + +impl io::WriteWith for File<'_, '_, S> +{ + fn write(&mut self, buf: &[u8]) -> Result { + let return_code = unsafe { ll::lfs_file_write( + &mut self.fs.alloc.state, + &mut self.alloc.state, + buf.as_ptr() as *const cty::c_void, + buf.len() as u32, + ) }; + Error::usize_result_from(return_code) + } + + fn flush(&mut self) -> Result<()> { Ok(()) } +} + +#[derive(Clone,Debug,PartialEq)] +pub struct DirEntry { + file_name: Filename, + metadata: Metadata, + #[cfg(feature = "dir-entry-path")] + path: Path, +} + +impl DirEntry { + // // Returns the full path to the file that this entry represents. + // pub fn path(&self) -> Path {} + + // Returns the metadata for the file that this entry points at. + pub fn metadata(&self) -> Metadata { + self.metadata.clone() + } + + // Returns the file type for the file that this entry points at. + pub fn file_type(&self) -> FileType { + self.metadata.file_type + } + + // Returns the bare file name of this directory entry without any other leading path component. + pub fn file_name(&self) -> Filename { + self.file_name.clone() + } + + /// Returns the full path to the file that this entry represents. + /// + /// The full path is created by joining the original path to read_dir with the filename of this entry. + #[cfg(feature = "dir-entry-path")] + pub fn path(&self) -> Path { + self.path.clone() + } + +} + +// /// The state of a `File`. Pre-allocate with `File::allocate`. +// pub struct FileAllocation +// { +// cache: Bytes, +// state: ll::lfs_file_t, +// config: ll::lfs_file_config, +// } + +// impl FileAllocation { +// pub fn new() -> Self { +// todo!(); +// } +// } + +// pub struct File<'a: 'b, 'b, S: driver::Storage> +// { +// alloc: &'b mut FileAllocation, +// fs: &'a mut Filesystem<'a, S>, +// } + +// struct ReadDirAllocation { +pub struct ReadDirAllocation { + state: ll::lfs_dir_t, +} + +impl ReadDirAllocation { + pub fn new() -> Self { + unsafe { mem::MaybeUninit::zeroed().assume_init() } + } +} + +pub struct ReadDir<'a, 'b, S: driver::Storage> +{ + alloc: &'b mut ReadDirAllocation, + fs: &'a mut Filesystem<'a, S>, + #[cfg(feature = "dir-entry-path")] + path: Path, +} + +impl<'a, 'b, S: driver::Storage> Iterator for ReadDir<'a, 'b, S> +{ + type Item = Result>; + + // remove this allowance again, once path overflow is properly handled + #[allow(unreachable_code)] + fn next(&mut self) -> Option { + let mut info: ll::lfs_info = unsafe { + mem::MaybeUninit::zeroed().assume_init() + }; + + let return_code = unsafe { + ll::lfs_dir_read( + &mut self.fs.alloc.state, + &mut self.alloc.state, + &mut info, + ) + }; + + if return_code > 0 { + #[cfg(feature = "dir-entry-path")] + panic!("not decided how to handle overflowing path arrays yet..."); + + // well here we have it: nasty C strings! + // actually... nasty C arrays with static lengths! o.O + let file_name = Filename::new(& unsafe { mem::transmute::<[cty::c_char; 256], [u8; 256]>(info.name) } ); + // let buf: &mut [u8] = unsafe { slice::from_raw_parts_mut(buffer as *mut u8, size as usize) }; + + let metadata = info.into(); + + let dir_entry = DirEntry { + file_name, + metadata, + #[cfg(feature = "dir-entry-path")] + path: self.path.clone()// / file_name + }; + return Some(Ok(dir_entry)); + } + + if return_code == 0 { + return None + } + + Some(Err(Error::result_from(return_code).unwrap_err())) + } +} + +impl<'a, 'b, S: driver::Storage> ReadDir<'a, 'b, S> { + + // Safety-hatch to experiment with missing parts of API + pub unsafe fn borrow_filesystem<'c>(&'c mut self) -> &'c mut Filesystem<'a, S> { + &mut self.fs + } +} + +impl ReadDir<'_, '_, S> { + // Again, not sure if this can be called twice + pub unsafe fn close(self) -> Result<()> + { + let return_code = ll::lfs_dir_close( + &mut self.fs.alloc.state, + &mut self.alloc.state, + ); + Error::result_from(return_code) + } +} + + +impl<'a, Storage: driver::Storage> Filesystem<'a, Storage> { + + // pub fn open_and_then<'a, R, S: driver::Storage>( + // &self, + // fs: &'a mut Filesystem<'a, S>, + // path: impl Into>, + // f: impl FnOnce(&mut File<'_, '_, S>) -> Result, + // ) + pub fn read_dir_and_then( + &'a mut self, + path: impl Into>, + 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 res = f(&mut read_dir); + unsafe { read_dir.close()? }; + res + } + + /// 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>( + &'a mut self, + alloc: &'b mut ReadDirAllocation, + path: impl Into>, + ) -> + Result> + { + let path = path.into(); + + let return_code = ll::lfs_dir_open( + &mut self.alloc.state, + &mut alloc.state, + &path as *const _ as *const cty::c_char, + ); + + let read_dir = ReadDir { + alloc, + fs: self, + #[cfg(feature = "dir-entry-path")] + path, + }; + + Error::result_from(return_code).map(|_| read_dir) + } + +} + + +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 return_code = unsafe { ll::lfs_mount(&mut fs.alloc.state, &fs.alloc.config) }; + Error::result_from(return_code).map(move |_| { fs } ) + } + + // Not public, user should use `mount`, possibly after `format` + fn new(alloc: &'a mut Allocation, storage: &'a mut Storage) -> Self { + + alloc.config.context = storage as *mut _ as *mut cty::c_void; + + alloc.config.read_buffer = &mut alloc.cache.read as *mut _ as *mut cty::c_void; + alloc.config.prog_buffer = &mut alloc.cache.write as *mut _ as *mut cty::c_void; + alloc.config.lookahead_buffer = &mut alloc.cache.lookahead as *mut _ as *mut cty::c_void; + + Filesystem { alloc, storage } + } + + /// Deconstruct `Filesystem`, intention is to allow access to + /// the underlying Flash peripheral in driver::Storage etc. + /// + /// See also `borrow_storage_mut`. + pub fn into_inner(self) -> (&'a mut Allocation, &'a mut Storage) { + (self.alloc, self. storage) + } + + /// Creates a new, empty directory at the provided path. + pub fn create_dir(&mut self, path: impl Into>) -> Result<()> { + + let return_code = unsafe { ll::lfs_mkdir( + &mut self.alloc.state, + &path.into() as *const _ as *const cty::c_char, + ) }; + Error::result_from(return_code) + } + + /// Recursively create a directory and all of its parent components if they are missing. + pub fn create_dir_all(&mut self, path: impl Into>) -> Result<()> { + // Placeholder implementation! + // - Path should gain a few methods + // - Maybe should pull in `heapless-bytes` (and merge upstream into `heapless`) + // - All kinds of sanity checks and possible logic errors possible... + let path = path.into(); + + for i in 0..path.0.len() { + if path.0[i] == b'/' { + let dir = &path.0[..i]; + match self.create_dir(dir) { + Ok(_) => {} + Err(io::Error::EntryAlreadyExisted) => {} + error => { panic!("{:?}", &error); } + } + } + } + Ok(()) + } + + /// Read the entire contents of a file into a bytes vector. + pub fn read>( + &'a mut self, + path: impl Into>, + ) -> Result> + { + let mut contents = Bytes::default(); + File::open_and_then(self, path, |file| { + use io::ReadWith; + file.read_exact(&mut contents) + })?; + Ok(contents) + } + + /// Write a slice as the entire contents of a file. + /// + /// This function will create a file if it does not exist, + /// and will entirely replace its contents if it does. + pub fn write( + &'a mut self, + path: impl Into>, + // contents: AsRef<[u8]>, + contents: &[u8], + ) -> Result<()> + { + File::create_and_then(self, path, |file| { + use io::WriteWith; + // file.write_all(contents.as_ref()) + file.write_all(contents) + })?; + Ok(()) + } + +} + +impl core::ops::Deref for Filesystem<'_, Storage> { + type Target = Storage; + + fn deref(&self) -> &Self::Target { + self.storage + } +} + +impl core::ops::DerefMut for Filesystem<'_, Storage> { + + fn deref_mut(&mut self) -> &mut Self::Target { + self.storage + } +} diff --git a/src/io.rs b/src/io.rs index a655c547..8bcb8ba2 100644 --- a/src/io.rs +++ b/src/io.rs @@ -3,11 +3,11 @@ pub mod prelude; use littlefs2_sys as ll; +use ufmt::derive::uDebug; use crate::{ fs::{ Filesystem, - SeekFrom, }, driver::Storage, }; @@ -108,6 +108,48 @@ pub trait WriteWith { /// Write out all pending writes to storage. fn flush(&mut self) -> Result<()>; + fn write_all(&mut self, mut buf: &[u8]) -> Result<()> { + while !buf.is_empty() { + match self.write(buf) { + Ok(0) => { + // failed to write whole buffer + return Err(Error::Io) + } + Ok(n) => buf = &buf[n..], + Err(e) => return Err(e), + } + } + Ok(()) + } +} + +/** Enumeration of possible methods to seek within an I/O object. + +Use the [`Seek`](../io/trait.Seek.html) trait. +*/ +#[derive(Clone,Copy,Debug,Eq,PartialEq)] +pub enum SeekFrom { + Start(u32), + End(i32), + Current(i32), +} + +impl SeekFrom { + pub(crate) fn off(self) -> i32 { + match self { + SeekFrom::Start(u) => u as i32, + SeekFrom::End(i) => i, + SeekFrom::Current(i) => i, + } + } + + pub(crate) fn whence(self) -> i32 { + match self { + SeekFrom::Start(_) => 0, + SeekFrom::End(_) => 2, + SeekFrom::Current(_) => 1, + } + } } /** The `Seek` trait provides a cursor which can be moved within a file. @@ -135,7 +177,7 @@ pub trait SeekWith { pub type Result = core::result::Result; /// Definition of errors that might be returned by filesystem functionality. -#[derive(Clone,Copy,Debug,PartialEq)] +#[derive(Clone,Copy,Debug,PartialEq,uDebug)] pub enum Error { /// Input / output error occurred. Io, diff --git a/src/lib.rs b/src/lib.rs index 017448f4..3472f28e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,6 +114,8 @@ pub mod macros; pub mod driver; pub mod fs; +#[cfg(feature = "closures")] +pub mod fsc; pub mod io; pub mod path; diff --git a/src/macros.rs b/src/macros.rs index fb0e5bcc..2ee336c4 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -20,7 +20,7 @@ macro_rules! ram_storage { ( result=$Result:ident, ) => { - struct $Backend { + pub struct $Backend { buf: [u8; $block_size * $block_count], } @@ -32,7 +32,7 @@ macro_rules! ram_storage { ( } } - struct $Name<'backend> { + pub struct $Name<'backend> { backend: &'backend mut $Backend, } @@ -137,3 +137,100 @@ macro_rules! ram_storage { ( ); }; } + +#[macro_export] +macro_rules! const_ram_storage { ( + + name=$Name:ident, + trait=$StorageTrait:path, + erase_value=$erase_value:expr, + read_size=$read_size:expr, + write_size=$write_size:expr, + cache_size_ty=$cache_size:path, + block_size=$block_size:expr, + block_count=$block_count:expr, + lookaheadwords_size_ty=$lookaheadwords_size:path, + filename_max_plus_one_ty=$filename_max_plus_one:path, + path_max_plus_one_ty=$path_max_plus_one:path, + result=$Result:ident, + +) => { + pub struct $Name { + buf: [u8; $block_size * $block_count], + } + + impl $Name { + const ERASE_VALUE: u8 = $erase_value; + pub const fn new() -> Self { + // Self::default() + Self { buf: [$erase_value; $block_size * $block_count] } + } + } + + impl Default for $Name { + fn default() -> Self { + Self { + buf: [$erase_value; $block_size * $block_count], + } + } + } + + impl $StorageTrait 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 LOOKAHEADWORDS_SIZE = $lookaheadwords_size; + type FILENAME_MAX_PLUS_ONE = $filename_max_plus_one; + type PATH_MAX_PLUS_ONE = $path_max_plus_one; + type ATTRBYTES_MAX = consts::U1022; + + fn read(&self, offset: usize, buf: &mut [u8]) -> $Result { + 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.buf[offset..].iter().zip(buf.iter_mut()) { + *to = *from; + } + Ok(buf.len()) + } + + fn write(&mut self, offset: usize, data: &[u8]) -> $Result { + 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.buf[offset..].iter_mut()) { + *to = *from; + } + Ok(data.len()) + } + + fn erase(&mut self, offset: usize, len: usize) -> $Result { + 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() { + *byte = Self::ERASE_VALUE; + } + Ok(len) + } + } + }; + ($Name:ident, $bytes:expr) => { + const_ram_storage!( + name=$Name, + trait=LfsStorage, + erase_value=0xff, + read_size=16, + write_size=512, + cache_size_ty=consts::U512, + block_size=512, + block_count=$bytes/512, + lookaheadwords_size_ty=consts::U1, + filename_max_plus_one_ty=consts::U256, + path_max_plus_one_ty=consts::U256, + result=LfsResult, + ); + }; +} diff --git a/src/path.rs b/src/path.rs index e557b4e6..eeb26185 100644 --- a/src/path.rs +++ b/src/path.rs @@ -66,19 +66,23 @@ where // pub fn new + ?Sized>(f: &F) -> Self { pub fn new(f: &[u8]) -> Self { let mut padded_filename: GenericArray = Default::default(); - let name_max = ::FILENAME_MAX_PLUS_ONE::to_usize(); + let name_max = ::FILENAME_MAX_PLUS_ONE::USIZE; // let len = cmp::min(name_max - 1, f.as_ref().len()); // padded_filename[..len].copy_from_slice(&f.as_ref()[..len]); let len = cmp::min(name_max - 1, f.len()); padded_filename[..len].copy_from_slice(&f[..len]); Filename(padded_filename) } + + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } } /// A slice of a specification of the location of a [`File`](../fs/struct.File.html). /// /// This module is rather incomplete, compared to `std::path`. -pub struct Path (GenericArray) +pub struct Path (pub(crate) GenericArray) where S: driver::Storage, ::PATH_MAX_PLUS_ONE: ArrayLength, @@ -97,6 +101,16 @@ where } } +impl PartialEq for Path +where + S: driver::Storage, + ::PATH_MAX_PLUS_ONE: ArrayLength, +{ + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + // to make `Metadata` Debug impl fmt::Debug for Path where @@ -116,7 +130,7 @@ where /// Silently truncates to maximum configured path length pub fn new + ?Sized>(p: &P) -> Self { let mut padded_path: GenericArray = Default::default(); - let name_max = ::PATH_MAX_PLUS_ONE::to_usize(); + let name_max = ::PATH_MAX_PLUS_ONE::USIZE; let len = cmp::min(name_max - 1, p.as_ref().len()); padded_path[..len].copy_from_slice(&p.as_ref()[..len]); Path(padded_path) @@ -133,6 +147,26 @@ where pub fn has_root(&self) -> bool { self.0.len() > 0 && self.0[0] == b'/' } + + // what to do about possible "array-too-small" errors? + // what does littlefs actually do? + // one option would be: + // + // enum Path { + // NotTruncated(RawPath), + // Truncated(RawPath), + // } + // + // impl Deref for Path { ... } + // + // that is, never fail, but tag if truncation was necessary + // this way, no need to do error handling for the rare cases, + // but can still detect them + + // pub fn join>(&self, path: P) -> Path { + // } + // pub fn try_join>(&self, path: P) -> Result { + // } } impl From<&str> for Path