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#[derive(Parser, Debug)]
19#[allow(clippy::struct_excessive_bools)]
20pub struct DumpTreeCommand {
21 path: PathBuf,
23
24 #[clap(short = 'e', long)]
26 extents: bool,
27
28 #[clap(short = 'd', long)]
30 device: bool,
31
32 #[clap(short = 'r', long)]
34 roots: bool,
35
36 #[clap(short = 'R', long)]
38 backups: bool,
39
40 #[clap(short = 'u', long)]
42 uuid: bool,
43
44 #[clap(short = 't', long)]
50 tree: Option<String>,
51
52 #[clap(short = 'b', long, num_args = 1)]
54 block: Vec<u64>,
55
56 #[clap(long)]
58 follow: bool,
59
60 #[clap(long)]
62 bfs: bool,
63
64 #[clap(long)]
66 dfs: bool,
67
68 #[clap(long)]
70 hide_names: bool,
71
72 #[clap(long)]
74 csum_headers: bool,
75
76 #[clap(long)]
78 csum_items: bool,
79
80 #[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 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 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 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 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 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 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 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}