Skip to main content

btrfs_cli/rescue/
clear_uuid_tree.rs

1use 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
18/// UUID tree object ID (tree 9).
19const UUID_TREE_OBJECTID: u64 =
20    btrfs_disk::raw::BTRFS_UUID_TREE_OBJECTID as u64;
21
22/// Root tree ID.
23const ROOT_TREE_OBJECTID: u64 = 1;
24
25/// Delete uuid tree so that kernel can rebuild it at mount time
26///
27/// The UUID tree maps subvolume UUIDs to their root IDs. If it becomes
28/// corrupted it can prevent mount or cause subvolume lookup failures.
29/// Deleting it is safe because the kernel rebuilds it automatically on
30/// the next mount.
31///
32/// The device must not be mounted.
33#[derive(Parser, Debug)]
34pub struct RescueClearUuidTreeCommand {
35    /// Path to the btrfs device
36    device: PathBuf,
37}
38
39/// Recursively walk a tree starting at `bytenr`, collecting every block
40/// address (root, internal nodes, leaves).
41fn 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        // Walk the UUID tree to collect every block address (and level).
86        // We don't need to delete items: we just need to drop the extent
87        // refs for every block and remove the ROOT_ITEM. The kernel will
88        // rebuild the tree on next mount.
89        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        // Queue a delayed ref drop for every block in the UUID tree, and
97        // pin them so the allocator doesn't reuse the addresses before
98        // commit.
99        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        // Delete the ROOT_ITEM for the UUID tree from the root tree.
111        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        // Remove the UUID tree from the in-memory roots map so commit
138        // doesn't try to update a ROOT_ITEM we just deleted.
139        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}