btrfs_cli/replace/
start.rs1use crate::{
2 Format, Runnable,
3 util::{check_device_for_overwrite, open_path},
4};
5use anyhow::{Context, Result, bail};
6use btrfs_uapi::{
7 filesystem::filesystem_info,
8 replace::{ReplaceSource, ReplaceState, replace_start, replace_status},
9 sysfs::SysfsBtrfs,
10};
11use clap::Parser;
12use std::{
13 ffi::CString, fs, os::unix::io::AsFd, path::PathBuf, thread, time::Duration,
14};
15
16#[derive(Parser, Debug)]
22pub struct ReplaceStartCommand {
23 pub source: String,
25
26 pub target: PathBuf,
28
29 pub mount_point: PathBuf,
31
32 #[clap(short = 'r', long)]
34 pub redundancy_only: bool,
35
36 #[clap(short = 'f', long)]
38 pub force: bool,
39
40 #[clap(short = 'B', long)]
42 pub no_background: bool,
43
44 #[clap(long)]
46 pub enqueue: bool,
47
48 #[clap(short = 'K', long)]
50 pub nodiscard: bool,
51}
52
53impl Runnable for ReplaceStartCommand {
54 fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
55 check_device_for_overwrite(&self.target, self.force)?;
57
58 let file = open_path(&self.mount_point)?;
59 let fd = file.as_fd();
60
61 if self.enqueue {
63 let info = filesystem_info(fd).with_context(|| {
64 format!(
65 "failed to get filesystem info for '{}'",
66 self.mount_point.display()
67 )
68 })?;
69 let sysfs = SysfsBtrfs::new(&info.uuid);
70 let op =
71 sysfs.wait_for_exclusive_operation().with_context(|| {
72 format!(
73 "failed to check exclusive operation on '{}'",
74 self.mount_point.display()
75 )
76 })?;
77 if op != "none" {
78 eprintln!("waited for exclusive operation '{op}' to finish");
79 }
80 }
81
82 let current = replace_status(fd).with_context(|| {
84 format!(
85 "failed to get replace status on '{}'",
86 self.mount_point.display()
87 )
88 })?;
89 if current.state == ReplaceState::Started {
90 bail!(
91 "a device replace operation is already in progress on '{}'",
92 self.mount_point.display()
93 );
94 }
95
96 let source = if let Ok(devid) = self.source.parse::<u64>() {
99 ReplaceSource::DevId(devid)
100 } else {
101 ReplaceSource::Path(
102 &CString::new(self.source.as_bytes()).with_context(|| {
103 format!("invalid source device path '{}'", self.source)
104 })?,
105 )
106 };
107
108 let tgtdev = CString::new(self.target.as_os_str().as_encoded_bytes())
109 .with_context(|| {
110 format!("invalid target device path '{}'", self.target.display())
111 })?;
112
113 if !self.nodiscard {
115 let tgtfile = fs::OpenOptions::new()
116 .write(true)
117 .open(&self.target)
118 .with_context(|| {
119 format!(
120 "failed to open target device '{}' for discard",
121 self.target.display()
122 )
123 })?;
124 match btrfs_uapi::blkdev::discard_whole_device(tgtfile.as_fd()) {
125 Ok(0) => {}
126 Ok(_) => eprintln!(
127 "discarded target device '{}'",
128 self.target.display()
129 ),
130 Err(e) => {
131 eprintln!(
132 "warning: discard failed on '{}': {e}; continuing anyway",
133 self.target.display()
134 );
135 }
136 }
137 }
138
139 match replace_start(fd, source, &tgtdev, self.redundancy_only)
140 .with_context(|| {
141 format!(
142 "failed to start replace on '{}'",
143 self.mount_point.display()
144 )
145 })? {
146 Ok(()) => {}
147 Err(e) => bail!("{e}"),
148 }
149
150 println!(
151 "replace started: {} -> {}",
152 self.source,
153 self.target.display(),
154 );
155
156 if self.no_background {
157 loop {
159 thread::sleep(Duration::from_secs(1));
160 let status = replace_status(fd).with_context(|| {
161 format!(
162 "failed to get replace status on '{}'",
163 self.mount_point.display()
164 )
165 })?;
166
167 let pct = status.progress_1000 as f64 / 10.0;
168 eprint!(
169 "\r{pct:.1}% done, {} write errs, {} uncorr. read errs",
170 status.num_write_errors,
171 status.num_uncorrectable_read_errors,
172 );
173
174 if status.state != ReplaceState::Started {
175 eprintln!();
176 match status.state {
177 ReplaceState::Finished => {
178 println!("replace finished successfully");
179 }
180 ReplaceState::Canceled => {
181 bail!("replace was cancelled");
182 }
183 _ => {
184 bail!(
185 "replace ended in unexpected state: {:?}",
186 status.state
187 );
188 }
189 }
190 break;
191 }
192 }
193 }
194
195 Ok(())
196 }
197}