Skip to main content

littlefs_rust/
filesystem.rs

1use alloc::boxed::Box;
2use alloc::vec;
3use alloc::vec::Vec;
4use core::cell::RefCell;
5use core::ffi::c_void;
6use core::mem::{ManuallyDrop, MaybeUninit};
7
8use littlefs_rust_core::{Lfs, LfsConfig, LfsInfo, LFS_ERR_IO};
9
10use crate::config::Config;
11use crate::dir::{dir_entry_from_info, ReadDir};
12use crate::error::{from_lfs_result, from_lfs_size, Error};
13use crate::file::File;
14use crate::metadata::{DirEntry, Metadata, OpenFlags};
15use crate::storage::Storage;
16
17pub(crate) struct FsInner<S: Storage> {
18    pub(crate) lfs: MaybeUninit<Lfs>,
19    pub(crate) config: LfsConfig,
20    pub(crate) storage: S,
21    _read_buf: Vec<u8>,
22    _prog_buf: Vec<u8>,
23    _lookahead_buf: Vec<u8>,
24    pub(crate) mounted: bool,
25}
26
27/// A mounted LittleFS filesystem.
28///
29/// All methods take `&self` via interior mutability, so multiple [`File`] and
30/// [`ReadDir`] handles can coexist. The internal state is heap-allocated and
31/// pinned so that core pointers remain stable across moves.
32///
33/// Use [`Filesystem::format`] to initialize storage, then [`Filesystem::mount`]
34/// to obtain a `Filesystem`. Call [`Filesystem::unmount`] to cleanly unmount
35/// and recover the storage, or let [`Drop`] handle it automatically.
36///
37/// `Filesystem` is `!Send` and `!Sync` (due to interior `RefCell`). This is
38/// appropriate for single-threaded embedded use. If you need cross-thread
39/// access, wrap it in a `Mutex`.
40pub struct Filesystem<S: Storage> {
41    pub(crate) inner: RefCell<Box<FsInner<S>>>,
42}
43
44// ── Trampolines ─────────────────────────────────────────────────────────────
45
46unsafe extern "C" fn trampoline_read<S: Storage>(
47    cfg: *const LfsConfig,
48    block: u32,
49    off: u32,
50    buffer: *mut u8,
51    size: u32,
52) -> i32 {
53    let storage = &mut *((*cfg).context as *mut S);
54    let buf = core::slice::from_raw_parts_mut(buffer, size as usize);
55    match storage.read(block, off, buf) {
56        Ok(()) => 0,
57        Err(_) => LFS_ERR_IO,
58    }
59}
60
61unsafe extern "C" fn trampoline_prog<S: Storage>(
62    cfg: *const LfsConfig,
63    block: u32,
64    off: u32,
65    buffer: *const u8,
66    size: u32,
67) -> i32 {
68    let storage = &mut *((*cfg).context as *mut S);
69    let buf = core::slice::from_raw_parts(buffer, size as usize);
70    match storage.write(block, off, buf) {
71        Ok(()) => 0,
72        Err(_) => LFS_ERR_IO,
73    }
74}
75
76unsafe extern "C" fn trampoline_erase<S: Storage>(cfg: *const LfsConfig, block: u32) -> i32 {
77    let storage = &mut *((*cfg).context as *mut S);
78    match storage.erase(block) {
79        Ok(()) => 0,
80        Err(_) => LFS_ERR_IO,
81    }
82}
83
84unsafe extern "C" fn trampoline_sync<S: Storage>(cfg: *const LfsConfig) -> i32 {
85    let storage = &mut *((*cfg).context as *mut S);
86    match storage.sync() {
87        Ok(()) => 0,
88        Err(_) => LFS_ERR_IO,
89    }
90}
91
92// ── FsInner construction ────────────────────────────────────────────────────
93
94fn build_inner<S: Storage>(storage: S, config: &Config) -> FsInner<S> {
95    let cache_size = config.resolve_cache_size() as usize;
96    let lookahead_size = config.resolve_lookahead_size() as usize;
97
98    let mut read_buf = vec![0u8; cache_size];
99    let mut prog_buf = vec![0u8; cache_size];
100    let mut lookahead_buf = vec![0u8; lookahead_size];
101
102    let lfs_config = LfsConfig {
103        context: core::ptr::null_mut(),
104        read: Some(trampoline_read::<S>),
105        prog: Some(trampoline_prog::<S>),
106        erase: Some(trampoline_erase::<S>),
107        sync: Some(trampoline_sync::<S>),
108        read_size: config.read_size,
109        prog_size: config.prog_size,
110        block_size: config.block_size,
111        block_count: config.block_count,
112        block_cycles: config.block_cycles,
113        cache_size: config.resolve_cache_size(),
114        lookahead_size: config.resolve_lookahead_size(),
115        compact_thresh: u32::MAX,
116        read_buffer: read_buf.as_mut_ptr() as *mut c_void,
117        prog_buffer: prog_buf.as_mut_ptr() as *mut c_void,
118        lookahead_buffer: lookahead_buf.as_mut_ptr() as *mut c_void,
119        name_max: config.name_max,
120        file_max: config.file_max,
121        attr_max: config.attr_max,
122        metadata_max: 0,
123        inline_max: 0,
124    };
125
126    FsInner {
127        lfs: MaybeUninit::zeroed(),
128        config: lfs_config,
129        storage,
130        _read_buf: read_buf,
131        _prog_buf: prog_buf,
132        _lookahead_buf: lookahead_buf,
133        mounted: false,
134    }
135}
136
137/// Wire `config.context` to point at `inner.storage`. Must be called after
138/// `inner` is at its final address (i.e., inside the `RefCell`).
139fn wire_context<S: Storage>(inner: &mut FsInner<S>) {
140    inner.config.context = &mut inner.storage as *mut S as *mut c_void;
141    inner.config.read_buffer = inner._read_buf.as_mut_ptr() as *mut c_void;
142    inner.config.prog_buffer = inner._prog_buf.as_mut_ptr() as *mut c_void;
143    inner.config.lookahead_buffer = inner._lookahead_buf.as_mut_ptr() as *mut c_void;
144}
145
146// ── Filesystem ──────────────────────────────────────────────────────────────
147
148impl<S: Storage> Filesystem<S> {
149    /// Format `storage` with a fresh LittleFS filesystem.
150    ///
151    /// This erases any existing data. The storage can be mounted afterwards
152    /// with [`Filesystem::mount`].
153    pub fn format(storage: &mut S, config: &Config) -> Result<(), Error> {
154        let mut inner = build_inner_borrowed(storage, config);
155        wire_context_borrowed(&mut inner);
156        let rc = littlefs_rust_core::lfs_format(
157            inner.lfs.as_mut_ptr(),
158            &inner.config as *const LfsConfig,
159        );
160        from_lfs_result(rc)
161    }
162
163    /// Mount an existing filesystem. Takes ownership of the storage.
164    ///
165    /// On failure the storage is returned alongside the error so the caller
166    /// can retry (e.g. format + mount).
167    pub fn mount(storage: S, config: Config) -> Result<Self, (Error, S)> {
168        let mut inner = Box::new(build_inner(storage, &config));
169        wire_context(&mut inner);
170        let rc = littlefs_rust_core::lfs_mount(
171            inner.lfs.as_mut_ptr(),
172            &inner.config as *const LfsConfig,
173        );
174        if rc != 0 {
175            return Err((Error::from(rc), inner.storage));
176        }
177        inner.mounted = true;
178        Ok(Filesystem {
179            inner: RefCell::new(inner),
180        })
181    }
182
183    /// Unmount and return the underlying storage.
184    ///
185    /// Prefer this over dropping when you need to check for errors or reuse
186    /// the storage.
187    pub fn unmount(self) -> Result<S, Error> {
188        let this = ManuallyDrop::new(self);
189        let mut inner = this.inner.borrow_mut();
190        let rc = if inner.mounted {
191            inner.mounted = false;
192            littlefs_rust_core::lfs_unmount(inner.lfs.as_mut_ptr())
193        } else {
194            0
195        };
196        drop(inner);
197        // Safety: we prevented Drop from running via ManuallyDrop, and we've
198        // already unmounted. Take ownership of the RefCell's contents.
199        let fs_inner = unsafe { core::ptr::read(&this.inner) }.into_inner();
200        from_lfs_result(rc)?;
201        Ok(fs_inner.storage)
202    }
203
204    pub(crate) fn cache_size(&self) -> u32 {
205        self.inner.borrow().config.cache_size
206    }
207
208    // ── File access ─────────────────────────────────────────────────────
209
210    /// Open a file with the given [`OpenFlags`].
211    ///
212    /// Common combinations: `READ`, `WRITE | CREATE | TRUNC`,
213    /// `WRITE | CREATE | APPEND`.
214    pub fn open(&self, path: &str, flags: OpenFlags) -> Result<File<'_, S>, Error> {
215        File::open(self, path, flags)
216    }
217
218    // ── Convenience file I/O ────────────────────────────────────────────
219
220    /// Read an entire file into a `Vec<u8>`.
221    pub fn read_to_vec(&self, path: &str) -> Result<Vec<u8>, Error> {
222        let file = self.open(path, OpenFlags::READ)?;
223        let size = file.size() as usize;
224        let mut buf = vec![0u8; size];
225        if size > 0 {
226            let n = file.read(&mut buf)?;
227            buf.truncate(n as usize);
228        }
229        Ok(buf)
230    }
231
232    /// Write `data` to a file, creating or truncating it.
233    pub fn write_file(&self, path: &str, data: &[u8]) -> Result<(), Error> {
234        let file = self.open(
235            path,
236            OpenFlags::WRITE | OpenFlags::CREATE | OpenFlags::TRUNC,
237        )?;
238        let mut offset = 0;
239        while offset < data.len() {
240            let n = file.write(&data[offset..])? as usize;
241            offset += n;
242        }
243        Ok(())
244    }
245
246    // ── Path operations ─────────────────────────────────────────────────
247
248    /// Create a directory. Fails if it already exists.
249    pub fn mkdir(&self, path: &str) -> Result<(), Error> {
250        let path_bytes = null_terminate(path);
251        let mut inner = self.inner.borrow_mut();
252        let rc = littlefs_rust_core::lfs_mkdir(inner.lfs.as_mut_ptr(), path_bytes.as_ptr());
253        from_lfs_result(rc)
254    }
255
256    /// Remove a file or empty directory.
257    pub fn remove(&self, path: &str) -> Result<(), Error> {
258        let path_bytes = null_terminate(path);
259        let mut inner = self.inner.borrow_mut();
260        let rc = littlefs_rust_core::lfs_remove(inner.lfs.as_mut_ptr(), path_bytes.as_ptr());
261        from_lfs_result(rc)
262    }
263
264    /// Rename or move a file or directory.
265    pub fn rename(&self, from: &str, to: &str) -> Result<(), Error> {
266        let from_bytes = null_terminate(from);
267        let to_bytes = null_terminate(to);
268        let mut inner = self.inner.borrow_mut();
269        let rc = littlefs_rust_core::lfs_rename(
270            inner.lfs.as_mut_ptr(),
271            from_bytes.as_ptr(),
272            to_bytes.as_ptr(),
273        );
274        from_lfs_result(rc)
275    }
276
277    /// Get metadata for a file or directory.
278    pub fn stat(&self, path: &str) -> Result<Metadata, Error> {
279        let path_bytes = null_terminate(path);
280        let mut info = MaybeUninit::<LfsInfo>::zeroed();
281        {
282            let mut inner = self.inner.borrow_mut();
283            let rc = littlefs_rust_core::lfs_stat(
284                inner.lfs.as_mut_ptr(),
285                path_bytes.as_ptr(),
286                info.as_mut_ptr(),
287            );
288            from_lfs_result(rc)?;
289        }
290        let entry = dir_entry_from_info(unsafe { &*info.as_ptr() });
291        Ok(Metadata {
292            name: entry.name,
293            file_type: entry.file_type,
294            size: entry.size,
295        })
296    }
297
298    /// Returns `true` if `path` exists.
299    pub fn exists(&self, path: &str) -> bool {
300        self.stat(path).is_ok()
301    }
302
303    // ── Directory listing ───────────────────────────────────────────────
304
305    /// Open a directory for iteration. The returned [`ReadDir`] is an
306    /// [`Iterator`] that skips `.` and `..` entries.
307    pub fn read_dir(&self, path: &str) -> Result<ReadDir<'_, S>, Error> {
308        ReadDir::open(self, path)
309    }
310
311    /// Collect all entries in a directory into a `Vec`.
312    pub fn list_dir(&self, path: &str) -> Result<Vec<DirEntry>, Error> {
313        let dir = self.read_dir(path)?;
314        dir.collect()
315    }
316
317    // ── FS-level ────────────────────────────────────────────────────────
318
319    /// Return the number of allocated blocks.
320    pub fn fs_size(&self) -> Result<u32, Error> {
321        let mut inner = self.inner.borrow_mut();
322        let rc = littlefs_rust_core::lfs_fs_size(inner.lfs.as_mut_ptr());
323        from_lfs_size(rc)
324    }
325
326    /// Run garbage collection to reclaim unused blocks.
327    pub fn gc(&self) -> Result<(), Error> {
328        let mut inner = self.inner.borrow_mut();
329        let rc = littlefs_rust_core::lfs_fs_gc(inner.lfs.as_mut_ptr());
330        from_lfs_result(rc)
331    }
332}
333
334impl<S: Storage> Drop for Filesystem<S> {
335    fn drop(&mut self) {
336        if let Ok(mut inner) = self.inner.try_borrow_mut() {
337            if inner.mounted {
338                let _ = littlefs_rust_core::lfs_unmount(inner.lfs.as_mut_ptr());
339                inner.mounted = false;
340            }
341        }
342    }
343}
344
345// ── format helper (borrows storage instead of taking ownership) ─────────────
346
347struct BorrowedFsInner<'a, S: Storage> {
348    lfs: MaybeUninit<Lfs>,
349    config: LfsConfig,
350    storage: &'a mut S,
351    _read_buf: Vec<u8>,
352    _prog_buf: Vec<u8>,
353    _lookahead_buf: Vec<u8>,
354}
355
356fn build_inner_borrowed<'a, S: Storage>(
357    storage: &'a mut S,
358    config: &Config,
359) -> BorrowedFsInner<'a, S> {
360    let cache_size = config.resolve_cache_size() as usize;
361    let lookahead_size = config.resolve_lookahead_size() as usize;
362
363    let mut read_buf = vec![0u8; cache_size];
364    let mut prog_buf = vec![0u8; cache_size];
365    let mut lookahead_buf = vec![0u8; lookahead_size];
366
367    let lfs_config = LfsConfig {
368        context: core::ptr::null_mut(),
369        read: Some(trampoline_read::<S>),
370        prog: Some(trampoline_prog::<S>),
371        erase: Some(trampoline_erase::<S>),
372        sync: Some(trampoline_sync::<S>),
373        read_size: config.read_size,
374        prog_size: config.prog_size,
375        block_size: config.block_size,
376        block_count: config.block_count,
377        block_cycles: config.block_cycles,
378        cache_size: config.resolve_cache_size(),
379        lookahead_size: config.resolve_lookahead_size(),
380        compact_thresh: u32::MAX,
381        read_buffer: read_buf.as_mut_ptr() as *mut c_void,
382        prog_buffer: prog_buf.as_mut_ptr() as *mut c_void,
383        lookahead_buffer: lookahead_buf.as_mut_ptr() as *mut c_void,
384        name_max: config.name_max,
385        file_max: config.file_max,
386        attr_max: config.attr_max,
387        metadata_max: 0,
388        inline_max: 0,
389    };
390
391    BorrowedFsInner {
392        lfs: MaybeUninit::zeroed(),
393        config: lfs_config,
394        storage,
395        _read_buf: read_buf,
396        _prog_buf: prog_buf,
397        _lookahead_buf: lookahead_buf,
398    }
399}
400
401fn wire_context_borrowed<S: Storage>(inner: &mut BorrowedFsInner<'_, S>) {
402    inner.config.context = inner.storage as *mut S as *mut c_void;
403    inner.config.read_buffer = inner._read_buf.as_mut_ptr() as *mut c_void;
404    inner.config.prog_buffer = inner._prog_buf.as_mut_ptr() as *mut c_void;
405    inner.config.lookahead_buffer = inner._lookahead_buf.as_mut_ptr() as *mut c_void;
406}
407
408fn null_terminate(s: &str) -> Vec<u8> {
409    let mut v: Vec<u8> = s.bytes().collect();
410    v.push(0);
411    v
412}