first draft

This commit is contained in:
tux
2026-11-03 20:19:21 +01:00
commit 158e115242
44 changed files with 10785 additions and 0 deletions
+314
View File
@@ -0,0 +1,314 @@
#![allow(
clippy::missing_safety_doc,
clippy::unnecessary_cast,
clippy::field_reassign_with_default
)]
//! C ABI-compatible libcdda_paranoia implementation.
use cdrustanoia_capi_types::*;
use cdrustanoia_core::device::{
host_big_endian, swap_raw16, DiscToc, Drive, InterfaceKind, ReadError, TimedRead,
CD_FRAMESIZE_RAW, MAXTRK,
};
use cdrustanoia_core::paranoia::Paranoia;
use libc::{c_char, c_int, c_long, c_void};
use std::ffi::CStr;
use std::ptr;
use std::time::Instant;
pub type ParanoiaCallback = Option<unsafe extern "C" fn(c_long, c_int)>;
struct CAbiDrive {
d: *mut CdromDrive,
toc: DiscToc,
model: String,
bigendianp: i32,
nsectors: i64,
interface: InterfaceKind,
}
unsafe fn toc_from_d(d: *mut CdromDrive) -> DiscToc {
let mut toc = DiscToc::default();
if d.is_null() {
return toc;
}
toc.tracks = (*d).tracks;
toc.cd_extra = (*d).cd_extra;
for i in 0..MAXTRK {
toc.entries[i].flags = (*d).disc_toc[i].b_flags;
toc.entries[i].track = (*d).disc_toc[i].b_track;
toc.entries[i].start_sector = (*d).disc_toc[i].dw_start_sector as i64;
}
toc
}
impl CAbiDrive {
unsafe fn new(d: *mut CdromDrive) -> Self {
let model = if (*d).drive_model.is_null() {
String::new()
} else {
CStr::from_ptr((*d).drive_model)
.to_string_lossy()
.into_owned()
};
let interface = match (*d).interface {
INTERFACE_COOKED_IOCTL => InterfaceKind::Cooked,
INTERFACE_TEST => InterfaceKind::Image,
_ => InterfaceKind::SgIo,
};
Self {
d,
toc: toc_from_d(d),
model,
bigendianp: (*d).bigendianp,
nsectors: (*d).nsectors as i64,
interface,
}
}
fn refresh(&mut self) {
unsafe {
self.toc = toc_from_d(self.d);
self.bigendianp = (*self.d).bigendianp;
self.nsectors = (*self.d).nsectors as i64;
}
}
}
impl Drive for CAbiDrive {
fn toc(&self) -> &DiscToc {
&self.toc
}
fn toc_mut(&mut self) -> &mut DiscToc {
&mut self.toc
}
fn opened(&self) -> bool {
unsafe { !self.d.is_null() && (*self.d).opened != 0 }
}
fn read_audio(
&mut self,
mut buffer: Option<&mut [u8]>,
begin: i64,
sectors: i64,
) -> Result<usize, ReadError> {
unsafe {
if self.d.is_null() || (*self.d).opened == 0 {
return Err(ReadError::closed());
}
let f = (*self.d)
.read_audio
.ok_or_else(|| ReadError::new(libc::EIO, "No read function"))?;
let ptr = match buffer.as_mut() {
Some(b) => b.as_mut_ptr() as *mut c_void,
None => ptr::null_mut(),
};
let ret = f(self.d, ptr, begin as c_long, sectors as c_long);
if ret < 0 {
let errno = match ret {
-404 => libc::ENOMEDIUM,
-300 => libc::ENOMEM,
_ => libc::EIO,
};
return Err(ReadError::new(errno, "SCSI read error"));
}
let n = ret as usize;
if let Some(buf) = buffer.as_mut() {
let be = (*self.d).bigendianp;
if n > 0 && be >= 0 && (be == 1) != host_big_endian() {
let len = (n * CD_FRAMESIZE_RAW).min(buf.len());
swap_raw16(&mut buf[..len]);
}
}
Ok(n)
}
}
fn read_timed(&mut self, buffer: Option<&mut [u8]>, begin: i64, sectors: i64) -> TimedRead {
let started = Instant::now();
let result = self.read_audio(buffer, begin, sectors);
TimedRead {
result,
milliseconds: started.elapsed().as_millis() as i32,
}
}
fn speed_set(&mut self, speed: i32) -> i32 {
unsafe {
if self.d.is_null() {
return -405;
}
match (*self.d).set_speed {
Some(f) => f(self.d, speed as c_int),
None => -405,
}
}
}
fn close(&mut self) {
// The paranoia library never closes the caller-owned drive.
}
fn drive_model(&self) -> &str {
&self.model
}
fn bigendianp(&self) -> i32 {
self.bigendianp
}
fn set_bigendianp(&mut self, value: i32) {
self.bigendianp = value;
unsafe {
if !self.d.is_null() {
(*self.d).bigendianp = value;
}
}
}
fn nsectors(&self) -> i64 {
self.nsectors
}
fn set_nsectors(&mut self, value: i64) {
self.nsectors = value;
unsafe {
if !self.d.is_null() {
(*self.d).nsectors = value as c_int;
}
}
}
fn interface(&self) -> InterfaceKind {
self.interface
}
}
unsafe fn p_ref<'a>(p: *mut c_void) -> Option<&'a mut Paranoia> {
if p.is_null() {
None
} else {
Some(&mut *(p as *mut Paranoia))
}
}
#[no_mangle]
pub unsafe extern "C" fn paranoia_version() -> *const c_char {
c"10.2".as_ptr()
}
#[no_mangle]
pub unsafe extern "C" fn paranoia_init(d: *mut CdromDrive) -> *mut c_void {
if d.is_null() {
return ptr::null_mut();
}
let mut adapter = CAbiDrive::new(d);
adapter.refresh();
let p = Paranoia::new(Box::new(adapter));
Box::into_raw(Box::new(p)) as *mut c_void
}
#[no_mangle]
pub unsafe extern "C" fn paranoia_free(p: *mut c_void) {
if !p.is_null() {
drop(Box::from_raw(p as *mut Paranoia));
}
}
#[no_mangle]
pub unsafe extern "C" fn paranoia_modeset(p: *mut c_void, mode: c_int) {
if let Some(p) = p_ref(p) {
p.modeset(mode);
}
}
#[no_mangle]
pub unsafe extern "C" fn paranoia_seek(p: *mut c_void, seek: c_long, mode: c_int) -> c_long {
match p_ref(p) {
Some(p) => p.seek(seek as i64, mode) as c_long,
None => -1,
}
}
#[no_mangle]
pub unsafe extern "C" fn paranoia_overlapset(p: *mut c_void, overlap: c_long) {
if let Some(p) = p_ref(p) {
p.overlapset(overlap as i64);
}
}
#[no_mangle]
pub unsafe extern "C" fn paranoia_cachemodel_size(p: *mut c_void, sectors: c_int) -> c_int {
match p_ref(p) {
Some(p) => p.cachemodel_size(sectors as i64) as c_int,
None => 0,
}
}
#[no_mangle]
pub unsafe extern "C" fn paranoia_read(p: *mut c_void, callback: ParanoiaCallback) -> *mut i16 {
paranoia_read_limited(p, callback, 20)
}
#[no_mangle]
pub unsafe extern "C" fn paranoia_read_limited(
p: *mut c_void,
callback: ParanoiaCallback,
max_retries: c_int,
) -> *mut i16 {
let p = match p_ref(p) {
Some(p) => p,
None => return ptr::null_mut(),
};
let mut cb = |pos: i64, func: i32| {
if let Some(f) = callback {
f(pos as c_long, func as c_int);
}
};
match p.read_limited(&mut cb, max_retries) {
Some(slice) => slice.as_mut_ptr(),
None => {
let errno = if p.drive().opened() {
libc::EIO
} else {
libc::EBADF
};
*libc::__errno_location() = errno;
ptr::null_mut()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use cdrustanoia_core::device::image::ImageDrive;
use cdrustanoia_core::device::samples_to_bytes;
use cdrustanoia_core::device::CD_FRAMEWORDS;
#[test]
fn c_api_query_and_read() {
unsafe {
let mut samples = Vec::new();
for i in 0..(3 * 1176) {
samples.push(((i * 37) as i16).wrapping_mul(3));
}
let bytes = samples_to_bytes(&samples);
let img = ImageDrive::from_bytes(bytes);
let d = cdda_interface::make_drive("test", None, Box::new(img));
assert_eq!(cdda_interface::cdda_open(d), 0);
assert_eq!(cdda_interface::cdda_tracks(d), 1);
assert_eq!(cdda_interface::cdda_disc_firstsector(d), 0);
assert_eq!(cdda_interface::cdda_disc_lastsector(d), 2);
let mut raw = vec![0u8; CD_FRAMESIZE_RAW];
let n = cdda_interface::cdda_read(d, raw.as_mut_ptr() as *mut c_void, 0, 1);
assert_eq!(n, 1);
assert_eq!(raw, samples_to_bytes(&samples[..1176]));
let p = paranoia_init(d);
assert!(!p.is_null());
paranoia_modeset(p, cdrustanoia_core::paranoia::PARANOIA_MODE_FULL);
paranoia_seek(p, 0, 0);
for sector in 0..3 {
let ptr = paranoia_read(p, None);
assert!(!ptr.is_null());
let got = std::slice::from_raw_parts(ptr, CD_FRAMEWORDS as usize);
assert_eq!(got, &samples[sector * 1176..(sector + 1) * 1176]);
}
paranoia_free(p);
cdda_interface::cdda_close(d);
}
}
}