use crate::{RunContext, Runnable};
use anyhow::{Context, Result, bail};
use btrfs_disk::items::{ChunkItem, DeviceItem};
use clap::Parser;
use std::{
fs::{File, OpenOptions},
path::PathBuf,
};
use uuid::Uuid;
mod apply;
mod reconstruct;
mod report;
mod scan;
#[derive(Parser, Debug)]
pub struct RescueChunkRecoverCommand {
#[clap(short = 'y', long)]
pub yes: bool,
#[clap(long)]
pub apply: bool,
pub device: PathBuf,
}
impl Runnable for RescueChunkRecoverCommand {
fn run(&self, _ctx: &RunContext) -> Result<()> {
if crate::util::is_mounted(&self.device) {
bail!("{} is currently mounted", self.device.display());
}
let mut file = File::open(&self.device).with_context(|| {
format!("failed to open '{}'", self.device.display())
})?;
eprintln!("Phase 1: scanning device for chunk tree data...");
let scan_result = scan::scan_device(&mut file)?;
eprintln!("Phase 2: reconstructing chunk tree...");
let reconstruction = reconstruct::reconstruct(&scan_result)?;
report::print_report(&self.device, &scan_result, &reconstruction);
if !reconstruction.chunk_root_covered {
bail!(
"chunk root logical address {:#x} is not covered by any recovered chunk",
scan_result.chunk_root,
);
}
if !self.apply {
println!(
"\nDry run complete. Use --apply to write the reconstructed chunk tree.",
);
return Ok(());
}
if !self.yes {
eprint!(
"\nAbout to write reconstructed chunk tree ({} chunks, {} devices). Continue? [y/N] ",
reconstruction.chunks.len(),
reconstruction.devices.len(),
);
let mut answer = String::new();
std::io::stdin().read_line(&mut answer)?;
if !answer.trim().eq_ignore_ascii_case("y") {
bail!("aborted by user");
}
}
drop(file);
let file = OpenOptions::new()
.read(true)
.write(true)
.open(&self.device)
.with_context(|| {
format!(
"failed to open '{}' for writing",
self.device.display()
)
})?;
eprintln!("Phase 4: writing reconstructed chunk tree...");
apply::apply_chunk_tree(file, &scan_result, &reconstruction)?;
println!("Chunk tree successfully written.");
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RecordSource {
Bootstrap,
ScannedLeaf {
bytenr: u64,
generation: u64,
},
}
impl RecordSource {
fn is_bootstrap(self) -> bool {
matches!(self, Self::Bootstrap)
}
}
#[derive(Debug, Clone)]
pub struct ChunkRecord {
pub logical: u64,
pub chunk: ChunkItem,
pub source: RecordSource,
pub generation: u64,
}
#[derive(Debug, Clone)]
pub struct DevRecord {
pub devid: u64,
pub device: DeviceItem,
pub source: RecordSource,
pub generation: u64,
}
#[derive(Debug)]
pub enum Conflict {
DevItem {
devid: u64,
winner_gen: u64,
loser_gen: u64,
},
ChunkItem {
logical: u64,
winner_gen: u64,
loser_gen: u64,
bootstrap_won: bool,
},
}
#[derive(Debug)]
pub enum Warning {
DanglingStripeRef { logical: u64, devid: u64 },
}
pub struct ScanResult {
pub fsid: Uuid,
pub metadata_uuid: Uuid,
pub has_metadata_uuid: bool,
pub nodesize: u32,
pub chunk_root: u64,
pub chunk_root_level: u8,
pub sb_generation: u64,
pub device_size: u64,
pub bytes_scanned: u64,
pub candidates_checked: u64,
pub valid_blocks: u64,
pub chunk_tree_leaves: u64,
pub chunk_records: Vec<ChunkRecord>,
pub dev_records: Vec<DevRecord>,
}
pub struct ReconstructionResult {
pub chunks: Vec<ChunkRecord>,
pub devices: Vec<DevRecord>,
pub conflicts: Vec<Conflict>,
pub warnings: Vec<Warning>,
pub chunk_root_covered: bool,
}