btrfs_cli/device/
remove.rs1use crate::{Format, Runnable};
2use anyhow::{Context, Result};
3use btrfs_uapi::device::{DeviceSpec, device_remove};
4use clap::Parser;
5use std::{ffi::CString, fs::File, os::unix::io::AsFd, path::PathBuf};
6
7#[derive(Parser, Debug)]
15pub struct DeviceRemoveCommand {
16 #[clap(required = true, num_args = 2..)]
21 pub args: Vec<String>,
22}
23
24impl Runnable for DeviceRemoveCommand {
25 fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
26 let (mount_str, specs) = self
29 .args
30 .split_last()
31 .expect("clap ensures at least 2 args");
32
33 let mount = PathBuf::from(mount_str);
34 let file = File::open(&mount)
35 .with_context(|| format!("failed to open '{}'", mount.display()))?;
36 let fd = file.as_fd();
37
38 let mut had_error = false;
39
40 for spec_str in specs {
41 match remove_one(fd, spec_str) {
42 Ok(()) => println!("removed device '{spec_str}'"),
43 Err(e) => {
44 eprintln!("error removing device '{spec_str}': {e}");
45 had_error = true;
46 }
47 }
48 }
49
50 if had_error {
51 anyhow::bail!("one or more devices could not be removed");
52 }
53
54 Ok(())
55 }
56}
57
58fn remove_one(fd: std::os::unix::io::BorrowedFd, spec_str: &str) -> Result<()> {
64 if let Ok(devid) = spec_str.parse::<u64>() {
65 device_remove(fd, DeviceSpec::Id(devid))
66 .with_context(|| format!("failed to remove devid {devid}"))?;
67 } else {
68 let cpath = CString::new(spec_str).with_context(|| {
69 format!("device spec contains a null byte: '{spec_str}'")
70 })?;
71 device_remove(fd, DeviceSpec::Path(&cpath))
72 .with_context(|| format!("failed to remove device '{spec_str}'"))?;
73 }
74 Ok(())
75}