use alloc::boxed::Box;
use alloc::vec;
use alloc::vec::Vec;
use core::cell::RefCell;
use core::ffi::c_void;
use core::mem::{ManuallyDrop, MaybeUninit};
use littlefs_rust_core::{Lfs, LfsConfig, LfsInfo, LFS_ERR_IO};
use crate::config::Config;
use crate::dir::{dir_entry_from_info, ReadDir};
use crate::error::{from_lfs_result, from_lfs_size, Error};
use crate::file::File;
use crate::metadata::{DirEntry, Metadata, OpenFlags};
use crate::storage::Storage;
pub(crate) struct FsInner<S: Storage> {
pub(crate) lfs: MaybeUninit<Lfs>,
pub(crate) config: LfsConfig,
pub(crate) storage: S,
_read_buf: Vec<u8>,
_prog_buf: Vec<u8>,
_lookahead_buf: Vec<u8>,
pub(crate) mounted: bool,
}
pub struct Filesystem<S: Storage> {
pub(crate) inner: RefCell<Box<FsInner<S>>>,
}
unsafe extern "C" fn trampoline_read<S: Storage>(
cfg: *const LfsConfig,
block: u32,
off: u32,
buffer: *mut u8,
size: u32,
) -> i32 {
let storage = &mut *((*cfg).context as *mut S);
let buf = core::slice::from_raw_parts_mut(buffer, size as usize);
match storage.read(block, off, buf) {
Ok(()) => 0,
Err(_) => LFS_ERR_IO,
}
}
unsafe extern "C" fn trampoline_prog<S: Storage>(
cfg: *const LfsConfig,
block: u32,
off: u32,
buffer: *const u8,
size: u32,
) -> i32 {
let storage = &mut *((*cfg).context as *mut S);
let buf = core::slice::from_raw_parts(buffer, size as usize);
match storage.write(block, off, buf) {
Ok(()) => 0,
Err(_) => LFS_ERR_IO,
}
}
unsafe extern "C" fn trampoline_erase<S: Storage>(cfg: *const LfsConfig, block: u32) -> i32 {
let storage = &mut *((*cfg).context as *mut S);
match storage.erase(block) {
Ok(()) => 0,
Err(_) => LFS_ERR_IO,
}
}
unsafe extern "C" fn trampoline_sync<S: Storage>(cfg: *const LfsConfig) -> i32 {
let storage = &mut *((*cfg).context as *mut S);
match storage.sync() {
Ok(()) => 0,
Err(_) => LFS_ERR_IO,
}
}
fn build_inner<S: Storage>(storage: S, config: &Config) -> FsInner<S> {
let cache_size = config.resolve_cache_size() as usize;
let lookahead_size = config.resolve_lookahead_size() as usize;
let mut read_buf = vec![0u8; cache_size];
let mut prog_buf = vec![0u8; cache_size];
let mut lookahead_buf = vec![0u8; lookahead_size];
let lfs_config = LfsConfig {
context: core::ptr::null_mut(),
read: Some(trampoline_read::<S>),
prog: Some(trampoline_prog::<S>),
erase: Some(trampoline_erase::<S>),
sync: Some(trampoline_sync::<S>),
read_size: config.read_size,
prog_size: config.prog_size,
block_size: config.block_size,
block_count: config.block_count,
block_cycles: config.block_cycles,
cache_size: config.resolve_cache_size(),
lookahead_size: config.resolve_lookahead_size(),
compact_thresh: u32::MAX,
read_buffer: read_buf.as_mut_ptr() as *mut c_void,
prog_buffer: prog_buf.as_mut_ptr() as *mut c_void,
lookahead_buffer: lookahead_buf.as_mut_ptr() as *mut c_void,
name_max: config.name_max,
file_max: config.file_max,
attr_max: config.attr_max,
metadata_max: 0,
inline_max: 0,
};
FsInner {
lfs: MaybeUninit::zeroed(),
config: lfs_config,
storage,
_read_buf: read_buf,
_prog_buf: prog_buf,
_lookahead_buf: lookahead_buf,
mounted: false,
}
}
fn wire_context<S: Storage>(inner: &mut FsInner<S>) {
inner.config.context = &mut inner.storage as *mut S as *mut c_void;
inner.config.read_buffer = inner._read_buf.as_mut_ptr() as *mut c_void;
inner.config.prog_buffer = inner._prog_buf.as_mut_ptr() as *mut c_void;
inner.config.lookahead_buffer = inner._lookahead_buf.as_mut_ptr() as *mut c_void;
}
impl<S: Storage> Filesystem<S> {
pub fn format(storage: &mut S, config: &Config) -> Result<(), Error> {
let mut inner = build_inner_borrowed(storage, config);
wire_context_borrowed(&mut inner);
let rc = littlefs_rust_core::lfs_format(
inner.lfs.as_mut_ptr(),
&inner.config as *const LfsConfig,
);
from_lfs_result(rc)
}
pub fn mount(storage: S, config: Config) -> Result<Self, (Error, S)> {
let mut inner = Box::new(build_inner(storage, &config));
wire_context(&mut inner);
let rc = littlefs_rust_core::lfs_mount(
inner.lfs.as_mut_ptr(),
&inner.config as *const LfsConfig,
);
if rc != 0 {
return Err((Error::from(rc), inner.storage));
}
inner.mounted = true;
Ok(Filesystem {
inner: RefCell::new(inner),
})
}
pub fn unmount(self) -> Result<S, Error> {
let this = ManuallyDrop::new(self);
let mut inner = this.inner.borrow_mut();
let rc = if inner.mounted {
inner.mounted = false;
littlefs_rust_core::lfs_unmount(inner.lfs.as_mut_ptr())
} else {
0
};
drop(inner);
let fs_inner = unsafe { core::ptr::read(&this.inner) }.into_inner();
from_lfs_result(rc)?;
Ok(fs_inner.storage)
}
pub(crate) fn cache_size(&self) -> u32 {
self.inner.borrow().config.cache_size
}
pub fn open(&self, path: &str, flags: OpenFlags) -> Result<File<'_, S>, Error> {
File::open(self, path, flags)
}
pub fn read_to_vec(&self, path: &str) -> Result<Vec<u8>, Error> {
let file = self.open(path, OpenFlags::READ)?;
let size = file.size() as usize;
let mut buf = vec![0u8; size];
if size > 0 {
let n = file.read(&mut buf)?;
buf.truncate(n as usize);
}
Ok(buf)
}
pub fn write_file(&self, path: &str, data: &[u8]) -> Result<(), Error> {
let file = self.open(
path,
OpenFlags::WRITE | OpenFlags::CREATE | OpenFlags::TRUNC,
)?;
let mut offset = 0;
while offset < data.len() {
let n = file.write(&data[offset..])? as usize;
offset += n;
}
Ok(())
}
pub fn mkdir(&self, path: &str) -> Result<(), Error> {
let path_bytes = null_terminate(path);
let mut inner = self.inner.borrow_mut();
let rc = littlefs_rust_core::lfs_mkdir(inner.lfs.as_mut_ptr(), path_bytes.as_ptr());
from_lfs_result(rc)
}
pub fn remove(&self, path: &str) -> Result<(), Error> {
let path_bytes = null_terminate(path);
let mut inner = self.inner.borrow_mut();
let rc = littlefs_rust_core::lfs_remove(inner.lfs.as_mut_ptr(), path_bytes.as_ptr());
from_lfs_result(rc)
}
pub fn rename(&self, from: &str, to: &str) -> Result<(), Error> {
let from_bytes = null_terminate(from);
let to_bytes = null_terminate(to);
let mut inner = self.inner.borrow_mut();
let rc = littlefs_rust_core::lfs_rename(
inner.lfs.as_mut_ptr(),
from_bytes.as_ptr(),
to_bytes.as_ptr(),
);
from_lfs_result(rc)
}
pub fn stat(&self, path: &str) -> Result<Metadata, Error> {
let path_bytes = null_terminate(path);
let mut info = MaybeUninit::<LfsInfo>::zeroed();
{
let mut inner = self.inner.borrow_mut();
let rc = littlefs_rust_core::lfs_stat(
inner.lfs.as_mut_ptr(),
path_bytes.as_ptr(),
info.as_mut_ptr(),
);
from_lfs_result(rc)?;
}
let entry = dir_entry_from_info(unsafe { &*info.as_ptr() });
Ok(Metadata {
name: entry.name,
file_type: entry.file_type,
size: entry.size,
})
}
pub fn exists(&self, path: &str) -> bool {
self.stat(path).is_ok()
}
pub fn read_dir(&self, path: &str) -> Result<ReadDir<'_, S>, Error> {
ReadDir::open(self, path)
}
pub fn list_dir(&self, path: &str) -> Result<Vec<DirEntry>, Error> {
let dir = self.read_dir(path)?;
dir.collect()
}
pub fn fs_size(&self) -> Result<u32, Error> {
let mut inner = self.inner.borrow_mut();
let rc = littlefs_rust_core::lfs_fs_size(inner.lfs.as_mut_ptr());
from_lfs_size(rc)
}
pub fn gc(&self) -> Result<(), Error> {
let mut inner = self.inner.borrow_mut();
let rc = littlefs_rust_core::lfs_fs_gc(inner.lfs.as_mut_ptr());
from_lfs_result(rc)
}
}
impl<S: Storage> Drop for Filesystem<S> {
fn drop(&mut self) {
if let Ok(mut inner) = self.inner.try_borrow_mut() {
if inner.mounted {
let _ = littlefs_rust_core::lfs_unmount(inner.lfs.as_mut_ptr());
inner.mounted = false;
}
}
}
}
struct BorrowedFsInner<'a, S: Storage> {
lfs: MaybeUninit<Lfs>,
config: LfsConfig,
storage: &'a mut S,
_read_buf: Vec<u8>,
_prog_buf: Vec<u8>,
_lookahead_buf: Vec<u8>,
}
fn build_inner_borrowed<'a, S: Storage>(
storage: &'a mut S,
config: &Config,
) -> BorrowedFsInner<'a, S> {
let cache_size = config.resolve_cache_size() as usize;
let lookahead_size = config.resolve_lookahead_size() as usize;
let mut read_buf = vec![0u8; cache_size];
let mut prog_buf = vec![0u8; cache_size];
let mut lookahead_buf = vec![0u8; lookahead_size];
let lfs_config = LfsConfig {
context: core::ptr::null_mut(),
read: Some(trampoline_read::<S>),
prog: Some(trampoline_prog::<S>),
erase: Some(trampoline_erase::<S>),
sync: Some(trampoline_sync::<S>),
read_size: config.read_size,
prog_size: config.prog_size,
block_size: config.block_size,
block_count: config.block_count,
block_cycles: config.block_cycles,
cache_size: config.resolve_cache_size(),
lookahead_size: config.resolve_lookahead_size(),
compact_thresh: u32::MAX,
read_buffer: read_buf.as_mut_ptr() as *mut c_void,
prog_buffer: prog_buf.as_mut_ptr() as *mut c_void,
lookahead_buffer: lookahead_buf.as_mut_ptr() as *mut c_void,
name_max: config.name_max,
file_max: config.file_max,
attr_max: config.attr_max,
metadata_max: 0,
inline_max: 0,
};
BorrowedFsInner {
lfs: MaybeUninit::zeroed(),
config: lfs_config,
storage,
_read_buf: read_buf,
_prog_buf: prog_buf,
_lookahead_buf: lookahead_buf,
}
}
fn wire_context_borrowed<S: Storage>(inner: &mut BorrowedFsInner<'_, S>) {
inner.config.context = inner.storage as *mut S as *mut c_void;
inner.config.read_buffer = inner._read_buf.as_mut_ptr() as *mut c_void;
inner.config.prog_buffer = inner._prog_buf.as_mut_ptr() as *mut c_void;
inner.config.lookahead_buffer = inner._lookahead_buf.as_mut_ptr() as *mut c_void;
}
fn null_terminate(s: &str) -> Vec<u8> {
let mut v: Vec<u8> = s.bytes().collect();
v.push(0);
v
}