diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0752a2223..34ed47466 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,20 +30,20 @@ jobs: - name: Check run: | - cargo check --all-targets - cargo check --all-targets --all-features - cargo check --all-targets --no-default-features - cargo check --all-targets --no-default-features --features serde - cargo check --all-targets --no-default-features --features dir-entry-path + cargo check --workspace --all-targets + cargo check --workspace --all-targets --all-features + cargo check --workspace --all-targets --no-default-features + cargo check --workspace --all-targets --no-default-features --features serde + cargo check --workspace --all-targets --no-default-features --features dir-entry-path - name: Build - run: cargo build --release --verbose + run: cargo build --workspace --release --verbose - name: Run tests if: matrix.target == 'x86_64-unknown-linux-gnu' run: > - cargo test && - cargo test --release + cargo test --workspace && + cargo test --workspace --release - name: Build Documentation run: cargo doc --no-deps diff --git a/Cargo.toml b/Cargo.toml index 1b071e331..69644b050 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,37 +1,41 @@ +[workspace] +members = ["core"] + +[workspace.package] +edition = "2021" +license = "Apache-2.0 OR MIT" +repository = "https://github.com/trussed-dev/littlefs2" + [package] name = "littlefs2" description = "Idiomatic Rust API for littlefs" version = "0.4.0" authors = ["Nicolas Stalder ", "Brandon Edens ", "The Trussed developers"] -edition = "2021" -license = "Apache-2.0 OR MIT" readme = "README.md" categories = ["embedded", "filesystem", "no-std"] -repository = "https://github.com/trussed-dev/littlefs2" documentation = "https://docs.rs/littlefs2" +edition.workspace = true +license.workspace = true +repository.workspace = true + [dependencies] -bitflags = "1" delog = "0.1.0" generic-array = "0.14" heapless = "0.7" +littlefs2-core = { version = "0.1", path = "core" } littlefs2-sys = "0.2" -[dependencies.serde] -version = "1" -default-features = false -features = ["derive"] -optional = true - [dev-dependencies] ssmarshal = "1" -serde = { version = "1.0", default-features = false, features = ["derive"] } +serde = { version = "1.0", default-features = false } # trybuild = "1" [features] default = ["dir-entry-path", "serde"] # use experimental closure-based API -dir-entry-path = [] +dir-entry-path = ["littlefs2-core/dir-entry-path"] +serde = ["littlefs2-core/serde"] # enable assertions in backend C code ll-assertions = ["littlefs2-sys/assertions"] # enable trace in backend C code diff --git a/README.md b/README.md index 76ce9b159..c32b4d724 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,8 @@ We follow [`std::fs`][std-fs] as much as reasonable. The low-level bindings are provided by the [littlefs2-sys][littlefs2-sys] library. +The core types that are independent of a specific implementation version are provided by the `littlefs2-core` crate, see the [`core`](./core) directory. These types are re-exported from the `littlefs2` crate too. + Upstream release: [v2.2.1][upstream-release] [geky]: https://github.com/geky diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md new file mode 100644 index 000000000..af61f1f0b --- /dev/null +++ b/core/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## Unreleased + +Initial release with the core types from `littlefs2`. diff --git a/core/Cargo.toml b/core/Cargo.toml new file mode 100644 index 000000000..8d94d123f --- /dev/null +++ b/core/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "littlefs2-core" +version = "0.1.0" +authors = ["The Trussed developers"] +description = "Core types for the littlefs2 crate" + +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +bitflags = "2.6.0" +generic-array = "0.14" +heapless = "0.7" +serde = { version = "1", default-features = false, features = ["derive"], optional = true } + +[features] +dir-entry-path = [] +serde = ["dep:serde"] diff --git a/core/src/consts.rs b/core/src/consts.rs new file mode 100644 index 000000000..569bc1c91 --- /dev/null +++ b/core/src/consts.rs @@ -0,0 +1,5 @@ +pub const PATH_MAX: usize = 255; +pub const PATH_MAX_PLUS_ONE: usize = PATH_MAX + 1; +pub const ATTRBYTES_MAX: u32 = 1_022; +#[allow(non_camel_case_types)] +pub type ATTRBYTES_MAX_TYPE = generic_array::typenum::consts::U1022; diff --git a/core/src/fs.rs b/core/src/fs.rs new file mode 100644 index 000000000..c4caa76b3 --- /dev/null +++ b/core/src/fs.rs @@ -0,0 +1,180 @@ +use core::cmp; + +use bitflags::bitflags; + +use crate::path::{Path, PathBuf}; + +pub type Bytes = generic_array::GenericArray; + +bitflags! { + /// Definition of file open flags which can be mixed and matched as appropriate. These definitions + /// are reminiscent of the ones defined by POSIX. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct FileOpenFlags: i32 { + /// 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; + } +} + +/// Regular file vs directory +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum FileType { + File, + Dir, +} + +impl FileType { + pub fn is_dir(&self) -> bool { + *self == FileType::Dir + } + + pub fn is_file(&self) -> bool { + *self == FileType::File + } +} + +/// File type (regular vs directory) and size of a file. +#[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Metadata { + file_type: FileType, + size: usize, +} + +impl Metadata { + pub fn new(file_type: FileType, size: usize) -> Self { + Self { file_type, size } + } + + 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 + } +} + +#[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, + pub data: Bytes, + pub 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 = crate::consts::ATTRBYTES_MAX as _; + let len = cmp::min(attr_max, self.size); + &self.data[..len] + } + + pub fn set_data(&mut self, data: &[u8]) -> &mut Self { + let attr_max = crate::consts::ATTRBYTES_MAX as _; + 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; + } + self + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct DirEntry { + file_name: PathBuf, + metadata: Metadata, + #[cfg(feature = "dir-entry-path")] + path: PathBuf, +} + +impl DirEntry { + pub fn new(parent: &Path, file_name: PathBuf, metadata: Metadata) -> Self { + let _ = parent; + Self { + #[cfg(feature = "dir-entry-path")] + path: parent.join(&file_name), + file_name, + metadata, + } + } + + // 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) -> &Path { + &self.file_name + } + + /// 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 + } + + #[cfg(feature = "dir-entry-path")] + #[doc(hidden)] + // This is used in `crypto-service` to "namespace" paths + // by mutating a DirEntry in-place. + pub unsafe fn path_buf_mut(&mut self) -> &mut PathBuf { + &mut self.path + } +} diff --git a/src/io.rs b/core/src/io.rs similarity index 76% rename from src/io.rs rename to core/src/io.rs index 9b01c99db..bea652af0 100644 --- a/src/io.rs +++ b/core/src/io.rs @@ -1,14 +1,10 @@ //! Traits and types for core I/O functionality. -pub mod prelude; - use core::{ ffi::c_int, fmt::{self, Debug, Formatter}, }; -use littlefs2_sys as ll; - /// The `Read` trait allows for reading bytes from a file. pub trait Read { /// Read at most buf.len() bytes. @@ -70,7 +66,7 @@ pub enum SeekFrom { } impl SeekFrom { - pub(crate) fn off(self) -> i32 { + pub fn off(self) -> i32 { match self { SeekFrom::Start(u) => u as i32, SeekFrom::End(i) => i, @@ -78,7 +74,7 @@ impl SeekFrom { } } - pub(crate) fn whence(self) -> i32 { + pub fn whence(self) -> i32 { match self { SeekFrom::Start(_) => 0, SeekFrom::End(_) => 2, @@ -88,7 +84,7 @@ impl SeekFrom { } /// Enumeration of possible methods to seek within an file that was just opened -/// Used in the [`read_chunk`](crate::fs::Filesystem::read_chunk) and [`write_chunk`](crate::fs::Filesystem::write_chunk) methods, +/// Used in the `read_chunk` and `write_chunk` methods, /// Where [`SeekFrom::Current`] would not make sense. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum OpenSeekFrom { @@ -122,8 +118,7 @@ pub type Result = core::result::Result; /// Specific error codes are available as associated constants of this type. /// /// ``` -/// # use littlefs2::io::Error; -/// +/// # use littlefs2_core::Error; /// assert_eq!(Error::IO.code(), -5); /// assert_eq!(Error::new(-5), Some(Error::IO)); /// ``` @@ -134,46 +129,46 @@ pub struct Error { impl Error { /// Input / output error occurred. - pub const IO: Self = Self::new_const(ll::lfs_error_LFS_ERR_IO); + pub const IO: Self = Self::new_const(-5); /// File or filesystem was corrupt. - pub const CORRUPTION: Self = Self::new_const(ll::lfs_error_LFS_ERR_CORRUPT); + pub const CORRUPTION: Self = Self::new_const(-84); /// No entry found with that name. - pub const NO_SUCH_ENTRY: Self = Self::new_const(ll::lfs_error_LFS_ERR_NOENT); + pub const NO_SUCH_ENTRY: Self = Self::new_const(-2); /// File or directory already exists. - pub const ENTRY_ALREADY_EXISTED: Self = Self::new_const(ll::lfs_error_LFS_ERR_EXIST); + pub const ENTRY_ALREADY_EXISTED: Self = Self::new_const(-17); /// Path name is not a directory. - pub const PATH_NOT_DIR: Self = Self::new_const(ll::lfs_error_LFS_ERR_NOTDIR); + pub const PATH_NOT_DIR: Self = Self::new_const(-20); /// Path specification is to a directory. - pub const PATH_IS_DIR: Self = Self::new_const(ll::lfs_error_LFS_ERR_ISDIR); + pub const PATH_IS_DIR: Self = Self::new_const(-21); /// Directory was not empty. - pub const DIR_NOT_EMPTY: Self = Self::new_const(ll::lfs_error_LFS_ERR_NOTEMPTY); + pub const DIR_NOT_EMPTY: Self = Self::new_const(-39); /// Bad file descriptor. - pub const BAD_FILE_DESCRIPTOR: Self = Self::new_const(ll::lfs_error_LFS_ERR_BADF); + pub const BAD_FILE_DESCRIPTOR: Self = Self::new_const(-9); /// File is too big. - pub const FILE_TOO_BIG: Self = Self::new_const(ll::lfs_error_LFS_ERR_FBIG); + pub const FILE_TOO_BIG: Self = Self::new_const(-27); /// Incorrect value specified to function. - pub const INVALID: Self = Self::new_const(ll::lfs_error_LFS_ERR_INVAL); + pub const INVALID: Self = Self::new_const(-22); /// No space left available for operation. - pub const NO_SPACE: Self = Self::new_const(ll::lfs_error_LFS_ERR_NOSPC); + pub const NO_SPACE: Self = Self::new_const(-28); /// No memory available for completing request. - pub const NO_MEMORY: Self = Self::new_const(ll::lfs_error_LFS_ERR_NOMEM); + pub const NO_MEMORY: Self = Self::new_const(-12); /// No attribute or data available - pub const NO_ATTRIBUTE: Self = Self::new_const(ll::lfs_error_LFS_ERR_NOATTR); + pub const NO_ATTRIBUTE: Self = Self::new_const(-61); /// Filename too long - pub const FILENAME_TOO_LONG: Self = Self::new_const(ll::lfs_error_LFS_ERR_NAMETOOLONG); + pub const FILENAME_TOO_LONG: Self = Self::new_const(-36); /// Construct an `Error` from an error code. /// @@ -225,8 +220,7 @@ impl Error { /// Prints the numeric error code and the name of the error (if known). /// /// ``` -/// # use littlefs2::io::Error; -/// +/// # use littlefs2_core::Error; /// assert_eq!( /// &format!("{:?}", Error::IO), /// "Error { code: -5, kind: Some(\"Io\") }", @@ -251,17 +245,3 @@ impl From for c_int { error.code } } - -pub fn error_code_from(result: Result) -> ll::lfs_error { - result - .map(|_| ll::lfs_error_LFS_ERR_OK) - .unwrap_or_else(From::from) -} - -pub fn result_from(return_value: T, error_code: ll::lfs_error) -> Result { - if let Some(error) = Error::new(error_code) { - Err(error) - } else { - Ok(return_value) - } -} diff --git a/core/src/lib.rs b/core/src/lib.rs new file mode 100644 index 000000000..f9fdac864 --- /dev/null +++ b/core/src/lib.rs @@ -0,0 +1,63 @@ +#![no_std] + +//! Core types for the [`littlefs2`][] crate. +//! +//! See the documentation for [`littlefs2`][] for more information. +//! +//! [`littlefs2`]: https://docs.rs/littlefs2 + +mod consts; +mod fs; +mod io; +mod object_safe; +mod path; + +pub use consts::{ATTRBYTES_MAX, ATTRBYTES_MAX_TYPE, PATH_MAX, PATH_MAX_PLUS_ONE}; +pub use fs::{Attribute, DirEntry, FileOpenFlags, FileType, Metadata}; +pub use io::{Error, OpenSeekFrom, Read, Result, Seek, SeekFrom, Write}; +pub use object_safe::{DirEntriesCallback, DynFile, DynFilesystem, FileCallback, Predicate}; +pub use path::{Ancestors, Iter, Path, PathBuf, PathError}; + +/// Creates a path from a string without a trailing null. +/// +/// Panics and causes a compiler error if the string contains null bytes or non-ascii characters. +/// +/// # Examples +/// +/// ``` +/// use littlefs2_core::{path, Path}; +/// +/// const HOME: &Path = path!("/home"); +/// let root = path!("/"); +/// ``` +/// +/// Illegal values: +/// +/// ```compile_fail +/// # use littlefs2_core::{path, Path}; +/// const WITH_NULL: &Path = path!("/h\0me"); // does not compile +/// ``` +/// +/// ```compile_fail +/// # use littlefs2_core::{path, Path}; +/// const WITH_UTF8: &Path = path!("/höme"); // does not compile +/// ``` +/// +/// The macro enforces const evaluation so that compilation fails for illegal values even if the +/// macro is not used in a const context: +/// +/// ```compile_fail +/// # use littlefs2_core::path; +/// let path = path!("te\0st"); // does not compile +/// ``` +#[macro_export] +macro_rules! path { + ($path:literal) => {{ + const _PATH: &$crate::Path = + match $crate::Path::from_str_with_nul(::core::concat!($path, "\0")) { + Ok(path) => path, + Err(_) => panic!("invalid littlefs2 path"), + }; + _PATH + }}; +} diff --git a/core/src/object_safe.rs b/core/src/object_safe.rs new file mode 100644 index 000000000..6296e50f1 --- /dev/null +++ b/core/src/object_safe.rs @@ -0,0 +1,151 @@ +use heapless::Vec; + +use crate::{ + fs::{Attribute, DirEntry, FileOpenFlags, Metadata}, + io::{Error, OpenSeekFrom, Read, Result, Seek, Write}, + path::Path, +}; + +// Make sure that the traits actually are object safe. +const _: Option<&dyn DynFile> = None; +const _: Option<&dyn DynFilesystem> = None; + +pub type DirEntriesCallback<'a, R = ()> = + &'a mut dyn FnMut(&mut dyn Iterator>) -> Result; +pub type FileCallback<'a, R = ()> = &'a mut dyn FnMut(&dyn DynFile) -> Result; +pub type Predicate<'a> = &'a dyn Fn(&DirEntry) -> bool; + +/// Object-safe trait for files. +/// +/// The methods for opening files cannot be implemented in this trait. Use these methods instead: +/// - [`DynFilesystem::create_file_and_then`](trait.DynFilesystem.html#method.create_file_and_then) +/// - [`DynFilesystem::open_file_and_then`](trait.DynFilesystem.html#method.open_file_and_then) +/// - [`DynFilesystem::open_file_with_options_and_then`](trait.DynFilesystem.html#method.open_file_with_options_and_then) +pub trait DynFile: Read + Seek + Write { + fn sync(&self) -> Result<()>; + fn len(&self) -> Result; + fn is_empty(&self) -> Result; + fn set_len(&self, size: usize) -> Result<()>; +} + +impl dyn DynFile + '_ { + pub fn read_to_end(&self, buf: &mut Vec) -> Result { + let had = buf.len(); + buf.resize_default(buf.capacity()).unwrap(); + let read = self.read(&mut buf[had..])?; + buf.truncate(had + read); + Ok(read) + } +} + +/// Object-safe trait for filesystems. +/// +/// The following methods cannot support generic return types in the callbacks: +/// - [`DynFilesystem::create_file_and_then_unit`][] +/// - [`DynFilesystem::open_file_and_then_unit`][] +/// - [`DynFilesystem::open_file_with_flags_and_then_unit`][] +/// - [`DynFilesystem::read_dir_and_then_unit`][] +/// +/// Use these helper functions instead: +/// - [`DynFilesystem::create_file_and_then`](#method.create_file_and_then) +/// - [`DynFilesystem::open_file_and_then`](#method.open_file_and_then) +/// - [`DynFilesystem::open_file_with_flags_and_then`](#method.open_file_with_flags_and_then) +/// - [`DynFilesystem::read_dir_and_then`](#method.read_dir_and_then) +pub trait DynFilesystem { + fn total_blocks(&self) -> usize; + fn total_space(&self) -> usize; + fn available_blocks(&self) -> Result; + fn available_space(&self) -> Result; + fn remove(&self, path: &Path) -> Result<()>; + fn remove_dir(&self, path: &Path) -> Result<()>; + #[cfg(feature = "dir-entry-path")] + fn remove_dir_all(&self, path: &Path) -> Result<()>; + #[cfg(feature = "dir-entry-path")] + fn remove_dir_all_where(&self, path: &Path, predicate: Predicate<'_>) -> Result; + fn rename(&self, from: &Path, to: &Path) -> Result<()>; + fn exists(&self, path: &Path) -> bool; + fn metadata(&self, path: &Path) -> Result; + fn create_file_and_then_unit(&self, path: &Path, f: FileCallback<'_>) -> Result<()>; + fn open_file_and_then_unit(&self, path: &Path, f: FileCallback<'_>) -> Result<()>; + fn open_file_with_flags_and_then_unit( + &self, + flags: FileOpenFlags, + path: &Path, + f: FileCallback<'_>, + ) -> Result<()>; + fn attribute(&self, path: &Path, id: u8) -> Result>; + fn remove_attribute(&self, path: &Path, id: u8) -> Result<()>; + fn set_attribute(&self, path: &Path, attribute: &Attribute) -> Result<()>; + fn read_dir_and_then_unit(&self, path: &Path, f: DirEntriesCallback<'_>) -> Result<()>; + fn create_dir(&self, path: &Path) -> Result<()>; + fn create_dir_all(&self, path: &Path) -> Result<()>; + fn write(&self, path: &Path, contents: &[u8]) -> Result<()>; + fn write_chunk(&self, path: &Path, contents: &[u8], pos: OpenSeekFrom) -> Result<()>; +} + +impl dyn DynFilesystem + '_ { + pub fn read(&self, path: &Path) -> Result> { + let mut contents = Vec::new(); + self.open_file_and_then(path, &mut |file| { + file.read_to_end(&mut contents)?; + Ok(()) + })?; + Ok(contents) + } + + pub fn read_chunk( + &self, + path: &Path, + pos: OpenSeekFrom, + ) -> Result<(Vec, usize)> { + let mut contents = Vec::new(); + let file_len = self.open_file_and_then(path, &mut |file| { + file.seek(pos.into())?; + let read_n = file.read(&mut contents)?; + contents.truncate(read_n); + file.len() + })?; + Ok((contents, file_len)) + } + + pub fn create_file_and_then(&self, path: &Path, f: FileCallback<'_, R>) -> Result { + let mut result = Err(Error::IO); + self.create_file_and_then_unit(path, &mut |file| { + result = Ok(f(file)?); + Ok(()) + })?; + result + } + + pub fn open_file_and_then(&self, path: &Path, f: FileCallback<'_, R>) -> Result { + let mut result = Err(Error::IO); + self.open_file_and_then_unit(path, &mut |file| { + result = Ok(f(file)?); + Ok(()) + })?; + result + } + + pub fn open_file_with_flags_and_then( + &self, + flags: FileOpenFlags, + path: &Path, + f: FileCallback<'_, R>, + ) -> Result { + let mut result = Err(Error::IO); + self.open_file_with_flags_and_then_unit(flags, path, &mut |file| { + result = Ok(f(file)?); + Ok(()) + })?; + result + } + + pub fn read_dir_and_then(&self, path: &Path, f: DirEntriesCallback<'_, R>) -> Result { + let mut result = Err(Error::IO); + self.read_dir_and_then_unit(path, &mut |entries| { + result = Ok(f(entries)?); + Ok(()) + })?; + result + } +} diff --git a/src/path.rs b/core/src/path.rs similarity index 96% rename from src/path.rs rename to core/src/path.rs index 2b044c0b3..7ef064b90 100644 --- a/src/path.rs +++ b/core/src/path.rs @@ -36,7 +36,7 @@ impl Path { /// /// ``` ///# use std::cmp::Ordering; - ///# use littlefs2::path; + ///# use littlefs2_core::path; /// assert_eq!(path!("some_path_a").cmp_str(path!("some_path_b")), Ordering::Less); /// assert_eq!(path!("some_path_b").cmp_str(path!("some_path_a")), Ordering::Greater); /// assert_eq!(path!("some_path").cmp_str(path!("some_path_a")), Ordering::Less); @@ -54,7 +54,7 @@ impl Path { /// /// ``` ///# use std::cmp::Ordering; - ///# use littlefs2::path; + ///# use littlefs2_core::path; /// assert_eq!(path!("some_path_a").cmp_lfs(path!("some_path_b")), Ordering::Less); /// assert_eq!(path!("some_path_b").cmp_lfs(path!("some_path_a")), Ordering::Greater); /// assert_eq!(path!("some_path").cmp_lfs(path!("some_path_a")), Ordering::Greater); @@ -153,7 +153,7 @@ impl Path { /// Return true if the path is empty /// /// ```rust - ///# use littlefs2::path; + ///# use littlefs2_core::path; /// /// assert!(path!("").is_empty()); /// assert!(!path!("something").is_empty()); @@ -165,7 +165,7 @@ impl Path { /// Get the name of the file this path points to if it points to one /// /// ``` - ///# use littlefs2::path; + ///# use littlefs2_core::path; /// let path = path!("/some/path/file.extension"); /// assert_eq!(path.file_name(), Some(path!("file.extension"))); /// @@ -199,7 +199,7 @@ impl Path { /// Iterate over the ancestors of the path /// /// ``` - ///# use littlefs2::path; + ///# use littlefs2_core::path; /// let path = path!("/some/path/file.extension"); /// let mut ancestors = path.ancestors(); /// assert_eq!(&*ancestors.next().unwrap(), path!("/some/path/file.extension")); @@ -217,7 +217,7 @@ impl Path { /// Iterate over the components of the path /// /// ``` - ///# use littlefs2::path; + ///# use littlefs2_core::path; /// let path = path!("/some/path/file.extension"); /// let mut iter = path.iter(); /// assert_eq!(&*iter.next().unwrap(), path!("/")); @@ -249,7 +249,7 @@ impl Path { pub const fn from_bytes_with_nul(bytes: &[u8]) -> Result<&Self> { match CStr::from_bytes_with_nul(bytes) { Ok(cstr) => Self::from_cstr(cstr), - Err(_) => Err(Error::NotCStr), + Err(_) => Err(PathError::NotCStr), } } @@ -263,11 +263,11 @@ impl Path { let bytes = cstr.to_bytes(); let n = cstr.to_bytes().len(); if n > consts::PATH_MAX { - Err(Error::TooLarge) + Err(PathError::TooLarge) } else if bytes.is_ascii() { Ok(unsafe { Self::from_cstr_unchecked(cstr) }) } else { - Err(Error::NotAscii) + Err(PathError::NotAscii) } } @@ -281,7 +281,7 @@ impl Path { } /// Returns the inner pointer to this C string. - pub(crate) fn as_ptr(&self) -> *const c_char { + pub fn as_ptr(&self) -> *const c_char { self.inner.as_ptr() } @@ -343,7 +343,7 @@ impl fmt::Display for Path { } impl<'b> TryFrom<&'b [u8]> for &'b Path { - type Error = Error; + type Error = PathError; fn try_from(bytes: &[u8]) -> Result<&Path> { Path::from_bytes_with_nul(bytes) @@ -361,7 +361,7 @@ macro_rules! array_impls { ($($N:expr),+) => { $( impl<'b> TryFrom<&'b [u8; $N]> for &'b Path { - type Error = Error; + type Error = PathError; fn try_from(bytes: &[u8; $N]) -> Result<&Path> { Path::from_bytes_with_nul(&bytes[..]) @@ -369,7 +369,7 @@ macro_rules! array_impls { } impl TryFrom<&[u8; $N]> for PathBuf { - type Error = Error; + type Error = PathError; fn try_from(bytes: &[u8; $N]) -> Result { Self::try_from(&bytes[..]) @@ -436,7 +436,12 @@ impl PathBuf { self.len = 1; } - pub(crate) unsafe fn from_buffer(buf: [c_char; consts::PATH_MAX_PLUS_ONE]) -> Self { + /// Creates a from a raw buffer containing a null-terminated ASCII string. + /// + /// # Safety + /// + /// The buffer must contain only ASCII characters and at least one null byte. + pub unsafe fn from_buffer_unchecked(buf: [c_char; consts::PATH_MAX_PLUS_ONE]) -> Self { let len = strlen(buf.as_ptr()) + 1 /* null byte */; PathBuf { buf, len } } @@ -467,9 +472,6 @@ impl PathBuf { .map(|byte| *byte != b'/') .unwrap_or(false); let slen = src.len(); - #[cfg(test)] - println!("{}, {}, {}", self.len, slen, consts::PATH_MAX_PLUS_ONE); - // hprintln!("{}, {}, {}", self.len, slen, consts::PATH_MAX_PLUS_ONE); assert!( self.len + slen @@ -511,7 +513,7 @@ impl From<&Path> for PathBuf { /// Accepts byte strings, with or without trailing nul. impl TryFrom<&[u8]> for PathBuf { - type Error = Error; + type Error = PathError; fn try_from(bytes: &[u8]) -> Result { // NB: This needs to set the final NUL byte, unless it already has one @@ -522,14 +524,14 @@ impl TryFrom<&[u8]> for PathBuf { bytes }; if bytes.len() > consts::PATH_MAX { - return Err(Error::TooLarge); + return Err(PathError::TooLarge); } for byte in bytes { if *byte == 0 { - return Err(Error::NotCStr); + return Err(PathError::NotCStr); } if !byte.is_ascii() { - return Err(Error::NotAscii); + return Err(PathError::NotAscii); } } @@ -542,7 +544,7 @@ impl TryFrom<&[u8]> for PathBuf { /// Accepts strings, with or without trailing nul. impl TryFrom<&str> for PathBuf { - type Error = Error; + type Error = PathError; fn try_from(s: &str) -> Result { PathBuf::try_from(s.as_bytes()) @@ -646,7 +648,7 @@ impl core::cmp::Eq for PathBuf {} /// Errors that arise from converting byte buffers into paths #[derive(Clone, Copy, Debug)] -pub enum Error { +pub enum PathError { /// Byte buffer contains non-ASCII characters NotAscii, /// Byte buffer is not a C string @@ -655,8 +657,7 @@ pub enum Error { TooLarge, } -/// Result type that has its Error variant set to `path::Error` -pub type Result = core::result::Result; +type Result = core::result::Result; #[cfg(test)] mod tests { diff --git a/src/consts.rs b/src/consts.rs index 040e436a3..5d11bb7cd 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -3,12 +3,8 @@ /// Re-export of `typenum::consts`. pub use generic_array::typenum::consts::*; +pub use littlefs2_core::{ATTRBYTES_MAX, ATTRBYTES_MAX_TYPE, PATH_MAX, PATH_MAX_PLUS_ONE}; + pub const FILENAME_MAX_PLUS_ONE: u32 = 255 + 1; -// pub type PATH_DEFAULT_MAX = generic_array::typenum::consts::U255; -// pub const PATH_MAX: u32 = 255; -pub const PATH_MAX: usize = 255; -pub const PATH_MAX_PLUS_ONE: usize = PATH_MAX + 1; pub const FILEBYTES_MAX: u32 = crate::ll::LFS_FILE_MAX as _; -pub const ATTRBYTES_MAX: u32 = 1_022; -pub type ATTRBYTES_MAX_TYPE = U1022; pub const LOOKAHEADWORDS_SIZE: u32 = 16; diff --git a/src/fs.rs b/src/fs.rs index 47e642939..74096d519 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -1,6 +1,5 @@ //! Experimental Filesystem version using closures. -use bitflags::bitflags; use core::ffi::{c_int, c_void}; use core::ptr::addr_of; use core::ptr::addr_of_mut; @@ -14,12 +13,28 @@ use littlefs2_sys as ll; // so far, don't need `heapless-bytes`. pub type Bytes = generic_array::GenericArray; +pub use littlefs2_core::{Attribute, DirEntry, FileOpenFlags, FileType, Metadata}; + use crate::{ driver, io::{self, Error, OpenSeekFrom, Result}, path::{Path, PathBuf}, }; +fn error_code_from(result: Result) -> ll::lfs_error { + result + .map(|_| ll::lfs_error_LFS_ERR_OK) + .unwrap_or_else(From::from) +} + +fn result_from(return_value: T, error_code: ll::lfs_error) -> Result { + if let Some(error) = Error::new(error_code) { + Err(error) + } else { + Ok(return_value) + } +} + struct Cache { read: UnsafeCell>, write: UnsafeCell>, @@ -158,71 +173,16 @@ pub struct Filesystem<'a, Storage: driver::Storage> { storage: &'a mut Storage, } -/// Regular file vs directory -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -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)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -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 ll::lfs_type { - 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, +fn metadata(info: ll::lfs_info) -> Metadata { + let file_type = match info.type_ as ll::lfs_type { + ll::lfs_type_LFS_TYPE_DIR => FileType::Dir, + ll::lfs_type_LFS_TYPE_REG => FileType::File, + _ => { + unreachable!(); } - } + }; + + Metadata::new(file_type, info.size as usize) } #[cfg(feature = "dir-entry-path")] @@ -241,7 +201,7 @@ impl Filesystem<'_, Storage> { 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) }; - io::result_from((), return_code) + result_from((), return_code) } // TODO: check if this is equivalent to `is_formatted`. @@ -287,8 +247,7 @@ impl Filesystem<'_, Storage> { /// by this method available, at any given time. pub fn available_blocks(&self) -> Result { let return_code = unsafe { ll::lfs_fs_size(&mut self.alloc.borrow_mut().state) }; - io::result_from(return_code, return_code) - .map(|blocks| self.total_blocks() - blocks as usize) + result_from(return_code, return_code).map(|blocks| self.total_blocks() - blocks as usize) } /// Available number of unused bytes in the filesystem @@ -305,7 +264,7 @@ impl Filesystem<'_, Storage> { pub fn remove(&self, path: &Path) -> Result<()> { let return_code = unsafe { ll::lfs_remove(&mut self.alloc.borrow_mut().state, path.as_ptr()) }; - io::result_from((), return_code) + result_from((), return_code) } /// Remove a file or directory. @@ -397,7 +356,7 @@ impl Filesystem<'_, Storage> { to.as_ptr(), ) }; - io::result_from((), return_code) + result_from((), return_code) } /// Check whether a file or directory exists at a path. @@ -423,7 +382,7 @@ impl Filesystem<'_, Storage> { let return_code = unsafe { ll::lfs_stat(&mut self.alloc.borrow_mut().state, path.as_ptr(), &mut info) }; - io::result_from((), return_code).map(|_| info.into()) + result_from((), return_code).map(|_| metadata(info)) } pub fn create_file_and_then( @@ -479,7 +438,7 @@ impl Filesystem<'_, Storage> { return Ok(None); } - io::result_from((), return_code)?; + result_from((), return_code)?; // TODO: get rid of this unreachable!(); } @@ -488,7 +447,7 @@ impl Filesystem<'_, Storage> { pub fn remove_attribute(&self, path: &Path, id: u8) -> Result<()> { let return_code = unsafe { ll::lfs_removeattr(&mut self.alloc.borrow_mut().state, path.as_ptr(), id) }; - io::result_from((), return_code) + result_from((), return_code) } /// Set attribute. @@ -497,13 +456,13 @@ impl Filesystem<'_, Storage> { ll::lfs_setattr( &mut self.alloc.borrow_mut().state, path.as_ptr(), - attribute.id, + attribute.id(), &attribute.data as *const _ as *const c_void, attribute.size as u32, ) }; - io::result_from((), return_code) + result_from((), return_code) } /// C callback interface used by LittleFS to read data with the lower level system below the @@ -522,7 +481,7 @@ impl Filesystem<'_, Storage> { 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) }; - io::error_code_from(storage.read(off, buf)) + error_code_from(storage.read(off, buf)) } /// C callback interface used by LittleFS to program data with the lower level system below the @@ -542,7 +501,7 @@ impl Filesystem<'_, Storage> { let off = (block * block_size + off) as usize; let buf: &[u8] = unsafe { slice::from_raw_parts(buffer as *const u8, size as usize) }; - io::error_code_from(storage.write(off, buf)) + error_code_from(storage.write(off, buf)) } /// C callback interface used by LittleFS to erase data with the lower level system below the @@ -552,7 +511,7 @@ impl Filesystem<'_, Storage> { let storage = unsafe { &mut *((*c).context as *mut Storage) }; let off = block as usize * Storage::BLOCK_SIZE; - io::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 @@ -564,74 +523,6 @@ impl Filesystem<'_, Storage> { } } -#[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 = crate::consts::ATTRBYTES_MAX as _; - let len = cmp::min(attr_max, self.size); - &self.data[..len] - } - - pub fn set_data(&mut self, data: &[u8]) -> &mut Self { - let attr_max = crate::consts::ATTRBYTES_MAX as _; - 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; - } - self - } -} - -bitflags! { - /// Definition of file open flags which can be mixed and matched as appropriate. These definitions - /// are reminiscent of the ones defined by POSIX. - pub struct FileOpenFlags: i32 { - /// 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; - } -} - /// The state of a `File`. Pre-allocate with `File::allocate`. pub struct FileAllocation { cache: UnsafeCell>, @@ -734,7 +625,7 @@ impl<'a, 'b, Storage: driver::Storage> File<'a, 'b, Storage> { // so we cannot assert unique mutable access. addr_of_mut!((*(*self.alloc.borrow_mut())).state), ); - io::result_from((), return_code) + result_from((), return_code) } /// Synchronize file contents to storage. @@ -748,7 +639,7 @@ impl<'a, 'b, Storage: driver::Storage> File<'a, 'b, Storage> { addr_of_mut!((*(*self.alloc.borrow_mut())).state), ) }; - io::result_from((), return_code) + result_from((), return_code) } /// Size of the file in bytes. @@ -762,7 +653,7 @@ impl<'a, 'b, Storage: driver::Storage> File<'a, 'b, Storage> { addr_of_mut!((*(*self.alloc.borrow_mut())).state), ) }; - io::result_from(return_code as usize, return_code) + result_from(return_code as usize, return_code) } pub fn is_empty(&self) -> Result { @@ -785,7 +676,7 @@ impl<'a, 'b, Storage: driver::Storage> File<'a, 'b, Storage> { size as u32, ) }; - io::result_from((), return_code) + result_from((), return_code) } // This belongs in `io::Read` but really don't want that to have a generic parameter @@ -868,7 +759,7 @@ impl OpenOptions { fs, }; - io::result_from(file, return_code) + result_from(file, return_code) } /// (Hopefully) safe abstraction around `open`. @@ -969,7 +860,7 @@ impl io::Read for File<'_, '_, S> { buf.len() as u32, ) }; - io::result_from(return_code as usize, return_code) + result_from(return_code as usize, return_code) } } @@ -986,7 +877,7 @@ impl io::Seek for File<'_, '_, S> { pos.whence(), ) }; - io::result_from(return_code as usize, return_code) + result_from(return_code as usize, return_code) } } @@ -1003,7 +894,7 @@ impl io::Write for File<'_, '_, S> { buf.len() as u32, ) }; - io::result_from(return_code as usize, return_code) + result_from(return_code as usize, return_code) } fn flush(&self) -> Result<()> { @@ -1011,51 +902,6 @@ impl io::Write for File<'_, '_, S> { } } -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct DirEntry { - file_name: PathBuf, - metadata: Metadata, - #[cfg(feature = "dir-entry-path")] - path: PathBuf, -} - -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) -> &Path { - &self.file_name - } - - /// 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 - } - - #[cfg(feature = "dir-entry-path")] - #[doc(hidden)] - // This is used in `crypto-service` to "namespace" paths - // by mutating a DirEntry in-place. - pub unsafe fn path_buf_mut(&mut self) -> &mut PathBuf { - &mut self.path - } -} - pub struct ReadDirAllocation { state: ll::lfs_dir_t, } @@ -1100,19 +946,10 @@ impl<'a, 'b, S: driver::Storage> Iterator for ReadDir<'a, 'b, S> { }; if return_code > 0 { - let file_name = unsafe { PathBuf::from_buffer(info.name) }; - let metadata = info.into(); + let file_name = unsafe { PathBuf::from_buffer_unchecked(info.name) }; + let metadata = metadata(info); - #[cfg(feature = "dir-entry-path")] - // TODO: error handling... - let path = self.path.join(&file_name); - - let dir_entry = DirEntry { - file_name, - metadata, - #[cfg(feature = "dir-entry-path")] - path, - }; + let dir_entry = DirEntry::new(&self.path, file_name, metadata); return Some(Ok(dir_entry)); } @@ -1120,7 +957,7 @@ impl<'a, 'b, S: driver::Storage> Iterator for ReadDir<'a, 'b, S> { return None; } - Some(Err(io::result_from((), return_code).unwrap_err())) + Some(Err(result_from((), return_code).unwrap_err())) } } @@ -1149,7 +986,7 @@ impl ReadDir<'_, '_, S> { addr_of_mut!((*(*self.alloc.borrow_mut())).state), ) }; - io::result_from((), return_code) + result_from((), return_code) } } @@ -1192,7 +1029,7 @@ impl<'a, Storage: driver::Storage> Filesystem<'a, Storage> { path: PathBuf::from(path), }; - io::result_from(read_dir, return_code) + result_from(read_dir, return_code) } } @@ -1224,7 +1061,7 @@ impl<'a, Storage: driver::Storage> Filesystem<'a, Storage> { let mut alloc = self.alloc.borrow_mut(); let return_code = unsafe { ll::lfs_mount(&mut alloc.state, &alloc.config) }; drop(alloc); - io::result_from((), return_code) + result_from((), return_code) } // Not public, user should use `mount`, possibly after `format` @@ -1255,7 +1092,7 @@ impl<'a, Storage: driver::Storage> Filesystem<'a, Storage> { println!("creating {:?}", path); let return_code = unsafe { ll::lfs_mkdir(&mut self.alloc.borrow_mut().state, path.as_ptr()) }; - io::result_from((), return_code) + result_from((), return_code) } /// Recursively create a directory and all of its parent components if they are missing. @@ -1582,7 +1419,7 @@ mod tests { // Do we want a way to borrow_filesystem for DirEntry? // One usecase is to read data from the files iterated over. // - if entry.metadata.is_file() { + if entry.metadata().is_file() { fs.write(entry.file_name(), b"wowee zowie")?; } } diff --git a/src/io/prelude.rs b/src/io/prelude.rs deleted file mode 100644 index e0100b200..000000000 --- a/src/io/prelude.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! Export of the Read, Write and Seek traits for ease of use. - -pub use super::{Read, Seek, SeekFrom, Write}; diff --git a/src/lib.rs b/src/lib.rs index 4b336f483..dc74da1a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -128,6 +128,26 @@ assert_eq!(&buf, b"black smoke"); /// Low-level bindings pub use littlefs2_sys as ll; +// Re-exports for compatibility with older versions +pub use littlefs2_core::path; + +/// Traits and types for core I/O functionality. +pub mod io { + pub use littlefs2_core::{Error, OpenSeekFrom, Read, Result, Seek, SeekFrom, Write}; + + pub mod prelude { + //! Export of the Read, Write and Seek traits for ease of use. + + pub use super::{Read, Seek, SeekFrom, Write}; + } +} + +/// Paths +pub mod path { + pub use littlefs2_core::{Ancestors, Iter, Path, PathBuf, PathError as Error}; + pub type Result = core::result::Result; +} + #[macro_use] extern crate delog; generate_macros!(); @@ -142,9 +162,7 @@ mod c_stubs; pub mod consts; pub mod driver; pub mod fs; -pub mod io; pub mod object_safe; -pub mod path; /// get information about the C backend pub fn version() -> Version { @@ -163,49 +181,5 @@ pub struct Version { pub backend: (u32, u32), } -/// Creates a path from a string without a trailing null. -/// -/// Panics and causes a compiler error if the string contains null bytes or non-ascii characters. -/// -/// # Examples -/// -/// ``` -/// use littlefs2::{path, path::Path}; -/// -/// const HOME: &Path = path!("/home"); -/// let root = path!("/"); -/// ``` -/// -/// Illegal values: -/// -/// ```compile_fail -/// # use littlefs2::{path, path::Path}; -/// const WITH_NULL: &Path = path!("/h\0me"); // does not compile -/// ``` -/// -/// ```compile_fail -/// # use littlefs2::{path, path::Path}; -/// const WITH_UTF8: &Path = path!("/höme"); // does not compile -/// ``` -/// -/// The macro enforces const evaluation so that compilation fails for illegal values even if the -/// macro is not used in a const context: -/// -/// ```compile_fail -/// # use littlefs2::path; -/// let path = path!("te\0st"); // does not compile -/// ``` -#[macro_export] -macro_rules! path { - ($path:literal) => {{ - const _PATH: &$crate::path::Path = - match $crate::path::Path::from_str_with_nul(::core::concat!($path, "\0")) { - Ok(path) => path, - Err(_) => panic!("invalid littlefs2 path"), - }; - _PATH - }}; -} - #[cfg(test)] mod tests; diff --git a/src/object_safe.rs b/src/object_safe.rs index 3182ed43b..fbfe49333 100644 --- a/src/object_safe.rs +++ b/src/object_safe.rs @@ -1,40 +1,20 @@ //! Object-safe traits for [`File`][], [`Filesystem`][] and [`Storage`][]. use generic_array::typenum::Unsigned as _; -use heapless::Vec; use crate::{ driver::Storage, - fs::{Attribute, DirEntry, File, FileOpenFlags, Filesystem, Metadata}, - io::{Error, OpenSeekFrom, Read, Result, Seek, Write}, + fs::{Attribute, File, FileOpenFlags, Filesystem, Metadata}, + io::{Error, OpenSeekFrom, Result}, path::Path, }; +pub use littlefs2_core::{DirEntriesCallback, DynFile, DynFilesystem, FileCallback, Predicate}; + // Make sure that the traits actually are object safe. -const _: Option<&dyn DynFile> = None; -const _: Option<&dyn DynFilesystem> = None; const _: Option<&dyn DynStorage> = None; -pub type DirEntriesCallback<'a, R = ()> = - &'a mut dyn FnMut(&mut dyn Iterator>) -> Result; -pub type FileCallback<'a, R = ()> = &'a mut dyn FnMut(&dyn DynFile) -> Result; pub type FilesystemCallback<'a, R = ()> = &'a mut dyn FnMut(&dyn DynFilesystem) -> Result; -pub type Predicate<'a> = &'a dyn Fn(&DirEntry) -> bool; - -/// Object-safe trait for [`File`][]. -/// -/// The methods for opening files cannot be implemented in this trait. Use these methods instead: -/// - [`DynFilesystem::create_file_and_then`](trait.DynFilesystem.html#method.create_file_and_then) -/// - [`DynFilesystem::open_file_and_then`](trait.DynFilesystem.html#method.open_file_and_then) -/// - [`DynFilesystem::open_file_with_options_and_then`](trait.DynFilesystem.html#method.open_file_with_options_and_then) -/// -/// All other methods are mirrored directly. See the documentation for [`File`][] for more information. -pub trait DynFile: Read + Seek + Write { - fn sync(&self) -> Result<()>; - fn len(&self) -> Result; - fn is_empty(&self) -> Result; - fn set_len(&self, size: usize) -> Result<()>; -} impl DynFile for File<'_, '_, S> { fn sync(&self) -> Result<()> { @@ -54,68 +34,6 @@ impl DynFile for File<'_, '_, S> { } } -impl dyn DynFile + '_ { - pub fn read_to_end(&self, buf: &mut Vec) -> Result { - let had = buf.len(); - buf.resize_default(buf.capacity()).unwrap(); - let read = self.read(&mut buf[had..])?; - buf.truncate(had + read); - Ok(read) - } -} - -/// Object-safe trait for [`Filesystem`][]. -/// -/// The following methods are implemented in [`DynStorage`][] instead: -/// - [`DynStorage::format`][] -/// - [`DynStorage::is_mountable`][] -/// - [`DynStorage::mount_and_then`](trait.DynStorage.html#method.mount_and_then) -/// -/// The following methods cannot support generic return types in the callbacks: -/// - [`DynFilesystem::create_file_and_then_unit`][] -/// - [`DynFilesystem::open_file_and_then_unit`][] -/// - [`DynFilesystem::open_file_with_flags_and_then_unit`][] -/// - [`DynFilesystem::read_dir_and_then_unit`][] -/// -/// Use these helper functions instead: -/// - [`DynFilesystem::create_file_and_then`](#method.create_file_and_then) -/// - [`DynFilesystem::open_file_and_then`](#method.open_file_and_then) -/// - [`DynFilesystem::open_file_with_options_and_then`](#method.open_file_with_options_and_then) -/// - [`DynFilesystem::read_dir_and_then`](#method.read_dir_and_then) -/// -/// All other methods are mirrored directly. See the documentation for [`Filesystem`][] for more information. -pub trait DynFilesystem { - fn total_blocks(&self) -> usize; - fn total_space(&self) -> usize; - fn available_blocks(&self) -> Result; - fn available_space(&self) -> Result; - fn remove(&self, path: &Path) -> Result<()>; - fn remove_dir(&self, path: &Path) -> Result<()>; - #[cfg(feature = "dir-entry-path")] - fn remove_dir_all(&self, path: &Path) -> Result<()>; - #[cfg(feature = "dir-entry-path")] - fn remove_dir_all_where(&self, path: &Path, predicate: Predicate<'_>) -> Result; - fn rename(&self, from: &Path, to: &Path) -> Result<()>; - fn exists(&self, path: &Path) -> bool; - fn metadata(&self, path: &Path) -> Result; - fn create_file_and_then_unit(&self, path: &Path, f: FileCallback<'_>) -> Result<()>; - fn open_file_and_then_unit(&self, path: &Path, f: FileCallback<'_>) -> Result<()>; - fn open_file_with_flags_and_then_unit( - &self, - flags: FileOpenFlags, - path: &Path, - f: FileCallback<'_>, - ) -> Result<()>; - fn attribute(&self, path: &Path, id: u8) -> Result>; - fn remove_attribute(&self, path: &Path, id: u8) -> Result<()>; - fn set_attribute(&self, path: &Path, attribute: &Attribute) -> Result<()>; - fn read_dir_and_then_unit(&self, path: &Path, f: DirEntriesCallback<'_>) -> Result<()>; - fn create_dir(&self, path: &Path) -> Result<()>; - fn create_dir_all(&self, path: &Path) -> Result<()>; - fn write(&self, path: &Path, contents: &[u8]) -> Result<()>; - fn write_chunk(&self, path: &Path, contents: &[u8], pos: OpenSeekFrom) -> Result<()>; -} - impl DynFilesystem for Filesystem<'_, S> { fn total_blocks(&self) -> usize { Filesystem::total_blocks(self) @@ -221,73 +139,6 @@ impl DynFilesystem for Filesystem<'_, S> { } } -impl dyn DynFilesystem + '_ { - pub fn read(&self, path: &Path) -> Result> { - let mut contents = Vec::new(); - self.open_file_and_then(path, &mut |file| { - file.read_to_end(&mut contents)?; - Ok(()) - })?; - Ok(contents) - } - - pub fn read_chunk( - &self, - path: &Path, - pos: OpenSeekFrom, - ) -> Result<(Vec, usize)> { - let mut contents = Vec::new(); - let file_len = self.open_file_and_then(path, &mut |file| { - file.seek(pos.into())?; - let read_n = file.read(&mut contents)?; - contents.truncate(read_n); - file.len() - })?; - Ok((contents, file_len)) - } - - pub fn create_file_and_then(&self, path: &Path, f: FileCallback<'_, R>) -> Result { - let mut result = Err(Error::IO); - self.create_file_and_then_unit(path, &mut |file| { - result = Ok(f(file)?); - Ok(()) - })?; - result - } - - pub fn open_file_and_then(&self, path: &Path, f: FileCallback<'_, R>) -> Result { - let mut result = Err(Error::IO); - self.open_file_and_then_unit(path, &mut |file| { - result = Ok(f(file)?); - Ok(()) - })?; - result - } - - pub fn open_file_with_flags_and_then( - &self, - flags: FileOpenFlags, - path: &Path, - f: FileCallback<'_, R>, - ) -> Result { - let mut result = Err(Error::IO); - self.open_file_with_flags_and_then_unit(flags, path, &mut |file| { - result = Ok(f(file)?); - Ok(()) - })?; - result - } - - pub fn read_dir_and_then(&self, path: &Path, f: DirEntriesCallback<'_, R>) -> Result { - let mut result = Err(Error::IO); - self.read_dir_and_then_unit(path, &mut |entries| { - result = Ok(f(entries)?); - Ok(()) - })?; - result - } -} - /// Object-safe trait for [`Storage`][]. /// /// It contains these additional methods from [`Filesystem`][]: diff --git a/src/tests.rs b/src/tests.rs index 8a63139af..f97a4a9ca 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -5,6 +5,7 @@ use crate::{ driver, fs::{Attribute, File, Filesystem}, io::{Error, OpenSeekFrom, Read, Result, SeekFrom}, + path, }; ram_storage!(