Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ jobs:

- name: Patch delog
run: |
echo '[patch.crates-io]' >> Cargo.toml
echo 'delog = { version = "0.1.6", git = "https://github.com/LechevSpace/delog.git", rev = "e83f3fd" }' >> Cargo.toml

- name: Build avr
Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ repository.workspace = true
all-features = true

[dependencies]
bitflags = "2.9.0"
delog = "0.1.0"
generic-array = "0.14"
heapless = "0.7"
Expand Down Expand Up @@ -57,3 +58,5 @@ log-error = []
# member `char name[LFS_NAME_MAX+1]`.
# This means that if we change `traits::Storage::FILENAME_MAX_PLUS_ONE`,
# we need to pass this on!
[patch.crates-io]
littlefs2-sys = { git = "https://github.com/trussed-dev/littlefs2-sys", rev = "v0.3.1-nitrokey.1" }
1 change: 1 addition & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ heapless-bytes04 = ["dep:heapless-bytes04"]
heapless07 = ["dep:heapless07"]
heapless08 = ["dep:heapless08"]
serde = ["dep:serde"]
debug-error = []
25 changes: 24 additions & 1 deletion core/src/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,30 @@ impl Error {
/// As a short-term fix, the `Debug` implementation currently always returns a static string.
impl Debug for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Error").finish()
#[cfg(not(feature = "debug-error"))]
{
f.debug_struct("Error").finish()
}
#[cfg(feature = "debug-error")]
{
match self {
&Self::IO => f.write_str("IO"),
&Self::CORRUPTION => f.write_str("CORRUPTION"),
&Self::NO_SUCH_ENTRY => f.write_str("NO_SUCH_ENTRY"),
&Self::ENTRY_ALREADY_EXISTED => f.write_str("ENTRY_ALREADY_EXISTED"),
&Self::PATH_NOT_DIR => f.write_str("PATH_NOT_DIR"),
&Self::PATH_IS_DIR => f.write_str("PATH_IS_DIR"),
&Self::DIR_NOT_EMPTY => f.write_str("DIR_NOT_EMPTY"),
&Self::BAD_FILE_DESCRIPTOR => f.write_str("BAD_FILE_DESCRIPTOR"),
&Self::FILE_TOO_BIG => f.write_str("FILE_TOO_BIG"),
&Self::INVALID => f.write_str("INVALID"),
&Self::NO_SPACE => f.write_str("NO_SPACE"),
&Self::NO_MEMORY => f.write_str("NO_MEMORY"),
&Self::NO_ATTRIBUTE => f.write_str("NO_ATTRIBUTE"),
&Self::FILENAME_TOO_LONG => f.write_str("FILENAME_TOO_LONG"),
other => f.debug_tuple("Error").field(&other.code).finish(),
}
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion core/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ impl Path {
}

pub fn parent(&self) -> Option<PathBuf> {
let rk_path_bytes = self.as_ref()[..].as_bytes();
let rk_path_bytes = self.as_ref().as_bytes();
match rk_path_bytes.iter().rposition(|x| *x == b'/') {
Some(0) if rk_path_bytes.len() != 1 => Some(path!("/").into()),
Some(slash_index) => {
Expand Down
80 changes: 67 additions & 13 deletions src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,25 @@ impl<Storage: driver::Storage> Default for Allocation<Storage> {
Self::new()
}
}

#[derive(Default, Clone, Debug)]
#[non_exhaustive]
pub struct Config {
pub mount_flags: MountFlags,
}

bitflags::bitflags! {
#[derive(Default, Clone, Copy,Debug)]
pub struct MountFlags: u32 {
const DISABLE_BLOCK_COUNT_CHECK = ll::lfs_fs_flags_LFS_CFG_DISABLE_BLOCK_COUNT_CHECK as _;
}
}

impl<Storage: driver::Storage> Allocation<Storage> {
pub fn new() -> Allocation<Storage> {
pub fn new() -> Self {
Self::with_config(Config::default())
}
pub fn with_config(config: Config) -> Allocation<Storage> {
let read_size: u32 = Storage::READ_SIZE as _;
let write_size: u32 = Storage::WRITE_SIZE as _;
let block_size: u32 = Storage::BLOCK_SIZE as _;
Expand Down Expand Up @@ -163,6 +180,7 @@ impl<Storage: driver::Storage> Allocation<Storage> {
metadata_max: 0,
inline_max: 0,
disk_version: DISK_VERSION.into(),
flags: config.mount_flags.bits(),
};

Self {
Expand Down Expand Up @@ -208,7 +226,11 @@ impl<Storage: driver::Storage> Filesystem<'_, Storage> {
}

pub fn format(storage: &mut Storage) -> Result<()> {
let alloc = &mut Allocation::new();
Self::format_with_config(storage, Config::default())
}

pub fn format_with_config(storage: &mut Storage, config: Config) -> Result<()> {
let alloc = &mut Allocation::with_config(config);
let fs = Filesystem::new(alloc, storage);
let mut alloc = fs.alloc.borrow_mut();
let return_code = unsafe { ll::lfs_format(&mut alloc.state, &alloc.config) };
Expand All @@ -217,7 +239,12 @@ impl<Storage: driver::Storage> Filesystem<'_, Storage> {

// TODO: check if this is equivalent to `is_formatted`.
pub fn is_mountable(storage: &mut Storage) -> bool {
let alloc = &mut Allocation::new();
Self::is_mountable_with_config(storage, Config::default())
}

// TODO: check if this is equivalent to `is_formatted`.
pub fn is_mountable_with_config(storage: &mut Storage, config: Config) -> bool {
let alloc = &mut Allocation::with_config(config);
Filesystem::mount(alloc, storage).is_ok()
}

Expand All @@ -233,11 +260,34 @@ impl<Storage: driver::Storage> Filesystem<'_, Storage> {
storage: &mut Storage,
f: impl FnOnce(&Filesystem<'_, Storage>) -> Result<R>,
) -> Result<R> {
let mut alloc = Allocation::new();
Self::mount_and_then_with_config(storage, Config::default(), f)
}

/// This API avoids the need for using `Allocation`.
pub fn mount_and_then_with_config<R>(
storage: &mut Storage,
config: Config,
f: impl FnOnce(&Filesystem<'_, Storage>) -> Result<R>,
) -> Result<R> {
let mut alloc = Allocation::with_config(config);
let fs = Filesystem::mount(&mut alloc, storage)?;
f(&fs)
}

pub fn shrink(&self, block_count: usize) -> Result<()> {
let mut alloc = self.alloc.borrow_mut();
let return_code = unsafe { ll::lfs_fs_shrink(&mut alloc.state, block_count as _) };
drop(alloc);
result_from((), return_code)
}

pub fn grow(&self, block_count: usize) -> Result<()> {
let mut alloc = self.alloc.borrow_mut();
let return_code = unsafe { ll::lfs_fs_grow(&mut alloc.state, block_count as _) };
drop(alloc);
result_from((), return_code)
}

/// Total number of blocks in the filesystem
pub fn total_blocks(&self) -> usize {
Storage::BLOCK_COUNT
Expand Down Expand Up @@ -1054,18 +1104,27 @@ impl<'a, Storage: driver::Storage> Filesystem<'a, Storage> {
Ok(fs)
}

fn set_alloc_config(alloc: &mut Allocation<Storage>, storage: &mut Storage) {
alloc.config.context = storage as *mut _ as *mut c_void;
alloc.config.read_buffer = alloc.cache.read.get() as *mut c_void;
alloc.config.prog_buffer = alloc.cache.write.get() as *mut c_void;
alloc.config.lookahead_buffer = alloc.cache.lookahead.get() as *mut c_void;
}

/// Mount the filesystem or, if that fails, call `f` with the mount error and the storage and then try again.
pub fn mount_or_else<F>(
alloc: &'a mut Allocation<Storage>,
storage: &'a mut Storage,
f: F,
) -> Result<Self>
where
F: FnOnce(Error, &mut Storage) -> Result<()>,
F: FnOnce(Error, &mut Storage, &mut Allocation<Storage>) -> Result<()>,
{
let fs = Self::new(alloc, storage);
let mut fs = Self::new(alloc, storage);
if let Err(err) = fs.raw_mount() {
f(err, fs.storage)?;
let alloc = fs.alloc.get_mut();
f(err, fs.storage, alloc)?;
Self::set_alloc_config(alloc, fs.storage);
fs.raw_mount()?;
}
Ok(fs)
Expand All @@ -1080,12 +1139,7 @@ impl<'a, Storage: driver::Storage> Filesystem<'a, Storage> {

// Not public, user should use `mount`, possibly after `format`
fn new(alloc: &'a mut Allocation<Storage>, storage: &'a mut Storage) -> Self {
alloc.config.context = storage as *mut _ as *mut c_void;

alloc.config.read_buffer = alloc.cache.read.get() as *mut c_void;
alloc.config.prog_buffer = alloc.cache.write.get() as *mut c_void;
alloc.config.lookahead_buffer = alloc.cache.lookahead.get() as *mut c_void;

Self::set_alloc_config(alloc, storage);
Filesystem {
alloc: RefCell::new(alloc),
storage,
Expand Down
107 changes: 106 additions & 1 deletion src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use core::convert::TryInto;
use generic_array::typenum::consts;
use littlefs2_core::PathBuf;

use crate::{
fs::{Attribute, File, Filesystem},
driver::Storage,
fs::{Allocation, Attribute, File, Filesystem, MountFlags},
io::{Error, OpenSeekFrom, Read, Result, SeekFrom},
path, BACKEND_VERSION, DISK_VERSION,
};
Expand Down Expand Up @@ -35,6 +37,20 @@ ram_storage!(
path_max_plus_one_ty = consts::U256,
);

ram_storage!(
name = LargerRamStorage,
backend = LargerRam,
erase_value = 0xff,
read_size = 20 * 5,
write_size = 20 * 7,
cache_size_ty = consts::U700,
block_size = 20 * 35,
block_count = 64,
lookahead_size_ty = consts::U16,
filename_max_plus_one_ty = consts::U256,
path_max_plus_one_ty = consts::U256,
);

#[test]
fn version() {
assert_eq!((BACKEND_VERSION.major(), BACKEND_VERSION.minor()), (2, 9));
Expand Down Expand Up @@ -521,6 +537,19 @@ fn test_iter_dirs() {
.unwrap();
}

#[test]
fn test_mount_or_else_clobber_alloc() {
let mut backend = Ram::default();
let mut storage = RamStorage::new(&mut backend);
let alloc = &mut Allocation::new();
Filesystem::mount_or_else(alloc, &mut storage, |_, storage, alloc| {
*alloc = Allocation::new();
Filesystem::format(storage).unwrap();
Ok(())
})
.unwrap();
}

// // These are some tests that ensure our type constructions
// // actually do what we intend them to do.
// // Since dev-features cannot be optional, trybuild is not `no_std`,
Expand All @@ -532,3 +561,79 @@ fn test_iter_dirs() {
// t.compile_fail("tests/ui/*-fail.rs");
// t.pass("tests/ui/*-pass.rs");
// }

#[test]
fn shrinking() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I’m a bit paranoid, but what about creating a file and making sure that it can still be read correctly after growing / shrinking?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

The C code already has a lot of such tests.

let backend = &mut Ram::default();
let storage = &mut RamStorage::new(backend);
let alloc = &mut Allocation::new();

Filesystem::format(storage).unwrap();
let fs = Filesystem::mount(alloc, storage).unwrap();
fs.write(path!("some-file"), &[42; 10]).unwrap();
fs.write(path!("some-large-file"), &[42; 1024]).unwrap();

assert_eq!(fs.read::<10>(path!("some-file")).unwrap(), &[42; 10]);
assert_eq!(
fs.read::<1024>(path!("some-large-file")).unwrap(),
&[42; 1024]
);

let larger_backend = &mut LargerRam::default();
larger_backend.buf[..backend.buf.len()].copy_from_slice(&backend.buf);
let larger_storage = &mut LargerRamStorage::new(larger_backend);
let larger_alloc = &mut Allocation::new();
assert!(matches!(
Filesystem::mount(larger_alloc, larger_storage),
Err(Error::INVALID)
));

let larger_alloc = &mut Allocation::with_config(crate::fs::Config {
mount_flags: MountFlags::DISABLE_BLOCK_COUNT_CHECK,
});

let fs = Filesystem::mount(larger_alloc, larger_storage).unwrap();
assert_eq!(fs.read::<10>(path!("some-file")).unwrap(), &[42; 10]);
assert_eq!(
fs.read::<1024>(path!("some-large-file")).unwrap(),
&[42; 1024]
);

fs.grow(LargerRamStorage::BLOCK_COUNT).unwrap();
assert_eq!(fs.read::<10>(path!("some-file")).unwrap(), &[42; 10]);
assert_eq!(
fs.read::<1024>(path!("some-large-file")).unwrap(),
&[42; 1024]
);

fs.shrink(RamStorage::BLOCK_COUNT).unwrap();
assert_eq!(fs.read::<10>(path!("some-file")).unwrap(), &[42; 10]);
assert_eq!(
fs.read::<1024>(path!("some-large-file")).unwrap(),
&[42; 1024]
);
}

#[test]
fn shrinking_full() {
let larger_backend = &mut LargerRam::default();
let larger_storage = &mut LargerRamStorage::new(larger_backend);
let larger_alloc = &mut Allocation::new();
Filesystem::format(larger_storage).unwrap();
let fs = Filesystem::mount(larger_alloc, larger_storage).unwrap();

for i in 0.. {
let path = format!("file-{i}");
let contents = &[0; 1024];
match fs.write(&PathBuf::try_from(&*path).unwrap(), contents) {
Ok(_) => continue,
Err(Error::NO_SPACE) => break,
Err(err) => panic!("{err:?}"),
}
}

assert!(matches!(
fs.shrink(RamStorage::BLOCK_COUNT),
Err(Error::DIR_NOT_EMPTY)
))
}