v0.13.3: broaden is_wedge_signature — fix dead-code wedge recovery

0.13.2's is_wedge_signature gated on opcode=SCSI_INQUIRY (0x12), but
drive_has_disc issues TEST UNIT READY (0x00). Production wedge errors
(E4000: 0x00/0xff/0x00) never matched → SCSI reset + USB reset
escalation never fired.

Drop the opcode gate. Status byte 0xFF is synthesised by our own
execute() path on poll() timeout — it's the ground-truth wedge
marker for any opcode.

Linux-only; macOS/Windows use sense-key-based wedge detection.
This commit is contained in:
2026-04-24 19:26:11 -07:00
parent 44ac967be9
commit 5f495d7a0b
3 changed files with 31 additions and 10 deletions
+20
View File
@@ -1,5 +1,25 @@
# Changelog # 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) ## 0.13.2 (2026-04-24)
### Public discovery + presence APIs; SCSI/USB primitives no longer ### Public discovery + presence APIs; SCSI/USB primitives no longer
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "libfreemkv" name = "libfreemkv"
version = "0.13.2" version = "0.13.3"
edition = "2024" edition = "2024"
rust-version = "1.86" rust-version = "1.86"
license = "AGPL-3.0-only" license = "AGPL-3.0-only"
+10 -9
View File
@@ -694,25 +694,26 @@ fn recover_then_probe(path: &Path, original: Error) -> Result<bool> {
Err(original) Err(original)
} }
/// Wedge signature: `Error::ScsiError` with INQUIRY opcode (0x12) and /// Wedge signature: `Error::ScsiError` with status byte 0xFF, for any
/// status byte 0xFF. 0xFF isn't a valid SCSI status — the kernel synthesises /// opcode. 0xFF isn't a real SCSI status — our own `execute()` path
/// it when the device gives no answer, which is the real-world signature /// synthesises it when poll() times out waiting for the kernel to
/// of a USB Mass Storage layer wedge. /// 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 { fn is_wedge_signature(err: &Error) -> bool {
matches!( matches!(
err, err,
Error::ScsiError { Error::ScsiError {
opcode: crate::scsi::SCSI_INQUIRY,
status: WEDGE_STATUS_BYTE, status: WEDGE_STATUS_BYTE,
.. ..
} }
) )
} }
/// Synthesised SCSI status byte returned by the Linux SG driver when /// Synthesised SCSI status byte returned by our own transport when
/// the kernel got no useful response from the device — the wedge /// poll() on the SG fd times out — the wedge signature. Real SCSI
/// signature. Real SCSI statuses are GOOD (0x00), CHECK_CONDITION (0x02), /// statuses are GOOD (0x00), CHECK_CONDITION (0x02), BUSY (0x08), etc.;
/// BUSY (0x08), etc.; 0xFF is reserved/invalid in the spec. /// 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; const WEDGE_STATUS_BYTE: u8 = 0xFF;
/// Settle time after `USBDEVFS_RESET` returns. The kernel re-enumerates /// Settle time after `USBDEVFS_RESET` returns. The kernel re-enumerates