use fs_btrfs::btree::header_offsets as o;
use fs_btrfs::chunk::{key_type, DiskKey};
use fs_btrfs::fs::Filesystem;
use fs_core::FileDevice;
use std::collections::{BTreeMap, BTreeSet};
use std::sync::Arc;
fn le64(b: &[u8], at: usize) -> u64 {
u64::from_le_bytes(b[at..at + 8].try_into().unwrap())
}
fn tree_name(owner: u64) -> &'static str {
match owner {
1 => "root",
2 => "extent",
3 => "chunk",
4 => "dev",
5 => "fs",
6 => "csum",
7 => "quota",
8 => "uuid",
9 => "free-space",
10 => "free-space-tree",
_ => "other",
}
}
type BlockId = (u64, u64);
type BlockKind = (u64, u8);
type ItemKey = (u64, u8, u64);
fn blocks(path: &str) -> (Filesystem, BTreeMap<BlockId, BlockKind>) {
let dev = Arc::new(FileDevice::open(path).unwrap());
let fs = Filesystem::mount(dev).unwrap();
let sb = fs.superblock().clone();
let bytes = std::fs::read(path).unwrap();
let n = sb.nodesize as usize;
let mut out = BTreeMap::new();
let mut at = 0usize;
while at + n <= bytes.len() {
let b = &bytes[at..at + n];
at += n;
if b[o::FSID..o::FSID + 16] != sb.fsid[..] {
continue;
}
if !sb.csum_type.verify(&b[32..], &b[..32]) {
continue;
}
out.insert(
(le64(b, o::BYTENR), le64(b, o::GENERATION)),
(le64(b, o::OWNER), b[o::LEVEL]),
);
}
(fs, out)
}
fn extent_items(fs: &Filesystem) -> BTreeMap<ItemKey, Vec<u8>> {
let mut out = BTreeMap::new();
fs.for_each_extent_item(&mut |k: &DiskKey, d: &[u8]| {
out.insert((k.objectid, k.key_type, k.offset), d.to_vec());
})
.unwrap();
out
}
fn main() {
let a = std::env::args().nth(1).unwrap();
let b = std::env::args().nth(2).unwrap();
let (fa, ba) = blocks(&a);
let (fb, bb) = blocks(&b);
println!(
"generation {} -> {}",
fa.superblock().generation,
fb.superblock().generation
);
println!(
"root {} -> {}",
fa.superblock().root,
fb.superblock().root
);
println!(
"bytes_used {} -> {}",
fa.superblock().bytes_used,
fb.superblock().bytes_used
);
let keys_a: BTreeSet<_> = ba.keys().copied().collect();
let keys_b: BTreeSet<_> = bb.keys().copied().collect();
println!("\n--- tree blocks the commit WROTE (present after, not before) ---");
let mut by_tree: BTreeMap<&str, Vec<(u64, u64, u8)>> = BTreeMap::new();
for k in keys_b.difference(&keys_a) {
let (owner, level) = bb[k];
by_tree
.entry(tree_name(owner))
.or_default()
.push((k.0, k.1, level));
}
for (t, mut v) in by_tree {
v.sort();
println!(" {t:<16} {} block(s)", v.len());
for (bytenr, gen, level) in v {
println!(" bytenr {bytenr:<12} gen {gen:<4} level {level}");
}
}
let ea = extent_items(&fa);
let eb = extent_items(&fb);
let ka: BTreeSet<_> = ea.keys().copied().collect();
let kb: BTreeSet<_> = eb.keys().copied().collect();
let name = |t: u8| match t {
key_type::EXTENT_ITEM => "EXTENT_ITEM",
key_type::METADATA_ITEM => "METADATA_ITEM",
key_type::BLOCK_GROUP_ITEM => "BLOCK_GROUP_ITEM",
176 => "TREE_BLOCK_REF",
178 => "EXTENT_DATA_REF",
182 => "SHARED_BLOCK_REF",
184 => "SHARED_DATA_REF",
_ => "?",
};
println!("\n--- extent tree items ADDED ---");
for k in kb.difference(&ka) {
println!(" {:<12} {:<18} offset {}", k.0, name(k.1), k.2);
}
println!("--- extent tree items REMOVED ---");
for k in ka.difference(&kb) {
println!(" {:<12} {:<18} offset {}", k.0, name(k.1), k.2);
}
println!("--- extent tree items CHANGED ---");
for k in ka.intersection(&kb) {
if ea[k] != eb[k] {
println!(" {:<12} {:<18} offset {}", k.0, name(k.1), k.2);
if k.1 == key_type::BLOCK_GROUP_ITEM {
println!(" used {} -> {}", le64(&ea[k], 0), le64(&eb[k], 0));
}
}
}
}