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#[derive(Parser, Debug)]
19pub struct DumpTreeCommand {
20 path: PathBuf,
22
23 #[clap(short = 'e', long)]
25 extents: bool,
26
27 #[clap(short = 'd', long)]
29 device: bool,
30
31 #[clap(short = 'r', long)]
33 roots: bool,
34
35 #[clap(short = 'R', long)]
37 backups: bool,
38
39 #[clap(short = 'u', long)]
41 uuid: bool,
42
43 #[clap(short = 't', long)]
49 tree: Option<String>,
50
51 #[clap(short = 'b', long, num_args = 1)]
53 block: Vec<u64>,
54
55 #[clap(long)]
57 follow: bool,
58
59 #[clap(long)]
61 bfs: bool,
62
63 #[clap(long)]
65 dfs: bool,
66
67 #[clap(long)]
69 hide_names: bool,
70
71 #[clap(long)]
73 csum_headers: bool,
74
75 #[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 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 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 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 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 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 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 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}