Skip to main content

btrfs_cli/scrub/
status.rs

1use 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/// Show the status of a running or finished scrub
15#[derive(Parser, Debug)]
16pub struct ScrubStatusCommand {
17    /// Show stats per device
18    #[clap(long, short)]
19    pub device: bool,
20
21    /// Print full raw data instead of summary
22    #[clap(short = 'R', long = "raw-data")]
23    pub raw_data: bool,
24
25    #[clap(flatten)]
26    pub units: UnitMode,
27
28    /// Path to a mounted btrfs filesystem or a device
29    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}