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)]
20#[allow(clippy::struct_excessive_bools)]
21pub struct ScrubStartCommand {
22 #[clap(short = 'B')]
24 pub no_background: bool,
25
26 #[clap(long, short)]
28 pub device: bool,
29
30 #[clap(long, short)]
32 pub readonly: bool,
33
34 #[clap(short = 'R')]
36 pub raw: bool,
37
38 #[clap(long, short)]
40 pub force: bool,
41
42 #[clap(long, value_name = "SIZE", value_parser = parse_size_with_suffix)]
45 pub limit: Option<u64>,
46
47 #[clap(short = 'c', value_name = "CLASS")]
49 pub ioprio_class: Option<i32>,
50
51 #[clap(short = 'n', value_name = "CDATA")]
53 pub ioprio_classdata: Option<i32>,
54
55 pub path: PathBuf,
57}
58
59impl Runnable for ScrubStartCommand {
60 fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
61 let file = open_path(&self.path)?;
62 let fd = file.as_fd();
63
64 let fs = filesystem_info(fd).with_context(|| {
65 format!(
66 "failed to get filesystem info for '{}'",
67 self.path.display()
68 )
69 })?;
70 let devices = device_info_all(fd, &fs).with_context(|| {
71 format!("failed to get device info for '{}'", self.path.display())
72 })?;
73
74 if !self.force {
75 for dev in &devices {
76 if scrub_progress(fd, dev.devid)
77 .with_context(|| {
78 format!(
79 "failed to check scrub status for device {}",
80 dev.devid
81 )
82 })?
83 .is_some()
84 {
85 bail!(
86 "Scrub is already running.\n\
87 To cancel use 'btrfs scrub cancel {path}'.\n\
88 To see the status use 'btrfs scrub status {path}'",
89 path = self.path.display()
90 );
91 }
92 }
93 }
94
95 let sysfs = SysfsBtrfs::new(&fs.uuid);
96 let old_limits = self.apply_limits(&sysfs, &devices)?;
97
98 if self.ioprio_class.is_some() || self.ioprio_classdata.is_some() {
99 super::set_ioprio(
100 self.ioprio_class.unwrap_or(3), self.ioprio_classdata.unwrap_or(0),
102 );
103 }
104
105 println!("UUID: {}", fs.uuid.as_hyphenated());
106
107 let mode = SizeFormat::HumanIec;
108 let mut fs_totals = btrfs_uapi::scrub::ScrubProgress::default();
109
110 for dev in &devices {
111 println!("scrubbing device {} ({})", dev.devid, dev.path);
112
113 match scrub_start(fd, dev.devid, self.readonly) {
114 Ok(progress) => {
115 super::accumulate(&mut fs_totals, &progress);
116 if self.device {
117 super::print_device_progress(
118 &progress, dev.devid, &dev.path, self.raw, &mode,
119 );
120 }
121 }
122 Err(e) => {
123 eprintln!("error scrubbing device {}: {e}", dev.devid);
124 }
125 }
126 }
127
128 if !self.device {
129 if self.raw {
130 super::print_raw_progress(&fs_totals, 0, "filesystem totals");
131 } else {
132 super::print_error_summary(&fs_totals);
133 }
134 } else if devices.len() > 1 {
135 println!("\nFilesystem totals:");
136 if self.raw {
137 super::print_raw_progress(&fs_totals, 0, "filesystem totals");
138 } else {
139 super::print_error_summary(&fs_totals);
140 }
141 }
142
143 self.restore_limits(&sysfs, &old_limits);
144
145 Ok(())
146 }
147}
148
149impl ScrubStartCommand {
150 fn apply_limits(
151 &self,
152 sysfs: &SysfsBtrfs,
153 devices: &[btrfs_uapi::device::DeviceInfo],
154 ) -> Result<Vec<(u64, u64)>> {
155 let mut old_limits = Vec::new();
156 if let Some(limit) = self.limit {
157 for dev in devices {
158 let old = sysfs.scrub_speed_max_get(dev.devid).with_context(
159 || {
160 format!(
161 "failed to read scrub limit for devid {}",
162 dev.devid
163 )
164 },
165 )?;
166 old_limits.push((dev.devid, old));
167 sysfs.scrub_speed_max_set(dev.devid, limit).with_context(
168 || {
169 format!(
170 "failed to set scrub limit for devid {}",
171 dev.devid
172 )
173 },
174 )?;
175 }
176 }
177 Ok(old_limits)
178 }
179
180 #[allow(clippy::unused_self)] fn restore_limits(&self, sysfs: &SysfsBtrfs, old_limits: &[(u64, u64)]) {
182 for &(devid, old_limit) in old_limits {
183 if let Err(e) = sysfs.scrub_speed_max_set(devid, old_limit) {
184 eprintln!(
185 "WARNING: failed to restore scrub limit for devid {devid}: {e}"
186 );
187 }
188 }
189 }
190}