btrfs_cli/device/
remove.rs1use crate::{Format, Runnable};
2use anyhow::{Context, Result};
3use btrfs_uapi::{
4 device::{DeviceSpec, device_remove},
5 filesystem::filesystem_info,
6 sysfs::SysfsBtrfs,
7};
8use clap::Parser;
9use std::{ffi::CString, fs::File, os::unix::io::AsFd, path::PathBuf};
10
11#[derive(Parser, Debug)]
19pub struct DeviceRemoveCommand {
20 #[clap(long)]
26 pub enqueue: bool,
27
28 #[clap(required = true, num_args = 2..)]
29 pub args: Vec<String>,
30}
31
32impl Runnable for DeviceRemoveCommand {
33 fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
34 let (mount_str, specs) = self
37 .args
38 .split_last()
39 .expect("clap ensures at least 2 args");
40
41 let mount = PathBuf::from(mount_str);
42 let file = File::open(&mount)
43 .with_context(|| format!("failed to open '{}'", mount.display()))?;
44 let fd = file.as_fd();
45
46 if self.enqueue {
47 let info = filesystem_info(fd).with_context(|| {
48 format!(
49 "failed to get filesystem info for '{}'",
50 mount.display()
51 )
52 })?;
53 let sysfs = SysfsBtrfs::new(&info.uuid);
54 let op =
55 sysfs.wait_for_exclusive_operation().with_context(|| {
56 format!(
57 "failed to check exclusive operation on '{}'",
58 mount.display()
59 )
60 })?;
61 if op != "none" {
62 eprintln!("waited for exclusive operation '{op}' to finish");
63 }
64 }
65
66 let mut had_error = false;
67
68 for spec_str in specs {
69 match remove_one(fd, spec_str) {
70 Ok(()) => println!("removed device '{spec_str}'"),
71 Err(e) => {
72 eprintln!("error removing device '{spec_str}': {e}");
73 had_error = true;
74 }
75 }
76 }
77
78 if had_error {
79 anyhow::bail!("one or more devices could not be removed");
80 }
81
82 Ok(())
83 }
84}
85
86fn remove_one(fd: std::os::unix::io::BorrowedFd, spec_str: &str) -> Result<()> {
92 if let Ok(devid) = spec_str.parse::<u64>() {
93 device_remove(fd, DeviceSpec::Id(devid))
94 .with_context(|| format!("failed to remove devid {devid}"))?;
95 } else {
96 let cpath = CString::new(spec_str).with_context(|| {
97 format!("device spec contains a null byte: '{spec_str}'")
98 })?;
99 device_remove(fd, DeviceSpec::Path(&cpath))
100 .with_context(|| format!("failed to remove device '{spec_str}'"))?;
101 }
102 Ok(())
103}