Skip to main content

fxfs/
fsck.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::filesystem::FxFilesystem;
6use crate::fsck::errors::{FsckError, FsckFatal, FsckIssue, FsckWarning};
7use crate::log::*;
8use crate::lsm_tree::Query;
9use crate::lsm_tree::skip_list_layer::SkipListLayer;
10use crate::lsm_tree::types::{
11    BoxedLayerIterator, Item, Key, Layer, LayerIterator, LayerKey, Value,
12};
13use crate::object_handle::INVALID_OBJECT_ID;
14use crate::object_store::allocator::{AllocatorKey, AllocatorValue, CoalescingIterator};
15use crate::object_store::journal::super_block::SuperBlockInstance;
16use crate::object_store::volume::root_volume;
17use crate::object_store::{ObjectStore, load_store_info};
18use anyhow::{Context, Error, anyhow};
19use futures::try_join;
20use fxfs_crypto::Crypt;
21use rustc_hash::FxHashSet as HashSet;
22use std::collections::BTreeMap;
23use std::iter::zip;
24use std::ops::Bound;
25use std::sync::Arc;
26use std::sync::atomic::{AtomicU64, Ordering};
27
28pub mod errors;
29
30mod store_scanner;
31
32#[cfg(test)]
33mod tests;
34
35/// General stats about filesystem fragmentation
36pub const NUM_FRAGMENTATION_HISTOGRAM_SLOTS: usize = 12;
37#[derive(Default, Debug)]
38pub struct FragmentationStats {
39    /// Histogram of extent size in bytes. Buckets are fixed as <=4kB, <=8kB, ... <=2MiB, >2MiB.
40    pub extent_size: [u64; NUM_FRAGMENTATION_HISTOGRAM_SLOTS],
41    /// Histogram of extents per file. Buckets are fixed as <=1, <=2, ... <=512, >512.
42    pub extent_count: [u64; NUM_FRAGMENTATION_HISTOGRAM_SLOTS],
43    /// Histogram of free space in bytes. Buckets are fixed as <=4kB, <=8kB, ... <=2MiB, >2MiB.
44    pub free_space: [u64; NUM_FRAGMENTATION_HISTOGRAM_SLOTS],
45}
46
47impl FragmentationStats {
48    /// Returns the histogram bucket for extent_size and free_space given size in bytes.
49    pub fn get_histogram_bucket_for_size(size: u64) -> usize {
50        return Self::get_histogram_bucket_for_count(size / 4096);
51    }
52    /// Returns the histogram bucket for extent_count.
53    pub fn get_histogram_bucket_for_count(count: u64) -> usize {
54        let log_count = (64 - count.leading_zeros()) as usize;
55        return log_count.clamp(1, NUM_FRAGMENTATION_HISTOGRAM_SLOTS) - 1;
56    }
57}
58
59/// Filesystem statistics gathered on during an fsck run.
60#[derive(Default, Debug)]
61pub struct FsckResult {
62    pub fragmentation: FragmentationStats,
63}
64
65pub struct FsckOptions<'a> {
66    /// Whether to fail fsck if any warnings are encountered.
67    pub fail_on_warning: bool,
68    // Whether to halt after the first error encountered (fatal or not).
69    pub halt_on_error: bool,
70    /// Whether to perform slower, more complete checks.
71    pub do_slow_passes: bool,
72    /// A callback to be invoked for each detected error, e.g. to log the error.
73    pub on_error: Box<dyn Fn(&FsckIssue) + Send + Sync + 'a>,
74    /// If true, suppress informational messages.
75    pub quiet: bool,
76    /// Whether to be noisy as we do checks.
77    pub verbose: bool,
78    /// Don't take the write lock. The caller needs to guarantee the filesystem isn't changing.
79    pub no_lock: bool,
80}
81
82impl Default for FsckOptions<'_> {
83    fn default() -> Self {
84        Self {
85            fail_on_warning: false,
86            halt_on_error: false,
87            do_slow_passes: true,
88            on_error: Box::new(FsckIssue::log),
89            quiet: false,
90            verbose: false,
91            no_lock: false,
92        }
93    }
94}
95
96/// Verifies the integrity of Fxfs.  See errors.rs for a list of checks performed.
97// TODO(https://fxbug.dev/42168496): add checks for:
98//  + The root parent object store ID and root object store ID must not conflict with any other
99//    stores or the allocator.
100//
101// TODO(https://fxbug.dev/42178152): This currently takes a write lock on the filesystem.  It would
102// be nice if we could take a snapshot.
103pub async fn fsck(filesystem: Arc<FxFilesystem>) -> Result<FsckResult, Error> {
104    fsck_with_options(filesystem, &FsckOptions::default()).await
105}
106
107pub async fn fsck_with_options(
108    filesystem: Arc<FxFilesystem>,
109    options: &FsckOptions<'_>,
110) -> Result<FsckResult, Error> {
111    let mut result = FsckResult::default();
112
113    if !options.quiet {
114        info!("Starting fsck");
115    }
116
117    let _guard = if options.no_lock { None } else { Some(filesystem.lock_commits().await) };
118
119    let mut fsck = Fsck::new(options);
120
121    let object_manager = filesystem.object_manager();
122    let super_block_header = filesystem.super_block_header();
123
124    // Keep track of all things that might exist in journal checkpoints so we can check for
125    // unexpected entries.
126    let mut journal_checkpoint_ids: HashSet<u64> = HashSet::default();
127    journal_checkpoint_ids.insert(super_block_header.allocator_object_id);
128    journal_checkpoint_ids.insert(super_block_header.root_store_object_id);
129
130    // Scan the root parent object store.
131    let mut root_objects =
132        vec![super_block_header.root_store_object_id, super_block_header.journal_object_id];
133    root_objects.append(&mut object_manager.root_store().parent_objects());
134    fsck.verbose("Scanning root parent store...");
135    store_scanner::scan_store(
136        &fsck,
137        object_manager.root_parent_store().as_ref(),
138        &root_objects,
139        &mut result,
140    )
141    .await?;
142    fsck.verbose("Scanning root parent store done");
143
144    let root_store = &object_manager.root_store();
145    let mut root_store_root_objects = Vec::new();
146    root_store_root_objects.append(&mut vec![
147        super_block_header.allocator_object_id,
148        SuperBlockInstance::A.object_id(),
149        SuperBlockInstance::B.object_id(),
150    ]);
151    root_store_root_objects.append(&mut root_store.root_objects());
152
153    let root_volume = root_volume(filesystem.clone()).await?;
154    let volume_directory = root_volume.volume_directory();
155    let layer_set = volume_directory.store().tree().layer_set();
156    let mut merger = layer_set.merger();
157    let mut iter = volume_directory.iter(&mut merger).await?;
158
159    // TODO(https://fxbug.dev/42178153): We could maybe iterate over stores concurrently.
160    while let Some((_, store_id, _)) = iter.get() {
161        journal_checkpoint_ids.insert(store_id);
162        fsck.check_child_store_metadata(
163            filesystem.as_ref(),
164            store_id,
165            &mut root_store_root_objects,
166        )
167        .await?;
168        iter.advance().await?;
169    }
170
171    let allocator = filesystem.allocator();
172    root_store_root_objects.append(&mut allocator.parent_objects());
173
174    if fsck.options.do_slow_passes {
175        // Scan each layer file for the root store.
176        let layer_set = root_store.tree().immutable_layer_set();
177        fsck.verbose(format!("Checking {} layers for root store...", layer_set.layers.len()));
178        for layer in layer_set.layers {
179            if let Some(handle) = layer.handle() {
180                fsck.verbose(format!(
181                    "Layer file {} for root_store is {} bytes",
182                    handle.object_id(),
183                    handle.get_size()
184                ));
185            }
186            fsck.check_layer_file_contents(
187                root_store.store_object_id(),
188                layer.handle().map(|h| h.object_id()).unwrap_or(INVALID_OBJECT_ID),
189                layer.clone(),
190            )
191            .await?;
192        }
193
194        // Scan each layer file for the allocator.
195        let layer_set = allocator.tree().immutable_layer_set();
196        fsck.verbose(format!("Checking {} layers for allocator...", layer_set.layers.len()));
197        for layer in layer_set.layers {
198            if let Some(handle) = layer.handle() {
199                fsck.verbose(format!(
200                    "Layer file {} for allocator is {} bytes",
201                    handle.object_id(),
202                    handle.get_size()
203                ));
204            }
205            fsck.check_layer_file_contents(
206                allocator.object_id(),
207                layer.handle().map(|h| h.object_id()).unwrap_or(INVALID_OBJECT_ID),
208                layer.clone(),
209            )
210            .await?;
211        }
212        fsck.verbose("Checking layers done");
213    }
214
215    // Finally scan the root object store.
216    fsck.verbose("Scanning root object store...");
217    store_scanner::scan_store(&fsck, root_store.as_ref(), &root_store_root_objects, &mut result)
218        .await?;
219    fsck.verbose("Scanning root object store done");
220
221    // Now compare our regenerated allocation map with what we actually have.
222    fsck.verbose("Verifying allocations...");
223    let mut store_ids = HashSet::default();
224    store_ids.insert(root_store.store_object_id());
225    store_ids.insert(object_manager.root_parent_store().store_object_id());
226    fsck.verify_allocations(filesystem.as_ref(), &store_ids, &mut result).await?;
227    fsck.verbose("Verifying allocations done");
228
229    // Every key in journal_file_offsets should map to an lsm tree (ObjectStore or Allocator).
230    // Excess entries mean we won't be able to reap the journal to free space.
231    // Missing entries are OK. Entries only exist if there is data for the store that hasn't been
232    // flushed yet.
233    for object_id in object_manager.journal_file_offsets().0.keys() {
234        if !journal_checkpoint_ids.contains(object_id) {
235            fsck.error(FsckError::UnexpectedJournalFileOffset(*object_id))?;
236        }
237    }
238
239    let errors = fsck.errors();
240    let warnings = fsck.warnings();
241    if errors > 0 || (fsck.options.fail_on_warning && warnings > 0) {
242        Err(anyhow!("Fsck encountered {} errors, {} warnings", errors, warnings))
243    } else {
244        if warnings > 0 {
245            warn!(count = warnings; "Fsck encountered warnings");
246        } else {
247            if !options.quiet {
248                info!("No issues detected");
249            }
250        }
251        Ok(result)
252    }
253}
254
255/// Verifies the integrity of a volume within Fxfs.  See errors.rs for a list of checks performed.
256// TODO(https://fxbug.dev/42178152): This currently takes a write lock on the filesystem.  It would
257// be nice if we could take a snapshot.
258pub async fn fsck_volume(
259    filesystem: &FxFilesystem,
260    store_id: u64,
261    crypt: Option<Arc<dyn Crypt>>,
262) -> Result<FsckResult, Error> {
263    fsck_volume_with_options(filesystem, &FsckOptions::default(), store_id, crypt).await
264}
265
266pub async fn fsck_volume_with_options(
267    filesystem: &FxFilesystem,
268    options: &FsckOptions<'_>,
269    store_id: u64,
270    crypt: Option<Arc<dyn Crypt>>,
271) -> Result<FsckResult, Error> {
272    let mut result = FsckResult::default();
273    if !options.quiet {
274        info!(store_id:?; "Starting volume fsck");
275    }
276
277    let store = filesystem.object_manager().store(store_id).context("open_store failed").unwrap();
278
279    let _relock_guard;
280    if store.is_locked() {
281        let crypt = crypt.ok_or_else(|| anyhow!("Invalid key"))?;
282        store.unlock_read_only(crypt).await?;
283        _relock_guard = scopeguard::guard(store.clone(), |store| {
284            store.lock_read_only();
285        });
286    }
287
288    let _guard = if options.no_lock { None } else { Some(filesystem.lock_commits().await) };
289
290    let mut fsck = Fsck::new(options);
291    fsck.check_child_store(store.as_ref(), &mut result).await?;
292    let mut store_ids = HashSet::default();
293    store_ids.insert(store_id);
294    fsck.verify_allocations(filesystem, &store_ids, &mut result).await?;
295
296    let errors = fsck.errors();
297    let warnings = fsck.warnings();
298    if errors > 0 || (fsck.options.fail_on_warning && warnings > 0) {
299        Err(anyhow!("Volume fsck encountered {} errors, {} warnings", errors, warnings))
300    } else {
301        if warnings > 0 {
302            warn!(count = warnings; "Volume fsck encountered warnings");
303        } else {
304            if !options.quiet {
305                info!("No issues detected");
306            }
307        }
308        Ok(result)
309    }
310}
311
312struct Fsck<'a> {
313    options: &'a FsckOptions<'a>,
314    // A list of allocations generated based on all extents found across all scanned object stores.
315    allocations: Arc<SkipListLayer<AllocatorKey, AllocatorValue>>,
316    errors: AtomicU64,
317    warnings: AtomicU64,
318}
319
320impl<'a> Fsck<'a> {
321    fn new(options: &'a FsckOptions<'a>) -> Self {
322        Fsck {
323            options,
324            // TODO(https://fxbug.dev/42178047): fix magic number
325            allocations: SkipListLayer::new(2048),
326            errors: AtomicU64::new(0),
327            warnings: AtomicU64::new(0),
328        }
329    }
330
331    // Log if in verbose mode.
332    fn verbose(&self, message: impl AsRef<str>) {
333        if self.options.verbose {
334            info!(message = message.as_ref(); "fsck");
335        }
336    }
337
338    fn errors(&self) -> u64 {
339        self.errors.load(Ordering::Relaxed)
340    }
341
342    fn warnings(&self) -> u64 {
343        self.warnings.load(Ordering::Relaxed)
344    }
345
346    fn assert<V>(&self, res: Result<V, Error>, error: FsckFatal) -> Result<V, Error> {
347        if res.is_err() {
348            (self.options.on_error)(&FsckIssue::Fatal(error.clone()));
349            return Err(anyhow!("{:?}", error)).context(res.err().unwrap());
350        }
351        res
352    }
353
354    fn warning(&self, error: FsckWarning) -> Result<(), Error> {
355        (self.options.on_error)(&FsckIssue::Warning(error));
356        self.warnings.fetch_add(1, Ordering::Relaxed);
357        Ok(())
358    }
359
360    fn error(&self, error: FsckError) -> Result<(), Error> {
361        (self.options.on_error)(&FsckIssue::Error(error.clone()));
362        self.errors.fetch_add(1, Ordering::Relaxed);
363        if self.options.halt_on_error { Err(anyhow!("{:?}", error)) } else { Ok(()) }
364    }
365
366    fn fatal(&self, error: FsckFatal) -> Result<(), Error> {
367        (self.options.on_error)(&FsckIssue::Fatal(error.clone()));
368        Err(anyhow!("{:?}", error))
369    }
370
371    // Does not actually verify the inner contents of the store; for that, use check_child_store.
372    async fn check_child_store_metadata(
373        &mut self,
374        filesystem: &FxFilesystem,
375        store_id: u64,
376        root_store_root_objects: &mut Vec<u64>,
377    ) -> Result<(), Error> {
378        let root_store = filesystem.root_store();
379
380        // Manually open the StoreInfo so we can validate it without unlocking the store.
381        let info = self.assert(
382            load_store_info(&root_store, store_id).await,
383            FsckFatal::MalformedStore(store_id),
384        )?;
385        root_store_root_objects.append(&mut info.parent_objects());
386        Ok(())
387    }
388
389    async fn check_child_store(
390        &mut self,
391        store: &ObjectStore,
392        result: &mut FsckResult,
393    ) -> Result<(), Error> {
394        let store_id = store.store_object_id();
395        if self.options.do_slow_passes {
396            let layer_set = store.tree().immutable_layer_set();
397            for layer in layer_set.layers {
398                let (layer_object_id, layer_size) = if let Some(h) = layer.handle() {
399                    (h.object_id(), h.get_size())
400                } else {
401                    (0, 0)
402                };
403                self.verbose(format!(
404                    "Layer file {} for store {} is {} bytes",
405                    layer_object_id, store_id, layer_size,
406                ));
407                self.check_layer_file_contents(store_id, layer_object_id, layer.clone()).await?
408            }
409        }
410
411        store_scanner::scan_store(self, store, &store.root_objects(), result)
412            .await
413            .context("scan_store failed")
414    }
415
416    async fn check_layer_file_contents<K: Key + LayerKey, V: Value>(
417        &self,
418        // This is the object ID of the store or allocator that the layer files belong to.
419        allocator_or_store_object_id: u64,
420        layer_file_object_id: u64,
421        layer: Arc<dyn Layer<K, V>>,
422    ) -> Result<(), Error> {
423        let mut iter: BoxedLayerIterator<'_, K, V> = self.assert(
424            layer.seek(Bound::Unbounded).await,
425            FsckFatal::MalformedLayerFile(allocator_or_store_object_id, layer_file_object_id),
426        )?;
427
428        let mut last_item: Option<Item<K, V>> = None;
429        while let Some(item) = iter.get() {
430            if let Some(last) = last_item {
431                if !last.key.cmp_upper_bound(&item.key).is_le() {
432                    self.fatal(FsckFatal::MisOrderedLayerFile(
433                        allocator_or_store_object_id,
434                        layer_file_object_id,
435                    ))?;
436                }
437                if last.key.overlaps(&item.key) {
438                    self.fatal(FsckFatal::OverlappingKeysInLayerFile(
439                        allocator_or_store_object_id,
440                        layer_file_object_id,
441                        item.into(),
442                        last.as_item_ref().into(),
443                    ))?;
444                }
445            }
446            if !layer.maybe_contains_key(item.key) {
447                // Key reported as not existing in filter
448                self.fatal(FsckFatal::InvalidBloomFilter(
449                    allocator_or_store_object_id,
450                    layer_file_object_id,
451                    item.into(),
452                ))?;
453            }
454            last_item = Some(item.cloned());
455            self.assert(
456                iter.advance().await,
457                FsckFatal::MalformedLayerFile(allocator_or_store_object_id, layer_file_object_id),
458            )?;
459        }
460        Ok(())
461    }
462
463    // Assumes that every store in `store_object_ids` has been previously scanned.
464    async fn verify_allocations(
465        &self,
466        filesystem: &FxFilesystem,
467        store_object_ids: &HashSet<u64>,
468        result: &mut FsckResult,
469    ) -> Result<(), Error> {
470        let allocator = filesystem.allocator();
471        let layer_set = allocator.tree().layer_set();
472        let mut merger = layer_set.merger();
473        let mut stored_allocations = CoalescingIterator::new(
474            allocator.filter(merger.query(Query::FullScan).await?, true).await?,
475        )
476        .await
477        .expect("filter failed");
478        let mut observed_allocations =
479            CoalescingIterator::new(self.allocations.seek(Bound::Unbounded).await?).await?;
480        let mut observed_owner_allocated_bytes = BTreeMap::new();
481        let mut extra_allocations: Vec<errors::Allocation> = vec![];
482        let bs = filesystem.block_size();
483        let mut previous_allocation_end = 0;
484        while let Some(allocation) = stored_allocations.get() {
485            if allocation.key.device_range.start % bs > 0
486                || allocation.key.device_range.end % bs > 0
487            {
488                self.error(FsckError::MisalignedAllocation(allocation.into()))?;
489            } else if allocation.key.device_range.start >= allocation.key.device_range.end {
490                self.error(FsckError::MalformedAllocation(allocation.into()))?;
491            }
492            let owner_object_id = match allocation.value {
493                AllocatorValue::None => INVALID_OBJECT_ID,
494                AllocatorValue::Abs { owner_object_id, .. } => *owner_object_id,
495            };
496            let r = &allocation.key.device_range;
497
498            // 'None' allocator values represent free space so should be ignored here.
499            if allocation.value != &AllocatorValue::None {
500                if r.start > previous_allocation_end {
501                    let size = r.start - previous_allocation_end;
502                    result.fragmentation.free_space
503                        [FragmentationStats::get_histogram_bucket_for_size(size)] += 1;
504                }
505                previous_allocation_end = r.end;
506            }
507
508            *observed_owner_allocated_bytes.entry(owner_object_id).or_insert(0) += r.end - r.start;
509            if !store_object_ids.contains(&owner_object_id) {
510                if filesystem.object_manager().store(owner_object_id).is_none() {
511                    self.error(FsckError::AllocationForNonexistentOwner(allocation.into()))?;
512                }
513                stored_allocations.advance().await?;
514                continue;
515            }
516            // Cross-reference allocations against the ones we observed.
517            match observed_allocations.get() {
518                None => extra_allocations.push(allocation.into()),
519                Some(observed_allocation) => {
520                    if allocation.key.device_range.end <= observed_allocation.key.device_range.start
521                    {
522                        extra_allocations.push(allocation.into());
523                        stored_allocations.advance().await?;
524                        continue;
525                    }
526                    if observed_allocation.key.device_range.end <= allocation.key.device_range.start
527                    {
528                        self.error(FsckError::MissingAllocation(observed_allocation.into()))?;
529                        observed_allocations.advance().await?;
530                        continue;
531                    }
532                    // We can only reconstruct the key/value fields of Item.
533                    if allocation.key != observed_allocation.key
534                        || allocation.value != observed_allocation.value
535                    {
536                        self.error(FsckError::AllocationMismatch(
537                            observed_allocation.into(),
538                            allocation.into(),
539                        ))?;
540                        stored_allocations.advance().await?;
541                        continue;
542                    }
543                }
544            }
545            try_join!(stored_allocations.advance(), observed_allocations.advance())?;
546        }
547        let device_size =
548            filesystem.device().block_count() * filesystem.device().block_size() as u64;
549        if previous_allocation_end < device_size {
550            let size = device_size - previous_allocation_end;
551            result.fragmentation.free_space
552                [FragmentationStats::get_histogram_bucket_for_size(size)] += 1;
553        }
554        while let Some(allocation) = observed_allocations.get() {
555            self.error(FsckError::MissingAllocation(allocation.into()))?;
556            observed_allocations.advance().await?;
557            continue;
558        }
559        let expected_allocated_bytes = observed_owner_allocated_bytes.values().sum::<u64>();
560        self.verbose(format!(
561            "Found {} bytes allocated (expected {} bytes). Total device size is {} bytes.",
562            allocator.get_allocated_bytes(),
563            expected_allocated_bytes,
564            device_size,
565        ));
566        if !extra_allocations.is_empty() {
567            self.error(FsckError::ExtraAllocations(extra_allocations))?;
568        }
569        // NB: If the allocator returns a value of 0 for a store, it just means the store has no
570        // data that it owns.  Fsck wouldn't have observed any allocations for these, so filter them
571        // out.
572        let owner_allocated_bytes = allocator
573            .get_owner_allocated_bytes()
574            .into_iter()
575            .filter(|(_, v)| *v > 0)
576            .collect::<BTreeMap<_, _>>();
577        if expected_allocated_bytes != allocator.get_allocated_bytes()
578            || observed_owner_allocated_bytes.len() != owner_allocated_bytes.len()
579            || zip(observed_owner_allocated_bytes.iter(), owner_allocated_bytes.iter())
580                .filter(|((k1, v1), (k2, v2))| (*k1, *v1) != (*k2, *v2))
581                .count()
582                != 0
583        {
584            self.error(FsckError::AllocatedBytesMismatch(
585                observed_owner_allocated_bytes.iter().map(|(k, v)| (*k, *v)).collect(),
586                owner_allocated_bytes.iter().map(|(k, v)| (*k, *v)).collect(),
587            ))?;
588        }
589        for (k, v) in allocator.owner_byte_limits() {
590            if !owner_allocated_bytes.contains_key(&k) {
591                self.warning(FsckWarning::LimitForNonExistentStore(k, v))?;
592            }
593        }
594        Ok(())
595    }
596}