updates
This commit is contained in:
@@ -325,6 +325,7 @@ impl Drive for ImageDrive {
|
||||
TimedRead {
|
||||
result,
|
||||
milliseconds: ms,
|
||||
microseconds: ms as i64 * 1000,
|
||||
}
|
||||
}
|
||||
fn speed_set(&mut self, _speed: i32) -> i32 {
|
||||
|
||||
+128
-64
@@ -179,6 +179,12 @@ pub struct LinuxDrive {
|
||||
read_cmd: ReadCommand,
|
||||
enable_mode: EnableMode,
|
||||
last_milliseconds: i32,
|
||||
last_microseconds: i64,
|
||||
error_retry: bool,
|
||||
/// Scratch buffer used by timing/analysis reads that do not request a
|
||||
/// destination buffer. Reusing it avoids a multi-megabyte allocation on
|
||||
/// every `read_timed(None, ...)` call, which would distort cache timing.
|
||||
scratch: Vec<u8>,
|
||||
}
|
||||
|
||||
impl LinuxDrive {
|
||||
@@ -201,6 +207,9 @@ impl LinuxDrive {
|
||||
read_cmd: ReadCommand::D8,
|
||||
enable_mode: EnableMode::Dummy,
|
||||
last_milliseconds: 0,
|
||||
last_microseconds: -1,
|
||||
error_retry: true,
|
||||
scratch: Vec::new(),
|
||||
};
|
||||
d.inquiry()?;
|
||||
d.check_atapi();
|
||||
@@ -241,6 +250,9 @@ impl LinuxDrive {
|
||||
read_cmd: ReadCommand::Read28,
|
||||
enable_mode: EnableMode::Dummy,
|
||||
last_milliseconds: 0,
|
||||
last_microseconds: -1,
|
||||
error_retry: true,
|
||||
scratch: Vec::new(),
|
||||
};
|
||||
Ok(d)
|
||||
}
|
||||
@@ -566,7 +578,9 @@ impl LinuxDrive {
|
||||
};
|
||||
let started = Instant::now();
|
||||
let rc = unsafe { libc::ioctl(self.fd, SG_IO, &mut hdr as *mut SgIoHdr) };
|
||||
self.last_milliseconds = started.elapsed().as_millis() as i32;
|
||||
let elapsed = started.elapsed();
|
||||
self.last_microseconds = elapsed.as_micros() as i64;
|
||||
self.last_milliseconds = ((elapsed.as_micros() + 500) / 1000) as i32;
|
||||
if rc < 0 {
|
||||
return Err(ScsiError::io(io::Error::last_os_error()));
|
||||
}
|
||||
@@ -703,10 +717,14 @@ impl LinuxDrive {
|
||||
let _ = use_msf;
|
||||
cdb[1] |= ((self.lun as u8) & 0x07) << 5;
|
||||
let actual = self.sg_command(&cdb[..len], Some(&mut buf[..need]), SG_DXFER_FROM_DEV)?;
|
||||
let count = count_2352_bytes(&buf[..need]);
|
||||
let by_count = count / CD_FRAMESIZE_RAW;
|
||||
let by_actual = actual / CD_FRAMESIZE_RAW;
|
||||
Ok(by_count.min(by_actual))
|
||||
// A successful ioctl with a completely untouched buffer means the
|
||||
// transfer failed silently. Do not inspect individual trailing
|
||||
// bytes: valid audio may legitimately end in any byte sequence,
|
||||
// including the 0x7f fill pattern.
|
||||
if actual >= need && buf[..need].iter().all(|b| *b == 0x7F) {
|
||||
return Ok(0);
|
||||
}
|
||||
Ok((actual / CD_FRAMESIZE_RAW).min(sectors as usize))
|
||||
}
|
||||
|
||||
fn scsi_read_map(
|
||||
@@ -715,15 +733,28 @@ impl LinuxDrive {
|
||||
p: Option<&mut [u8]>,
|
||||
begin: i64,
|
||||
sectors: i64,
|
||||
) -> Result<usize, ReadError> {
|
||||
let mut scratch = std::mem::take(&mut self.scratch);
|
||||
let result = self.scsi_read_map_inner(command, p, begin, sectors, &mut scratch);
|
||||
self.scratch = scratch;
|
||||
result
|
||||
}
|
||||
|
||||
fn scsi_read_map_inner(
|
||||
&mut self,
|
||||
command: ReadCommand,
|
||||
p: Option<&mut [u8]>,
|
||||
begin: i64,
|
||||
sectors: i64,
|
||||
scratch: &mut Vec<u8>,
|
||||
) -> Result<usize, ReadError> {
|
||||
let mut requested = sectors.min(self.nsectors).max(1);
|
||||
let mut retry_count = 0;
|
||||
let mut scratch;
|
||||
let buffer: &mut [u8] = match p {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
scratch = vec![0u8; (requested * CD_FRAMESIZE_RAW as i64) as usize];
|
||||
&mut scratch
|
||||
scratch.resize((requested * CD_FRAMESIZE_RAW as i64) as usize, 0);
|
||||
scratch.as_mut_slice()
|
||||
}
|
||||
};
|
||||
if buffer.len() < (requested * CD_FRAMESIZE_RAW as i64) as usize {
|
||||
@@ -748,6 +779,12 @@ impl LinuxDrive {
|
||||
if errno == libc::ENOMEDIUM {
|
||||
return Err(ReadError::no_medium());
|
||||
}
|
||||
if !self.error_retry {
|
||||
return Err(ReadError::new(
|
||||
errno,
|
||||
format!("Read error at sector {}", begin),
|
||||
));
|
||||
}
|
||||
if requested == 1 && retry_count > MAX_RETRIES - 1 {
|
||||
return Err(ReadError::new(
|
||||
errno,
|
||||
@@ -801,15 +838,27 @@ impl LinuxDrive {
|
||||
p: Option<&mut [u8]>,
|
||||
begin: i64,
|
||||
sectors: i64,
|
||||
) -> Result<usize, ReadError> {
|
||||
let mut scratch = std::mem::take(&mut self.scratch);
|
||||
let result = self.cooked_read_inner(p, begin, sectors, &mut scratch);
|
||||
self.scratch = scratch;
|
||||
result
|
||||
}
|
||||
|
||||
fn cooked_read_inner(
|
||||
&mut self,
|
||||
p: Option<&mut [u8]>,
|
||||
begin: i64,
|
||||
sectors: i64,
|
||||
scratch: &mut Vec<u8>,
|
||||
) -> Result<usize, ReadError> {
|
||||
let mut requested = sectors.min(self.nsectors).max(1);
|
||||
let mut retry = 0;
|
||||
let mut scratch;
|
||||
let buf: &mut [u8] = match p {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
scratch = vec![0u8; (requested * CD_FRAMESIZE_RAW as i64) as usize];
|
||||
&mut scratch
|
||||
scratch.resize((requested * CD_FRAMESIZE_RAW as i64) as usize, 0);
|
||||
scratch.as_mut_slice()
|
||||
}
|
||||
};
|
||||
if buf.len() < (requested * CD_FRAMESIZE_RAW as i64) as usize {
|
||||
@@ -826,12 +875,20 @@ impl LinuxDrive {
|
||||
};
|
||||
let started = Instant::now();
|
||||
let rc = unsafe { libc::ioctl(self.fd, CDROMREADAUDIO, &mut arg as *mut _) };
|
||||
self.last_milliseconds = started.elapsed().as_millis() as i32;
|
||||
let elapsed = started.elapsed();
|
||||
self.last_microseconds = elapsed.as_micros() as i64;
|
||||
self.last_milliseconds = ((elapsed.as_micros() + 500) / 1000) as i32;
|
||||
if rc == 0 {
|
||||
return Ok(requested as usize);
|
||||
}
|
||||
let err = io::Error::last_os_error();
|
||||
let errno = err.raw_os_error().unwrap_or(libc::EIO);
|
||||
if !self.error_retry {
|
||||
return Err(ReadError::new(
|
||||
errno,
|
||||
format!("Read error at sector {}", begin),
|
||||
));
|
||||
}
|
||||
if errno == libc::ENXIO || errno == libc::EBADF || errno == libc::ENOMEDIUM {
|
||||
return Err(ReadError::no_medium());
|
||||
}
|
||||
@@ -859,40 +916,33 @@ impl LinuxDrive {
|
||||
|
||||
fn verify_read_command(&mut self) -> Result<(), String> {
|
||||
let mut buf = vec![0u8; CD_FRAMESIZE_RAW];
|
||||
let mut tried_any = false;
|
||||
let tried = [self.read_cmd];
|
||||
for command in tried {
|
||||
if self.try_read_command(command, &mut buf) {
|
||||
if self.try_read_command(self.read_cmd, &mut buf, false) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let all = [
|
||||
ReadCommand::Read28,
|
||||
ReadCommand::ReadA8,
|
||||
ReadCommand::MmcB10,
|
||||
ReadCommand::MmcB2_10,
|
||||
ReadCommand::MmcB6_F8,
|
||||
ReadCommand::MmcB2_F8,
|
||||
ReadCommand::MmcB4_F8,
|
||||
ReadCommand::Mmc2_F8,
|
||||
ReadCommand::Mmc3_F8,
|
||||
ReadCommand::Msf10,
|
||||
ReadCommand::MsfF8,
|
||||
ReadCommand::Msf3F8,
|
||||
ReadCommand::D4_10,
|
||||
ReadCommand::D4_12,
|
||||
ReadCommand::D5,
|
||||
ReadCommand::D8,
|
||||
];
|
||||
for command in all {
|
||||
if self.try_read_command(command, &mut buf, true) {
|
||||
self.read_cmd = command;
|
||||
return Ok(());
|
||||
}
|
||||
tried_any = true;
|
||||
}
|
||||
if tried_any {
|
||||
let all = [
|
||||
ReadCommand::Read28,
|
||||
ReadCommand::ReadA8,
|
||||
ReadCommand::MmcB10,
|
||||
ReadCommand::MmcB2_10,
|
||||
ReadCommand::MmcB6_F8,
|
||||
ReadCommand::MmcB2_F8,
|
||||
ReadCommand::MmcB4_F8,
|
||||
ReadCommand::Mmc2_F8,
|
||||
ReadCommand::Mmc3_F8,
|
||||
ReadCommand::Msf10,
|
||||
ReadCommand::MsfF8,
|
||||
ReadCommand::Msf3F8,
|
||||
ReadCommand::D4_10,
|
||||
ReadCommand::D4_12,
|
||||
ReadCommand::D5,
|
||||
ReadCommand::D8,
|
||||
];
|
||||
for command in all {
|
||||
if self.try_read_command(command, &mut buf) {
|
||||
self.read_cmd = command;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(
|
||||
"Unable to find any suitable command set from probe; drive probably not CDDA capable"
|
||||
@@ -900,7 +950,12 @@ impl LinuxDrive {
|
||||
)
|
||||
}
|
||||
|
||||
fn try_read_command(&mut self, command: ReadCommand, buf: &mut [u8]) -> bool {
|
||||
fn try_read_command(
|
||||
&mut self,
|
||||
command: ReadCommand,
|
||||
buf: &mut [u8],
|
||||
require_nonzero: bool,
|
||||
) -> bool {
|
||||
if self.enable_cdda().is_err() {
|
||||
return false;
|
||||
}
|
||||
@@ -910,10 +965,7 @@ impl LinuxDrive {
|
||||
let last = self.toc.track_lastsector(t);
|
||||
let sector = (first + last) >> 1;
|
||||
if let Ok(n) = self.read_once(command, sector, 1, buf) {
|
||||
if n > 0
|
||||
&& count_2352_bytes(buf) == CD_FRAMESIZE_RAW
|
||||
&& buf.iter().any(|b| *b != 0)
|
||||
{
|
||||
if n > 0 && (!require_nonzero || buf.iter().any(|b| *b != 0)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1073,15 +1125,6 @@ fn fft(re: &mut [f64], im: &mut [f64]) {
|
||||
}
|
||||
}
|
||||
|
||||
fn count_2352_bytes(buf: &[u8]) -> usize {
|
||||
for i in (0..buf.len()).rev() {
|
||||
if buf[i] != 0x7F {
|
||||
return ((i + 3) >> 2) << 2;
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
fn check_sbp(status: u8, sense: &[u8]) -> ScsiError {
|
||||
let key = sense.get(2).copied().unwrap_or(0) & 0x0F;
|
||||
let asc = sense.get(12).copied().unwrap_or(0);
|
||||
@@ -1219,10 +1262,12 @@ impl Drive for LinuxDrive {
|
||||
}
|
||||
fn read_timed(&mut self, buffer: Option<&mut [u8]>, begin: i64, sectors: i64) -> TimedRead {
|
||||
self.last_milliseconds = -1;
|
||||
self.last_microseconds = -1;
|
||||
let result = self.read_audio(buffer, begin, sectors);
|
||||
TimedRead {
|
||||
result,
|
||||
milliseconds: self.last_milliseconds,
|
||||
microseconds: self.last_microseconds,
|
||||
}
|
||||
}
|
||||
fn speed_set(&mut self, speed: i32) -> i32 {
|
||||
@@ -1268,9 +1313,30 @@ impl Drive for LinuxDrive {
|
||||
fn set_nsectors(&mut self, value: i64) {
|
||||
self.nsectors = value;
|
||||
}
|
||||
fn set_error_retry(&mut self, enabled: bool) {
|
||||
self.error_retry = enabled;
|
||||
}
|
||||
fn interface(&self) -> InterfaceKind {
|
||||
self.interface
|
||||
}
|
||||
fn is_atapi(&self) -> bool {
|
||||
self.is_atapi
|
||||
}
|
||||
fn is_mmc(&self) -> bool {
|
||||
self.is_mmc
|
||||
}
|
||||
fn density(&self) -> u8 {
|
||||
self.density
|
||||
}
|
||||
fn fua(&self) -> bool {
|
||||
self.fua
|
||||
}
|
||||
fn lun(&self) -> i32 {
|
||||
self.lun
|
||||
}
|
||||
fn adjust_ssize(&self) -> i64 {
|
||||
self.adjust_ssize
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LinuxDrive {
|
||||
@@ -1295,15 +1361,13 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fill_byte_counting() {
|
||||
fn fill_pattern_detection() {
|
||||
let mut buf = vec![0x7Fu8; 2352 * 2];
|
||||
assert_eq!(count_2352_bytes(&buf), 0);
|
||||
assert!(buf.iter().all(|b| *b == 0x7F));
|
||||
buf[10] = 0;
|
||||
assert_eq!(count_2352_bytes(&buf), 12);
|
||||
for b in buf.iter_mut() {
|
||||
*b = 0;
|
||||
}
|
||||
assert_eq!(count_2352_bytes(&buf), buf.len());
|
||||
assert!(buf.iter().any(|b| *b != 0x7F));
|
||||
let tail = [0x7Fu8; 16];
|
||||
assert!(tail.iter().all(|b| *b == 0x7F));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -252,7 +252,11 @@ impl fmt::Display for ReadError {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TimedRead {
|
||||
pub result: Result<usize, ReadError>,
|
||||
/// Whole milliseconds, matching the C ABI.
|
||||
pub milliseconds: i32,
|
||||
/// Higher precision duration for internal timing analysis. Backends
|
||||
/// that only know millisecond figures fill this with `ms * 1000`.
|
||||
pub microseconds: i64,
|
||||
}
|
||||
|
||||
/// Backend interface used by both the command line program and the C ABI.
|
||||
@@ -318,7 +322,29 @@ pub trait Drive {
|
||||
fn set_bigendianp(&mut self, value: i32);
|
||||
fn nsectors(&self) -> i64;
|
||||
fn set_nsectors(&mut self, value: i64);
|
||||
/// Enable or disable the low-level read retry loop. Cache analysis
|
||||
/// disables retries so that timing reflects the first access.
|
||||
fn set_error_retry(&mut self, _enabled: bool) {}
|
||||
fn interface(&self) -> InterfaceKind;
|
||||
/// Optional drive attributes exposed through the C ABI.
|
||||
fn is_atapi(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn is_mmc(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn density(&self) -> u8 {
|
||||
0
|
||||
}
|
||||
fn fua(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn lun(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
fn adjust_ssize(&self) -> i64 {
|
||||
1
|
||||
}
|
||||
fn cdda_version(&self) -> &'static str {
|
||||
crate::VERSIONNUM
|
||||
}
|
||||
|
||||
+14
-2
@@ -15,8 +15,8 @@ pub mod callback;
|
||||
pub mod device;
|
||||
pub mod paranoia;
|
||||
|
||||
pub const VERSIONNUM: &str = "26.0.1";
|
||||
pub const VERSION: &str = "cdrustanoia 26.0.1 (2026-11-03)";
|
||||
pub const VERSIONNUM: &str = env!("CARGO_PKG_VERSION");
|
||||
pub const VERSION: &str = concat!("cdrustanoia ", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
pub fn paranoia_version() -> &'static str {
|
||||
VERSIONNUM
|
||||
@@ -25,3 +25,15 @@ pub fn paranoia_version() -> &'static str {
|
||||
pub fn cdda_version() -> &'static str {
|
||||
VERSIONNUM
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn versions_come_from_cargo() {
|
||||
assert_eq!(super::VERSIONNUM, env!("CARGO_PKG_VERSION"));
|
||||
assert_eq!(
|
||||
super::VERSION,
|
||||
concat!("cdrustanoia ", env!("CARGO_PKG_VERSION"))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,3 +128,38 @@ fn jittered_image_roundtrip() {
|
||||
};
|
||||
run_config(sectors, config);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fragmented_image_roundtrip() {
|
||||
use cdrustanoia_core::device::image::{ImageConfig, ImageFaults};
|
||||
let sectors = 24usize;
|
||||
let config = ImageConfig {
|
||||
tracks: vec![(0, sectors as i64 - 1, 0)],
|
||||
faults: ImageFaults {
|
||||
max_read_sectors: 3,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
run_config(sectors, config);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn silent_image_roundtrip() {
|
||||
use cdrustanoia_core::device::image::{ImageConfig, ImageFaults};
|
||||
let sectors = 24usize;
|
||||
let config = ImageConfig {
|
||||
tracks: vec![(0, sectors as i64 - 1, 0)],
|
||||
faults: ImageFaults::default(),
|
||||
..Default::default()
|
||||
};
|
||||
let bytes = vec![0u8; sectors * CD_FRAMESIZE_RAW];
|
||||
let drive = ImageDrive::with_config(bytes, config);
|
||||
let mut p = Paranoia::new(Box::new(drive));
|
||||
p.modeset(PARANOIA_MODE_FULL);
|
||||
let mut cb = |_: i64, _: i32| {};
|
||||
for _ in 0..sectors {
|
||||
let sec = p.read(&mut cb).expect("read silent sector");
|
||||
assert!(sec.iter().all(|s| *s == 0));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user