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
+16
View File
@@ -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
+3
View File
@@ -0,0 +1,3 @@
fn main() {
println!("cargo:rustc-cdylib-link-arg=-Wl,-soname,libcdda_interface.so.0");
}
+571
View File
@@ -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) {}