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    fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
29        let file = open_path(&self.mount_point)?;
30        let fd = file.as_fd();
31
32        loop {
33            let status = replace_status(fd).with_context(|| {
34                format!(
35                    "failed to get replace status on '{}'",
36                    self.mount_point.display()
37                )
38            })?;
39
40            let line = match status.state {
41                ReplaceState::NeverStarted => "Never started".to_string(),
42                ReplaceState::Started => {
43                    let pct = status.progress_1000 as f64 / 10.0;
44                    format!(
45                        "{pct:.1}% done, {} write errs, {} uncorr. read errs",
46                        status.num_write_errors,
47                        status.num_uncorrectable_read_errors,
48                    )
49                }
50                ReplaceState::Finished => {
51                    let started = status
52                        .time_started
53                        .map(|t| format_time_short(&t))
54                        .unwrap_or_default();
55                    let stopped = status
56                        .time_stopped
57                        .map(|t| format_time_short(&t))
58                        .unwrap_or_default();
59                    format!(
60                        "Started on {started}, finished on {stopped}, \
61                         {} write errs, {} uncorr. read errs",
62                        status.num_write_errors,
63                        status.num_uncorrectable_read_errors,
64                    )
65                }
66                ReplaceState::Canceled => {
67                    let started = status
68                        .time_started
69                        .map(|t| format_time_short(&t))
70                        .unwrap_or_default();
71                    let stopped = status
72                        .time_stopped
73                        .map(|t| format_time_short(&t))
74                        .unwrap_or_default();
75                    let pct = status.progress_1000 as f64 / 10.0;
76                    format!(
77                        "Started on {started}, canceled on {stopped} at {pct:.1}%, \
78                         {} write errs, {} uncorr. read errs",
79                        status.num_write_errors,
80                        status.num_uncorrectable_read_errors,
81                    )
82                }
83                ReplaceState::Suspended => {
84                    let started = status
85                        .time_started
86                        .map(|t| format_time_short(&t))
87                        .unwrap_or_default();
88                    let stopped = status
89                        .time_stopped
90                        .map(|t| format_time_short(&t))
91                        .unwrap_or_default();
92                    let pct = status.progress_1000 as f64 / 10.0;
93                    format!(
94                        "Started on {started}, suspended on {stopped} at {pct:.1}%, \
95                         {} write errs, {} uncorr. read errs",
96                        status.num_write_errors,
97                        status.num_uncorrectable_read_errors,
98                    )
99                }
100            };
101
102            print!("\r{line}");
103            std::io::stdout().flush()?;
104
105            // Terminal states or one-shot mode: print newline and exit.
106            if self.once || status.state != ReplaceState::Started {
107                println!();
108                break;
109            }
110
111            thread::sleep(Duration::from_secs(1));
112        }
113
114        Ok(())
115    }
116}