btrfs_cli/replace/
status.rs1use crate::{Format, Runnable};
2use anyhow::{Context, Result};
3use btrfs_uapi::replace::{ReplaceState, replace_status};
4use clap::Parser;
5use std::{
6 fs::File, io::Write, os::unix::io::AsFd, path::PathBuf, thread,
7 time::Duration,
8};
9
10#[derive(Parser, Debug)]
16pub struct ReplaceStatusCommand {
17 #[clap(short = '1', long)]
19 pub once: bool,
20
21 pub mount_point: PathBuf,
23}
24
25fn format_time(t: &std::time::SystemTime) -> String {
26 let secs = t
27 .duration_since(std::time::UNIX_EPOCH)
28 .unwrap_or_default()
29 .as_secs();
30
31 let secs_i64 = secs as nix::libc::time_t;
34 let mut tm: nix::libc::tm = unsafe { std::mem::zeroed() };
35 unsafe { nix::libc::localtime_r(&secs_i64, &mut tm) };
36
37 let mut buf = [0u8; 64];
39 let fmt = b"%e.%b %T\0";
40 let len = unsafe {
41 nix::libc::strftime(
42 buf.as_mut_ptr() as *mut nix::libc::c_char,
43 buf.len(),
44 fmt.as_ptr() as *const nix::libc::c_char,
45 &tm,
46 )
47 };
48 String::from_utf8_lossy(&buf[..len]).into_owned()
49}
50
51impl Runnable for ReplaceStatusCommand {
52 fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
53 let file = File::open(&self.mount_point).with_context(|| {
54 format!("failed to open '{}'", self.mount_point.display())
55 })?;
56 let fd = file.as_fd();
57
58 loop {
59 let status = replace_status(fd).with_context(|| {
60 format!(
61 "failed to get replace status on '{}'",
62 self.mount_point.display()
63 )
64 })?;
65
66 let line = match status.state {
67 ReplaceState::NeverStarted => "Never started".to_string(),
68 ReplaceState::Started => {
69 let pct = status.progress_1000 as f64 / 10.0;
70 format!(
71 "{pct:.1}% done, {} write errs, {} uncorr. read errs",
72 status.num_write_errors,
73 status.num_uncorrectable_read_errors,
74 )
75 }
76 ReplaceState::Finished => {
77 let started = status
78 .time_started
79 .map(|t| format_time(&t))
80 .unwrap_or_default();
81 let stopped = status
82 .time_stopped
83 .map(|t| format_time(&t))
84 .unwrap_or_default();
85 format!(
86 "Started on {started}, finished on {stopped}, \
87 {} write errs, {} uncorr. read errs",
88 status.num_write_errors,
89 status.num_uncorrectable_read_errors,
90 )
91 }
92 ReplaceState::Canceled => {
93 let started = status
94 .time_started
95 .map(|t| format_time(&t))
96 .unwrap_or_default();
97 let stopped = status
98 .time_stopped
99 .map(|t| format_time(&t))
100 .unwrap_or_default();
101 let pct = status.progress_1000 as f64 / 10.0;
102 format!(
103 "Started on {started}, canceled on {stopped} at {pct:.1}%, \
104 {} write errs, {} uncorr. read errs",
105 status.num_write_errors,
106 status.num_uncorrectable_read_errors,
107 )
108 }
109 ReplaceState::Suspended => {
110 let started = status
111 .time_started
112 .map(|t| format_time(&t))
113 .unwrap_or_default();
114 let stopped = status
115 .time_stopped
116 .map(|t| format_time(&t))
117 .unwrap_or_default();
118 let pct = status.progress_1000 as f64 / 10.0;
119 format!(
120 "Started on {started}, suspended on {stopped} at {pct:.1}%, \
121 {} write errs, {} uncorr. read errs",
122 status.num_write_errors,
123 status.num_uncorrectable_read_errors,
124 )
125 }
126 };
127
128 print!("\r{line}");
129 std::io::stdout().flush()?;
130
131 if self.once || status.state != ReplaceState::Started {
133 println!();
134 break;
135 }
136
137 thread::sleep(Duration::from_secs(1));
138 }
139
140 Ok(())
141 }
142}