use crate::{
RunContext, Runnable,
util::{human_bytes, open_path},
};
use anyhow::{Context, Result};
use btrfs_disk::{
items::DeviceExtent,
reader,
tree::{KeyType, TreeBlock},
};
use clap::Parser;
use std::{
fs::File,
io::{Read, Seek},
os::unix::io::AsFd,
path::PathBuf,
};
#[derive(Parser, Debug)]
#[allow(clippy::doc_markdown)]
pub struct MinDevSizeCommand {
#[arg(long = "id", default_value = "1")]
devid: u64,
#[clap(long)]
pub offline: bool,
path: PathBuf,
}
impl Runnable for MinDevSizeCommand {
fn run(&self, _ctx: &RunContext) -> Result<()> {
let size = if self.offline {
self.compute_offline()?
} else {
self.compute_online()?
};
println!("{} bytes ({})", size, human_bytes(size));
Ok(())
}
}
impl MinDevSizeCommand {
fn compute_online(&self) -> Result<u64> {
let file = open_path(&self.path)?;
btrfs_uapi::device::device_min_size(file.as_fd(), self.devid)
.with_context(|| {
format!(
"failed to determine min device size for devid {} on '{}'",
self.devid,
self.path.display()
)
})
}
fn compute_offline(&self) -> Result<u64> {
let file = File::open(&self.path).with_context(|| {
format!("failed to open '{}'", self.path.display())
})?;
let mut open = reader::filesystem_open(file).with_context(|| {
format!(
"failed to open btrfs filesystem on '{}'",
self.path.display()
)
})?;
let dev_extents =
collect_dev_extents(&mut open.reader, &open.tree_roots, self.devid);
Ok(btrfs_uapi::device::compute_min_size(&dev_extents))
}
}
fn collect_dev_extents<R: Read + Seek>(
block_reader: &mut reader::BlockReader<R>,
tree_roots: &std::collections::BTreeMap<u64, (u64, u64)>,
devid: u64,
) -> Vec<(u64, u64)> {
let mut dev_extents: Vec<(u64, u64)> = Vec::new();
let dev_root = tree_roots
.get(&u64::from(btrfs_disk::raw::BTRFS_DEV_TREE_OBJECTID))
.map(|&(bytenr, _)| bytenr);
let Some(dev_root) = dev_root else {
return dev_extents;
};
let mut visitor = |_raw: &[u8], block: &TreeBlock| {
if let TreeBlock::Leaf { items, data, .. } = block {
for item in items {
if item.key.key_type != KeyType::DeviceExtent {
continue;
}
if item.key.objectid != devid {
continue;
}
let start =
std::mem::size_of::<btrfs_disk::raw::btrfs_header>()
+ item.offset as usize;
let item_data = &data[start..][..item.size as usize];
if let Some(de) = DeviceExtent::parse(item_data) {
dev_extents.push((item.key.offset, de.length));
}
}
}
};
let _ = reader::tree_walk_tolerant(
block_reader,
dev_root,
&mut visitor,
&mut |_, _| {},
);
dev_extents.sort_by_key(|&(start, _)| start);
dev_extents
}