use crate::AnomalyKind;
pub fn audit<R: std::io::Read + std::io::Seek>(
reader: &mut R,
volume: &apfs_core::volume::ApfsVolume,
block_size: usize,
) -> crate::Result<Vec<AnomalyKind>> {
let snaps = apfs_core::snapshot::list_snapshots(reader, volume, block_size)?;
let mut out = Vec::new();
for s in &snaps {
match apfs_core::snapshot::resolve_snapshot_xid(reader, volume, &s.name, block_size)? {
Some(x) if x == s.xid => {}
_ => out.push(AnomalyKind::SnapshotMissingMetadata {
name: s.name.clone(),
}),
}
}
let by_xid: Vec<(u64, u64)> = snaps.iter().map(|s| (s.xid, s.create_time)).collect();
out.extend(snapshot_xid_disorder(&by_xid));
Ok(out)
}
fn snapshot_xid_disorder(snaps: &[(u64, u64)]) -> Vec<AnomalyKind> {
let mut sorted: Vec<(u64, u64)> = snaps.to_vec();
sorted.sort_by_key(|&(xid, _)| xid);
let mut out = Vec::new();
for w in sorted.windows(2) {
let ((_, t0), (xid1, t1)) = (w[0], w[1]);
if t0 != 0 && t1 != 0 && t1 < t0 {
out.push(AnomalyKind::SnapshotXidDisorder { xid: xid1 });
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ascending_xid_and_time_is_clean() {
assert!(snapshot_xid_disorder(&[(1, 100), (2, 200), (3, 300)]).is_empty());
}
#[test]
fn later_xid_older_time_is_disorder() {
let v = snapshot_xid_disorder(&[(1, 100), (2, 300), (3, 200)]);
assert_eq!(v.len(), 1);
assert_eq!(v[0].code(), "APFS-SNAPSHOT-XID-DISORDER");
}
#[test]
fn zero_create_times_are_not_disorder() {
assert!(snapshot_xid_disorder(&[(1, 0), (2, 0)]).is_empty());
}
}