first draft
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "cdda_paranoia"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "cdda_paranoia"
|
||||
crate-type = ["cdylib", "staticlib", "rlib"]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
cdrustanoia_core.workspace = true
|
||||
cdrustanoia_capi_types.workspace = true
|
||||
libc.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
cdda_interface = { path = "../cdda-interface" }
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("cargo:rustc-cdylib-link-arg=-Wl,-soname,libcdda_paranoia.so.0");
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user