1use crate::io::Read;
42use crate::path::Path;
43use ::alloc::boxed::Box;
44use ::alloc::collections::BTreeMap;
45use ::alloc::collections::VecDeque;
46use ::alloc::format;
47use ::alloc::string::{String, ToString};
48use ::alloc::vec;
49use ::alloc::vec::Vec;
50
51use crate::block::BlockDevice;
52use crate::fs::{
53 DirEntry, EntryKind, FileAttrs, FileMeta, FileSource, Filesystem, MutationCapability, StatFs,
54 XattrPair,
55};
56use crate::{Error, Result};
57
58#[path = "alloc.rs"]
62mod alloc;
63#[path = "ctz.rs"]
64mod ctz;
65#[path = "mdir.rs"]
66mod mdir;
67#[path = "rw.rs"]
68mod rw;
69#[path = "size_plan.rs"]
70mod size_plan;
71#[cfg(test)]
72#[path = "tests.rs"]
73mod tests;
74
75pub(super) use super::{
80 DISK_VERSION_2_0, DISK_VERSION_2_1, FILE_MAX, MAGIC, SUPERBLOCK_PAIR, index, tag,
81};
82
83pub use size_plan::LittleFsSizePlan;
84
85use self::alloc::Alloc;
86use mdir::{Entry, Geom, Mdir, Struct};
87
88const XATTR_PREFIX: &str = "user.littlefs.";
91
92#[derive(Debug, Clone)]
94pub struct LittleFsFormatOpts {
95 pub block_size: u32,
98 pub block_count: Option<u32>,
100 pub prog_size: u32,
103 pub disk_version: u32,
106 pub name_max: u32,
108 pub inline_max: Option<u32>,
112}
113
114impl Default for LittleFsFormatOpts {
115 fn default() -> Self {
116 Self {
117 block_size: 4096,
118 block_count: None,
119 prog_size: 256,
120 disk_version: DISK_VERSION_2_1,
121 name_max: 255,
122 inline_max: None,
123 }
124 }
125}
126
127pub struct LittleFs {
129 geom: Geom,
130 version: u32,
131 name_max: u32,
132 file_max: u32,
133 attr_max: u32,
134 inline_max: u32,
135 root: [u32; 2],
136 alloc: Option<Alloc>,
139 cache: MdirCache,
140}
141
142struct MdirCache {
146 map: BTreeMap<[u32; 2], Mdir>,
147 order: VecDeque<[u32; 2]>,
148 cap: usize,
149}
150
151impl MdirCache {
152 fn new(cap: usize) -> Self {
153 Self {
154 map: BTreeMap::new(),
155 order: VecDeque::new(),
156 cap,
157 }
158 }
159
160 fn key(pair: [u32; 2]) -> [u32; 2] {
163 if pair[0] <= pair[1] {
164 pair
165 } else {
166 [pair[1], pair[0]]
167 }
168 }
169
170 fn get(&self, pair: [u32; 2]) -> Option<&Mdir> {
171 self.map.get(&Self::key(pair))
172 }
173
174 fn put(&mut self, mdir: Mdir) {
175 let k = Self::key(mdir.pair);
176 if self.map.insert(k, mdir).is_none() {
177 self.order.push_back(k);
178 while self.order.len() > self.cap {
179 if let Some(old) = self.order.pop_front() {
180 self.map.remove(&old);
181 }
182 }
183 }
184 }
185
186 fn remove(&mut self, pair: [u32; 2]) {
187 let k = Self::key(pair);
188 self.map.remove(&k);
189 self.order.retain(|p| *p != k);
190 }
191}
192
193enum Resolved {
195 Root,
197 Entry { mdir: Mdir, id: usize },
199}
200
201impl LittleFs {
202 pub fn format(dev: &mut dyn BlockDevice, opts: &LittleFsFormatOpts) -> Result<Self> {
204 let block_size = opts.block_size;
205 if block_size < 128 || !block_size.is_power_of_two() {
209 return Err(Error::InvalidArgument(format!(
210 "littlefs: block_size {block_size} must be a power of two and at least 128"
211 )));
212 }
213 let prog_size = opts.prog_size.max(1);
214 if !prog_size.is_power_of_two() || prog_size > block_size {
215 return Err(Error::InvalidArgument(format!(
216 "littlefs: prog_size {prog_size} must be a power of two no larger than the block size"
217 )));
218 }
219 if opts.disk_version != DISK_VERSION_2_0 && opts.disk_version != DISK_VERSION_2_1 {
220 return Err(Error::InvalidArgument(format!(
221 "littlefs: unsupported disk version {:#010x} (use 2.0 or 2.1)",
222 opts.disk_version
223 )));
224 }
225
226 let avail = (dev.total_size() / block_size as u64).min(u32::MAX as u64) as u32;
227 let block_count = opts.block_count.unwrap_or(avail);
228 if block_count > avail {
229 return Err(Error::InvalidArgument(format!(
230 "littlefs: block_count {block_count} exceeds the {avail} blocks the device holds"
231 )));
232 }
233 if block_count < 4 {
235 return Err(Error::InvalidArgument(
236 "littlefs: a volume needs at least 4 blocks".into(),
237 ));
238 }
239 if opts.name_max == 0 || opts.name_max > tag::MAX_SIZE as u32 {
240 return Err(Error::InvalidArgument(format!(
241 "littlefs: name_max {} must be between 1 and {}",
242 opts.name_max,
243 tag::MAX_SIZE
244 )));
245 }
246
247 let geom = Geom {
248 block_size,
249 block_count,
250 prog_size,
251 fcrc: opts.disk_version >= DISK_VERSION_2_1,
252 };
253 let attr_max = tag::MAX_SIZE as u32;
254 let inline_max = pick_inline_max(&geom, opts.inline_max)?;
255
256 let mut fs = Self {
257 geom,
258 version: opts.disk_version,
259 name_max: opts.name_max,
260 file_max: FILE_MAX,
261 attr_max,
262 inline_max,
263 root: SUPERBLOCK_PAIR,
264 alloc: None,
265 cache: MdirCache::new(32),
266 };
267
268 let mut root = Mdir::empty([SUPERBLOCK_PAIR[1], SUPERBLOCK_PAIR[0]]);
273 root.entries.push(Entry {
274 kind: tag::TYPE_SUPERBLOCK as u8,
275 name: MAGIC.to_vec(),
276 data: Some(Struct::Inline(fs.superblock_bytes())),
277 attrs: Vec::new(),
278 });
279 fs.commit(dev, &mut root)?;
280 fs.commit(dev, &mut root)?;
281
282 let mut a = Alloc::new(block_count);
284 a.mark(SUPERBLOCK_PAIR[0]);
285 a.mark(SUPERBLOCK_PAIR[1]);
286 fs.alloc = Some(a);
287 Ok(fs)
288 }
289
290 pub fn open(dev: &mut dyn BlockDevice) -> Result<Self> {
292 let mut head = [0u8; 44];
296 let n = head.len().min(dev.total_size() as usize);
297 dev.read_at(0, &mut head[..n])?;
298 if &head[8..16] != MAGIC {
299 return Err(Error::InvalidImage(
300 "littlefs: no \"littlefs\" magic at offset 8".into(),
301 ));
302 }
303 let version = tag::le32(&head[20..24]);
304 let block_size = tag::le32(&head[24..28]);
305 let block_count = tag::le32(&head[28..32]);
306 if version >> 16 != 2 {
307 return Err(Error::Unsupported(format!(
308 "littlefs: on-disk version {}.{} (only v2 is supported)",
309 version >> 16,
310 version & 0xffff
311 )));
312 }
313 if version & 0xffff > 1 {
314 return Err(Error::Unsupported(format!(
315 "littlefs: on-disk version 2.{} is newer than 2.1",
316 version & 0xffff
317 )));
318 }
319 if !(128..=16 * 1024 * 1024).contains(&block_size) || block_count == 0 {
320 return Err(Error::InvalidImage(format!(
321 "littlefs: implausible geometry ({block_size}-byte blocks × {block_count})"
322 )));
323 }
324 if (block_size as u64).saturating_mul(block_count as u64) > dev.total_size() {
325 return Err(Error::InvalidImage(format!(
326 "littlefs: volume claims {block_count} × {block_size}-byte blocks but the device holds {} bytes",
327 dev.total_size()
328 )));
329 }
330
331 let geom = Geom {
332 block_size,
333 block_count,
334 prog_size: 1,
335 fcrc: version >= DISK_VERSION_2_1,
336 };
337 let mut fs = Self {
338 geom,
339 version,
340 name_max: tag::le32(&head[32..36]),
341 file_max: tag::le32(&head[36..40]),
342 attr_max: tag::le32(&head[40..44]),
343 inline_max: 0,
344 root: SUPERBLOCK_PAIR,
345 alloc: None,
346 cache: MdirCache::new(32),
347 };
348 if fs.name_max == 0 || fs.name_max > tag::MAX_SIZE as u32 {
349 fs.name_max = 255;
350 }
351 if fs.file_max == 0 {
352 fs.file_max = FILE_MAX;
353 }
354 if fs.attr_max == 0 || fs.attr_max > tag::MAX_SIZE as u32 {
355 fs.attr_max = tag::MAX_SIZE as u32;
356 }
357
358 let mut pair = Some(SUPERBLOCK_PAIR);
362 let mut hops = 0u32;
363 while let Some(p) = pair {
364 let m = mdir::fetch(dev, &fs.geom, p)?;
365 if m.entries
366 .first()
367 .is_some_and(|e| e.kind == tag::TYPE_SUPERBLOCK as u8)
368 {
369 fs.root = m.pair;
370 if let Some(p) = m.fcrc_size
374 && p.is_power_of_two()
375 && p <= block_size
376 {
377 fs.geom.prog_size = p;
378 }
379 }
380 pair = m.tail;
381 hops += 1;
382 if hops > block_count {
383 return Err(Error::InvalidImage(
384 "littlefs: cycle in the metadata-pair list".into(),
385 ));
386 }
387 }
388 if fs.geom.prog_size == 1 {
389 fs.geom.prog_size = 256.min(block_size / 4).max(1);
390 }
391 fs.inline_max = pick_inline_max(&fs.geom, None)?;
392 fs.cache = MdirCache::new(32);
393 Ok(fs)
394 }
395
396 pub fn geometry(&self) -> (u32, u32) {
398 (self.geom.block_size, self.geom.block_count)
399 }
400
401 pub fn version(&self) -> (u16, u16) {
403 ((self.version >> 16) as u16, (self.version & 0xffff) as u16)
404 }
405
406 pub fn inline_max(&self) -> u32 {
409 self.inline_max
410 }
411
412 pub fn program_size(&self) -> u32 {
418 self.geom.prog_size
419 }
420
421 pub fn used_blocks(&mut self, dev: &mut dyn BlockDevice) -> Result<u32> {
423 Ok(self.allocator(dev)?.used())
424 }
425
426 fn superblock_bytes(&self) -> Vec<u8> {
428 let mut b = Vec::with_capacity(24);
429 for v in [
430 self.version,
431 self.geom.block_size,
432 self.geom.block_count,
433 self.name_max,
434 self.file_max,
435 self.attr_max,
436 ] {
437 b.extend_from_slice(&v.to_le_bytes());
438 }
439 b
440 }
441
442 fn fetch(&mut self, dev: &mut dyn BlockDevice, pair: [u32; 2]) -> Result<Mdir> {
446 if let Some(m) = self.cache.get(pair) {
447 return Ok(m.clone());
448 }
449 let m = mdir::fetch(dev, &self.geom, pair)?;
450 self.cache.put(m.clone());
451 Ok(m)
452 }
453
454 fn commit(&mut self, dev: &mut dyn BlockDevice, mdir: &mut Mdir) -> Result<()> {
457 while mdir::needs_split(&self.geom, mdir) {
458 let at = mdir::split_point(&self.geom, mdir);
459 if at == 0 {
460 return Err(Error::InvalidArgument(
461 "littlefs: a single entry is too large for a metadata block".into(),
462 ));
463 }
464 let mut tail = self.new_pair(dev)?;
465 tail.entries = mdir.entries.split_off(at);
466 tail.tail = mdir.tail;
467 tail.hard = mdir.hard;
468 self.commit(dev, &mut tail)?;
469 mdir.tail = Some(tail.pair);
472 mdir.hard = true;
473 }
474
475 mdir.rev = mdir.rev.wrapping_add(1);
476 let target = mdir.pair[1];
477 mdir::write_compaction(dev, &self.geom, mdir, target, mdir.rev)?;
478 mdir.pair.swap(0, 1);
480 self.cache.put(mdir.clone());
481 Ok(())
482 }
483
484 fn new_pair(&mut self, dev: &mut dyn BlockDevice) -> Result<Mdir> {
490 let pair = self.allocator(dev)?.take_pair()?;
491 let mut m = Mdir::empty(pair);
492 m.rev = mdir::read_rev(dev, &self.geom, pair[0]).unwrap_or(0);
493 Ok(m)
494 }
495
496 fn allocator(&mut self, dev: &mut dyn BlockDevice) -> Result<&mut Alloc> {
501 if self.alloc.is_none() {
502 let a = self.scan_used(dev)?;
503 self.alloc = Some(a);
504 }
505 Ok(self.alloc.as_mut().expect("just built"))
506 }
507
508 fn scan_used(&mut self, dev: &mut dyn BlockDevice) -> Result<Alloc> {
511 let geom = self.geom;
512 let mut a = Alloc::new(geom.block_count);
513 let mut next = Some(SUPERBLOCK_PAIR);
514 let mut hops = 0u32;
515 while let Some(pair) = next {
516 let m = self.fetch(dev, pair)?;
517 a.mark(m.pair[0]);
518 a.mark(m.pair[1]);
519 for e in &m.entries {
520 if let Some(Struct::Ctz { head, size }) = &e.data {
521 ctz::traverse(dev, &geom, *head, *size, &mut |b| a.mark(b))?;
522 }
523 }
524 next = m.tail;
525 hops += 1;
526 if hops > geom.block_count {
527 return Err(Error::InvalidImage(
528 "littlefs: cycle in the metadata-pair list".into(),
529 ));
530 }
531 }
532 Ok(a)
533 }
534
535 fn free_data(&mut self, dev: &mut dyn BlockDevice, data: &Struct) -> Result<()> {
537 let Struct::Ctz { head, size } = data else {
538 return Ok(());
539 };
540 let geom = self.geom;
541 let mut blocks = Vec::new();
542 ctz::traverse(dev, &geom, *head, *size, &mut |b| blocks.push(b))?;
543 let a = self.allocator(dev)?;
544 for b in blocks {
545 a.free(b);
546 }
547 Ok(())
548 }
549
550 fn resolve(&mut self, dev: &mut dyn BlockDevice, path: &Path) -> Result<Resolved> {
554 self.try_resolve(dev, path)?.ok_or_else(|| {
555 Error::InvalidArgument(format!("littlefs: no such path {:?}", path.display()))
556 })
557 }
558
559 fn try_resolve(&mut self, dev: &mut dyn BlockDevice, path: &Path) -> Result<Option<Resolved>> {
561 let comps = components(path)?;
562 let mut dir = self.root;
563 let mut out = Resolved::Root;
564 for (i, name) in comps.iter().enumerate() {
565 let Some((mdir, id)) = self.find_in_dir(dev, dir, name.as_bytes())? else {
566 return Ok(None);
567 };
568 if i + 1 < comps.len() {
569 dir = match &mdir.entries[id].data {
570 Some(Struct::Dir(p)) => *p,
571 _ => {
572 return Err(Error::InvalidArgument(format!(
573 "littlefs: {name:?} is not a directory"
574 )));
575 }
576 };
577 }
578 out = Resolved::Entry { mdir, id };
579 }
580 Ok(Some(out))
581 }
582
583 fn dir_head(&self, r: &Resolved) -> Result<[u32; 2]> {
585 match r {
586 Resolved::Root => Ok(self.root),
587 Resolved::Entry { mdir, id } => match &mdir.entries[*id].data {
588 Some(Struct::Dir(p)) => Ok(*p),
589 _ => Err(Error::InvalidArgument(
590 "littlefs: not a directory".to_string(),
591 )),
592 },
593 }
594 }
595
596 fn parent_head(
598 &mut self,
599 dev: &mut dyn BlockDevice,
600 path: &Path,
601 ) -> Result<([u32; 2], String)> {
602 let comps = components(path)?;
603 let (name, parents) = comps
604 .split_last()
605 .ok_or_else(|| Error::InvalidArgument("littlefs: empty path".into()))?;
606 let mut dir = self.root;
607 for p in parents {
608 let Some((mdir, id)) = self.find_in_dir(dev, dir, p.as_bytes())? else {
609 return Err(Error::InvalidArgument(format!(
610 "littlefs: no such directory {p:?}"
611 )));
612 };
613 dir = match &mdir.entries[id].data {
614 Some(Struct::Dir(pair)) => *pair,
615 _ => {
616 return Err(Error::InvalidArgument(format!(
617 "littlefs: {p:?} is not a directory"
618 )));
619 }
620 };
621 }
622 Ok((dir, (*name).to_string()))
623 }
624
625 fn find_in_dir(
627 &mut self,
628 dev: &mut dyn BlockDevice,
629 head: [u32; 2],
630 name: &[u8],
631 ) -> Result<Option<(Mdir, usize)>> {
632 for m in self.chain(dev, head)? {
633 if let Some(id) = m.find(name) {
634 return Ok(Some((m, id)));
635 }
636 }
637 Ok(None)
638 }
639
640 fn chain(&mut self, dev: &mut dyn BlockDevice, head: [u32; 2]) -> Result<Vec<Mdir>> {
642 let mut out = Vec::new();
643 let mut pair = Some(head);
644 while let Some(p) = pair {
645 let m = self.fetch(dev, p)?;
646 pair = if m.hard { m.tail } else { None };
647 out.push(m);
648 if out.len() as u32 > self.geom.block_count {
649 return Err(Error::InvalidImage(
650 "littlefs: cycle in a directory's metadata chain".into(),
651 ));
652 }
653 }
654 Ok(out)
655 }
656
657 fn find_pred(&mut self, dev: &mut dyn BlockDevice, pair: [u32; 2]) -> Result<Mdir> {
660 let key = MdirCache::key(pair);
661 let mut next = Some(SUPERBLOCK_PAIR);
662 let mut hops = 0u32;
663 while let Some(p) = next {
664 let m = self.fetch(dev, p)?;
665 if m.tail.map(MdirCache::key) == Some(key) {
666 return Ok(m);
667 }
668 next = m.tail;
669 hops += 1;
670 if hops > self.geom.block_count {
671 break;
672 }
673 }
674 Err(Error::InvalidImage(
675 "littlefs: metadata pair is not on the threaded list".into(),
676 ))
677 }
678
679 fn insert_entry(
684 &mut self,
685 dev: &mut dyn BlockDevice,
686 head: [u32; 2],
687 entry: Entry,
688 ) -> Result<()> {
689 let mut pair = head;
690 loop {
691 let mut m = self.fetch(dev, pair)?;
692 let start = m.entries.iter().take_while(|e| !e.is_file()).count();
695 let pos = m.entries[start..]
696 .iter()
697 .position(|e| e.name.as_slice() > entry.name.as_slice())
698 .map(|p| p + start);
699 match pos {
700 Some(p) => {
701 m.entries.insert(p, entry);
702 return self.commit(dev, &mut m);
703 }
704 None => match (m.hard, m.tail) {
705 (true, Some(t)) => pair = t,
706 _ => {
707 m.entries.push(entry);
708 return self.commit(dev, &mut m);
709 }
710 },
711 }
712 }
713 }
714
715 fn write_file(
717 &mut self,
718 dev: &mut dyn BlockDevice,
719 path: &Path,
720 body: &mut dyn Read,
721 len: u64,
722 ) -> Result<()> {
723 let (head, name) = self.parent_head(dev, path)?;
724 self.check_name(&name)?;
725 if len > self.file_max as u64 {
726 return Err(Error::InvalidArgument(format!(
727 "littlefs: {len} bytes exceeds the volume's {}-byte file limit",
728 self.file_max
729 )));
730 }
731
732 let existing = self.find_in_dir(dev, head, name.as_bytes())?;
733 if let Some((m, id)) = &existing
734 && m.entries[*id].kind == tag::TYPE_DIR as u8
735 {
736 return Err(Error::InvalidArgument(format!(
737 "littlefs: {name:?} already exists as a directory"
738 )));
739 }
740
741 let data = self.write_data(dev, body, len)?;
742 match existing {
743 Some((mut m, id)) => {
745 if let Some(old) = m.entries[id].data.clone() {
746 self.free_data(dev, &old)?;
747 }
748 m.entries[id].data = Some(data);
749 self.commit(dev, &mut m)
750 }
751 None => self.insert_entry(
752 dev,
753 head,
754 Entry {
755 kind: tag::TYPE_REG as u8,
756 name: name.into_bytes(),
757 data: Some(data),
758 attrs: Vec::new(),
759 },
760 ),
761 }
762 }
763
764 fn write_data(
767 &mut self,
768 dev: &mut dyn BlockDevice,
769 body: &mut dyn Read,
770 len: u64,
771 ) -> Result<Struct> {
772 if len <= self.inline_max as u64 {
773 let mut buf = vec![0u8; len as usize];
774 body.read_exact(&mut buf)?;
775 return Ok(Struct::Inline(buf));
776 }
777 let geom = self.geom;
778 let mut src = ctz::ReaderSource { body };
779 let alloc = self.allocator(dev)?;
780 let head =
781 ctz::write_blocks(dev, &geom, alloc, 0, None, 0, &mut src, len)?.ok_or_else(|| {
782 Error::InvalidArgument("littlefs: empty skip-list for a non-empty file".into())
783 })?;
784 Ok(Struct::Ctz {
785 head,
786 size: len as u32,
787 })
788 }
789
790 fn make_dir(&mut self, dev: &mut dyn BlockDevice, path: &Path) -> Result<()> {
794 let (head, name) = self.parent_head(dev, path)?;
795 self.check_name(&name)?;
796 if let Some((m, id)) = self.find_in_dir(dev, head, name.as_bytes())? {
797 return if m.entries[id].kind == tag::TYPE_DIR as u8 {
798 Ok(())
799 } else {
800 Err(Error::InvalidArgument(format!(
801 "littlefs: {name:?} already exists"
802 )))
803 };
804 }
805
806 let mut dir = self.new_pair(dev)?;
807 let pred_pair = self
810 .chain(dev, head)?
811 .last()
812 .expect("a directory always has at least one pair")
813 .pair;
814 let mut pred = self.fetch(dev, pred_pair)?;
815 dir.tail = pred.tail;
816 dir.hard = false;
817 self.commit(dev, &mut dir)?;
818 pred.tail = Some(dir.pair);
819 pred.hard = false;
820 self.commit(dev, &mut pred)?;
821
822 self.insert_entry(
823 dev,
824 head,
825 Entry {
826 kind: tag::TYPE_DIR as u8,
827 name: name.into_bytes(),
828 data: Some(Struct::Dir(dir.pair)),
829 attrs: Vec::new(),
830 },
831 )
832 }
833
834 fn remove_path(&mut self, dev: &mut dyn BlockDevice, path: &Path) -> Result<()> {
836 let Resolved::Entry { mdir, id } = self.resolve(dev, path)? else {
837 return Err(Error::InvalidArgument(
838 "littlefs: cannot remove the root directory".into(),
839 ));
840 };
841 let entry = mdir.entries[id].clone();
842
843 if entry.kind == tag::TYPE_DIR as u8 {
844 let head = match &entry.data {
845 Some(Struct::Dir(p)) => *p,
846 _ => {
847 return Err(Error::InvalidImage(
848 "littlefs: directory entry without a metadata pair".into(),
849 ));
850 }
851 };
852 let chain = self.chain(dev, head)?;
853 if chain.iter().any(|m| m.entries.iter().any(Entry::is_file)) {
854 return Err(Error::InvalidArgument(format!(
855 "littlefs: directory {:?} is not empty",
856 path.display()
857 )));
858 }
859
860 let mut parent = mdir;
863 parent.entries.remove(id);
864 self.commit(dev, &mut parent)?;
865
866 let last = chain.last().expect("chain is never empty");
867 let mut pred = self.find_pred(dev, head)?;
868 pred.tail = last.tail;
869 pred.hard = last.hard;
870 for m in &chain {
874 if let Some(g) = m.gdelta {
875 let mut acc = pred.gdelta.unwrap_or([0u8; 12]);
876 for (a, b) in acc.iter_mut().zip(g.iter()) {
877 *a ^= *b;
878 }
879 pred.gdelta = if acc == [0u8; 12] { None } else { Some(acc) };
880 }
881 }
882 self.commit(dev, &mut pred)?;
883
884 for m in &chain {
885 self.cache.remove(m.pair);
886 let a = self.allocator(dev)?;
887 a.free(m.pair[0]);
888 a.free(m.pair[1]);
889 }
890 return Ok(());
891 }
892
893 if let Some(data) = &entry.data {
894 self.free_data(dev, data)?;
895 }
896 let mut parent = mdir;
897 parent.entries.remove(id);
898 self.commit(dev, &mut parent)
899 }
900
901 fn check_name(&self, name: &str) -> Result<()> {
903 if name.is_empty() {
904 return Err(Error::InvalidArgument("littlefs: empty name".into()));
905 }
906 if name.len() > self.name_max as usize {
907 return Err(Error::InvalidArgument(format!(
908 "littlefs: name {name:?} is longer than the volume's {}-byte limit",
909 self.name_max
910 )));
911 }
912 Ok(())
913 }
914
915 fn list_dir(&mut self, dev: &mut dyn BlockDevice, path: &Path) -> Result<Vec<DirEntry>> {
917 let r = self.resolve(dev, path)?;
918 let head = self.dir_head(&r)?;
919 let mut out = Vec::new();
920 for m in self.chain(dev, head)? {
921 for (id, e) in m.entries.iter().enumerate().filter(|(_, e)| e.is_file()) {
922 out.push(DirEntry {
923 name: String::from_utf8_lossy(&e.name).into_owned(),
924 inode: synthetic_inode(m.pair, id, e),
925 kind: entry_kind(e),
926 size: entry_size(e),
927 });
928 }
929 }
930 Ok(out)
931 }
932
933 fn file_source(&mut self, dev: &mut dyn BlockDevice, path: &Path) -> Result<rw::Source> {
935 let Resolved::Entry { mdir, id } = self.resolve(dev, path)? else {
936 return Err(Error::InvalidArgument(
937 "littlefs: the root is not a file".into(),
938 ));
939 };
940 let e = &mdir.entries[id];
941 if e.kind != tag::TYPE_REG as u8 {
942 return Err(Error::InvalidArgument(format!(
943 "littlefs: {:?} is not a regular file",
944 path.display()
945 )));
946 }
947 Ok(match &e.data {
948 Some(Struct::Inline(d)) => rw::Source::Inline(d.clone()),
949 Some(Struct::Ctz { head, size }) => rw::Source::Ctz {
950 head: *head,
951 size: *size,
952 },
953 _ => rw::Source::Inline(Vec::new()),
954 })
955 }
956}
957
958fn pick_inline_max(geom: &Geom, requested: Option<u32>) -> Result<u32> {
961 let ceiling = (tag::MAX_SIZE as u32).min(geom.split_limit() as u32 / 2);
962 let v = requested.unwrap_or_else(|| (geom.block_size / 8).min(ceiling));
963 if v > ceiling {
964 return Err(Error::InvalidArgument(format!(
965 "littlefs: inline_max {v} exceeds the {ceiling} bytes a {}-byte block can inline",
966 geom.block_size
967 )));
968 }
969 Ok(v)
970}
971
972fn components(path: &Path) -> Result<Vec<&str>> {
982 let s = path
983 .to_str()
984 .ok_or_else(|| Error::InvalidArgument("littlefs: non-UTF-8 path".into()))?;
985 let mut out: Vec<&str> = Vec::new();
986 for c in s.split(['/', '\\']) {
987 match c {
988 "" | "." => {}
989 ".." => {
990 if out.pop().is_none() {
991 return Err(Error::InvalidArgument(
992 "littlefs: path escapes the root".into(),
993 ));
994 }
995 }
996 other => out.push(other),
997 }
998 }
999 Ok(out)
1000}
1001
1002fn entry_kind(e: &Entry) -> EntryKind {
1003 if e.kind == tag::TYPE_DIR as u8 {
1004 EntryKind::Dir
1005 } else {
1006 EntryKind::Regular
1007 }
1008}
1009
1010fn entry_size(e: &Entry) -> u64 {
1011 match &e.data {
1012 Some(Struct::Inline(d)) => d.len() as u64,
1013 Some(Struct::Ctz { size, .. }) => *size as u64,
1014 _ => 0,
1015 }
1016}
1017
1018fn synthetic_inode(pair: [u32; 2], id: usize, e: &Entry) -> u32 {
1024 match &e.data {
1025 Some(Struct::Dir(p)) => p[0].max(1),
1026 _ => 0x8000_0000 | (pair[0].wrapping_shl(8) & 0x7fff_ff00) | (id as u32 & 0xff),
1027 }
1028}
1029
1030fn xattr_name(kind: u8) -> String {
1032 format!("{XATTR_PREFIX}{kind}")
1033}
1034
1035fn xattr_type(name: &str) -> Result<u8> {
1037 name.strip_prefix(XATTR_PREFIX)
1038 .and_then(|n| n.parse::<u8>().ok())
1039 .ok_or_else(|| {
1040 Error::Unsupported(format!(
1041 "littlefs: only {XATTR_PREFIX}<0-255> attributes can be stored (got {name:?})"
1042 ))
1043 })
1044}
1045
1046impl Filesystem for LittleFs {
1047 fn streams_immediately(&self) -> bool {
1048 true
1049 }
1050
1051 fn create_file(
1052 &mut self,
1053 dev: &mut dyn BlockDevice,
1054 path: &Path,
1055 src: FileSource,
1056 _meta: FileMeta,
1057 ) -> Result<()> {
1058 let (mut reader, len) = src.open()?;
1059 self.write_file(dev, path, &mut reader, len)
1060 }
1061
1062 fn create_file_streaming(
1063 &mut self,
1064 dev: &mut dyn BlockDevice,
1065 path: &Path,
1066 body: &mut dyn Read,
1067 len: u64,
1068 _meta: FileMeta,
1069 ) -> Result<()> {
1070 self.write_file(dev, path, body, len)
1071 }
1072
1073 fn create_dir(
1074 &mut self,
1075 dev: &mut dyn BlockDevice,
1076 path: &Path,
1077 _meta: FileMeta,
1078 ) -> Result<()> {
1079 if components(path)?.is_empty() {
1080 return Ok(()); }
1082 self.make_dir(dev, path)
1083 }
1084
1085 fn create_symlink(
1086 &mut self,
1087 _dev: &mut dyn BlockDevice,
1088 _path: &Path,
1089 _target: &Path,
1090 _meta: FileMeta,
1091 ) -> Result<()> {
1092 Err(Error::Unsupported(
1093 "littlefs: the format has no symbolic links".into(),
1094 ))
1095 }
1096
1097 fn create_device(
1098 &mut self,
1099 _dev: &mut dyn BlockDevice,
1100 _path: &Path,
1101 _kind: crate::fs::DeviceKind,
1102 _major: u32,
1103 _minor: u32,
1104 _meta: FileMeta,
1105 ) -> Result<()> {
1106 Err(Error::Unsupported(
1107 "littlefs: the format has no device nodes".into(),
1108 ))
1109 }
1110
1111 fn remove(&mut self, dev: &mut dyn BlockDevice, path: &Path) -> Result<()> {
1112 self.remove_path(dev, path)
1113 }
1114
1115 fn list(&mut self, dev: &mut dyn BlockDevice, path: &Path) -> Result<Vec<DirEntry>> {
1116 self.list_dir(dev, path)
1117 }
1118
1119 fn read_file<'a>(
1120 &'a mut self,
1121 dev: &'a mut dyn BlockDevice,
1122 path: &Path,
1123 ) -> Result<Box<dyn Read + 'a>> {
1124 let src = self.file_source(dev, path)?;
1125 Ok(Box::new(rw::FileReader::new(dev, self.geom, src)))
1126 }
1127
1128 fn open_file_ro<'a>(
1129 &'a mut self,
1130 dev: &'a mut dyn BlockDevice,
1131 path: &Path,
1132 ) -> Result<Box<dyn crate::fs::FileReadHandle + 'a>> {
1133 let src = self.file_source(dev, path)?;
1134 Ok(Box::new(rw::FileReader::new(dev, self.geom, src)))
1135 }
1136
1137 fn open_file_rw<'a>(
1138 &'a mut self,
1139 dev: &'a mut dyn BlockDevice,
1140 path: &Path,
1141 flags: crate::fs::OpenFlags,
1142 meta: Option<FileMeta>,
1143 ) -> Result<Box<dyn crate::fs::FileHandle + 'a>> {
1144 rw::open_rw(self, dev, path, flags, meta)
1145 }
1146
1147 fn truncate(&mut self, dev: &mut dyn BlockDevice, path: &Path, new_size: u64) -> Result<()> {
1148 rw::truncate(self, dev, path, new_size)
1149 }
1150
1151 fn rename(
1152 &mut self,
1153 dev: &mut dyn BlockDevice,
1154 old_path: &Path,
1155 new_path: &Path,
1156 ) -> Result<()> {
1157 let Resolved::Entry { mdir, id } = self.resolve(dev, old_path)? else {
1158 return Err(Error::InvalidArgument(
1159 "littlefs: cannot rename the root directory".into(),
1160 ));
1161 };
1162 let entry = mdir.entries[id].clone();
1163 let (dst_head, name) = self.parent_head(dev, new_path)?;
1164 self.check_name(&name)?;
1165 if self.find_in_dir(dev, dst_head, name.as_bytes())?.is_some() {
1166 return Err(Error::InvalidArgument(format!(
1167 "littlefs: {:?} already exists",
1168 new_path.display()
1169 )));
1170 }
1171
1172 let mut src = mdir;
1175 src.entries.remove(id);
1176 self.commit(dev, &mut src)?;
1177 self.insert_entry(
1178 dev,
1179 dst_head,
1180 Entry {
1181 kind: entry.kind,
1182 name: name.into_bytes(),
1183 data: entry.data,
1184 attrs: entry.attrs,
1185 },
1186 )
1187 }
1188
1189 fn getattr(&mut self, dev: &mut dyn BlockDevice, path: &Path) -> Result<FileAttrs> {
1190 let r = self.resolve(dev, path)?;
1191 let (kind, size, inode) = match &r {
1192 Resolved::Root => (EntryKind::Dir, 0, self.root[0].max(1)),
1193 Resolved::Entry { mdir, id } => {
1194 let e = &mdir.entries[*id];
1195 (
1196 entry_kind(e),
1197 entry_size(e),
1198 synthetic_inode(mdir.pair, *id, e),
1199 )
1200 }
1201 };
1202 Ok(FileAttrs {
1205 kind,
1206 mode: if kind == EntryKind::Dir { 0o755 } else { 0o644 },
1207 uid: 0,
1208 gid: 0,
1209 size,
1210 blocks: size.div_ceil(512),
1211 nlink: if kind == EntryKind::Dir { 2 } else { 1 },
1212 atime: 0,
1213 mtime: 0,
1214 ctime: 0,
1215 rdev: 0,
1216 inode,
1217 })
1218 }
1219
1220 fn list_xattrs(&mut self, dev: &mut dyn BlockDevice, path: &Path) -> Result<Vec<XattrPair>> {
1221 let Resolved::Entry { mdir, id } = self.resolve(dev, path)? else {
1222 return Ok(Vec::new());
1223 };
1224 Ok(mdir.entries[id]
1225 .attrs
1226 .iter()
1227 .map(|(k, v)| XattrPair {
1228 name: xattr_name(*k),
1229 value: v.clone(),
1230 })
1231 .collect())
1232 }
1233
1234 fn set_xattr(
1235 &mut self,
1236 dev: &mut dyn BlockDevice,
1237 path: &Path,
1238 name: &str,
1239 value: &[u8],
1240 ) -> Result<()> {
1241 let kind = xattr_type(name)?;
1242 if value.len() > self.attr_max as usize {
1243 return Err(Error::InvalidArgument(format!(
1244 "littlefs: attribute value of {} bytes exceeds the volume's {}-byte limit",
1245 value.len(),
1246 self.attr_max
1247 )));
1248 }
1249 let Resolved::Entry { mut mdir, id } = self.resolve(dev, path)? else {
1250 return Err(Error::InvalidArgument(
1251 "littlefs: the root has no attributes".into(),
1252 ));
1253 };
1254 let attrs = &mut mdir.entries[id].attrs;
1255 attrs.retain(|(k, _)| *k != kind);
1256 attrs.push((kind, value.to_vec()));
1257 attrs.sort_by_key(|(k, _)| *k);
1258 self.commit(dev, &mut mdir)
1259 }
1260
1261 fn remove_xattr(&mut self, dev: &mut dyn BlockDevice, path: &Path, name: &str) -> Result<()> {
1262 let kind = xattr_type(name)?;
1263 let Resolved::Entry { mut mdir, id } = self.resolve(dev, path)? else {
1264 return Err(Error::InvalidArgument(
1265 "littlefs: the root has no attributes".into(),
1266 ));
1267 };
1268 mdir.entries[id].attrs.retain(|(k, _)| *k != kind);
1269 self.commit(dev, &mut mdir)
1270 }
1271
1272 fn statfs(&mut self, dev: &mut dyn BlockDevice) -> Result<StatFs> {
1273 let used = self.allocator(dev)?.used() as u64;
1274 let total = self.geom.block_count as u64;
1275 Ok(StatFs {
1276 block_size: self.geom.block_size,
1277 blocks: total,
1278 blocks_free: total.saturating_sub(used),
1279 blocks_avail: total.saturating_sub(used),
1280 inodes: 0,
1281 inodes_free: 0,
1282 name_max: self.name_max,
1283 })
1284 }
1285
1286 fn flush(&mut self, dev: &mut dyn BlockDevice) -> Result<()> {
1287 dev.sync()
1290 }
1291
1292 fn mutation_capability(&self) -> MutationCapability {
1293 MutationCapability::Mutable
1294 }
1295}
1296
1297impl crate::fs::FilesystemFactory for LittleFs {
1298 type FormatOpts = LittleFsFormatOpts;
1299
1300 fn format(dev: &mut dyn BlockDevice, opts: &Self::FormatOpts) -> Result<Self> {
1301 LittleFs::format(dev, opts)
1302 }
1303
1304 fn open(dev: &mut dyn BlockDevice) -> Result<Self> {
1305 LittleFs::open(dev)
1306 }
1307
1308 fn size_plan(opts: &Self::FormatOpts) -> Option<Box<dyn crate::fs::FsSizePlan>> {
1309 Some(Box::new(LittleFsSizePlan::new(opts)))
1310 }
1311}