1use crate::fsck::errors::{FsckError, FsckFatal, FsckWarning};
6use crate::fsck::{FragmentationStats, Fsck, FsckResult};
7use crate::lsm_tree::Query;
8use crate::lsm_tree::types::{Item, ItemRef, LayerIterator};
9use crate::object_handle::INVALID_OBJECT_ID;
10use crate::object_store::allocator::{self, AllocatorKey, AllocatorValue};
11use crate::object_store::directory::decrypt_filename;
12use crate::object_store::graveyard::Graveyard;
13use crate::object_store::object_record::EncryptedCasefoldChild;
14use crate::object_store::{
15 AttributeKey, ChildValue, DEFAULT_DATA_ATTRIBUTE_ID, EXTENDED_ATTRIBUTE_RANGE_END,
16 EXTENDED_ATTRIBUTE_RANGE_START, ExtendedAttributeValue, ExtentKey, ExtentMode, ExtentValue,
17 FSCRYPT_KEY_ID, FSVERITY_MERKLE_ATTRIBUTE_ID, FsverityMetadata, ObjectAttributes,
18 ObjectDescriptor, ObjectKey, ObjectKeyData, ObjectKind, ObjectStore, ObjectValue,
19 ProjectProperty, RootDigest, VOLUME_DATA_KEY_ID,
20};
21use crate::range::RangeExt;
22use crate::round::round_up;
23use anyhow::{Error, bail};
24use fxfs_crypto::{Crypt, WrappedKey, WrappingKeyId, key_to_cipher};
25use rustc_hash::FxHashSet as HashSet;
26use std::cell::UnsafeCell;
27use std::collections::btree_map::BTreeMap;
28use std::ops::Range;
29use std::sync::Arc;
30
31#[derive(Debug)]
33struct ScannedAttribute {
34 attribute_id: u64,
37 size: u64,
39 has_overwrite_extents_flag: Option<bool>,
43 observed_overwrite_extents: bool,
45}
46
47#[derive(Debug)]
49struct ScannedAttributes {
50 attributes: Vec<ScannedAttribute>,
51 tombstoned_attributes: Vec<u64>,
53 stored_allocated_size: u64,
55 observed_allocated_size: u64,
57 in_graveyard: bool,
59 extended_attributes: Vec<u64>,
61}
62
63#[derive(Clone, Debug)]
65enum VerifiedType {
66 None,
67 Internal(usize),
69 F2fs(u64),
71}
72
73#[derive(Debug)]
74struct ScannedFile {
75 parents: Vec<u64>,
79 stored_refs: u64,
81 attributes: ScannedAttributes,
83 verified: VerifiedType,
85}
86
87#[derive(Debug)]
88struct ScannedDir {
89 stored_sub_dirs: u64,
91 observed_sub_dirs: u64,
93 parent: Option<u64>,
96 visited: UnsafeCell<bool>,
98 wrapping_key_id: Option<WrappingKeyId>,
100 attributes: ScannedAttributes,
102 casefold: bool,
104 fscrypt_key: Option<WrappedKey>,
106}
107
108unsafe impl Sync for ScannedDir {}
109
110#[derive(Debug)]
111struct ScannedSymlink {
112 parents: Vec<u64>,
114 stored_refs: u64,
116 attributes: ScannedAttributes,
118 encrypted: bool,
119}
120
121#[derive(Debug)]
122enum ScannedObject {
123 Directory(ScannedDir),
124 File(ScannedFile),
125 Graveyard,
126 Symlink(ScannedSymlink),
127 Tombstone,
129}
130
131struct ScannedStore<'a> {
132 fsck: &'a Fsck<'a>,
133 crypt: Option<Arc<dyn Crypt>>,
134 objects: BTreeMap<u64, ScannedObject>,
135 root_objects: Vec<u64>,
136 store_id: u64,
137 is_root_store: bool,
138 is_encrypted: bool,
139 current_object: Option<CurrentObject>,
140 root_dir_id: u64,
141 stored_project_usages: BTreeMap<u64, (i64, i64)>,
142 total_project_usages: BTreeMap<u64, (i64, i64)>,
143 used_project_ids: BTreeMap<u64, u64>,
145}
146
147struct CurrentObject {
148 object_id: u64,
149 key_ids: HashSet<u64>,
150 lazy_keys: bool,
151}
152
153impl<'a> ScannedStore<'a> {
154 fn new(
155 fsck: &'a Fsck<'a>,
156 crypt: Option<Arc<dyn Crypt>>,
157 root_objects: impl AsRef<[u64]>,
158 store_id: u64,
159 is_root_store: bool,
160 is_encrypted: bool,
161 root_dir_id: u64,
162 ) -> Self {
163 Self {
164 fsck,
165 crypt,
166 objects: BTreeMap::new(),
167 root_objects: root_objects.as_ref().into(),
168 store_id,
169 is_root_store,
170 is_encrypted,
171 current_object: None,
172 root_dir_id,
173 stored_project_usages: BTreeMap::new(),
174 total_project_usages: BTreeMap::new(),
175 used_project_ids: BTreeMap::new(),
176 }
177 }
178
179 fn process(&mut self, key: &ObjectKey, value: &ObjectValue) -> Result<(), Error> {
181 match key.data {
182 ObjectKeyData::Object => {
183 match value {
184 ObjectValue::None => {
185 if self.objects.insert(key.object_id, ScannedObject::Tombstone).is_some() {
186 self.fsck.error(FsckError::TombstonedObjectHasRecords(
187 self.store_id,
188 key.object_id,
189 ))?;
190 }
191 }
192 ObjectValue::Some => {
193 self.fsck.error(FsckError::UnexpectedRecordInObjectStore(
194 self.store_id,
195 key.into(),
196 value.into(),
197 ))?;
198 }
199 ObjectValue::Object {
200 kind: ObjectKind::File { refs },
201 attributes: ObjectAttributes { project_id, allocated_size, .. },
202 } => {
203 if *project_id > 0 {
204 self.used_project_ids.insert(*project_id, key.object_id);
205 let entry = self.total_project_usages.entry(*project_id).or_default();
206 entry.0 += i64::try_from(*allocated_size).unwrap();
207 entry.1 += 1;
208 }
209 self.current_object = Some(CurrentObject {
210 object_id: key.object_id,
211 key_ids: HashSet::default(),
212 lazy_keys: false,
213 });
214 let parents = if self.root_objects.contains(&key.object_id) {
215 vec![INVALID_OBJECT_ID]
216 } else {
217 vec![]
218 };
219 self.objects.insert(
220 key.object_id,
221 ScannedObject::File(ScannedFile {
222 parents,
223 stored_refs: *refs,
224 attributes: ScannedAttributes {
225 attributes: Vec::new(),
226 tombstoned_attributes: Vec::new(),
227 stored_allocated_size: *allocated_size,
228 observed_allocated_size: 0,
229 in_graveyard: false,
230 extended_attributes: Vec::new(),
231 },
232 verified: VerifiedType::None,
233 }),
234 );
235 }
236 ObjectValue::Object {
237 kind: ObjectKind::Directory { sub_dirs, casefold, wrapping_key_id },
239 attributes: ObjectAttributes { project_id, allocated_size, .. },
240 } => {
241 if *project_id > 0 {
242 self.used_project_ids.insert(*project_id, key.object_id);
243 let entry = self.total_project_usages.entry(*project_id).or_default();
244 entry.1 += 1;
246 }
247 let parent = if self.root_objects.contains(&key.object_id) {
248 Some(INVALID_OBJECT_ID)
249 } else {
250 None
251 };
252 self.current_object = Some(CurrentObject {
253 object_id: key.object_id,
254 key_ids: HashSet::default(),
255 lazy_keys: true,
256 });
257 self.objects.insert(
260 key.object_id,
261 ScannedObject::Directory(ScannedDir {
262 stored_sub_dirs: *sub_dirs,
263 observed_sub_dirs: 0,
264 parent,
265 visited: UnsafeCell::new(false),
266 wrapping_key_id: *wrapping_key_id,
267 attributes: ScannedAttributes {
268 attributes: Vec::new(),
269 tombstoned_attributes: Vec::new(),
270 stored_allocated_size: *allocated_size,
271 observed_allocated_size: 0,
272 in_graveyard: false,
273 extended_attributes: Vec::new(),
274 },
275 casefold: *casefold,
276 fscrypt_key: None,
277 }),
278 );
279 }
280 ObjectValue::Object { kind: ObjectKind::Graveyard, attributes } => {
281 self.objects.insert(key.object_id, ScannedObject::Graveyard);
282 if attributes.project_id != 0 {
283 self.fsck.error(FsckError::ProjectOnGraveyard(
284 self.store_id,
285 attributes.project_id,
286 key.object_id,
287 ))?;
288 }
289 }
290 ObjectValue::Object {
291 kind: ObjectKind::Symlink { refs, .. },
292 attributes: ObjectAttributes { project_id, allocated_size, .. },
293 } => {
294 if *project_id > 0 {
295 self.used_project_ids.insert(*project_id, key.object_id);
296 let entry = self.total_project_usages.entry(*project_id).or_default();
297 entry.1 += 1;
299 }
300 self.current_object = Some(CurrentObject {
301 object_id: key.object_id,
302 key_ids: HashSet::default(),
303 lazy_keys: true,
304 });
305 self.objects.insert(
306 key.object_id,
307 ScannedObject::Symlink(ScannedSymlink {
308 parents: vec![],
309 stored_refs: *refs,
310 encrypted: false,
311 attributes: ScannedAttributes {
312 attributes: Vec::new(),
313 tombstoned_attributes: Vec::new(),
314 stored_allocated_size: *allocated_size,
315 observed_allocated_size: 0,
316 in_graveyard: false,
317 extended_attributes: Vec::new(),
318 },
319 }),
320 );
321 }
322 ObjectValue::Object {
323 kind: ObjectKind::EncryptedSymlink { refs, .. },
324 attributes: ObjectAttributes { project_id, allocated_size, .. },
325 } => {
326 if *project_id > 0 {
327 self.used_project_ids.insert(*project_id, key.object_id);
328 let entry = self.total_project_usages.entry(*project_id).or_default();
329 entry.1 += 1;
331 }
332 self.current_object = Some(CurrentObject {
333 object_id: key.object_id,
334 key_ids: HashSet::default(),
335 lazy_keys: true,
336 });
337 self.objects.insert(
338 key.object_id,
339 ScannedObject::Symlink(ScannedSymlink {
340 parents: vec![],
341 stored_refs: *refs,
342 encrypted: true,
343 attributes: ScannedAttributes {
344 attributes: Vec::new(),
345 tombstoned_attributes: Vec::new(),
346 stored_allocated_size: *allocated_size,
347 observed_allocated_size: 0,
348 in_graveyard: false,
349 extended_attributes: Vec::new(),
350 },
351 }),
352 );
353 }
354 _ => {
355 self.fsck.error(FsckError::MalformedObjectRecord(
356 self.store_id,
357 key.into(),
358 value.into(),
359 ))?;
360 }
361 }
362 }
363 ObjectKeyData::Keys => {
364 if let ObjectValue::Keys(keys) = value {
365 if let Some(current_file) = &mut self.current_object {
366 assert!(current_file.key_ids.is_empty());
369 for (key_id, encryption_key) in keys.iter() {
370 if !current_file.key_ids.insert(*key_id) {
371 self.fsck.error(FsckError::DuplicateKey(
372 self.store_id,
373 key.object_id,
374 *key_id,
375 ))?;
376 }
377 if *key_id == FSCRYPT_KEY_ID {
378 if let Some(ScannedObject::Directory(dir)) =
379 self.objects.get_mut(¤t_file.object_id)
380 {
381 dir.fscrypt_key = Some(encryption_key.clone().into());
382 }
383 }
384 }
385 } else {
386 self.fsck
387 .warning(FsckWarning::OrphanedKeys(self.store_id, key.object_id))?;
388 }
389 } else {
390 self.fsck.error(FsckError::MalformedObjectRecord(
391 self.store_id,
392 key.into(),
393 value.into(),
394 ))?;
395 }
396 }
397 ObjectKeyData::Attribute(attribute_id, AttributeKey::Attribute) => {
398 match value {
399 ObjectValue::Attribute { size, has_overwrite_extents } => {
400 match self.objects.get_mut(&key.object_id) {
401 Some(
402 ScannedObject::File(ScannedFile { attributes, .. })
403 | ScannedObject::Directory(ScannedDir { attributes, .. })
404 | ScannedObject::Symlink(ScannedSymlink { attributes, .. }),
405 ) => {
406 attributes.attributes.push(ScannedAttribute {
407 attribute_id,
408 size: *size,
409 has_overwrite_extents_flag: Some(*has_overwrite_extents),
410 observed_overwrite_extents: false,
411 });
412 }
413 Some(ScannedObject::Graveyard) => { }
414 Some(ScannedObject::Tombstone) => {
415 self.fsck.error(FsckError::TombstonedObjectHasRecords(
416 self.store_id,
417 key.object_id,
418 ))?;
419 }
420 None => {
421 self.fsck.warning(FsckWarning::OrphanedAttribute(
426 self.store_id,
427 key.object_id,
428 attribute_id,
429 ))?;
430 }
431 }
432 }
433 ObjectValue::VerifiedAttribute { size, fsverity_metadata } => {
434 match self.objects.get_mut(&key.object_id) {
435 Some(ScannedObject::File(ScannedFile {
436 attributes,
437 verified: is_verified,
438 ..
439 })) => {
440 attributes.attributes.push(ScannedAttribute {
441 attribute_id,
442 size: *size,
443 has_overwrite_extents_flag: None,
444 observed_overwrite_extents: false,
445 });
446 *is_verified = match &fsverity_metadata {
447 FsverityMetadata::Internal(
448 RootDigest::Sha256(root_hash),
449 _,
450 ) => VerifiedType::Internal(root_hash.len()),
451 FsverityMetadata::Internal(
452 RootDigest::Sha512(root_hash),
453 _,
454 ) => VerifiedType::Internal(root_hash.len()),
455 FsverityMetadata::F2fs(range) => VerifiedType::F2fs(range.end),
456 };
457 }
458 Some(ScannedObject::Directory(..) | ScannedObject::Symlink(..)) => {
459 self.fsck.error(FsckError::NonFileMarkedAsVerified(
460 self.store_id,
461 key.object_id,
462 ))?;
463 }
464 Some(ScannedObject::Graveyard) => { }
465 Some(ScannedObject::Tombstone) => {
466 self.fsck.error(FsckError::TombstonedObjectHasRecords(
467 self.store_id,
468 key.object_id,
469 ))?;
470 }
471 None => {
472 self.fsck.warning(FsckWarning::OrphanedAttribute(
473 self.store_id,
474 key.object_id,
475 attribute_id,
476 ))?;
477 }
478 }
479 }
480 ObjectValue::None => (),
482 _ => {
483 self.fsck.error(FsckError::MalformedObjectRecord(
484 self.store_id,
485 key.into(),
486 value.into(),
487 ))?;
488 }
489 }
490 }
491 ObjectKeyData::Attribute(_, AttributeKey::Extent(_)) => {
493 match value {
494 ObjectValue::Extent(ExtentValue::Some { key_id, .. }) => {
496 if let Some(current_file) = &self.current_object {
497 if !self.is_encrypted && *key_id == 0 && current_file.key_ids.is_empty()
498 {
499 } else if !current_file.key_ids.contains(key_id) {
501 self.fsck.error(FsckError::MissingKey(
502 self.store_id,
503 key.object_id,
504 *key_id,
505 ))?;
506 }
507 } else {
508 }
510 }
511 ObjectValue::Extent(ExtentValue::None) => {}
513 _ => {
514 self.fsck.error(FsckError::MalformedObjectRecord(
515 self.store_id,
516 key.into(),
517 value.into(),
518 ))?;
519 }
520 }
521 }
522 ObjectKeyData::Child { .. }
524 | ObjectKeyData::CasefoldChild { .. }
525 | ObjectKeyData::EncryptedChild(_)
526 | ObjectKeyData::EncryptedCasefoldChild(_) => match value {
527 ObjectValue::None => {}
528 ObjectValue::Child(ChildValue { object_id: child_id, object_descriptor }) => {
529 if *child_id == INVALID_OBJECT_ID {
530 self.fsck.warning(FsckWarning::InvalidObjectIdInStore(
531 self.store_id,
532 key.into(),
533 value.into(),
534 ))?;
535 }
536 if self.root_objects.contains(child_id) {
537 self.fsck.error(FsckError::RootObjectHasParent(
538 self.store_id,
539 *child_id,
540 key.object_id,
541 ))?;
542 }
543 if object_descriptor == &ObjectDescriptor::Volume && !self.is_root_store {
544 self.fsck.error(FsckError::VolumeInChildStore(self.store_id, *child_id))?;
545 }
546 }
547 _ => {
548 self.fsck.error(FsckError::MalformedObjectRecord(
549 self.store_id,
550 key.into(),
551 value.into(),
552 ))?;
553 }
554 },
555 ObjectKeyData::Project { project_id, property: ProjectProperty::Limit } => {
556 if self.root_dir_id != key.object_id {
558 self.fsck.error(FsckError::NonRootProjectIdMetadata(
559 self.store_id,
560 key.object_id,
561 project_id,
562 ))?;
563 }
564 match value {
565 ObjectValue::None | ObjectValue::BytesAndNodes { .. } => {}
566 _ => {
567 self.fsck.error(FsckError::MalformedObjectRecord(
568 self.store_id,
569 key.into(),
570 value.into(),
571 ))?;
572 }
573 }
574 }
575 ObjectKeyData::Project { project_id, property: ProjectProperty::Usage } => {
576 if self.root_dir_id != key.object_id {
578 self.fsck.error(FsckError::NonRootProjectIdMetadata(
579 self.store_id,
580 key.object_id,
581 project_id,
582 ))?;
583 }
584 match value {
585 ObjectValue::None => {
586 self.stored_project_usages.remove(&project_id);
587 }
588 ObjectValue::BytesAndNodes { bytes, nodes } => {
589 self.stored_project_usages.insert(project_id, (*bytes, *nodes));
590 }
591 _ => {
592 self.fsck.error(FsckError::MalformedObjectRecord(
593 self.store_id,
594 key.into(),
595 value.into(),
596 ))?;
597 }
598 }
599 }
600 ObjectKeyData::ExtendedAttribute { .. } => match value {
601 ObjectValue::None => {}
602 ObjectValue::ExtendedAttribute(ExtendedAttributeValue::Inline(_)) => {
603 if self.objects.get(&key.object_id).is_none() {
604 self.fsck.warning(FsckWarning::OrphanedExtendedAttributeRecord(
605 self.store_id,
606 key.object_id,
607 ))?;
608 }
609 }
610 ObjectValue::ExtendedAttribute(ExtendedAttributeValue::AttributeId(id)) => {
611 match self.objects.get_mut(&key.object_id) {
612 Some(
613 ScannedObject::File(ScannedFile { attributes, .. })
614 | ScannedObject::Directory(ScannedDir { attributes, .. })
615 | ScannedObject::Symlink(ScannedSymlink { attributes, .. }),
616 ) => {
617 attributes.extended_attributes.push(*id);
618 }
619 Some(ScannedObject::Graveyard) => { }
620 Some(ScannedObject::Tombstone) => {
621 self.fsck.error(FsckError::TombstonedObjectHasRecords(
622 self.store_id,
623 key.object_id,
624 ))?;
625 }
626 None => {
627 self.fsck.warning(FsckWarning::OrphanedExtendedAttributeRecord(
628 self.store_id,
629 key.object_id,
630 ))?;
631 }
632 }
633 }
634 _ => {
635 self.fsck.error(FsckError::MalformedObjectRecord(
636 self.store_id,
637 key.into(),
638 value.into(),
639 ))?;
640 }
641 },
642 ObjectKeyData::GraveyardEntry { .. } => {}
643 ObjectKeyData::GraveyardAttributeEntry { .. } => {}
644 }
645 Ok(())
646 }
647
648 async fn process_child(
651 &mut self,
652 parent_id: u64,
653 child_id: u64,
654 object_descriptor: &ObjectDescriptor,
655 object_key_data: &ObjectKeyData,
656 ) -> Result<(), Error> {
657 let mut child_wrapping_key_id = None;
658 if let Some(ScannedObject::Directory(dir)) = self.objects.get(&parent_id) {
659 match object_key_data {
660 ObjectKeyData::Child { .. } => {
661 if dir.casefold {
662 self.fsck.error(FsckError::CasefoldInconsistency(
663 self.store_id,
664 parent_id,
665 child_id,
666 ))?;
667 }
668 }
669 ObjectKeyData::CasefoldChild { .. } => {
670 if !dir.casefold {
671 self.fsck.error(FsckError::CasefoldInconsistency(
672 self.store_id,
673 parent_id,
674 child_id,
675 ))?;
676 }
677 }
678 ObjectKeyData::EncryptedCasefoldChild(EncryptedCasefoldChild {
679 hash_code,
680 name,
681 }) => {
682 if dir.wrapping_key_id.is_none() {
683 self.fsck.error(FsckError::UnencryptedDirectoryHasEncryptedChild(
684 self.store_id,
685 parent_id,
686 child_id,
687 ))?;
688 }
689 if let Some(crypt) = &self.crypt
690 && let Some(key) = &dir.fscrypt_key
691 {
692 if let Ok(unwrapped_key) = crypt.unwrap_key(key, parent_id).await {
694 let cipher = key_to_cipher(key, &unwrapped_key)?;
695 match decrypt_filename(cipher.as_ref(), parent_id, &name) {
696 Ok(name) => {
697 if cipher.hash_code_casefold(&name) != *hash_code {
698 self.fsck.error(FsckError::BadCasefoldHash(
699 self.store_id,
700 parent_id,
701 child_id,
702 ))?;
703 }
704 }
705 Err(_) => {
706 }
708 }
709 }
710 }
711 }
712 ObjectKeyData::EncryptedChild(_) => {
713 if dir.wrapping_key_id.is_none() {
714 self.fsck.error(FsckError::UnencryptedDirectoryHasEncryptedChild(
715 self.store_id,
716 parent_id,
717 child_id,
718 ))?;
719 }
720 }
721 _ => {
722 bail!(
723 "Unexpected object_key_data type in process_child: {:?}",
724 object_key_data
725 );
726 }
727 };
728 }
729 match (self.objects.get_mut(&child_id), object_descriptor) {
730 (
731 Some(ScannedObject::File(ScannedFile { parents, .. })),
732 ObjectDescriptor::File | ObjectDescriptor::Volume,
733 ) => {
734 parents.push(parent_id);
735 }
736 (
737 Some(ScannedObject::Directory(ScannedDir { parent, wrapping_key_id, .. })),
738 ObjectDescriptor::Directory,
739 ) => {
740 if matches!(
741 object_key_data,
742 ObjectKeyData::EncryptedChild(_) | ObjectKeyData::EncryptedCasefoldChild(_)
743 ) {
744 if let Some(id) = wrapping_key_id {
745 child_wrapping_key_id = Some(*id);
746 } else {
747 self.fsck.error(FsckError::EncryptedChildDirectoryNoWrappingKey(
748 self.store_id,
749 child_id,
750 ))?;
751 }
752 }
753 if parent.is_some() {
754 self.fsck
757 .error(FsckError::MultipleLinksToDirectory(self.store_id, child_id))?;
758 }
759 *parent = Some(parent_id);
760 }
761 (Some(ScannedObject::Tombstone), _) => {
762 self.fsck.error(FsckError::TombstonedObjectHasRecords(self.store_id, parent_id))?;
763 return Ok(());
764 }
765 (None, _) => {
766 self.fsck.error(FsckError::MissingObjectInfo(self.store_id, child_id))?;
767 return Ok(());
768 }
769 (Some(s), _) => {
770 let expected = match s {
771 ScannedObject::Directory(_) => ObjectDescriptor::Directory,
772 ScannedObject::File(_) | ScannedObject::Graveyard => ObjectDescriptor::File,
773 ScannedObject::Symlink(ScannedSymlink { parents, .. }) => {
774 parents.push(parent_id);
775 ObjectDescriptor::Symlink
776 }
777 ScannedObject::Tombstone => unreachable!(),
778 };
779 if &expected != object_descriptor {
780 self.fsck.error(FsckError::ConflictingTypeForLink(
781 self.store_id,
782 child_id,
783 expected.into(),
784 object_descriptor.into(),
785 ))?;
786 }
787 }
788 }
789 match self.objects.get_mut(&parent_id) {
790 Some(
791 ScannedObject::File(..) | ScannedObject::Graveyard | ScannedObject::Symlink(_),
792 ) => {
793 self.fsck.error(FsckError::ObjectHasChildren(self.store_id, parent_id))?;
794 }
795 Some(ScannedObject::Directory(ScannedDir {
796 observed_sub_dirs,
797 wrapping_key_id,
798 ..
799 })) => {
800 if let Some(parent_wrapping_key_id) = *wrapping_key_id {
801 if !matches!(
802 object_key_data,
803 ObjectKeyData::EncryptedChild(_) | ObjectKeyData::EncryptedCasefoldChild(_)
804 ) {
805 self.fsck.error(FsckError::EncryptedDirectoryHasUnencryptedChild(
806 self.store_id,
807 parent_id,
808 child_id,
809 ))?;
810 } else {
811 if let Some(child_wrapping_key_id) = child_wrapping_key_id {
812 if child_wrapping_key_id != parent_wrapping_key_id {
813 self.fsck.error(
814 FsckError::ChildEncryptedWithDifferentWrappingKeyThanParent(
815 self.store_id,
816 parent_id,
817 child_id,
818 parent_wrapping_key_id,
819 child_wrapping_key_id,
820 ),
821 )?;
822 }
823 }
824 }
825 }
826 if *object_descriptor == ObjectDescriptor::Directory {
827 *observed_sub_dirs += 1;
828 }
829 }
830 Some(ScannedObject::Tombstone) => {
831 self.fsck.error(FsckError::TombstonedObjectHasRecords(self.store_id, parent_id))?;
832 }
833 None => self.fsck.error(FsckError::MissingObjectInfo(self.store_id, parent_id))?,
834 }
835 Ok(())
836 }
837
838 fn process_extent(
840 &mut self,
841 object_id: u64,
842 attribute_id: u64,
843 range: &Range<u64>,
844 device_offset: u64,
845 bs: u64,
846 is_overwrite_extent: bool,
847 ) -> Result<(), Error> {
848 if range.start % bs > 0 || range.end % bs > 0 {
849 self.fsck.error(FsckError::MisalignedExtent(
850 self.store_id,
851 object_id,
852 range.clone(),
853 0,
854 ))?;
855 }
856 if range.start >= range.end {
857 self.fsck.error(FsckError::MalformedExtent(
858 self.store_id,
859 object_id,
860 range.clone(),
861 0,
862 ))?;
863 return Ok(());
864 }
865 let len = range.end - range.start;
866 match self.objects.get_mut(&object_id) {
867 Some(
868 ScannedObject::File(ScannedFile { attributes, .. })
869 | ScannedObject::Directory(ScannedDir { attributes, .. })
870 | ScannedObject::Symlink(ScannedSymlink { attributes, .. }),
871 ) => {
872 let ScannedAttributes {
873 attributes,
874 tombstoned_attributes,
875 observed_allocated_size: allocated_size,
876 in_graveyard,
877 ..
878 } = attributes;
879 match attributes.iter_mut().find(|attribute| attribute.attribute_id == attribute_id)
880 {
881 Some(attribute) => {
882 if !*in_graveyard
883 && !tombstoned_attributes.contains(&attribute_id)
884 && range.end > round_up(attribute.size, bs).unwrap()
885 {
886 self.fsck.error(FsckError::ExtentExceedsLength(
887 self.store_id,
888 object_id,
889 attribute_id,
890 attribute.size,
891 range.into(),
892 ))?;
893 }
894 attribute.observed_overwrite_extents =
895 attribute.observed_overwrite_extents || is_overwrite_extent;
896 }
897 None => {
898 self.fsck.warning(FsckWarning::ExtentForMissingAttribute(
899 self.store_id,
900 object_id,
901 attribute_id,
902 ))?;
903 }
904 }
905 *allocated_size += len;
906 }
907 Some(ScannedObject::Graveyard | ScannedObject::Tombstone) => { }
908 None => {
909 self.fsck
910 .warning(FsckWarning::ExtentForNonexistentObject(self.store_id, object_id))?;
911 }
912 }
913 if device_offset % bs > 0 {
914 self.fsck.error(FsckError::MisalignedExtent(
915 self.store_id,
916 object_id,
917 range.clone(),
918 device_offset,
919 ))?;
920 }
921 let item = Item::new(
922 AllocatorKey { device_range: device_offset..device_offset + len },
923 AllocatorValue::Abs { count: 1, owner_object_id: self.store_id },
924 );
925 let lower_bound: AllocatorKey = item.key.lower_bound_for_merge_into();
926 self.fsck.allocations.merge_into(item, &lower_bound, allocator::merge::merge);
927 Ok(())
928 }
929
930 fn handle_graveyard_entry(
932 &mut self,
933 object_id: u64,
934 attribute_id: Option<u64>,
935 tombstone: bool,
936 ) -> Result<(), Error> {
937 match self.objects.get_mut(&object_id) {
938 Some(ScannedObject::File(ScannedFile {
939 parents,
940 attributes: ScannedAttributes { in_graveyard, tombstoned_attributes, .. },
941 ..
942 })) => {
943 if attribute_id.is_none() {
944 *in_graveyard = true;
945 }
946 if tombstone {
947 if let Some(attribute_id) = attribute_id {
948 tombstoned_attributes.push(attribute_id);
949 } else {
950 parents.push(INVALID_OBJECT_ID)
951 }
952 }
953 }
954 Some(
955 ScannedObject::Directory(ScannedDir { attributes, .. })
956 | ScannedObject::Symlink(ScannedSymlink { attributes, .. }),
957 ) => {
958 if tombstone {
959 if let Some(attribute_id) = attribute_id {
960 attributes.tombstoned_attributes.push(attribute_id);
961 } else {
962 attributes.in_graveyard = true;
963 }
964 } else {
965 self.fsck.error(FsckError::UnexpectedObjectInGraveyard(object_id))?;
966 }
967 }
968 Some(ScannedObject::Graveyard | ScannedObject::Tombstone) => {
969 self.fsck.error(FsckError::UnexpectedObjectInGraveyard(object_id))?;
970 }
971 None => {
972 self.fsck.warning(FsckWarning::GraveyardRecordForAbsentObject(
973 self.store_id,
974 object_id,
975 ))?;
976 }
977 }
978 Ok(())
979 }
980
981 fn finish_file(&mut self) -> Result<(), Error> {
983 if let Some(current_file) = self.current_object.take() {
984 if self.is_encrypted {
985 let mut key_ids = vec![];
986 for id in current_file.key_ids.iter() {
987 key_ids.push(*id);
988 }
989
990 if key_ids.is_empty() && !current_file.lazy_keys {
996 self.fsck.error(FsckError::MissingEncryptionKeys(
997 self.store_id,
998 current_file.object_id,
999 ))?;
1000 }
1001
1002 match self.objects.get_mut(¤t_file.object_id) {
1003 Some(ScannedObject::Directory(ScannedDir {
1004 wrapping_key_id,
1005 attributes,
1006 ..
1007 })) => {
1008 if !attributes.extended_attributes.is_empty() {
1009 if !key_ids.contains(&VOLUME_DATA_KEY_ID) {
1010 self.fsck.error(FsckError::MissingKey(
1011 self.store_id,
1012 current_file.object_id,
1013 VOLUME_DATA_KEY_ID,
1014 ))?;
1015 }
1016 }
1017 if wrapping_key_id.is_some() {
1018 if !key_ids.contains(&FSCRYPT_KEY_ID) {
1019 self.fsck.error(FsckError::MissingKey(
1020 self.store_id,
1021 current_file.object_id,
1022 FSCRYPT_KEY_ID,
1023 ))?;
1024 }
1025 }
1026 }
1027 Some(ScannedObject::Symlink(ScannedSymlink { encrypted, .. })) => {
1028 if *encrypted {
1029 if !key_ids.contains(&FSCRYPT_KEY_ID) {
1030 self.fsck.error(FsckError::MissingKey(
1031 self.store_id,
1032 current_file.object_id,
1033 FSCRYPT_KEY_ID,
1034 ))?;
1035 }
1036 }
1037 }
1038 Some(_) => {}
1039 None => self.fsck.error(FsckError::MissingObjectInfo(
1040 self.store_id,
1041 current_file.object_id,
1042 ))?,
1043 }
1044 }
1045 }
1046 Ok(())
1047 }
1048}
1049
1050async fn scan_extents_and_directory_children<'a>(
1055 store: &ObjectStore,
1056 scanned: &mut ScannedStore<'a>,
1057 result: &mut FsckResult,
1058) -> Result<(), Error> {
1059 let bs = store.block_size();
1060 let layer_set = store.tree().layer_set();
1061 let mut merger = layer_set.merger();
1062 let mut iter = merger.query(Query::FullScan).await?;
1063 let mut allocated_bytes = 0;
1064 let mut extent_count = 0;
1065 let mut previous_object_id = INVALID_OBJECT_ID;
1066 while let Some(itemref) = iter.get() {
1067 match itemref {
1068 ItemRef {
1069 key:
1070 ObjectKey {
1071 object_id,
1072 data:
1073 ObjectKeyData::Attribute(
1074 attribute_id,
1075 AttributeKey::Extent(ExtentKey { range }),
1076 ),
1077 },
1078 value: ObjectValue::Extent(extent),
1079 ..
1080 } => {
1081 if let ExtentValue::Some { device_offset, mode, .. } = extent {
1083 let size = range.length().unwrap_or(0);
1084 allocated_bytes += size;
1085
1086 if previous_object_id != *object_id {
1087 if previous_object_id != INVALID_OBJECT_ID {
1088 result.fragmentation.extent_count
1089 [FragmentationStats::get_histogram_bucket_for_count(
1090 extent_count,
1091 )] += 1;
1092 }
1093 extent_count = 0;
1094 previous_object_id = *object_id;
1095 }
1096 result.fragmentation.extent_size
1097 [FragmentationStats::get_histogram_bucket_for_size(size)] += 1;
1098 extent_count += 1;
1099
1100 let is_overwrite_extent =
1101 matches!(mode, ExtentMode::Overwrite | ExtentMode::OverwritePartial(_));
1102
1103 scanned.process_extent(
1104 *object_id,
1105 *attribute_id,
1106 range,
1107 *device_offset,
1108 bs,
1109 is_overwrite_extent,
1110 )?;
1111 };
1112 }
1113 ItemRef {
1114 key:
1115 ObjectKey {
1116 object_id,
1117 data:
1118 object_key_data @ (ObjectKeyData::Child { .. }
1119 | ObjectKeyData::EncryptedChild(_)
1120 | ObjectKeyData::CasefoldChild { .. }
1121 | ObjectKeyData::EncryptedCasefoldChild { .. }),
1122 },
1123 value: ObjectValue::Child(ChildValue { object_id: child_id, object_descriptor }),
1124 ..
1125 } => {
1126 scanned
1127 .process_child(*object_id, *child_id, object_descriptor, object_key_data)
1128 .await?
1129 }
1130 _ => {}
1131 }
1132 iter.advance().await?;
1133 }
1134 if extent_count != 0 && previous_object_id != INVALID_OBJECT_ID {
1135 result.fragmentation.extent_count
1136 [FragmentationStats::get_histogram_bucket_for_count(extent_count)] += 1;
1137 }
1138 scanned.fsck.verbose(format!(
1139 "Store {} has {} bytes allocated",
1140 store.store_object_id(),
1141 allocated_bytes
1142 ));
1143 Ok(())
1144}
1145
1146fn validate_attributes(
1147 fsck: &Fsck<'_>,
1148 store_id: u64,
1149 object_id: u64,
1150 attributes: &ScannedAttributes,
1151 is_file: bool,
1152 verified: VerifiedType,
1153 block_size: u64,
1154) -> Result<(), Error> {
1155 let ScannedAttributes {
1156 attributes,
1157 tombstoned_attributes,
1158 observed_allocated_size,
1159 stored_allocated_size,
1160 extended_attributes,
1161 ..
1162 } = attributes;
1163 if observed_allocated_size != stored_allocated_size {
1164 fsck.error(FsckError::AllocatedSizeMismatch(
1165 store_id,
1166 object_id,
1167 *observed_allocated_size,
1168 *stored_allocated_size,
1169 ))?;
1170 }
1171
1172 if is_file {
1173 let data_attribute =
1174 attributes.iter().find(|attribute| attribute.attribute_id == DEFAULT_DATA_ATTRIBUTE_ID);
1175 match data_attribute {
1176 None => fsck.error(FsckError::MissingDataAttribute(store_id, object_id))?,
1177 Some(data_attribute) => {
1178 let merkle_attribute = attributes
1179 .iter()
1180 .find(|attribute| attribute.attribute_id == FSVERITY_MERKLE_ATTRIBUTE_ID);
1181
1182 if !matches!(verified, VerifiedType::None) && merkle_attribute.is_none() {
1186 fsck.error(FsckError::VerifiedFileDoesNotHaveAMerkleAttribute(
1187 store_id, object_id,
1188 ))?;
1189 }
1190
1191 match verified {
1192 VerifiedType::None => {}
1193 VerifiedType::Internal(hash_size) => {
1194 if let Some(merkle_attribute) = merkle_attribute {
1195 let expected_size = if data_attribute.size == 0 {
1196 hash_size as u64
1197 } else {
1200 ((data_attribute.size + (block_size - 1)) / block_size)
1201 * hash_size as u64
1202 };
1203 if merkle_attribute.size != expected_size {
1204 fsck.error(FsckError::IncorrectMerkleTreeSize(
1205 store_id,
1206 object_id,
1207 expected_size,
1208 merkle_attribute.size,
1209 ))?;
1210 }
1211 } else {
1212 fsck.error(FsckError::VerifiedFileDoesNotHaveAMerkleAttribute(
1213 store_id, object_id,
1214 ))?;
1215 }
1216 }
1217 VerifiedType::F2fs(attr_end) => {
1218 match merkle_attribute {
1219 Some(merkle_attribute) => {
1223 if merkle_attribute.size != attr_end {
1224 fsck.error(FsckError::IncorrectMerkleTreeSize(
1225 store_id,
1226 object_id,
1227 attr_end,
1228 merkle_attribute.size,
1229 ))?;
1230 }
1231 }
1232 None => {
1233 fsck.error(FsckError::VerifiedFileDoesNotHaveAMerkleAttribute(
1234 store_id, object_id,
1235 ))?;
1236 }
1237 }
1238 }
1239 }
1240 }
1241 }
1242 }
1243
1244 for attr in tombstoned_attributes {
1246 if attributes.iter().find(|attribute| attribute.attribute_id == *attr).is_none() {
1247 fsck.error(FsckError::TombstonedAttributeDoesNotExist(store_id, object_id, *attr))?
1248 }
1249 }
1250
1251 for expected_attribute_id in extended_attributes {
1252 if attributes
1253 .iter()
1254 .find(|attribute| attribute.attribute_id == *expected_attribute_id)
1255 .is_none()
1256 {
1257 fsck.error(FsckError::MissingAttributeForExtendedAttribute(
1258 store_id,
1259 object_id,
1260 *expected_attribute_id,
1261 ))?;
1262 }
1263 }
1264
1265 for attribute in attributes {
1266 if attribute.attribute_id >= EXTENDED_ATTRIBUTE_RANGE_START
1267 && attribute.attribute_id < EXTENDED_ATTRIBUTE_RANGE_END
1268 {
1269 if extended_attributes
1272 .iter()
1273 .find(|xattr_id| attribute.attribute_id == **xattr_id)
1274 .is_none()
1275 {
1276 fsck.warning(FsckWarning::OrphanedExtendedAttribute(
1277 store_id,
1278 object_id,
1279 attribute.attribute_id,
1280 ))?;
1281 }
1282 }
1283
1284 if attribute
1285 .has_overwrite_extents_flag
1286 .is_some_and(|has_flag| has_flag && !attribute.observed_overwrite_extents)
1287 {
1288 fsck.error(FsckError::MissingOverwriteExtents(
1289 store_id,
1290 object_id,
1291 attribute.attribute_id,
1292 ))?;
1293 }
1294 if attribute
1295 .has_overwrite_extents_flag
1296 .is_some_and(|has_flag| !has_flag && attribute.observed_overwrite_extents)
1297 {
1298 fsck.error(FsckError::OverwriteExtentFlagUnset(
1299 store_id,
1300 object_id,
1301 attribute.attribute_id,
1302 ))?;
1303 }
1304 }
1305
1306 Ok(())
1307}
1308
1309pub(super) async fn scan_store(
1312 fsck: &Fsck<'_>,
1313 store: &ObjectStore,
1314 root_objects: impl AsRef<[u64]>,
1315 result: &mut FsckResult,
1316) -> Result<(), Error> {
1317 let store_id = store.store_object_id();
1318 let next_object_id = store.query_next_object_id();
1319
1320 let mut scanned = ScannedStore::new(
1321 fsck,
1322 store.crypt(),
1323 root_objects,
1324 store_id,
1325 store.is_root(),
1326 store.is_encrypted(),
1327 store.root_directory_object_id(),
1328 );
1329
1330 let layer_set = store.tree().layer_set();
1332 let mut merger = layer_set.merger();
1333 let mut iter = merger.query(Query::FullScan).await?;
1334 let mut last_item: Option<Item<ObjectKey, ObjectValue>> = None;
1335
1336 let mut last_object_id = INVALID_OBJECT_ID;
1337 let mut highest_object_id = INVALID_OBJECT_ID;
1338
1339 while let Some(item) = iter.get() {
1340 if let Some(last_item) = last_item {
1341 if last_item.key >= *item.key {
1342 fsck.fatal(FsckFatal::MisOrderedObjectStore(store_id))?;
1343 }
1344 }
1345 if item.key.object_id == INVALID_OBJECT_ID {
1346 fsck.warning(FsckWarning::InvalidObjectIdInStore(
1347 store_id,
1348 item.key.into(),
1349 item.value.into(),
1350 ))?;
1351 }
1352 if let Some(current_file) = &scanned.current_object {
1353 if item.key.object_id != current_file.object_id {
1354 scanned.finish_file()?;
1355 }
1356 }
1357 if item.key.object_id != last_object_id {
1358 if item.key.object_id == next_object_id {
1359 fsck.error(FsckError::NextObjectIdInUse(store_id, next_object_id))?;
1360 }
1361 if let Some(id) = store.to_unencrypted_object_id(item.key.object_id)
1362 && id > highest_object_id
1363 {
1364 highest_object_id = id;
1365 }
1366 last_object_id = item.key.object_id;
1367 }
1368 scanned.process(item.key, item.value)?;
1369 last_item = Some(item.cloned());
1370 iter.advance().await?;
1371 }
1372 scanned.finish_file()?;
1373
1374 for (project_id, node_id) in scanned.used_project_ids.iter() {
1375 if !scanned.stored_project_usages.contains_key(project_id) {
1376 fsck.error(FsckError::ProjectUsedWithNoUsageTracking(store_id, *project_id, *node_id))?;
1377 }
1378 }
1379 for (project_id, (bytes_stored, nodes_stored)) in scanned.stored_project_usages.iter() {
1380 if let Some((bytes_used, nodes_used)) = scanned.total_project_usages.get(&project_id) {
1381 if *bytes_stored != *bytes_used || *nodes_stored != *nodes_used {
1382 fsck.warning(FsckWarning::ProjectUsageInconsistent(
1383 store_id,
1384 *project_id,
1385 (*bytes_stored, *nodes_stored),
1386 (*bytes_used, *nodes_used),
1387 ))?;
1388 }
1389 } else {
1390 if *bytes_stored > 0 || *nodes_stored > 0 {
1391 fsck.warning(FsckWarning::ProjectUsageInconsistent(
1392 store_id,
1393 *project_id,
1394 (*bytes_stored, *nodes_stored),
1395 (0, 0),
1396 ))?;
1397 }
1398 }
1399 }
1400
1401 let layer_set = store.tree().layer_set();
1405 let mut merger = layer_set.merger();
1406 let mut iter = fsck.assert(
1407 Graveyard::iter(store.graveyard_directory_object_id(), &mut merger).await,
1408 FsckFatal::MalformedGraveyard,
1409 )?;
1410 while let Some(info) = iter.get() {
1411 match info.value() {
1412 ObjectValue::Some => {
1413 scanned.handle_graveyard_entry(info.object_id(), info.attribute_id(), true)?
1414 }
1415 ObjectValue::Trim => {
1416 if let Some(attribute_id) = info.attribute_id() {
1417 fsck.error(FsckError::TrimValueForGraveyardAttributeEntry(
1418 store_id,
1419 info.object_id(),
1420 attribute_id,
1421 ))?
1422 } else {
1423 scanned.handle_graveyard_entry(info.object_id(), None, false)?
1424 }
1425 }
1426 _ => fsck.error(FsckError::BadGraveyardValue(store_id, info.object_id()))?,
1427 }
1428 fsck.assert(iter.advance().await, FsckFatal::MalformedGraveyard)?;
1429 }
1430
1431 scan_extents_and_directory_children(store, &mut scanned, result).await?;
1432
1433 for oid in scanned.root_objects {
1437 if let Some(ScannedObject::Directory(ScannedDir { visited, .. })) =
1438 scanned.objects.get_mut(&oid)
1439 {
1440 *visited.get_mut() = true;
1441 }
1442 }
1443
1444 let mut num_objects = 0;
1446 let mut files = 0;
1447 let mut directories = 0;
1448 let mut symlinks = 0;
1449 let mut tombstones = 0;
1450 let mut other = 0;
1451 let mut stack = Vec::new();
1452 for (object_id, object) in &scanned.objects {
1453 num_objects += 1;
1454 match object {
1455 ScannedObject::File(ScannedFile {
1456 parents,
1457 stored_refs,
1458 attributes,
1459 verified: is_verified,
1460 ..
1461 }) => {
1462 files += 1;
1463 let observed_refs = parents.len().try_into().unwrap();
1464 if observed_refs != *stored_refs && observed_refs > 0 {
1466 fsck.error(FsckError::RefCountMismatch(
1467 *object_id,
1468 observed_refs,
1469 *stored_refs,
1470 ))?;
1471 }
1472 validate_attributes(
1473 fsck,
1474 store_id,
1475 *object_id,
1476 attributes,
1477 true,
1478 is_verified.clone(),
1479 store.block_size(),
1480 )?;
1481 if parents.is_empty() {
1482 fsck.warning(FsckWarning::OrphanedObject(store_id, *object_id))?;
1483 }
1484 if parents.contains(&INVALID_OBJECT_ID) && parents.len() > 1 {
1485 let parents = parents
1486 .iter()
1487 .filter(|oid| **oid != INVALID_OBJECT_ID)
1488 .cloned()
1489 .collect::<Vec<u64>>();
1490 fsck.error(FsckError::ZombieFile(store_id, *object_id, parents))?;
1491 }
1492 }
1493 ScannedObject::Directory(ScannedDir {
1494 stored_sub_dirs,
1495 observed_sub_dirs,
1496 parent,
1497 visited,
1498 attributes,
1499 ..
1500 }) => {
1501 directories += 1;
1502 if *observed_sub_dirs != *stored_sub_dirs {
1503 fsck.error(FsckError::SubDirCountMismatch(
1504 store_id,
1505 *object_id,
1506 *observed_sub_dirs,
1507 *stored_sub_dirs,
1508 ))?;
1509 }
1510 validate_attributes(
1511 fsck,
1512 store_id,
1513 *object_id,
1514 attributes,
1515 false,
1516 VerifiedType::None,
1517 store.block_size(),
1518 )?;
1519 if let &Some(mut oid) = parent {
1520 if attributes.in_graveyard {
1521 fsck.error(FsckError::ZombieDir(store_id, *object_id, oid))?;
1522 }
1523 if !std::mem::replace(unsafe { &mut *visited.get() }, true) {
1527 stack.push(*object_id);
1528 loop {
1529 if let Some(ScannedObject::Directory(ScannedDir {
1530 parent: Some(parent),
1531 visited,
1532 ..
1533 })) = scanned.objects.get(&oid)
1534 {
1535 stack.push(oid);
1536 oid = *parent;
1537 if std::mem::replace(unsafe { &mut *visited.get() }, true) {
1539 break;
1540 }
1541 } else {
1542 break;
1545 }
1546 }
1547 for s in stack.drain(..) {
1550 if s == oid {
1551 fsck.error(FsckError::LinkCycle(store_id, oid))?;
1552 break;
1553 }
1554 }
1555 }
1556 } else if !attributes.in_graveyard {
1557 fsck.warning(FsckWarning::OrphanedObject(store_id, *object_id))?;
1558 }
1559 }
1560 ScannedObject::Graveyard => other += 1,
1561 ScannedObject::Symlink(ScannedSymlink { parents, stored_refs, attributes, .. }) => {
1562 symlinks += 1;
1563 let observed_refs = parents.len().try_into().unwrap();
1564 if observed_refs != *stored_refs && observed_refs > 0 {
1566 fsck.error(FsckError::RefCountMismatch(
1567 *object_id,
1568 observed_refs,
1569 *stored_refs,
1570 ))?;
1571 }
1572 validate_attributes(
1573 fsck,
1574 store_id,
1575 *object_id,
1576 attributes,
1577 false,
1578 VerifiedType::None,
1579 store.block_size(),
1580 )?;
1581 if attributes.in_graveyard {
1582 if !parents.is_empty() {
1583 fsck.error(FsckError::ZombieSymlink(
1584 store_id,
1585 *object_id,
1586 parents.clone(),
1587 ))?;
1588 }
1589 } else if parents.is_empty() {
1590 fsck.warning(FsckWarning::OrphanedObject(store_id, *object_id))?;
1591 }
1592 }
1593 ScannedObject::Tombstone => {
1594 tombstones += 1;
1595 num_objects -= 1;
1596 }
1597 }
1598 }
1599 if num_objects != store.object_count() {
1600 fsck.error(FsckError::ObjectCountMismatch(store_id, num_objects, store.object_count()))?;
1601 }
1602 let last_object_id = store.unencrypted_last_object_id();
1603 if last_object_id != INVALID_OBJECT_ID && last_object_id < highest_object_id {
1604 fsck.error(FsckError::BadLastObjectId(highest_object_id, last_object_id))?;
1605 }
1606 fsck.verbose(format!(
1607 "Store {store_id} has {files} files, {directories} dirs, {symlinks} symlinks, \
1608 {tombstones} tombstones, {other} other objects",
1609 ));
1610
1611 Ok(())
1612}