- 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).
88 lines
2.7 KiB
Rust
88 lines
2.7 KiB
Rust
// Mimics ISO dump exactly — read + write + progress
|
|
use libfreemkv::Drive;
|
|
use std::io::Write;
|
|
use std::path::Path;
|
|
use std::time::Instant;
|
|
|
|
fn main() {
|
|
let device = std::env::args()
|
|
.skip(1)
|
|
.find(|a| !a.starts_with('-'))
|
|
.unwrap_or_else(|| match libfreemkv::find_drive() {
|
|
Some(d) => d.device_path().to_string(),
|
|
None => {
|
|
eprintln!("No drives found");
|
|
std::process::exit(1);
|
|
}
|
|
});
|
|
|
|
let mut drive = Drive::open(Path::new(&device)).unwrap_or_else(|e| {
|
|
eprintln!("Cannot open {}: {}", device, e);
|
|
std::process::exit(1);
|
|
});
|
|
eprintln!("wait_ready...");
|
|
let _ = drive.wait_ready();
|
|
eprintln!("read_capacity...");
|
|
let cap = drive.read_capacity().unwrap();
|
|
eprintln!("capacity: {} sectors", cap);
|
|
|
|
let batch = libfreemkv::disc::detect_max_batch_sectors(drive.device_path());
|
|
let mut buf = vec![0u8; batch as usize * 2048];
|
|
|
|
// Open /dev/null writer like ISO dump does
|
|
let file = std::fs::File::create("/dev/null").unwrap();
|
|
let mut writer = std::io::BufWriter::with_capacity(4 * 1024 * 1024, file);
|
|
|
|
eprintln!(
|
|
"Reading 1000 batches ({:.1} MB) with write + progress...",
|
|
1000.0 * batch as f64 * 2048.0 / 1_048_576.0
|
|
);
|
|
|
|
let start = Instant::now();
|
|
let mut ok = 0u32;
|
|
let mut fail = 0u32;
|
|
let mut bytes: u64 = 0;
|
|
|
|
// Recovery flag: true matches pre-0.11.13 bench behavior — full SCSI
|
|
// ECC retry loop on errors (slower, what the rip path used before the
|
|
// adaptive batch sizer landed). Flip to `false` for the fast-fail path
|
|
// that current rips use; benches are configurable via this constant.
|
|
const READ_WITH_RECOVERY: bool = true;
|
|
|
|
for i in 0..1000u32 {
|
|
let lba = i * batch as u32;
|
|
match drive.read(lba, batch, &mut buf, READ_WITH_RECOVERY) {
|
|
Ok(_) => {
|
|
writer.write_all(&buf).unwrap();
|
|
ok += 1;
|
|
}
|
|
Err(e) => {
|
|
fail += 1;
|
|
if fail <= 5 {
|
|
eprintln!(" FAIL LBA {}: {}", lba, e);
|
|
}
|
|
buf.fill(0);
|
|
writer.write_all(&buf).unwrap();
|
|
}
|
|
}
|
|
bytes += buf.len() as u64;
|
|
|
|
if i % 50 == 0 && i > 0 {
|
|
let elapsed = start.elapsed().as_secs_f64();
|
|
let mb = bytes as f64 / 1_048_576.0;
|
|
eprint!("\r {:.1} MB | {:.1} MB/s ", mb, mb / elapsed);
|
|
}
|
|
}
|
|
|
|
let elapsed = start.elapsed().as_secs_f64();
|
|
let mb = ok as f64 * batch as f64 * 2048.0 / 1_048_576.0;
|
|
eprintln!(
|
|
"\n{} ok, {} fail, {:.1} MB in {:.1}s = {:.1} MB/s",
|
|
ok,
|
|
fail,
|
|
mb,
|
|
elapsed,
|
|
mb / elapsed
|
|
);
|
|
}
|