use super::print_tree::{self, PrintOptions};
use crate::{RunContext, Runnable, util::open_path};
use anyhow::{Context, Result, bail};
use btrfs_disk::{
reader::{self, Traversal},
superblock::Superblock,
tree::{ObjectId, TreeBlock},
};
use clap::Parser;
use std::{collections::BTreeMap, path::PathBuf};
#[derive(Parser, Debug)]
#[allow(clippy::struct_excessive_bools)]
pub struct DumpTreeCommand {
path: PathBuf,
#[clap(short = 'e', long)]
extents: bool,
#[clap(short = 'd', long)]
device: bool,
#[clap(short = 'r', long)]
roots: bool,
#[clap(short = 'R', long)]
backups: bool,
#[clap(short = 'u', long)]
uuid: bool,
#[clap(short = 't', long)]
tree: Option<String>,
#[clap(short = 'b', long, num_args = 1)]
block: Vec<u64>,
#[clap(long)]
follow: bool,
#[clap(long)]
bfs: bool,
#[clap(long)]
dfs: bool,
#[clap(long)]
hide_names: bool,
#[clap(long)]
csum_headers: bool,
#[clap(long)]
csum_items: bool,
#[clap(long)]
noscan: bool,
}
impl Runnable for DumpTreeCommand {
#[allow(clippy::too_many_lines)]
fn run(&self, _ctx: &RunContext) -> Result<()> {
let file = open_path(&self.path)?;
let open = reader::filesystem_open(file).with_context(|| {
format!(
"failed to open btrfs filesystem on '{}'",
self.path.display()
)
})?;
let traversal = if self.dfs {
Traversal::Dfs
} else {
Traversal::Bfs
};
let csum_size = open.superblock.csum_type.size();
let nodesize = open.superblock.nodesize;
let opts = PrintOptions {
hide_names: self.hide_names,
csum_headers: self.csum_headers,
csum_items: self.csum_items,
csum_size,
};
let mut reader = open.reader;
let sb = &open.superblock;
let tree_roots = &open.tree_roots;
let mut print = |block: &TreeBlock| {
print_tree::print_tree_block(block, nodesize, &opts);
};
if !self.block.is_empty() {
for &logical in &self.block {
reader::block_visit(
&mut reader,
logical,
self.follow,
traversal,
&mut print,
)?;
}
return Ok(());
}
if let Some(ref tree_name) = self.tree {
let tree_id = parse_tree_id(tree_name)?;
let root_bytenr = find_tree_root(tree_id, sb, tree_roots)?;
reader::tree_walk(&mut reader, root_bytenr, traversal, &mut print)?;
return Ok(());
}
if self.roots || self.backups {
print_roots(sb, tree_roots);
if self.backups {
print_backup_roots(sb);
}
return Ok(());
}
if self.uuid {
if let Some(&(root, _)) =
tree_roots.get(&ObjectId::UuidTree.to_raw())
{
reader::tree_walk(&mut reader, root, traversal, &mut print)?;
} else {
bail!("UUID tree not found");
}
return Ok(());
}
if self.extents {
for &tree_id in
&[ObjectId::ExtentTree.to_raw(), ObjectId::DevTree.to_raw()]
{
if let Some(&(root, _)) = tree_roots.get(&tree_id) {
let name = ObjectId::from_raw(tree_id);
println!("{name}:");
reader::tree_walk(
&mut reader,
root,
traversal,
&mut print,
)?;
println!();
}
}
return Ok(());
}
if self.device {
println!("ROOT_TREE:");
reader::tree_walk(&mut reader, sb.root, traversal, &mut print)?;
println!();
println!("CHUNK_TREE:");
reader::tree_walk(
&mut reader,
sb.chunk_root,
traversal,
&mut print,
)?;
println!();
if let Some(&(root, _)) =
tree_roots.get(&ObjectId::DevTree.to_raw())
{
println!("DEV_TREE:");
reader::tree_walk(&mut reader, root, traversal, &mut print)?;
println!();
}
return Ok(());
}
println!("root tree");
reader::tree_walk(&mut reader, sb.root, traversal, &mut print)?;
println!("chunk tree");
reader::tree_walk(&mut reader, sb.chunk_root, traversal, &mut print)?;
let mut sorted_roots: Vec<_> = tree_roots.iter().collect();
sorted_roots.sort_by_key(|&(id, _)| *id);
for &(&tree_id, &(root_bytenr, key_offset)) in &sorted_roots {
let label = tree_label(tree_id);
let oid = ObjectId::from_raw(tree_id);
println!("{label} key ({oid} ROOT_ITEM {key_offset}) ");
reader::tree_walk(&mut reader, root_bytenr, traversal, &mut print)?;
}
println!("total bytes {}", sb.total_bytes);
println!("bytes used {}", sb.bytes_used);
println!("uuid {}", sb.fsid.as_hyphenated());
Ok(())
}
}
fn tree_label(tree_id: u64) -> &'static str {
match ObjectId::from_raw(tree_id) {
ObjectId::RootTree => "root tree",
ObjectId::ExtentTree => "extent tree",
ObjectId::ChunkTree => "chunk tree",
ObjectId::DevTree => "device tree",
ObjectId::FsTree => "fs tree",
ObjectId::CsumTree => "checksum tree",
ObjectId::QuotaTree => "quota tree",
ObjectId::UuidTree => "uuid tree",
ObjectId::FreeSpaceTree => "free space tree",
ObjectId::BlockGroupTree => "block group tree",
ObjectId::RaidStripeTree => "raid stripe tree",
ObjectId::RemapTree => "remap tree",
ObjectId::DataRelocTree => "data reloc tree",
ObjectId::TreeLog => "log tree",
_ => "file tree",
}
}
fn parse_tree_id(name: &str) -> Result<u64> {
if let Some(oid) = ObjectId::from_tree_name(name) {
Ok(oid.to_raw())
} else {
bail!("unknown tree name '{name}'");
}
}
fn find_tree_root(
tree_id: u64,
sb: &Superblock,
tree_roots: &BTreeMap<u64, (u64, u64)>,
) -> Result<u64> {
if tree_id == ObjectId::RootTree.to_raw() {
return Ok(sb.root);
}
if tree_id == ObjectId::ChunkTree.to_raw() {
return Ok(sb.chunk_root);
}
if tree_id == ObjectId::TreeLog.to_raw() && sb.log_root != 0 {
return Ok(sb.log_root);
}
tree_roots
.get(&tree_id)
.map(|&(bytenr, _)| bytenr)
.ok_or_else(|| {
let name = ObjectId::from_raw(tree_id);
anyhow::anyhow!("tree {name} (id {tree_id}) not found")
})
}
fn print_roots(sb: &Superblock, tree_roots: &BTreeMap<u64, (u64, u64)>) {
println!("root tree bytenr {} level {}", sb.root, sb.root_level);
println!(
"chunk tree bytenr {} level {}",
sb.chunk_root, sb.chunk_root_level
);
if sb.log_root != 0 {
println!(
"log tree bytenr {} level {}",
sb.log_root, sb.log_root_level
);
}
for (&tree_id, &(bytenr, _)) in tree_roots {
let name = ObjectId::from_raw(tree_id);
println!("tree {name} (id {tree_id}) bytenr {bytenr}");
}
}
fn print_backup_roots(sb: &Superblock) {
for (i, root) in sb.backup_roots.iter().enumerate() {
println!("backup {i}:");
println!(
"\ttree_root {} tree_root_gen {}",
root.tree_root, root.tree_root_gen
);
println!(
"\tchunk_root {} chunk_root_gen {}",
root.chunk_root, root.chunk_root_gen
);
println!(
"\textent_root {} extent_root_gen {}",
root.extent_root, root.extent_root_gen
);
println!(
"\tfs_root {} fs_root_gen {}",
root.fs_root, root.fs_root_gen
);
println!(
"\tdev_root {} dev_root_gen {}",
root.dev_root, root.dev_root_gen
);
println!(
"\tcsum_root {} csum_root_gen {}",
root.csum_root, root.csum_root_gen
);
}
}