Skip to main content

btrfs_cli/rescue/
super_recover.rs

1use crate::{RunContext, Runnable, util::is_mounted};
2use anyhow::{Context, Result, bail};
3use btrfs_disk::superblock::{
4    SUPER_MIRROR_MAX, read_superblock_bytes_at, super_mirror_offset,
5    superblock_generation, superblock_is_valid, write_superblock_all_mirrors,
6};
7use clap::Parser;
8use std::{
9    fs::{File, OpenOptions},
10    io::{self, BufRead, Write},
11    path::PathBuf,
12};
13
14/// Recover bad superblocks from good copies
15///
16/// Reads all superblock mirrors and validates their checksums and magic.
17/// If any mirrors are corrupted, the best valid copy (highest generation)
18/// is written back to all mirrors.
19///
20/// The device must not be mounted.
21#[derive(Parser, Debug)]
22pub struct RescueSuperRecoverCommand {
23    /// Path to the device
24    device: PathBuf,
25
26    /// Assume an answer of 'yes' to all questions
27    #[clap(short = 'y', long)]
28    yes: bool,
29}
30
31struct MirrorRecord {
32    bytenr: u64,
33    buf: [u8; 4096],
34    generation: u64,
35}
36
37impl Runnable for RescueSuperRecoverCommand {
38    fn run(&self, _ctx: &RunContext) -> Result<()> {
39        if is_mounted(&self.device) {
40            bail!("{} is currently mounted", self.device.display());
41        }
42
43        let mut file = File::open(&self.device).with_context(|| {
44            format!("failed to open '{}'", self.device.display())
45        })?;
46
47        let mut good: Vec<MirrorRecord> = Vec::new();
48        let mut bad: Vec<MirrorRecord> = Vec::new();
49
50        for i in 0..SUPER_MIRROR_MAX {
51            let bytenr = super_mirror_offset(i);
52            match read_superblock_bytes_at(&mut file, bytenr) {
53                Ok(buf) => {
54                    if superblock_is_valid(&buf) {
55                        let generation = superblock_generation(&buf);
56                        good.push(MirrorRecord {
57                            bytenr,
58                            buf,
59                            generation,
60                        });
61                    } else {
62                        let generation = superblock_generation(&buf);
63                        bad.push(MirrorRecord {
64                            bytenr,
65                            buf,
66                            generation,
67                        });
68                    }
69                }
70                Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {
71                    // Mirror offset is beyond end of device — skip silently.
72                }
73                Err(e) => {
74                    return Err(e).with_context(|| {
75                        format!(
76                            "failed to read mirror {} from '{}'",
77                            i,
78                            self.device.display()
79                        )
80                    });
81                }
82            }
83        }
84
85        // Demote good mirrors with generation below the maximum to bad.
86        if let Some(max_gen) = good.iter().map(|r| r.generation).max() {
87            let (keep, demote): (Vec<_>, Vec<_>) =
88                good.into_iter().partition(|r| r.generation == max_gen);
89            good = keep;
90            bad.extend(demote);
91        }
92
93        println!("[All good supers]:");
94        for r in &good {
95            println!("\t\tdevice name = {}", self.device.display());
96            println!("\t\tsuperblock bytenr = {}", r.bytenr);
97            println!();
98        }
99        println!("[All bad supers]:");
100        for r in &bad {
101            println!("\t\tdevice name = {}", self.device.display());
102            println!("\t\tsuperblock bytenr = {}", r.bytenr);
103            println!();
104        }
105
106        if bad.is_empty() {
107            println!("All superblocks are valid, no need to recover");
108            return Ok(());
109        }
110
111        if good.is_empty() {
112            bail!("no valid superblock found on '{}'", self.device.display());
113        }
114
115        if !self.yes {
116            print!(
117                "Make sure this is a btrfs disk otherwise the tool will destroy other fs, Are you sure? (yes/no): "
118            );
119            io::stdout().flush()?;
120            let stdin = io::stdin();
121            let mut line = String::new();
122            stdin.lock().read_line(&mut line)?;
123            if line.trim() != "yes" {
124                bail!("aborted by user");
125            }
126        }
127
128        // Write the best good mirror to all mirrors.
129        let source = &good[0];
130        let mut file_rw = OpenOptions::new()
131            .read(true)
132            .write(true)
133            .open(&self.device)
134            .with_context(|| {
135                format!(
136                    "failed to open '{}' for writing",
137                    self.device.display()
138                )
139            })?;
140        write_superblock_all_mirrors(&mut file_rw, &source.buf).with_context(
141            || {
142                format!(
143                    "failed to write superblocks to '{}'",
144                    self.device.display()
145                )
146            },
147        )?;
148
149        println!("Recovered bad superblocks successfully");
150        Ok(())
151    }
152}