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#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
18pub enum CheckMode {
19 Original,
20 Lowmem,
21}
22
23#[derive(Parser, Debug)]
30#[allow(clippy::doc_markdown, clippy::struct_excessive_bools)]
31pub struct CheckCommand {
32 device: PathBuf,
34
35 #[clap(short = 's', long = "super")]
37 superblock: Option<u64>,
38
39 #[clap(short = 'b', long)]
41 backup: bool,
42
43 #[clap(short = 'r', long)]
45 tree_root: Option<u64>,
46
47 #[clap(long)]
49 chunk_root: Option<u64>,
50
51 #[clap(long)]
53 readonly: bool,
54
55 #[clap(long)]
57 repair: bool,
58
59 #[clap(long)]
61 force: bool,
62
63 #[clap(long)]
65 mode: Option<CheckMode>,
66
67 #[clap(long)]
69 init_csum_tree: bool,
70
71 #[clap(long)]
73 init_extent_tree: bool,
74
75 #[clap(long)]
77 check_data_csum: bool,
78
79 #[clap(short = 'Q', long)]
81 qgroup_report: bool,
82
83 #[clap(short = 'E', long)]
85 subvol_extents: Option<u64>,
86
87 #[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 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 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)] 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 eprintln!("[1/7] checking superblocks");
155 superblock::check_superblocks(&mut file, &mut results);
156
157 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 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 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 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 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}