btrfs_cli/scrub/
status.rs1use crate::{
2 Format, RunContext, Runnable,
3 filesystem::UnitMode,
4 util::{fmt_size, open_path},
5};
6use anyhow::{Context, Result};
7use btrfs_uapi::{
8 device::device_info_all, filesystem::filesystem_info, scrub::scrub_progress,
9};
10use clap::Parser;
11use cols::Cols;
12use std::{os::unix::io::AsFd, path::PathBuf};
13
14#[derive(Parser, Debug)]
16pub struct ScrubStatusCommand {
17 #[clap(long, short)]
19 pub device: bool,
20
21 #[clap(short = 'R', long = "raw-data")]
23 pub raw_data: bool,
24
25 #[clap(flatten)]
26 pub units: UnitMode,
27
28 pub path: PathBuf,
30}
31
32impl Runnable for ScrubStatusCommand {
33 #[allow(clippy::too_many_lines)]
34 fn run(&self, ctx: &RunContext) -> Result<()> {
35 let mode = self.units.resolve();
36 let file = open_path(&self.path)?;
37 let fd = file.as_fd();
38
39 let fs = filesystem_info(fd).with_context(|| {
40 format!(
41 "failed to get filesystem info for '{}'",
42 self.path.display()
43 )
44 })?;
45 let devices = device_info_all(fd, &fs).with_context(|| {
46 format!("failed to get device info for '{}'", self.path.display())
47 })?;
48
49 println!("UUID: {}", fs.uuid.as_hyphenated());
50
51 let mut any_running = false;
52 let mut fs_totals = btrfs_uapi::scrub::ScrubProgress::default();
53
54 match ctx.format {
55 Format::Modern => {
56 let mut rows: Vec<ScrubRow> = Vec::new();
57 for dev in &devices {
58 match scrub_progress(fd, dev.devid).with_context(|| {
59 format!(
60 "failed to get scrub progress for device {}",
61 dev.devid
62 )
63 })? {
64 None => {
65 rows.push(ScrubRow {
66 devid: dev.devid,
67 path: dev.path.clone(),
68 scrubbed: "-".to_string(),
69 errors: "-".to_string(),
70 });
71 }
72 Some(progress) => {
73 any_running = true;
74 super::accumulate(&mut fs_totals, &progress);
75 rows.push(ScrubRow {
76 devid: dev.devid,
77 path: dev.path.clone(),
78 scrubbed: format!(
79 "{}/~{}",
80 fmt_size(progress.bytes_scrubbed(), &mode),
81 fmt_size(dev.bytes_used, &mode),
82 ),
83 errors: format_error_count(&progress),
84 });
85 }
86 }
87 }
88
89 if any_running {
90 let mut out = std::io::stdout().lock();
91 let _ = ScrubRow::print_table(&rows, &mut out);
92 } else {
93 println!("\tno scrub in progress");
94 }
95 }
96 Format::Text => {
97 for dev in &devices {
98 match scrub_progress(fd, dev.devid).with_context(|| {
99 format!(
100 "failed to get scrub progress for device {}",
101 dev.devid
102 )
103 })? {
104 None => {
105 if self.device {
106 println!(
107 "device {} ({}): no scrub in progress",
108 dev.devid, dev.path
109 );
110 }
111 }
112 Some(progress) => {
113 any_running = true;
114 super::accumulate(&mut fs_totals, &progress);
115 if self.device {
116 super::print_device_progress(
117 &progress,
118 dev.devid,
119 &dev.path,
120 self.raw_data,
121 &mode,
122 );
123 }
124 }
125 }
126 }
127
128 if !any_running {
129 println!("\tno scrub in progress");
130 } else if !self.device {
131 if self.raw_data {
132 super::print_raw_progress(
133 &fs_totals,
134 0,
135 "filesystem totals",
136 );
137 } else {
138 println!(
139 "Bytes scrubbed: {}",
140 fmt_size(fs_totals.bytes_scrubbed(), &mode)
141 );
142 super::print_error_summary(&fs_totals);
143 }
144 }
145 }
146 Format::Json => unreachable!(),
147 }
148
149 Ok(())
150 }
151}
152
153#[derive(Cols)]
154struct ScrubRow {
155 #[column(header = "DEVID", right)]
156 devid: u64,
157 #[column(header = "PATH")]
158 path: String,
159 #[column(header = "SCRUBBED", right)]
160 scrubbed: String,
161 #[column(header = "ERRORS")]
162 errors: String,
163}
164
165pub(super) fn format_error_count(
166 p: &btrfs_uapi::scrub::ScrubProgress,
167) -> String {
168 if p.is_clean() {
169 "no errors".to_string()
170 } else {
171 let total =
172 p.read_errors + p.csum_errors + p.verify_errors + p.super_errors;
173 let uncorrectable = p.uncorrectable_errors;
174 if uncorrectable > 0 {
175 format!("{total} errors ({uncorrectable} uncorrectable)")
176 } else {
177 format!("{total} errors (all corrected)")
178 }
179 }
180}