v0.13.45: multipass adaptive probe, NOT_READY retry, progress bytes_bad_total

- Adaptive probe algorithm in Disc::copy skip_on_error mode: after 4
  consecutive errors, probe 1 sector at 256x batch (8 MB) ahead. If
  good, zero-fill gap, mark NonTrimmed, jump. Clears bad zones in
  seconds instead of hours.
- NOT_READY sense key (0x02) now retries up to 3x with 3s pause before
  marking NonTrimmed. BU40N returns NOT READY for bad sectors, not
  MEDIUM ERROR.
- PassProgress struct gains bytes_bad_total field for consumer-side
  bad/retryable byte counts.
- Mapfile header version string fix: no longer duplicates 'libfreemkv v'
  prefix on each write.
- Structured sense_key/asc/ascq logging in copy error path.
This commit is contained in:
MattJackson
2026-04-29 19:41:34 -07:00
parent 9e3d0f1383
commit a6f1bd19bc
4 changed files with 93 additions and 105 deletions
+10 -14
View File
@@ -439,7 +439,7 @@ fn test_disc_copy_completes_full_disc_with_failing_reader() {
let opts = CopyOptions {
decrypt: false,
skip_on_error: true,
error_pause_ms: Some(0),
..Default::default()
};
@@ -519,7 +519,7 @@ fn test_disc_copy_halts_promptly_on_failing_reader() {
let opts = CopyOptions {
decrypt: false,
skip_on_error: true,
error_pause_ms: Some(0),
halt: Some(halt),
..Default::default()
};
@@ -607,7 +607,7 @@ fn test_disc_copy_marks_failed_ecc_blocks_as_nontrimmed() {
let opts = CopyOptions {
decrypt: false,
skip_on_error: true,
error_pause_ms: Some(0),
..Default::default()
};
@@ -618,20 +618,16 @@ fn test_disc_copy_marks_failed_ecc_blocks_as_nontrimmed() {
let _ = std::fs::remove_file(&iso_path);
let _ = std::fs::remove_file(libfreemkv::disc::mapfile_path_for(&iso_path));
// With graduated batch restore, Pass 1 drops to batch=1 after the
// first batch=32 failure, reads individually (count=1 succeeds for
// BlockSizeFailingReader), and recovers those sectors. After 200 OK
// at batch=1, it tries batch=2 which fails again. Net result: most
// sectors are recovered (Finished), only the batch>1 failures produce
// NonTrimmed blocks.
assert!(
result.bytes_good > 0,
"Pass 1 should recover batch=1-readable sectors. Got bytes_good={}",
result.bytes_good
// Pass 1 reads every batch at bpt=32 (no batch reduction, no skip-ahead).
// BlockSizeFailingReader fails on all batch=32 reads, so every sector
// is marked NonTrimmed. bytes_good=0 is correct — Pass 2 recovers them.
assert_eq!(
result.bytes_good, 0,
"Pass 1 at bpt=32 should not recover any sectors when all batches fail"
);
assert!(
result.bytes_pending > 0,
"batch>1 failures should produce NonTrimmed pending Pass 2"
"all sectors should be NonTrimmed pending Pass 2"
);
assert!(
!result.complete,