Skip to main content

btrfs_cli/scrub/
start.rs

1use crate::{
2    Format, Runnable,
3    util::{SizeFormat, open_path, parse_size_with_suffix},
4};
5use anyhow::{Context, Result, bail};
6use btrfs_uapi::{
7    device::device_info_all,
8    filesystem::filesystem_info,
9    scrub::{scrub_progress, scrub_start},
10    sysfs::SysfsBtrfs,
11};
12use clap::Parser;
13use std::{os::unix::io::AsFd, path::PathBuf};
14
15/// Start a new scrub on the filesystem or a device.
16///
17/// Scrubs all devices sequentially. This command blocks until the scrub
18/// completes; use Ctrl-C to cancel.
19#[derive(Parser, Debug)]
20pub struct ScrubStartCommand {
21    /// Do not background (default behavior, accepted for compatibility)
22    #[clap(short = 'B')]
23    pub no_background: bool,
24
25    /// Stats per device
26    #[clap(long, short)]
27    pub device: bool,
28
29    /// Read-only mode: check for errors but do not attempt repairs
30    #[clap(long, short)]
31    pub readonly: bool,
32
33    /// Print full raw data instead of summary
34    #[clap(short = 'R')]
35    pub raw: bool,
36
37    /// Force starting new scrub even if a scrub is already running
38    #[clap(long, short)]
39    pub force: bool,
40
41    /// Set the throughput limit for each device (0 for unlimited), restored
42    /// afterwards
43    #[clap(long, value_name = "SIZE", value_parser = parse_size_with_suffix)]
44    pub limit: Option<u64>,
45
46    /// Set ioprio class (see ionice(1) manpage)
47    #[clap(short = 'c', value_name = "CLASS")]
48    pub ioprio_class: Option<i32>,
49
50    /// Set ioprio classdata (see ionice(1) manpage)
51    #[clap(short = 'n', value_name = "CDATA")]
52    pub ioprio_classdata: Option<i32>,
53
54    /// Path to a mounted btrfs filesystem or a device
55    pub path: PathBuf,
56}
57
58impl Runnable for ScrubStartCommand {
59    fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
60        let file = open_path(&self.path)?;
61        let fd = file.as_fd();
62
63        let fs = filesystem_info(fd).with_context(|| {
64            format!(
65                "failed to get filesystem info for '{}'",
66                self.path.display()
67            )
68        })?;
69        let devices = device_info_all(fd, &fs).with_context(|| {
70            format!("failed to get device info for '{}'", self.path.display())
71        })?;
72
73        if !self.force {
74            for dev in &devices {
75                if scrub_progress(fd, dev.devid)
76                    .with_context(|| {
77                        format!(
78                            "failed to check scrub status for device {}",
79                            dev.devid
80                        )
81                    })?
82                    .is_some()
83                {
84                    bail!(
85                        "Scrub is already running.\n\
86                         To cancel use 'btrfs scrub cancel {path}'.\n\
87                         To see the status use 'btrfs scrub status {path}'",
88                        path = self.path.display()
89                    );
90                }
91            }
92        }
93
94        let sysfs = SysfsBtrfs::new(&fs.uuid);
95        let old_limits = self.apply_limits(&sysfs, &devices)?;
96
97        if self.ioprio_class.is_some() || self.ioprio_classdata.is_some() {
98            super::set_ioprio(
99                self.ioprio_class.unwrap_or(3), // default: idle
100                self.ioprio_classdata.unwrap_or(0),
101            );
102        }
103
104        println!("UUID: {}", fs.uuid.as_hyphenated());
105
106        let mode = SizeFormat::HumanIec;
107        let mut fs_totals = btrfs_uapi::scrub::ScrubProgress::default();
108
109        for dev in &devices {
110            println!("scrubbing device {} ({})", dev.devid, dev.path);
111
112            match scrub_start(fd, dev.devid, self.readonly) {
113                Ok(progress) => {
114                    super::accumulate(&mut fs_totals, &progress);
115                    if self.device {
116                        super::print_device_progress(
117                            &progress, dev.devid, &dev.path, self.raw, &mode,
118                        );
119                    }
120                }
121                Err(e) => {
122                    eprintln!("error scrubbing device {}: {e}", dev.devid);
123                }
124            }
125        }
126
127        if !self.device {
128            if self.raw {
129                super::print_raw_progress(&fs_totals, 0, "filesystem totals");
130            } else {
131                super::print_error_summary(&fs_totals);
132            }
133        } else if devices.len() > 1 {
134            println!("\nFilesystem totals:");
135            if self.raw {
136                super::print_raw_progress(&fs_totals, 0, "filesystem totals");
137            } else {
138                super::print_error_summary(&fs_totals);
139            }
140        }
141
142        self.restore_limits(&sysfs, &old_limits);
143
144        Ok(())
145    }
146}
147
148impl ScrubStartCommand {
149    fn apply_limits(
150        &self,
151        sysfs: &SysfsBtrfs,
152        devices: &[btrfs_uapi::device::DeviceInfo],
153    ) -> Result<Vec<(u64, u64)>> {
154        let mut old_limits = Vec::new();
155        if let Some(limit) = self.limit {
156            for dev in devices {
157                let old = sysfs.scrub_speed_max_get(dev.devid).with_context(
158                    || {
159                        format!(
160                            "failed to read scrub limit for devid {}",
161                            dev.devid
162                        )
163                    },
164                )?;
165                old_limits.push((dev.devid, old));
166                sysfs.scrub_speed_max_set(dev.devid, limit).with_context(
167                    || {
168                        format!(
169                            "failed to set scrub limit for devid {}",
170                            dev.devid
171                        )
172                    },
173                )?;
174            }
175        }
176        Ok(old_limits)
177    }
178
179    fn restore_limits(&self, sysfs: &SysfsBtrfs, old_limits: &[(u64, u64)]) {
180        for &(devid, old_limit) in old_limits {
181            if let Err(e) = sysfs.scrub_speed_max_set(devid, old_limit) {
182                eprintln!(
183                    "WARNING: failed to restore scrub limit for devid {devid}: {e}"
184                );
185            }
186        }
187    }
188}