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#[derive(Parser, Debug)]
20pub struct ScrubStartCommand {
21 #[clap(short = 'B')]
23 pub no_background: bool,
24
25 #[clap(long, short)]
27 pub device: bool,
28
29 #[clap(long, short)]
31 pub readonly: bool,
32
33 #[clap(short = 'R')]
35 pub raw: bool,
36
37 #[clap(long, short)]
39 pub force: bool,
40
41 #[clap(long, value_name = "SIZE", value_parser = parse_size_with_suffix)]
44 pub limit: Option<u64>,
45
46 #[clap(short = 'c', value_name = "CLASS")]
48 pub ioprio_class: Option<i32>,
49
50 #[clap(short = 'n', value_name = "CDATA")]
52 pub ioprio_classdata: Option<i32>,
53
54 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), 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}