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