use crate::{RunContext, Runnable};
use anyhow::{Context, Result};
use btrfs_uapi::{
device::{DeviceSpec, device_remove},
filesystem::filesystem_info,
sysfs::SysfsBtrfs,
};
use clap::Parser;
use std::{ffi::CString, fs::File, os::unix::io::AsFd, path::PathBuf};
#[derive(Parser, Debug)]
#[allow(clippy::doc_markdown)]
pub struct DeviceRemoveCommand {
#[clap(long)]
pub enqueue: bool,
#[clap(required = true, num_args = 2..)]
pub args: Vec<String>,
}
impl Runnable for DeviceRemoveCommand {
fn run(&self, _ctx: &RunContext) -> Result<()> {
let (mount_str, specs) = self
.args
.split_last()
.expect("clap ensures at least 2 args");
let mount = PathBuf::from(mount_str);
let file = File::open(&mount)
.with_context(|| format!("failed to open '{}'", mount.display()))?;
let fd = file.as_fd();
if self.enqueue {
let info = filesystem_info(fd).with_context(|| {
format!(
"failed to get filesystem info for '{}'",
mount.display()
)
})?;
let sysfs = SysfsBtrfs::new(&info.uuid);
let op =
sysfs.wait_for_exclusive_operation().with_context(|| {
format!(
"failed to check exclusive operation on '{}'",
mount.display()
)
})?;
if op != "none" {
eprintln!("waited for exclusive operation '{op}' to finish");
}
}
let mut had_error = false;
for spec_str in specs {
match remove_one(fd, spec_str) {
Ok(()) => println!("removed device '{spec_str}'"),
Err(e) => {
eprintln!("error removing device '{spec_str}': {e}");
had_error = true;
}
}
}
if had_error {
anyhow::bail!("one or more devices could not be removed");
}
Ok(())
}
}
fn remove_one(fd: std::os::unix::io::BorrowedFd, spec_str: &str) -> Result<()> {
if let Ok(devid) = spec_str.parse::<u64>() {
device_remove(fd, &DeviceSpec::Id(devid))
.with_context(|| format!("failed to remove devid {devid}"))?;
} else {
let cpath = CString::new(spec_str).with_context(|| {
format!("device spec contains a null byte: '{spec_str}'")
})?;
device_remove(fd, &DeviceSpec::Path(&cpath))
.with_context(|| format!("failed to remove device '{spec_str}'"))?;
}
Ok(())
}