v0.11.9: fast verify reads — 5s timeout, no recovery loop
This commit is contained in:
@@ -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
@@ -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"
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user