Skip to main content

btrfs_cli/inspect/
dump_tree.rs

1use super::print_tree::{self, PrintOptions};
2use crate::{RunContext, Runnable, util::open_path};
3use anyhow::{Context, Result, bail};
4use btrfs_disk::{
5    reader::{self, Traversal},
6    superblock::Superblock,
7    tree::{ObjectId, TreeBlock},
8};
9use clap::Parser;
10use std::{collections::BTreeMap, path::PathBuf};
11
12/// Dump tree blocks from a btrfs device or image file.
13///
14/// Reads raw btrfs tree blocks from a block device or filesystem image and
15/// prints their contents in a human-readable format. By default all trees
16/// are printed. Use -t to select a specific tree, -b to print a specific
17/// block, or -e/-d/-u to print subsets.
18#[derive(Parser, Debug)]
19#[allow(clippy::struct_excessive_bools)]
20pub struct DumpTreeCommand {
21    /// Path to a btrfs block device or image file
22    path: PathBuf,
23
24    /// Print only extent-related trees (extent tree and device tree)
25    #[clap(short = 'e', long)]
26    extents: bool,
27
28    /// Print only device-related trees (root tree, chunk tree, device tree)
29    #[clap(short = 'd', long)]
30    device: bool,
31
32    /// Print only short root node info
33    #[clap(short = 'r', long)]
34    roots: bool,
35
36    /// Print root node info and backup roots
37    #[clap(short = 'R', long)]
38    backups: bool,
39
40    /// Print only the UUID tree
41    #[clap(short = 'u', long)]
42    uuid: bool,
43
44    /// Print only the specified tree (by name or numeric ID).
45    ///
46    /// Tree names: root, extent, chunk, dev, fs, csum, uuid, quota,
47    /// free-space, block-group, raid-stripe, remap, tree-log, data-reloc.
48    /// Numeric IDs (e.g. 5 for the filesystem tree) are also accepted.
49    #[clap(short = 't', long)]
50    tree: Option<String>,
51
52    /// Print only the block at this logical byte number (repeatable)
53    #[clap(short = 'b', long, num_args = 1)]
54    block: Vec<u64>,
55
56    /// With -b, also print all child blocks
57    #[clap(long)]
58    follow: bool,
59
60    /// Use breadth-first traversal (default)
61    #[clap(long)]
62    bfs: bool,
63
64    /// Use depth-first traversal
65    #[clap(long)]
66    dfs: bool,
67
68    /// Hide filenames, subvolume names, xattr names and data
69    #[clap(long)]
70    hide_names: bool,
71
72    /// Print checksums stored in metadata block headers
73    #[clap(long)]
74    csum_headers: bool,
75
76    /// Print checksums stored in checksum items
77    #[clap(long)]
78    csum_items: bool,
79
80    /// Do not scan for additional devices (accepted for compatibility,
81    /// has no effect since this implementation reads from a single device)
82    #[clap(long)]
83    noscan: bool,
84}
85
86impl Runnable for DumpTreeCommand {
87    #[allow(clippy::too_many_lines)]
88    fn run(&self, _ctx: &RunContext) -> Result<()> {
89        let file = open_path(&self.path)?;
90
91        let open = reader::filesystem_open(file).with_context(|| {
92            format!(
93                "failed to open btrfs filesystem on '{}'",
94                self.path.display()
95            )
96        })?;
97
98        let traversal = if self.dfs {
99            Traversal::Dfs
100        } else {
101            Traversal::Bfs
102        };
103
104        let csum_size = open.superblock.csum_type.size();
105        let nodesize = open.superblock.nodesize;
106        let opts = PrintOptions {
107            hide_names: self.hide_names,
108            csum_headers: self.csum_headers,
109            csum_items: self.csum_items,
110            csum_size,
111        };
112
113        let mut reader = open.reader;
114        let sb = &open.superblock;
115        let tree_roots = &open.tree_roots;
116        let mut print = |block: &TreeBlock| {
117            print_tree::print_tree_block(block, nodesize, &opts);
118        };
119
120        // --block: print specific blocks
121        if !self.block.is_empty() {
122            for &logical in &self.block {
123                reader::block_visit(
124                    &mut reader,
125                    logical,
126                    self.follow,
127                    traversal,
128                    &mut print,
129                )?;
130            }
131            return Ok(());
132        }
133
134        // --tree: print a specific tree
135        if let Some(ref tree_name) = self.tree {
136            let tree_id = parse_tree_id(tree_name)?;
137            let root_bytenr = find_tree_root(tree_id, sb, tree_roots)?;
138            reader::tree_walk(&mut reader, root_bytenr, traversal, &mut print)?;
139            return Ok(());
140        }
141
142        // --roots / --backups: print short root info
143        if self.roots || self.backups {
144            print_roots(sb, tree_roots);
145            if self.backups {
146                print_backup_roots(sb);
147            }
148            return Ok(());
149        }
150
151        // --uuid: print only UUID tree
152        if self.uuid {
153            if let Some(&(root, _)) =
154                tree_roots.get(&ObjectId::UuidTree.to_raw())
155            {
156                reader::tree_walk(&mut reader, root, traversal, &mut print)?;
157            } else {
158                bail!("UUID tree not found");
159            }
160            return Ok(());
161        }
162
163        // --extents: extent and device trees
164        if self.extents {
165            for &tree_id in
166                &[ObjectId::ExtentTree.to_raw(), ObjectId::DevTree.to_raw()]
167            {
168                if let Some(&(root, _)) = tree_roots.get(&tree_id) {
169                    let name = ObjectId::from_raw(tree_id);
170                    println!("{name}:");
171                    reader::tree_walk(
172                        &mut reader,
173                        root,
174                        traversal,
175                        &mut print,
176                    )?;
177                    println!();
178                }
179            }
180            return Ok(());
181        }
182
183        // --device: root tree, chunk tree, device tree
184        if self.device {
185            println!("ROOT_TREE:");
186            reader::tree_walk(&mut reader, sb.root, traversal, &mut print)?;
187            println!();
188
189            println!("CHUNK_TREE:");
190            reader::tree_walk(
191                &mut reader,
192                sb.chunk_root,
193                traversal,
194                &mut print,
195            )?;
196            println!();
197
198            if let Some(&(root, _)) =
199                tree_roots.get(&ObjectId::DevTree.to_raw())
200            {
201                println!("DEV_TREE:");
202                reader::tree_walk(&mut reader, root, traversal, &mut print)?;
203                println!();
204            }
205            return Ok(());
206        }
207
208        // Default: print all trees
209        println!("root tree");
210        reader::tree_walk(&mut reader, sb.root, traversal, &mut print)?;
211        println!("chunk tree");
212        reader::tree_walk(&mut reader, sb.chunk_root, traversal, &mut print)?;
213
214        let mut sorted_roots: Vec<_> = tree_roots.iter().collect();
215        sorted_roots.sort_by_key(|&(id, _)| *id);
216
217        for &(&tree_id, &(root_bytenr, key_offset)) in &sorted_roots {
218            let label = tree_label(tree_id);
219            let oid = ObjectId::from_raw(tree_id);
220            println!("{label} key ({oid} ROOT_ITEM {key_offset}) ");
221            reader::tree_walk(&mut reader, root_bytenr, traversal, &mut print)?;
222        }
223
224        println!("total bytes {}", sb.total_bytes);
225        println!("bytes used {}", sb.bytes_used);
226        println!("uuid {}", sb.fsid.as_hyphenated());
227
228        Ok(())
229    }
230}
231
232fn tree_label(tree_id: u64) -> &'static str {
233    match ObjectId::from_raw(tree_id) {
234        ObjectId::RootTree => "root tree",
235        ObjectId::ExtentTree => "extent tree",
236        ObjectId::ChunkTree => "chunk tree",
237        ObjectId::DevTree => "device tree",
238        ObjectId::FsTree => "fs tree",
239        ObjectId::CsumTree => "checksum tree",
240        ObjectId::QuotaTree => "quota tree",
241        ObjectId::UuidTree => "uuid tree",
242        ObjectId::FreeSpaceTree => "free space tree",
243        ObjectId::BlockGroupTree => "block group tree",
244        ObjectId::RaidStripeTree => "raid stripe tree",
245        ObjectId::RemapTree => "remap tree",
246        ObjectId::DataRelocTree => "data reloc tree",
247        ObjectId::TreeLog => "log tree",
248        _ => "file tree",
249    }
250}
251
252fn parse_tree_id(name: &str) -> Result<u64> {
253    if let Some(oid) = ObjectId::from_tree_name(name) {
254        Ok(oid.to_raw())
255    } else {
256        bail!("unknown tree name '{name}'");
257    }
258}
259
260fn find_tree_root(
261    tree_id: u64,
262    sb: &Superblock,
263    tree_roots: &BTreeMap<u64, (u64, u64)>,
264) -> Result<u64> {
265    if tree_id == ObjectId::RootTree.to_raw() {
266        return Ok(sb.root);
267    }
268    if tree_id == ObjectId::ChunkTree.to_raw() {
269        return Ok(sb.chunk_root);
270    }
271    if tree_id == ObjectId::TreeLog.to_raw() && sb.log_root != 0 {
272        return Ok(sb.log_root);
273    }
274
275    tree_roots
276        .get(&tree_id)
277        .map(|&(bytenr, _)| bytenr)
278        .ok_or_else(|| {
279            let name = ObjectId::from_raw(tree_id);
280            anyhow::anyhow!("tree {name} (id {tree_id}) not found")
281        })
282}
283
284fn print_roots(sb: &Superblock, tree_roots: &BTreeMap<u64, (u64, u64)>) {
285    println!("root tree bytenr {} level {}", sb.root, sb.root_level);
286    println!(
287        "chunk tree bytenr {} level {}",
288        sb.chunk_root, sb.chunk_root_level
289    );
290    if sb.log_root != 0 {
291        println!(
292            "log tree bytenr {} level {}",
293            sb.log_root, sb.log_root_level
294        );
295    }
296    for (&tree_id, &(bytenr, _)) in tree_roots {
297        let name = ObjectId::from_raw(tree_id);
298        println!("tree {name} (id {tree_id}) bytenr {bytenr}");
299    }
300}
301
302fn print_backup_roots(sb: &Superblock) {
303    for (i, root) in sb.backup_roots.iter().enumerate() {
304        println!("backup {i}:");
305        println!(
306            "\ttree_root {} tree_root_gen {}",
307            root.tree_root, root.tree_root_gen
308        );
309        println!(
310            "\tchunk_root {} chunk_root_gen {}",
311            root.chunk_root, root.chunk_root_gen
312        );
313        println!(
314            "\textent_root {} extent_root_gen {}",
315            root.extent_root, root.extent_root_gen
316        );
317        println!(
318            "\tfs_root {} fs_root_gen {}",
319            root.fs_root, root.fs_root_gen
320        );
321        println!(
322            "\tdev_root {} dev_root_gen {}",
323            root.dev_root, root.dev_root_gen
324        );
325        println!(
326            "\tcsum_root {} csum_root_gen {}",
327            root.csum_root, root.csum_root_gen
328        );
329    }
330}