fxfs/fsck/
store_scanner.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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// Information about a specific attribute.
32#[derive(Debug)]
33struct ScannedAttribute {
34    // ID of the attribute. Most commonly zero, which is the attribute value regular data is stored
35    // in, but others are used for extended attributes and merkle trees.
36    attribute_id: u64,
37    // The logical size of this attribute according to its metadata.
38    size: u64,
39    // The attribute metadata claims to have at least one extents with an Overwrite or
40    // OverwritePartial ExtentMode. None for attributes that don't care about overwrite extents,
41    // like VerifiedAttribute.
42    has_overwrite_extents_flag: Option<bool>,
43    // An overwrite extent was found for this attribute.
44    observed_overwrite_extents: bool,
45}
46
47// Information for scanned objects about their allocated attributes.
48#[derive(Debug)]
49struct ScannedAttributes {
50    attributes: Vec<ScannedAttribute>,
51    // A list of attributes that have been tombstoned.
52    tombstoned_attributes: Vec<u64>,
53    // The object's allocated size, according to its metadata.
54    stored_allocated_size: u64,
55    // The allocated size of the object (computed by summing up the extents for the file).
56    observed_allocated_size: u64,
57    // The object is in the graveyard which means extents beyond the end of the file are allowed.
58    in_graveyard: bool,
59    // Any extended attributes which point at an fxfs attribute for this object.
60    extended_attributes: Vec<u64>,
61}
62
63// The verified status of a file.
64#[derive(Clone, Debug)]
65enum VerifiedType {
66    None,
67    // Internal with the size of the hash.
68    Internal(usize),
69    // F2fs formatted with end of the range.
70    F2fs(u64),
71}
72
73#[derive(Debug)]
74struct ScannedFile {
75    // A list of parent object IDs for the file.  INVALID_OBJECT_ID indicates a reference from
76    // outside the object store (either the graveyard, or because the object is a root object of the
77    // store and probably has a reference to it in e.g. the StoreInfo or superblock).
78    parents: Vec<u64>,
79    // The number of references to the file, according to its metadata.
80    stored_refs: u64,
81    // Attributes for this file.
82    attributes: ScannedAttributes,
83    // If fsverity-enabled, contains the hash size.
84    verified: VerifiedType,
85}
86
87#[derive(Debug)]
88struct ScannedDir {
89    // The number of sub-directories of the dir, according to its metadata.
90    stored_sub_dirs: u64,
91    // The number of sub-directories we found for the dir.
92    observed_sub_dirs: u64,
93    // The parent object of the directory.  See ScannedFile::parents.  Note that directories can
94    // only have one parent, hence this is just an Option (not a Vec).
95    parent: Option<u64>,
96    // Used to detect directory cycles.
97    visited: UnsafeCell<bool>,
98    // If set, stores the wrapping_key_id that the directory was encrypted with.
99    wrapping_key_id: Option<WrappingKeyId>,
100    // Attributes for this directory.
101    attributes: ScannedAttributes,
102    // True if directory uses casefold
103    casefold: bool,
104    // The fscrypt encryption key.
105    fscrypt_key: Option<WrappedKey>,
106}
107
108unsafe impl Sync for ScannedDir {}
109
110#[derive(Debug)]
111struct ScannedSymlink {
112    // A list of parent object IDs for the symlink.
113    parents: Vec<u64>,
114    // The number of references to the file, according to its metadata.
115    stored_refs: u64,
116    // Attributes for this symlink
117    attributes: ScannedAttributes,
118    encrypted: bool,
119}
120
121#[derive(Debug)]
122enum ScannedObject {
123    Directory(ScannedDir),
124    File(ScannedFile),
125    Graveyard,
126    Symlink(ScannedSymlink),
127    // A tombstoned object, which should have no other records associated with it.
128    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    /// Tracks project ids used and an example node to examine if it should not have been included.
144    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    // Process an object store record, adding or updating any objects known to the ScannedStore.
180    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                        // TODO: https://fxbug.dev/356897866: Add validation for fscrypt.
238                        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                            // Increment only nodes.
245                            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                        // We've verified no duplicate keys, and Object records come first,
258                        // so this should always be the first time we encounter this object.
259                        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                            // Increment only nodes.
298                            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                            // Increment only nodes.
330                            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                        // Duplicates items should have already been checked, but not
367                        // duplicate key IDs.
368                        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(&current_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) => { /* NOP */ }
414                            Some(ScannedObject::Tombstone) => {
415                                self.fsck.error(FsckError::TombstonedObjectHasRecords(
416                                    self.store_id,
417                                    key.object_id,
418                                ))?;
419                            }
420                            None => {
421                                // We verify key ordering elsewhere, and Object records come before
422                                // Attribute records, so we should never find an attribute without
423                                // its object already encountered.  Thus, this is an orphaned
424                                // attribute.
425                                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) => { /* NOP */ }
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                    // Deleted attribute.
481                    ObjectValue::None => (),
482                    _ => {
483                        self.fsck.error(FsckError::MalformedObjectRecord(
484                            self.store_id,
485                            key.into(),
486                            value.into(),
487                        ))?;
488                    }
489                }
490            }
491            // Mostly ignore extents on this pass. We'll process them later.
492            ObjectKeyData::Attribute(_, AttributeKey::Extent(_)) => {
493                match value {
494                    // Regular extent record.
495                    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                                // Unencrypted files in unencrypted stores should use key ID 0.
500                            } 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                            // This must be an orphaned extent, which should get picked up later.
509                        }
510                    }
511                    // Deleted extent.
512                    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            // TODO(b/365631616): Check that the child type matches the directory metadata.
523            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                // Should only be set on the root object store
557                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                // Should only be set on the root object store
577                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) => { /* NOP */ }
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    /// Performs some checks on the child link and records information such as the sub-directory
649    /// count that get verified later.
650    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                        && !matches!(key, WrappedKey::Fxfs(_))
692                    {
693                        // Ignore unwrap errors.
694                        if let Ok(unwrapped_key) = crypt.unwrap_key(key, parent_id).await {
695                            let cipher = key_to_cipher(key, &unwrapped_key)?;
696                            match decrypt_filename(cipher.as_ref(), parent_id, &name) {
697                                Ok(name) => {
698                                    if cipher.hash_code_casefold(&name) != *hash_code {
699                                        self.fsck.error(FsckError::BadCasefoldHash(
700                                            self.store_id,
701                                            parent_id,
702                                            child_id,
703                                        ))?;
704                                    }
705                                }
706                                Err(_) => {
707                                    // Ignore decryption errors; we can't verify the hash.
708                                }
709                            }
710                        }
711                    }
712                }
713                ObjectKeyData::EncryptedChild(_) => {
714                    if dir.wrapping_key_id.is_none() {
715                        self.fsck.error(FsckError::UnencryptedDirectoryHasEncryptedChild(
716                            self.store_id,
717                            parent_id,
718                            child_id,
719                        ))?;
720                    }
721                }
722                _ => {
723                    bail!(
724                        "Unexpected object_key_data type in process_child: {:?}",
725                        object_key_data
726                    );
727                }
728            };
729        }
730        match (self.objects.get_mut(&child_id), object_descriptor) {
731            (
732                Some(ScannedObject::File(ScannedFile { parents, .. })),
733                ObjectDescriptor::File | ObjectDescriptor::Volume,
734            ) => {
735                parents.push(parent_id);
736            }
737            (
738                Some(ScannedObject::Directory(ScannedDir { parent, wrapping_key_id, .. })),
739                ObjectDescriptor::Directory,
740            ) => {
741                if matches!(
742                    object_key_data,
743                    ObjectKeyData::EncryptedChild(_) | ObjectKeyData::EncryptedCasefoldChild(_)
744                ) {
745                    if let Some(id) = wrapping_key_id {
746                        child_wrapping_key_id = Some(*id);
747                    } else {
748                        self.fsck.error(FsckError::EncryptedChildDirectoryNoWrappingKey(
749                            self.store_id,
750                            child_id,
751                        ))?;
752                    }
753                }
754                if parent.is_some() {
755                    // TODO(https://fxbug.dev/42168496): Accumulating and reporting all parents
756                    // might be useful.
757                    self.fsck
758                        .error(FsckError::MultipleLinksToDirectory(self.store_id, child_id))?;
759                }
760                *parent = Some(parent_id);
761            }
762            (Some(ScannedObject::Tombstone), _) => {
763                self.fsck.error(FsckError::TombstonedObjectHasRecords(self.store_id, parent_id))?;
764                return Ok(());
765            }
766            (None, _) => {
767                self.fsck.error(FsckError::MissingObjectInfo(self.store_id, child_id))?;
768                return Ok(());
769            }
770            (Some(s), _) => {
771                let expected = match s {
772                    ScannedObject::Directory(_) => ObjectDescriptor::Directory,
773                    ScannedObject::File(_) | ScannedObject::Graveyard => ObjectDescriptor::File,
774                    ScannedObject::Symlink(ScannedSymlink { parents, .. }) => {
775                        parents.push(parent_id);
776                        ObjectDescriptor::Symlink
777                    }
778                    ScannedObject::Tombstone => unreachable!(),
779                };
780                if &expected != object_descriptor {
781                    self.fsck.error(FsckError::ConflictingTypeForLink(
782                        self.store_id,
783                        child_id,
784                        expected.into(),
785                        object_descriptor.into(),
786                    ))?;
787                }
788            }
789        }
790        match self.objects.get_mut(&parent_id) {
791            Some(
792                ScannedObject::File(..) | ScannedObject::Graveyard | ScannedObject::Symlink(_),
793            ) => {
794                self.fsck.error(FsckError::ObjectHasChildren(self.store_id, parent_id))?;
795            }
796            Some(ScannedObject::Directory(ScannedDir {
797                observed_sub_dirs,
798                wrapping_key_id,
799                ..
800            })) => {
801                if let Some(parent_wrapping_key_id) = *wrapping_key_id {
802                    if !matches!(
803                        object_key_data,
804                        ObjectKeyData::EncryptedChild(_) | ObjectKeyData::EncryptedCasefoldChild(_)
805                    ) {
806                        self.fsck.error(FsckError::EncryptedDirectoryHasUnencryptedChild(
807                            self.store_id,
808                            parent_id,
809                            child_id,
810                        ))?;
811                    } else {
812                        if let Some(child_wrapping_key_id) = child_wrapping_key_id {
813                            if child_wrapping_key_id != parent_wrapping_key_id {
814                                self.fsck.error(
815                                    FsckError::ChildEncryptedWithDifferentWrappingKeyThanParent(
816                                        self.store_id,
817                                        parent_id,
818                                        child_id,
819                                        parent_wrapping_key_id,
820                                        child_wrapping_key_id,
821                                    ),
822                                )?;
823                            }
824                        }
825                    }
826                }
827                if *object_descriptor == ObjectDescriptor::Directory {
828                    *observed_sub_dirs += 1;
829                }
830            }
831            Some(ScannedObject::Tombstone) => {
832                self.fsck.error(FsckError::TombstonedObjectHasRecords(self.store_id, parent_id))?;
833            }
834            None => self.fsck.error(FsckError::MissingObjectInfo(self.store_id, parent_id))?,
835        }
836        Ok(())
837    }
838
839    // Process an extent, performing some checks and building fsck.allocations.
840    fn process_extent(
841        &mut self,
842        object_id: u64,
843        attribute_id: u64,
844        range: &Range<u64>,
845        device_offset: u64,
846        bs: u64,
847        is_overwrite_extent: bool,
848    ) -> Result<(), Error> {
849        if range.start % bs > 0 || range.end % bs > 0 {
850            self.fsck.error(FsckError::MisalignedExtent(
851                self.store_id,
852                object_id,
853                range.clone(),
854                0,
855            ))?;
856        }
857        if range.start >= range.end {
858            self.fsck.error(FsckError::MalformedExtent(
859                self.store_id,
860                object_id,
861                range.clone(),
862                0,
863            ))?;
864            return Ok(());
865        }
866        let len = range.end - range.start;
867        match self.objects.get_mut(&object_id) {
868            Some(
869                ScannedObject::File(ScannedFile { attributes, .. })
870                | ScannedObject::Directory(ScannedDir { attributes, .. })
871                | ScannedObject::Symlink(ScannedSymlink { attributes, .. }),
872            ) => {
873                let ScannedAttributes {
874                    attributes,
875                    tombstoned_attributes,
876                    observed_allocated_size: allocated_size,
877                    in_graveyard,
878                    ..
879                } = attributes;
880                match attributes.iter_mut().find(|attribute| attribute.attribute_id == attribute_id)
881                {
882                    Some(attribute) => {
883                        if !*in_graveyard
884                            && !tombstoned_attributes.contains(&attribute_id)
885                            && range.end > round_up(attribute.size, bs).unwrap()
886                        {
887                            self.fsck.error(FsckError::ExtentExceedsLength(
888                                self.store_id,
889                                object_id,
890                                attribute_id,
891                                attribute.size,
892                                range.into(),
893                            ))?;
894                        }
895                        attribute.observed_overwrite_extents =
896                            attribute.observed_overwrite_extents || is_overwrite_extent;
897                    }
898                    None => {
899                        self.fsck.warning(FsckWarning::ExtentForMissingAttribute(
900                            self.store_id,
901                            object_id,
902                            attribute_id,
903                        ))?;
904                    }
905                }
906                *allocated_size += len;
907            }
908            Some(ScannedObject::Graveyard | ScannedObject::Tombstone) => { /* NOP */ }
909            None => {
910                self.fsck
911                    .warning(FsckWarning::ExtentForNonexistentObject(self.store_id, object_id))?;
912            }
913        }
914        if device_offset % bs > 0 {
915            self.fsck.error(FsckError::MisalignedExtent(
916                self.store_id,
917                object_id,
918                range.clone(),
919                device_offset,
920            ))?;
921        }
922        let item = Item::new(
923            AllocatorKey { device_range: device_offset..device_offset + len },
924            AllocatorValue::Abs { count: 1, owner_object_id: self.store_id },
925        );
926        let lower_bound: AllocatorKey = item.key.lower_bound_for_merge_into();
927        self.fsck.allocations.merge_into(item, &lower_bound, allocator::merge::merge);
928        Ok(())
929    }
930
931    // A graveyard entry can either be for tombstoning a file/attribute, or for trimming a file.
932    fn handle_graveyard_entry(
933        &mut self,
934        object_id: u64,
935        attribute_id: Option<u64>,
936        tombstone: bool,
937    ) -> Result<(), Error> {
938        match self.objects.get_mut(&object_id) {
939            Some(ScannedObject::File(ScannedFile {
940                parents,
941                attributes: ScannedAttributes { in_graveyard, tombstoned_attributes, .. },
942                ..
943            })) => {
944                if attribute_id.is_none() {
945                    *in_graveyard = true;
946                }
947                if tombstone {
948                    if let Some(attribute_id) = attribute_id {
949                        tombstoned_attributes.push(attribute_id);
950                    } else {
951                        parents.push(INVALID_OBJECT_ID)
952                    }
953                }
954            }
955            Some(
956                ScannedObject::Directory(ScannedDir { attributes, .. })
957                | ScannedObject::Symlink(ScannedSymlink { attributes, .. }),
958            ) => {
959                if tombstone {
960                    if let Some(attribute_id) = attribute_id {
961                        attributes.tombstoned_attributes.push(attribute_id);
962                    } else {
963                        attributes.in_graveyard = true;
964                    }
965                } else {
966                    self.fsck.error(FsckError::UnexpectedObjectInGraveyard(object_id))?;
967                }
968            }
969            Some(ScannedObject::Graveyard | ScannedObject::Tombstone) => {
970                self.fsck.error(FsckError::UnexpectedObjectInGraveyard(object_id))?;
971            }
972            None => {
973                self.fsck.warning(FsckWarning::GraveyardRecordForAbsentObject(
974                    self.store_id,
975                    object_id,
976                ))?;
977            }
978        }
979        Ok(())
980    }
981
982    // Called when all items for the current file have been processed.
983    fn finish_file(&mut self) -> Result<(), Error> {
984        if let Some(current_file) = self.current_object.take() {
985            if self.is_encrypted {
986                let mut key_ids = vec![];
987                for id in current_file.key_ids.iter() {
988                    key_ids.push(*id);
989                }
990
991                // If the store is unencrypted, then the file might or might not have encryption
992                // keys (e.g. the root store has encrypted layer files). Also, if the object has
993                // lazily generated keys, like directories and symlinks, it will only have keys if
994                // an attribute has been written, in which case we check the existence of the key
995                // then.
996                if key_ids.is_empty() && !current_file.lazy_keys {
997                    self.fsck.error(FsckError::MissingEncryptionKeys(
998                        self.store_id,
999                        current_file.object_id,
1000                    ))?;
1001                }
1002
1003                match self.objects.get_mut(&current_file.object_id) {
1004                    Some(ScannedObject::Directory(ScannedDir {
1005                        wrapping_key_id,
1006                        attributes,
1007                        ..
1008                    })) => {
1009                        if !attributes.extended_attributes.is_empty() {
1010                            if !key_ids.contains(&VOLUME_DATA_KEY_ID) {
1011                                self.fsck.error(FsckError::MissingKey(
1012                                    self.store_id,
1013                                    current_file.object_id,
1014                                    VOLUME_DATA_KEY_ID,
1015                                ))?;
1016                            }
1017                        }
1018                        if wrapping_key_id.is_some() {
1019                            if !key_ids.contains(&FSCRYPT_KEY_ID) {
1020                                self.fsck.error(FsckError::MissingKey(
1021                                    self.store_id,
1022                                    current_file.object_id,
1023                                    FSCRYPT_KEY_ID,
1024                                ))?;
1025                            }
1026                        }
1027                    }
1028                    Some(ScannedObject::Symlink(ScannedSymlink { encrypted, .. })) => {
1029                        if *encrypted {
1030                            if !key_ids.contains(&FSCRYPT_KEY_ID) {
1031                                self.fsck.error(FsckError::MissingKey(
1032                                    self.store_id,
1033                                    current_file.object_id,
1034                                    FSCRYPT_KEY_ID,
1035                                ))?;
1036                            }
1037                        }
1038                    }
1039                    Some(_) => {}
1040                    None => self.fsck.error(FsckError::MissingObjectInfo(
1041                        self.store_id,
1042                        current_file.object_id,
1043                    ))?,
1044                }
1045            }
1046        }
1047        Ok(())
1048    }
1049}
1050
1051// Scans extents and directory child entries in the store, emitting synthesized allocations into
1052// |fsck.allocations|, updating the sizes for files in |scanned| and performing checks on directory
1053// children.
1054// TODO(https://fxbug.dev/42177485): Roll the extent scanning back into main function.
1055async fn scan_extents_and_directory_children<'a>(
1056    store: &ObjectStore,
1057    scanned: &mut ScannedStore<'a>,
1058    result: &mut FsckResult,
1059) -> Result<(), Error> {
1060    let bs = store.block_size();
1061    let layer_set = store.tree().layer_set();
1062    let mut merger = layer_set.merger();
1063    let mut iter = merger.query(Query::FullScan).await?;
1064    let mut allocated_bytes = 0;
1065    let mut extent_count = 0;
1066    let mut previous_object_id = INVALID_OBJECT_ID;
1067    while let Some(itemref) = iter.get() {
1068        match itemref {
1069            ItemRef {
1070                key:
1071                    ObjectKey {
1072                        object_id,
1073                        data:
1074                            ObjectKeyData::Attribute(
1075                                attribute_id,
1076                                AttributeKey::Extent(ExtentKey { range }),
1077                            ),
1078                    },
1079                value: ObjectValue::Extent(extent),
1080                ..
1081            } => {
1082                // Ignore deleted extents.
1083                if let ExtentValue::Some { device_offset, mode, .. } = extent {
1084                    let size = range.length().unwrap_or(0);
1085                    allocated_bytes += size;
1086
1087                    if previous_object_id != *object_id {
1088                        if previous_object_id != INVALID_OBJECT_ID {
1089                            result.fragmentation.extent_count
1090                                [FragmentationStats::get_histogram_bucket_for_count(
1091                                    extent_count,
1092                                )] += 1;
1093                        }
1094                        extent_count = 0;
1095                        previous_object_id = *object_id;
1096                    }
1097                    result.fragmentation.extent_size
1098                        [FragmentationStats::get_histogram_bucket_for_size(size)] += 1;
1099                    extent_count += 1;
1100
1101                    let is_overwrite_extent =
1102                        matches!(mode, ExtentMode::Overwrite | ExtentMode::OverwritePartial(_));
1103
1104                    scanned.process_extent(
1105                        *object_id,
1106                        *attribute_id,
1107                        range,
1108                        *device_offset,
1109                        bs,
1110                        is_overwrite_extent,
1111                    )?;
1112                };
1113            }
1114            ItemRef {
1115                key:
1116                    ObjectKey {
1117                        object_id,
1118                        data:
1119                            object_key_data @ (ObjectKeyData::Child { .. }
1120                            | ObjectKeyData::EncryptedChild(_)
1121                            | ObjectKeyData::CasefoldChild { .. }
1122                            | ObjectKeyData::EncryptedCasefoldChild { .. }),
1123                    },
1124                value: ObjectValue::Child(ChildValue { object_id: child_id, object_descriptor }),
1125                ..
1126            } => {
1127                scanned
1128                    .process_child(*object_id, *child_id, object_descriptor, object_key_data)
1129                    .await?
1130            }
1131            _ => {}
1132        }
1133        iter.advance().await?;
1134    }
1135    if extent_count != 0 && previous_object_id != INVALID_OBJECT_ID {
1136        result.fragmentation.extent_count
1137            [FragmentationStats::get_histogram_bucket_for_count(extent_count)] += 1;
1138    }
1139    scanned.fsck.verbose(format!(
1140        "Store {} has {} bytes allocated",
1141        store.store_object_id(),
1142        allocated_bytes
1143    ));
1144    Ok(())
1145}
1146
1147fn validate_attributes(
1148    fsck: &Fsck<'_>,
1149    store_id: u64,
1150    object_id: u64,
1151    attributes: &ScannedAttributes,
1152    is_file: bool,
1153    verified: VerifiedType,
1154    block_size: u64,
1155) -> Result<(), Error> {
1156    let ScannedAttributes {
1157        attributes,
1158        tombstoned_attributes,
1159        observed_allocated_size,
1160        stored_allocated_size,
1161        extended_attributes,
1162        ..
1163    } = attributes;
1164    if observed_allocated_size != stored_allocated_size {
1165        fsck.error(FsckError::AllocatedSizeMismatch(
1166            store_id,
1167            object_id,
1168            *observed_allocated_size,
1169            *stored_allocated_size,
1170        ))?;
1171    }
1172
1173    if is_file {
1174        let data_attribute =
1175            attributes.iter().find(|attribute| attribute.attribute_id == DEFAULT_DATA_ATTRIBUTE_ID);
1176        match data_attribute {
1177            None => fsck.error(FsckError::MissingDataAttribute(store_id, object_id))?,
1178            Some(data_attribute) => {
1179                let merkle_attribute = attributes
1180                    .iter()
1181                    .find(|attribute| attribute.attribute_id == FSVERITY_MERKLE_ATTRIBUTE_ID);
1182
1183                // Note a merkle attribute can exist for a non-verified file in the case that the
1184                // power cut while we were writing the merkle attribute across multiple txns. In
1185                // this case, the merkle attribute will be cleaned up on reboot.
1186                if !matches!(verified, VerifiedType::None) && merkle_attribute.is_none() {
1187                    fsck.error(FsckError::VerifiedFileDoesNotHaveAMerkleAttribute(
1188                        store_id, object_id,
1189                    ))?;
1190                }
1191
1192                match verified {
1193                    VerifiedType::None => {}
1194                    VerifiedType::Internal(hash_size) => {
1195                        if let Some(merkle_attribute) = merkle_attribute {
1196                            let expected_size = if data_attribute.size == 0 {
1197                                hash_size as u64
1198                            // Else, use ceiling integer division in case data_size is not a
1199                            // multiple of block size.
1200                            } else {
1201                                ((data_attribute.size + (block_size - 1)) / block_size)
1202                                    * hash_size as u64
1203                            };
1204                            if merkle_attribute.size != expected_size {
1205                                fsck.error(FsckError::IncorrectMerkleTreeSize(
1206                                    store_id,
1207                                    object_id,
1208                                    expected_size,
1209                                    merkle_attribute.size,
1210                                ))?;
1211                            }
1212                        } else {
1213                            fsck.error(FsckError::VerifiedFileDoesNotHaveAMerkleAttribute(
1214                                store_id, object_id,
1215                            ))?;
1216                        }
1217                    }
1218                    VerifiedType::F2fs(attr_end) => {
1219                        match merkle_attribute {
1220                            // Cannot verify descriptor, hash or contents, as the attribute may be
1221                            // encrypted with a key which we cannot access. We can just check if it
1222                            // ends at the right offset.
1223                            Some(merkle_attribute) => {
1224                                if merkle_attribute.size != attr_end {
1225                                    fsck.error(FsckError::IncorrectMerkleTreeSize(
1226                                        store_id,
1227                                        object_id,
1228                                        attr_end,
1229                                        merkle_attribute.size,
1230                                    ))?;
1231                                }
1232                            }
1233                            None => {
1234                                fsck.error(FsckError::VerifiedFileDoesNotHaveAMerkleAttribute(
1235                                    store_id, object_id,
1236                                ))?;
1237                            }
1238                        }
1239                    }
1240                }
1241            }
1242        }
1243    }
1244
1245    // Attributes queued for tombstoning must exist.
1246    for attr in tombstoned_attributes {
1247        if attributes.iter().find(|attribute| attribute.attribute_id == *attr).is_none() {
1248            fsck.error(FsckError::TombstonedAttributeDoesNotExist(store_id, object_id, *attr))?
1249        }
1250    }
1251
1252    for expected_attribute_id in extended_attributes {
1253        if attributes
1254            .iter()
1255            .find(|attribute| attribute.attribute_id == *expected_attribute_id)
1256            .is_none()
1257        {
1258            fsck.error(FsckError::MissingAttributeForExtendedAttribute(
1259                store_id,
1260                object_id,
1261                *expected_attribute_id,
1262            ))?;
1263        }
1264    }
1265
1266    for attribute in attributes {
1267        if attribute.attribute_id >= EXTENDED_ATTRIBUTE_RANGE_START
1268            && attribute.attribute_id < EXTENDED_ATTRIBUTE_RANGE_END
1269        {
1270            // For all the attributes in the extended attribute range, make sure there is an
1271            // extended attribute record for them.
1272            if extended_attributes
1273                .iter()
1274                .find(|xattr_id| attribute.attribute_id == **xattr_id)
1275                .is_none()
1276            {
1277                fsck.warning(FsckWarning::OrphanedExtendedAttribute(
1278                    store_id,
1279                    object_id,
1280                    attribute.attribute_id,
1281                ))?;
1282            }
1283        }
1284
1285        if attribute
1286            .has_overwrite_extents_flag
1287            .is_some_and(|has_flag| has_flag && !attribute.observed_overwrite_extents)
1288        {
1289            fsck.error(FsckError::MissingOverwriteExtents(
1290                store_id,
1291                object_id,
1292                attribute.attribute_id,
1293            ))?;
1294        }
1295        if attribute
1296            .has_overwrite_extents_flag
1297            .is_some_and(|has_flag| !has_flag && attribute.observed_overwrite_extents)
1298        {
1299            fsck.error(FsckError::OverwriteExtentFlagUnset(
1300                store_id,
1301                object_id,
1302                attribute.attribute_id,
1303            ))?;
1304        }
1305    }
1306
1307    Ok(())
1308}
1309
1310/// Scans an object store, accumulating all of its allocations into |fsck.allocations| and
1311/// validating various object properties.
1312pub(super) async fn scan_store(
1313    fsck: &Fsck<'_>,
1314    store: &ObjectStore,
1315    root_objects: impl AsRef<[u64]>,
1316    result: &mut FsckResult,
1317) -> Result<(), Error> {
1318    let store_id = store.store_object_id();
1319    let next_object_id = store.query_next_object_id();
1320
1321    let mut scanned = ScannedStore::new(
1322        fsck,
1323        store.crypt(),
1324        root_objects,
1325        store_id,
1326        store.is_root(),
1327        store.is_encrypted(),
1328        store.root_directory_object_id(),
1329    );
1330
1331    // Scan the store for objects, attributes, and parent/child relationships.
1332    let layer_set = store.tree().layer_set();
1333    let mut merger = layer_set.merger();
1334    let mut iter = merger.query(Query::FullScan).await?;
1335    let mut last_item: Option<Item<ObjectKey, ObjectValue>> = None;
1336    while let Some(item) = iter.get() {
1337        if let Some(last_item) = last_item {
1338            if last_item.key >= *item.key {
1339                fsck.fatal(FsckFatal::MisOrderedObjectStore(store_id))?;
1340            }
1341        }
1342        if item.key.object_id == INVALID_OBJECT_ID {
1343            fsck.warning(FsckWarning::InvalidObjectIdInStore(
1344                store_id,
1345                item.key.into(),
1346                item.value.into(),
1347            ))?;
1348        }
1349        if next_object_id != INVALID_OBJECT_ID && item.key.object_id == next_object_id {
1350            fsck.error(FsckError::NextObjectIdInUse(store_id, next_object_id))?;
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        scanned.process(item.key, item.value)?;
1358        last_item = Some(item.cloned());
1359        iter.advance().await?;
1360    }
1361    scanned.finish_file()?;
1362
1363    for (project_id, node_id) in scanned.used_project_ids.iter() {
1364        if !scanned.stored_project_usages.contains_key(project_id) {
1365            fsck.error(FsckError::ProjectUsedWithNoUsageTracking(store_id, *project_id, *node_id))?;
1366        }
1367    }
1368    for (project_id, (bytes_stored, nodes_stored)) in scanned.stored_project_usages.iter() {
1369        if let Some((bytes_used, nodes_used)) = scanned.total_project_usages.get(&project_id) {
1370            if *bytes_stored != *bytes_used || *nodes_stored != *nodes_used {
1371                fsck.warning(FsckWarning::ProjectUsageInconsistent(
1372                    store_id,
1373                    *project_id,
1374                    (*bytes_stored, *nodes_stored),
1375                    (*bytes_used, *nodes_used),
1376                ))?;
1377            }
1378        } else {
1379            if *bytes_stored > 0 || *nodes_stored > 0 {
1380                fsck.warning(FsckWarning::ProjectUsageInconsistent(
1381                    store_id,
1382                    *project_id,
1383                    (*bytes_stored, *nodes_stored),
1384                    (0, 0),
1385                ))?;
1386            }
1387        }
1388    }
1389
1390    // Add a reference for files in the graveyard (which acts as the file's parent until it is
1391    // purged, leaving only the Object record in the original store and no links to the file).
1392    // This must be done after scanning the object store.
1393    let layer_set = store.tree().layer_set();
1394    let mut merger = layer_set.merger();
1395    let mut iter = fsck.assert(
1396        Graveyard::iter(store.graveyard_directory_object_id(), &mut merger).await,
1397        FsckFatal::MalformedGraveyard,
1398    )?;
1399    while let Some(info) = iter.get() {
1400        match info.value() {
1401            ObjectValue::Some => {
1402                scanned.handle_graveyard_entry(info.object_id(), info.attribute_id(), true)?
1403            }
1404            ObjectValue::Trim => {
1405                if let Some(attribute_id) = info.attribute_id() {
1406                    fsck.error(FsckError::TrimValueForGraveyardAttributeEntry(
1407                        store_id,
1408                        info.object_id(),
1409                        attribute_id,
1410                    ))?
1411                } else {
1412                    scanned.handle_graveyard_entry(info.object_id(), None, false)?
1413                }
1414            }
1415            _ => fsck.error(FsckError::BadGraveyardValue(store_id, info.object_id()))?,
1416        }
1417        fsck.assert(iter.advance().await, FsckFatal::MalformedGraveyard)?;
1418    }
1419
1420    scan_extents_and_directory_children(store, &mut scanned, result).await?;
1421
1422    // At this point, we've provided all of the inputs to |scanned|.
1423
1424    // Mark all the root directories as visited so that cycle detection below works.
1425    for oid in scanned.root_objects {
1426        if let Some(ScannedObject::Directory(ScannedDir { visited, .. })) =
1427            scanned.objects.get_mut(&oid)
1428        {
1429            *visited.get_mut() = true;
1430        }
1431    }
1432
1433    // Iterate over all objects performing checks we were unable to perform earlier.
1434    let mut num_objects = 0;
1435    let mut files = 0;
1436    let mut directories = 0;
1437    let mut symlinks = 0;
1438    let mut tombstones = 0;
1439    let mut other = 0;
1440    let mut stack = Vec::new();
1441    for (object_id, object) in &scanned.objects {
1442        num_objects += 1;
1443        match object {
1444            ScannedObject::File(ScannedFile {
1445                parents,
1446                stored_refs,
1447                attributes,
1448                verified: is_verified,
1449                ..
1450            }) => {
1451                files += 1;
1452                let observed_refs = parents.len().try_into().unwrap();
1453                // observed_refs == 0 is handled separately to distinguish orphaned objects
1454                if observed_refs != *stored_refs && observed_refs > 0 {
1455                    fsck.error(FsckError::RefCountMismatch(
1456                        *object_id,
1457                        observed_refs,
1458                        *stored_refs,
1459                    ))?;
1460                }
1461                validate_attributes(
1462                    fsck,
1463                    store_id,
1464                    *object_id,
1465                    attributes,
1466                    true,
1467                    is_verified.clone(),
1468                    store.block_size(),
1469                )?;
1470                if parents.is_empty() {
1471                    fsck.warning(FsckWarning::OrphanedObject(store_id, *object_id))?;
1472                }
1473                if parents.contains(&INVALID_OBJECT_ID) && parents.len() > 1 {
1474                    let parents = parents
1475                        .iter()
1476                        .filter(|oid| **oid != INVALID_OBJECT_ID)
1477                        .cloned()
1478                        .collect::<Vec<u64>>();
1479                    fsck.error(FsckError::ZombieFile(store_id, *object_id, parents))?;
1480                }
1481            }
1482            ScannedObject::Directory(ScannedDir {
1483                stored_sub_dirs,
1484                observed_sub_dirs,
1485                parent,
1486                visited,
1487                attributes,
1488                ..
1489            }) => {
1490                directories += 1;
1491                if *observed_sub_dirs != *stored_sub_dirs {
1492                    fsck.error(FsckError::SubDirCountMismatch(
1493                        store_id,
1494                        *object_id,
1495                        *observed_sub_dirs,
1496                        *stored_sub_dirs,
1497                    ))?;
1498                }
1499                validate_attributes(
1500                    fsck,
1501                    store_id,
1502                    *object_id,
1503                    attributes,
1504                    false,
1505                    VerifiedType::None,
1506                    store.block_size(),
1507                )?;
1508                if let &Some(mut oid) = parent {
1509                    if attributes.in_graveyard {
1510                        fsck.error(FsckError::ZombieDir(store_id, *object_id, oid))?;
1511                    }
1512                    // Check this directory is attached to a root object.
1513                    // SAFETY: This is safe because here and below are the only places that we
1514                    // manipulate `visited`.
1515                    if !std::mem::replace(unsafe { &mut *visited.get() }, true) {
1516                        stack.push(*object_id);
1517                        loop {
1518                            if let Some(ScannedObject::Directory(ScannedDir {
1519                                parent: Some(parent),
1520                                visited,
1521                                ..
1522                            })) = scanned.objects.get(&oid)
1523                            {
1524                                stack.push(oid);
1525                                oid = *parent;
1526                                // SAFETY: See above.
1527                                if std::mem::replace(unsafe { &mut *visited.get() }, true) {
1528                                    break;
1529                                }
1530                            } else {
1531                                // This indicates an error (e.g. missing parent), but they should be
1532                                // reported elsewhere.
1533                                break;
1534                            }
1535                        }
1536                        // Check that the object we got to isn't one in our stack which would
1537                        // indicate a cycle.
1538                        for s in stack.drain(..) {
1539                            if s == oid {
1540                                fsck.error(FsckError::LinkCycle(store_id, oid))?;
1541                                break;
1542                            }
1543                        }
1544                    }
1545                } else if !attributes.in_graveyard {
1546                    fsck.warning(FsckWarning::OrphanedObject(store_id, *object_id))?;
1547                }
1548            }
1549            ScannedObject::Graveyard => other += 1,
1550            ScannedObject::Symlink(ScannedSymlink { parents, stored_refs, attributes, .. }) => {
1551                symlinks += 1;
1552                let observed_refs = parents.len().try_into().unwrap();
1553                // observed_refs == 0 is handled separately to distinguish orphaned objects
1554                if observed_refs != *stored_refs && observed_refs > 0 {
1555                    fsck.error(FsckError::RefCountMismatch(
1556                        *object_id,
1557                        observed_refs,
1558                        *stored_refs,
1559                    ))?;
1560                }
1561                validate_attributes(
1562                    fsck,
1563                    store_id,
1564                    *object_id,
1565                    attributes,
1566                    false,
1567                    VerifiedType::None,
1568                    store.block_size(),
1569                )?;
1570                if attributes.in_graveyard {
1571                    if !parents.is_empty() {
1572                        fsck.error(FsckError::ZombieSymlink(
1573                            store_id,
1574                            *object_id,
1575                            parents.clone(),
1576                        ))?;
1577                    }
1578                } else if parents.is_empty() {
1579                    fsck.warning(FsckWarning::OrphanedObject(store_id, *object_id))?;
1580                }
1581            }
1582            ScannedObject::Tombstone => {
1583                tombstones += 1;
1584                num_objects -= 1;
1585            }
1586        }
1587    }
1588    if num_objects != store.object_count() {
1589        fsck.error(FsckError::ObjectCountMismatch(store_id, num_objects, store.object_count()))?;
1590    }
1591    fsck.verbose(format!(
1592        "Store {store_id} has {files} files, {directories} dirs, {symlinks} symlinks, \
1593         {tombstones} tombstones, {other} other objects",
1594    ));
1595
1596    Ok(())
1597}