From c8822ddea3d311285f766ba2957778ef5ac51b04 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Tue, 30 Jun 2026 07:19:03 -0700 Subject: [PATCH] error: treat IoError/DeviceNotFound as SCSI transport failures (dead bus) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit is_scsi_transport_failure recognized only the 0xFF SCSI sentinel, so the two NON-SCSI dead-bus faults the Linux SG_IO transport returns — Error::IoError (ioctl(SG_IO) == -1: ENODEV/EIO on an unplugged bridge) and Error::DeviceNotFound (fd gone) — were classified as recoverable bad sectors. Every caller (sweep read_error, patch, single-pass fill_extents) would then zero-fill / skip-ahead across a wedged bus instead of aborting the pass and re-enumerating the bridge — potentially zero-filling a huge span. Add both to the classifier so a dead bus aborts the pass everywhere. (Companion to the unlock_bridge fix, which maps the same two faults to 0xFF across the freemkv-unlock seam.) --- src/error.rs | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/error.rs b/src/error.rs index a206a5b..6425be5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -879,6 +879,15 @@ impl Error { status: Some(crate::scsi::SCSI_STATUS_TRANSPORT_FAILURE), .. } + ) || matches!( + // A failed `ioctl(SG_IO)` (Error::IoError, e.g. ENODEV/EIO on an + // unplugged USB bridge) and a vanished device (Error::DeviceNotFound, + // fd gone) are dead-bus / transport-layer faults too — NOT recoverable + // bad sectors. Treat them as transport failures so sweep / patch / + // fill_extents abort the pass and re-enumerate the bridge instead of + // zero-filling every read against a wedged device. + self, + Error::IoError { .. } | Error::DeviceNotFound { .. } ) } @@ -1375,11 +1384,13 @@ mod tests { } } - /// is_scsi_transport_failure returns true only for SCSI_STATUS_TRANSPORT_FAILURE (0xFF). - /// Spec: comment on SCSI_STATUS_TRANSPORT_FAILURE says "synthesised sentinel: the - /// transport never delivered a SCSI status byte". + /// is_scsi_transport_failure is true for the 0xFF SCSI sentinel AND for the + /// non-SCSI dead-bus faults (Error::IoError from a failed ioctl(SG_IO), + /// Error::DeviceNotFound from a vanished fd) — but NEVER for a real SCSI + /// reply (CHECK CONDITION) or unrelated errors. /// Mutation: testing against 0x02 (CHECK CONDITION) would wrongly mark CHECK - /// CONDITION replies as transport failures. + /// CONDITION replies as transport failures; dropping the IoError/ + /// DeviceNotFound arm would let a dead bus zero-fill the disc. #[test] fn is_scsi_transport_failure_only_for_0xff() { use crate::scsi::SCSI_STATUS_TRANSPORT_FAILURE; @@ -1403,7 +1414,22 @@ mod tests { }; assert!(!cc.is_scsi_transport_failure()); - // False for non-SCSI errors. + // True: non-SCSI dead-bus faults — a failed ioctl(SG_IO) and a vanished + // device are transport-layer failures, not recoverable bad sectors. + assert!( + Error::IoError { + source: std::io::Error::from(std::io::ErrorKind::NotConnected) + } + .is_scsi_transport_failure() + ); + assert!( + Error::DeviceNotFound { + path: "/dev/sg9".into() + } + .is_scsi_transport_failure() + ); + + // False for unrelated errors. assert!(!Error::Halted.is_scsi_transport_failure()); }