CI / Format and clippy (push) Successful in 1m57s
CI / Dependency audit (push) Successful in 13s
CI / Check desktop app (Linux) (push) Successful in 3m54s
CI / Tests (Linux) (push) Failing after 7m25s
Release / Create release (push) Successful in 6s
Release / CLI (linux-aarch64) (push) Successful in 1m48s
Release / CLI (linux-x86_64) (push) Successful in 2m39s
Release / Desktop app (linux-aarch64) (push) Successful in 10m9s
Release / Desktop app (linux-x86_64) (push) Successful in 12m20s
Release / Desktop app (windows) (push) Successful in 7m32s
Release / Build latest.json (push) Successful in 5s
Completes the English-only standardization for code, comments, scripts and CI templates (UI/CLI-facing strings stay bilingual pt-BR/en, untouched). Covers the remaining crates/, apps/gui/ui/ and apps/gui/src-tauri/ comments, .gitea/ workflow and template comments, .githooks/ hooks, and the scripts/ source comments for the files already renamed in a prior commit (check.sh, generate-logo.py, install-hooks.sh). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VAyr8d7HuAdUGB6aA5xmDD
72 lines
2.5 KiB
Rust
72 lines
2.5 KiB
Rust
//! HFS+: real `dfvfs` image (`target/testdata/dfvfs/hfsplus.raw`) with simulated leaf-record deletion.
|
|
|
|
mod common;
|
|
use common::*;
|
|
use guara_core::*;
|
|
|
|
fn image() -> Option<Vec<u8>> {
|
|
let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
.join("../../target/testdata/dfvfs/hfsplus.raw");
|
|
p.exists().then(|| std::fs::read(p).unwrap())
|
|
}
|
|
|
|
fn scan_bytes(bytes: &[u8]) -> Collect {
|
|
init();
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let p = dir.path().join("h.img");
|
|
std::fs::write(&p, bytes).unwrap();
|
|
scan_path(
|
|
&p,
|
|
&ScanOptions {
|
|
mode: ScanMode::Quick,
|
|
..Default::default()
|
|
},
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn pristine_volume_has_no_deleted_files_and_is_recognized() {
|
|
let Some(img) = image() else {
|
|
eprintln!("SKIP: execute scripts/fetch-testdata.sh");
|
|
return;
|
|
};
|
|
let c = scan_bytes(&img);
|
|
assert_eq!(c.layout.as_ref().unwrap().partitions[0].fs, FsKind::HfsPlus);
|
|
assert!(
|
|
c.found.is_empty(),
|
|
"{:?}",
|
|
c.found.iter().map(|f| &f.name).collect::<Vec<_>>()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn record_left_in_leaf_slack_is_recovered() {
|
|
let Some(mut img) = image() else { return };
|
|
let bs = 4096usize;
|
|
let node = 186 * bs + bs; // catalog starts at allocation block 186; node 1 is the single leaf node
|
|
// Simulate deleting the last 5 records at the end of the leaf node by decrementing `numRecords`.
|
|
img[node + 10..node + 12].copy_from_slice(&21u16.to_be_bytes());
|
|
// Clear the allocation bitmap (at block 1) so the deleted files' data blocks are marked free.
|
|
let blk = 278usize;
|
|
img[bs + blk / 8] &= !(0x80 >> (blk % 8));
|
|
let c = scan_bytes(&img);
|
|
let f = c
|
|
.found
|
|
.iter()
|
|
.find(|f| f.name == "fseventsd-uuid")
|
|
.unwrap_or_else(|| panic!("{:?}", c.found.iter().map(|f| &f.name).collect::<Vec<_>>()));
|
|
assert_eq!(f.origin, Origin::Hfs);
|
|
assert_eq!(f.size, 36);
|
|
assert_eq!(f.dir.as_deref(), Some("/.fseventsd"));
|
|
assert!(f.confidence >= Confidence::Medium);
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let p = dir.path().join("h.img");
|
|
std::fs::write(&p, &img).unwrap();
|
|
let out = dir.path().join("out");
|
|
let rep = recover_all(&p, std::slice::from_ref(f), &out);
|
|
assert!(rep.failed.is_empty());
|
|
let rp = std::path::PathBuf::from(&rep.recovered[0].path);
|
|
let got = std::fs::read(if rp.is_absolute() { rp } else { out.join(rp) }).unwrap();
|
|
assert_eq!(got, &img[blk * bs..blk * bs + 36]);
|
|
}
|