btrfs_cli/rescue/
clear_uuid_tree.rs1use crate::{RunContext, Runnable, util::is_mounted};
2use anyhow::{Context, Result, bail};
3use btrfs_disk::tree::{DiskKey, KeyType};
4use btrfs_transaction::{
5 filesystem::Filesystem,
6 items,
7 path::BtrfsPath,
8 search::{self, SearchIntent},
9 transaction::Transaction,
10};
11use clap::Parser;
12use std::{
13 fs::OpenOptions,
14 io::{Read, Seek, Write},
15 path::PathBuf,
16};
17
18const UUID_TREE_OBJECTID: u64 =
20 btrfs_disk::raw::BTRFS_UUID_TREE_OBJECTID as u64;
21
22const ROOT_TREE_OBJECTID: u64 = 1;
24
25#[derive(Parser, Debug)]
34pub struct RescueClearUuidTreeCommand {
35 device: PathBuf,
37}
38
39fn collect_tree_blocks<R: Read + Write + Seek>(
42 fs: &mut Filesystem<R>,
43 bytenr: u64,
44 out: &mut Vec<(u64, u8)>,
45) -> Result<()> {
46 let eb = fs
47 .read_block(bytenr)
48 .with_context(|| format!("failed to read tree block at {bytenr}"))?;
49 let level = eb.level();
50 out.push((bytenr, level));
51
52 if eb.is_node() {
53 let nritems = eb.nritems() as usize;
54 for slot in 0..nritems {
55 let child = eb.key_ptr_blockptr(slot);
56 collect_tree_blocks(fs, child, out)?;
57 }
58 }
59 Ok(())
60}
61
62impl Runnable for RescueClearUuidTreeCommand {
63 fn run(&self, _ctx: &RunContext) -> Result<()> {
64 if is_mounted(&self.device) {
65 bail!("{} is currently mounted", self.device.display());
66 }
67
68 let file = OpenOptions::new()
69 .read(true)
70 .write(true)
71 .open(&self.device)
72 .with_context(|| {
73 format!("failed to open '{}'", self.device.display())
74 })?;
75
76 let mut fs = Filesystem::open(file).with_context(|| {
77 format!("failed to open filesystem on '{}'", self.device.display())
78 })?;
79
80 let Some(uuid_root_bytenr) = fs.root_bytenr(UUID_TREE_OBJECTID) else {
81 println!("no uuid tree found, nothing to do");
82 return Ok(());
83 };
84
85 let mut tree_blocks = Vec::new();
90 collect_tree_blocks(&mut fs, uuid_root_bytenr, &mut tree_blocks)
91 .context("failed to walk uuid tree")?;
92
93 let mut trans = Transaction::start(&mut fs)
94 .context("failed to start transaction")?;
95
96 for &(bytenr, level) in &tree_blocks {
100 trans.delayed_refs.drop_ref(
101 bytenr,
102 true,
103 UUID_TREE_OBJECTID,
104 level,
105 );
106 trans.pin_block(bytenr);
107 fs.evict_block(bytenr);
108 }
109
110 let root_key = DiskKey {
112 objectid: UUID_TREE_OBJECTID,
113 key_type: KeyType::RootItem,
114 offset: 0,
115 };
116 let mut path = BtrfsPath::new();
117 let found = search::search_slot(
118 Some(&mut trans),
119 &mut fs,
120 ROOT_TREE_OBJECTID,
121 &root_key,
122 &mut path,
123 SearchIntent::Delete,
124 true,
125 )
126 .context("failed to search root tree for uuid tree entry")?;
127
128 if found {
129 let leaf = path.nodes[0].as_mut().ok_or_else(|| {
130 anyhow::anyhow!("no leaf in path for root tree deletion")
131 })?;
132 items::del_items(leaf, path.slots[0], 1);
133 fs.mark_dirty(leaf);
134 }
135 path.release();
136
137 fs.remove_root(UUID_TREE_OBJECTID);
140
141 trans
142 .commit(&mut fs)
143 .context("failed to commit transaction")?;
144 fs.sync().context("failed to sync to disk")?;
145
146 println!(
147 "Cleared uuid tree on {} ({} blocks freed), kernel will rebuild it on next mount",
148 self.device.display(),
149 tree_blocks.len()
150 );
151 Ok(())
152 }
153}