Skip to main content

btrfs_cli/scrub/
resume.rs

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