btrfs_cli/replace/
status.rs1use 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#[derive(Parser, Debug)]
18pub struct ReplaceStatusCommand {
19 #[clap(short = '1', long)]
21 pub once: bool,
22
23 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 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}