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
80impl Runnable for DumpTreeCommand {
81    fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
82        let file = open_path(&self.path)?;
83
84        let open = reader::filesystem_open(file).with_context(|| {
85            format!(
86                "failed to open btrfs filesystem on '{}'",
87                self.path.display()
88            )
89        })?;
90
91        let traversal = if self.dfs {
92            Traversal::Dfs
93        } else {
94            Traversal::Bfs
95        };
96
97        let csum_size = open.superblock.csum_type.size();
98        let nodesize = open.superblock.nodesize;
99        let opts = PrintOptions {
100            hide_names: self.hide_names,
101            csum_headers: self.csum_headers,
102            csum_items: self.csum_items,
103            csum_size,
104        };
105
106        let mut reader = open.reader;
107        let sb = &open.superblock;
108        let tree_roots = &open.tree_roots;
109        let mut print = |block: &TreeBlock| {
110            print_tree::print_tree_block(block, nodesize, &opts);
111        };
112
113        // --block: print specific blocks
114        if !self.block.is_empty() {
115            for &logical in &self.block {
116                reader::block_visit(
117                    &mut reader,
118                    logical,
119                    self.follow,
120                    traversal,
121                    &mut print,
122                )?;
123            }
124            return Ok(());
125        }
126
127        // --tree: print a specific tree
128        if let Some(ref tree_name) = self.tree {
129            let tree_id = parse_tree_id(tree_name)?;
130            let root_bytenr = find_tree_root(tree_id, sb, tree_roots)?;
131            reader::tree_walk(&mut reader, root_bytenr, traversal, &mut print)?;
132            return Ok(());
133        }
134
135        // --roots / --backups: print short root info
136        if self.roots || self.backups {
137            print_roots(sb, tree_roots);
138            if self.backups {
139                print_backup_roots(sb);
140            }
141            return Ok(());
142        }
143
144        // --uuid: print only UUID tree
145        if self.uuid {
146            if let Some(&(root, _)) =
147                tree_roots.get(&ObjectId::UuidTree.to_raw())
148            {
149                reader::tree_walk(&mut reader, root, traversal, &mut print)?;
150            } else {
151                bail!("UUID tree not found");
152            }
153            return Ok(());
154        }
155
156        // --extents: extent and device trees
157        if self.extents {
158            for &tree_id in
159                &[ObjectId::ExtentTree.to_raw(), ObjectId::DevTree.to_raw()]
160            {
161                if let Some(&(root, _)) = tree_roots.get(&tree_id) {
162                    let name = ObjectId::from_raw(tree_id);
163                    println!("{name}:");
164                    reader::tree_walk(
165                        &mut reader,
166                        root,
167                        traversal,
168                        &mut print,
169                    )?;
170                    println!();
171                }
172            }
173            return Ok(());
174        }
175
176        // --device: root tree, chunk tree, device tree
177        if self.device {
178            println!("ROOT_TREE:");
179            reader::tree_walk(&mut reader, sb.root, traversal, &mut print)?;
180            println!();
181
182            println!("CHUNK_TREE:");
183            reader::tree_walk(
184                &mut reader,
185                sb.chunk_root,
186                traversal,
187                &mut print,
188            )?;
189            println!();
190
191            if let Some(&(root, _)) =
192                tree_roots.get(&ObjectId::DevTree.to_raw())
193            {
194                println!("DEV_TREE:");
195                reader::tree_walk(&mut reader, root, traversal, &mut print)?;
196                println!();
197            }
198            return Ok(());
199        }
200
201        // Default: print all trees
202        println!("root tree");
203        reader::tree_walk(&mut reader, sb.root, traversal, &mut print)?;
204        println!("chunk tree");
205        reader::tree_walk(&mut reader, sb.chunk_root, traversal, &mut print)?;
206
207        let mut sorted_roots: Vec<_> = tree_roots.iter().collect();
208        sorted_roots.sort_by_key(|&(id, _)| *id);
209
210        for &(&tree_id, &(root_bytenr, key_offset)) in &sorted_roots {
211            let label = tree_label(tree_id);
212            let oid = ObjectId::from_raw(tree_id);
213            println!("{label} key ({oid} ROOT_ITEM {key_offset}) ");
214            reader::tree_walk(&mut reader, root_bytenr, traversal, &mut print)?;
215        }
216
217        println!("total bytes {}", sb.total_bytes);
218        println!("bytes used {}", sb.bytes_used);
219        println!("uuid {}", sb.fsid.as_hyphenated());
220
221        Ok(())
222    }
223}
224
225fn tree_label(tree_id: u64) -> &'static str {
226    match ObjectId::from_raw(tree_id) {
227        ObjectId::RootTree => "root tree",
228        ObjectId::ExtentTree => "extent tree",
229        ObjectId::ChunkTree => "chunk tree",
230        ObjectId::DevTree => "device tree",
231        ObjectId::FsTree => "fs tree",
232        ObjectId::CsumTree => "checksum tree",
233        ObjectId::QuotaTree => "quota tree",
234        ObjectId::UuidTree => "uuid tree",
235        ObjectId::FreeSpaceTree => "free space tree",
236        ObjectId::BlockGroupTree => "block group tree",
237        ObjectId::RaidStripeTree => "raid stripe tree",
238        ObjectId::RemapTree => "remap tree",
239        ObjectId::DataRelocTree => "data reloc tree",
240        ObjectId::TreeLog => "log tree",
241        _ => "file tree",
242    }
243}
244
245fn parse_tree_id(name: &str) -> Result<u64> {
246    if let Some(oid) = ObjectId::from_tree_name(name) {
247        Ok(oid.to_raw())
248    } else {
249        bail!("unknown tree name '{name}'");
250    }
251}
252
253fn find_tree_root(
254    tree_id: u64,
255    sb: &Superblock,
256    tree_roots: &BTreeMap<u64, (u64, u64)>,
257) -> Result<u64> {
258    if tree_id == ObjectId::RootTree.to_raw() {
259        return Ok(sb.root);
260    }
261    if tree_id == ObjectId::ChunkTree.to_raw() {
262        return Ok(sb.chunk_root);
263    }
264    if tree_id == ObjectId::TreeLog.to_raw() && sb.log_root != 0 {
265        return Ok(sb.log_root);
266    }
267
268    tree_roots
269        .get(&tree_id)
270        .map(|&(bytenr, _)| bytenr)
271        .ok_or_else(|| {
272            let name = ObjectId::from_raw(tree_id);
273            anyhow::anyhow!("tree {name} (id {tree_id}) not found")
274        })
275}
276
277fn print_roots(sb: &Superblock, tree_roots: &BTreeMap<u64, (u64, u64)>) {
278    println!("root tree bytenr {} level {}", sb.root, sb.root_level);
279    println!(
280        "chunk tree bytenr {} level {}",
281        sb.chunk_root, sb.chunk_root_level
282    );
283    if sb.log_root != 0 {
284        println!(
285            "log tree bytenr {} level {}",
286            sb.log_root, sb.log_root_level
287        );
288    }
289    for (&tree_id, &(bytenr, _)) in tree_roots {
290        let name = ObjectId::from_raw(tree_id);
291        println!("tree {name} (id {tree_id}) bytenr {bytenr}");
292    }
293}
294
295fn print_backup_roots(sb: &Superblock) {
296    for (i, root) in sb.backup_roots.iter().enumerate() {
297        println!("backup {i}:");
298        println!(
299            "\ttree_root {} tree_root_gen {}",
300            root.tree_root, root.tree_root_gen
301        );
302        println!(
303            "\tchunk_root {} chunk_root_gen {}",
304            root.chunk_root, root.chunk_root_gen
305        );
306        println!(
307            "\textent_root {} extent_root_gen {}",
308            root.extent_root, root.extent_root_gen
309        );
310        println!(
311            "\tfs_root {} fs_root_gen {}",
312            root.fs_root, root.fs_root_gen
313        );
314        println!(
315            "\tdev_root {} dev_root_gen {}",
316            root.dev_root, root.dev_root_gen
317        );
318        println!(
319            "\tcsum_root {} csum_root_gen {}",
320            root.csum_root, root.csum_root_gen
321        );
322    }
323}