Skip to main content

btrfs_cli/
check.rs

1use crate::{RunContext, Runnable, util::is_mounted};
2use anyhow::{Result, bail};
3use btrfs_disk::{raw, reader, superblock::SUPER_MIRROR_MAX};
4use clap::Parser;
5use std::{fs::File, path::PathBuf};
6
7mod chunks;
8mod csums;
9mod errors;
10mod extents;
11mod fs_roots;
12mod root_refs;
13mod superblock;
14mod tree_structure;
15
16/// Check mode for filesystem verification.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
18pub enum CheckMode {
19    Original,
20    Lowmem,
21}
22
23/// Check structural integrity of a filesystem (unmounted).
24///
25/// Verify the integrity of a btrfs filesystem by checking internal structures,
26/// extent trees, and data checksums. The filesystem must be unmounted before
27/// running this command. This is a potentially slow operation that requires
28/// CAP_SYS_ADMIN. Use --readonly to perform checks without attempting repairs.
29#[derive(Parser, Debug)]
30#[allow(clippy::doc_markdown, clippy::struct_excessive_bools)]
31pub struct CheckCommand {
32    /// Path to the device containing the btrfs filesystem
33    device: PathBuf,
34
35    /// Use this superblock copy
36    #[clap(short = 's', long = "super")]
37    superblock: Option<u64>,
38
39    /// Use the first valid backup root copy
40    #[clap(short = 'b', long)]
41    backup: bool,
42
43    /// Use the given bytenr for the tree root
44    #[clap(short = 'r', long)]
45    tree_root: Option<u64>,
46
47    /// Use the given bytenr for the chunk tree root
48    #[clap(long)]
49    chunk_root: Option<u64>,
50
51    /// Run in read-only mode (default)
52    #[clap(long)]
53    readonly: bool,
54
55    /// Try to repair the filesystem (dangerous)
56    #[clap(long)]
57    repair: bool,
58
59    /// Skip mount checks
60    #[clap(long)]
61    force: bool,
62
63    /// Checker operating mode
64    #[clap(long)]
65    mode: Option<CheckMode>,
66
67    /// Create a new CRC tree
68    #[clap(long)]
69    init_csum_tree: bool,
70
71    /// Create a new extent tree
72    #[clap(long)]
73    init_extent_tree: bool,
74
75    /// Verify checksums of data blocks
76    #[clap(long)]
77    check_data_csum: bool,
78
79    /// Print a report on qgroup consistency
80    #[clap(short = 'Q', long)]
81    qgroup_report: bool,
82
83    /// Print subvolume extents and sharing state for the given subvolume ID
84    #[clap(short = 'E', long)]
85    subvol_extents: Option<u64>,
86
87    /// Indicate progress
88    #[clap(short = 'p', long)]
89    progress: bool,
90}
91
92impl Runnable for CheckCommand {
93    #[allow(clippy::too_many_lines)]
94    fn run(&self, _ctx: &RunContext) -> Result<()> {
95        // Reject unsupported flags.
96        if self.repair {
97            bail!("repair mode is not yet supported");
98        }
99        if self.init_csum_tree {
100            bail!("--init-csum-tree is not yet supported");
101        }
102        if self.init_extent_tree {
103            bail!("--init-extent-tree is not yet supported");
104        }
105        if self.qgroup_report {
106            bail!("--qgroup-report is not yet supported");
107        }
108        if self.subvol_extents.is_some() {
109            bail!("--subvol-extents is not yet supported");
110        }
111
112        // Mount check.
113        if !self.force && is_mounted(&self.device) {
114            bail!(
115                "'{}' is mounted, use --force to continue",
116                self.device.display()
117            );
118        }
119
120        if let Some(m) = self.superblock
121            && m >= u64::from(SUPER_MIRROR_MAX)
122        {
123            bail!(
124                "super mirror index {m} is out of range (max {})",
125                SUPER_MIRROR_MAX - 1
126            );
127        }
128
129        eprintln!("Opening filesystem to check...");
130
131        let mut file = File::open(&self.device)?;
132        #[allow(clippy::cast_possible_truncation)] // mirror index fits in u32
133        let mirror = self.superblock.unwrap_or(0) as u32;
134
135        let mut open = open_with_overrides(
136            file.try_clone()?,
137            mirror,
138            self.backup,
139            self.tree_root,
140            self.chunk_root,
141        )?;
142
143        let sb = &open.superblock;
144        eprintln!("Checking filesystem on {}", self.device.display());
145        eprintln!("UUID: {}", sb.fsid);
146
147        let mut results = errors::CheckResults::new(sb.bytes_used);
148
149        // Phase 1: Superblock validation.
150        eprintln!("[1/7] checking superblocks");
151        superblock::check_superblocks(&mut file, &mut results);
152
153        // Phase 2: Tree structure checks (all trees).
154        eprintln!("[2/7] checking root items");
155        let tree_block_addrs = tree_structure::check_all_trees(
156            &mut open.reader,
157            sb,
158            &open.tree_roots,
159            &mut results,
160        );
161
162        // Phase 3: Extent tree cross-checks.
163        eprintln!("[3/7] checking extents");
164        let extent_root = open
165            .tree_roots
166            .get(&u64::from(raw::BTRFS_EXTENT_TREE_OBJECTID))
167            .map(|&(bytenr, _)| bytenr);
168        if let Some(extent_root) = extent_root {
169            extents::check_extent_tree(
170                &mut open.reader,
171                extent_root,
172                &tree_block_addrs,
173                &mut results,
174            );
175        }
176
177        // Phase 4: Chunk / block group / device extent cross-checks.
178        eprintln!("[4/7] checking free space tree");
179        let block_group_tree_root = if sb.compat_ro_flags
180            & u64::from(raw::BTRFS_FEATURE_COMPAT_RO_BLOCK_GROUP_TREE)
181            != 0
182        {
183            open.tree_roots
184                .get(&u64::from(raw::BTRFS_BLOCK_GROUP_TREE_OBJECTID))
185                .map(|&(bytenr, _)| bytenr)
186        } else {
187            None
188        };
189        let dev_tree_root = open
190            .tree_roots
191            .get(&u64::from(raw::BTRFS_DEV_TREE_OBJECTID))
192            .map(|&(bytenr, _)| bytenr);
193        if let (Some(er), Some(dr)) = (extent_root, dev_tree_root) {
194            chunks::check_chunks(
195                &mut open.reader,
196                er,
197                block_group_tree_root,
198                dr,
199                &mut results,
200            );
201        }
202
203        // Phase 5: FS tree checks.
204        eprintln!("[5/7] checking fs roots");
205        fs_roots::check_fs_roots(
206            &mut open.reader,
207            &open.tree_roots,
208            &mut results,
209        );
210
211        // Phase 6: Checksum tree verification.
212        if self.check_data_csum {
213            eprintln!("[6/7] checking csums items (verifying data)");
214        } else {
215            eprintln!(
216                "[6/7] checking only csums items \
217                 (without verifying data)"
218            );
219        }
220        let csum_root = open
221            .tree_roots
222            .get(&u64::from(raw::BTRFS_CSUM_TREE_OBJECTID))
223            .map(|&(bytenr, _)| bytenr);
224        if let Some(csum_root) = csum_root {
225            csums::check_csums(
226                &mut open.reader,
227                sb,
228                csum_root,
229                self.check_data_csum,
230                &mut results,
231            );
232        }
233
234        eprintln!("[7/7] checking root refs");
235        root_refs::check_root_refs(&mut open.reader, sb.root, &mut results);
236
237        results.print_summary();
238
239        if results.has_errors() {
240            std::process::exit(1);
241        }
242
243        Ok(())
244    }
245}
246
247/// Open a filesystem for checking, applying root pointer overrides.
248///
249/// Supports `--backup` (use best backup root from the superblock),
250/// `--tree-root` (override the root tree pointer), and `--chunk-root`
251/// (override the chunk tree pointer).
252fn open_with_overrides(
253    file: File,
254    mirror: u32,
255    backup: bool,
256    tree_root: Option<u64>,
257    chunk_root: Option<u64>,
258) -> Result<reader::OpenFilesystem<File>> {
259    use btrfs_disk::{chunk, superblock as sb_mod};
260
261    // If no overrides, use the standard path.
262    if !backup && tree_root.is_none() && chunk_root.is_none() {
263        return Ok(reader::filesystem_open_mirror(file, mirror)?);
264    }
265
266    // Read the superblock first, then apply overrides before walking trees.
267    let mut reader = file;
268    let mut sb = sb_mod::read_superblock(&mut reader, mirror)?;
269
270    // --backup: find the best backup root (highest generation with nonzero tree_root).
271    if backup {
272        let best = sb
273            .backup_roots
274            .iter()
275            .filter(|b| b.tree_root != 0)
276            .max_by_key(|b| b.tree_root_gen);
277
278        if let Some(b) = best {
279            eprintln!(
280                "Using backup root: tree_root={:#x} (gen {}), chunk_root={:#x} (gen {})",
281                b.tree_root, b.tree_root_gen, b.chunk_root, b.chunk_root_gen,
282            );
283            sb.root = b.tree_root;
284            sb.root_level = b.tree_root_level;
285            sb.chunk_root = b.chunk_root;
286            sb.chunk_root_level = b.chunk_root_level;
287            sb.generation = b.tree_root_gen;
288        } else {
289            bail!("no valid backup roots found in superblock");
290        }
291    }
292
293    // Explicit overrides take precedence over --backup.
294    if let Some(bytenr) = tree_root {
295        eprintln!("Using tree root override: {bytenr:#x}");
296        sb.root = bytenr;
297    }
298    if let Some(bytenr) = chunk_root {
299        eprintln!("Using chunk root override: {bytenr:#x}");
300        sb.chunk_root = bytenr;
301    }
302
303    // Bootstrap chunk cache and walk the (possibly overridden) chunk tree.
304    let chunk_cache = chunk::seed_from_sys_chunk_array(
305        &sb.sys_chunk_array,
306        sb.sys_chunk_array_size,
307    );
308    let mut block_reader = btrfs_disk::reader::BlockReader::new(
309        reader,
310        sb.dev_item.devid,
311        sb.nodesize,
312        chunk_cache,
313    );
314
315    reader::read_chunk_tree(&mut block_reader, sb.chunk_root)?;
316    let tree_roots = reader::read_root_tree(&mut block_reader, sb.root)?;
317
318    let mut per_device_dev_items = std::collections::BTreeMap::new();
319    per_device_dev_items.insert(sb.dev_item.devid, sb.dev_item.clone());
320    Ok(reader::OpenFilesystem {
321        reader: block_reader,
322        superblock: sb,
323        tree_roots,
324        per_device_dev_items,
325    })
326}