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 #[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 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 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 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 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 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 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 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}