Skip to main content

btrfs_cli/
scrub.rs

1use crate::{
2    Format, Runnable,
3    util::{SizeFormat, fmt_size},
4};
5use anyhow::Result;
6use btrfs_uapi::scrub::ScrubProgress;
7use clap::Parser;
8
9mod cancel;
10mod limit;
11mod resume;
12mod start;
13mod status;
14
15pub use self::{cancel::*, limit::*, resume::*, start::*, status::*};
16
17/// Verify checksums of data and metadata.
18///
19/// Scrub reads all data and metadata on a filesystem and verifies checksums.
20/// This detects hardware errors and bit rot. Scrub is typically a long-running
21/// operation and can be paused, resumed, or cancelled. Progress and status can
22/// be queried, and speed limits can be configured. Requires CAP_SYS_ADMIN.
23#[derive(Parser, Debug)]
24pub struct ScrubCommand {
25    #[clap(subcommand)]
26    pub subcommand: ScrubSubcommand,
27}
28
29impl Runnable for ScrubCommand {
30    fn run(&self, format: Format, dry_run: bool) -> Result<()> {
31        match &self.subcommand {
32            ScrubSubcommand::Start(cmd) => cmd.run(format, dry_run),
33            ScrubSubcommand::Cancel(cmd) => cmd.run(format, dry_run),
34            ScrubSubcommand::Resume(cmd) => cmd.run(format, dry_run),
35            ScrubSubcommand::Status(cmd) => cmd.run(format, dry_run),
36            ScrubSubcommand::Limit(cmd) => cmd.run(format, dry_run),
37        }
38    }
39}
40
41#[derive(Parser, Debug)]
42pub enum ScrubSubcommand {
43    Start(ScrubStartCommand),
44    Cancel(ScrubCancelCommand),
45    Resume(ScrubResumeCommand),
46    Status(ScrubStatusCommand),
47    Limit(ScrubLimitCommand),
48}
49
50/// Format a bytes-per-second limit for display; `0` means unlimited.
51fn format_limit(limit: u64, mode: &SizeFormat) -> String {
52    if limit == 0 {
53        "unlimited".to_owned()
54    } else {
55        format!("{}/s", fmt_size(limit, mode))
56    }
57}
58
59/// Number of decimal digits in `n` (minimum 1).
60fn digits(n: u64) -> usize {
61    if n == 0 { 1 } else { n.ilog10() as usize + 1 }
62}
63
64/// Add progress counters from `src` into `dst`.
65fn accumulate(dst: &mut ScrubProgress, src: &ScrubProgress) {
66    dst.data_extents_scrubbed += src.data_extents_scrubbed;
67    dst.tree_extents_scrubbed += src.tree_extents_scrubbed;
68    dst.data_bytes_scrubbed += src.data_bytes_scrubbed;
69    dst.tree_bytes_scrubbed += src.tree_bytes_scrubbed;
70    dst.read_errors += src.read_errors;
71    dst.csum_errors += src.csum_errors;
72    dst.verify_errors += src.verify_errors;
73    dst.super_errors += src.super_errors;
74    dst.uncorrectable_errors += src.uncorrectable_errors;
75    dst.corrected_errors += src.corrected_errors;
76    dst.unverified_errors += src.unverified_errors;
77    dst.no_csum += src.no_csum;
78    dst.csum_discards += src.csum_discards;
79    dst.malloc_errors += src.malloc_errors;
80}
81
82/// Print a single-device progress summary (default format).
83fn print_progress_summary(
84    p: &ScrubProgress,
85    devid: u64,
86    path: &str,
87    mode: &SizeFormat,
88) {
89    println!(
90        "  devid {devid} ({path}): scrubbed {}",
91        fmt_size(p.bytes_scrubbed(), mode)
92    );
93    print_error_summary(p);
94}
95
96/// Print the error summary line.
97fn print_error_summary(p: &ScrubProgress) {
98    if p.malloc_errors > 0 {
99        eprintln!(
100            "WARNING: memory allocation errors during scrub — results may be inaccurate"
101        );
102    }
103    print!("  Error summary:  ");
104    if p.is_clean() {
105        println!(" no errors found");
106    } else {
107        if p.read_errors > 0 {
108            print!(" read={}", p.read_errors);
109        }
110        if p.super_errors > 0 {
111            print!(" super={}", p.super_errors);
112        }
113        if p.verify_errors > 0 {
114            print!(" verify={}", p.verify_errors);
115        }
116        if p.csum_errors > 0 {
117            print!(" csum={}", p.csum_errors);
118        }
119        println!();
120        println!("    Corrected:      {}", p.corrected_errors);
121        println!("    Uncorrectable:  {}", p.uncorrectable_errors);
122        println!("    Unverified:     {}", p.unverified_errors);
123    }
124}
125
126/// Print all raw progress fields for a single device.
127fn print_raw_progress(p: &ScrubProgress, devid: u64, path: &str) {
128    println!("  devid {devid} ({path}):");
129    println!("    data_extents_scrubbed: {}", p.data_extents_scrubbed);
130    println!("    tree_extents_scrubbed: {}", p.tree_extents_scrubbed);
131    println!("    data_bytes_scrubbed:   {}", p.data_bytes_scrubbed);
132    println!("    tree_bytes_scrubbed:   {}", p.tree_bytes_scrubbed);
133    println!("    read_errors:           {}", p.read_errors);
134    println!("    csum_errors:           {}", p.csum_errors);
135    println!("    verify_errors:         {}", p.verify_errors);
136    println!("    no_csum:               {}", p.no_csum);
137    println!("    csum_discards:         {}", p.csum_discards);
138    println!("    super_errors:          {}", p.super_errors);
139    println!("    malloc_errors:         {}", p.malloc_errors);
140    println!("    uncorrectable_errors:  {}", p.uncorrectable_errors);
141    println!("    unverified_errors:     {}", p.unverified_errors);
142    println!("    corrected_errors:      {}", p.corrected_errors);
143    println!("    last_physical:         {}", p.last_physical);
144}
145
146/// Print device progress in either raw or summary format.
147fn print_device_progress(
148    p: &ScrubProgress,
149    devid: u64,
150    path: &str,
151    raw: bool,
152    mode: &SizeFormat,
153) {
154    if raw {
155        print_raw_progress(p, devid, path);
156    } else {
157        print_progress_summary(p, devid, path, mode);
158    }
159}
160
161/// Set the IO scheduling priority for the current thread.
162///
163/// Class values: 1 = realtime, 2 = best-effort, 3 = idle.
164/// Classdata is the priority level within the class (0-7 for RT and BE).
165/// Failure is logged as a warning and ignored.
166fn set_ioprio(class: i32, classdata: i32) {
167    const IOPRIO_WHO_PROCESS: i32 = 1;
168    const IOPRIO_CLASS_SHIFT: i32 = 13;
169    let value = (class << IOPRIO_CLASS_SHIFT) | classdata;
170    let ret = unsafe {
171        libc::syscall(libc::SYS_ioprio_set, IOPRIO_WHO_PROCESS, 0, value)
172    };
173    if ret < 0 {
174        eprintln!("WARNING: setting ioprio failed (ignored)");
175    }
176}