v0.13.20 — sync blocking SG_IO + cross-platform parity strip
- scsi/linux.rs: full rewrite from async write/poll/read+1.5s timeout+ close-on-timeout to one synchronous ioctl(fd, SG_IO, &hdr). Kernel honors hdr.timeout and runs its own ABORT/RESET escalation. Errors check host_status and driver_status (both 0xFF-synthesised) plus status. Sense-key parser handles descriptor (0x72/0x73) + fixed (0x70/0x71) formats. Deleted fd_recovery, bg close+open thread, fd swap dance. -331/+155 lines. - scsi/macos.rs: try_recover() removed (userspace handle-recovery on task failure was the same anti-pattern stripped from Linux). bsd_name field deleted. Errors bubble up directly. - scsi/windows.rs: try_recover() removed, wide_path field deleted, INVALID_HANDLE guard removed. - scsi/mod.rs: parse_sense_key() helper extracted (used by all three platforms now — single canonical sense-key parse rather than three inlined copies). +10 unit tests covering descriptor format, fixed format, truncated buffers, unknown response codes. - drive/mod.rs: Drive::reset() deleted (escalating eject + STOP/START + reinit recovery — per audit, kernel handles its own escalation; userspace shouldn't). pub fn find_drives() -> Vec<Drive> deleted (opened N drives just to throw most away). find_drive() now uses discover_drives() directly. wait_ready() simplified — drops the reset path on sense_key=5, just keeps polling TUR for 60 iterations. - lib.rs: find_drives re-export removed. - benches/sgio_read.rs: switched to find_drive() (no longer iterates a drive list). Net: 9 files changed, 226 insertions(+), 473 deletions(-). 329 tests pass, clippy -D warnings clean. No consumer breakage (CLI, autorip, bdemu compile + test green). Architecture decision documented in (internal)/docs/audits/2026-04-26-scsi-architecture-research.md (primary-source survey of MakeMKV, sg_dd, ddrescue, and the kernel mid-layer's own scsi_eh.rst escalation ladder).
This commit is contained in:
+16
-55
@@ -85,15 +85,11 @@ unsafe extern "system" {
|
||||
|
||||
pub struct SptiTransport {
|
||||
handle: isize,
|
||||
/// Wide-encoded device path used by `try_recover()` to reopen the
|
||||
/// handle after a failed DeviceIoControl. Saved from `open()` so we
|
||||
/// don't have to re-resolve the device path on recovery.
|
||||
wide_path: Vec<u16>,
|
||||
}
|
||||
|
||||
// SptiTransport contains an isize HANDLE and a Vec<u16>; both Send. The
|
||||
// auto-derived Send is intentional. Sync is NOT — handle mutation in
|
||||
// execute() requires &mut, enforced by the trait object dispatch.
|
||||
// SptiTransport's only field is the isize HANDLE — Send is auto-derived
|
||||
// and intentional. Sync is NOT: handle mutation in execute() requires
|
||||
// &mut, enforced by the trait object dispatch.
|
||||
|
||||
/// Normalize a device path to Windows \\.\X: format.
|
||||
///
|
||||
@@ -151,34 +147,7 @@ impl SptiTransport {
|
||||
});
|
||||
}
|
||||
|
||||
Ok(SptiTransport {
|
||||
handle,
|
||||
wide_path: wide,
|
||||
})
|
||||
}
|
||||
|
||||
/// Recover the handle after a failed DeviceIoControl. Closes the bad
|
||||
/// handle and opens a fresh one synchronously (CloseHandle/CreateFileW
|
||||
/// are fast on Windows — no in-flight CDB to drain like Linux SG_IO).
|
||||
/// On success, `self.handle` is replaced and the next `execute()` call
|
||||
/// uses the new handle. On failure, `self.handle` is set to
|
||||
/// INVALID_HANDLE_VALUE and subsequent calls return `DeviceNotFound`.
|
||||
fn try_recover(&mut self) {
|
||||
if self.handle != INVALID_HANDLE_VALUE {
|
||||
unsafe { CloseHandle(self.handle) };
|
||||
}
|
||||
let new_handle = unsafe {
|
||||
CreateFileW(
|
||||
self.wide_path.as_ptr(),
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
std::ptr::null(),
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
std::ptr::null(),
|
||||
)
|
||||
};
|
||||
self.handle = new_handle;
|
||||
Ok(SptiTransport { handle })
|
||||
}
|
||||
|
||||
/// Reset the drive to a known good state.
|
||||
@@ -282,15 +251,6 @@ impl ScsiTransport for SptiTransport {
|
||||
data: &mut [u8],
|
||||
timeout_ms: u32,
|
||||
) -> Result<ScsiResult> {
|
||||
// Per RIP_DESIGN.md §15.1: parity with Linux's recovery contract.
|
||||
// If a prior call invalidated the handle and try_recover() also
|
||||
// failed, fail fast.
|
||||
if self.handle == INVALID_HANDLE_VALUE {
|
||||
return Err(Error::DeviceNotFound {
|
||||
path: String::new(),
|
||||
});
|
||||
}
|
||||
|
||||
// Zero the data buffer for reads to prevent returning uninitialized data
|
||||
// if the driver doesn't fully update DataTransferLength.
|
||||
if direction == DataDirection::FromDevice {
|
||||
@@ -339,12 +299,13 @@ impl ScsiTransport for SptiTransport {
|
||||
};
|
||||
|
||||
if ok == 0 {
|
||||
// Driver-level failure (timeout, handle gone, etc.). Recover
|
||||
// the handle so the caller's retry loop can resume — same
|
||||
// observable contract as Linux's async fd recovery, but
|
||||
// synchronous because Windows's CloseHandle/CreateFileW don't
|
||||
// block on in-flight CDBs.
|
||||
self.try_recover();
|
||||
// Driver-level failure (timeout, handle gone, etc.). Bubble
|
||||
// up; in-library handle recovery was removed in 0.13.20 along
|
||||
// with Linux's async fd-recovery and macOS's `try_recover` —
|
||||
// the kernel mid-layer already did its escalation by the time
|
||||
// DeviceIoControl returned, and re-issuing reset/reopen here
|
||||
// is at best redundant and at worst deepens the wedge. Caller
|
||||
// surfaces the failure to UX.
|
||||
return Err(Error::ScsiError {
|
||||
opcode: cdb[0],
|
||||
status: 0xFF,
|
||||
@@ -353,11 +314,11 @@ impl ScsiTransport for SptiTransport {
|
||||
}
|
||||
|
||||
if sptwb.spt.ScsiStatus != 0 {
|
||||
let sense_key = if sptwb.sense[2] != 0 {
|
||||
sptwb.sense[2] & 0x0F
|
||||
} else {
|
||||
0
|
||||
};
|
||||
// SPTI doesn't surface a "bytes written into sense buffer"
|
||||
// count separate from SenseInfoLength (input). Pass the full
|
||||
// K_SENSE_SIZE; parse_sense_key keys off byte 0's response
|
||||
// code to handle descriptor (0x72/0x73) vs fixed (0x70/0x71).
|
||||
let sense_key = super::parse_sense_key(&sptwb.sense, K_SENSE_SIZE as u8);
|
||||
return Err(Error::ScsiError {
|
||||
opcode: cdb[0],
|
||||
status: sptwb.spt.ScsiStatus,
|
||||
|
||||
Reference in New Issue
Block a user