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#[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, _ctx: &RunContext) -> 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.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 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)] 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 eprintln!("[1/7] checking superblocks");
151 superblock::check_superblocks(&mut file, &mut results);
152
153 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 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 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 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 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
247fn 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 !backup && tree_root.is_none() && chunk_root.is_none() {
263 return Ok(reader::filesystem_open_mirror(file, mirror)?);
264 }
265
266 let mut reader = file;
268 let mut sb = sb_mod::read_superblock(&mut reader, mirror)?;
269
270 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 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 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}