diff --git a/libcdio-cli/Cargo.toml b/libcdio-cli/Cargo.toml index b381648..041416f 100644 --- a/libcdio-cli/Cargo.toml +++ b/libcdio-cli/Cargo.toml @@ -30,11 +30,17 @@ name = "iso-read-rs" path = "src/iso-read/main.rs" required-features = ["iso-read"] +[[bin]] +name = "mmc-tool-rs" +path = "src/mmc-tool/main.rs" +required-features = ["mmc-tool"] + [features] -default = ["cd-drive", "iso-info", "iso-read"] +default = ["cd-drive", "iso-info", "iso-read", "mmc-tool"] cd-drive = [] iso-info = ["libcdio-rs/iso9660", "libcdio-rs/udf", "dep:time"] iso-read = ["libcdio-rs/iso9660", "libcdio-rs/udf"] +mmc-tool = [] [dev-dependencies] assert_cmd = { version = "2.2.2", features = ["color"] } diff --git a/libcdio-cli/src/mmc-tool/cli.rs b/libcdio-cli/src/mmc-tool/cli.rs new file mode 100644 index 0000000..1435b84 --- /dev/null +++ b/libcdio-cli/src/mmc-tool/cli.rs @@ -0,0 +1,71 @@ +// Copyright (C) 2026 Shiva Kiran Koninty +// +// This file is part of libcdio-cli. +// +// libcdio-cli 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-cli 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-cli. If not, see . + +use std::path::PathBuf; + +use clap::{Args, Parser}; + +#[derive(Debug, Parser)] +#[command(arg_required_else_help = true, long_about = libcdio_cli::HEADER, version)] +pub struct Cli { + /// Show debugging information (1 = Error, 2 = Warn, 3 = Info, 4 = Debug) + #[arg( + default_value = "2", + short, + long, + value_name = "LEVEL", + value_parser = clap::value_parser!(u8).range(1..=4), + )] + pub debug: u8, + + /// Path to an MMC device. + pub device: Option, + + #[command(flatten)] + pub actions: MmcActions, +} + +#[derive(Args, Debug)] +#[group(required = true, multiple = false)] +pub struct MmcActions { + /// Eject the drive + #[arg(short, long)] + pub eject: bool, + + /// Close the tray, if present + #[arg(short, long)] + pub close_tray: bool, + + /// Put the device into standby + #[arg(short, long)] + pub standby: bool, + + /// Get the MCN (Media Catalog Number) of the media + #[arg(short, long)] + pub mcn: bool, + + /// Get hardware identifiers (Product, Vendor and Revision) + #[arg(short, long)] + pub inquiry: bool, + + /// Set the drive read and write speed in KB/s. + /// + /// Falls back to the nearest supported value if the provided value is not + /// supported. + #[arg(short = 'S', long)] + pub speed: Option, +} diff --git a/libcdio-cli/src/mmc-tool/main.rs b/libcdio-cli/src/mmc-tool/main.rs new file mode 100644 index 0000000..dd1b1b9 --- /dev/null +++ b/libcdio-cli/src/mmc-tool/main.rs @@ -0,0 +1,64 @@ +// Copyright (C) 2026 Shiva Kiran Koninty +// +// This file is part of libcdio-cli. +// +// libcdio-cli 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-cli 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-cli. If not, see . + +use anyhow::{Context, Result}; +use clap::Parser; +use libcdio_rs::{ + Mmc, + mmc::{PowerCondition, RotationMode}, +}; + +use crate::cli::Cli; + +mod cli; + +fn main() -> Result<()> { + let cli = Cli::parse(); + libcdio_cli::setup_logs(cli.debug); + let mmc = if let Some(device) = cli.device { + Mmc::with_device(device)? + } else { + Mmc::new()? + }; + + if cli.actions.eject { + mmc.allow_media_removal()?; + mmc.eject()?; + } else if cli.actions.close_tray { + mmc.close_tray()?; + } else if cli.actions.standby { + mmc.set_power_state(PowerCondition::Standby)?; + } else if cli.actions.mcn { + let mcn = mmc + .media_catalog_number() + .context("could not get MCN")? + .context("current media does not have a Media Catalog Number")?; + println!("{}", mcn); + } else if cli.actions.inquiry { + let ident = mmc.hardware_identifiers()?; + println!("Product: {}", ident.product); + println!("Vendor: {}", ident.vendor); + println!("Revision: {}", ident.revision); + } else if let Some(speed) = cli.actions.speed { + // some drives may not support CLV (Constant Linear Velocity) + if mmc.set_cd_speed(RotationMode::Clv, speed, speed).is_err() { + mmc.set_cd_speed(RotationMode::Cav, speed, speed)?; + } + } + + Ok(()) +} diff --git a/libcdio-rs/src/mmc/start_stop_unit.rs b/libcdio-rs/src/mmc/start_stop_unit.rs index a49f9a7..12b601a 100644 --- a/libcdio-rs/src/mmc/start_stop_unit.rs +++ b/libcdio-rs/src/mmc/start_stop_unit.rs @@ -41,24 +41,40 @@ impl Mmc { Ok(()) } + /// Set power state. + pub fn set_power_state(&self, state: PowerCondition) -> Result<(), MmcSetPowerStateError> { + self.start_stop_unit(StartStopOperation::Power(state))?; + 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[3] = layer_number & LAYER_NUM_BITMASK; + cdb[4] |= 1 << FORMAT_LAYER_BITPOS; } - cdb[4] = match operation { + // loej and start + cdb[4] |= match operation { StartStopOperation::StartDisc => 0b01, StartStopOperation::EjectDisc => 0b10, StartStopOperation::LoadStartDisc | StartStopOperation::Jump { .. } => 0b11, _ => 0b00, }; + if let StartStopOperation::Power(pow_cond) = operation { + cdb[4] |= (pow_cond as u8 & POWER_COND_BITMASK) << POWER_COND_BITPOS; + } self.run_command(Some(MmcDirection::Write), &mut [], cdb)?; - Ok(()) + return Ok(()); + + const LAYER_NUM_BITMASK: u8 = 0b11; + const FORMAT_LAYER_BITPOS: usize = 2; + const POWER_COND_BITMASK: u8 = 0b1111; + const POWER_COND_BITPOS: usize = 4; } } @@ -76,6 +92,13 @@ pub struct MmcCloseTrayError { pub source: MmcStartStopError, } +/// could not set power state of MMC device +#[derive(Debug, Display, Error)] +pub struct MmcSetPowerStateError { + #[from] + pub source: MmcStartStopError, +} + /// error from a `START STOP UNIT` command #[derive(Debug, Display, Error)] pub struct MmcStartStopError { @@ -103,7 +126,7 @@ enum StartStopOperation { /// A power state as defined under MMC `START STOP UNIT` #[allow(unused)] -enum PowerCondition { +pub enum PowerCondition { Idle = 0x2, Standby = 0x3, Sleep = 0x5,