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