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