diff --git a/README.md b/README.md index 0dbb8ed..4b89209 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,49 @@ Utilities to work with CD/DVD media, ISO 9660 and UDF images. cargo install libcdio-cli ``` +## iso-ls +Lists files of an ISO 9660 or UDF filesystem. +```console +$ iso-ls -h +Inspect metadata and list contents of ISO 9660 and UDF files + +Usage: iso-ls [OPTIONS] + +Arguments: + Path to an ISO 9660 or UDF image + +Options: + -m, --metadata Print image metadata + -h, --help Print help (see more with '--help') + -V, --version Print version +``` + +Listing the contents of a UDF filesystem: +```console +$ iso-ls tests/data/udf1.iso +/: + dr-xr-xr-x 2000 3000 2 88 Jun 19 2026 20:42:57 . + dr-xr-xr-x 2000 3000 1 144 Jun 19 2026 20:42:57 licenses + +/licenses/: + dr-xr-xr-x 2000 3000 2 88 Jun 19 2026 20:42:57 . + -r--r--r-- 2000 3000 1 35149 Jun 19 2026 20:41:12 COPYING + -r--r--r-- 2000 3000 1 7652 Jun 19 2026 20:41:16 COPYING.LESSER +``` + +Listing the image metadata of an ISO 9660 filesystem: +```console +$ iso-ls -m tests/data/joliet.iso +Image : tests/data/joliet.iso +Application : K3B THE CD KREATOR VERSION 0.11.12 (C) 2003 SEBASTIAN TRUEG AND THE K3B TEAM +Preparer : K3b - Version 0.11.12 +Publisher : Rocky Bernstein +System : LINUX +Volume : K3b data project +Joliet : Level 3 +Rock Ridge : no +``` + ## Development ### Use the provided Git Hooks These are set to perform lint and formatting checks before every 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..d9dbc63 100644 --- a/src/iso-cp/cli.rs +++ b/src/iso-cp/cli.rs @@ -51,3 +51,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/iso-ls/cli.rs b/src/iso-ls/cli.rs index bd9d404..4cacd01 100644 --- a/src/iso-ls/cli.rs +++ b/src/iso-ls/cli.rs @@ -17,50 +17,29 @@ use std::path::PathBuf; -use clap::{Args, Parser}; +use clap::Parser; /// Inspect metadata and list contents of ISO 9660 and UDF files. #[derive(Parser)] #[command(arg_required_else_help = true, long_about = libcdio_cli::HEADER, version)] pub struct Cli { - /// The file argument as an option or a positional argument - #[command(flatten)] - pub file: FileArg, + /// Path to an ISO 9660 or UDF image. + #[arg(value_name = "IMAGE")] + pub image: PathBuf, - /// Show contents of ISO9660 image in long listing format - #[arg(short = 'l', long, group = "listing")] - pub iso9660: bool, - - /// Do not use Rock Ridge extensions - #[arg(long)] - pub no_rock_ridge: bool, - - /// Do not use CD-ROM XA extensions - #[arg(long)] - pub no_xa: bool, - - /// Check if the image uses Rock Ridge extensions by considering a maximum - /// of FILE_COUNT files. Provide '0' to check all files. - #[arg(short = 'r', long, value_name = "FILE_COUNT")] - pub show_rock_ridge: Option, - - /// Produce only error outputs. + /// Print image metadata. #[arg(short, long)] - pub quiet: bool, - - /// Show contents of UDF image in long listing format - #[arg(short = 'U', long, group = "listing")] - pub udf: bool, + pub metadata: bool, } -#[derive(Args)] -#[group(required = true, multiple = false)] -pub struct FileArg { - /// Path to an ISO9660 and/or UDF image - #[arg(short = 'i', long = "input", 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-ls/main.rs b/src/iso-ls/main.rs index 7d251da..0bf8695 100644 --- a/src/iso-ls/main.rs +++ b/src/iso-ls/main.rs @@ -33,44 +33,32 @@ use crate::cli::Cli; const DATE_FMT: &[BorrowedFormatItem] = format_description!("[month repr:short] [day] [year] [hour]:[minute]:[second]"); -static LINE: &str = "__________________________________"; fn main() -> Result<()> { let cli = Cli::parse(); tracing_subscriber::fmt() .with_env_filter(EnvFilter::from_default_env()) .init(); - let mut output: &mut dyn io::Write = if cli.quiet { - &mut io::sink() - } else { - &mut io::stdout() - }; - let file = cli.file.positional.or(cli.file.option).expect( - "the cli logic must ensure that the file argument is provided either as a positional or as an option", - ); - - if cli.udf { - return print_udf_contents(file, &mut output); - } + let mut output = &mut io::stdout(); + let file = cli.image; - let iso = Iso::new(file.clone())?; - print_iso9660_metadata(&iso, &file, &mut output) - .context("io error while printing iso9660 metadata")?; - - if cli.show_rock_ridge.is_some() { - let file_limit = cli.show_rock_ridge.filter(|file_limit| *file_limit != 0); - print_rock_ridge(&iso, file_limit, &mut output) - .context("io error while printing rock ridge status")?; + if cli.metadata { + let iso = Iso::new(file.clone())?; + return print_iso9660_metadata(&iso, &file, &mut output).map_err(Into::into); } - print_joliet_level(&iso, &mut output).context("io error while printing joliet level")?; + let Err(udf_err) = print_udf_contents(file.clone(), &mut output) else { + return Ok(()); + }; - if cli.iso9660 { - print_iso9660_contents(&iso, &mut output, !cli.no_rock_ridge, !cli.no_xa) - .context("error printing iso9660 contents")?; + match Iso::new(file) { + Ok(iso) => { + print_iso9660_contents(&iso, &mut output).context("error printing iso9660 contents") + } + Err(iso_err) => bail!( + "could not open image as UDF or ISO 9660:\n udf error: {udf_err:?}\n iso error: {iso_err:?}", + ), } - - Ok(()) } fn print_iso9660_metadata( @@ -78,8 +66,7 @@ fn print_iso9660_metadata( path: &Path, mut out: impl io::Write, ) -> Result<(), io::Error> { - writeln!(out, "{LINE}")?; - writeln!(out, "ISO 9660 image: {}", path.display())?; + writeln!(out, "Image : {}", path.display())?; let mut write_if_some = |key, val| { let Some(val) = val else { return Ok(()) }; writeln!(out, "{key} : {val}") @@ -91,36 +78,26 @@ fn print_iso9660_metadata( write_if_some("Volume ", iso.volume())?; write_if_some("Volume Set ", iso.volume_set())?; - Ok(()) -} + let joliet_level = iso.joliet_level().map(|j| format!("Level {}", u8::from(j))); + writeln!( + out, + "Joliet : {}", + joliet_level.as_deref().unwrap_or("no") + )?; -fn print_rock_ridge( - iso: &Iso, - file_limit: Option, - mut out: impl io::Write, -) -> Result<(), io::Error> { - let status = match iso.have_rock_ridge(file_limit) { - Ok(true) => "yes", - Ok(false) => "no", - _ => "possibly not", + if let Ok(r) = iso.have_rock_ridge(None) { + writeln!(out, "Rock Ridge : {}", if r { "yes" } else { "no" })?; }; - writeln!(out, "Rock Ridge : {}", status) + + Ok(()) } /// Outputs the file contents of the ISO 9660 image in an ls-like listing format. -fn print_iso9660_contents( - iso: &Iso, - mut out: impl io::Write, - use_rock_ridge: bool, - use_xa: bool, -) -> Result<()> { +fn print_iso9660_contents(iso: &Iso, mut out: impl io::Write) -> Result<()> { const ISO9660_DEPTH_LIMIT: usize = 512; let mut dirs = VecDeque::new(); dirs.push_back(("/".to_owned(), 0)); // (path, depth) - writeln!(out, "{}", LINE)?; - writeln!(out, "ISO-9660 Information")?; - while let Some((dir_path, depth)) = dirs.pop_front() { if depth == ISO9660_DEPTH_LIMIT { bail!("directory recursion too deep. ISO most probably damaged"); @@ -129,7 +106,7 @@ fn print_iso9660_contents( writeln!(out, "{}:", dir_path)?; for entry in iso.read_dir(dir_path.clone())? { - let rock_ridge = use_rock_ridge.then_some(entry.rock_ridge()).flatten(); + let rock_ridge = entry.rock_ridge(); let entry_name = if rock_ridge.is_none() { entry.filename()? } else { @@ -154,7 +131,7 @@ fn print_iso9660_contents( write!(out, " {}", rock.group_id)?; write!(out, " [LSN {:6}]", entry.lsn())?; write!(out, " {:9}", total_size)?; - } else if use_xa && let Some(xa) = entry.xa() { + } else if let Some(xa) = entry.xa() { write!(out, " {}", xa_file_mode_str(xa.file_attr))?; write!(out, " {}", xa.user_id)?; write!(out, " {}", xa.group_id)?; @@ -275,11 +252,3 @@ fn print_udf_contents(path: PathBuf, out: &mut dyn io::Write) -> Result<()> { Ok(()) } - -fn print_joliet_level(iso: &Iso, mut out: impl io::Write) -> Result<(), io::Error> { - let Some(joliet_level) = iso.joliet_level() else { - return writeln!(out, "No Joliet extensions"); - }; - - writeln!(out, "Joliet Level: {}", u8::from(joliet_level)) -} 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-ls.rs b/tests/iso-ls.rs index ed73ca9..c02a4f9 100644 --- a/tests/iso-ls.rs +++ b/tests/iso-ls.rs @@ -5,18 +5,19 @@ fn cmd() -> Command { } static ROCK_RIDGE_FILE: &str = "tests/data/rock-ridge.iso"; -static ROCK_METADATA: &str = r"__________________________________ -ISO 9660 image: tests/data/rock-ridge.iso +static ROCK_METADATA: &str = r"Image : tests/data/rock-ridge.iso Application : K3B THE CD KREATOR VERSION 0.11.20 (C) 2003 SEBASTIAN TRUEG AND THE K3B TEAM Preparer : K3b - Version 0.11.20 Publisher : Rocky Bernstein System : LINUX Volume : Rock Ridge Copy test -No Joliet extensions +Joliet : no +Rock Ridge : yes "; #[test] fn rock_metadata() { cmd() + .arg("-m") .arg(ROCK_RIDGE_FILE) .assert() .success() @@ -24,18 +25,19 @@ fn rock_metadata() { } static JOLIET_FILE: &str = "tests/data/joliet.iso"; -static JOLIET_METADATA: &str = r"__________________________________ -ISO 9660 image: tests/data/joliet.iso +static JOLIET_METADATA: &str = r"Image : tests/data/joliet.iso Application : K3B THE CD KREATOR VERSION 0.11.12 (C) 2003 SEBASTIAN TRUEG AND THE K3B TEAM Preparer : K3b - Version 0.11.12 Publisher : Rocky Bernstein System : LINUX Volume : K3b data project -Joliet Level: 3 +Joliet : Level 3 +Rock Ridge : no "; #[test] fn joliet_metadata() { cmd() + .arg("-m") .arg(JOLIET_FILE) .assert() .success() @@ -43,21 +45,24 @@ fn joliet_metadata() { } static XA_FILE: &str = "tests/data/xa.iso"; -static XA_METADATA: &str = r"__________________________________ -ISO 9660 image: tests/data/xa.iso +static XA_METADATA: &str = r"Image : tests/data/xa.iso Application : GENISOIMAGE ISO 9660/HFS FILESYSTEM CREATOR (C) 1993 E.YOUNGDALE (C) 1997-2006 J.PEARSON/J.SCHILLING (C) 2006-2007 CDRKIT TEAM System : LINUX Volume : CDROM -No Joliet extensions +Joliet : no +Rock Ridge : no "; #[test] fn xa_metadata() { - cmd().arg(XA_FILE).assert().success().stdout(XA_METADATA); + cmd() + .arg("-m") + .arg(XA_FILE) + .assert() + .success() + .stdout(XA_METADATA); } -static ROCK_CONTENTS: &str = r"__________________________________ -ISO-9660 Information -/: +static ROCK_CONTENTS: &str = r"/: dr-xr-xr-x 4 0 0 [LSN 23] 2048 Oct 22 2004 02:21:14 . dr-xr-xr-x 2 0 0 [LSN 23] 2048 Oct 22 2004 02:21:14 .. dr-xr-xr-x 2 0 0 [LSN 24] 2048 Mar 05 2005 16:12:25 copy @@ -82,16 +87,13 @@ ISO-9660 Information fn rock_contents() { cmd() .env("TZ", "UTC") - .arg("-l") .arg(ROCK_RIDGE_FILE) .assert() .success() - .stdout(ROCK_METADATA.to_owned() + ROCK_CONTENTS); + .stdout(ROCK_CONTENTS); } -static JOLIET_CONTENTS: &str = r"__________________________________ -ISO-9660 Information -/: +static JOLIET_CONTENTS: &str = r"/: d [LSN 31] 2048 Oct 22 2004 22:44:59 . d [LSN 31] 2048 Oct 22 2004 22:44:59 .. d [LSN 32] 2048 Oct 22 2004 22:44:59 libcdio @@ -114,16 +116,13 @@ ISO-9660 Information fn joliet_contents() { cmd() .env("TZ", "UTC") - .arg("-l") .arg(JOLIET_FILE) .assert() .success() - .stdout(JOLIET_METADATA.to_owned() + JOLIET_CONTENTS); + .stdout(JOLIET_CONTENTS); } -static XA_CONTENTS: &str = r"__________________________________ -ISO-9660 Information -/: +static XA_CONTENTS: &str = r"/: d---1xrxrxr 1000 3000 [fn 00] [LSN 23] 2048 Jun 08 2026 04:44:35 . d---1xrxrxr 1000 3000 [fn 00] [LSN 23] 2048 Jun 08 2026 04:44:35 .. ----1--xr-- 1000 3000 [fn 00] [LSN 25] 35149 Jun 08 2026 04:43:19 copying @@ -133,11 +132,10 @@ ISO-9660 Information fn xa_contents() { cmd() .env("TZ", "UTC") - .arg("-l") .arg(XA_FILE) .assert() .success() - .stdout(XA_METADATA.to_owned() + XA_CONTENTS); + .stdout(XA_CONTENTS); } static UDF_FILE: &str = "tests/data/udf.iso"; @@ -150,7 +148,6 @@ static UDF_OUTPUT: &str = "/: fn udf() { cmd() .env("TZ", "UTC") - .arg("-U") .arg(UDF_FILE) .assert() .success()