use super::errors::{CheckError, CheckResults};
use btrfs_disk::{
items::RootRef,
reader::{self, BlockReader},
tree::{KeyType, TreeBlock},
};
use std::{
collections::BTreeMap,
io::{Read, Seek},
};
const HEADER_SIZE: usize = std::mem::size_of::<btrfs_disk::raw::btrfs_header>();
struct RefEntry {
dirid: u64,
sequence: u64,
name: Vec<u8>,
}
pub fn check_root_refs<R: Read + Seek>(
reader: &mut BlockReader<R>,
root_bytenr: u64,
results: &mut CheckResults,
) {
let mut forward_refs: BTreeMap<(u64, u64), RefEntry> = BTreeMap::new();
let mut back_refs: BTreeMap<(u64, u64), RefEntry> = BTreeMap::new();
let mut read_errors: Vec<(u64, String)> = Vec::new();
let mut visitor = |_raw: &[u8], block: &TreeBlock| {
if let TreeBlock::Leaf { items, data, .. } = block {
for item in items {
let start = HEADER_SIZE + item.offset as usize;
let item_data = &data[start..][..item.size as usize];
match item.key.key_type {
KeyType::RootRef => {
let parent = item.key.objectid;
let child = item.key.offset;
if let Some(rr) = RootRef::parse(item_data) {
forward_refs.insert(
(child, parent),
RefEntry {
dirid: rr.dirid,
sequence: rr.sequence,
name: rr.name,
},
);
}
}
KeyType::RootBackref => {
let child = item.key.objectid;
let parent = item.key.offset;
if let Some(rr) = RootRef::parse(item_data) {
back_refs.insert(
(child, parent),
RefEntry {
dirid: rr.dirid,
sequence: rr.sequence,
name: rr.name,
},
);
}
}
_ => {}
}
}
}
};
let mut on_error = |logical: u64, err: &std::io::Error| {
read_errors.push((logical, err.to_string()));
};
if let Err(e) = reader::tree_walk_tolerant(
reader,
root_bytenr,
&mut visitor,
&mut on_error,
) {
results.report(CheckError::ReadError {
logical: root_bytenr,
detail: format!("root tree: {e}"),
});
return;
}
for (logical, detail) in read_errors {
results.report(CheckError::ReadError { logical, detail });
}
for (&(child, parent), fwd) in &forward_refs {
match back_refs.get(&(child, parent)) {
None => {
results
.report(CheckError::RootBackrefMissing { child, parent });
}
Some(back) => {
check_fields_match(child, parent, fwd, back, results);
}
}
}
for &(child, parent) in back_refs.keys() {
if !forward_refs.contains_key(&(child, parent)) {
results.report(CheckError::RootRefMissing { child, parent });
}
}
}
fn check_fields_match(
child: u64,
parent: u64,
fwd: &RefEntry,
back: &RefEntry,
results: &mut CheckResults,
) {
if fwd.dirid != back.dirid {
results.report(CheckError::RootRefMismatch {
child,
parent,
detail: format!(
"dirid mismatch: ROOT_REF has {}, ROOT_BACKREF has {}",
fwd.dirid, back.dirid
),
});
}
if fwd.sequence != back.sequence {
results.report(CheckError::RootRefMismatch {
child,
parent,
detail: format!(
"sequence mismatch: ROOT_REF has {}, ROOT_BACKREF has {}",
fwd.sequence, back.sequence
),
});
}
if fwd.name != back.name {
let fwd_name = String::from_utf8_lossy(&fwd.name);
let back_name = String::from_utf8_lossy(&back.name);
results.report(CheckError::RootRefMismatch {
child,
parent,
detail: format!(
"name mismatch: ROOT_REF has '{fwd_name}', ROOT_BACKREF has '{back_name}'"
),
});
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_fields_match_identical() {
let fwd = RefEntry {
dirid: 256,
sequence: 3,
name: b"subvol1".to_vec(),
};
let back = RefEntry {
dirid: 256,
sequence: 3,
name: b"subvol1".to_vec(),
};
let mut results = CheckResults::new(0);
check_fields_match(257, 5, &fwd, &back, &mut results);
assert_eq!(results.error_count, 0);
}
#[test]
fn check_fields_match_dirid_mismatch() {
let fwd = RefEntry {
dirid: 256,
sequence: 3,
name: b"subvol1".to_vec(),
};
let back = RefEntry {
dirid: 512,
sequence: 3,
name: b"subvol1".to_vec(),
};
let mut results = CheckResults::new(0);
check_fields_match(257, 5, &fwd, &back, &mut results);
assert_eq!(results.error_count, 1);
}
#[test]
fn check_fields_match_sequence_mismatch() {
let fwd = RefEntry {
dirid: 256,
sequence: 3,
name: b"subvol1".to_vec(),
};
let back = RefEntry {
dirid: 256,
sequence: 7,
name: b"subvol1".to_vec(),
};
let mut results = CheckResults::new(0);
check_fields_match(257, 5, &fwd, &back, &mut results);
assert_eq!(results.error_count, 1);
}
#[test]
fn check_fields_match_name_mismatch() {
let fwd = RefEntry {
dirid: 256,
sequence: 3,
name: b"subvol1".to_vec(),
};
let back = RefEntry {
dirid: 256,
sequence: 3,
name: b"subvol2".to_vec(),
};
let mut results = CheckResults::new(0);
check_fields_match(257, 5, &fwd, &back, &mut results);
assert_eq!(results.error_count, 1);
}
#[test]
fn check_fields_match_all_mismatch() {
let fwd = RefEntry {
dirid: 256,
sequence: 3,
name: b"subvol1".to_vec(),
};
let back = RefEntry {
dirid: 512,
sequence: 7,
name: b"subvol2".to_vec(),
};
let mut results = CheckResults::new(0);
check_fields_match(257, 5, &fwd, &back, &mut results);
assert_eq!(results.error_count, 3);
}
}