Skip to main content

btrfs_cli/scrub/
resume.rs

1use crate::{
2    Format, Runnable,
3    util::{SizeFormat, open_path},
4};
5use anyhow::{Context, Result};
6use btrfs_uapi::{
7    device::device_info_all, filesystem::filesystem_info, scrub::scrub_start,
8};
9use clap::Parser;
10use std::{os::unix::io::AsFd, path::PathBuf};
11
12/// Resume a previously cancelled or interrupted scrub
13///
14/// Scrubs all devices sequentially. This command blocks until the scrub
15/// completes; use Ctrl-C to cancel.
16#[derive(Parser, Debug)]
17pub struct ScrubResumeCommand {
18    /// Do not background (default behavior, accepted for compatibility)
19    #[clap(short = 'B')]
20    pub no_background: bool,
21
22    /// Stats per device
23    #[clap(long, short)]
24    pub device: bool,
25
26    /// Read-only mode: check for errors but do not attempt repairs
27    #[clap(long, short)]
28    pub readonly: bool,
29
30    /// Print full raw data instead of summary
31    #[clap(short = 'R')]
32    pub raw: bool,
33
34    /// Set ioprio class (see ionice(1) manpage)
35    #[clap(short = 'c', value_name = "CLASS")]
36    pub ioprio_class: Option<i32>,
37
38    /// Set ioprio classdata (see ionice(1) manpage)
39    #[clap(short = 'n', value_name = "CDATA")]
40    pub ioprio_classdata: Option<i32>,
41
42    /// Path to a mounted btrfs filesystem or a device
43    pub path: PathBuf,
44}
45
46impl Runnable for ScrubResumeCommand {
47    fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
48        // Resume uses the same ioctl as start; the kernel tracks where it left
49        // off via the scrub state on disk.
50        let file = open_path(&self.path)?;
51        let fd = file.as_fd();
52
53        let fs = filesystem_info(fd).with_context(|| {
54            format!(
55                "failed to get filesystem info for '{}'",
56                self.path.display()
57            )
58        })?;
59        let devices = device_info_all(fd, &fs).with_context(|| {
60            format!("failed to get device info for '{}'", self.path.display())
61        })?;
62
63        if self.ioprio_class.is_some() || self.ioprio_classdata.is_some() {
64            super::set_ioprio(
65                self.ioprio_class.unwrap_or(3), // default: idle
66                self.ioprio_classdata.unwrap_or(0),
67            );
68        }
69
70        println!("UUID: {}", fs.uuid.as_hyphenated());
71
72        let mode = SizeFormat::HumanIec;
73        let mut fs_totals = btrfs_uapi::scrub::ScrubProgress::default();
74
75        for dev in &devices {
76            println!("resuming scrub on device {} ({})", dev.devid, dev.path);
77
78            match scrub_start(fd, dev.devid, self.readonly) {
79                Ok(progress) => {
80                    super::accumulate(&mut fs_totals, &progress);
81                    if self.device {
82                        super::print_device_progress(
83                            &progress, dev.devid, &dev.path, self.raw, &mode,
84                        );
85                    }
86                }
87                Err(e) => {
88                    eprintln!(
89                        "error resuming scrub on device {}: {e}",
90                        dev.devid
91                    );
92                }
93            }
94        }
95
96        if !self.device {
97            if self.raw {
98                super::print_raw_progress(&fs_totals, 0, "filesystem totals");
99            } else {
100                super::print_error_summary(&fs_totals);
101            }
102        } else if devices.len() > 1 {
103            println!("\nFilesystem totals:");
104            if self.raw {
105                super::print_raw_progress(&fs_totals, 0, "filesystem totals");
106            } else {
107                super::print_error_summary(&fs_totals);
108            }
109        }
110
111        Ok(())
112    }
113}