v0.11.9: fast verify reads — 5s timeout, no recovery loop

This commit is contained in:
Matt Jackson
2026-04-20 01:01:44 +00:00
parent a693ef965b
commit 8870efa77b
5 changed files with 47 additions and 5 deletions
+6
View File
@@ -1,5 +1,11 @@
# Changelog # Changelog
## 0.11.9 (2026-04-20)
### Fast verify reads
- **read_sectors_fast()** — single-attempt 5s timeout SCSI read for verify. No recovery loop. Bad sectors detected in seconds instead of 10+ minutes.
- **SectorReader trait** — added read_sectors_fast() with default fallback to read_sectors().
## 0.11.8 (2026-04-20) ## 0.11.8 (2026-04-20)
### Disc verify ### Disc verify
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "libfreemkv" name = "libfreemkv"
version = "0.11.8" version = "0.11.9"
edition = "2021" edition = "2021"
rust-version = "1.86" rust-version = "1.86"
license = "AGPL-3.0-only" license = "AGPL-3.0-only"
+30
View File
@@ -542,6 +542,32 @@ impl Drive {
Err(Error::DiscRead { sector: lba as u64 }) Err(Error::DiscRead { sector: lba as u64 })
} }
/// Fast single-attempt read for verification — no recovery, short timeout.
/// Returns Ok on success, Err on any failure. Does not retry.
pub fn read_fast(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result<usize> {
let cdb = [
crate::scsi::SCSI_READ_10,
0x00,
(lba >> 24) as u8,
(lba >> 16) as u8,
(lba >> 8) as u8,
lba as u8,
0x00,
(count >> 8) as u8,
count as u8,
0x00,
];
match self.scsi.as_mut().execute(
&cdb,
crate::scsi::DataDirection::FromDevice,
buf,
5_000, // 5 second timeout — enough for healthy reads, fast fail for bad sectors
) {
Ok(result) => Ok(result.bytes_transferred),
Err(_) => Err(Error::DiscRead { sector: lba as u64 }),
}
}
/// Read the disc capacity in sectors (2048 bytes each). /// Read the disc capacity in sectors (2048 bytes each).
pub fn read_capacity(&mut self) -> Result<u32> { pub fn read_capacity(&mut self) -> Result<u32> {
let cdb = [ let cdb = [
@@ -643,6 +669,10 @@ impl SectorReader for Drive {
fn read_sectors(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result<usize> { fn read_sectors(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result<usize> {
self.read(lba, count, buf) self.read(lba, count, buf)
} }
fn read_sectors_fast(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result<usize> {
self.read_fast(lba, count, buf)
}
} }
/// Find all optical drives connected to this system. /// Find all optical drives connected to this system.
+6
View File
@@ -12,6 +12,12 @@ pub trait SectorReader: Send {
/// `buf` must be at least `count * 2048` bytes. /// `buf` must be at least `count * 2048` bytes.
fn read_sectors(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result<usize>; fn read_sectors(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result<usize>;
/// Fast single-attempt read — no recovery, short timeout.
/// Default implementation calls read_sectors (ISO files don't need fast mode).
fn read_sectors_fast(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result<usize> {
self.read_sectors(lba, count, buf)
}
/// Total capacity in sectors, if known. /// Total capacity in sectors, if known.
fn capacity(&self) -> u32 { fn capacity(&self) -> u32 {
0 0
+4 -4
View File
@@ -105,7 +105,7 @@ pub fn verify_title(
let batch_start = Instant::now(); let batch_start = Instant::now();
let batch_ok = reader let batch_ok = reader
.read_sectors(lba, count, &mut buf[..bytes]) .read_sectors_fast(lba, count, &mut buf[..bytes])
.is_ok(); .is_ok();
let batch_ms = batch_start.elapsed().as_millis(); let batch_ms = batch_start.elapsed().as_millis();
@@ -144,7 +144,7 @@ pub fn verify_title(
let s1 = Instant::now(); let s1 = Instant::now();
let first_ok = reader let first_ok = reader
.read_sectors(sector_lba, 1, &mut buf[sector_offset..sector_offset + 2048]) .read_sectors_fast(sector_lba, 1, &mut buf[sector_offset..sector_offset + 2048])
.is_ok(); .is_ok();
let s1_ms = s1.elapsed().as_millis(); let s1_ms = s1.elapsed().as_millis();
@@ -155,10 +155,10 @@ pub fn verify_title(
slow += 1; slow += 1;
SectorStatus::Slow SectorStatus::Slow
} else { } else {
// Retry once more // Retry once more after brief pause
std::thread::sleep(std::time::Duration::from_secs(2)); std::thread::sleep(std::time::Duration::from_secs(2));
if reader if reader
.read_sectors(sector_lba, 1, &mut buf[sector_offset..sector_offset + 2048]) .read_sectors_fast(sector_lba, 1, &mut buf[sector_offset..sector_offset + 2048])
.is_ok() .is_ok()
{ {
recovered += 1; recovered += 1;