use crate::{RunContext, Runnable};
use anyhow::{Context, Result};
use btrfs_uapi::device::{device_forget, device_scan};
use clap::Parser;
use std::{
ffi::CString,
fs,
path::{Path, PathBuf},
};
#[derive(Parser, Debug)]
pub struct DeviceScanCommand {
#[clap(long, short = 'u', alias = "forget")]
pub forget: bool,
pub devices: Vec<PathBuf>,
}
impl Runnable for DeviceScanCommand {
fn run(&self, _ctx: &RunContext) -> Result<()> {
if self.forget {
if self.devices.is_empty() {
device_forget(None).context(
"failed to unregister stale devices from kernel",
)?;
println!("unregistered all stale devices");
} else {
let mut had_error = false;
for device in &self.devices {
match forget_one(device) {
Ok(()) => {
println!("unregistered '{}'", device.display());
}
Err(e) => {
eprintln!(
"error unregistering '{}': {e}",
device.display()
);
had_error = true;
}
}
}
if had_error {
anyhow::bail!(
"one or more devices could not be unregistered"
);
}
}
} else if self.devices.is_empty() {
scan_all()?;
} else {
let mut had_error = false;
for device in &self.devices {
match scan_one(device) {
Ok(()) => println!("registered '{}'", device.display()),
Err(e) => {
eprintln!(
"error registering '{}': {e}",
device.display()
);
had_error = true;
}
}
}
if had_error {
anyhow::bail!("one or more devices could not be registered");
}
}
Ok(())
}
}
fn scan_all() -> Result<()> {
let devices = block_devices_from_proc_partitions()
.context("failed to enumerate block devices from /proc/partitions")?;
if devices.is_empty() {
println!("no block devices found");
return Ok(());
}
let mut registered = 0u32;
for device in &devices {
let Some(path_str) = device.to_str() else {
continue;
};
let Ok(cpath) = CString::new(path_str) else {
continue;
};
if device_scan(&cpath).is_ok() {
println!("registered '{}'", device.display());
registered += 1;
}
}
if registered == 0 {
println!("no btrfs devices found");
}
Ok(())
}
fn block_devices_from_proc_partitions() -> Result<Vec<PathBuf>> {
let contents = fs::read_to_string("/proc/partitions")
.context("failed to read /proc/partitions")?;
let mut devices = Vec::new();
for line in contents.lines().skip(2) {
let Some(name) = line.split_whitespace().nth(3) else {
continue;
};
devices.push(PathBuf::from(format!("/dev/{name}")));
}
Ok(devices)
}
fn scan_one(device: &Path) -> Result<()> {
let path_str = device.to_str().ok_or_else(|| {
anyhow::anyhow!("path is not valid UTF-8: '{}'", device.display())
})?;
let cpath = CString::new(path_str).with_context(|| {
format!("path contains a null byte: '{}'", device.display())
})?;
device_scan(&cpath).with_context(|| {
format!("failed to register '{}'", device.display())
})?;
Ok(())
}
fn forget_one(device: &Path) -> Result<()> {
let path_str = device.to_str().ok_or_else(|| {
anyhow::anyhow!("path is not valid UTF-8: '{}'", device.display())
})?;
let cpath = CString::new(path_str).with_context(|| {
format!("path contains a null byte: '{}'", device.display())
})?;
device_forget(Some(&cpath)).with_context(|| {
format!("failed to unregister '{}'", device.display())
})?;
Ok(())
}