Skip to content
Open
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
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,22 @@ repository.workspace = true
[package.metadata.docs.rs]
all-features = true

[[example]]
name = "list"
required-features = ["alloc", "littlefs2-core/heapless07"]

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

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

[features]
Expand Down
95 changes: 74 additions & 21 deletions examples/list.rs
Original file line number Diff line number Diff line change
@@ -1,60 +1,91 @@
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>,
#[arg(short, long)]
show_hex: bool,
}

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

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

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

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

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

let available_blocks = fs.available_blocks().unwrap();
println!("expected_len: {expected_len}");
println!("actual_len: {actual_len}");
println!("available_blocks: {available_blocks}");
println!();

let path = PathBuf::new();
list(&fs, &path);
list(&fs, &path, args.show_hex);
}

fn list(fs: &dyn DynFilesystem, path: &Path) {
fn list(fs: &dyn DynFilesystem, path: &Path, show_hex: bool) {
fs.read_dir_and_then(path, &mut |iter| {
for entry in iter {
let entry = entry.unwrap();
match entry.file_type() {
FileType::File => println!("F {}", entry.path()),
FileType::File => {
println!("F {}", entry.path());
if show_hex {
let bytes: heapless::Vec<u8, 4096> = fs.read(entry.path()).unwrap();
println!(" {}", hex::encode_upper(&bytes));
}
}
FileType::Dir => match entry.file_name().as_str() {
"." => (),
".." => (),
_ => {
println!("D {}", entry.path());
list(fs, entry.path());
list(fs, entry.path(), show_hex);
}
},
}
Expand All @@ -67,16 +98,38 @@ fn list(fs: &dyn DynFilesystem, path: &Path) {
struct FileStorage {
file: File,
len: usize,
read_size: usize,
write_size: usize,
block_size: usize,
block_count: usize,
cache_size: usize,
lookahead_size: usize,
}

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

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

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

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

fn read(&mut self, off: usize, buf: &mut [u8]) -> Result<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
122 changes: 101 additions & 21 deletions src/driver.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,90 @@
//! The `Storage`, `Read`, `Write` and `Seek` driver.
#![allow(non_camel_case_types)]

use generic_array::ArrayLength;
use crate::io::Error;

use crate::io::Result;
mod private {
pub struct NotEnoughCapacity;
pub trait Sealed {
/// Returns a buffer of bytes initialized and valid. If [`set_len`]() was called previously successfully,
/// its last call defines the minimum number of valid bytes
fn as_ptr(&self) -> *const u8;
/// Returns a buffer of bytes initialized and valid. If [`set_len`]() was called previously successfully,
/// its last call defines the minimum number of valid bytes
fn as_mut_ptr(&mut self) -> *mut u8;

/// Current lenght, set by the last call to [`set_len`](Buffer::set_len)
fn current_len(&self) -> usize;

/// Atempts to set the length of the buffer to `len`
///
/// If succeeded, the buffer obtained through the pointer operation **must** be of at least `len` bytes
fn set_len(&mut self, len: usize) -> Result<(), NotEnoughCapacity>;

// We could use a `Default` trait bound but it's not implemented for all array sizes
fn empty() -> Self;
}
}

pub(crate) use private::Sealed;

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

impl<const N: usize> private::Sealed for [u8; N] {
fn as_ptr(&self) -> *const u8 {
<[u8]>::as_ptr(self)
}

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

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

fn set_len(&mut self, len: usize) -> Result<(), private::NotEnoughCapacity> {
if len > N {
Err(private::NotEnoughCapacity)
} else {
Ok(())
}
}

fn empty() -> Self {
[0; N]
}
}

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

#[cfg(feature = "alloc")]
impl private::Sealed for alloc::vec::Vec<u8> {
fn as_ptr(&self) -> *const u8 {
<[u8]>::as_ptr(self)
}

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

fn current_len(&self) -> usize {
self.len()
}

fn set_len(&mut self, len: usize) -> Result<(), private::NotEnoughCapacity> {
self.resize(len, 0);
Ok(())
}

fn empty() -> Self {
Self::new()
}
}

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

/// Users of this library provide a "storage driver" by implementing this trait.
///
Expand All @@ -12,44 +93,43 @@ use crate::io::Result;
/// Do note that due to caches, files still must be synched. And unfortunately,
/// this can't be automatically done in `drop`, since it needs mut refs to both
/// filesystem and storage.
///
/// The `*_SIZE` types must be `generic_array::typenume::consts` such as `U256`.
///
/// Why? Currently, associated constants can not be used (as constants...) to define
/// arrays. This "will be fixed" as part of const generics.
/// Once that's done, we can get rid of `generic-array`s, and replace the
/// `*_SIZE` types with `usize`s.
pub trait Storage {
// /// Error type for user-provided read/write/erase methods
// type Error = usize;

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

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

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

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

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

/// littlefs uses a read cache, a write cache, and one cache per per file.
/// Must be a multiple of `READ_SIZE` and `WRITE_SIZE`.
/// Must be a factor of `BLOCK_SIZE`.
type CACHE_SIZE: ArrayLength<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 Expand Up @@ -83,13 +163,13 @@ pub trait Storage {

/// Read data from the storage device.
/// Guaranteed to be called only with bufs of length a multiple of READ_SIZE.
fn read(&mut self, off: usize, buf: &mut [u8]) -> Result<usize>;
fn read(&mut self, off: usize, buf: &mut [u8]) -> Result<usize, Error>;
/// Write data to the storage device.
/// Guaranteed to be called only with bufs of length a multiple of WRITE_SIZE.
fn write(&mut self, off: usize, data: &[u8]) -> Result<usize>;
fn write(&mut self, off: usize, data: &[u8]) -> Result<usize, Error>;
/// Erase data from the storage device.
/// Guaranteed to be called only with bufs of length a multiple of BLOCK_SIZE.
fn erase(&mut self, off: usize, len: usize) -> Result<usize>;
fn erase(&mut self, off: usize, len: usize) -> Result<usize, Error>;
// /// Synchronize writes to the storage device.
// fn sync(&mut self) -> Result<usize>;
}
Loading