btrfs_cli/rescue/
chunk_recover.rs1use crate::{RunContext, Runnable};
2use anyhow::{Context, Result, bail};
3use btrfs_disk::items::{ChunkItem, DeviceItem};
4use clap::Parser;
5use std::{
6 fs::{File, OpenOptions},
7 path::PathBuf,
8};
9use uuid::Uuid;
10
11mod apply;
12mod reconstruct;
13mod report;
14mod scan;
15
16#[derive(Parser, Debug)]
26pub struct RescueChunkRecoverCommand {
27 #[clap(short = 'y', long)]
29 pub yes: bool,
30
31 #[clap(long)]
33 pub apply: bool,
34
35 pub device: PathBuf,
37}
38
39impl Runnable for RescueChunkRecoverCommand {
40 fn run(&self, _ctx: &RunContext) -> Result<()> {
41 if crate::util::is_mounted(&self.device) {
42 bail!("{} is currently mounted", self.device.display());
43 }
44
45 let mut file = File::open(&self.device).with_context(|| {
46 format!("failed to open '{}'", self.device.display())
47 })?;
48
49 eprintln!("Phase 1: scanning device for chunk tree data...");
50 let scan_result = scan::scan_device(&mut file)?;
51
52 eprintln!("Phase 2: reconstructing chunk tree...");
53 let reconstruction = reconstruct::reconstruct(&scan_result)?;
54
55 report::print_report(&self.device, &scan_result, &reconstruction);
56
57 if !reconstruction.chunk_root_covered {
58 bail!(
59 "chunk root logical address {:#x} is not covered by any recovered chunk",
60 scan_result.chunk_root,
61 );
62 }
63
64 if !self.apply {
65 println!(
66 "\nDry run complete. Use --apply to write the reconstructed chunk tree.",
67 );
68 return Ok(());
69 }
70
71 if !self.yes {
72 eprint!(
73 "\nAbout to write reconstructed chunk tree ({} chunks, {} devices). Continue? [y/N] ",
74 reconstruction.chunks.len(),
75 reconstruction.devices.len(),
76 );
77 let mut answer = String::new();
78 std::io::stdin().read_line(&mut answer)?;
79 if !answer.trim().eq_ignore_ascii_case("y") {
80 bail!("aborted by user");
81 }
82 }
83
84 drop(file);
86 let file = OpenOptions::new()
87 .read(true)
88 .write(true)
89 .open(&self.device)
90 .with_context(|| {
91 format!(
92 "failed to open '{}' for writing",
93 self.device.display()
94 )
95 })?;
96
97 eprintln!("Phase 4: writing reconstructed chunk tree...");
98 apply::apply_chunk_tree(file, &scan_result, &reconstruction)?;
99
100 println!("Chunk tree successfully written.");
101 Ok(())
102 }
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub enum RecordSource {
110 Bootstrap,
112 ScannedLeaf {
114 bytenr: u64,
116 generation: u64,
118 },
119}
120
121impl RecordSource {
122 fn is_bootstrap(self) -> bool {
123 matches!(self, Self::Bootstrap)
124 }
125}
126
127#[derive(Debug, Clone)]
129pub struct ChunkRecord {
130 pub logical: u64,
132 pub chunk: ChunkItem,
134 pub source: RecordSource,
136 pub generation: u64,
138}
139
140#[derive(Debug, Clone)]
142pub struct DevRecord {
143 pub devid: u64,
145 pub device: DeviceItem,
147 pub source: RecordSource,
149 pub generation: u64,
151}
152
153#[derive(Debug)]
155pub enum Conflict {
156 DevItem {
158 devid: u64,
159 winner_gen: u64,
160 loser_gen: u64,
161 },
162 ChunkItem {
164 logical: u64,
165 winner_gen: u64,
166 loser_gen: u64,
167 bootstrap_won: bool,
168 },
169}
170
171#[derive(Debug)]
173pub enum Warning {
174 DanglingStripeRef { logical: u64, devid: u64 },
176}
177
178pub struct ScanResult {
180 pub fsid: Uuid,
181 pub metadata_uuid: Uuid,
182 pub has_metadata_uuid: bool,
183 pub nodesize: u32,
184 pub chunk_root: u64,
185 pub chunk_root_level: u8,
186 pub sb_generation: u64,
187 pub device_size: u64,
188 pub bytes_scanned: u64,
189 pub candidates_checked: u64,
190 pub valid_blocks: u64,
191 pub chunk_tree_leaves: u64,
192 pub chunk_records: Vec<ChunkRecord>,
193 pub dev_records: Vec<DevRecord>,
194}
195
196pub struct ReconstructionResult {
198 pub chunks: Vec<ChunkRecord>,
200 pub devices: Vec<DevRecord>,
202 pub conflicts: Vec<Conflict>,
204 pub warnings: Vec<Warning>,
206 pub chunk_root_covered: bool,
208}