Skip to main content

fxfs/fsck/
errors.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::log::*;
6use crate::lsm_tree::types::ItemRef;
7use crate::object_store::allocator::{AllocatorKey, AllocatorValue};
8use crate::object_store::{AttributeId, ObjectDescriptor, ProjectId};
9use fxfs_crypto::WrappingKeyId;
10use std::ops::Range;
11
12#[derive(Clone, Debug, PartialEq)]
13pub enum FsckIssue {
14    /// Warnings don't prevent the filesystem from mounting and don't fail fsck, but they indicate a
15    /// consistency issue.
16    Warning(FsckWarning),
17    /// Errors prevent the filesystem from mounting, and will result in fsck failing, but will let
18    /// fsck continue to run to find more issues.
19    Error(FsckError),
20    /// Fatal errors are like Errors, but they're serious enough that fsck should be halted, as any
21    /// further results will probably be false positives.
22    Fatal(FsckFatal),
23}
24
25impl FsckIssue {
26    /// Translates an error to a human-readable string, intended for reporting errors to the user.
27    /// For debugging, std::fmt::Debug is preferred.
28    // TODO(https://fxbug.dev/42177349): Localization
29    pub fn to_string(&self) -> String {
30        match self {
31            FsckIssue::Warning(w) => format!("WARNING: {}", w.to_string()),
32            FsckIssue::Error(e) => format!("ERROR: {}", e.to_string()),
33            FsckIssue::Fatal(f) => format!("FATAL: {}", f.to_string()),
34        }
35    }
36    pub fn is_error(&self) -> bool {
37        match self {
38            FsckIssue::Error(_) | FsckIssue::Fatal(_) => true,
39            FsckIssue::Warning(_) => false,
40        }
41    }
42    pub fn log(&self) {
43        match self {
44            FsckIssue::Warning(w) => w.log(),
45            FsckIssue::Error(e) => e.log(),
46            FsckIssue::Fatal(f) => f.log(),
47        }
48    }
49}
50
51#[derive(Clone, Debug, PartialEq)]
52#[allow(dead_code)]
53pub struct Allocation {
54    range: Range<u64>,
55    value: AllocatorValue,
56}
57
58impl From<ItemRef<'_, AllocatorKey, AllocatorValue>> for Allocation {
59    fn from(item: ItemRef<'_, AllocatorKey, AllocatorValue>) -> Self {
60        Self { range: (*item.key.device_range).clone(), value: item.value.clone() }
61    }
62}
63
64#[derive(Clone, Debug, PartialEq)]
65#[allow(dead_code)]
66pub struct Key(String);
67
68impl<K: std::fmt::Debug, V> From<ItemRef<'_, K, V>> for Key {
69    fn from(item: ItemRef<'_, K, V>) -> Self {
70        Self(format!("{:?}", item.key))
71    }
72}
73
74impl<K: std::fmt::Debug> From<&K> for Key {
75    fn from(k: &K) -> Self {
76        Self(format!("{:?}", k))
77    }
78}
79
80#[derive(Clone, Debug, PartialEq)]
81#[allow(dead_code)]
82pub struct Value(String);
83
84impl<K, V: std::fmt::Debug> From<ItemRef<'_, K, V>> for Value {
85    fn from(item: ItemRef<'_, K, V>) -> Self {
86        Self(format!("{:?}", item.value))
87    }
88}
89
90// `From<V: std::fmt::Debug> for Value` creates a recursive definition since Value is Debug, so we
91// have to go concrete here.
92impl From<ObjectDescriptor> for Value {
93    fn from(d: ObjectDescriptor) -> Self {
94        Self(format!("{:?}", d))
95    }
96}
97
98impl<V: std::fmt::Debug> From<&V> for Value {
99    fn from(v: &V) -> Self {
100        Self(format!("{:?}", v))
101    }
102}
103
104#[derive(Clone, Debug, PartialEq)]
105pub enum FsckWarning {
106    ExtentForMissingAttribute(u64, u64, AttributeId),
107    ExtentForNonexistentObject(u64, u64),
108    GraveyardRecordForAbsentObject(u64, u64),
109    InvalidObjectIdInStore(u64, Key, Value),
110    LimitForNonExistentStore(u64, u64),
111    OrphanedAttribute(u64, u64, AttributeId),
112    OrphanedObject(u64, u64),
113    OrphanedKeys(u64, u64),
114    OrphanedExtendedAttribute(u64, u64, AttributeId),
115    OrphanedExtendedAttributeRecord(u64, u64),
116    ProjectUsageInconsistent(u64, ProjectId, (i64, i64), (i64, i64)),
117}
118
119impl FsckWarning {
120    fn to_string(&self) -> String {
121        match self {
122            FsckWarning::ExtentForMissingAttribute(store_id, object_id, attr_id) => {
123                format!(
124                    "Found an extent in store {store_id} for missing attribute {attr_id} on \
125                    object {object_id}"
126                )
127            }
128            FsckWarning::ExtentForNonexistentObject(store_id, object_id) => {
129                format!("Found an extent in store {store_id} for a non-existent object {object_id}")
130            }
131            FsckWarning::GraveyardRecordForAbsentObject(store_id, object_id) => {
132                format!(
133                    "Graveyard contains an entry for object {object_id} in store {store_id}, but \
134                    that object is absent"
135                )
136            }
137            FsckWarning::InvalidObjectIdInStore(store_id, key, value) => {
138                format!("Store {store_id} has an invalid object ID ({key:?}, {value:?})")
139            }
140            FsckWarning::LimitForNonExistentStore(store_id, limit) => {
141                format!("Bytes limit of {limit} found for nonexistent store id {store_id}")
142            }
143            FsckWarning::OrphanedAttribute(store_id, object_id, attribute_id) => {
144                format!(
145                    "Attribute {attribute_id} found for object {object_id} which doesn't exist in \
146                    store {store_id}"
147                )
148            }
149            FsckWarning::OrphanedObject(store_id, object_id) => {
150                format!("Orphaned object {object_id} was found in store {store_id}")
151            }
152            FsckWarning::OrphanedKeys(store_id, object_id) => {
153                format!("Orphaned keys for object {object_id} were found in store {store_id}")
154            }
155            FsckWarning::OrphanedExtendedAttribute(store_id, object_id, attribute_id) => {
156                format!(
157                    "Orphaned extended attribute for object {object_id} was found in store \
158                    {store_id} with attribute id {attribute_id}"
159                )
160            }
161            FsckWarning::OrphanedExtendedAttributeRecord(store_id, object_id) => {
162                format!(
163                    "Orphaned extended attribute record for object {object_id} was found in store \
164                    {store_id}"
165                )
166            }
167            FsckWarning::ProjectUsageInconsistent(
168                store_id,
169                project_id,
170                (stored_bytes, stored_nodes),
171                (used_bytes, used_nodes),
172            ) => {
173                format!(
174                    "Project id {project_id} in store {store_id} expected usage \
175                    ({stored_bytes}, {stored_nodes}) found ({used_bytes}, {used_nodes})"
176                )
177            }
178        }
179    }
180
181    fn log(&self) {
182        match self {
183            FsckWarning::ExtentForMissingAttribute(store_id, oid, attr_id) => {
184                warn!(store_id, oid, attr_id; "Found an extent for a missing attribute");
185            }
186            FsckWarning::ExtentForNonexistentObject(store_id, oid) => {
187                warn!(store_id, oid; "Extent for missing object");
188            }
189            FsckWarning::GraveyardRecordForAbsentObject(store_id, oid) => {
190                warn!(store_id, oid; "Graveyard entry for missing object");
191            }
192            FsckWarning::InvalidObjectIdInStore(store_id, key, value) => {
193                warn!(store_id, key:?, value:?; "Invalid object ID");
194            }
195            FsckWarning::LimitForNonExistentStore(store_id, limit) => {
196                warn!(store_id, limit; "Found limit for non-existent owner store.");
197            }
198            FsckWarning::OrphanedAttribute(store_id, oid, attribute_id) => {
199                warn!(store_id, oid, attribute_id; "Attribute for missing object");
200            }
201            FsckWarning::OrphanedObject(store_id, oid) => {
202                warn!(oid, store_id; "Orphaned object");
203            }
204            FsckWarning::OrphanedKeys(store_id, oid) => {
205                warn!(oid, store_id; "Orphaned keys");
206            }
207            FsckWarning::OrphanedExtendedAttribute(store_id, oid, attribute_id) => {
208                warn!(oid, store_id, attribute_id; "Orphaned extended attribute");
209            }
210            FsckWarning::OrphanedExtendedAttributeRecord(store_id, oid) => {
211                warn!(oid, store_id; "Orphaned extended attribute record");
212            }
213            FsckWarning::ProjectUsageInconsistent(store_id, project_id, stored, used) => {
214                warn!(project_id, store_id, stored:?, used:?; "Project Inconsistent");
215            }
216        }
217    }
218}
219
220#[derive(Clone, Debug, PartialEq)]
221pub enum FsckError {
222    AllocatedBytesMismatch(Vec<(u64, u64)>, Vec<(u64, u64)>),
223    AllocatedSizeMismatch(u64, u64, u64, u64),
224    AllocationForNonexistentOwner(Allocation),
225    AllocationMismatch(Allocation, Allocation),
226    BadCasefoldHash(u64, u64, u64, u32, u32),
227    BadGraveyardValue(u64, u64),
228    BadLastObjectId(u64, u64),
229    CasefoldInconsistency(u64, u64, u64),
230    ChildEncryptedWithDifferentWrappingKeyThanParent(u64, u64, u64, WrappingKeyId, WrappingKeyId),
231    ConflictingTypeForLink(u64, u64, Value, Value),
232    DuplicateKey(u64, u64, u64),
233    EncryptedChildDirectoryNoWrappingKey(u64, u64),
234    EncryptedDirectoryHasUnencryptedChild(u64, u64, u64),
235    ExtentExceedsLength(u64, u64, AttributeId, u64, Value),
236    ExtraAllocations(Vec<Allocation>),
237    IllegalKeyInRootStore(u64, u64),
238    IncorrectMerkleTreeSize(u64, u64, u64, u64),
239    LinkCycle(u64, u64),
240    MalformedAllocation(Allocation),
241    MalformedExtent(u64, u64, Range<u64>, u64),
242    MalformedObjectRecord(u64, Key, Value),
243    MisalignedAllocation(Allocation),
244    MisalignedExtent(u64, u64, Range<u64>, u64),
245    MissingAllocation(Allocation),
246    InvalidExtendedAttributeId(u64, u64, AttributeId),
247    MissingAttributeForExtendedAttribute(u64, u64, AttributeId),
248    MissingDataAttribute(u64, u64),
249    MissingEncryptionKeys(u64, u64),
250    MissingKey(u64, u64, u64),
251    MissingObjectInfo(u64, u64),
252    MissingOverwriteExtents(u64, u64, AttributeId),
253    MultipleLinksToDirectory(u64, u64),
254    NextObjectIdInUse(u64, u64),
255    NonFileMarkedAsVerified(u64, u64),
256    NonRootProjectIdMetadata(u64, u64, ProjectId),
257    ObjectCountMismatch(u64, u64, u64),
258    ObjectHasChildren(u64, u64),
259    OverwriteExtentFlagUnset(u64, u64, AttributeId),
260    ProjectOnGraveyard(u64, ProjectId, u64),
261    ProjectUsedWithNoUsageTracking(u64, ProjectId, u64),
262    RefCountMismatch(u64, u64, u64),
263    RootObjectHasParent(u64, u64, u64),
264    SubDirCountMismatch(u64, u64, u64, u64),
265    TombstonedAttributeDoesNotExist(u64, u64, AttributeId),
266    TombstonedObjectHasRecords(u64, u64),
267    TrimValueForGraveyardAttributeEntry(u64, u64, AttributeId),
268    UnencryptedDirectoryHasEncryptedChild(u64, u64, u64),
269    UnexpectedJournalFileOffset(u64),
270    UnexpectedObjectInGraveyard(u64),
271    UnexpectedRecordInObjectStore(u64, Key, Value),
272    VerifiedFileDoesNotHaveAMerkleAttribute(u64, u64),
273    VolumeInChildStore(u64, u64),
274    ZombieDir(u64, u64, u64),
275    ZombieFile(u64, u64, Vec<u64>),
276    ZombieSymlink(u64, u64, Vec<u64>),
277    InvalidInoLblk32KeyUsage(u64, u64),
278}
279
280impl FsckError {
281    fn to_string(&self) -> String {
282        match self {
283            FsckError::AllocatedBytesMismatch(observed, stored) => {
284                format!(
285                    "Per-owner allocated bytes was {stored:?}, but sum of allocations gave \
286                     {observed:?}"
287                )
288            }
289            FsckError::AllocatedSizeMismatch(store_id, oid, observed, stored) => {
290                format!(
291                    "Expected {stored} bytes allocated for object {oid} in store {store_id}, but \
292                     found {observed} bytes"
293                )
294            }
295            FsckError::AllocationForNonexistentOwner(alloc) => {
296                format!("Allocation {alloc:?} for non-existent owner")
297            }
298            FsckError::AllocationMismatch(observed, stored) => {
299                format!("Observed allocation {observed:?} but allocator has {stored:?}")
300            }
301            FsckError::BadCasefoldHash(store_id, parent_id, child_id, expected, actual) => {
302                format!(
303                    "Bad casefold hash code for store {store_id}, directory {parent_id}, child \
304                     {child_id}. Expected {expected:08x}, actual {actual:08x}",
305                )
306            }
307            FsckError::BadLastObjectId(highest, last_object_id) => {
308                format!("Last object ID {last_object_id} is less than highest found {highest}")
309            }
310            FsckError::CasefoldInconsistency(store_id, parent_id, child_id) => {
311                format!(
312                    "CasefoldChild inconsistent for store {store_id}, directory {parent_id}, \
313                     child {child_id}"
314                )
315            }
316            FsckError::ConflictingTypeForLink(store_id, object_id, expected, actual) => {
317                format!(
318                    "Object {object_id} in store {store_id} is of type {expected:?} but has a \
319                     link of type {actual:?}"
320                )
321            }
322            FsckError::ExtentExceedsLength(store_id, oid, attr_id, size, extent) => {
323                format!(
324                    "Extent {extent:?} exceeds length {size} of attr {attr_id} on object {oid} in \
325                     store {store_id}"
326                )
327            }
328            FsckError::ExtraAllocations(allocations) => {
329                format!("Unexpected allocations {allocations:?}")
330            }
331            FsckError::IllegalKeyInRootStore(store_id, object_id) => {
332                format!("Object {object_id} in root store {store_id} uses an illegal key type")
333            }
334            FsckError::ObjectHasChildren(store_id, object_id) => {
335                format!("Object {object_id} in store {store_id} has unexpected children")
336            }
337            FsckError::UnexpectedJournalFileOffset(object_id) => {
338                format!(
339                    "SuperBlock journal_file_offsets contains unexpected object_id \
340                     ({object_id:?})."
341                )
342            }
343            FsckError::LinkCycle(store_id, object_id) => {
344                format!("Detected cycle involving object {object_id} in store {store_id}")
345            }
346            FsckError::MalformedAllocation(allocations) => {
347                format!("Malformed allocation {allocations:?}")
348            }
349            FsckError::MalformedExtent(store_id, oid, extent, device_offset) => {
350                format!(
351                    "Extent {extent:?} (offset {device_offset}) for object {oid} in store \
352                     {store_id} is malformed"
353                )
354            }
355            FsckError::MalformedObjectRecord(store_id, key, value) => {
356                format!(
357                    "Object record in store {store_id} has mismatched key {key:?} and value \
358                     {value:?}"
359                )
360            }
361            FsckError::MisalignedAllocation(allocations) => {
362                format!("Misaligned allocation {allocations:?}")
363            }
364            FsckError::MisalignedExtent(store_id, oid, extent, device_offset) => {
365                format!(
366                    "Extent {extent:?} (offset {device_offset}) for object {oid} in store \
367                     {store_id} is misaligned"
368                )
369            }
370            FsckError::MissingAllocation(allocation) => {
371                format!("Observed {allocation:?} but didn't find record in allocator")
372            }
373            FsckError::InvalidExtendedAttributeId(store_id, oid, attribute_id) => {
374                format!(
375                    "Object {oid} in store {store_id} has an extended attribute stored in an \
376                     invalid attribute {attribute_id}"
377                )
378            }
379            FsckError::MissingAttributeForExtendedAttribute(store_id, oid, attribute_id) => {
380                format!(
381                    "Object {oid} in store {store_id} has an extended attribute stored in a \
382                     nonexistent attribute {attribute_id}"
383                )
384            }
385            FsckError::MissingDataAttribute(store_id, oid) => {
386                format!("File {oid} in store {store_id} didn't have the default data attribute")
387            }
388            FsckError::MissingObjectInfo(store_id, object_id) => {
389                format!("Object {object_id} in store {store_id} had no object record")
390            }
391            FsckError::MultipleLinksToDirectory(store_id, object_id) => {
392                format!("Directory {object_id} in store {store_id} has multiple links")
393            }
394            FsckError::NonRootProjectIdMetadata(store_id, object_id, project_id) => {
395                format!(
396                    "Project Id {project_id} metadata in store {store_id} attached to object \
397                     {object_id}"
398                )
399            }
400            FsckError::ObjectCountMismatch(store_id, observed, stored) => {
401                format!("Store {store_id} had {observed} objects, expected {stored}")
402            }
403            FsckError::ProjectOnGraveyard(store_id, project_id, object_id) => {
404                format!(
405                    "Store {store_id} had graveyard object {object_id} with project id \
406                     {project_id}"
407                )
408            }
409            FsckError::ProjectUsedWithNoUsageTracking(store_id, project_id, node_id) => {
410                format!(
411                    "Store {store_id} had node {node_id} with project ids {project_id} but no \
412                     usage tracking metadata"
413                )
414            }
415            FsckError::RefCountMismatch(oid, observed, stored) => {
416                format!("Object {oid} had {observed} references, expected {stored}")
417            }
418            FsckError::RootObjectHasParent(store_id, object_id, apparent_parent_id) => {
419                format!(
420                    "Object {object_id} is child of {apparent_parent_id} but is a root object of \
421                     store {store_id}"
422                )
423            }
424            FsckError::SubDirCountMismatch(store_id, object_id, observed, stored) => {
425                format!(
426                    "Directory {object_id} in store {store_id} should have {stored} sub dirs but \
427                     had {observed}"
428                )
429            }
430            FsckError::TombstonedObjectHasRecords(store_id, object_id) => {
431                format!(
432                    "Tombstoned object {object_id} in store {store_id} was referenced by other \
433                     records"
434                )
435            }
436            FsckError::UnexpectedObjectInGraveyard(object_id) => {
437                format!("Found a non-file object {object_id} in graveyard")
438            }
439            FsckError::UnexpectedRecordInObjectStore(store_id, key, value) => {
440                format!("Unexpected record ({key:?}, {value:?}) in object store {store_id}")
441            }
442            FsckError::VolumeInChildStore(store_id, object_id) => {
443                format!("Volume {object_id} found in child store {store_id} instead of root store")
444            }
445            FsckError::BadGraveyardValue(store_id, object_id) => {
446                format!("Bad graveyard value with key <{store_id}, {object_id}>")
447            }
448            FsckError::MissingEncryptionKeys(store_id, object_id) => {
449                format!("Missing encryption keys for <{store_id}, {object_id}>")
450            }
451            FsckError::MissingKey(store_id, object_id, key_id) => {
452                format!("Missing encryption key for <{store_id}, {object_id}, {key_id}>")
453            }
454            FsckError::EncryptedChildDirectoryNoWrappingKey(store_id, object_id) => {
455                format!(
456                    "Encrypted directory {object_id} in store {store_id} does not have a wrapping \
457                     key id set"
458                )
459            }
460            FsckError::EncryptedDirectoryHasUnencryptedChild(store_id, parent_oid, child_oid) => {
461                format!(
462                    "Encrypted parent directory {parent_oid} in store {store_id} has unencrypted \
463                     child {child_oid}"
464                )
465            }
466            FsckError::UnencryptedDirectoryHasEncryptedChild(store_id, parent_oid, child_oid) => {
467                format!(
468                    "Unencrypted parent directory {parent_oid} in store {store_id} has encrypted \
469                     child {child_oid}"
470                )
471            }
472            FsckError::ChildEncryptedWithDifferentWrappingKeyThanParent(
473                store_id,
474                parent_id,
475                child_id,
476                parent_wrapping_key_id,
477                child_wrapping_key_id,
478            ) => {
479                format!(
480                    "Parent directory {parent_id} in store {store_id} encrypted with \
481                     {parent_wrapping_key_id:?}, child {child_id} encrypted with \
482                     {child_wrapping_key_id:?}",
483                )
484            }
485            FsckError::DuplicateKey(store_id, object_id, key_id) => {
486                format!("Duplicate key for <{store_id}, {object_id}, {key_id}>")
487            }
488            FsckError::ZombieFile(store_id, object_id, parent_object_ids) => {
489                format!(
490                    "File {object_id} in store {store_id} is in graveyard but still has links \
491                     from {parent_object_ids:?}",
492                )
493            }
494            FsckError::ZombieDir(store_id, object_id, parent_object_id) => {
495                format!(
496                    "Directory {object_id} in store {store_id} is in graveyard but still has \
497                     a link from {parent_object_id}",
498                )
499            }
500            FsckError::ZombieSymlink(store_id, object_id, parent_object_ids) => {
501                format!(
502                    "Symlink {object_id} in store {store_id} is in graveyard but still has \
503                     links from {parent_object_ids:?}",
504                )
505            }
506            FsckError::VerifiedFileDoesNotHaveAMerkleAttribute(store_id, object_id) => {
507                format!(
508                    "Object {object_id} in store {store_id} is marked as fsverity-enabled but is \
509                     missing a merkle attribute"
510                )
511            }
512            FsckError::NonFileMarkedAsVerified(store_id, object_id) => {
513                format!(
514                    "Object {object_id} in store {store_id} is marked as verified but is not a \
515                     file"
516                )
517            }
518            FsckError::InvalidInoLblk32KeyUsage(store_id, object_id) => {
519                format!("Object {object_id} in store {store_id} uses an InoLblk32 key invalidly")
520            }
521            FsckError::IncorrectMerkleTreeSize(store_id, object_id, expected_size, actual_size) => {
522                format!(
523                    "Object {object_id} in store {store_id} has merkle tree of size \
524                     {actual_size} expected {expected_size}"
525                )
526            }
527            FsckError::TombstonedAttributeDoesNotExist(store_id, object_id, attribute_id) => {
528                format!(
529                    "Object {object_id} in store {store_id} has an attribute {attribute_id} that \
530                     is tombstoned but does not exist.",
531                )
532            }
533            FsckError::TrimValueForGraveyardAttributeEntry(store_id, object_id, attribute_id) => {
534                format!(
535                    "Object {object_id} in store {store_id} has a GraveyardAttributeEntry for \
536                     attribute {attribute_id} that has ObjectValue::Trim",
537                )
538            }
539            FsckError::MissingOverwriteExtents(store_id, object_id, attribute_id) => {
540                format!(
541                    "Object {object_id} in store {store_id} has an attribute {attribute_id} that \
542                     indicated it had overwrite extents but none were found",
543                )
544            }
545            FsckError::OverwriteExtentFlagUnset(store_id, object_id, attribute_id) => {
546                format!(
547                    "Object {object_id} in store {store_id} has an attribute {attribute_id} with \
548                     overwrite extents but the metadata indicated it would not",
549                )
550            }
551            FsckError::NextObjectIdInUse(store_id, next_object_id) => {
552                format!("Next object ID {store_id} will use ({next_object_id}) is already in use",)
553            }
554        }
555    }
556
557    fn log(&self) {
558        match self {
559            FsckError::AllocatedBytesMismatch(observed, stored) => {
560                error!(observed:?, stored:?; "Unexpected allocated bytes");
561            }
562            FsckError::AllocatedSizeMismatch(store_id, oid, observed, stored) => {
563                error!(observed, oid, store_id, stored; "Unexpected allocated size");
564            }
565            FsckError::AllocationForNonexistentOwner(alloc) => {
566                error!(alloc:?; "Allocation for non-existent owner")
567            }
568            FsckError::AllocationMismatch(observed, stored) => {
569                error!(observed:?, stored:?; "Unexpected allocation");
570            }
571            FsckError::BadCasefoldHash(store_id, parent_id, child_id, expected, actual) => {
572                warn!(store_id, parent_id, child_id, expected, actual; "Bad casefold hash code");
573            }
574            FsckError::BadLastObjectId(highest, last_object_id) => {
575                error!(highest, last_object_id; "Last object ID is less than highest found");
576            }
577            FsckError::CasefoldInconsistency(store_id, parent_id, child_id) => {
578                error!(store_id:?, parent_id:?, child_id:?; "CasefoldChild inconsistent");
579            }
580            FsckError::ConflictingTypeForLink(store_id, oid, expected, actual) => {
581                error!(store_id, oid, expected:?, actual:?; "Bad link");
582            }
583            FsckError::ExtentExceedsLength(store_id, oid, attr_id, size, extent) => {
584                error!(store_id, oid, attr_id, size, extent:?; "Extent exceeds length");
585            }
586            FsckError::ExtraAllocations(allocations) => {
587                error!(allocations:?; "Unexpected allocations");
588            }
589            FsckError::IllegalKeyInRootStore(store_id, oid) => {
590                error!(store_id, oid; "Illegal key in root store");
591            }
592            FsckError::ObjectHasChildren(store_id, oid) => {
593                error!(store_id, oid; "Object has unexpected children");
594            }
595            FsckError::UnexpectedJournalFileOffset(object_id) => {
596                error!(
597                    oid = object_id;
598                    "SuperBlock journal_file_offsets contains unexpected object-id"
599                );
600            }
601            FsckError::LinkCycle(store_id, oid) => {
602                error!(store_id, oid; "Link cycle");
603            }
604            FsckError::MalformedAllocation(allocations) => {
605                error!(allocations:?; "Malformed allocations");
606            }
607            FsckError::MalformedExtent(store_id, oid, extent, device_offset) => {
608                error!(store_id, oid, extent:?, device_offset; "Malformed extent");
609            }
610            FsckError::MalformedObjectRecord(store_id, key, value) => {
611                error!(store_id, key:?, value:?; "Mismatched key and value");
612            }
613            FsckError::MisalignedAllocation(allocations) => {
614                error!(allocations:?; "Misaligned allocation");
615            }
616            FsckError::MisalignedExtent(store_id, oid, extent, device_offset) => {
617                error!(store_id, oid, extent:?, device_offset; "Misaligned extent");
618            }
619            FsckError::MissingAllocation(allocation) => {
620                error!(allocation:?; "Missing allocation");
621            }
622            FsckError::InvalidExtendedAttributeId(store_id, oid, attribute_id) => {
623                error!(store_id, oid, attribute_id; "Invalid extended attribute id");
624            }
625            FsckError::MissingAttributeForExtendedAttribute(store_id, oid, attribute_id) => {
626                error!(store_id, oid, attribute_id; "Missing attribute for extended attribute");
627            }
628            FsckError::MissingDataAttribute(store_id, oid) => {
629                error!(store_id, oid; "Missing default attribute");
630            }
631            FsckError::MissingObjectInfo(store_id, oid) => {
632                error!(store_id, oid; "Missing object record");
633            }
634            FsckError::MultipleLinksToDirectory(store_id, oid) => {
635                error!(store_id, oid; "Directory with multiple links");
636            }
637            FsckError::NonRootProjectIdMetadata(store_id, object_id, project_id) => {
638                error!(
639                    store_id,
640                    object_id, project_id; "Non root object in volume with project id metadata"
641                );
642            }
643            FsckError::ObjectCountMismatch(store_id, observed, stored) => {
644                error!(store_id, observed, stored; "Object count mismatch");
645            }
646            FsckError::ProjectOnGraveyard(store_id, project_id, object_id) => {
647                error!(store_id, project_id, object_id; "Project was set on graveyard object");
648            }
649            FsckError::ProjectUsedWithNoUsageTracking(store_id, project_id, node_id) => {
650                error!(store_id, project_id, node_id; "Project used without tracking metadata");
651            }
652            FsckError::RefCountMismatch(oid, observed, stored) => {
653                error!(oid, observed, stored; "Reference count mismatch");
654            }
655            FsckError::RootObjectHasParent(store_id, oid, apparent_parent_id) => {
656                error!(store_id, oid, apparent_parent_id; "Root object is a child");
657            }
658            FsckError::SubDirCountMismatch(store_id, oid, observed, stored) => {
659                error!(store_id, oid, observed, stored; "Sub-dir count mismatch");
660            }
661            FsckError::TombstonedObjectHasRecords(store_id, oid) => {
662                error!(store_id, oid; "Tombstoned object with references");
663            }
664            FsckError::UnexpectedObjectInGraveyard(oid) => {
665                error!(oid; "Unexpected object in graveyard");
666            }
667            FsckError::UnexpectedRecordInObjectStore(store_id, key, value) => {
668                error!(store_id, key:?, value:?; "Unexpected record");
669            }
670            FsckError::VolumeInChildStore(store_id, oid) => {
671                error!(store_id, oid; "Volume in child store");
672            }
673            FsckError::BadGraveyardValue(store_id, oid) => {
674                error!(store_id, oid; "Bad graveyard value");
675            }
676            FsckError::MissingEncryptionKeys(store_id, oid) => {
677                error!(store_id, oid; "Missing encryption keys");
678            }
679            FsckError::MissingKey(store_id, oid, key_id) => {
680                error!(store_id, oid, key_id; "Missing encryption key");
681            }
682            FsckError::EncryptedChildDirectoryNoWrappingKey(store_id, oid) => {
683                error!(store_id, oid; "Encrypted directory does not have a wrapping key id");
684            }
685            FsckError::EncryptedDirectoryHasUnencryptedChild(store_id, parent_oid, child_oid) => {
686                error!(
687                    store_id,
688                    parent_oid, child_oid; "Encrypted directory has unencrypted child"
689                );
690            }
691            FsckError::UnencryptedDirectoryHasEncryptedChild(store_id, parent_oid, child_oid) => {
692                error!(
693                    store_id,
694                    parent_oid, child_oid; "Unencrypted directory has encrypted child"
695                );
696            }
697            FsckError::ChildEncryptedWithDifferentWrappingKeyThanParent(
698                store_id,
699                parent_id,
700                child_id,
701                parent_wrapping_key_id,
702                child_wrapping_key_id,
703            ) => {
704                error!(
705                    store_id,
706                    parent_id,
707                    child_id,
708                    parent_wrapping_key_id:?,
709                    child_wrapping_key_id:?;
710                    "Child object encrypted with different wrapping key than parent"
711                );
712            }
713            FsckError::DuplicateKey(store_id, oid, key_id) => {
714                error!(store_id, oid, key_id; "Duplicate key")
715            }
716            FsckError::ZombieFile(store_id, oid, parent_oids) => {
717                error!(store_id, oid, parent_oids:?; "Links exist to file in graveyard")
718            }
719            FsckError::ZombieDir(store_id, oid, parent_oid) => {
720                error!(store_id, oid, parent_oid; "A link exists to directory in graveyard")
721            }
722            FsckError::ZombieSymlink(store_id, oid, parent_oids) => {
723                error!(store_id, oid, parent_oids:?; "Links exists to symlink in graveyard")
724            }
725            FsckError::VerifiedFileDoesNotHaveAMerkleAttribute(store_id, oid) => {
726                error!(store_id, oid; "Verified file does not have a merkle attribute")
727            }
728            FsckError::NonFileMarkedAsVerified(store_id, oid) => {
729                error!(store_id, oid; "Non-file marked as verified")
730            }
731            FsckError::InvalidInoLblk32KeyUsage(store_id, oid) => {
732                error!(store_id, oid; "Invalid InoLblk32 key usage")
733            }
734            FsckError::IncorrectMerkleTreeSize(store_id, oid, expected_size, actual_size) => {
735                error!(
736                    store_id,
737                    oid, expected_size, actual_size; "Verified file has incorrect merkle tree size"
738                )
739            }
740            FsckError::TombstonedAttributeDoesNotExist(store_id, oid, attribute_id) => {
741                error!(store_id, oid, attribute_id; "Tombstoned attribute does not exist")
742            }
743            FsckError::TrimValueForGraveyardAttributeEntry(store_id, oid, attribute_id) => {
744                error!(
745                    store_id,
746                    oid, attribute_id; "Invalid Trim value for a graveyard attribute entry",
747                )
748            }
749            FsckError::MissingOverwriteExtents(store_id, oid, attribute_id) => {
750                error!(
751                    store_id,
752                    oid,
753                    attribute_id;
754                    "Overwrite extents indicated, but no overwrite extents were found",
755                )
756            }
757            FsckError::OverwriteExtentFlagUnset(store_id, oid, attribute_id) => {
758                error!(
759                    store_id,
760                    oid,
761                    attribute_id;
762                    "Overwrite extents were found, but metadata flag was not set",
763                )
764            }
765            FsckError::NextObjectIdInUse(store_id, next_object_id) => {
766                error!(store_id, next_object_id; "Next object ID is already in use");
767            }
768        }
769    }
770}
771
772#[derive(Clone, Debug, PartialEq)]
773pub enum FsckFatal {
774    MalformedGraveyard,
775    MalformedLayerFile(u64, u64),
776    MalformedStore(u64),
777    MisOrderedLayerFile(u64, u64),
778    MisOrderedObjectStore(u64),
779    OverlappingKeysInLayerFile(u64, u64, Key, Key),
780    InvalidBloomFilter(u64, u64, Key),
781}
782
783impl FsckFatal {
784    fn to_string(&self) -> String {
785        match self {
786            FsckFatal::MalformedGraveyard => {
787                "Graveyard is malformed; root store is inconsistent".to_string()
788            }
789            FsckFatal::MalformedLayerFile(store_id, layer_file_id) => {
790                format!("Layer file {layer_file_id} in object store {store_id} is malformed")
791            }
792            FsckFatal::MalformedStore(id) => {
793                format!("Object store {id} is malformed; root store is inconsistent")
794            }
795            FsckFatal::MisOrderedLayerFile(store_id, layer_file_id) => {
796                format!(
797                    "Layer file {layer_file_id} for store/allocator {store_id} contains \
798                     out-of-order records"
799                )
800            }
801            FsckFatal::MisOrderedObjectStore(store_id) => {
802                format!("Store/allocator {store_id} contains out-of-order or duplicate records")
803            }
804            FsckFatal::OverlappingKeysInLayerFile(store_id, layer_file_id, key1, key2) => {
805                format!(
806                    "Layer file {layer_file_id} for store/allocator {store_id} contains \
807                     overlapping keys {key1:?} and {key2:?}"
808                )
809            }
810            FsckFatal::InvalidBloomFilter(store_id, layer_file_id, key) => {
811                format!(
812                    "Filter for layer files is invalid: reported that key {key:?} in layer file \
813                     {layer_file_id} for store/allocator {store_id} does not exist"
814                )
815            }
816        }
817    }
818
819    fn log(&self) {
820        match self {
821            FsckFatal::MalformedGraveyard => {
822                error!("Graveyard is malformed; root store is inconsistent");
823            }
824            FsckFatal::MalformedLayerFile(store_id, layer_file_id) => {
825                error!(store_id, layer_file_id; "Layer file malformed");
826            }
827            FsckFatal::MalformedStore(id) => {
828                error!(id; "Malformed store; root store is inconsistent");
829            }
830            FsckFatal::MisOrderedLayerFile(store_id, layer_file_id) => {
831                // This can be for stores or the allocator.
832                error!(oid = store_id, layer_file_id; "Layer file contains out-of-order records");
833            }
834            FsckFatal::MisOrderedObjectStore(store_id) => {
835                // This can be for stores or the allocator.
836                error!(
837                    oid = store_id;
838                    "Store/allocator contains out-of-order or duplicate records"
839                );
840            }
841            FsckFatal::OverlappingKeysInLayerFile(store_id, layer_file_id, key1, key2) => {
842                // This can be for stores or the allocator.
843                error!(oid = store_id, layer_file_id, key1:?, key2:?; "Overlapping keys");
844            }
845            FsckFatal::InvalidBloomFilter(store_id, layer_file_id, key) => {
846                error!(oid = store_id, layer_file_id, key:?; "Filter for layer files invalid");
847            }
848        }
849    }
850}
851
852#[cfg(test)]
853mod tests {
854    use super::*;
855    use crate::lsm_tree::types::Item;
856    use crate::object_store::ObjectDescriptor;
857
858    #[fuchsia::test]
859    fn test_type_conversions() {
860        let alloc_item = Item::new(
861            AllocatorKey { device_range: (0..4096).into() },
862            AllocatorValue::Abs { count: 1, owner_object_id: 2 },
863        );
864        let alloc = Allocation::from(alloc_item.as_item_ref());
865        assert_eq!(alloc.range, 0..4096);
866        assert_eq!(alloc.value, AllocatorValue::Abs { count: 1, owner_object_id: 2 });
867
868        let key_from_item = Key::from(alloc_item.as_item_ref());
869        let key_from_ref = Key::from(&alloc_item.key);
870        assert_eq!(key_from_item, key_from_ref);
871
872        let val_from_item = Value::from(alloc_item.as_item_ref());
873        let val_from_ref = Value::from(&alloc_item.value);
874        assert_eq!(val_from_item, val_from_ref);
875
876        let val_from_desc = Value::from(ObjectDescriptor::File);
877        assert_eq!(val_from_desc, Value::from(&ObjectDescriptor::File));
878    }
879
880    #[fuchsia::test]
881    fn test_fsck_issue() {
882        let warning = FsckIssue::Warning(FsckWarning::OrphanedObject(1, 2));
883        assert!(!warning.is_error());
884        assert!(warning.to_string().starts_with("WARNING: "));
885        warning.log();
886
887        let err = FsckIssue::Error(FsckError::UnexpectedObjectInGraveyard(3));
888        assert!(err.is_error());
889        assert!(err.to_string().starts_with("ERROR: "));
890        err.log();
891
892        let fatal = FsckIssue::Fatal(FsckFatal::MalformedGraveyard);
893        assert!(fatal.is_error());
894        assert!(fatal.to_string().starts_with("FATAL: "));
895        fatal.log();
896    }
897
898    #[fuchsia::test]
899    fn test_fsck_warnings() {
900        let k = Key::from(&1u64);
901        let v = Value::from(&2u64);
902        let attr_id = AttributeId(3);
903        let proj_id = ProjectId::new(2).unwrap();
904        let warnings = vec![
905            FsckWarning::ExtentForMissingAttribute(10, 20, attr_id),
906            FsckWarning::ExtentForNonexistentObject(10, 20),
907            FsckWarning::GraveyardRecordForAbsentObject(10, 20),
908            FsckWarning::InvalidObjectIdInStore(10, k, v),
909            FsckWarning::LimitForNonExistentStore(10, 100),
910            FsckWarning::OrphanedAttribute(10, 20, attr_id),
911            FsckWarning::OrphanedObject(10, 20),
912            FsckWarning::OrphanedKeys(10, 20),
913            FsckWarning::OrphanedExtendedAttribute(10, 20, attr_id),
914            FsckWarning::OrphanedExtendedAttributeRecord(10, 20),
915            FsckWarning::ProjectUsageInconsistent(10, proj_id, (10, 1), (20, 2)),
916        ];
917        assert!(
918            FsckWarning::GraveyardRecordForAbsentObject(10, 20)
919                .to_string()
920                .contains("object 20 in store 10")
921        );
922        for warning in warnings {
923            assert!(!warning.to_string().is_empty());
924            warning.log();
925        }
926    }
927
928    #[fuchsia::test]
929    fn test_fsck_errors() {
930        let alloc = Allocation {
931            range: 0..4096,
932            value: AllocatorValue::Abs { count: 1, owner_object_id: 2 },
933        };
934        let k = Key::from(&1u64);
935        let v = Value::from(&2u64);
936        let attr_id = AttributeId(3);
937        let proj_id = ProjectId::new(2).unwrap();
938        let wk1: WrappingKeyId = u128::to_le_bytes(1);
939        let wk2: WrappingKeyId = u128::to_le_bytes(2);
940        let errors = vec![
941            FsckError::AllocatedBytesMismatch(vec![(1, 100)], vec![(1, 200)]),
942            FsckError::AllocatedSizeMismatch(10, 20, 100, 200),
943            FsckError::AllocationForNonexistentOwner(alloc.clone()),
944            FsckError::AllocationMismatch(alloc.clone(), alloc.clone()),
945            FsckError::BadCasefoldHash(10, 20, 3, 4, 5),
946            FsckError::BadGraveyardValue(10, 20),
947            FsckError::BadLastObjectId(10, 5),
948            FsckError::CasefoldInconsistency(10, 20, 3),
949            FsckError::ChildEncryptedWithDifferentWrappingKeyThanParent(10, 20, 3, wk1, wk2),
950            FsckError::ConflictingTypeForLink(10, 20, v.clone(), v.clone()),
951            FsckError::DuplicateKey(10, 20, 3),
952            FsckError::EncryptedChildDirectoryNoWrappingKey(10, 20),
953            FsckError::EncryptedDirectoryHasUnencryptedChild(10, 20, 3),
954            FsckError::ExtentExceedsLength(10, 20, attr_id, 100, v.clone()),
955            FsckError::ExtraAllocations(vec![alloc.clone()]),
956            FsckError::IllegalKeyInRootStore(10, 20),
957            FsckError::IncorrectMerkleTreeSize(10, 20, 100, 200),
958            FsckError::LinkCycle(10, 20),
959            FsckError::MalformedAllocation(alloc.clone()),
960            FsckError::MalformedExtent(10, 20, 0..4096, 8192),
961            FsckError::MalformedObjectRecord(10, k.clone(), v.clone()),
962            FsckError::MisalignedAllocation(alloc.clone()),
963            FsckError::MisalignedExtent(10, 20, 0..4096, 8192),
964            FsckError::MissingAllocation(alloc),
965            FsckError::InvalidExtendedAttributeId(10, 20, attr_id),
966            FsckError::MissingAttributeForExtendedAttribute(10, 20, attr_id),
967            FsckError::MissingDataAttribute(10, 20),
968            FsckError::MissingEncryptionKeys(10, 20),
969            FsckError::MissingKey(10, 20, 3),
970            FsckError::MissingObjectInfo(10, 20),
971            FsckError::MissingOverwriteExtents(10, 20, attr_id),
972            FsckError::MultipleLinksToDirectory(10, 20),
973            FsckError::NextObjectIdInUse(10, 20),
974            FsckError::NonFileMarkedAsVerified(10, 20),
975            FsckError::NonRootProjectIdMetadata(10, 20, proj_id),
976            FsckError::ObjectCountMismatch(10, 20, 3),
977            FsckError::ObjectHasChildren(10, 20),
978            FsckError::OverwriteExtentFlagUnset(10, 20, attr_id),
979            FsckError::ProjectOnGraveyard(10, proj_id, 3),
980            FsckError::ProjectUsedWithNoUsageTracking(10, proj_id, 3),
981            FsckError::RefCountMismatch(10, 20, 3),
982            FsckError::RootObjectHasParent(10, 20, 3),
983            FsckError::SubDirCountMismatch(10, 20, 3, 4),
984            FsckError::TombstonedAttributeDoesNotExist(10, 20, attr_id),
985            FsckError::TombstonedObjectHasRecords(10, 20),
986            FsckError::TrimValueForGraveyardAttributeEntry(10, 20, attr_id),
987            FsckError::UnencryptedDirectoryHasEncryptedChild(10, 20, 3),
988            FsckError::UnexpectedJournalFileOffset(10),
989            FsckError::UnexpectedObjectInGraveyard(10),
990            FsckError::UnexpectedRecordInObjectStore(10, k, v.clone()),
991            FsckError::VerifiedFileDoesNotHaveAMerkleAttribute(10, 20),
992            FsckError::VolumeInChildStore(10, 20),
993            FsckError::ZombieDir(10, 20, 3),
994            FsckError::ZombieFile(10, 20, vec![3]),
995            FsckError::ZombieSymlink(10, 20, vec![3]),
996            FsckError::InvalidInoLblk32KeyUsage(10, 20),
997        ];
998        assert!(
999            FsckError::ConflictingTypeForLink(10, 20, v.clone(), v)
1000                .to_string()
1001                .contains("Object 20 in store 10")
1002        );
1003        assert!(FsckError::LinkCycle(10, 20).to_string().contains("object 20 in store 10"));
1004        assert!(
1005            FsckError::MissingAttributeForExtendedAttribute(10, 20, attr_id)
1006                .to_string()
1007                .contains("Object 20 in store 10")
1008        );
1009        assert!(
1010            FsckError::MissingDataAttribute(10, 20).to_string().contains("File 20 in store 10")
1011        );
1012        assert!(FsckError::MissingObjectInfo(10, 20).to_string().contains("Object 20 in store 10"));
1013        assert!(
1014            FsckError::MultipleLinksToDirectory(10, 20)
1015                .to_string()
1016                .contains("Directory 20 in store 10")
1017        );
1018        assert!(
1019            FsckError::TombstonedObjectHasRecords(10, 20)
1020                .to_string()
1021                .contains("object 20 in store 10")
1022        );
1023        assert!(
1024            FsckError::VerifiedFileDoesNotHaveAMerkleAttribute(10, 20)
1025                .to_string()
1026                .contains("Object 20 in store 10")
1027        );
1028        assert!(
1029            FsckError::NonFileMarkedAsVerified(10, 20)
1030                .to_string()
1031                .contains("Object 20 in store 10")
1032        );
1033        for err in errors {
1034            assert!(!err.to_string().is_empty());
1035            err.log();
1036        }
1037    }
1038
1039    #[fuchsia::test]
1040    fn test_fsck_fatals() {
1041        let k = Key::from(&1u64);
1042        let fatals = vec![
1043            FsckFatal::MalformedGraveyard,
1044            FsckFatal::MalformedLayerFile(1, 2),
1045            FsckFatal::MalformedStore(1),
1046            FsckFatal::MisOrderedLayerFile(1, 2),
1047            FsckFatal::MisOrderedObjectStore(1),
1048            FsckFatal::OverlappingKeysInLayerFile(1, 2, k.clone(), k.clone()),
1049            FsckFatal::InvalidBloomFilter(1, 2, k),
1050        ];
1051        for fatal in fatals {
1052            assert!(!fatal.to_string().is_empty());
1053            fatal.log();
1054        }
1055    }
1056}