diff --git a/README.md b/README.md index 0dbb8ed..cd7be8e 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,31 @@ Utilities to work with CD/DVD media, ISO 9660 and UDF images. | iso-ls | List files of ISO 9660 and UDF filesystems. | | mmc-cli | Issue SCSI MMC commands to a drive. | +## iso-cp +Copies files from ISO 9660 or UDF filesystem. +```console +$ iso-cp -h +Copy files from an ISO 9660 or UDF filesystem + +Usage: iso-cp + +Arguments: + Path to an ISO 9660 or UDF image + Path to a source file in the image + Path to a destination file or directory + +Options: + -h, --help Print help (see more with '--help') + -V, --version Print version +$ # Copying a license file from a UDF filesystem +$ iso-cp tests/data/udf1.iso licenses/COPYING.LESSER ./lgpl +$ cat lgpl | head -2 + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 +``` + +Copying whole directories is currently not supported. + ## Install - Install [Rust][rust-install]. - Install [clang][bindgen-reqs]. diff --git a/src/drive-info/cli.rs b/src/drive-info/cli.rs index 7480f4d..84ed88a 100644 --- a/src/drive-info/cli.rs +++ b/src/drive-info/cli.rs @@ -39,3 +39,15 @@ pub struct DriveArg { #[arg(value_name = "DRIVE")] pub positional: Option, } + +#[cfg(test)] +mod tests { + use clap::CommandFactory; + + use super::*; + + #[test] + fn verify_cli() { + Cli::command().debug_assert(); + } +} diff --git a/src/iso-cp/cli.rs b/src/iso-cp/cli.rs index f157d02..4d822e2 100644 --- a/src/iso-cp/cli.rs +++ b/src/iso-cp/cli.rs @@ -17,37 +17,35 @@ use std::path::PathBuf; -use clap::{Args, Parser}; +use clap::Parser; -/// Extract files from ISO 9660 and UDF files. +/// Copy files from an ISO 9660 or UDF filesystem. #[derive(Parser)] #[command(arg_required_else_help = true, version)] pub struct Cli { - /// Path to the file in the image to extract - #[arg(short, long, value_name = "FILE")] - pub extract: String, - - /// Path to an ISO9660 and/or UDF image - #[command(flatten)] - pub image: FileArg, - - /// Path of the output file. Defaults to name of the extracted file. - #[arg(short, long, value_name = "FILE")] - pub output_file: Option, - - /// Use UDF - #[arg(short = 'U', long)] - pub udf: bool, + /// Path to an ISO 9660 or UDF image. + #[arg(value_name = "IMAGE")] + pub image: PathBuf, + + /// Path to a source file in the image. + /// + /// Directories are currently not supported. + #[arg(value_name = "SOURCE")] + pub source: String, + + /// Path to a destination file or directory. + #[arg(value_name = "DESTINATION")] + pub destination: PathBuf, } -#[derive(Args)] -#[group(required = true, multiple = false)] -pub struct FileArg { - /// Path to an ISO9660 and/or UDF image - #[arg(short = 'i', long = "image", value_name = "FILE")] - pub option: Option, +#[cfg(test)] +mod tests { + use clap::CommandFactory; + + use super::*; - /// Path to an ISO9660 and/or UDF image - #[arg(value_name = "FILE")] - pub positional: Option, + #[test] + fn verify_cli() { + Cli::command().debug_assert(); + } } diff --git a/src/iso-cp/main.rs b/src/iso-cp/main.rs index 1c21c12..98d39d2 100644 --- a/src/iso-cp/main.rs +++ b/src/iso-cp/main.rs @@ -31,28 +31,39 @@ fn main() -> Result<()> { tracing_subscriber::fmt() .with_env_filter(EnvFilter::from_default_env()) .init(); - let image = cli.image.positional.or(cli.image.option) - .expect( "the cli logic must ensure that the file argument is provided either as a positional or as an option"); + let image = cli.image; if !image.exists() { bail!("could not open input file at {}", image.display()); } - let output = cli.output_file.unwrap_or(PathBuf::from(&cli.extract)); + let output = if cli.destination.is_dir() { + let source = PathBuf::from(&cli.source); + let source_file = source.file_name().context("invalid source file name")?; + cli.destination.join(source_file) + } else { + cli.destination + }; let mut output = File::create(output).context("could not create output file")?; - if cli.udf { - udf_extract(image, cli.extract, &mut output)?; - } else { - iso9660_extract(image, cli.extract, &mut output)?; - } + let iso_err = match Iso::new(image.clone()) { + Ok(iso) => return iso9660_extract(&iso, cli.source, &mut output), + Err(err) => err, + }; - Ok(()) + match Udf::new(image) { + Ok(udf) => udf_extract(&udf, cli.source, &mut output), + Err(udf_err) => bail!( + "could not open file as ISO 9660 or UDF\n ISO error: {iso_err:?}\nUDF error: {udf_err:?}", + ), + } } /// Extract given file from a UDF image. -fn udf_extract(image: PathBuf, extract: String, output: &mut File) -> Result<()> { - let udf = Udf::new(image)?; - let entry = udf.entry(extract)?; +fn udf_extract(udf: &Udf, source: String, output: &mut File) -> Result<()> { + let entry = udf.entry(source)?; + if entry.is_dir() { + bail!("copying directories is currently not supported"); + } io::copy(&mut entry.reader(), output)?; @@ -60,9 +71,11 @@ fn udf_extract(image: PathBuf, extract: String, output: &mut File) -> Result<()> } /// Extract given file from an ISO 9660 image. -fn iso9660_extract(image: PathBuf, extract: String, output: &mut File) -> Result<()> { - let iso = Iso::new(image.clone())?; - let entry = iso.entry(extract)?; +fn iso9660_extract(iso: &Iso, source: String, output: &mut File) -> Result<()> { + let entry = iso.entry(source)?; + if entry.is_dir() { + bail!("copying directories is currently not supported"); + } io::copy(&mut entry.reader(), output)?; diff --git a/src/iso-ls/cli.rs b/src/iso-ls/cli.rs index bd9d404..4f73492 100644 --- a/src/iso-ls/cli.rs +++ b/src/iso-ls/cli.rs @@ -64,3 +64,15 @@ pub struct FileArg { #[arg(value_name = "FILE")] pub positional: Option, } + +#[cfg(test)] +mod tests { + use clap::CommandFactory; + + use super::*; + + #[test] + fn verify_cli() { + Cli::command().debug_assert(); + } +} diff --git a/src/mmc-cli/cli.rs b/src/mmc-cli/cli.rs index 8f7fc85..0b6dfba 100644 --- a/src/mmc-cli/cli.rs +++ b/src/mmc-cli/cli.rs @@ -59,3 +59,15 @@ pub struct MmcActions { #[arg(short = 'S', long)] pub speed: Option, } + +#[cfg(test)] +mod tests { + use clap::CommandFactory; + + use super::*; + + #[test] + fn verify_cli() { + Cli::command().debug_assert(); + } +} diff --git a/tests/iso-cp.rs b/tests/iso-cp.rs index dc1243d..54ac3d8 100644 --- a/tests/iso-cp.rs +++ b/tests/iso-cp.rs @@ -12,13 +12,9 @@ static UDF_FILE: &str = "tests/data/udf1.iso"; fn extract_udf() { let output = NamedTempFile::new("out").unwrap(); cmd() - .arg("-e") - .arg("licenses/COPYING") - .arg("-i") .arg(UDF_FILE) - .arg("-o") + .arg("licenses/COPYING") .arg(output.path()) - .arg("-U") .assert() .success(); @@ -31,11 +27,8 @@ static ISO9660_FILE: &str = "tests/data/xa.iso"; fn extract_iso9660() { let output = NamedTempFile::new("out").unwrap(); cmd() - .arg("-e") - .arg("copying") - .arg("-i") .arg(ISO9660_FILE) - .arg("-o") + .arg("copying") .arg(output.path()) .assert() .success();