feat: Guará 0.1.0 — recuperação de arquivos (núcleo, CLI e interface gráfica)
Núcleo em Rust somente leitura: NTFS, FAT, exFAT, partições MBR/GPT e perdidas, carving por estrutura, fusão sem duplicatas, confiança por arquivo, imagem forense, pausar/retomar. CLI `guara`, UI web/Tauri, CI e releases por tag. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
765cb0d4a2
57 files changed
+8621
No files matched your search
@@ -0,0 +1,87 @@
|
||||
//! Imagem forense de um disco: leitura tolerante a setores ruins, com mapa de
|
||||
//! erros e hash. Serve para trabalhar sempre sobre a cópia.
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::source::Source;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct ImageReport {
|
||||
pub bytes: u64,
|
||||
pub sha256: String,
|
||||
/// (offset, tamanho) dos trechos ilegíveis, preenchidos com zeros.
|
||||
pub bad_ranges: Vec<(u64, u64)>,
|
||||
pub cancelled: bool,
|
||||
}
|
||||
|
||||
const BLOCK: usize = 1 << 20;
|
||||
const SECTOR: usize = 512;
|
||||
|
||||
pub fn create_image(
|
||||
src: &dyn Source,
|
||||
dest: &Path,
|
||||
cancelled: &(dyn Fn() -> bool + Sync),
|
||||
progress: &mut dyn FnMut(u64, u64),
|
||||
) -> Result<ImageReport> {
|
||||
let mut out = std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(dest)
|
||||
.map_err(|e| {
|
||||
Error::msg(format!(
|
||||
"não foi possível criar {}: {e} (o arquivo não pode existir)",
|
||||
dest.display()
|
||||
))
|
||||
})?;
|
||||
let size = src.size();
|
||||
let mut rep = ImageReport::default();
|
||||
let mut hasher = Sha256::new();
|
||||
let mut buf = vec![0u8; BLOCK];
|
||||
let mut pos = 0u64;
|
||||
while pos < size {
|
||||
if cancelled() {
|
||||
rep.cancelled = true;
|
||||
break;
|
||||
}
|
||||
let want = (size - pos).min(BLOCK as u64) as usize;
|
||||
match read_full(src, pos, &mut buf[..want]) {
|
||||
Ok(()) => {}
|
||||
Err(_) => {
|
||||
// Refaz setor a setor para isolar os trechos ruins.
|
||||
let mut o = 0;
|
||||
while o < want {
|
||||
let n = SECTOR.min(want - o);
|
||||
if read_full(src, pos + o as u64, &mut buf[o..o + n]).is_err() {
|
||||
buf[o..o + n].fill(0);
|
||||
match rep.bad_ranges.last_mut() {
|
||||
Some(l) if l.0 + l.1 == pos + o as u64 => l.1 += n as u64,
|
||||
_ => rep.bad_ranges.push((pos + o as u64, n as u64)),
|
||||
}
|
||||
}
|
||||
o += n;
|
||||
}
|
||||
}
|
||||
}
|
||||
out.write_all(&buf[..want])?;
|
||||
hasher.update(&buf[..want]);
|
||||
pos += want as u64;
|
||||
progress(pos, size);
|
||||
}
|
||||
out.flush()?;
|
||||
rep.bytes = pos;
|
||||
rep.sha256 = hasher
|
||||
.finalize()
|
||||
.iter()
|
||||
.map(|b| format!("{b:02x}"))
|
||||
.collect();
|
||||
let map = dest.with_extension("map.json");
|
||||
std::fs::write(map, serde_json::to_vec_pretty(&rep)?)?;
|
||||
Ok(rep)
|
||||
}
|
||||
|
||||
fn read_full(src: &dyn Source, off: u64, buf: &mut [u8]) -> std::io::Result<()> {
|
||||
crate::source::read_exact_at(src, off, buf)
|
||||
}
|
||||
Reference in new issue
Block a user