use core::ffi::{c_int, c_void};
use core::ptr::addr_of;
use core::ptr::addr_of_mut;
use core::{
cell::{RefCell, UnsafeCell},
mem, slice,
};
use generic_array::typenum::marker_traits::Unsigned;
use littlefs2_sys as ll;
#[allow(deprecated)]
pub type Bytes<SIZE> = generic_array::GenericArray<u8, SIZE>;
pub use littlefs2_core::{Attribute, DirEntry, FileOpenFlags, FileType, Metadata};
use crate::{
driver,
io::{self, Error, OpenSeekFrom, Result},
path::{Path, PathBuf},
DISK_VERSION,
};
fn error_code_from<T>(result: Result<T>) -> ll::lfs_error {
result
.map(|_| ll::lfs_error_LFS_ERR_OK)
.unwrap_or_else(From::from)
}
fn result_from<T>(return_value: T, error_code: ll::lfs_error) -> Result<T> {
if let Some(error) = Error::new(error_code) {
Err(error)
} else {
Ok(return_value)
}
}
pub fn u32_result(return_value: i32) -> Result<u32> {
u32::try_from(return_value).map_err(|_| {
let error_code = c_int::try_from(return_value).unwrap_or(c_int::MIN);
Error::new(error_code).unwrap()
})
}
struct Cache<Storage: driver::Storage> {
read: UnsafeCell<Bytes<Storage::CACHE_SIZE>>,
write: UnsafeCell<Bytes<Storage::CACHE_SIZE>>,
#[allow(deprecated)]
lookahead: UnsafeCell<generic_array::GenericArray<u64, Storage::LOOKAHEAD_SIZE>>,
}
impl<S: driver::Storage> Cache<S> {
pub fn new() -> Self {
Self {
read: Default::default(),
write: Default::default(),
lookahead: Default::default(),
}
}
}
impl<S: driver::Storage> Default for Cache<S> {
fn default() -> Self {
Self::new()
}
}
pub struct Allocation<Storage: driver::Storage> {
cache: Cache<Storage>,
config: ll::lfs_config,
state: ll::lfs_t,
}
impl<Storage: driver::Storage> Default for Allocation<Storage> {
fn default() -> Self {
Self::new()
}
}
#[derive(Default, Clone, Debug)]
#[non_exhaustive]
pub struct Config {
#[cfg(feature = "unstable-littlefs-patched")]
pub mount_flags: MountFlags,
}
#[cfg(feature = "unstable-littlefs-patched")]
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() -> Self {
Self::with_config(Config::default())
}
pub fn with_config(config: Config) -> Allocation<Storage> {
let _ = config;
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 = <Storage as driver::Storage>::CACHE_SIZE::U32;
let lookahead_size: u32 = 8 * <Storage as driver::Storage>::LOOKAHEAD_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);
debug_assert!(block_size >= 128);
debug_assert!(cache_size > 0);
debug_assert!(lookahead_size > 0);
debug_assert!(cache_size.is_multiple_of(read_size));
debug_assert!(cache_size.is_multiple_of(write_size));
debug_assert!(block_size.is_multiple_of(cache_size));
let cache = Cache::new();
let filename_max_plus_one: u32 = crate::consts::FILENAME_MAX_PLUS_ONE;
debug_assert!(filename_max_plus_one > 1);
debug_assert!(filename_max_plus_one <= 1_022 + 1);
debug_assert!(filename_max_plus_one == 255 + 1);
let path_max_plus_one: u32 = crate::consts::PATH_MAX_PLUS_ONE as _;
debug_assert!(path_max_plus_one >= filename_max_plus_one);
let file_max = crate::consts::FILEBYTES_MAX;
assert!(file_max > 0);
assert!(file_max <= 2_147_483_647);
assert!(file_max == 2_147_483_647);
let attr_max: u32 = crate::consts::ATTRBYTES_MAX;
assert!(attr_max > 0);
assert!(attr_max <= 1_022);
assert!(attr_max == 1_022);
let config = ll::lfs_config {
context: core::ptr::null_mut(),
read: Some(<Filesystem<'_, Storage>>::lfs_config_read),
prog: Some(<Filesystem<'_, Storage>>::lfs_config_prog),
erase: Some(<Filesystem<'_, Storage>>::lfs_config_erase),
sync: Some(<Filesystem<'_, Storage>>::lfs_config_sync),
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,
compact_thresh: 0,
metadata_max: 0,
inline_max: 0,
disk_version: DISK_VERSION.into(),
#[cfg(feature = "unstable-littlefs-patched")]
flags: config.mount_flags.bits(),
};
Self {
cache,
state: unsafe { mem::MaybeUninit::zeroed().assume_init() },
config,
}
}
}
pub struct Filesystem<'a, Storage: driver::Storage> {
alloc: RefCell<&'a mut Allocation<Storage>>,
storage: &'a mut Storage,
}
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)
}
struct RemoveDirAllProgress {
files_removed: usize,
skipped_any: bool,
}
impl<Storage: driver::Storage> Filesystem<'_, Storage> {
pub fn allocate() -> Allocation<Storage> {
Allocation::new()
}
pub fn format(storage: &mut Storage) -> Result<()> {
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) };
result_from((), return_code)
}
pub fn is_mountable(storage: &mut Storage) -> bool {
Self::is_mountable_with_config(storage, Config::default())
}
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()
}
pub unsafe fn borrow_storage_mut(&mut self) -> &mut Storage {
self.storage
}
pub fn mount_and_then<R>(
storage: &mut Storage,
f: impl FnOnce(&Filesystem<'_, Storage>) -> Result<R>,
) -> Result<R> {
Self::mount_and_then_with_config(storage, Config::default(), f)
}
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)
}
#[cfg(feature = "unstable-littlefs-patched")]
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)
}
pub fn total_blocks(&self) -> usize {
Storage::BLOCK_COUNT
}
pub fn total_space(&self) -> usize {
Storage::BLOCK_COUNT * Storage::BLOCK_SIZE
}
pub fn available_blocks(&self) -> Result<usize> {
let return_code = unsafe { ll::lfs_fs_size(&mut self.alloc.borrow_mut().state) };
u32_result(return_code)
.map(|blocks| usize::try_from(blocks).unwrap_or(usize::MAX))
.map(|blocks| self.total_blocks().saturating_sub(blocks))
}
pub fn available_space(&self) -> Result<usize> {
self.available_blocks()
.map(|blocks| blocks * Storage::BLOCK_SIZE)
}
pub fn remove(&self, path: &Path) -> Result<()> {
let return_code =
unsafe { ll::lfs_remove(&mut self.alloc.borrow_mut().state, path.as_ptr()) };
result_from((), return_code)
}
pub fn remove_dir(&self, path: &Path) -> Result<()> {
self.remove(path)
}
pub fn remove_dir_all(&self, path: &Path) -> Result<()> {
self.remove_dir_all_where(path, &|_| true).map(|_| ())
}
fn remove_dir_all_where_inner<P>(
&self,
path: &Path,
predicate: &P,
) -> Result<RemoveDirAllProgress>
where
P: Fn(&DirEntry) -> bool,
{
use crate::path;
if !self.exists(path) {
debug_now!("no such directory {}, early return", path);
return Ok(RemoveDirAllProgress {
files_removed: 0,
skipped_any: false,
});
}
let mut skipped_any = false;
let mut files_removed = 0;
debug_now!("starting to remove_dir_all_where in {}", path);
self.read_dir_and_then(path, |read_dir| {
for entry in read_dir.skip(2) {
let entry = entry?;
if entry.file_type().is_file() {
if predicate(&entry) {
debug_now!("removing file {}", &entry.path());
self.remove(entry.path())?;
debug_now!("...done");
files_removed += 1;
} else {
debug_now!("skipping file {}", &entry.path());
skipped_any = true;
}
}
if entry.file_type().is_dir() {
debug_now!("recursing into directory {}", &entry.path());
let progress = self.remove_dir_all_where_inner(entry.path(), predicate)?;
files_removed += progress.files_removed;
skipped_any |= progress.skipped_any;
debug_now!("...back");
}
}
Ok(())
})?;
if !skipped_any && path != path!("") && path != path!("/") {
debug_now!("removing directory {} too", &path);
self.remove_dir(path)?;
debug_now!("..worked");
}
Ok(RemoveDirAllProgress {
files_removed,
skipped_any,
})
}
pub fn remove_dir_all_where<P>(&self, path: &Path, predicate: &P) -> Result<usize>
where
P: Fn(&DirEntry) -> bool,
{
self.remove_dir_all_where_inner(path, predicate)
.map(|progress| progress.files_removed)
}
pub fn rename(&self, from: &Path, to: &Path) -> Result<()> {
let return_code = unsafe {
ll::lfs_rename(
&mut self.alloc.borrow_mut().state,
from.as_ptr(),
to.as_ptr(),
)
};
result_from((), return_code)
}
pub fn exists(&self, path: &Path) -> bool {
self.metadata(path).is_ok()
}
pub fn metadata(&self, path: &Path) -> Result<Metadata> {
let mut info: ll::lfs_info = unsafe { mem::MaybeUninit::zeroed().assume_init() };
let return_code =
unsafe { ll::lfs_stat(&mut self.alloc.borrow_mut().state, path.as_ptr(), &mut info) };
result_from((), return_code).map(|_| metadata(info))
}
pub fn create_file_and_then<R>(
&self,
path: &Path,
f: impl FnOnce(&File<'_, '_, Storage>) -> Result<R>,
) -> Result<R> {
File::create_and_then(self, path, f)
}
pub fn open_file_and_then<R>(
&self,
path: &Path,
f: impl FnOnce(&File<'_, '_, Storage>) -> Result<R>,
) -> Result<R> {
File::open_and_then(self, path, f)
}
pub fn with_options() -> OpenOptions {
OpenOptions::new()
}
pub fn open_file_with_options_and_then<R>(
&self,
o: impl FnOnce(&mut OpenOptions) -> &OpenOptions,
path: &Path,
f: impl FnOnce(&File<'_, '_, Storage>) -> Result<R>,
) -> Result<R> {
let mut options = OpenOptions::new();
o(&mut options).open_and_then(self, path, f)
}
pub fn attribute<'a>(
&self,
path: &Path,
id: u8,
buffer: &'a mut [u8],
) -> Result<Option<Attribute<'a>>> {
let n = u32::try_from(buffer.len()).unwrap_or(u32::MAX);
let return_code = unsafe {
ll::lfs_getattr(
&mut self.alloc.borrow_mut().state,
path.as_ptr(),
id,
buffer as *mut _ as *mut c_void,
n,
)
};
u32_result(return_code)
.map(|n| {
let total_size = usize::try_from(n).unwrap_or(usize::MAX);
Some(Attribute::new(buffer, total_size))
})
.or_else(|err| {
if err == Error::NO_ATTRIBUTE {
Ok(None)
} else {
Err(err)
}
})
}
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) };
result_from((), return_code)
}
pub fn set_attribute(&self, path: &Path, id: u8, data: &[u8]) -> Result<()> {
let return_code = unsafe {
ll::lfs_setattr(
&mut self.alloc.borrow_mut().state,
path.as_ptr(),
id,
data as *const _ as *const c_void,
u32::try_from(data.len()).unwrap_or(u32::MAX),
)
};
result_from((), return_code)
}
extern "C" fn lfs_config_read(
c: *const ll::lfs_config,
block: ll::lfs_block_t,
off: ll::lfs_off_t,
buffer: *mut c_void,
size: ll::lfs_size_t,
) -> c_int {
let storage = unsafe { &mut *((*c).context as *mut Storage) };
debug_assert!(!c.is_null());
let block_size = Storage::BLOCK_SIZE;
let off = block as usize * block_size + off as usize;
let buf: &mut [u8] = unsafe { slice::from_raw_parts_mut(buffer as *mut u8, size as usize) };
error_code_from(storage.read(off, buf))
}
extern "C" fn lfs_config_prog(
c: *const ll::lfs_config,
block: ll::lfs_block_t,
off: ll::lfs_off_t,
buffer: *const c_void,
size: ll::lfs_size_t,
) -> c_int {
let storage = unsafe { &mut *((*c).context as *mut Storage) };
debug_assert!(!c.is_null());
let block_size = Storage::BLOCK_SIZE;
let off = block as usize * block_size + off as usize;
let buf: &[u8] = unsafe { slice::from_raw_parts(buffer as *const u8, size as usize) };
error_code_from(storage.write(off, buf))
}
extern "C" fn lfs_config_erase(c: *const ll::lfs_config, block: ll::lfs_block_t) -> c_int {
let storage = unsafe { &mut *((*c).context as *mut Storage) };
let off = block as usize * Storage::BLOCK_SIZE;
error_code_from(storage.erase(off, Storage::BLOCK_SIZE))
}
extern "C" fn lfs_config_sync(_c: *const ll::lfs_config) -> c_int {
0
}
}
pub struct FileAllocation<S: driver::Storage> {
cache: UnsafeCell<Bytes<S::CACHE_SIZE>>,
state: ll::lfs_file_t,
config: ll::lfs_file_config,
}
impl<S: driver::Storage> Default for FileAllocation<S> {
fn default() -> Self {
Self::new()
}
}
impl<S: driver::Storage> FileAllocation<S> {
pub fn new() -> Self {
let cache_size: u32 = <S as driver::Storage>::CACHE_SIZE::to_u32();
debug_assert!(cache_size > 0);
unsafe { mem::MaybeUninit::zeroed().assume_init() }
}
}
pub struct File<'a, 'b, S: driver::Storage> {
alloc: RefCell<*mut FileAllocation<S>>,
fs: &'b Filesystem<'a, S>,
}
impl<'a, 'b, Storage: driver::Storage> File<'a, 'b, Storage> {
pub fn allocate() -> FileAllocation<Storage> {
FileAllocation::new()
}
pub fn with_options() -> OpenOptions {
OpenOptions::new()
}
pub unsafe fn open(
fs: &'b Filesystem<'a, Storage>,
alloc: &'b mut FileAllocation<Storage>,
path: &Path,
) -> Result<Self> {
OpenOptions::new().read(true).open(fs, alloc, path)
}
pub fn open_and_then<R>(
fs: &Filesystem<'a, Storage>,
path: &Path,
f: impl FnOnce(&File<'_, '_, Storage>) -> Result<R>,
) -> Result<R> {
OpenOptions::new().read(true).open_and_then(fs, path, f)
}
pub unsafe fn create(
fs: &'b Filesystem<'a, Storage>,
alloc: &'b mut FileAllocation<Storage>,
path: &Path,
) -> Result<Self> {
OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(fs, alloc, path)
}
pub fn create_and_then<R>(
fs: &Filesystem<'a, Storage>,
path: &Path,
f: impl FnOnce(&File<'_, '_, Storage>) -> Result<R>,
) -> Result<R> {
OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open_and_then(fs, path, f)
}
pub unsafe fn borrow_filesystem<'c>(&'c mut self) -> &'c Filesystem<'a, Storage> {
self.fs
}
pub unsafe fn close(self) -> Result<()> {
let return_code = ll::lfs_file_close(
&mut self.fs.alloc.borrow_mut().state,
addr_of_mut!((*(*self.alloc.borrow_mut())).state),
);
result_from((), return_code)
}
pub fn sync(&self) -> Result<()> {
let return_code = unsafe {
ll::lfs_file_sync(
&mut self.fs.alloc.borrow_mut().state,
addr_of_mut!((*(*self.alloc.borrow_mut())).state),
)
};
result_from((), return_code)
}
pub fn len(&self) -> Result<usize> {
let return_code = unsafe {
ll::lfs_file_size(
&mut self.fs.alloc.borrow_mut().state,
addr_of_mut!((*(*self.alloc.borrow_mut())).state),
)
};
u32_result(return_code).map(|n| n as usize)
}
pub fn is_empty(&self) -> Result<bool> {
self.len().map(|l| l == 0)
}
pub fn set_len(&self, size: usize) -> Result<()> {
let return_code = unsafe {
ll::lfs_file_truncate(
&mut self.fs.alloc.borrow_mut().state,
addr_of_mut!((*(*self.alloc.borrow_mut())).state),
size as u32,
)
};
result_from((), return_code)
}
pub fn read_to_end(&self, buf: &mut heapless::VecView<u8>) -> Result<usize> {
let had = buf.len();
buf.resize_default(buf.capacity()).unwrap();
let read = self.read(&mut buf[had..])?;
buf.resize_default(had + read).unwrap();
Ok(read)
}
pub fn read(&self, buf: &mut [u8]) -> Result<usize> {
<Self as io::Read>::read(self, buf)
}
pub fn seek(&self, pos: io::SeekFrom) -> Result<usize> {
<Self as io::Seek>::seek(self, pos)
}
pub fn write(&self, buf: &[u8]) -> Result<usize> {
<Self as io::Write>::write(self, buf)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct OpenOptions(FileOpenFlags);
impl Default for OpenOptions {
fn default() -> Self {
Self::new()
}
}
impl OpenOptions {
pub unsafe fn open<'a, 'b, S: driver::Storage>(
&self,
fs: &'b Filesystem<'a, S>,
alloc: &mut FileAllocation<S>,
path: &Path,
) -> Result<File<'a, 'b, S>> {
alloc.config.buffer = alloc.cache.get() as *mut _;
let return_code = ll::lfs_file_opencfg(
&mut fs.alloc.borrow_mut().state,
addr_of_mut!(alloc.state),
path.as_ptr(),
self.0.bits(),
addr_of!(alloc.config),
);
let file = File {
alloc: RefCell::new(alloc),
fs,
};
result_from(file, return_code)
}
pub fn open_and_then<'a, R, S: driver::Storage>(
&self,
fs: &Filesystem<'a, S>,
path: &Path,
f: impl FnOnce(&File<'a, '_, S>) -> Result<R>,
) -> Result<R> {
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 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
}
}
impl From<FileOpenFlags> for OpenOptions {
fn from(flags: FileOpenFlags) -> Self {
Self(flags)
}
}
impl<S: driver::Storage> io::Read for File<'_, '_, S> {
fn read(&self, buf: &mut [u8]) -> Result<usize> {
let return_code = unsafe {
ll::lfs_file_read(
&mut self.fs.alloc.borrow_mut().state,
addr_of_mut!((*(*self.alloc.borrow_mut())).state),
buf.as_mut_ptr() as *mut c_void,
buf.len() as u32,
)
};
u32_result(return_code).map(|n| n as usize)
}
}
impl<S: driver::Storage> io::Seek for File<'_, '_, S> {
fn seek(&self, pos: io::SeekFrom) -> Result<usize> {
let return_code = unsafe {
ll::lfs_file_seek(
&mut self.fs.alloc.borrow_mut().state,
addr_of_mut!((*(*self.alloc.borrow_mut())).state),
pos.off(),
pos.whence(),
)
};
u32_result(return_code).map(|n| n as usize)
}
}
impl<S: driver::Storage> io::Write for File<'_, '_, S> {
fn write(&self, buf: &[u8]) -> Result<usize> {
let return_code = unsafe {
ll::lfs_file_write(
&mut self.fs.alloc.borrow_mut().state,
addr_of_mut!((*(*self.alloc.borrow_mut())).state),
buf.as_ptr() as *const c_void,
buf.len() as u32,
)
};
u32_result(return_code).map(|n| n as usize)
}
fn flush(&self) -> Result<()> {
Ok(())
}
}
pub struct ReadDirAllocation {
state: ll::lfs_dir_t,
}
impl Default for ReadDirAllocation {
fn default() -> Self {
Self::new()
}
}
impl ReadDirAllocation {
pub fn new() -> Self {
unsafe { mem::MaybeUninit::zeroed().assume_init() }
}
}
pub struct ReadDir<'a, 'b, S: driver::Storage> {
alloc: RefCell<*mut ReadDirAllocation>,
fs: &'b Filesystem<'a, S>,
path: &'b Path,
}
impl<S: driver::Storage> Iterator for ReadDir<'_, '_, S> {
type Item = Result<DirEntry>;
#[allow(unreachable_code)]
fn next(&mut self) -> Option<Self::Item> {
let mut info: ll::lfs_info = unsafe { mem::MaybeUninit::zeroed().assume_init() };
let return_code = unsafe {
ll::lfs_dir_read(
&mut self.fs.alloc.borrow_mut().state,
addr_of_mut!((*(*self.alloc.borrow_mut())).state),
&mut info,
)
};
if return_code > 0 {
let file_name = unsafe { PathBuf::from_buffer_unchecked(info.name) };
let metadata = metadata(info);
let path = self.path.join(&file_name);
let dir_entry = DirEntry::new(file_name, metadata, path);
return Some(Ok(dir_entry));
}
if return_code == 0 {
return None;
}
Some(Err(result_from((), return_code).unwrap_err()))
}
}
impl<'a, S: driver::Storage> ReadDir<'a, '_, S> {
pub unsafe fn borrow_filesystem<'b>(&'b mut self) -> &'b Filesystem<'a, S> {
self.fs
}
}
impl<S: driver::Storage> ReadDir<'_, '_, S> {
pub fn close(self) -> Result<()> {
let return_code = unsafe {
ll::lfs_dir_close(
&mut self.fs.alloc.borrow_mut().state,
addr_of_mut!((*(*self.alloc.borrow_mut())).state),
)
};
result_from((), return_code)
}
}
impl<'a, Storage: driver::Storage> Filesystem<'a, Storage> {
pub fn read_dir_and_then<R>(
&self,
path: &Path,
f: impl FnOnce(&mut ReadDir<'_, '_, Storage>) -> Result<R>,
) -> Result<R> {
let mut alloc = ReadDirAllocation::new();
let mut read_dir = unsafe { self.read_dir(&mut alloc, path)? };
let res = f(&mut read_dir);
read_dir.close()?;
res
}
pub unsafe fn read_dir<'b>(
&'b self,
alloc: &'b mut ReadDirAllocation,
path: &'b Path,
) -> Result<ReadDir<'a, 'b, Storage>> {
let return_code = ll::lfs_dir_open(
&mut self.alloc.borrow_mut().state,
addr_of_mut!(alloc.state),
path.as_ptr(),
);
let read_dir = ReadDir {
alloc: RefCell::new(alloc),
fs: self,
path,
};
result_from(read_dir, return_code)
}
}
impl<'a, Storage: driver::Storage> Filesystem<'a, Storage> {
pub fn mount(alloc: &'a mut Allocation<Storage>, storage: &'a mut Storage) -> Result<Self> {
let fs = Self::new(alloc, storage);
fs.raw_mount()?;
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;
}
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, &mut Allocation<Storage>) -> Result<()>,
{
let mut fs = Self::new(alloc, storage);
if let Err(err) = fs.raw_mount() {
let alloc = fs.alloc.get_mut();
f(err, fs.storage, alloc)?;
Self::set_alloc_config(alloc, fs.storage);
fs.raw_mount()?;
}
Ok(fs)
}
fn raw_mount(&self) -> Result<()> {
let mut alloc = self.alloc.borrow_mut();
let return_code = unsafe { ll::lfs_mount(&mut alloc.state, &alloc.config) };
drop(alloc);
result_from((), return_code)
}
fn new(alloc: &'a mut Allocation<Storage>, storage: &'a mut Storage) -> Self {
Self::set_alloc_config(alloc, storage);
Filesystem {
alloc: RefCell::new(alloc),
storage,
}
}
pub fn into_inner(self) -> (&'a mut Allocation<Storage>, &'a mut Storage) {
(self.alloc.into_inner(), self.storage)
}
pub fn create_dir(&self, path: &Path) -> Result<()> {
#[cfg(test)]
println!("creating {path:?}");
let return_code =
unsafe { ll::lfs_mkdir(&mut self.alloc.borrow_mut().state, path.as_ptr()) };
result_from((), return_code)
}
pub fn create_dir_all(&self, path: &Path) -> Result<()> {
let path_slice = path.as_ref().as_bytes();
for i in 0..path_slice.len() {
if path_slice[i] == b'/' {
let dir = PathBuf::try_from(&path_slice[..i]).map_err(|_| Error::IO)?;
#[cfg(test)]
println!("generated PathBuf dir {dir:?} using i = {i}");
if let Err(error) = self.create_dir(&dir) {
if error != Error::ENTRY_ALREADY_EXISTED {
return Err(error);
}
}
}
}
if let Err(error) = self.create_dir(path) {
if error != Error::ENTRY_ALREADY_EXISTED {
return Err(error);
}
}
Ok(())
}
pub fn read<const N: usize>(&self, path: &Path) -> Result<heapless::Vec<u8, N>> {
let mut contents: heapless::Vec<u8, N> = Default::default();
File::open_and_then(self, path, |file| {
let len = file.read_to_end(&mut contents)?;
Ok(len)
})?;
Ok(contents)
}
pub fn read_chunk<const N: usize>(
&self,
path: &Path,
pos: OpenSeekFrom,
) -> Result<(heapless::Vec<u8, N>, usize)> {
let mut contents: heapless::Vec<u8, N> = Default::default();
contents.resize_default(contents.capacity()).unwrap();
let file_len = File::open_and_then(self, path, |file| {
file.seek(pos.into())?;
let read_n = file.read(&mut contents)?;
contents.truncate(read_n);
file.len()
})?;
Ok((contents, file_len))
}
pub fn write(&self, path: &Path, contents: &[u8]) -> Result<()> {
#[cfg(test)]
println!("writing {path:?}");
File::create_and_then(self, path, |file| {
use io::Write;
file.write_all(contents)
})?;
Ok(())
}
pub fn write_chunk(&self, path: &Path, contents: &[u8], pos: OpenSeekFrom) -> Result<()> {
#[cfg(test)]
println!("writing {path:?}");
OpenOptions::new()
.read(true)
.write(true)
.truncate(false)
.open_and_then(self, path, |file| {
use io::Write;
file.seek(pos.into())?;
file.write_all(contents)
})?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::path;
use core::convert::TryInto;
const_ram_storage!(TestStorage, 4096);
#[test]
fn disk_version() {
let mut test_storage = TestStorage::new();
Filesystem::format(&mut test_storage).unwrap();
Filesystem::mount_and_then(&mut test_storage, |fs| {
let mut fs_info = ll::lfs_fsinfo {
disk_version: 0,
block_size: 0,
block_count: 0,
name_max: 0,
file_max: 0,
attr_max: 0,
};
let return_code =
unsafe { ll::lfs_fs_stat(&mut fs.alloc.borrow_mut().state, &mut fs_info) };
result_from((), return_code).unwrap();
assert_eq!(fs_info.disk_version, DISK_VERSION.into());
Ok(())
})
.unwrap();
}
#[test]
fn todo() {
let mut test_storage = TestStorage::new();
let jackson5 = b"ABC 123";
let jackson5 = &jackson5[..];
Filesystem::format(&mut test_storage).unwrap();
Filesystem::mount_and_then(&mut test_storage, |fs| {
println!("blocks going in: {}", fs.available_blocks()?);
fs.create_dir_all(b"/tmp/test\0".try_into().unwrap())?;
println!("dir done");
fs.write(b"/tmp/test/a.txt\0".try_into().unwrap(), jackson5)?;
println!("a.txt");
fs.write(b"/tmp/test/b.txt\0".try_into().unwrap(), jackson5)?;
fs.write(b"/tmp/test/c.txt\0".try_into().unwrap(), jackson5)?;
println!("blocks after 3 files of size 3: {}", fs.available_blocks()?);
fs.read_dir_and_then(b"/\0".try_into().unwrap(), |read_dir| {
for entry in read_dir {
let entry = entry?;
println!("{:?} --> path = {:?}", entry.file_name(), entry.path());
}
Ok(())
})?;
fs.read_dir_and_then(b"/tmp\0".try_into().unwrap(), |read_dir| {
for entry in read_dir {
println!("entry: {:?}", entry?.file_name());
}
Ok(())
})?;
fs.read_dir_and_then(b"/tmp/test\0".try_into().unwrap(), |read_dir| {
for entry in read_dir {
let entry = entry?;
println!("entry: {:?}", entry.file_name());
println!("path: {:?}", entry.path());
let attribute: &[u8] = if entry.file_type().is_dir() {
b"directory alarm"
} else {
fs.write(entry.path(), b"Alles neu macht n\xc3\xa4chstens der Mai")?;
b"ceci n'est pas une pipe"
};
fs.set_attribute(entry.path(), 37, attribute)?;
}
Ok(())
})?;
fs.read_dir_and_then(b"/tmp/test\0".try_into().unwrap(), |read_dir| {
for (i, entry) in read_dir.enumerate() {
let entry = entry?;
println!("\nfile {}: {:?}", i, entry.file_name());
if entry.file_type().is_file() {
let content: heapless::Vec<u8, 256> = fs.read(entry.path())?;
println!("content:\n{:?}", core::str::from_utf8(&content).unwrap());
}
let mut buffer = [0; Attribute::MAX_SIZE as _];
if let Some(attribute) = fs.attribute(entry.path(), 37, &mut buffer)? {
println!(
"attribute 37: {:?}",
core::str::from_utf8(attribute.data()).unwrap()
);
}
if entry.file_type().is_file() {
println!("removing {:?}", entry.path());
fs.remove(entry.path())?;
}
if i == 1 {
println!("writing new file");
fs.write(b"/tmp/test/out-of-nowhere.txt\0".try_into().unwrap(), &[])?;
}
}
Ok(())
})?;
println!("\nDELETION SPREE\n");
fs.remove_dir_all(path!("/tmp"))?;
Ok(())
})
.unwrap();
let mut alloc = Allocation::new();
let fs = Filesystem::mount(&mut alloc, &mut test_storage).unwrap();
fs.write(path!("z.txt"), jackson5).unwrap();
}
#[test]
fn remove_dir_all() {
let mut test_storage = TestStorage::new();
let jackson5 = b"ABC 123";
let jackson5 = &jackson5[..];
Filesystem::format(&mut test_storage).unwrap();
Filesystem::mount_and_then(&mut test_storage, |fs| {
fs.create_dir_all(b"/tmp/test\0".try_into().unwrap())?;
fs.write(b"/tmp/test/a.txt\0".try_into().unwrap(), jackson5)?;
fs.write(b"/tmp/test/b.txt\0".try_into().unwrap(), jackson5)?;
fs.write(b"/tmp/test/c.txt\0".try_into().unwrap(), jackson5)?;
println!("\nDELETION SPREE\n");
fs.remove_dir_all(b"/tmp\0".try_into().unwrap())?;
Ok(())
})
.unwrap();
}
#[test]
fn path() {
let _path: &Path = b"a.txt\0".try_into().unwrap();
}
#[test]
fn open_file_with_options_and_then() {
let mut test_storage = TestStorage::new();
Filesystem::format(&mut test_storage).unwrap();
Filesystem::mount_and_then(&mut test_storage, |fs| {
let filename = b"append.to.me\0".try_into().unwrap();
fs.write(filename, b"first part")?;
fs.open_file_with_options_and_then(
|options| options.write(true).create(false).truncate(false),
filename,
|file| {
file.seek(io::SeekFrom::End(0))?;
file.write(b" - ")?;
file.write(b"second part")?;
Ok(())
},
)?;
let content: heapless::Vec<_, 256> = fs.read(filename)?;
assert_eq!(content, b"first part - second part");
Ok(())
})
.unwrap();
}
#[test]
fn nested() {
let mut test_storage = TestStorage::new();
Filesystem::format(&mut test_storage).unwrap();
Filesystem::mount_and_then(&mut test_storage, |fs| {
fs.write(b"a.txt\0".try_into().unwrap(), &[])?;
fs.write(b"b.txt\0".try_into().unwrap(), &[])?;
fs.write(b"c.txt\0".try_into().unwrap(), &[])?;
fs.read_dir_and_then(b".\0".try_into().unwrap(), |read_dir| {
for entry in read_dir {
let entry = entry?;
println!("{:?}", entry.file_name());
if entry.metadata().is_file() {
fs.write(entry.file_name(), b"wowee zowie")?;
}
}
Ok(())
})?;
Ok(())
})
.unwrap();
}
#[test]
fn issue_3_original_report() {
let mut test_storage = TestStorage::new();
Filesystem::format(&mut test_storage).unwrap();
Filesystem::mount_and_then(&mut test_storage, |fs| {
fs.write(b"a.txt\0".try_into().unwrap(), &[])?;
fs.write(b"b.txt\0".try_into().unwrap(), &[])?;
fs.write(b"c.txt\0".try_into().unwrap(), &[])?;
fs.read_dir_and_then(b".\0".try_into().unwrap(), |read_dir| {
for entry in read_dir {
let entry = entry?;
println!("{:?}", entry.file_type());
}
Ok(())
})?;
let mut a1 = File::allocate();
let f1 = unsafe { File::create(fs, &mut a1, b"a.txt\0".try_into().unwrap())? };
f1.write(b"some text")?;
let mut a2 = File::allocate();
let f2 = unsafe { File::create(fs, &mut a2, b"b.txt\0".try_into().unwrap())? };
f2.write(b"more text")?;
unsafe { f1.close()? }; unsafe { f2.close()? };
Ok(())
})
.unwrap();
}
}