first draft
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "cdda_interface"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "cdda_interface"
|
||||
crate-type = ["cdylib", "staticlib", "rlib"]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
cdrustanoia_core.workspace = true
|
||||
cdrustanoia_capi_types.workspace = true
|
||||
libc.workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("cargo:rustc-cdylib-link-arg=-Wl,-soname,libcdda_interface.so.0");
|
||||
}
|
||||
@@ -0,0 +1,571 @@
|
||||
#![allow(
|
||||
clippy::missing_safety_doc,
|
||||
clippy::unnecessary_cast,
|
||||
clippy::field_reassign_with_default
|
||||
)]
|
||||
|
||||
//! C ABI-compatible libcdda_interface implementation.
|
||||
|
||||
use cdrustanoia_capi_types::*;
|
||||
use cdrustanoia_core::device::discover;
|
||||
use cdrustanoia_core::device::{DiscToc, Drive, InterfaceKind, ReadError};
|
||||
use libc::{c_char, c_int, c_long, c_void};
|
||||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
|
||||
pub struct BackendHolder {
|
||||
pub drive: Box<dyn Drive>,
|
||||
}
|
||||
|
||||
unsafe fn cstr(s: &str) -> *mut c_char {
|
||||
let clean: String = s.chars().filter(|c| *c != '\0').collect();
|
||||
match CString::new(clean) {
|
||||
Ok(c) => libc::strdup(c.as_ptr()),
|
||||
Err(_) => ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn set_error(d: *mut CdromDrive, message: &str) {
|
||||
if d.is_null() {
|
||||
return;
|
||||
}
|
||||
if !(*d).errorbuf.is_null() {
|
||||
libc::free((*d).errorbuf as *mut c_void);
|
||||
}
|
||||
(*d).errorbuf = cstr(message);
|
||||
}
|
||||
|
||||
pub unsafe fn holder<'a>(d: *mut CdromDrive) -> Option<&'a mut BackendHolder> {
|
||||
if d.is_null() {
|
||||
return None;
|
||||
}
|
||||
let private = (*d).private;
|
||||
if private.is_null() {
|
||||
return None;
|
||||
}
|
||||
let h = (*private).holder as *mut BackendHolder;
|
||||
if h.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(&mut *h)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn make_drive(
|
||||
device: &str,
|
||||
ioctl_device: Option<&str>,
|
||||
backend: Box<dyn Drive>,
|
||||
) -> *mut CdromDrive {
|
||||
let mut d = Box::new(CdromDrive::empty());
|
||||
d.drive_type = 0;
|
||||
d.interface = match backend.interface() {
|
||||
InterfaceKind::SgIo => INTERFACE_SGIO_SCSI,
|
||||
InterfaceKind::Cooked => INTERFACE_COOKED_IOCTL,
|
||||
InterfaceKind::Image => INTERFACE_TEST,
|
||||
};
|
||||
d.bigendianp = backend.bigendianp();
|
||||
d.nsectors = backend.nsectors() as c_int;
|
||||
d.cdda_device_name = cstr(device);
|
||||
d.ioctl_device_name = cstr(ioctl_device.unwrap_or(device));
|
||||
d.drive_model = cstr(backend.drive_model());
|
||||
d.enable_cdda = Some(cb_enable_cdda);
|
||||
d.read_toc = Some(cb_read_toc);
|
||||
d.read_audio = Some(cb_read_audio);
|
||||
d.set_speed = Some(cb_set_speed);
|
||||
d.error_retry = 1;
|
||||
d.report_all = 0;
|
||||
d.adjust_ssize = 1;
|
||||
d.lun = 0;
|
||||
let private = Box::new(CddaPrivateData {
|
||||
holder: Box::into_raw(Box::new(BackendHolder { drive: backend })) as *mut c_void,
|
||||
last_milliseconds: -1,
|
||||
clock: 0,
|
||||
});
|
||||
d.private = Box::into_raw(private);
|
||||
Box::into_raw(d)
|
||||
}
|
||||
|
||||
unsafe fn free_drive(d: *mut CdromDrive) {
|
||||
if d.is_null() {
|
||||
return;
|
||||
}
|
||||
let private = (*d).private;
|
||||
if !private.is_null() {
|
||||
let holder = (*private).holder as *mut BackendHolder;
|
||||
if !holder.is_null() {
|
||||
drop(Box::from_raw(holder));
|
||||
}
|
||||
drop(Box::from_raw(private));
|
||||
(*d).private = ptr::null_mut();
|
||||
}
|
||||
if !(*d).cdda_device_name.is_null() {
|
||||
libc::free((*d).cdda_device_name as *mut c_void);
|
||||
}
|
||||
if !(*d).ioctl_device_name.is_null() {
|
||||
libc::free((*d).ioctl_device_name as *mut c_void);
|
||||
}
|
||||
if !(*d).drive_model.is_null() {
|
||||
libc::free((*d).drive_model as *mut c_void);
|
||||
}
|
||||
if !(*d).errorbuf.is_null() {
|
||||
libc::free((*d).errorbuf as *mut c_void);
|
||||
}
|
||||
if !(*d).messagebuf.is_null() {
|
||||
libc::free((*d).messagebuf as *mut c_void);
|
||||
}
|
||||
drop(Box::from_raw(d));
|
||||
}
|
||||
|
||||
unsafe extern "C" fn cb_enable_cdda(_d: *mut CdromDrive, _onoff: c_int) -> c_int {
|
||||
0
|
||||
}
|
||||
|
||||
unsafe extern "C" fn cb_read_toc(d: *mut CdromDrive) -> c_int {
|
||||
if d.is_null() {
|
||||
return -1;
|
||||
}
|
||||
(*d).tracks
|
||||
}
|
||||
|
||||
unsafe fn read_into(
|
||||
d: *mut CdromDrive,
|
||||
p: *mut c_void,
|
||||
begin: c_long,
|
||||
sectors: c_long,
|
||||
) -> Result<usize, ReadError> {
|
||||
let h = holder(d).ok_or_else(ReadError::closed)?;
|
||||
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 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)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn cb_read_audio(
|
||||
d: *mut CdromDrive,
|
||||
p: *mut c_void,
|
||||
begin: c_long,
|
||||
sectors: c_long,
|
||||
) -> c_long {
|
||||
match read_into(d, p, begin, sectors) {
|
||||
Ok(n) => n as c_long,
|
||||
Err(e) => {
|
||||
set_errno(e.errno);
|
||||
match e.errno {
|
||||
libc::ENOMEDIUM => -404,
|
||||
libc::ENOMEM => -300,
|
||||
_ => -7,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn cb_set_speed(d: *mut CdromDrive, speed: c_int) -> c_int {
|
||||
match holder(d) {
|
||||
Some(h) => h.drive.speed_set(speed),
|
||||
None => -405,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_errno(value: c_int) {
|
||||
unsafe {
|
||||
*libc::__errno_location() = value;
|
||||
}
|
||||
}
|
||||
|
||||
fn toc_from_drive(d: &CdromDrive) -> DiscToc {
|
||||
let mut toc = DiscToc::default();
|
||||
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
|
||||
}
|
||||
|
||||
unsafe fn fill_toc(d: *mut CdromDrive) {
|
||||
let h = match holder(d) {
|
||||
Some(h) => h,
|
||||
None => return,
|
||||
};
|
||||
let toc = h.drive.toc().clone();
|
||||
(*d).tracks = toc.tracks;
|
||||
(*d).cd_extra = toc.cd_extra;
|
||||
for i in 0..MAXTRK {
|
||||
(*d).disc_toc[i].b_flags = toc.entries[i].flags;
|
||||
(*d).disc_toc[i].b_track = toc.entries[i].track;
|
||||
(*d).disc_toc[i].dw_start_sector = toc.entries[i].start_sector as i32;
|
||||
}
|
||||
(*d).audio_first_sector = toc.disc_firstsector() as c_long;
|
||||
(*d).audio_last_sector = toc.disc_lastsector() as c_long;
|
||||
(*d).bigendianp = h.drive.bigendianp();
|
||||
(*d).nsectors = h.drive.nsectors() as c_int;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cdda_identify(
|
||||
device: *const c_char,
|
||||
messagedest: c_int,
|
||||
messages: *mut *mut c_char,
|
||||
) -> *mut CdromDrive {
|
||||
if !messages.is_null() {
|
||||
*messages = ptr::null_mut();
|
||||
}
|
||||
if device.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let device = match std::ffi::CStr::from_ptr(device).to_str() {
|
||||
Ok(s) => s,
|
||||
Err(_) => return ptr::null_mut(),
|
||||
};
|
||||
match discover::identify_device(device, messagedest == CDDA_MESSAGE_PRINTIT) {
|
||||
Ok(drive) => make_drive(device, Some(device), drive),
|
||||
Err(_) => ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cdda_identify_cooked(
|
||||
device: *const c_char,
|
||||
messagedest: c_int,
|
||||
messages: *mut *mut c_char,
|
||||
) -> *mut CdromDrive {
|
||||
if !messages.is_null() {
|
||||
*messages = ptr::null_mut();
|
||||
}
|
||||
if device.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let device = match std::ffi::CStr::from_ptr(device).to_str() {
|
||||
Ok(s) => s,
|
||||
Err(_) => return ptr::null_mut(),
|
||||
};
|
||||
match discover::identify_cooked(device, messagedest == CDDA_MESSAGE_PRINTIT) {
|
||||
Ok(drive) => make_drive(device, Some(device), drive),
|
||||
Err(_) => ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cdda_identify_scsi(
|
||||
generic_device: *const c_char,
|
||||
ioctl_device: *const c_char,
|
||||
messagedest: c_int,
|
||||
messages: *mut *mut c_char,
|
||||
) -> *mut CdromDrive {
|
||||
if !messages.is_null() {
|
||||
*messages = ptr::null_mut();
|
||||
}
|
||||
let generic = if generic_device.is_null() {
|
||||
None
|
||||
} else {
|
||||
std::ffi::CStr::from_ptr(generic_device).to_str().ok()
|
||||
};
|
||||
let ioctl = if ioctl_device.is_null() {
|
||||
None
|
||||
} else {
|
||||
std::ffi::CStr::from_ptr(ioctl_device).to_str().ok()
|
||||
};
|
||||
let verbose = messagedest == CDDA_MESSAGE_PRINTIT;
|
||||
if let Some(dev) = generic {
|
||||
if let Ok(drive) = discover::identify_sgio(dev, verbose) {
|
||||
return make_drive(dev, ioctl, drive);
|
||||
}
|
||||
}
|
||||
if let Some(dev) = ioctl {
|
||||
if let Ok(drive) = discover::identify_device(dev, verbose) {
|
||||
return make_drive(dev, Some(dev), drive);
|
||||
}
|
||||
}
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cdda_find_a_cdrom(
|
||||
messagedest: c_int,
|
||||
messages: *mut *mut c_char,
|
||||
) -> *mut CdromDrive {
|
||||
if !messages.is_null() {
|
||||
*messages = ptr::null_mut();
|
||||
}
|
||||
match discover::find_a_cdrom(messagedest == CDDA_MESSAGE_PRINTIT) {
|
||||
Some(drive) => make_drive("auto", None, drive),
|
||||
None => ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cdda_open(d: *mut CdromDrive) -> c_int {
|
||||
if d.is_null() {
|
||||
return -100;
|
||||
}
|
||||
if (*d).opened != 0 {
|
||||
return 0;
|
||||
}
|
||||
if let Some(h) = holder(d) {
|
||||
if let Err(e) = h.drive.open_disc() {
|
||||
set_errno(libc::EIO);
|
||||
set_error(d, &format!("Unable to open disc: {}\n", e));
|
||||
return if e.contains("illegal number of tracks") {
|
||||
-3
|
||||
} else if e.contains("contents header") {
|
||||
-4
|
||||
} else if e.contains("contents entry") {
|
||||
-5
|
||||
} else if e.contains("lead-out") {
|
||||
-2
|
||||
} else if e.contains("CDDA") || e.contains("command set") {
|
||||
-6
|
||||
} else {
|
||||
-9
|
||||
};
|
||||
}
|
||||
}
|
||||
fill_toc(d);
|
||||
if (*d).tracks < 1 {
|
||||
(*d).opened = 0;
|
||||
return -9;
|
||||
}
|
||||
for i in 0..(*d).tracks as usize {
|
||||
if (*d).disc_toc[i].dw_start_sector < 0 || (*d).disc_toc[i + 1].dw_start_sector == 0 {
|
||||
(*d).opened = 0;
|
||||
return -9;
|
||||
}
|
||||
}
|
||||
(*d).opened = 1;
|
||||
0
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cdda_close(d: *mut CdromDrive) -> c_int {
|
||||
if d.is_null() {
|
||||
return 0;
|
||||
}
|
||||
free_drive(d);
|
||||
0
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cdda_read_timed(
|
||||
d: *mut CdromDrive,
|
||||
buffer: *mut c_void,
|
||||
beginsector: c_long,
|
||||
sectors: c_long,
|
||||
ms: *mut c_int,
|
||||
) -> c_long {
|
||||
if !ms.is_null() {
|
||||
*ms = -1;
|
||||
}
|
||||
if d.is_null() || (*d).opened == 0 {
|
||||
set_errno(libc::EBADF);
|
||||
set_error(d, "400: Device not open\n");
|
||||
return -400;
|
||||
}
|
||||
if sectors <= 0 {
|
||||
return sectors;
|
||||
}
|
||||
let h = match holder(d) {
|
||||
Some(h) => h,
|
||||
None => {
|
||||
set_errno(libc::EBADF);
|
||||
return -400;
|
||||
}
|
||||
};
|
||||
if (*d).bigendianp >= 0 {
|
||||
h.drive.set_bigendianp((*d).bigendianp);
|
||||
}
|
||||
if (*d).nsectors > 0 {
|
||||
h.drive.set_nsectors((*d).nsectors as i64);
|
||||
}
|
||||
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 buf = std::slice::from_raw_parts_mut(buffer as *mut u8, len);
|
||||
h.drive
|
||||
.read_timed(Some(buf), beginsector as i64, sectors as i64)
|
||||
};
|
||||
if !ms.is_null() {
|
||||
*ms = timed.milliseconds;
|
||||
}
|
||||
if (*d).bigendianp < 0 {
|
||||
(*d).bigendianp = h.drive.bigendianp();
|
||||
}
|
||||
match timed.result {
|
||||
Ok(n) => n as c_long,
|
||||
Err(e) => {
|
||||
set_errno(e.errno);
|
||||
set_error(d, &format!("SCSI read error: {}\n", e.message));
|
||||
match e.errno {
|
||||
libc::ENOMEDIUM => -404,
|
||||
libc::ENOMEM => -300,
|
||||
_ => -7,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cdda_read(
|
||||
d: *mut CdromDrive,
|
||||
buffer: *mut c_void,
|
||||
beginsector: c_long,
|
||||
sectors: c_long,
|
||||
) -> c_long {
|
||||
cdda_read_timed(d, buffer, beginsector, sectors, ptr::null_mut())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cdda_speed_set(d: *mut CdromDrive, speed: c_int) -> c_int {
|
||||
match holder(d) {
|
||||
Some(h) => {
|
||||
let ret = h.drive.speed_set(speed);
|
||||
if ret == 0 {
|
||||
0
|
||||
} else {
|
||||
-405
|
||||
}
|
||||
}
|
||||
None => -405,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cdda_verbose_set(
|
||||
d: *mut CdromDrive,
|
||||
err_action: c_int,
|
||||
mes_action: c_int,
|
||||
) {
|
||||
if !d.is_null() {
|
||||
(*d).errordest = err_action;
|
||||
(*d).messagedest = mes_action;
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cdda_messages(d: *mut CdromDrive) -> *mut c_char {
|
||||
if d.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let ret = (*d).messagebuf;
|
||||
(*d).messagebuf = ptr::null_mut();
|
||||
ret
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cdda_errors(d: *mut CdromDrive) -> *mut c_char {
|
||||
if d.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let ret = (*d).errorbuf;
|
||||
(*d).errorbuf = ptr::null_mut();
|
||||
ret
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cdda_version() -> *const c_char {
|
||||
c"10.2".as_ptr()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cdda_tracks(d: *mut CdromDrive) -> c_long {
|
||||
if d.is_null() || (*d).opened == 0 {
|
||||
return -400;
|
||||
}
|
||||
(*d).tracks as c_long
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cdda_track_firstsector(d: *mut CdromDrive, track: c_int) -> c_long {
|
||||
if d.is_null() || (*d).opened == 0 {
|
||||
return -400;
|
||||
}
|
||||
toc_from_drive(&*d).track_firstsector(track) as c_long
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cdda_track_lastsector(d: *mut CdromDrive, track: c_int) -> c_long {
|
||||
if d.is_null() || (*d).opened == 0 {
|
||||
return -400;
|
||||
}
|
||||
toc_from_drive(&*d).track_lastsector(track) as c_long
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cdda_disc_firstsector(d: *mut CdromDrive) -> c_long {
|
||||
if d.is_null() || (*d).opened == 0 {
|
||||
return -400;
|
||||
}
|
||||
toc_from_drive(&*d).disc_firstsector() as c_long
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cdda_disc_lastsector(d: *mut CdromDrive) -> c_long {
|
||||
if d.is_null() || (*d).opened == 0 {
|
||||
return -400;
|
||||
}
|
||||
toc_from_drive(&*d).disc_lastsector() as c_long
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cdda_sector_gettrack(d: *mut CdromDrive, sector: c_long) -> c_int {
|
||||
if d.is_null() || (*d).opened == 0 {
|
||||
return -400;
|
||||
}
|
||||
toc_from_drive(&*d).sector_gettrack(sector as i64)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cdda_track_channels(d: *mut CdromDrive, track: c_int) -> c_int {
|
||||
if d.is_null() || (*d).opened == 0 {
|
||||
return -400;
|
||||
}
|
||||
toc_from_drive(&*d).track_channels(track)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cdda_track_audiop(d: *mut CdromDrive, track: c_int) -> c_int {
|
||||
if d.is_null() || (*d).opened == 0 {
|
||||
return -400;
|
||||
}
|
||||
toc_from_drive(&*d).track_audiop(track)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cdda_track_copyp(d: *mut CdromDrive, track: c_int) -> c_int {
|
||||
if d.is_null() || (*d).opened == 0 {
|
||||
return -400;
|
||||
}
|
||||
toc_from_drive(&*d).track_copyp(track)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cdda_track_preemp(d: *mut CdromDrive, track: c_int) -> c_int {
|
||||
if d.is_null() || (*d).opened == 0 {
|
||||
return -400;
|
||||
}
|
||||
toc_from_drive(&*d).track_preemp(track)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cdda_track_bitmap(
|
||||
d: *mut CdromDrive,
|
||||
track: c_int,
|
||||
bit: c_int,
|
||||
set: c_int,
|
||||
clear: c_int,
|
||||
) -> c_int {
|
||||
if d.is_null() || (*d).opened == 0 {
|
||||
return -400;
|
||||
}
|
||||
toc_from_drive(&*d).track_bitmap(track, bit as u8, set, clear)
|
||||
}
|
||||
|
||||
// Keep the c_long import used on all targets.
|
||||
#[allow(dead_code)]
|
||||
fn _typecheck(_: c_long) {}
|
||||
Reference in New Issue
Block a user