diff --git a/CHANGELOG.md b/CHANGELOG.md index 25d0487..9864129 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 0.13.3 (2026-04-24) + +### Bug fix — `drive_has_disc` wedge recovery was dead code for TUR errors + +The wedge-signature predicate introduced in 0.13.2 gated on +`opcode == SCSI_INQUIRY (0x12)` — a holdover from when enumerate-time +INQUIRY was the only path wedges surfaced on. `drive_has_disc` issues +`TEST UNIT READY (0x00)`, so its wedge errors (`E4000: 0x00/0xff/0x00`) +never matched the predicate and the SCSI-reset + USB-reset escalation +never fired. Production result: the BU40N USB BD-RE stayed wedged +indefinitely, with autorip logging `recovery exhausted` on the raw +pass-through error while no recovery had actually been attempted. + +Fix: drop the opcode constraint. Status byte `0xFF` is synthesised by +our own `execute()` path when `poll()` on the SG fd times out; it's +the ground-truth wedge marker regardless of which opcode was in flight. +Doc comments on `is_wedge_signature` and `WEDGE_STATUS_BYTE` updated +accordingly. Linux-only — macOS / Windows use sense-key-based wedge +detection and are unaffected. + ## 0.13.2 (2026-04-24) ### Public discovery + presence APIs; SCSI/USB primitives no longer diff --git a/Cargo.toml b/Cargo.toml index 670211e..a366c96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libfreemkv" -version = "0.13.2" +version = "0.13.3" edition = "2024" rust-version = "1.86" license = "AGPL-3.0-only" diff --git a/src/scsi/linux.rs b/src/scsi/linux.rs index c47cd82..c0a7aca 100644 --- a/src/scsi/linux.rs +++ b/src/scsi/linux.rs @@ -694,25 +694,26 @@ fn recover_then_probe(path: &Path, original: Error) -> Result { Err(original) } -/// Wedge signature: `Error::ScsiError` with INQUIRY opcode (0x12) and -/// status byte 0xFF. 0xFF isn't a valid SCSI status — the kernel synthesises -/// it when the device gives no answer, which is the real-world signature -/// of a USB Mass Storage layer wedge. +/// Wedge signature: `Error::ScsiError` with status byte 0xFF, for any +/// opcode. 0xFF isn't a real SCSI status — our own `execute()` path +/// synthesises it when poll() times out waiting for the kernel to +/// deliver a response. That timeout is the ground-truth signature of +/// the USB Mass Storage layer wedging; opcode-in-flight is incidental. fn is_wedge_signature(err: &Error) -> bool { matches!( err, Error::ScsiError { - opcode: crate::scsi::SCSI_INQUIRY, status: WEDGE_STATUS_BYTE, .. } ) } -/// Synthesised SCSI status byte returned by the Linux SG driver when -/// the kernel got no useful response from the device — the wedge -/// signature. Real SCSI statuses are GOOD (0x00), CHECK_CONDITION (0x02), -/// BUSY (0x08), etc.; 0xFF is reserved/invalid in the spec. +/// Synthesised SCSI status byte returned by our own transport when +/// poll() on the SG fd times out — the wedge signature. Real SCSI +/// statuses are GOOD (0x00), CHECK_CONDITION (0x02), BUSY (0x08), etc.; +/// 0xFF is reserved/invalid in the spec, so we can't collide with a +/// real device response. Applies to any opcode (TUR, INQUIRY, READ, …). const WEDGE_STATUS_BYTE: u8 = 0xFF; /// Settle time after `USBDEVFS_RESET` returns. The kernel re-enumerates