Skip to main content

btrfs_cli/replace/
status.rs

1use crate::{
2    Format, Runnable,
3    util::{format_time_short, open_path},
4};
5use anyhow::{Context, Result};
6use btrfs_uapi::replace::{ReplaceState, replace_status};
7use clap::Parser;
8use std::{
9    io::Write, os::unix::io::AsFd, path::PathBuf, thread, time::Duration,
10};
11
12/// Print status of a running device replace operation.
13///
14/// Without -1 the status is printed continuously until the replace operation
15/// finishes or is cancelled. With -1 the status is printed once and the
16/// command exits.
17#[derive(Parser, Debug)]
18pub struct ReplaceStatusCommand {
19    /// Print once instead of continuously until the replace finishes
20    #[clap(short = '1', long)]
21    pub once: bool,
22
23    /// Path to a mounted btrfs filesystem
24    pub mount_point: PathBuf,
25}
26
27impl Runnable for ReplaceStatusCommand {
28    #[allow(clippy::cast_precision_loss)]
29    fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
30        let file = open_path(&self.mount_point)?;
31        let fd = file.as_fd();
32
33        loop {
34            let status = replace_status(fd).with_context(|| {
35                format!(
36                    "failed to get replace status on '{}'",
37                    self.mount_point.display()
38                )
39            })?;
40
41            let line = match status.state {
42                ReplaceState::NeverStarted => "Never started".to_string(),
43                ReplaceState::Started => {
44                    let pct = status.progress_1000 as f64 / 10.0;
45                    format!(
46                        "{pct:.1}% done, {} write errs, {} uncorr. read errs",
47                        status.num_write_errors,
48                        status.num_uncorrectable_read_errors,
49                    )
50                }
51                ReplaceState::Finished => {
52                    let started = status
53                        .time_started
54                        .map(|t| format_time_short(&t))
55                        .unwrap_or_default();
56                    let stopped = status
57                        .time_stopped
58                        .map(|t| format_time_short(&t))
59                        .unwrap_or_default();
60                    format!(
61                        "Started on {started}, finished on {stopped}, \
62                         {} write errs, {} uncorr. read errs",
63                        status.num_write_errors,
64                        status.num_uncorrectable_read_errors,
65                    )
66                }
67                ReplaceState::Canceled => {
68                    let started = status
69                        .time_started
70                        .map(|t| format_time_short(&t))
71                        .unwrap_or_default();
72                    let stopped = status
73                        .time_stopped
74                        .map(|t| format_time_short(&t))
75                        .unwrap_or_default();
76                    let pct = status.progress_1000 as f64 / 10.0;
77                    format!(
78                        "Started on {started}, canceled on {stopped} at {pct:.1}%, \
79                         {} write errs, {} uncorr. read errs",
80                        status.num_write_errors,
81                        status.num_uncorrectable_read_errors,
82                    )
83                }
84                ReplaceState::Suspended => {
85                    let started = status
86                        .time_started
87                        .map(|t| format_time_short(&t))
88                        .unwrap_or_default();
89                    let stopped = status
90                        .time_stopped
91                        .map(|t| format_time_short(&t))
92                        .unwrap_or_default();
93                    let pct = status.progress_1000 as f64 / 10.0;
94                    format!(
95                        "Started on {started}, suspended on {stopped} at {pct:.1}%, \
96                         {} write errs, {} uncorr. read errs",
97                        status.num_write_errors,
98                        status.num_uncorrectable_read_errors,
99                    )
100                }
101            };
102
103            print!("\r{line}");
104            std::io::stdout().flush()?;
105
106            // Terminal states or one-shot mode: print newline and exit.
107            if self.once || status.state != ReplaceState::Started {
108                println!();
109                break;
110            }
111
112            thread::sleep(Duration::from_secs(1));
113        }
114
115        Ok(())
116    }
117}