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                    {
692                        // Ignore unwrap errors.
693                        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                                    // Ignore decryption errors; we can't verify the hash.
707                                }
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                    // TODO(https://fxbug.dev/42168496): Accumulating and reporting all parents
755                    // might be useful.
756                    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    // Process an extent, performing some checks and building fsck.allocations.
839    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) => { /* NOP */ }
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    // A graveyard entry can either be for tombstoning a file/attribute, or for trimming a file.
931    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    // Called when all items for the current file have been processed.
982    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 the store is unencrypted, then the file might or might not have encryption
991                // keys (e.g. the root store has encrypted layer files). Also, if the object has
992                // lazily generated keys, like directories and symlinks, it will only have keys if
993                // an attribute has been written, in which case we check the existence of the key
994                // then.
995                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(&current_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
1050// Scans extents and directory child entries in the store, emitting synthesized allocations into
1051// |fsck.allocations|, updating the sizes for files in |scanned| and performing checks on directory
1052// children.
1053// TODO(https://fxbug.dev/42177485): Roll the extent scanning back into main function.
1054async 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                // Ignore deleted extents.
1082                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                // Note a merkle attribute can exist for a non-verified file in the case that the
1183                // power cut while we were writing the merkle attribute across multiple txns. In
1184                // this case, the merkle attribute will be cleaned up on reboot.
1185                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, use ceiling integer division in case data_size is not a
1198                            // multiple of block size.
1199                            } 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                            // Cannot verify descriptor, hash or contents, as the attribute may be
1220                            // encrypted with a key which we cannot access. We can just check if it
1221                            // ends at the right offset.
1222                            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    // Attributes queued for tombstoning must exist.
1245    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            // For all the attributes in the extended attribute range, make sure there is an
1270            // extended attribute record for them.
1271            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
1309/// Scans an object store, accumulating all of its allocations into |fsck.allocations| and
1310/// validating various object properties.
1311pub(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    // Scan the store for objects, attributes, and parent/child relationships.
1331    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    // Add a reference for files in the graveyard (which acts as the file's parent until it is
1402    // purged, leaving only the Object record in the original store and no links to the file).
1403    // This must be done after scanning the object store.
1404    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    // At this point, we've provided all of the inputs to |scanned|.
1434
1435    // Mark all the root directories as visited so that cycle detection below works.
1436    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    // Iterate over all objects performing checks we were unable to perform earlier.
1445    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                // observed_refs == 0 is handled separately to distinguish orphaned objects
1465                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                    // Check this directory is attached to a root object.
1524                    // SAFETY: This is safe because here and below are the only places that we
1525                    // manipulate `visited`.
1526                    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                                // SAFETY: See above.
1538                                if std::mem::replace(unsafe { &mut *visited.get() }, true) {
1539                                    break;
1540                                }
1541                            } else {
1542                                // This indicates an error (e.g. missing parent), but they should be
1543                                // reported elsewhere.
1544                                break;
1545                            }
1546                        }
1547                        // Check that the object we got to isn't one in our stack which would
1548                        // indicate a cycle.
1549                        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                // observed_refs == 0 is handled separately to distinguish orphaned objects
1565                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}