libfreemkv: single-pass disc->MKV recovers marginal sectors before giving up
Single-pass disc->MKV has no Pass N, so its read bottom-out now issues one bounded recovery read (recovery=true, ~60s ECC) before skipping or aborting, matching the multipass patch. Fixes a transient/marginal sector surfaced as a read failure direct-to-MKV while multipass recovered it. One read, not a loop (hard rule #2); recovered data is used so no bogus-status hole reopens.
This commit is contained in:
+44
-13
@@ -448,11 +448,47 @@ impl DiscStream {
|
|||||||
|
|
||||||
if (sectors as u32) <= align {
|
if (sectors as u32) <= align {
|
||||||
// Bottomed out at one unit (AACS) / one sector (CSS) / the
|
// Bottomed out at one unit (AACS) / one sector (CSS) / the
|
||||||
// extent tail. Skip the WHOLE failed unit or bail. Zero-filling
|
// extent tail. This is single-pass disc→MKV, which has NO Pass N
|
||||||
// and advancing by the full unit keeps current_offset
|
// to come back and recover later — so before we skip or bail,
|
||||||
// unit-aligned, so the next read still begins on a real AACS
|
// give the drive its full ECC recovery budget ONCE
|
||||||
// unit boundary (a 1-sector skip here would desync the rest of
|
// (`recovery=true` → READ_RECOVERY_TIMEOUT_MS, ~60s), exactly as
|
||||||
// the title — the bug this guards).
|
// the multipass patch does on its bad ranges. A single bounded
|
||||||
|
// read, never a loop (hard rule #2: tight retry loops on one LBA
|
||||||
|
// push the BU40N into fast-fail). On success we USE the recovered
|
||||||
|
// data, so the old "transient retry returns a bogus status for
|
||||||
|
// readable data" hole cannot reopen; the earlier 10s-timeout read
|
||||||
|
// gave the drive no chance to recover a marginal sector that a
|
||||||
|
// 60s ECC read can.
|
||||||
|
tracing::debug!(
|
||||||
|
target: "mux",
|
||||||
|
"fill_extents last-chance recovery read at LBA {} ({} sectors, 60s ECC)",
|
||||||
|
lba,
|
||||||
|
sectors
|
||||||
|
);
|
||||||
|
let rec =
|
||||||
|
self.reader
|
||||||
|
.read_sectors(lba, sectors, &mut self.read_buf[..bytes], true);
|
||||||
|
if let Ok(&got) = rec.as_ref() {
|
||||||
|
debug_assert!(got <= bytes, "recovery read over-reported byte count");
|
||||||
|
if let Some(ev) = self.adaptive.on_success(sectors) {
|
||||||
|
self.emit(ev);
|
||||||
|
}
|
||||||
|
let got = got.min(bytes);
|
||||||
|
self.buf_valid = got;
|
||||||
|
self.current_offset += sectors as u32;
|
||||||
|
self.bytes_read_total = self.bytes_read_total.saturating_add(got as u64);
|
||||||
|
self.emit(EventKind::BytesRead {
|
||||||
|
bytes: self.bytes_read_total,
|
||||||
|
total: self.bytes_total_extents,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recovery read also failed. Skip the WHOLE failed unit or bail.
|
||||||
|
// Zero-filling and advancing by the full unit keeps
|
||||||
|
// current_offset unit-aligned, so the next read still begins on a
|
||||||
|
// real AACS unit boundary (a 1-sector skip here would desync the
|
||||||
|
// rest of the title — the bug this guards).
|
||||||
if self.skip_errors {
|
if self.skip_errors {
|
||||||
let zb = sectors as usize * 2048;
|
let zb = sectors as usize * 2048;
|
||||||
self.read_buf.resize(zb, 0);
|
self.read_buf.resize(zb, 0);
|
||||||
@@ -468,14 +504,9 @@ impl DiscStream {
|
|||||||
self.current_offset += sectors as u32;
|
self.current_offset += sectors as u32;
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
// Build the error from the failure we ALREADY hold.
|
// Build the error from the recovery failure we now hold
|
||||||
// Re-reading the same known-bad LBA here doubled drive
|
// (falling back to the original 10s-read failure).
|
||||||
// abuse (hard rule #2: repeated failed reads on the
|
let err = rec.err().or(res.err());
|
||||||
// same LBA push the BU40N into fast-fail) and, if the
|
|
||||||
// retry transiently succeeded, dropped the good data
|
|
||||||
// and returned a bogus status=0/sense=None error for a
|
|
||||||
// readable sector.
|
|
||||||
let err = res.err();
|
|
||||||
let (status, sense) =
|
let (status, sense) =
|
||||||
err.as_ref().map(extract_scsi_context).unwrap_or((0, None));
|
err.as_ref().map(extract_scsi_context).unwrap_or((0, None));
|
||||||
return Err(crate::error::Error::DiscRead {
|
return Err(crate::error::Error::DiscRead {
|
||||||
|
|||||||
Reference in New Issue
Block a user