Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove need for generic array and typenum constants #101

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,20 @@ repository.workspace = true
[package.metadata.docs.rs]
all-features = true

[[example]]
name = "list"
required-features = ["alloc"]

[dependencies]
delog = "0.1.0"
generic-array = "0.14"
heapless = "0.7"
littlefs2-core = { version = "0.1", path = "core" }
littlefs2-sys = { version = "0.3.1", features = ["multiversion"] }

[dev-dependencies]
ssmarshal = "1"
serde = { version = "1.0", default-features = false, features = ["derive"] }
clap = { version = "4.5.31", features = ["derive"] }
# trybuild = "1"

[features]
Expand Down
78 changes: 62 additions & 16 deletions examples/list.rs
Original file line number Diff line number Diff line change
@@ -1,40 +1,64 @@
use std::{
env,
fs::File,
io::{Read as _, Seek as _, SeekFrom},
};

use littlefs2::{
consts::{U1, U512},
driver::Storage,
fs::{Allocation, FileType, Filesystem},
io::{Error, Result},
object_safe::DynFilesystem,
path::{Path, PathBuf},
};

const BLOCK_COUNT: usize = 128;
const BLOCK_SIZE: usize = 512;
use clap::Parser;

#[derive(Parser)]
struct Args {
path: String,
block_size: usize,
#[arg(short, long)]
write_size: Option<usize>,
#[arg(short, long)]
read_size: Option<usize>,
#[arg(short, long)]
cache_size: Option<usize>,
#[arg(short, long)]
lookahead_size: Option<usize>,
#[arg(short, long)]
block_count: Option<usize>,
}

const BLOCK_COUNT: usize = 288;
const BLOCK_SIZE: usize = 256;

fn main() {
let path = env::args().nth(1).expect("missing argument");
let file = File::open(&path).expect("failed to open file");
let args = Args::parse();
let file = File::open(&args.path).expect("failed to open file");
let metadata = file.metadata().expect("failed to query metadata");

let expected_len = BLOCK_COUNT * BLOCK_SIZE;
let actual_len = usize::try_from(metadata.len()).unwrap();

assert_eq!(actual_len % BLOCK_COUNT, 0);
if let Some(block_count) = args.block_count {
assert_eq!(actual_len, args.block_size * block_count);
}
assert_eq!(actual_len % args.block_size, 0);
let block_count = actual_len / args.block_size;

let mut s = FileStorage {
file,
len: actual_len,
read_size: args.read_size.unwrap_or(args.block_size),
write_size: args.write_size.unwrap_or(args.block_size),
cache_size: args.cache_size.unwrap_or(args.block_size),
lookahead_size: args.lookahead_size.unwrap_or(1),
block_count,
block_size: args.block_size,
};
let mut alloc = Allocation::new();
let mut alloc = Allocation::new(&s);
let fs = Filesystem::mount(&mut alloc, &mut s).expect("failed to mount filesystem");

let available_blocks = fs.available_blocks().unwrap();
println!("expected_len: {expected_len}");
println!("actual_len: {actual_len}");
println!("available_blocks: {available_blocks}");
println!();
Expand Down Expand Up @@ -67,16 +91,38 @@ fn list(fs: &dyn DynFilesystem, path: &Path) {
struct FileStorage {
file: File,
len: usize,
read_size: usize,
write_size: usize,
block_size: usize,
block_count: usize,
cache_size: usize,
lookahead_size: usize,
}

impl Storage for FileStorage {
type CACHE_SIZE = U512;
type LOOKAHEAD_SIZE = U1;
type CACHE_BUFFER = Vec<u8>;
type LOOKAHEAD_BUFFER = Vec<u8>;

fn read_size(&self) -> usize {
self.read_size
}
fn write_size(&self) -> usize {
self.write_size
}
fn block_size(&self) -> usize {
self.block_size
}
fn block_count(&self) -> usize {
self.block_count
}

const READ_SIZE: usize = 16;
const WRITE_SIZE: usize = 512;
const BLOCK_SIZE: usize = BLOCK_SIZE;
const BLOCK_COUNT: usize = BLOCK_COUNT;
fn cache_size(&self) -> usize {
self.cache_size
}

fn lookahead_size(&self) -> usize {
self.lookahead_size
}

fn read(&mut self, off: usize, buf: &mut [u8]) -> Result<usize> {
assert!(off + buf.len() <= BLOCK_SIZE * BLOCK_COUNT);
Expand Down
3 changes: 0 additions & 3 deletions src/consts.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
#![allow(non_camel_case_types)]

/// Re-export of `typenum::consts`.
pub use generic_array::typenum::consts::*;

pub const PATH_MAX: usize = littlefs2_core::PathBuf::MAX_SIZE;
pub const PATH_MAX_PLUS_ONE: usize = littlefs2_core::PathBuf::MAX_SIZE_PLUS_ONE;
pub const FILENAME_MAX_PLUS_ONE: u32 = 255 + 1;
Expand Down
117 changes: 99 additions & 18 deletions src/driver.rs
Original file line number Diff line number Diff line change
@@ -1,55 +1,136 @@
//! The `Storage`, `Read`, `Write` and `Seek` driver.
#![allow(non_camel_case_types)]

use generic_array::ArrayLength;

use crate::io::Result;

mod private {
pub trait Sealed {}
}

/// Safety: implemented only by `[u8; N]` and `Vec<u8>` if the alloc feature is enabled
pub unsafe trait Buffer: private::Sealed {
Copy link
Member

Choose a reason for hiding this comment

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

AFAIS the Filesystem implementation only uses with_capacity and the as_ptr/as_mut_ptr methods. Why do we need resizing as part of the trait?

/// The maximum capacity of the buffer type.
/// Can be [`usize::Max`]()
const MAX_CAPACITY: usize;

/// Returns a buffer of bytes initialized and valid. If [`set_capacity`]() was called previously,
/// its last call defines the minimum number of valid bytes
fn as_ptr(&self) -> *const u8;
/// Returns a buffer of bytes initialized and valid. If [`set_capacity`]() was called previously,
/// its last call defines the minimum number of valid bytes
fn as_mut_ptr(&mut self) -> *mut u8;

/// Current capacity, set by the last call to [`set_capacity`](Buffer::set_capacity)
/// or at initialization through [`with_capacity`](Buffer::with_capacity)
fn current_capacity(&self) -> usize;

/// Can panic if `capacity` > `Self::MAX_CAPACITY`
fn set_capacity(&mut self, capacity: usize);

/// Can panic if `capacity` > `Self::MAX_CAPACITY`
fn with_capacity(capacity: usize) -> Self;
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn’t this be fallible so that invalid configuration leads to an error instead of a panic?

Copy link
Contributor Author

@sosthene-nitrokey sosthene-nitrokey Mar 10, 2025

Choose a reason for hiding this comment

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

I changed this to make the function private as part of Sealed, and Buffer is "publicly empty", that way there is no risk of public use of this.

I'm also documenting the fact that this can panic from the definitions in the Storage trait.

Copy link
Contributor Author

@sosthene-nitrokey sosthene-nitrokey Mar 11, 2025

Choose a reason for hiding this comment

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

Made it fallible.

}

impl<const N: usize> private::Sealed for [u8; N] {}

unsafe impl<const N: usize> Buffer for [u8; N] {
const MAX_CAPACITY: usize = N;

fn as_ptr(&self) -> *const u8 {
<[u8]>::as_ptr(self)
}

fn as_mut_ptr(&mut self) -> *mut u8 {
<[u8]>::as_mut_ptr(self)
}

fn current_capacity(&self) -> usize {
N
}

fn set_capacity(&mut self, _capacity: usize) {
// noop, fixed capacity
}
fn with_capacity(capacity: usize) -> Self {
assert!(capacity <= N);
[0; N]
}
}

#[cfg(feature = "alloc")]
impl private::Sealed for alloc::vec::Vec<u8> {}

#[cfg(feature = "alloc")]
unsafe impl Buffer for alloc::vec::Vec<u8> {
const MAX_CAPACITY: usize = usize::MAX;

fn as_ptr(&self) -> *const u8 {
<[u8]>::as_ptr(self)
}

fn as_mut_ptr(&mut self) -> *mut u8 {
<[u8]>::as_mut_ptr(self)
}

fn current_capacity(&self) -> usize {
self.capacity()
}

fn set_capacity(&mut self, capacity: usize) {
self.resize(capacity, 0)
}

fn with_capacity(capacity: usize) -> Self {
let mut this = alloc::vec::Vec::with_capacity(capacity);
this.set_capacity(capacity);
this
}
}

/// Users of this library provide a "storage driver" by implementing this trait.
///
/// The `write` method is assumed to be synchronized to storage immediately.
/// littlefs provides more flexibility - if required, this could also be exposed.
/// Do note that due to caches, files still must be synched. And unfortunately,
/// this can't be automatically done in `drop`, since it needs mut refs to both
/// filesystem and storage.
///
/// The `*_SIZE` types must be `generic_array::typenume::consts` such as `U256`.
///
/// Why? Currently, associated constants can not be used (as constants...) to define
/// arrays. This "will be fixed" as part of const generics.
/// Once that's done, we can get rid of `generic-array`s, and replace the
/// `*_SIZE` types with `usize`s.
pub trait Storage {
// /// Error type for user-provided read/write/erase methods
// type Error = usize;

/// Minimum size of block read in bytes. Not in superblock
const READ_SIZE: usize;
fn read_size(&self) -> usize;

/// Minimum size of block write in bytes. Not in superblock
const WRITE_SIZE: usize;
fn write_size(&self) -> usize;

/// Size of an erasable block in bytes, as unsigned typenum.
/// Must be a multiple of both `READ_SIZE` and `WRITE_SIZE`.
/// [At least 128](https://github.com/littlefs-project/littlefs/issues/264#issuecomment-519963153). Stored in superblock.
const BLOCK_SIZE: usize;
fn block_size(&self) -> usize;

/// Number of erasable blocks.
/// Hence storage capacity is `BLOCK_COUNT * BLOCK_SIZE`
const BLOCK_COUNT: usize;
fn block_count(&self) -> usize;

/// Suggested values are 100-1000, higher is more performant but
/// less wear-leveled. Default of -1 disables wear-leveling.
/// Value zero is invalid, must be positive or -1.
const BLOCK_CYCLES: isize = -1;
fn block_cycles(&self) -> isize {
-1
}

/// littlefs uses a read cache, a write cache, and one cache per per file.
/// Must be a multiple of `READ_SIZE` and `WRITE_SIZE`.
/// Must be a factor of `BLOCK_SIZE`.
type CACHE_SIZE: ArrayLength<u8>;
type CACHE_BUFFER: Buffer;

/// Must be a multiple of `read_size` and `write_size`.
/// Must be a factor of `block_size`.
fn cache_size(&self) -> usize;

/// Lookahead buffer used by littlefs
type LOOKAHEAD_BUFFER: Buffer;
/// Size of the lookahead buffer used by littlefs, measured in multiples of 8 bytes.
type LOOKAHEAD_SIZE: ArrayLength<u64>;
fn lookahead_size(&self) -> usize;

///// Maximum length of a filename plus one. Stored in superblock.
///// Should default to 255+1, but associated type defaults don't exist currently.
Expand Down
Loading