btrfs_cli/inspect/
min_dev_size.rs1use crate::{
2 RunContext, Runnable,
3 util::{human_bytes, open_path},
4};
5use anyhow::{Context, Result};
6use btrfs_disk::{
7 items::DeviceExtent,
8 reader,
9 tree::{KeyType, TreeBlock},
10};
11use clap::Parser;
12use std::{
13 fs::File,
14 io::{Read, Seek},
15 os::unix::io::AsFd,
16 path::PathBuf,
17};
18
19#[derive(Parser, Debug)]
25#[allow(clippy::doc_markdown)]
26pub struct MinDevSizeCommand {
27 #[arg(long = "id", default_value = "1")]
29 devid: u64,
30
31 #[clap(long)]
34 pub offline: bool,
35
36 path: PathBuf,
39}
40
41impl Runnable for MinDevSizeCommand {
42 fn run(&self, _ctx: &RunContext) -> Result<()> {
43 let size = if self.offline {
44 self.compute_offline()?
45 } else {
46 self.compute_online()?
47 };
48
49 println!("{} bytes ({})", size, human_bytes(size));
50 Ok(())
51 }
52}
53
54impl MinDevSizeCommand {
55 fn compute_online(&self) -> Result<u64> {
56 let file = open_path(&self.path)?;
57 btrfs_uapi::device::device_min_size(file.as_fd(), self.devid)
58 .with_context(|| {
59 format!(
60 "failed to determine min device size for devid {} on '{}'",
61 self.devid,
62 self.path.display()
63 )
64 })
65 }
66
67 fn compute_offline(&self) -> Result<u64> {
68 let file = File::open(&self.path).with_context(|| {
69 format!("failed to open '{}'", self.path.display())
70 })?;
71
72 let mut open = reader::filesystem_open(file).with_context(|| {
73 format!(
74 "failed to open btrfs filesystem on '{}'",
75 self.path.display()
76 )
77 })?;
78
79 let dev_extents =
80 collect_dev_extents(&mut open.reader, &open.tree_roots, self.devid);
81
82 Ok(btrfs_uapi::device::compute_min_size(&dev_extents))
83 }
84}
85
86fn collect_dev_extents<R: Read + Seek>(
90 block_reader: &mut reader::BlockReader<R>,
91 tree_roots: &std::collections::BTreeMap<u64, (u64, u64)>,
92 devid: u64,
93) -> Vec<(u64, u64)> {
94 let mut dev_extents: Vec<(u64, u64)> = Vec::new();
95
96 let dev_root = tree_roots
97 .get(&u64::from(btrfs_disk::raw::BTRFS_DEV_TREE_OBJECTID))
98 .map(|&(bytenr, _)| bytenr);
99
100 let Some(dev_root) = dev_root else {
101 return dev_extents;
102 };
103
104 let mut visitor = |_raw: &[u8], block: &TreeBlock| {
105 if let TreeBlock::Leaf { items, data, .. } = block {
106 for item in items {
107 if item.key.key_type != KeyType::DeviceExtent {
108 continue;
109 }
110 if item.key.objectid != devid {
111 continue;
112 }
113 let start =
114 std::mem::size_of::<btrfs_disk::raw::btrfs_header>()
115 + item.offset as usize;
116 let item_data = &data[start..][..item.size as usize];
117 if let Some(de) = DeviceExtent::parse(item_data) {
118 dev_extents.push((item.key.offset, de.length));
119 }
120 }
121 }
122 };
123
124 let _ = reader::tree_walk_tolerant(
125 block_reader,
126 dev_root,
127 &mut visitor,
128 &mut |_, _| {},
129 );
130
131 dev_extents.sort_by_key(|&(start, _)| start);
134 dev_extents
135}