Skip to main content

btrfs_cli/
check.rs

1use crate::{Format, 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, _format: Format, _dry_run: bool) -> 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.backup {
106            bail!("--backup is not yet supported");
107        }
108        if self.tree_root.is_some() {
109            bail!("--tree-root is not yet supported");
110        }
111        if self.chunk_root.is_some() {
112            bail!("--chunk-root is not yet supported");
113        }
114        if self.qgroup_report {
115            bail!("--qgroup-report is not yet supported");
116        }
117        if self.subvol_extents.is_some() {
118            bail!("--subvol-extents is not yet supported");
119        }
120
121        // Mount check.
122        if !self.force && is_mounted(&self.device) {
123            bail!(
124                "'{}' is mounted, use --force to continue",
125                self.device.display()
126            );
127        }
128
129        if let Some(m) = self.superblock
130            && m >= u64::from(SUPER_MIRROR_MAX)
131        {
132            bail!(
133                "super mirror index {m} is out of range (max {})",
134                SUPER_MIRROR_MAX - 1
135            );
136        }
137
138        eprintln!("Opening filesystem to check...");
139
140        let mut file = File::open(&self.device)?;
141        #[allow(clippy::cast_possible_truncation)] // mirror index fits in u32
142        let mirror = self.superblock.unwrap_or(0) as u32;
143
144        let mut open =
145            reader::filesystem_open_mirror(file.try_clone()?, mirror)?;
146
147        let sb = &open.superblock;
148        eprintln!("Checking filesystem on {}", self.device.display());
149        eprintln!("UUID: {}", sb.fsid);
150
151        let mut results = errors::CheckResults::new(sb.bytes_used);
152
153        // Phase 1: Superblock validation.
154        eprintln!("[1/7] checking superblocks");
155        superblock::check_superblocks(&mut file, &mut results);
156
157        // Phase 2: Tree structure checks (all trees).
158        eprintln!("[2/7] checking root items");
159        let tree_block_addrs = tree_structure::check_all_trees(
160            &mut open.reader,
161            sb,
162            &open.tree_roots,
163            &mut results,
164        );
165
166        // Phase 3: Extent tree cross-checks.
167        eprintln!("[3/7] checking extents");
168        let extent_root = open
169            .tree_roots
170            .get(&u64::from(raw::BTRFS_EXTENT_TREE_OBJECTID))
171            .map(|&(bytenr, _)| bytenr);
172        if let Some(extent_root) = extent_root {
173            extents::check_extent_tree(
174                &mut open.reader,
175                extent_root,
176                &tree_block_addrs,
177                &mut results,
178            );
179        }
180
181        // Phase 4: Chunk / block group / device extent cross-checks.
182        eprintln!("[4/7] checking free space tree");
183        let block_group_tree_root = if sb.compat_ro_flags
184            & u64::from(raw::BTRFS_FEATURE_COMPAT_RO_BLOCK_GROUP_TREE)
185            != 0
186        {
187            open.tree_roots
188                .get(&u64::from(raw::BTRFS_BLOCK_GROUP_TREE_OBJECTID))
189                .map(|&(bytenr, _)| bytenr)
190        } else {
191            None
192        };
193        let dev_tree_root = open
194            .tree_roots
195            .get(&u64::from(raw::BTRFS_DEV_TREE_OBJECTID))
196            .map(|&(bytenr, _)| bytenr);
197        if let (Some(er), Some(dr)) = (extent_root, dev_tree_root) {
198            chunks::check_chunks(
199                &mut open.reader,
200                er,
201                block_group_tree_root,
202                dr,
203                &mut results,
204            );
205        }
206
207        // Phase 5: FS tree checks.
208        eprintln!("[5/7] checking fs roots");
209        fs_roots::check_fs_roots(
210            &mut open.reader,
211            &open.tree_roots,
212            &mut results,
213        );
214
215        // Phase 6: Checksum tree verification.
216        if self.check_data_csum {
217            eprintln!("[6/7] checking csums items (verifying data)");
218        } else {
219            eprintln!(
220                "[6/7] checking only csums items \
221                 (without verifying data)"
222            );
223        }
224        let csum_root = open
225            .tree_roots
226            .get(&u64::from(raw::BTRFS_CSUM_TREE_OBJECTID))
227            .map(|&(bytenr, _)| bytenr);
228        if let Some(csum_root) = csum_root {
229            csums::check_csums(
230                &mut open.reader,
231                sb,
232                csum_root,
233                self.check_data_csum,
234                &mut results,
235            );
236        }
237
238        eprintln!("[7/7] checking root refs");
239        root_refs::check_root_refs(&mut open.reader, sb.root, &mut results);
240
241        results.print_summary();
242
243        if results.has_errors() {
244            std::process::exit(1);
245        }
246
247        Ok(())
248    }
249}