use crate::{RunContext, Runnable, util::is_mounted};
use anyhow::{Context, Result, bail};
use btrfs_disk::tree::{DiskKey, KeyType};
use btrfs_transaction::{
filesystem::Filesystem,
items,
path::BtrfsPath,
search::{self, SearchIntent},
transaction::Transaction,
};
use clap::Parser;
use std::{
fs::OpenOptions,
io::{Read, Seek, Write},
path::PathBuf,
};
const UUID_TREE_OBJECTID: u64 =
btrfs_disk::raw::BTRFS_UUID_TREE_OBJECTID as u64;
const ROOT_TREE_OBJECTID: u64 = 1;
#[derive(Parser, Debug)]
pub struct RescueClearUuidTreeCommand {
device: PathBuf,
}
fn collect_tree_blocks<R: Read + Write + Seek>(
fs: &mut Filesystem<R>,
bytenr: u64,
out: &mut Vec<(u64, u8)>,
) -> Result<()> {
let eb = fs
.read_block(bytenr)
.with_context(|| format!("failed to read tree block at {bytenr}"))?;
let level = eb.level();
out.push((bytenr, level));
if eb.is_node() {
let nritems = eb.nritems() as usize;
for slot in 0..nritems {
let child = eb.key_ptr_blockptr(slot);
collect_tree_blocks(fs, child, out)?;
}
}
Ok(())
}
impl Runnable for RescueClearUuidTreeCommand {
fn run(&self, _ctx: &RunContext) -> Result<()> {
if is_mounted(&self.device) {
bail!("{} is currently mounted", self.device.display());
}
let file = OpenOptions::new()
.read(true)
.write(true)
.open(&self.device)
.with_context(|| {
format!("failed to open '{}'", self.device.display())
})?;
let mut fs = Filesystem::open(file).with_context(|| {
format!("failed to open filesystem on '{}'", self.device.display())
})?;
let Some(uuid_root_bytenr) = fs.root_bytenr(UUID_TREE_OBJECTID) else {
println!("no uuid tree found, nothing to do");
return Ok(());
};
let mut tree_blocks = Vec::new();
collect_tree_blocks(&mut fs, uuid_root_bytenr, &mut tree_blocks)
.context("failed to walk uuid tree")?;
let mut trans = Transaction::start(&mut fs)
.context("failed to start transaction")?;
for &(bytenr, level) in &tree_blocks {
trans.delayed_refs.drop_ref(
bytenr,
true,
UUID_TREE_OBJECTID,
level,
);
trans.pin_block(bytenr);
fs.evict_block(bytenr);
}
let root_key = DiskKey {
objectid: UUID_TREE_OBJECTID,
key_type: KeyType::RootItem,
offset: 0,
};
let mut path = BtrfsPath::new();
let found = search::search_slot(
Some(&mut trans),
&mut fs,
ROOT_TREE_OBJECTID,
&root_key,
&mut path,
SearchIntent::Delete,
true,
)
.context("failed to search root tree for uuid tree entry")?;
if found {
let leaf = path.nodes[0].as_mut().ok_or_else(|| {
anyhow::anyhow!("no leaf in path for root tree deletion")
})?;
items::del_items(leaf, path.slots[0], 1);
fs.mark_dirty(leaf);
}
path.release();
fs.remove_root(UUID_TREE_OBJECTID);
trans
.commit(&mut fs)
.context("failed to commit transaction")?;
fs.sync().context("failed to sync to disk")?;
println!(
"Cleared uuid tree on {} ({} blocks freed), kernel will rebuild it on next mount",
self.device.display(),
tree_blocks.len()
);
Ok(())
}
}