use crate::{RunContext, Runnable, util::is_mounted};
use anyhow::{Result, bail};
use btrfs_disk::{raw, reader, superblock::SUPER_MIRROR_MAX};
use clap::Parser;
use std::{fs::File, path::PathBuf};
mod chunks;
mod csums;
mod errors;
mod extents;
mod fs_roots;
mod root_refs;
mod superblock;
mod tree_structure;
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum CheckMode {
Original,
Lowmem,
}
#[derive(Parser, Debug)]
#[allow(clippy::doc_markdown, clippy::struct_excessive_bools)]
pub struct CheckCommand {
device: PathBuf,
#[clap(short = 's', long = "super")]
superblock: Option<u64>,
#[clap(short = 'b', long)]
backup: bool,
#[clap(short = 'r', long)]
tree_root: Option<u64>,
#[clap(long)]
chunk_root: Option<u64>,
#[clap(long)]
readonly: bool,
#[clap(long)]
repair: bool,
#[clap(long)]
force: bool,
#[clap(long)]
mode: Option<CheckMode>,
#[clap(long)]
init_csum_tree: bool,
#[clap(long)]
init_extent_tree: bool,
#[clap(long)]
check_data_csum: bool,
#[clap(short = 'Q', long)]
qgroup_report: bool,
#[clap(short = 'E', long)]
subvol_extents: Option<u64>,
#[clap(short = 'p', long)]
progress: bool,
}
impl Runnable for CheckCommand {
#[allow(clippy::too_many_lines)]
fn run(&self, _ctx: &RunContext) -> Result<()> {
if self.repair {
bail!("repair mode is not yet supported");
}
if self.init_csum_tree {
bail!("--init-csum-tree is not yet supported");
}
if self.init_extent_tree {
bail!("--init-extent-tree is not yet supported");
}
if self.qgroup_report {
bail!("--qgroup-report is not yet supported");
}
if self.subvol_extents.is_some() {
bail!("--subvol-extents is not yet supported");
}
if !self.force && is_mounted(&self.device) {
bail!(
"'{}' is mounted, use --force to continue",
self.device.display()
);
}
if let Some(m) = self.superblock
&& m >= u64::from(SUPER_MIRROR_MAX)
{
bail!(
"super mirror index {m} is out of range (max {})",
SUPER_MIRROR_MAX - 1
);
}
eprintln!("Opening filesystem to check...");
let mut file = File::open(&self.device)?;
#[allow(clippy::cast_possible_truncation)] let mirror = self.superblock.unwrap_or(0) as u32;
let mut open = open_with_overrides(
file.try_clone()?,
mirror,
self.backup,
self.tree_root,
self.chunk_root,
)?;
let sb = &open.superblock;
eprintln!("Checking filesystem on {}", self.device.display());
eprintln!("UUID: {}", sb.fsid);
let mut results = errors::CheckResults::new(sb.bytes_used);
eprintln!("[1/7] checking superblocks");
superblock::check_superblocks(&mut file, &mut results);
eprintln!("[2/7] checking root items");
let tree_block_addrs = tree_structure::check_all_trees(
&mut open.reader,
sb,
&open.tree_roots,
&mut results,
);
eprintln!("[3/7] checking extents");
let extent_root = open
.tree_roots
.get(&u64::from(raw::BTRFS_EXTENT_TREE_OBJECTID))
.map(|&(bytenr, _)| bytenr);
if let Some(extent_root) = extent_root {
extents::check_extent_tree(
&mut open.reader,
extent_root,
&tree_block_addrs,
&mut results,
);
}
eprintln!("[4/7] checking free space tree");
let block_group_tree_root = if sb.compat_ro_flags
& u64::from(raw::BTRFS_FEATURE_COMPAT_RO_BLOCK_GROUP_TREE)
!= 0
{
open.tree_roots
.get(&u64::from(raw::BTRFS_BLOCK_GROUP_TREE_OBJECTID))
.map(|&(bytenr, _)| bytenr)
} else {
None
};
let dev_tree_root = open
.tree_roots
.get(&u64::from(raw::BTRFS_DEV_TREE_OBJECTID))
.map(|&(bytenr, _)| bytenr);
if let (Some(er), Some(dr)) = (extent_root, dev_tree_root) {
chunks::check_chunks(
&mut open.reader,
er,
block_group_tree_root,
dr,
&mut results,
);
}
eprintln!("[5/7] checking fs roots");
fs_roots::check_fs_roots(
&mut open.reader,
&open.tree_roots,
&mut results,
);
if self.check_data_csum {
eprintln!("[6/7] checking csums items (verifying data)");
} else {
eprintln!(
"[6/7] checking only csums items \
(without verifying data)"
);
}
let csum_root = open
.tree_roots
.get(&u64::from(raw::BTRFS_CSUM_TREE_OBJECTID))
.map(|&(bytenr, _)| bytenr);
if let Some(csum_root) = csum_root {
csums::check_csums(
&mut open.reader,
sb,
csum_root,
self.check_data_csum,
&mut results,
);
}
eprintln!("[7/7] checking root refs");
root_refs::check_root_refs(&mut open.reader, sb.root, &mut results);
results.print_summary();
if results.has_errors() {
std::process::exit(1);
}
Ok(())
}
}
fn open_with_overrides(
file: File,
mirror: u32,
backup: bool,
tree_root: Option<u64>,
chunk_root: Option<u64>,
) -> Result<reader::OpenFilesystem<File>> {
use btrfs_disk::{chunk, superblock as sb_mod};
if !backup && tree_root.is_none() && chunk_root.is_none() {
return Ok(reader::filesystem_open_mirror(file, mirror)?);
}
let mut reader = file;
let mut sb = sb_mod::read_superblock(&mut reader, mirror)?;
if backup {
let best = sb
.backup_roots
.iter()
.filter(|b| b.tree_root != 0)
.max_by_key(|b| b.tree_root_gen);
if let Some(b) = best {
eprintln!(
"Using backup root: tree_root={:#x} (gen {}), chunk_root={:#x} (gen {})",
b.tree_root, b.tree_root_gen, b.chunk_root, b.chunk_root_gen,
);
sb.root = b.tree_root;
sb.root_level = b.tree_root_level;
sb.chunk_root = b.chunk_root;
sb.chunk_root_level = b.chunk_root_level;
sb.generation = b.tree_root_gen;
} else {
bail!("no valid backup roots found in superblock");
}
}
if let Some(bytenr) = tree_root {
eprintln!("Using tree root override: {bytenr:#x}");
sb.root = bytenr;
}
if let Some(bytenr) = chunk_root {
eprintln!("Using chunk root override: {bytenr:#x}");
sb.chunk_root = bytenr;
}
let chunk_cache = chunk::seed_from_sys_chunk_array(
&sb.sys_chunk_array,
sb.sys_chunk_array_size,
);
let mut block_reader = btrfs_disk::reader::BlockReader::new(
reader,
sb.dev_item.devid,
sb.nodesize,
chunk_cache,
);
reader::read_chunk_tree(&mut block_reader, sb.chunk_root)?;
let tree_roots = reader::read_root_tree(&mut block_reader, sb.root)?;
let mut per_device_dev_items = std::collections::BTreeMap::new();
per_device_dev_items.insert(sb.dev_item.devid, sb.dev_item.clone());
Ok(reader::OpenFilesystem {
reader: block_reader,
superblock: sb,
tree_roots,
per_device_dev_items,
})
}