Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
154 lines
4.6 KiB
Rust
154 lines
4.6 KiB
Rust
//! Recuperação de ponta a ponta sobre imagens reais (FAT32, NTFS, ext4).
|
|
|
|
mod common;
|
|
use common::*;
|
|
use guara_core::*;
|
|
|
|
fn check(fs: &str, only_carvable: bool) {
|
|
let Some(dir) = fixtures() else { return };
|
|
let img = dir.join(format!("{fs}.img"));
|
|
let before = sha256_file(&img);
|
|
let res = scan_path(&img, &ScanOptions::default());
|
|
let out = tempfile::tempdir().unwrap();
|
|
let rep = recover_all(&img, &res.found, out.path());
|
|
assert!(rep.failed.is_empty(), "falhas: {:?}", rep.failed);
|
|
let got = hashes_in(out.path());
|
|
for (hash, path) in expected(&dir, fs) {
|
|
let carvable = [".jpg", ".png", ".pdf", ".docx", ".db"]
|
|
.iter()
|
|
.any(|e| path.ends_with(e));
|
|
if only_carvable && !carvable {
|
|
continue;
|
|
}
|
|
assert!(
|
|
got.contains_key(&hash),
|
|
"[{fs}] não recuperado bit a bit: {path}"
|
|
);
|
|
}
|
|
// Os dois métodos juntos não podem listar o mesmo conteúdo duas vezes.
|
|
let mut seen = std::collections::HashSet::new();
|
|
for f in &res.found {
|
|
if let Some(o) = f.first_offset() {
|
|
assert!(seen.insert(o), "[{fs}] achado duplicado no offset {o}");
|
|
}
|
|
}
|
|
if fs != "ext4" {
|
|
for f in res.found.iter().filter(|f| f.origin != Origin::Carved) {
|
|
assert!(
|
|
f.modified.is_some() && f.created.is_some(),
|
|
"[{fs}] {} sem datas de modificação/criação",
|
|
f.name
|
|
);
|
|
}
|
|
}
|
|
assert_eq!(before, sha256_file(&img), "a origem foi modificada!");
|
|
}
|
|
|
|
#[test]
|
|
fn fat32_recupera_apagados_com_nome_e_pasta() {
|
|
check("fat32", false);
|
|
let Some(dir) = fixtures() else { return };
|
|
let res = scan_path(&dir.join("fat32.img"), &ScanOptions::default());
|
|
assert!(res
|
|
.found
|
|
.iter()
|
|
.any(|f| f.dir.as_deref() == Some("Documentos/Trabalho") && f.name == "relatorio.pdf"));
|
|
assert!(
|
|
res.found.iter().all(|f| f.name != "mantida.jpg"),
|
|
"arquivo existente não deve ser listado"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn ntfs_recupera_apagados() {
|
|
check("ntfs", false);
|
|
}
|
|
|
|
#[test]
|
|
fn ext4_recupera_por_assinatura() {
|
|
check("ext4", true);
|
|
}
|
|
|
|
#[test]
|
|
fn varredura_rapida_nao_faz_carving() {
|
|
let Some(dir) = fixtures() else { return };
|
|
let res = scan_path(
|
|
&dir.join("ext4.img"),
|
|
&ScanOptions {
|
|
mode: ScanMode::Quick,
|
|
..Default::default()
|
|
},
|
|
);
|
|
// O ext4 agora tem leitor de metadados (inodes e diário), mas a varredura rápida nunca faz carving.
|
|
assert!(res.found.iter().all(|f| f.origin != Origin::Carved));
|
|
let res = scan_path(
|
|
&dir.join("ntfs.img"),
|
|
&ScanOptions {
|
|
mode: ScanMode::Quick,
|
|
..Default::default()
|
|
},
|
|
);
|
|
assert_eq!(
|
|
res.found
|
|
.iter()
|
|
.filter(|f| f.origin == Origin::Ntfs)
|
|
.count(),
|
|
7
|
|
);
|
|
}
|
|
|
|
/// Regressão: mkfs.vfat -F 32 em mídia pequena (< 65525 clusters) é FAT32 pelo BPB.
|
|
#[test]
|
|
fn fat32_pequeno_com_poucos_clusters_e_reconhecido() {
|
|
use std::process::Command;
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let img = dir.path().join("p.img");
|
|
let ok = Command::new("truncate")
|
|
.args(["-s", "32M"])
|
|
.arg(&img)
|
|
.status()
|
|
.map(|s| s.success())
|
|
.unwrap_or(false)
|
|
&& Command::new("mkfs.vfat")
|
|
.args(["-F", "32"])
|
|
.arg(&img)
|
|
.output()
|
|
.map(|o| o.status.success())
|
|
.unwrap_or(false);
|
|
let file = dir.path().join("arquivo.txt");
|
|
std::fs::write(&file, b"conteudo de teste\n".repeat(500)).unwrap();
|
|
let ok = ok
|
|
&& Command::new("mcopy")
|
|
.arg("-i")
|
|
.arg(&img)
|
|
.arg(&file)
|
|
.arg("::/")
|
|
.status()
|
|
.map(|s| s.success())
|
|
.unwrap_or(false)
|
|
&& Command::new("mdel")
|
|
.arg("-i")
|
|
.arg(&img)
|
|
.arg("::/arquivo.txt")
|
|
.status()
|
|
.map(|s| s.success())
|
|
.unwrap_or(false);
|
|
if !ok {
|
|
eprintln!("SKIP: mtools/dosfstools ausentes");
|
|
return;
|
|
}
|
|
let res = scan_path(
|
|
&img,
|
|
&ScanOptions {
|
|
mode: ScanMode::Quick,
|
|
..Default::default()
|
|
},
|
|
);
|
|
let layout = res.layout.unwrap();
|
|
assert_eq!(layout.partitions[0].fs, FsKind::Fat32);
|
|
assert_eq!(res.found.len(), 1);
|
|
let out = tempfile::tempdir().unwrap();
|
|
recover_all(&img, &res.found, out.path());
|
|
assert!(hashes_in(out.path()).contains_key(&sha256(&b"conteudo de teste\n".repeat(500))));
|
|
}
|