Skip to main content

btrfs_cli/
scrub.rs

1use crate::{Format, Runnable, util::human_bytes};
2use anyhow::Result;
3use btrfs_uapi::scrub::ScrubProgress;
4use clap::Parser;
5
6mod cancel;
7mod limit;
8mod resume;
9mod start;
10mod status;
11
12pub use self::{cancel::*, limit::*, resume::*, start::*, status::*};
13
14/// Verify checksums of data and metadata.
15///
16/// Scrub reads all data and metadata on a filesystem and verifies checksums.
17/// This detects hardware errors and bit rot. Scrub is typically a long-running
18/// operation and can be paused, resumed, or cancelled. Progress and status can
19/// be queried, and speed limits can be configured. Requires CAP_SYS_ADMIN.
20#[derive(Parser, Debug)]
21pub struct ScrubCommand {
22    #[clap(subcommand)]
23    pub subcommand: ScrubSubcommand,
24}
25
26impl Runnable for ScrubCommand {
27    fn run(&self, format: Format, dry_run: bool) -> Result<()> {
28        match &self.subcommand {
29            ScrubSubcommand::Start(cmd) => cmd.run(format, dry_run),
30            ScrubSubcommand::Cancel(cmd) => cmd.run(format, dry_run),
31            ScrubSubcommand::Resume(cmd) => cmd.run(format, dry_run),
32            ScrubSubcommand::Status(cmd) => cmd.run(format, dry_run),
33            ScrubSubcommand::Limit(cmd) => cmd.run(format, dry_run),
34        }
35    }
36}
37
38#[derive(Parser, Debug)]
39pub enum ScrubSubcommand {
40    Start(ScrubStartCommand),
41    Cancel(ScrubCancelCommand),
42    Resume(ScrubResumeCommand),
43    Status(ScrubStatusCommand),
44    Limit(ScrubLimitCommand),
45}
46
47/// Format a bytes-per-second limit for display; `0` means unlimited.
48fn format_limit(limit: u64) -> String {
49    if limit == 0 {
50        "unlimited".to_owned()
51    } else {
52        format!("{}/s", human_bytes(limit))
53    }
54}
55
56/// Number of decimal digits in `n` (minimum 1).
57fn digits(n: u64) -> usize {
58    if n == 0 { 1 } else { n.ilog10() as usize + 1 }
59}
60
61/// Add progress counters from `src` into `dst`.
62fn accumulate(dst: &mut ScrubProgress, src: &ScrubProgress) {
63    dst.data_extents_scrubbed += src.data_extents_scrubbed;
64    dst.tree_extents_scrubbed += src.tree_extents_scrubbed;
65    dst.data_bytes_scrubbed += src.data_bytes_scrubbed;
66    dst.tree_bytes_scrubbed += src.tree_bytes_scrubbed;
67    dst.read_errors += src.read_errors;
68    dst.csum_errors += src.csum_errors;
69    dst.verify_errors += src.verify_errors;
70    dst.super_errors += src.super_errors;
71    dst.uncorrectable_errors += src.uncorrectable_errors;
72    dst.corrected_errors += src.corrected_errors;
73    dst.unverified_errors += src.unverified_errors;
74    dst.no_csum += src.no_csum;
75    dst.csum_discards += src.csum_discards;
76    dst.malloc_errors += src.malloc_errors;
77}
78
79/// Print a single-device progress summary.
80fn print_progress_summary(p: &ScrubProgress, devid: u64, path: &str) {
81    println!(
82        "  devid {devid} ({path}): scrubbed {}",
83        human_bytes(p.bytes_scrubbed())
84    );
85    print_error_summary(p);
86}
87
88/// Print the error summary line.
89fn print_error_summary(p: &ScrubProgress) {
90    if p.malloc_errors > 0 {
91        eprintln!(
92            "WARNING: memory allocation errors during scrub — results may be inaccurate"
93        );
94    }
95    print!("  Error summary:  ");
96    if p.is_clean() {
97        println!(" no errors found");
98    } else {
99        if p.read_errors > 0 {
100            print!(" read={}", p.read_errors);
101        }
102        if p.super_errors > 0 {
103            print!(" super={}", p.super_errors);
104        }
105        if p.verify_errors > 0 {
106            print!(" verify={}", p.verify_errors);
107        }
108        if p.csum_errors > 0 {
109            print!(" csum={}", p.csum_errors);
110        }
111        println!();
112        println!("    Corrected:      {}", p.corrected_errors);
113        println!("    Uncorrectable:  {}", p.uncorrectable_errors);
114        println!("    Unverified:     {}", p.unverified_errors);
115    }
116}