This commit is contained in:
tux
2026-11-04 08:32:00 +01:00
parent 8012e912ec
commit 2d50b0fbea
16 changed files with 1542 additions and 215 deletions
+3
View File
@@ -14,3 +14,6 @@ path = "src/lib.rs"
cdrustanoia_core.workspace = true
cdrustanoia_capi_types.workspace = true
libc.workspace = true
[dev-dependencies]
tempfile = "3"
+137 -5
View File
@@ -8,9 +8,11 @@
use cdrustanoia_capi_types::*;
use cdrustanoia_core::device::discover;
use cdrustanoia_core::device::image::ImageDrive;
use cdrustanoia_core::device::{DiscToc, Drive, InterfaceKind, ReadError};
use libc::{c_char, c_int, c_long, c_void};
use std::ffi::CString;
use std::path::Path;
use std::ptr;
pub struct BackendHolder {
@@ -42,6 +44,12 @@ unsafe fn set_error(d: *mut CdromDrive, message: &str) {
(*d).errorbuf = cstr(message);
}
unsafe fn set_message(messages: *mut *mut c_char, message: &str) {
if !messages.is_null() && (*messages).is_null() {
*messages = cstr(message);
}
}
pub unsafe fn holder<'a>(d: *mut CdromDrive) -> Option<&'a mut BackendHolder> {
if d.is_null() {
return None;
@@ -65,6 +73,13 @@ pub unsafe fn make_drive(
) -> *mut CdromDrive {
let mut d = Box::new(CdromDrive::empty());
d.drive_type = 0;
d.is_atapi = backend.is_atapi() as c_int;
d.is_mmc = backend.is_mmc() as c_int;
d.density = backend.density();
d.fua = backend.fua() as c_int;
d.lun = backend.lun();
d.adjust_ssize = backend.adjust_ssize() as c_int;
d.bigbuff = backend.nsectors().max(0).saturating_mul(2352) as c_long;
d.interface = match backend.interface() {
InterfaceKind::SgIo => INTERFACE_SGIO_SCSI,
InterfaceKind::Cooked => INTERFACE_COOKED_IOCTL,
@@ -141,10 +156,23 @@ unsafe fn read_into(
sectors: c_long,
) -> Result<usize, ReadError> {
let h = holder(d).ok_or_else(ReadError::closed)?;
if !d.is_null() {
h.drive.set_error_retry((*d).error_retry != 0);
if (*d).nsectors > 0 {
h.drive.set_nsectors((*d).nsectors as i64);
}
if (*d).bigendianp >= 0 {
h.drive.set_bigendianp((*d).bigendianp);
}
}
if p.is_null() {
h.drive.read_audio_raw(None, begin as i64, sectors as i64)
} else {
let len = (sectors.max(0) as usize) * cdrustanoia_core::device::CD_FRAMESIZE_RAW;
let Some(len) =
(sectors.max(0) as usize).checked_mul(cdrustanoia_core::device::CD_FRAMESIZE_RAW)
else {
return Err(ReadError::new(libc::EINVAL, "Invalid read size"));
};
let buf = std::slice::from_raw_parts_mut(p as *mut u8, len);
h.drive
.read_audio_raw(Some(buf), begin as i64, sectors as i64)
@@ -212,6 +240,7 @@ unsafe fn fill_toc(d: *mut CdromDrive) {
(*d).audio_last_sector = toc.disc_lastsector() as c_long;
(*d).bigendianp = h.drive.bigendianp();
(*d).nsectors = h.drive.nsectors() as c_int;
(*d).bigbuff = (*d).nsectors.max(0) as c_long * 2352;
}
#[no_mangle]
@@ -242,7 +271,13 @@ unsafe fn cdda_identify_impl(
};
match discover::identify_device(device, messagedest == CDDA_MESSAGE_PRINTIT) {
Ok(drive) => make_drive(device, Some(device), drive),
Err(_) => ptr::null_mut(),
Err(e) => {
set_message(
messages,
&format!("\tCould not identify {}: {}\n", device, e),
);
ptr::null_mut()
}
}
}
@@ -274,7 +309,13 @@ unsafe fn cdda_identify_cooked_impl(
};
match discover::identify_cooked(device, messagedest == CDDA_MESSAGE_PRINTIT) {
Ok(drive) => make_drive(device, Some(device), drive),
Err(_) => ptr::null_mut(),
Err(e) => {
set_message(
messages,
&format!("\tCould not identify cooked device {}: {}\n", device, e),
);
ptr::null_mut()
}
}
}
@@ -320,9 +361,64 @@ unsafe fn cdda_identify_scsi_impl(
return make_drive(dev, Some(dev), drive);
}
}
set_message(
messages,
"\tCould not identify a usable SCSI/cooked device\n",
);
ptr::null_mut()
}
#[no_mangle]
pub unsafe extern "C" fn cdda_identify_test(
filename: *const c_char,
messagedest: c_int,
messages: *mut *mut c_char,
) -> *mut CdromDrive {
ffi_guard(ptr::null_mut(), || {
cdda_identify_test_impl(filename, messagedest, messages)
})
}
unsafe fn cdda_identify_test_impl(
filename: *const c_char,
messagedest: c_int,
messages: *mut *mut c_char,
) -> *mut CdromDrive {
if !messages.is_null() {
*messages = ptr::null_mut();
}
if filename.is_null() {
set_message(messages, "\tNo filename supplied for test interface\n");
return ptr::null_mut();
}
let filename = match std::ffi::CStr::from_ptr(filename).to_str() {
Ok(value) => value,
Err(_) => return ptr::null_mut(),
};
let verbose = messagedest == CDDA_MESSAGE_PRINTIT;
if verbose {
eprintln!("\tTesting {} for file/test interface", filename);
}
match ImageDrive::open_path(Path::new(filename)) {
Ok(drive) => {
if verbose {
eprintln!("\t\tCDROM sensed: {}", drive.drive_model());
}
make_drive(filename, None, Box::new(drive))
}
Err(e) => {
if verbose {
eprintln!("\t\tCould not open file {}: {}", filename, e);
}
set_message(
messages,
&format!("\tCould not open file {}: {}\n", filename, e),
);
ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn cdda_find_a_cdrom(
messagedest: c_int,
@@ -342,7 +438,10 @@ unsafe fn cdda_find_a_cdrom_impl(
}
match discover::find_a_cdrom(messagedest == CDDA_MESSAGE_PRINTIT) {
Some(drive) => make_drive("auto", None, drive),
None => ptr::null_mut(),
None => {
set_message(messages, "\tNo CD-ROM drive found\n");
ptr::null_mut()
}
}
}
@@ -443,6 +542,7 @@ unsafe fn cdda_read_timed_impl(
return -400;
}
};
h.drive.set_error_retry((*d).error_retry != 0);
if (*d).bigendianp >= 0 {
h.drive.set_bigendianp((*d).bigendianp);
}
@@ -452,7 +552,12 @@ unsafe fn cdda_read_timed_impl(
let timed = if buffer.is_null() {
h.drive.read_timed(None, beginsector as i64, sectors as i64)
} else {
let len = (sectors as usize) * cdrustanoia_core::device::CD_FRAMESIZE_RAW;
let Some(len) = (sectors as usize).checked_mul(cdrustanoia_core::device::CD_FRAMESIZE_RAW)
else {
set_errno(libc::EINVAL);
set_error(d, "Invalid read size\n");
return -7;
};
let buf = std::slice::from_raw_parts_mut(buffer as *mut u8, len);
h.drive
.read_timed(Some(buf), beginsector as i64, sectors as i64)
@@ -460,6 +565,9 @@ unsafe fn cdda_read_timed_impl(
if !ms.is_null() {
*ms = timed.milliseconds;
}
if !(*d).private.is_null() {
(*(*d).private).last_milliseconds = timed.milliseconds;
}
if (*d).bigendianp < 0 {
(*d).bigendianp = h.drive.bigendianp();
}
@@ -680,6 +788,7 @@ mod tests {
TimedRead {
result: Err(ReadError::closed()),
milliseconds: -1,
microseconds: -1,
}
}
fn speed_set(&mut self, _speed: i32) -> i32 {
@@ -702,6 +811,29 @@ mod tests {
}
}
#[test]
fn c_abi_version_stays_cdparanoia_compatible() {
let version = unsafe { std::ffi::CStr::from_ptr(cdda_version()) };
assert_eq!(version.to_str().unwrap(), "10.2");
}
#[test]
fn test_interface_exposes_image_backend() {
let dir = tempfile::tempdir().unwrap();
let image = dir.path().join("disc.raw");
std::fs::write(&image, vec![0u8; 2352 * 10]).unwrap();
let name = CString::new(image.to_string_lossy().as_bytes()).unwrap();
unsafe {
let d = cdda_identify_test(name.as_ptr(), CDDA_MESSAGE_FORGETIT, ptr::null_mut());
assert!(!d.is_null());
assert_eq!((*d).interface, INTERFACE_TEST);
assert_eq!(cdda_open(d), 0);
assert_eq!(cdda_tracks(d), 1);
assert_eq!(cdda_disc_lastsector(d), 9);
assert_eq!(cdda_close(d), 0);
}
}
#[test]
fn c_api_catches_panics() {
unsafe {