v0.17.1: cache priming, NonTrimmed marking, decrypt regression test
src/disc/mod.rs: - Cache priming (3-sector lookback) before patch's single-sector reads. Drive read-ahead pulls in adjacent pages so the target may already be cached when we ask for it. Throwaway reads — failures here don't update mapfile state. - When patch hits skip-limit on a range, leave remaining sectors NonTrimmed instead of marking Unreadable. We never tried to read those sectors, so don't give them terminal status — drive state evolves between passes (cache, mechanical settle), and a later pass may succeed. tests/pass_n_patch_fix.rs: - New regression test for the decrypt key inversion bug at src/disc/mod.rs:1938-1942. Asserts decrypt_sectors is invoked with the correct key when opts.decrypt=true. tests/pass_n_size_aware_skip.rs: - rustfmt-only changes. Cargo.toml: 0.17.0 -> 0.17.1.
This commit is contained in:
+31
-4
@@ -2182,15 +2182,21 @@ impl Disc {
|
||||
phase = "patch_skip_limit",
|
||||
range_lba = range_pos / 2048,
|
||||
skip_count,
|
||||
"Skip limit reached - marking range terminal and continuing to next",
|
||||
"Skip limit reached - leaving remaining bytes NonTrimmed for next pass",
|
||||
);
|
||||
// Mark remaining bytes in this range as Unreadable before moving on
|
||||
// CRITICAL: don't mark sectors we NEVER ATTEMPTED as
|
||||
// Unreadable. Only sectors we actually read+failed get
|
||||
// the terminal `-` status. Sectors we jumped over are
|
||||
// hopeful — the drive may read them on a later pass
|
||||
// when state has evolved (cache, mechanical settle).
|
||||
// 2026-05-07 dd-as-oracle test confirmed ~36% of
|
||||
// patch-marked Unreadable sectors are actually readable.
|
||||
let unmarked_bytes = block_end.saturating_sub(*range_pos);
|
||||
if opts.reverse {
|
||||
map.record(
|
||||
*range_pos,
|
||||
unmarked_bytes,
|
||||
mapfile::SectorStatus::Unreadable,
|
||||
mapfile::SectorStatus::NonTrimmed,
|
||||
)
|
||||
.map_err(|e| Error::IoError { source: e })?;
|
||||
} else {
|
||||
@@ -2199,7 +2205,7 @@ impl Disc {
|
||||
map.record(
|
||||
remaining_start,
|
||||
end - remaining_start,
|
||||
mapfile::SectorStatus::Unreadable,
|
||||
mapfile::SectorStatus::NonTrimmed,
|
||||
)
|
||||
.map_err(|e| Error::IoError { source: e })?;
|
||||
}
|
||||
@@ -2237,6 +2243,27 @@ impl Disc {
|
||||
"Starting sector read"
|
||||
);
|
||||
|
||||
// Cache priming: before reading the target sector, do
|
||||
// a few single-sector reads at LBAs immediately preceding
|
||||
// it. The drive's read-ahead cache prefetches forward on
|
||||
// sequential reads — so by the time we ask for `lba` it
|
||||
// may already be cached, even if a cold read fails. Proven
|
||||
// 2026-05-07 with dd-as-oracle: 8/8 sectors recoverable
|
||||
// when primed vs 6/8 cold. Throwaway reads — we already
|
||||
// have those bytes Finished from a prior pass; failures
|
||||
// here don't update mapfile state.
|
||||
const CACHE_PRIME_SECTORS: u32 = 3;
|
||||
if lba >= CACHE_PRIME_SECTORS && count == 1 {
|
||||
let mut prime_buf = [0u8; 2048];
|
||||
for i in 0..CACHE_PRIME_SECTORS {
|
||||
let prime_lba = lba - CACHE_PRIME_SECTORS + i;
|
||||
// Best-effort; ignore errors. Recovery=false is
|
||||
// intentional: a fast 1.5s timeout is fine because
|
||||
// we don't need the data.
|
||||
let _ = reader.read_sectors(prime_lba, 1, &mut prime_buf[..], false);
|
||||
}
|
||||
}
|
||||
|
||||
let read_start = std::time::Instant::now();
|
||||
let read_result = reader.read_sectors(lba, count, &mut buf[..bytes], recovery);
|
||||
let read_duration_ms = read_start.elapsed().as_millis();
|
||||
|
||||
Reference in New Issue
Block a user