Skip to main content

btrfs_cli/inspect/
dump_tree.rs

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