Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions libcdio-rs/src/cdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,29 @@ pub(crate) struct Cdio {
}

impl Cdio {
/// Create a new Cdio object with the given parameters.
pub(crate) fn new(device: Option<&CStr>, driver: driver_id_t) -> Option<Self> {
/// Initialize a hardware Cdio resource with read-write access.
pub(crate) fn with_device(device: Option<&CStr>) -> Option<Self> {
let source = device.map(|s| s.as_ptr()).unwrap_or(ptr::null());
NonNull::new(Self::open(source, driver)).map(|cdio| Self { cdio })
NonNull::new(Self::open(true, source, driver_id_t_DRIVER_DEVICE)).map(|cdio| Self { cdio })
}

fn open(source: *const c_char, driver: driver_id_t) -> *mut CdIo_t {
fn open(allow_writes: bool, source: *const c_char, driver: driver_id_t) -> *mut CdIo_t {
logging::init_logger();
let access_mode = if allow_writes {
RW_ACCESS_MODE.as_ptr()
} else {
ptr::null()
};

// SAFETY: This invokes cdio_init(), which mutates a static variable.
// CDIO_LAST_DRIVER_LOCK is held to prevent data races.
let _lock = CDIO_LAST_DRIVER_LOCK.lock().unwrap();
unsafe { libcdio_sys::cdio_open(source, driver) }
}
return unsafe { libcdio_sys::cdio_open_am(source, driver, access_mode) };

pub(crate) const DEVICE_DRIVER: driver_id_t = driver_id_t_DRIVER_DEVICE;
/// Although prefixed "MMC", this does imply read-write for all
/// operations
static RW_ACCESS_MODE: &CStr = c"MMC_RDWR";
}
}

impl Deref for Cdio {
Expand Down
7 changes: 4 additions & 3 deletions libcdio-rs/src/drive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ impl Drive {
/// Get a list of connected drives.
/// The values could be used with [`Self::with_drive()`].
pub fn drives() -> Vec<PathBuf> {
let drive_list = unsafe { libcdio_sys::cdio_get_devices(Cdio::DEVICE_DRIVER) };
let drive_list =
unsafe { libcdio_sys::cdio_get_devices(libcdio_sys::driver_id_t_DRIVER_DEVICE) };
if drive_list.is_null() {
return vec![];
}
Expand Down Expand Up @@ -72,7 +73,7 @@ impl Drive {
/// # Errors
/// If there are no drives connected, or the drive could not be opened.
pub fn new() -> Result<Self, DriveNotFoundError> {
Cdio::new(None, Cdio::DEVICE_DRIVER)
Cdio::with_device(None)
.ok_or(DriveNotFoundError)
.map(|cdio| Self { cdio })
}
Expand All @@ -91,7 +92,7 @@ impl Drive {
source: WithDriveErrorKind::DriveHasNullChar(err),
}
})?;
let cdio = Cdio::new(Some(&drive), Cdio::DEVICE_DRIVER).ok_or_else(|| WithDriveError {
let cdio = Cdio::with_device(Some(&drive)).ok_or_else(|| WithDriveError {
drive: os_string_from_bytes_safe(drive.into_bytes()).into(),
source: WithDriveErrorKind::CouldNotOpenAsDrive,
})?;
Expand Down
7 changes: 5 additions & 2 deletions libcdio-rs/src/mmc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ pub use get_config::*;
pub use get_event_status::*;
pub use read_disc_info::*;
pub use read_subchannel::*;
pub use start_stop_unit::*;
pub use test_unit_ready::*;
pub use read_toc::*;

mod get_config;
mod get_event_status;
mod read_disc_info;
mod read_subchannel;
mod start_stop_unit;
mod test_unit_ready;
mod read_toc;

Expand Down Expand Up @@ -77,7 +79,7 @@ impl Mmc {
/// # Errors
/// If an MMC capable device could not be found.
pub fn new() -> Result<Mmc, MmcNotFoundError> {
Cdio::new(None, Cdio::DEVICE_DRIVER)
Cdio::with_device(None)
.map(|cdio| Self { cdio })
.filter(|mmc| mmc.level().is_ok())
.ok_or(MmcNotFoundError)
Expand All @@ -95,7 +97,7 @@ impl Mmc {
source: WithDeviceErrorKind::DeviceHasNullChar(err),
}
})?;
let Some(cdio) = Cdio::new(Some(&device), Cdio::DEVICE_DRIVER) else {
let Some(cdio) = Cdio::with_device(Some(&device)) else {
return Err(WithDeviceError {
device: os_string_from_bytes_safe(device.into_bytes()).into(),
source: WithDeviceErrorKind::CouldNotOpenDevice,
Expand Down Expand Up @@ -365,6 +367,7 @@ pub enum OsError {
enum MmcCommand {
#[allow(unused)]
GetConfiguration = 0x46,
StartStopUnit = 0x1B,
TestUnitReady = 0x00,
ReadDiscInfo = 0x51,
ReadToc = 0x43,
Expand Down
112 changes: 112 additions & 0 deletions libcdio-rs/src/mmc/start_stop_unit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright (C) 2026 Shiva Kiran Koninty <shiva@skran.xyz>
//
// This file is part of libcdio-rs.
//
// libcdio-rs is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// libcdio-rs is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with libcdio-rs. If not, see <https://www.gnu.org/licenses/>.

//! Routines based on MMC `START STOP UNIT`.

use displaydoc::Display;
use thiserror::Error;

use crate::{
Mmc,
mmc::{Cdb, MmcCommand, MmcDirection, MmcError},
};

/// Routines based on MMC `START STOP UNIT`.
impl Mmc {
/// Eject the media, if permitted.
///
/// This might require a prior allow eject operation.
pub fn eject(&self) -> Result<(), MmcEjectError> {
self.start_stop_unit(StartStopOperation::EjectDisc)?;
Ok(())
}

/// Close the tray.
pub fn close_tray(&self) -> Result<(), MmcCloseTrayError> {
self.start_stop_unit(StartStopOperation::LoadStartDisc)?;
Ok(())
}

fn start_stop_unit(&self, operation: StartStopOperation) -> Result<(), MmcStartStopError> {
let mut cdb = Cdb::default();

cdb[0] = MmcCommand::StartStopUnit as u8;
cdb[1] = 0; // not using the immediate bit for now
if let StartStopOperation::Jump { layer_number } = operation {
cdb[3] = layer_number & 0b11
}
cdb[4] = match operation {
StartStopOperation::StartDisc => 0b01,
StartStopOperation::EjectDisc => 0b10,
StartStopOperation::LoadStartDisc | StartStopOperation::Jump { .. } => 0b11,
_ => 0b00,
};

self.run_command(Some(MmcDirection::Write), &mut [], cdb)?;

Ok(())
}
}

/// could not eject MMC device
#[derive(Debug, Display, Error)]
pub struct MmcEjectError {
#[from]
pub source: MmcStartStopError,
}

/// could not close tray of the MMC device
#[derive(Debug, Display, Error)]
pub struct MmcCloseTrayError {
#[from]
pub source: MmcStartStopError,
}

/// error from a `START STOP UNIT` command
#[derive(Debug, Display, Error)]
pub struct MmcStartStopError {
#[from]
pub source: MmcError,
}

/// Operations of the `START STOP UNIT` command
#[allow(unused)]
enum StartStopOperation {
StopDisc,
StartDisc,
EjectDisc,
LoadStartDisc,

/// Change the online format-layer to the specified value for hybrid discs.
/// Only the last two bits will be set.
Jump {
layer_number: u8,
},

/// Place the device in the specified power condition
Power(PowerCondition),
}

/// A power state as defined under MMC `START STOP UNIT`
#[allow(unused)]
enum PowerCondition {
Idle = 0x2,
Standby = 0x3,
Sleep = 0x5,
}

// TODO: add manual tests