1use super::*;
2
3impl<'a> ImageStorage<'a> {
4 fn full_len_is_available(&self, cfg: Config) -> bool {
5 let Some(expected) = cfg.block_size.checked_mul(cfg.block_count) else {
6 return false;
7 };
8 match self {
9 ImageStorage::Borrowed(image) => image.len() >= expected,
10 ImageStorage::Owned(image) => image.len() >= expected,
11 ImageStorage::Device { device, .. } => {
12 let device_cfg = device.config();
13 device_cfg.block_size == cfg.block_size && device_cfg.block_count >= cfg.block_count
14 }
15 }
16 }
17
18 fn read(&self, cfg: Config, block: u32, off: usize, out: &mut [u8]) -> Result<()> {
19 if block as usize >= cfg.block_count {
20 return Err(Error::OutOfBounds);
21 }
22 if off.checked_add(out.len()).ok_or(Error::OutOfBounds)? > cfg.block_size {
23 return Err(Error::OutOfBounds);
24 }
25
26 match self {
27 ImageStorage::Borrowed(image) => {
28 let start = block as usize * cfg.block_size + off;
29 let end = start + out.len();
30 out.copy_from_slice(image.get(start..end).ok_or(Error::OutOfBounds)?);
31 Ok(())
32 }
33 ImageStorage::Owned(image) => {
34 let start = block as usize * cfg.block_size + off;
35 let end = start + out.len();
36 out.copy_from_slice(image.get(start..end).ok_or(Error::OutOfBounds)?);
37 Ok(())
38 }
39 ImageStorage::Device { device, cache } => cache.read(*device, cfg, block, off, out),
40 }
41 }
42
43 fn read_block(&self, cfg: Config, block: u32, out: &mut [u8]) -> Result<()> {
44 if out.len() != cfg.block_size {
45 return Err(Error::InvalidConfig);
46 }
47 self.read(cfg, block, 0, out)
48 }
49}
50
51impl<'a> Filesystem<'a> {
52 pub fn mount(image: &'a [u8], cfg: Config) -> Result<Self> {
55 Self::mount_with_options(image, cfg, FilesystemOptions::default())
56 }
57
58 pub fn mount_with_options(
60 image: &'a [u8],
61 cfg: Config,
62 options: FilesystemOptions,
63 ) -> Result<Self> {
64 Self::mount_storage(ImageStorage::Borrowed(image), cfg, options)
65 }
66
67 pub fn mount_device<D: BlockDevice + 'a>(device: &'a D) -> Result<Self> {
74 Self::mount_device_with_options(device, FilesystemOptions::default())
75 }
76
77 pub fn mount_device_with_options<D: BlockDevice + 'a>(
81 device: &'a D,
82 options: FilesystemOptions,
83 ) -> Result<Self> {
84 Self::mount_device_cached(device, options, 4)
85 }
86
87 pub(crate) fn mount_device_cached<D: BlockDevice + 'a>(
88 device: &'a D,
89 options: FilesystemOptions,
90 cache_slots: usize,
91 ) -> Result<Self> {
92 let cfg = device.config();
93 let options = options.validate(cfg)?;
94 Self::mount_storage(
95 ImageStorage::Device {
96 device,
97 cache: Rc::new(ReadBlockCache::new(
98 options.cache_size_for(cfg),
99 cache_slots,
100 )),
101 },
102 cfg,
103 options,
104 )
105 }
106
107 fn mount_storage(
108 image: ImageStorage<'a>,
109 cfg: Config,
110 options: FilesystemOptions,
111 ) -> Result<Self> {
112 let options = options.validate(cfg)?;
113 if cfg.block_size < 16 || cfg.block_count < 2 {
114 return Err(Error::InvalidConfig);
115 }
116 if !image.full_len_is_available(cfg) {
117 return Err(Error::OutOfBounds);
118 }
119
120 let root = Self::read_pair_from_storage(&image, cfg, [0, 1])?;
121
122 let name = root.find(Tag::new(LFS_TYPE_SUPERBLOCK, 0, 8))?;
126 if name.data != b"littlefs" {
127 return Err(Error::Corrupt);
128 }
129
130 let sb = root.find(Tag::new(LFS_TYPE_INLINESTRUCT, 0, 24))?;
131 if sb.data.len() != 24 {
132 return Err(Error::Corrupt);
133 }
134 let info = FsInfo {
135 disk_version: le32(&sb.data[0..4])?,
136 block_size: le32(&sb.data[4..8])?,
137 block_count: le32(&sb.data[8..12])?,
138 name_max: le32(&sb.data[12..16])?,
139 file_max: le32(&sb.data[16..20])?,
140 attr_max: le32(&sb.data[20..24])?,
141 };
142
143 if info.disk_version != SUPPORTED_DISK_VERSION {
144 return Err(Error::Unsupported);
145 }
146 if info.block_size as usize != cfg.block_size {
147 return Err(Error::InvalidConfig);
148 }
149 if info.block_count as usize > cfg.block_count {
150 return Err(Error::InvalidConfig);
151 }
152 if info.name_max > options.name_max
153 || info.file_max > options.file_max
154 || info.attr_max > options.attr_max
155 {
156 return Err(Error::InvalidConfig);
157 }
158
159 let global_state = Self::collect_global_state(&image, cfg, &root)?;
160 let allocation_seed = Self::collect_allocation_seed(&image, cfg, &root)?;
161
162 Ok(Self {
163 image,
164 cfg,
165 root,
166 info,
167 options,
168 global_state,
169 allocation_seed,
170 })
171 }
172
173 pub fn info(&self) -> &FsInfo {
174 &self.info
175 }
176
177 pub fn options(&self) -> FilesystemOptions {
178 self.options
179 }
180
181 pub fn limits(&self) -> FilesystemLimits {
182 FilesystemLimits {
183 block_size: self.info.block_size,
184 block_count: self.info.block_count,
185 name_max: self.info.name_max,
186 file_max: self.info.file_max,
187 attr_max: self.info.attr_max,
188 }
189 }
190
191 pub(super) fn global_state(&self) -> GlobalState {
192 self.global_state
193 }
194
195 pub(super) fn allocation_seed(&self) -> u32 {
196 self.allocation_seed
197 }
198
199 pub(super) fn inline_threshold(&self) -> usize {
200 self.options.inline_threshold(self.cfg, self.info.attr_max)
201 }
202
203 pub(super) fn read_pair(&self, pair: [u32; 2]) -> Result<MetadataPair> {
204 Self::read_pair_from_storage(&self.image, self.cfg, pair)
205 }
206
207 fn read_pair_from_storage(
208 image: &ImageStorage<'_>,
209 cfg: Config,
210 pair: [u32; 2],
211 ) -> Result<MetadataPair> {
212 MetadataPair::read_from(cfg, pair, |block, out| image.read_block(cfg, block, out))
213 }
214
215 pub fn directory_usage(&self, path: &str) -> Result<DirectoryUsage> {
216 let pair = self.resolve_dir(path)?;
217 self.directory_usage_from_pair(&pair)
218 }
219
220 pub fn root_entries(&self) -> Result<Vec<DirEntry>> {
221 self.read_dir("/")
222 }
223
224 pub fn read_dir(&self, path: &str) -> Result<Vec<DirEntry>> {
225 let pair = self.resolve_dir(path)?;
226 self.entries_in_pair(&pair)
227 }
228
229 pub fn read_dir_with<F>(&self, path: &str, mut visitor: F) -> Result<()>
236 where
237 F: FnMut(DirEntry) -> Result<()>,
238 {
239 let pair = self.resolve_dir(path)?;
240 self.for_each_file_in_pair_chain(&pair, |file| visitor(file.dir_entry()))
241 }
242
243 pub fn open_dir<'fs>(&'fs self, path: &str) -> Result<DirHandle<'fs, 'a>> {
244 let pair = self.resolve_dir(path)?;
245 Ok(DirHandle {
246 fs: self,
247 head: pair.pair,
248 pos: 0,
249 })
250 }
251
252 pub fn stat(&self, path: &str) -> Result<DirEntry> {
253 if path.is_empty() || path == "/" {
256 return Ok(DirEntry {
257 name: alloc::string::String::new(),
258 ty: FileType::Dir,
259 size: 0,
260 });
261 }
262
263 self.resolve_dir_entry(path)
264 }
265
266 pub fn read_file(&self, path: &str) -> Result<Vec<u8>> {
267 let file = self.resolve_file_no_attrs(path)?;
268 if file.ty != FileType::File {
269 return Err(Error::IsDir);
270 }
271
272 match file.data {
273 FileData::Inline(data) => Ok(data),
274 FileData::Ctz { head, size } => self.read_ctz(head, size),
275 FileData::Directory(_) => Err(Error::IsDir),
276 }
277 }
278
279 pub fn read_file_into(&self, path: &str, out: &mut [u8]) -> Result<usize> {
287 let file = self.resolve_file_no_attrs(path)?;
288 if file.ty != FileType::File {
289 return Err(Error::IsDir);
290 }
291
292 let size = file.dir_entry().size as usize;
293 if out.len() < size {
294 return Err(Error::NoSpace);
295 }
296
297 match file.data {
298 FileData::Inline(data) => {
299 out[..data.len()].copy_from_slice(&data);
300 Ok(data.len())
301 }
302 FileData::Ctz { head, size } => self.read_ctz_into(head, size, out),
303 FileData::Directory(_) => Err(Error::IsDir),
304 }
305 }
306
307 pub fn read_file_at(&self, path: &str, offset: usize, out: &mut [u8]) -> Result<usize> {
313 let file = self.resolve_file_no_attrs(path)?;
314 if file.ty != FileType::File {
315 return Err(Error::IsDir);
316 }
317
318 let size = file.dir_entry().size as usize;
319 if offset >= size || out.is_empty() {
320 return Ok(0);
321 }
322 let n = core::cmp::min(out.len(), size - offset);
323 match file.data {
324 FileData::Inline(data) => {
325 out[..n].copy_from_slice(&data[offset..offset + n]);
326 Ok(n)
327 }
328 FileData::Ctz { head, size } => self.read_ctz_range(head, size, offset, &mut out[..n]),
329 FileData::Directory(_) => Err(Error::IsDir),
330 }
331 }
332
333 pub(super) fn read_file_data_at(
334 &self,
335 data: &FileData,
336 offset: usize,
337 out: &mut [u8],
338 ) -> Result<usize> {
339 let size = match data {
340 FileData::Inline(data) => data.len(),
341 FileData::Ctz { size, .. } => *size as usize,
342 FileData::Directory(_) => return Err(Error::IsDir),
343 };
344 if offset >= size || out.is_empty() {
345 return Ok(0);
346 }
347 let n = core::cmp::min(out.len(), size - offset);
348 match data {
349 FileData::Inline(data) => {
350 out[..n].copy_from_slice(&data[offset..offset + n]);
351 Ok(n)
352 }
353 FileData::Ctz { head, size } => {
354 self.read_ctz_range(*head, *size, offset, &mut out[..n])
355 }
356 FileData::Directory(_) => Err(Error::IsDir),
357 }
358 }
359
360 pub fn read_attr(&self, path: &str, attr_type: u8) -> Result<Vec<u8>> {
361 self.resolve_attr(path, attr_type)?.ok_or(Error::NotFound)
362 }
363
364 pub fn read_attr_into(&self, path: &str, attr_type: u8, out: &mut [u8]) -> Result<usize> {
370 self.resolve_attr_into(path, attr_type, out)?
371 .ok_or(Error::NotFound)
372 }
373
374 pub fn walk(&self, path: &str) -> Result<Vec<WalkEntry>> {
375 let base = normalize_dir_path(path)?;
376 let mut out = Vec::new();
377 self.walk_with(&base, |entry| {
378 out.push(entry);
379 Ok(())
380 })?;
381 Ok(out)
382 }
383
384 pub fn walk_with<F>(&self, path: &str, mut visitor: F) -> Result<()>
391 where
392 F: FnMut(WalkEntry) -> Result<()>,
393 {
394 let base = normalize_dir_path(path)?;
395 self.walk_dir_with(&base, &mut visitor)
396 }
397
398 pub fn used_blocks(&self) -> Result<Vec<bool>> {
406 let mut used = alloc::vec![false; self.cfg.block_count];
407 let mut seen_pairs = Vec::new();
408 self.mark_pair_tree(&self.root, &mut used, &mut seen_pairs)?;
409 Ok(used)
410 }
411
412 fn walk_dir_with<F>(&self, path: &str, visitor: &mut F) -> Result<()>
413 where
414 F: FnMut(WalkEntry) -> Result<()>,
415 {
416 let mut entries = self.read_dir(path)?;
417 entries.sort_by(|a, b| a.name.cmp(&b.name));
418
419 for entry in entries {
420 let child_path = join_path(path, &entry.name);
421 visitor(WalkEntry {
422 path: child_path.clone(),
423 entry: entry.clone(),
424 })?;
425 if entry.ty == FileType::Dir {
426 self.walk_dir_with(&child_path, visitor)?;
427 }
428 }
429
430 Ok(())
431 }
432
433 pub(super) fn resolve_dir(&self, path: &str) -> Result<MetadataPair> {
434 if path.is_empty() || path == "/" {
439 return Ok(self.root.clone());
440 }
441
442 let mut pair = self.root.clone();
443 for component in components(path)? {
444 let file = self.find_file_in_pair_chain_no_attrs(&pair, component)?;
445 match file.data {
446 FileData::Directory(child) if file.ty == FileType::Dir => {
447 pair = self.read_pair(child)?;
448 }
449 _ => return Err(Error::NotDir),
450 }
451 }
452 Ok(pair)
453 }
454
455 fn resolve_dir_entry(&self, path: &str) -> Result<DirEntry> {
456 let parts = components(path)?;
457 let (name, parents) = parts.split_last().ok_or(Error::InvalidPath)?;
458
459 let mut pair = self.root.clone();
460 for component in parents {
461 let file = self.find_file_in_pair_chain_no_attrs(&pair, component)?;
462 match file.data {
463 FileData::Directory(child) if file.ty == FileType::Dir => {
464 pair = self.read_pair(child)?;
465 }
466 _ => return Err(Error::NotDir),
467 }
468 }
469
470 self.find_dir_entry_in_pair_chain(&pair, name)
471 }
472
473 pub(super) fn resolve_file_no_attrs(&self, path: &str) -> Result<FileRecord> {
474 let parts = components(path)?;
475 let (name, parents) = parts.split_last().ok_or(Error::InvalidPath)?;
476
477 let mut pair = self.root.clone();
478 for component in parents {
479 let file = self.find_file_in_pair_chain_no_attrs(&pair, component)?;
480 match file.data {
481 FileData::Directory(child) if file.ty == FileType::Dir => {
482 pair = self.read_pair(child)?;
483 }
484 _ => return Err(Error::NotDir),
485 }
486 }
487
488 self.find_file_in_pair_chain_no_attrs(&pair, name)
489 }
490
491 fn resolve_attr(&self, path: &str, attr_type: u8) -> Result<Option<Vec<u8>>> {
492 let parts = components(path)?;
493 let (name, parents) = parts.split_last().ok_or(Error::InvalidPath)?;
494
495 let mut pair = self.root.clone();
496 for component in parents {
497 let file = self.find_file_in_pair_chain_no_attrs(&pair, component)?;
498 match file.data {
499 FileData::Directory(child) if file.ty == FileType::Dir => {
500 pair = self.read_pair(child)?;
501 }
502 _ => return Err(Error::NotDir),
503 }
504 }
505
506 self.find_attr_in_pair_chain(&pair, name, attr_type)
507 }
508
509 fn resolve_attr_into(
510 &self,
511 path: &str,
512 attr_type: u8,
513 out: &mut [u8],
514 ) -> Result<Option<usize>> {
515 let parts = components(path)?;
516 let (name, parents) = parts.split_last().ok_or(Error::InvalidPath)?;
517
518 let mut pair = self.root.clone();
519 for component in parents {
520 let file = self.find_file_in_pair_chain_no_attrs(&pair, component)?;
521 match file.data {
522 FileData::Directory(child) if file.ty == FileType::Dir => {
523 pair = self.read_pair(child)?;
524 }
525 _ => return Err(Error::NotDir),
526 }
527 }
528
529 self.find_attr_in_pair_chain_into(&pair, name, attr_type, out)
530 }
531
532 fn entries_in_pair(&self, pair: &MetadataPair) -> Result<Vec<DirEntry>> {
533 let mut entries = Vec::new();
534 self.for_each_file_in_pair_chain(pair, |file| {
535 entries.push(file.dir_entry());
536 Ok(())
537 })?;
538 Ok(entries)
539 }
540
541 pub(super) fn dir_entry_at(&self, head: [u32; 2], index: usize) -> Result<Option<DirEntry>> {
542 let mut current = if head == self.root.pair {
543 self.root.clone()
544 } else {
545 self.read_pair(head)?
546 };
547 let mut seen = Vec::<[u32; 2]>::new();
548 let mut remaining = index;
549
550 loop {
551 if seen.contains(¤t.pair) {
552 return Err(Error::Corrupt);
553 }
554 seen.push(current.pair);
555
556 let mut found = None;
557 let mut local_index = 0usize;
558 current.fold_dir(|_id, file| {
559 if found.is_none() && local_index == remaining {
560 found = Some(file.dir_entry());
561 }
562 local_index += 1;
563 Ok(())
564 })?;
565 if found.is_some() {
566 return Ok(found);
567 }
568 remaining = remaining.saturating_sub(local_index);
569
570 match current.hardtail()? {
571 Some(next) if next != [LFS_NULL, LFS_NULL] => {
572 current = self.read_pair(next)?;
573 }
574 _ => return Ok(None),
575 }
576 }
577 }
578
579 pub(super) fn files_in_pair_chain(&self, pair: &MetadataPair) -> Result<Vec<FileRecord>> {
580 let mut files = Vec::new();
581 self.for_each_file_in_pair_chain(pair, |file| {
582 files.push(file);
583 Ok(())
584 })?;
585 Ok(files)
586 }
587
588 pub(super) fn for_each_file_in_pair_chain<F>(
589 &self,
590 pair: &MetadataPair,
591 mut visitor: F,
592 ) -> Result<()>
593 where
594 F: FnMut(FileRecord) -> Result<()>,
595 {
596 let mut current = pair.clone();
597 let mut seen = Vec::<[u32; 2]>::new();
598
599 loop {
600 if seen.contains(¤t.pair) {
603 return Err(Error::Corrupt);
604 }
605 seen.push(current.pair);
606 current.fold_dir(|_id, file| visitor(file))?;
607
608 match current.hardtail()? {
609 Some(next) if next != [LFS_NULL, LFS_NULL] => {
610 current = self.read_pair(next)?;
611 }
612 _ => break,
613 }
614 }
615
616 Ok(())
617 }
618
619 fn find_file_in_pair_chain_no_attrs(
620 &self,
621 pair: &MetadataPair,
622 name: &str,
623 ) -> Result<FileRecord> {
624 let mut current = pair.clone();
625 let mut seen = Vec::<[u32; 2]>::new();
626
627 loop {
628 if seen.contains(¤t.pair) {
629 return Err(Error::Corrupt);
630 }
631 seen.push(current.pair);
632
633 if let Some(file) = current.find_name_no_attrs(name)? {
634 return Ok(file);
635 }
636
637 match current.hardtail()? {
638 Some(next) if next != [LFS_NULL, LFS_NULL] => {
639 current = self.read_pair(next)?;
640 }
641 _ => return Err(Error::NotFound),
642 }
643 }
644 }
645
646 fn find_dir_entry_in_pair_chain(&self, pair: &MetadataPair, name: &str) -> Result<DirEntry> {
647 let mut current = pair.clone();
648 let mut seen = Vec::<[u32; 2]>::new();
649
650 loop {
651 if seen.contains(¤t.pair) {
652 return Err(Error::Corrupt);
653 }
654 seen.push(current.pair);
655
656 if let Some(entry) = current.find_dir_entry(name)? {
657 return Ok(entry);
658 }
659
660 match current.hardtail()? {
661 Some(next) if next != [LFS_NULL, LFS_NULL] => {
662 current = self.read_pair(next)?;
663 }
664 _ => return Err(Error::NotFound),
665 }
666 }
667 }
668
669 fn find_attr_in_pair_chain(
670 &self,
671 pair: &MetadataPair,
672 name: &str,
673 attr_type: u8,
674 ) -> Result<Option<Vec<u8>>> {
675 let mut current = pair.clone();
676 let mut seen = Vec::<[u32; 2]>::new();
677
678 loop {
679 if seen.contains(¤t.pair) {
680 return Err(Error::Corrupt);
681 }
682 seen.push(current.pair);
683
684 if let Some(attr) = current.find_attr(name, attr_type)? {
685 return Ok(Some(attr));
686 }
687
688 match current.hardtail()? {
689 Some(next) if next != [LFS_NULL, LFS_NULL] => {
690 current = self.read_pair(next)?;
691 }
692 _ => return Ok(None),
693 }
694 }
695 }
696
697 fn find_attr_in_pair_chain_into(
698 &self,
699 pair: &MetadataPair,
700 name: &str,
701 attr_type: u8,
702 out: &mut [u8],
703 ) -> Result<Option<usize>> {
704 let mut current = pair.clone();
705 let mut seen = Vec::<[u32; 2]>::new();
706
707 loop {
708 if seen.contains(¤t.pair) {
709 return Err(Error::Corrupt);
710 }
711 seen.push(current.pair);
712
713 if let Some(len) = current.copy_attr_into(name, attr_type, out)? {
714 return Ok(Some(len));
715 }
716
717 match current.hardtail()? {
718 Some(next) if next != [LFS_NULL, LFS_NULL] => {
719 current = self.read_pair(next)?;
720 }
721 _ => return Ok(None),
722 }
723 }
724 }
725
726 fn directory_usage_from_pair(&self, pair: &MetadataPair) -> Result<DirectoryUsage> {
727 let mut entry_count = 0usize;
728 let mut metadata_pair_count = 0usize;
729 let mut current = pair.clone();
730 let mut seen = Vec::<[u32; 2]>::new();
731 let mut is_split = false;
732 loop {
733 if seen.contains(¤t.pair) {
734 return Err(Error::Corrupt);
735 }
736 seen.push(current.pair);
737 metadata_pair_count += 1;
738 entry_count += current.file_count()?;
739 match current.hardtail()? {
740 Some(next) if next != [LFS_NULL, LFS_NULL] => {
741 is_split = true;
742 current = self.read_pair(next)?;
743 }
744 _ => {
745 return Ok(DirectoryUsage {
746 entry_count,
747 metadata_pair_count,
748 is_split,
749 append_bytes_used: current.state.off,
750 append_bytes_remaining: self
751 .cfg
752 .block_size
753 .saturating_sub(current.state.off),
754 });
755 }
756 }
757 }
758 }
759
760 fn mark_pair_tree(
761 &self,
762 pair: &MetadataPair,
763 used: &mut [bool],
764 seen_pairs: &mut Vec<[u32; 2]>,
765 ) -> Result<()> {
766 let files = self.mark_pair_chain(pair, used, seen_pairs)?;
767 for file in files {
768 match file.data {
769 FileData::Directory(child) if file.ty == FileType::Dir => {
770 let child = self.read_pair(child)?;
771 self.mark_pair_tree(&child, used, seen_pairs)?;
772 }
773 FileData::Ctz { head, size } if file.ty == FileType::File => {
774 self.mark_ctz_blocks(head, size, used)?;
775 }
776 _ => {}
777 }
778 }
779 Ok(())
780 }
781
782 fn mark_pair_chain(
783 &self,
784 pair: &MetadataPair,
785 used: &mut [bool],
786 seen_pairs: &mut Vec<[u32; 2]>,
787 ) -> Result<Vec<FileRecord>> {
788 let mut files = Vec::new();
789 let mut current = pair.clone();
790
791 loop {
792 if seen_pairs.contains(¤t.pair) {
793 return Err(Error::Corrupt);
794 }
795 seen_pairs.push(current.pair);
796 self.mark_block(current.pair[0], used)?;
797 self.mark_block(current.pair[1], used)?;
798 files.extend(current.files()?);
799
800 match current.hardtail()? {
801 Some(next) if next != [LFS_NULL, LFS_NULL] => {
802 current = self.read_pair(next)?;
803 }
804 _ => break,
805 }
806 }
807
808 Ok(files)
809 }
810
811 fn mark_ctz_blocks(&self, head: u32, size: u32, used: &mut [bool]) -> Result<()> {
812 let mut pos = 0u32;
813 while pos < size {
814 let (block, off) = self.ctz_find(head, size, pos)?;
815 if block == LFS_NULL {
816 return Err(Error::Corrupt);
817 }
818 self.mark_block(block, used)?;
819
820 let off = off as usize;
821 if off >= self.cfg.block_size {
822 return Err(Error::Corrupt);
823 }
824 let diff = core::cmp::min((size - pos) as usize, self.cfg.block_size - off);
825 pos += diff as u32;
826 }
827 Ok(())
828 }
829
830 fn mark_block(&self, block: u32, used: &mut [bool]) -> Result<()> {
831 let slot = used.get_mut(block as usize).ok_or(Error::OutOfBounds)?;
832 if *slot {
833 return Err(Error::Corrupt);
834 }
835 *slot = true;
836 Ok(())
837 }
838
839 fn collect_global_state(
840 image: &ImageStorage<'_>,
841 cfg: Config,
842 root: &MetadataPair,
843 ) -> Result<GlobalState> {
844 if root.tail()?.is_some() {
845 return Self::collect_global_state_thread(image, cfg, root);
846 }
847 let mut global_state = GlobalState::default();
848 let mut seen_pairs = Vec::new();
849 Self::collect_global_state_pair_tree(image, cfg, root, &mut seen_pairs, &mut global_state)?;
850 Ok(global_state)
851 }
852
853 fn collect_allocation_seed(
854 image: &ImageStorage<'_>,
855 cfg: Config,
856 root: &MetadataPair,
857 ) -> Result<u32> {
858 if root.tail()?.is_some() {
859 return Self::collect_allocation_seed_thread(image, cfg, root);
860 }
861 let mut seed = 0;
862 let mut seen_pairs = Vec::new();
863 Self::collect_allocation_seed_pair_tree(image, cfg, root, &mut seen_pairs, &mut seed)?;
864 Ok(seed)
865 }
866
867 fn collect_allocation_seed_thread(
868 image: &ImageStorage<'_>,
869 cfg: Config,
870 root: &MetadataPair,
871 ) -> Result<u32> {
872 let mut seed = 0;
873 let mut current = root.clone();
874 let mut seen = Vec::<[u32; 2]>::new();
875 loop {
876 if seen.contains(¤t.pair) {
877 break;
878 }
879 seen.push(current.pair);
880 current.fold_commit_crcs_into_seed(&mut seed)?;
881
882 let Some(tail) = current.tail()? else {
883 break;
884 };
885 if tail.pair == [LFS_NULL, LFS_NULL] {
886 break;
887 }
888 current = Self::read_pair_from_storage(image, cfg, tail.pair)?;
889 }
890 Ok(seed)
891 }
892
893 fn collect_allocation_seed_pair_tree(
894 image: &ImageStorage<'_>,
895 cfg: Config,
896 pair: &MetadataPair,
897 seen_pairs: &mut Vec<[u32; 2]>,
898 seed: &mut u32,
899 ) -> Result<()> {
900 if seen_pairs.contains(&pair.pair) {
901 return Err(Error::Corrupt);
902 }
903 seen_pairs.push(pair.pair);
904 pair.fold_commit_crcs_into_seed(seed)?;
905 for file in pair.files()? {
906 if let FileData::Directory(child) = file.data
907 && file.ty == FileType::Dir
908 {
909 let child = Self::read_pair_from_storage(image, cfg, child)?;
910 Self::collect_allocation_seed_pair_tree(image, cfg, &child, seen_pairs, seed)?;
911 }
912 }
913 Ok(())
914 }
915
916 fn collect_global_state_thread(
917 image: &ImageStorage<'_>,
918 cfg: Config,
919 root: &MetadataPair,
920 ) -> Result<GlobalState> {
921 let mut global_state = GlobalState::default();
922 let mut current = root.clone();
923 let mut seen = Vec::<[u32; 2]>::new();
924 loop {
925 if seen.contains(¤t.pair) {
926 break;
932 }
933 seen.push(current.pair);
934 global_state.xor(current.global_state_delta()?);
935
936 let Some(tail) = current.tail()? else {
937 break;
938 };
939 if tail.pair == [LFS_NULL, LFS_NULL] {
940 break;
941 }
942 current = Self::read_pair_from_storage(image, cfg, tail.pair)?;
943 }
944 Ok(global_state)
945 }
946
947 fn collect_global_state_pair_tree(
948 image: &ImageStorage<'_>,
949 cfg: Config,
950 pair: &MetadataPair,
951 seen_pairs: &mut Vec<[u32; 2]>,
952 global_state: &mut GlobalState,
953 ) -> Result<()> {
954 let files =
955 Self::collect_global_state_pair_chain(image, cfg, pair, seen_pairs, global_state)?;
956 for file in files {
957 if let FileData::Directory(child) = file.data
958 && file.ty == FileType::Dir
959 {
960 let child = Self::read_pair_from_storage(image, cfg, child)?;
961 Self::collect_global_state_pair_tree(image, cfg, &child, seen_pairs, global_state)?;
962 }
963 }
964 Ok(())
965 }
966
967 fn collect_global_state_pair_chain(
968 image: &ImageStorage<'_>,
969 cfg: Config,
970 pair: &MetadataPair,
971 seen_pairs: &mut Vec<[u32; 2]>,
972 global_state: &mut GlobalState,
973 ) -> Result<Vec<FileRecord>> {
974 let mut files = Vec::new();
975 let mut current = pair.clone();
976 loop {
977 if seen_pairs.contains(¤t.pair) {
978 return Ok(Vec::new());
984 }
985 seen_pairs.push(current.pair);
986 global_state.xor(current.global_state_delta()?);
987 files.extend(current.files()?);
988
989 match current.hardtail()? {
990 Some(next) if next != [LFS_NULL, LFS_NULL] => {
991 current = Self::read_pair_from_storage(image, cfg, next)?;
992 }
993 _ => break,
994 }
995 }
996 Ok(files)
997 }
998
999 fn read_ctz(&self, head: u32, size: u32) -> Result<Vec<u8>> {
1000 let mut out = alloc::vec![0; size as usize];
1001 self.read_ctz_range(head, size, 0, &mut out)?;
1002 Ok(out)
1003 }
1004
1005 fn read_ctz_into(&self, head: u32, size: u32, out: &mut [u8]) -> Result<usize> {
1006 if out.len() < size as usize {
1007 return Err(Error::NoSpace);
1008 }
1009
1010 self.read_ctz_range(head, size, 0, &mut out[..size as usize])
1011 }
1012
1013 fn read_ctz_range(&self, head: u32, size: u32, offset: usize, out: &mut [u8]) -> Result<usize> {
1019 if offset > size as usize {
1020 return Ok(0);
1021 }
1022 let end = core::cmp::min(size as usize, offset.saturating_add(out.len()));
1023 let mut pos = u32::try_from(offset).map_err(|_| Error::OutOfBounds)?;
1024 let mut dst = 0usize;
1025 while pos < size && (pos as usize) < end {
1026 let (block, off) = self.ctz_find(head, size, pos)?;
1027 if block == LFS_NULL {
1028 return Err(Error::Corrupt);
1029 }
1030
1031 let off = off as usize;
1032 if off >= self.cfg.block_size {
1033 return Err(Error::Corrupt);
1034 }
1035 let diff = core::cmp::min((size - pos) as usize, self.cfg.block_size - off);
1036 let logical_start = pos as usize;
1037 let logical_end = logical_start + diff;
1038 if logical_end > offset {
1039 let copy_start = core::cmp::max(logical_start, offset);
1040 let copy_end = core::cmp::min(logical_end, end);
1041 let src_start = off + (copy_start - logical_start);
1042 let len = copy_end - copy_start;
1043 self.image
1044 .read(self.cfg, block, src_start, &mut out[dst..dst + len])?;
1045 dst += len;
1046 }
1047 pos += diff as u32;
1048 }
1049
1050 Ok(dst)
1051 }
1052
1053 fn ctz_find(&self, mut head: u32, size: u32, pos: u32) -> Result<(u32, u32)> {
1054 if size == 0 {
1055 return Ok((LFS_NULL, 0));
1056 }
1057
1058 let mut last = size - 1;
1059 let mut target_pos = pos;
1060 let mut current = self.ctz_index(&mut last)?;
1061 let target = self.ctz_index(&mut target_pos)?;
1062
1063 while current > target {
1064 let skip = core::cmp::min(npw2(current - target + 1) - 1, ctz(current));
1068 let ptr_off = (4 * skip) as usize;
1069 if ptr_off + 4 > self.cfg.block_size {
1070 return Err(Error::Corrupt);
1071 }
1072 let mut ptr = [0u8; 4];
1073 self.image.read(self.cfg, head, ptr_off, &mut ptr)?;
1074 head = le32(&ptr)?;
1075 current -= 1 << skip;
1076 }
1077
1078 Ok((head, target_pos))
1079 }
1080
1081 fn ctz_index(&self, off: &mut u32) -> Result<u32> {
1082 let size = *off;
1086 let b = self
1087 .cfg
1088 .block_size
1089 .checked_sub(8)
1090 .ok_or(Error::InvalidConfig)? as u32;
1091 let mut i = size / b;
1092 if i == 0 {
1093 return Ok(0);
1094 }
1095
1096 i = (size - 4 * (popc(i - 1) + 2)) / b;
1097 *off = size - b * i - 4 * popc(i);
1098 Ok(i)
1099 }
1100}
1101
1102impl Filesystem<'static> {
1103 pub fn format_device<D: BlockDevice>(device: &mut D) -> Result<()> {
1109 Self::format_device_with_options(device, FilesystemOptions::default())
1110 }
1111
1112 pub fn format_device_with_options<D: BlockDevice>(
1114 device: &mut D,
1115 options: FilesystemOptions,
1116 ) -> Result<()> {
1117 let cfg = device.config();
1118 let options = options.validate(cfg)?;
1119 let root = ImageBuilder::empty_root_block_with_options(cfg, options)?;
1120 let mut cache = BlockCache::new_with_cache_size(cfg, options.cache_size_for(cfg))?;
1121 for block in 0..cfg.block_count {
1122 cache.erase(device, block as u32)?;
1123 }
1124 cache.prog(device, 0, 0, &root)?;
1125 cache.sync(device)
1126 }
1127
1128 pub fn mount_device_mut<D: BlockDevice + 'static>(device: D) -> Result<FilesystemMut<D>> {
1135 FilesystemMut::mount(device)
1136 }
1137
1138 pub fn mount_device_mut_with_options<D: BlockDevice + 'static>(
1140 device: D,
1141 options: FilesystemOptions,
1142 ) -> Result<FilesystemMut<D>> {
1143 FilesystemMut::mount_with_options(device, options)
1144 }
1145}
1146
1147#[cfg(test)]
1148mod tests {
1149 use super::*;
1150 use crate::format::LFS_TYPE_MOVESTATE;
1151
1152 fn append_movestate(image: &mut [u8], cfg: Config, pair: &MetadataPair, delta: GlobalState) {
1153 let start = pair.active_block as usize * cfg.block_size;
1154 let end = start + cfg.block_size;
1155 let block = image.get_mut(start..end).expect("metadata block slice");
1156 let mut payload = Vec::new();
1157 payload.extend_from_slice(&delta.tag.to_le_bytes());
1158 payload.extend_from_slice(&delta.pair[0].to_le_bytes());
1159 payload.extend_from_slice(&delta.pair[1].to_le_bytes());
1160 let entry = CommitEntry::new(Tag::new(LFS_TYPE_MOVESTATE, 0x3ff, 12), &payload);
1161 let mut writer =
1162 MetadataCommitWriter::append(block, 16, pair.state).expect("append movestate commit");
1163 writer
1164 .write_entries(&[entry])
1165 .expect("write movestate entry");
1166 writer.finish().expect("finish movestate commit");
1167 }
1168
1169 #[test]
1170 fn mount_aggregates_global_state_from_visible_pair_tree() {
1171 let cfg = Config {
1172 block_size: 512,
1173 block_count: 32,
1174 };
1175 let mut builder = ImageBuilder::new(cfg).expect("builder");
1176 builder.create_dir("/docs").expect("create child directory");
1177 builder
1178 .add_inline_file("/docs/a.txt", b"a")
1179 .expect("create child file");
1180 let mut image = builder.build().expect("build image");
1181 let fs = Filesystem::mount(&image, cfg).expect("mount initial image");
1182 let root = fs.root.clone();
1183 let docs = fs.resolve_dir("/docs").expect("resolve child dir");
1184
1185 let root_delta = GlobalState {
1186 tag: Tag::new(LFS_TYPE_DELETE, 7, 0).0,
1187 pair: [0x10, 0x11],
1188 };
1189 let docs_delta = GlobalState {
1190 tag: Tag::new(LFS_TYPE_DELETE, 3, 0).0,
1191 pair: [0x05, 0x15],
1192 };
1193 append_movestate(&mut image, cfg, &root, root_delta);
1194 append_movestate(&mut image, cfg, &docs, docs_delta);
1195
1196 let fs = Filesystem::mount(&image, cfg).expect("mount image with global state");
1197 assert_eq!(
1198 fs.global_state(),
1199 GlobalState {
1200 tag: root_delta.tag ^ docs_delta.tag,
1201 pair: [
1202 root_delta.pair[0] ^ docs_delta.pair[0],
1203 root_delta.pair[1] ^ docs_delta.pair[1],
1204 ],
1205 }
1206 );
1207 }
1208}