From 6134ae7fd042e379bdda437bc1d375ebb02bb341 Mon Sep 17 00:00:00 2001 From: Shiva Kiran Koninty Date: Fri, 14 Aug 2026 16:39:55 +0530 Subject: [PATCH 01/13] lib/iso9660: Rearrange code Rearrange code to bring the `impl`s near the `struct`s, keeping things consistent with the rest of the code. --- libcdio-rs/src/iso9660.rs | 152 ++++++++++++++++---------------- libcdio-rs/src/iso9660/entry.rs | 26 +++--- libcdio-rs/src/iso9660/rock.rs | 40 ++++----- libcdio-rs/src/iso9660/xa.rs | 60 ++++++------- 4 files changed, 139 insertions(+), 139 deletions(-) diff --git a/libcdio-rs/src/iso9660.rs b/libcdio-rs/src/iso9660.rs index 5272bc0..c92ab78 100644 --- a/libcdio-rs/src/iso9660.rs +++ b/libcdio-rs/src/iso9660.rs @@ -51,44 +51,6 @@ pub struct Iso9660 { pub(crate) ptr: NonNull, } -/// A builder for [Iso9660]. -#[derive(Clone, Debug)] -pub struct Iso9660Builder<'a> { - extensions: Iso9660Extensions, - path: &'a Path, -} - -bitflags! { - /// ISO 9660 Extensions. - /// # Examples - /// ```rust, no_run - /// use libcdio_rs::iso9660::Iso9660Extensions; - /// // pick HighSierra and RockRidge - /// let extensions = Iso9660Extensions::HighSierra & Iso9660Extensions::RockRidge; - /// // pick everything except RockRidge - /// let extensions = Iso9660Extensions::all() - Iso9660Extensions::RockRidge; - /// // pick nothing - /// let extensions = Iso9660Extensions::empty(); - /// ``` - #[derive(Clone, Copy, Debug)] - pub struct Iso9660Extensions: u8 { - const HighSierra = iso_extension_enum_s_ISO_EXTENSION_HIGH_SIERRA as _; - const JolietLevel1 = iso_extension_enum_s_ISO_EXTENSION_JOLIET_LEVEL1 as _; - const JolietLevel2 = iso_extension_enum_s_ISO_EXTENSION_JOLIET_LEVEL2 as _; - const JolietLevel3 = iso_extension_enum_s_ISO_EXTENSION_JOLIET_LEVEL3 as _; - const RockRidge = iso_extension_enum_s_ISO_EXTENSION_ROCK_RIDGE as _; - } -} - -/// Joliet level. -#[repr(u8)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] -pub enum JolietLevel { - One = 1, - Two, - Three, -} - impl Iso9660 { /// The number of bytes used by an ISO 9660 block. pub const BLOCK_SIZE: usize = 2048; @@ -101,6 +63,18 @@ impl Iso9660 { Self::open(&path, Iso9660Extensions::all()) } + fn open(path: &CStr, extensions: Iso9660Extensions) -> Option { + init_logger(); + + // SAFETY: path is duplicated by the method, so its safe to drop afterwards + let iso9660_ptr = + unsafe { libcdio_sys::iso9660_open_ext(path.as_ptr(), extensions.bits()) }; + + Some(Self { + ptr: NonNull::new(iso9660_ptr)?, + }) + } + /// Returns a builder object. See [`Iso9660Builder`]. pub fn builder<'a>(path: &'a Path) -> Iso9660Builder<'a> { Iso9660Builder::new(path) @@ -111,6 +85,32 @@ impl Iso9660 { self.get_identifier(libcdio_sys::iso9660_ifs_get_application_id) } + /// Helper for the methods that return iso9660 identifiers. + fn get_identifier( + &self, + func: unsafe extern "C" fn(*mut iso9660_t, *mut *mut c_char) -> bool, + ) -> Option { + let mut identifier_ptr = ptr::null_mut(); + + // SAFETY: The method allocates a string and points the identifier_ptr to it. + // It must be freed after use. + let success = unsafe { func(self.ptr.as_ptr(), &raw mut identifier_ptr) }; + if !success || identifier_ptr.is_null() { + return None; + } + + let identifier = unsafe { CStr::from_ptr(identifier_ptr) }; + let identifier = identifier.to_string_lossy().to_string(); + + // SAFETY: application_id has been duplicated into a Rust string + // above, thus safe to free + unsafe { + libcdio_sys::cdio_free(identifier_ptr.cast()); + } + + Some(identifier) + } + /// Returns the Data Preparer Identifier. pub fn data_preparer(&self) -> Option { self.get_identifier(libcdio_sys::iso9660_ifs_get_preparer_id) @@ -150,44 +150,19 @@ impl Iso9660 { Some(joliet_level) } +} - fn open(path: &CStr, extensions: Iso9660Extensions) -> Option { - init_logger(); - - // SAFETY: path is duplicated by the method, so its safe to drop afterwards - let iso9660_ptr = - unsafe { libcdio_sys::iso9660_open_ext(path.as_ptr(), extensions.bits()) }; - - Some(Self { - ptr: NonNull::new(iso9660_ptr)?, - }) +impl Drop for Iso9660 { + fn drop(&mut self) { + let _ = unsafe { libcdio_sys::iso9660_close(self.ptr.as_ptr()) }; } +} - /// Helper for the methods that return iso9660 identifiers. - fn get_identifier( - &self, - func: unsafe extern "C" fn(*mut iso9660_t, *mut *mut c_char) -> bool, - ) -> Option { - let mut identifier_ptr = ptr::null_mut(); - - // SAFETY: The method allocates a string and points the identifier_ptr to it. - // It must be freed after use. - let success = unsafe { func(self.ptr.as_ptr(), &raw mut identifier_ptr) }; - if !success || identifier_ptr.is_null() { - return None; - } - - let identifier = unsafe { CStr::from_ptr(identifier_ptr) }; - let identifier = identifier.to_string_lossy().to_string(); - - // SAFETY: application_id has been duplicated into a Rust string - // above, thus safe to free - unsafe { - libcdio_sys::cdio_free(identifier_ptr.cast()); - } - - Some(identifier) - } +/// A builder for [Iso9660]. +#[derive(Clone, Debug)] +pub struct Iso9660Builder<'a> { + extensions: Iso9660Extensions, + path: &'a Path, } impl<'a> Iso9660Builder<'a> { @@ -213,12 +188,37 @@ impl<'a> Iso9660Builder<'a> { } } -impl Drop for Iso9660 { - fn drop(&mut self) { - let _ = unsafe { libcdio_sys::iso9660_close(self.ptr.as_ptr()) }; +bitflags! { + /// ISO 9660 Extensions. + /// # Examples + /// ```rust, no_run + /// use libcdio_rs::iso9660::Iso9660Extensions; + /// // pick HighSierra and RockRidge + /// let extensions = Iso9660Extensions::HighSierra & Iso9660Extensions::RockRidge; + /// // pick everything except RockRidge + /// let extensions = Iso9660Extensions::all() - Iso9660Extensions::RockRidge; + /// // pick nothing + /// let extensions = Iso9660Extensions::empty(); + /// ``` + #[derive(Clone, Copy, Debug)] + pub struct Iso9660Extensions: u8 { + const HighSierra = iso_extension_enum_s_ISO_EXTENSION_HIGH_SIERRA as _; + const JolietLevel1 = iso_extension_enum_s_ISO_EXTENSION_JOLIET_LEVEL1 as _; + const JolietLevel2 = iso_extension_enum_s_ISO_EXTENSION_JOLIET_LEVEL2 as _; + const JolietLevel3 = iso_extension_enum_s_ISO_EXTENSION_JOLIET_LEVEL3 as _; + const RockRidge = iso_extension_enum_s_ISO_EXTENSION_ROCK_RIDGE as _; } } +/// Joliet level. +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +pub enum JolietLevel { + One = 1, + Two, + Three, +} + #[cfg(test)] pub(crate) mod tests { use super::*; diff --git a/libcdio-rs/src/iso9660/entry.rs b/libcdio-rs/src/iso9660/entry.rs index 0056940..69b55f7 100644 --- a/libcdio-rs/src/iso9660/entry.rs +++ b/libcdio-rs/src/iso9660/entry.rs @@ -28,19 +28,6 @@ use time::OffsetDateTime; use crate::iso9660::{Iso9660, ds, util}; -/// ISO 9660 file/directory entry. -pub struct Iso9660Entry<'a> { - /// The parent ISO 9660 object - pub(crate) iso: &'a Iso9660, - pub(crate) stat: NonNull, -} - -/// A type that implements [`io::Read`], for reading an ISO9660 entry. -pub struct Iso9660EntryReader<'a> { - bytes_read: usize, - entry: &'a Iso9660Entry<'a>, -} - impl Iso9660 { /// Read directory at `path` and return a list of entries. /// @@ -79,6 +66,13 @@ impl Iso9660 { } } +/// ISO 9660 file/directory entry. +pub struct Iso9660Entry<'a> { + /// The parent ISO 9660 object + pub(crate) iso: &'a Iso9660, + pub(crate) stat: NonNull, +} + impl Iso9660Entry<'_> { /// Returns the raw filename of the entry. /// Returns `None` if the filename has non UTF-8 characters or on error. @@ -160,6 +154,12 @@ impl Drop for Iso9660Entry<'_> { } } +/// A type that implements [`io::Read`], for reading an ISO9660 entry. +pub struct Iso9660EntryReader<'a> { + bytes_read: usize, + entry: &'a Iso9660Entry<'a>, +} + impl io::Read for Iso9660EntryReader<'_> { fn read(&mut self, buf: &mut [u8]) -> io::Result { let file_size = self.entry.total_size() as usize; diff --git a/libcdio-rs/src/iso9660/rock.rs b/libcdio-rs/src/iso9660/rock.rs index 28e823f..e9a09e5 100644 --- a/libcdio-rs/src/iso9660/rock.rs +++ b/libcdio-rs/src/iso9660/rock.rs @@ -25,26 +25,6 @@ use time::OffsetDateTime; use crate::iso9660::{Iso9660, entry::Iso9660Entry, util}; -/// ISO 9660 Rock Ridge extensions. -#[derive(Clone, Debug)] -#[non_exhaustive] -pub struct RockRidge { - /// Create time - pub create_time: Option, - /// Group ID - pub group_id: u32, - /// Number of hard links - pub hard_links: u32, - /// Unix file mode - pub mode: Mode, - /// Modify time - pub modify_time: Option, - /// Symlink target - pub symlink_to: Option, - /// User ID - pub user_id: u32, -} - impl Iso9660 { /// Checks if any file has Rock Ridge extensions. Returns `None` on error. /// This can be time consuming, therefore `file_limit` can be provided to @@ -95,6 +75,26 @@ impl Iso9660Entry<'_> { } } +/// ISO 9660 Rock Ridge extensions. +#[derive(Clone, Debug)] +#[non_exhaustive] +pub struct RockRidge { + /// Create time + pub create_time: Option, + /// Group ID + pub group_id: u32, + /// Number of hard links + pub hard_links: u32, + /// Unix file mode + pub mode: Mode, + /// Modify time + pub modify_time: Option, + /// Symlink target + pub symlink_to: Option, + /// User ID + pub user_id: u32, +} + fn convert_rock_timefield(field: iso_rock_time_s) -> Option { if !field.b_used { return None; diff --git a/libcdio-rs/src/iso9660/xa.rs b/libcdio-rs/src/iso9660/xa.rs index 03ca91d..8e8a9a2 100644 --- a/libcdio-rs/src/iso9660/xa.rs +++ b/libcdio-rs/src/iso9660/xa.rs @@ -21,36 +21,6 @@ use bitflags::bitflags; use crate::iso9660::entry::Iso9660Entry; -/// CD-ROM XA (eXtended Architecture) attributes -#[derive(Clone, Debug)] -#[non_exhaustive] -pub struct CdRomXa { - pub file_attr: XaFileAttributes, - pub file_num: u8, - pub group_id: u16, - pub user_id: u16, - total_size: u64, -} - -bitflags! { - /// XA File Attributes. - /// For more information: https://psx-spx.consoledev.net/cdromformat/#cdrom-iso-file-and-directory-descriptors - #[derive(Clone, Copy, Debug)] - pub struct XaFileAttributes: u16 { - const OwnerRead = 1 << 0; - const OwnerExecute = 1 << 2; - const GroupRead = 1 << 4; - const GroupExecute = 1 << 6; - const WorldRead = 1 << 8; - const WorldExecute = 1 << 10; - const Mode2 = 1 << 11; - const Mode2Form2 = 1 << 12; - const Interleaved = 1 << 13; - const Cdda = 1 << 14; - const Directory = 1 << 15; - } -} - impl Iso9660Entry<'_> { /// Return CD-ROM XA (eXtended Architecture) attributes. /// `None` is returned if the attributes are not present. @@ -73,6 +43,17 @@ impl Iso9660Entry<'_> { } } +/// CD-ROM XA (eXtended Architecture) attributes +#[derive(Clone, Debug)] +#[non_exhaustive] +pub struct CdRomXa { + pub file_attr: XaFileAttributes, + pub file_num: u8, + pub group_id: u16, + pub user_id: u16, + total_size: u64, +} + impl CdRomXa { /// Return multi extent size. /// Returns `None` if not using Mode2/Form2 encoding. @@ -91,6 +72,25 @@ impl CdRomXa { } } +bitflags! { + /// XA File Attributes. + /// For more information: https://psx-spx.consoledev.net/cdromformat/#cdrom-iso-file-and-directory-descriptors + #[derive(Clone, Copy, Debug)] + pub struct XaFileAttributes: u16 { + const OwnerRead = 1 << 0; + const OwnerExecute = 1 << 2; + const GroupRead = 1 << 4; + const GroupExecute = 1 << 6; + const WorldRead = 1 << 8; + const WorldExecute = 1 << 10; + const Mode2 = 1 << 11; + const Mode2Form2 = 1 << 12; + const Interleaved = 1 << 13; + const Cdda = 1 << 14; + const Directory = 1 << 15; + } +} + #[cfg(test)] mod tests { use std::path::Path; From 76d8c29741805779f348c707f5b37879b592a461 Mon Sep 17 00:00:00 2001 From: Shiva Kiran Koninty Date: Mon, 17 Aug 2026 09:15:14 +0530 Subject: [PATCH 02/13] lib/iso9660: Make re-export structure consistent with other modules Rather than have an `xa` module, re-export everything within `xa` and other child modules at the level of the `iso9660` module, keeping things consistent with others like `mmc`. --- libcdio-cli/src/iso-info/main.rs | 2 +- libcdio-rs/src/iso9660.rs | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/libcdio-cli/src/iso-info/main.rs b/libcdio-cli/src/iso-info/main.rs index 7a0f509..4f39cff 100644 --- a/libcdio-cli/src/iso-info/main.rs +++ b/libcdio-cli/src/iso-info/main.rs @@ -27,7 +27,7 @@ use anyhow::{Context, Result, bail}; use clap::Parser; use libcdio_rs::{ Iso9660, Udf, - iso9660::{Iso9660Extensions, xa::XaFileAttributes}, + iso9660::{Iso9660Extensions, XaFileAttributes}, }; use time::{UtcOffset, format_description::BorrowedFormatItem, macros::format_description}; use tracing_subscriber::EnvFilter; diff --git a/libcdio-rs/src/iso9660.rs b/libcdio-rs/src/iso9660.rs index c92ab78..33aa15e 100644 --- a/libcdio-rs/src/iso9660.rs +++ b/libcdio-rs/src/iso9660.rs @@ -21,12 +21,11 @@ mod ds; mod entry; mod rock; mod util; -pub mod xa; +mod xa; -pub use entry::Iso9660Entry; -pub use rock::RockRidge; -#[doc(inline)] -pub use xa::CdRomXa; +pub use entry::*; +pub use rock::*; +pub use xa::*; use std::{ ffi::{CStr, CString, c_char}, From 4f8f856d7c9a2d68313d128f08928c3022904a39 Mon Sep 17 00:00:00 2001 From: Shiva Kiran Koninty Date: Mon, 17 Aug 2026 09:25:17 +0530 Subject: [PATCH 03/13] lib/iso9660: Rename `Iso9660*` to `Iso*` for brevity --- libcdio-cli/src/iso-info/main.rs | 22 ++++++------ libcdio-cli/src/iso-read/main.rs | 4 +-- libcdio-rs/src/iso9660.rs | 62 ++++++++++++++++---------------- libcdio-rs/src/iso9660/entry.rs | 60 +++++++++++++++---------------- libcdio-rs/src/iso9660/rock.rs | 24 ++++++------- libcdio-rs/src/iso9660/xa.rs | 8 ++--- libcdio-rs/src/lib.rs | 2 +- 7 files changed, 91 insertions(+), 91 deletions(-) diff --git a/libcdio-cli/src/iso-info/main.rs b/libcdio-cli/src/iso-info/main.rs index 4f39cff..57a792f 100644 --- a/libcdio-cli/src/iso-info/main.rs +++ b/libcdio-cli/src/iso-info/main.rs @@ -26,8 +26,8 @@ use std::{ use anyhow::{Context, Result, bail}; use clap::Parser; use libcdio_rs::{ - Iso9660, Udf, - iso9660::{Iso9660Extensions, XaFileAttributes}, + Iso, Udf, + iso9660::{IsoExtensions, XaFileAttributes}, }; use time::{UtcOffset, format_description::BorrowedFormatItem, macros::format_description}; use tracing_subscriber::EnvFilter; @@ -51,14 +51,14 @@ fn main() -> Result<()> { 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", ); - let mut extensions = Iso9660Extensions::all(); + let mut extensions = IsoExtensions::all(); if cli.no_joliet { - extensions -= Iso9660Extensions::JolietLevel1; - extensions -= Iso9660Extensions::JolietLevel2; - extensions -= Iso9660Extensions::JolietLevel3; + extensions -= IsoExtensions::JolietLevel1; + extensions -= IsoExtensions::JolietLevel2; + extensions -= IsoExtensions::JolietLevel3; } - if let Some(iso) = Iso9660::builder(&file).extensions(extensions).build() { + if let Some(iso) = Iso::builder(&file).extensions(extensions).build() { print_iso9660_metadata(&iso, &file, &mut output) .context("io error while printing iso9660 metadata")?; @@ -86,7 +86,7 @@ fn main() -> Result<()> { } fn print_iso9660_metadata( - iso: &Iso9660, + iso: &Iso, path: &Path, mut out: impl io::Write, ) -> Result<(), io::Error> { @@ -107,7 +107,7 @@ fn print_iso9660_metadata( } fn print_rock_ridge( - iso: &Iso9660, + iso: &Iso, file_limit: Option, mut out: impl io::Write, ) -> Result<(), io::Error> { @@ -121,7 +121,7 @@ fn print_rock_ridge( /// Outputs the file contents of the ISO 9660 image in an ls-like listing format. fn print_iso9660_contents( - iso: &Iso9660, + iso: &Iso, mut out: impl io::Write, use_rock_ridge: bool, use_xa: bool, @@ -295,7 +295,7 @@ fn print_udf_contents(path: PathBuf, out: &mut dyn io::Write) -> Result<()> { Ok(()) } -fn print_joliet_level(iso: &Iso9660, mut out: impl io::Write) -> Result<(), io::Error> { +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"); }; diff --git a/libcdio-cli/src/iso-read/main.rs b/libcdio-cli/src/iso-read/main.rs index 329cebd..760d09d 100644 --- a/libcdio-cli/src/iso-read/main.rs +++ b/libcdio-cli/src/iso-read/main.rs @@ -23,7 +23,7 @@ use std::{ use anyhow::{Context, Result, bail}; use clap::Parser; -use libcdio_rs::{Iso9660, Udf}; +use libcdio_rs::{Iso, Udf}; use tracing_subscriber::EnvFilter; use crate::cli::Cli; @@ -65,7 +65,7 @@ fn udf_extract(image: PathBuf, extract: String, output: &mut File) -> Result<()> /// Extract given file from an ISO 9660 image. fn iso9660_extract(image: &Path, extract: &str, output: &mut File) -> Result<()> { - let iso = Iso9660::new(image) + let iso = Iso::new(image) .with_context(|| format!("could not open image '{}' as iso9660", image.display()))?; let entry = iso.entry(extract).with_context(|| { format!( diff --git a/libcdio-rs/src/iso9660.rs b/libcdio-rs/src/iso9660.rs index 33aa15e..2df5e94 100644 --- a/libcdio-rs/src/iso9660.rs +++ b/libcdio-rs/src/iso9660.rs @@ -46,11 +46,11 @@ use num_enum::{IntoPrimitive, TryFromPrimitive}; use crate::logging::init_logger; /// The main ISO 9660 type -pub struct Iso9660 { +pub struct Iso { pub(crate) ptr: NonNull, } -impl Iso9660 { +impl Iso { /// The number of bytes used by an ISO 9660 block. pub const BLOCK_SIZE: usize = 2048; @@ -59,10 +59,10 @@ impl Iso9660 { pub fn new(path: &Path) -> Option { let path = CString::new(path.to_str()?).ok()?; - Self::open(&path, Iso9660Extensions::all()) + Self::open(&path, IsoExtensions::all()) } - fn open(path: &CStr, extensions: Iso9660Extensions) -> Option { + fn open(path: &CStr, extensions: IsoExtensions) -> Option { init_logger(); // SAFETY: path is duplicated by the method, so its safe to drop afterwards @@ -74,9 +74,9 @@ impl Iso9660 { }) } - /// Returns a builder object. See [`Iso9660Builder`]. - pub fn builder<'a>(path: &'a Path) -> Iso9660Builder<'a> { - Iso9660Builder::new(path) + /// Returns a builder object. See [`IsoBuilder`]. + pub fn builder<'a>(path: &'a Path) -> IsoBuilder<'a> { + IsoBuilder::new(path) } /// Returns the Application Identifier. @@ -151,39 +151,39 @@ impl Iso9660 { } } -impl Drop for Iso9660 { +impl Drop for Iso { fn drop(&mut self) { let _ = unsafe { libcdio_sys::iso9660_close(self.ptr.as_ptr()) }; } } -/// A builder for [Iso9660]. +/// A builder for [Iso]. #[derive(Clone, Debug)] -pub struct Iso9660Builder<'a> { - extensions: Iso9660Extensions, +pub struct IsoBuilder<'a> { + extensions: IsoExtensions, path: &'a Path, } -impl<'a> Iso9660Builder<'a> { +impl<'a> IsoBuilder<'a> { pub fn new(path: &'a Path) -> Self { Self { path, - extensions: Iso9660Extensions::empty(), + extensions: IsoExtensions::empty(), } } /// Set the extensions to be activated. This is set to be empty by default. - pub fn extensions(mut self, extensions: Iso9660Extensions) -> Self { + pub fn extensions(mut self, extensions: IsoExtensions) -> Self { self.extensions = extensions; self } /// Build the iso9660 type with the set options. /// Returns `None` on error. - pub fn build(self) -> Option { + pub fn build(self) -> Option { let path = CString::new(self.path.to_str()?).ok()?; - Iso9660::open(&path, self.extensions) + Iso::open(&path, self.extensions) } } @@ -191,16 +191,16 @@ bitflags! { /// ISO 9660 Extensions. /// # Examples /// ```rust, no_run - /// use libcdio_rs::iso9660::Iso9660Extensions; + /// use libcdio_rs::iso9660::IsoExtensions; /// // pick HighSierra and RockRidge - /// let extensions = Iso9660Extensions::HighSierra & Iso9660Extensions::RockRidge; + /// let extensions = IsoExtensions::HighSierra & IsoExtensions::RockRidge; /// // pick everything except RockRidge - /// let extensions = Iso9660Extensions::all() - Iso9660Extensions::RockRidge; + /// let extensions = IsoExtensions::all() - IsoExtensions::RockRidge; /// // pick nothing - /// let extensions = Iso9660Extensions::empty(); + /// let extensions = IsoExtensions::empty(); /// ``` #[derive(Clone, Copy, Debug)] - pub struct Iso9660Extensions: u8 { + pub struct IsoExtensions: u8 { const HighSierra = iso_extension_enum_s_ISO_EXTENSION_HIGH_SIERRA as _; const JolietLevel1 = iso_extension_enum_s_ISO_EXTENSION_JOLIET_LEVEL1 as _; const JolietLevel2 = iso_extension_enum_s_ISO_EXTENSION_JOLIET_LEVEL2 as _; @@ -231,14 +231,14 @@ pub(crate) mod tests { #[test_log::test(test)] fn new() { - let iso = Iso9660::new(test_rockridge_file()); + let iso = Iso::new(test_rockridge_file()); assert!(iso.is_some()); } #[test] fn builder() { - let extensions = Iso9660Extensions::HighSierra & Iso9660Extensions::RockRidge; - let iso = Iso9660::builder(test_rockridge_file()) + let extensions = IsoExtensions::HighSierra & IsoExtensions::RockRidge; + let iso = Iso::builder(test_rockridge_file()) .extensions(extensions) .build(); assert!(iso.is_some()); @@ -246,13 +246,13 @@ pub(crate) mod tests { #[test] fn joliet_level() { - let iso = Iso9660::new(test_joliet_file()).unwrap(); + let iso = Iso::new(test_joliet_file()).unwrap(); assert_eq!(iso.joliet_level().unwrap(), JolietLevel::Three); } #[test] fn application() { - let iso = Iso9660::new(test_rockridge_file()).unwrap(); + let iso = Iso::new(test_rockridge_file()).unwrap(); assert_eq!( &iso.application().unwrap(), "K3B THE CD KREATOR VERSION 0.11.20 (C) 2003 SEBASTIAN TRUEG AND THE K3B TEAM" @@ -261,31 +261,31 @@ pub(crate) mod tests { #[test] fn data_preparer() { - let iso = Iso9660::new(test_rockridge_file()).unwrap(); + let iso = Iso::new(test_rockridge_file()).unwrap(); assert_eq!(&iso.data_preparer().unwrap(), "K3b - Version 0.11.20",); } #[test] fn publisher() { - let iso = Iso9660::new(test_rockridge_file()).unwrap(); + let iso = Iso::new(test_rockridge_file()).unwrap(); assert_eq!(&iso.publisher().unwrap(), "Rocky Bernstein"); } #[test] fn system() { - let iso = Iso9660::new(test_rockridge_file()).unwrap(); + let iso = Iso::new(test_rockridge_file()).unwrap(); assert_eq!(&iso.system().unwrap(), "LINUX"); } #[test] fn volume() { - let iso = Iso9660::new(test_rockridge_file()).unwrap(); + let iso = Iso::new(test_rockridge_file()).unwrap(); assert_eq!(&iso.volume().unwrap(), "Rock Ridge Copy test"); } #[test] fn volume_set() { - let iso = Iso9660::new(test_rockridge_file()).unwrap(); + let iso = Iso::new(test_rockridge_file()).unwrap(); assert!(&iso.volume_set().is_none()); } } diff --git a/libcdio-rs/src/iso9660/entry.rs b/libcdio-rs/src/iso9660/entry.rs index 69b55f7..7af31b6 100644 --- a/libcdio-rs/src/iso9660/entry.rs +++ b/libcdio-rs/src/iso9660/entry.rs @@ -26,25 +26,25 @@ use std::{ use libcdio_sys::{iso9660_stat_s, iso9660_stat_s__STAT_DIR}; use time::OffsetDateTime; -use crate::iso9660::{Iso9660, ds, util}; +use crate::iso9660::{Iso, ds, util}; -impl Iso9660 { +impl Iso { /// Read directory at `path` and return a list of entries. /// /// Only '/' may be used for path separators. /// Returns `None` on error. - pub fn read_dir(&self, path: &str) -> Option>> { + pub fn read_dir(&self, path: &str) -> Option>> { let path = CString::new(path).ok()?; let dirlist = unsafe { libcdio_sys::iso9660_ifs_readdir(self.ptr.as_ptr(), path.as_ptr()) }; if dirlist.is_null() { return None; } - // SAFETY: dirlist is not null and the data will be owned by `Iso9660Entry`. + // SAFETY: dirlist is not null and the data will be owned by `IsoEntry`. let dirlist = unsafe { ds::cdiolist_to_vec(dirlist) }; let dirlist = dirlist .into_iter() .filter_map(|entry| { - Some(Iso9660Entry { + Some(IsoEntry { iso: self, stat: NonNull::new(entry.cast())?, }) @@ -55,11 +55,11 @@ impl Iso9660 { } /// Return entry for `path`. `None` is returned on error. - pub fn entry(&self, path: &str) -> Option> { + pub fn entry(&self, path: &str) -> Option> { let path = CString::new(path).ok()?; let stat = unsafe { libcdio_sys::iso9660_ifs_stat(self.ptr.as_ptr(), path.as_ptr()) }; - Some(Iso9660Entry { + Some(IsoEntry { iso: self, stat: NonNull::new(stat)?, }) @@ -67,13 +67,13 @@ impl Iso9660 { } /// ISO 9660 file/directory entry. -pub struct Iso9660Entry<'a> { +pub struct IsoEntry<'a> { /// The parent ISO 9660 object - pub(crate) iso: &'a Iso9660, + pub(crate) iso: &'a Iso, pub(crate) stat: NonNull, } -impl Iso9660Entry<'_> { +impl IsoEntry<'_> { /// Returns the raw filename of the entry. /// Returns `None` if the filename has non UTF-8 characters or on error. pub fn filename_raw(&self) -> Option<&str> { @@ -140,33 +140,33 @@ impl Iso9660Entry<'_> { /// A type that implements [`io::Read`], for reading an ISO9660 entry. /// Returns `None` on error. - pub fn reader(&self) -> Iso9660EntryReader<'_> { - Iso9660EntryReader { + pub fn reader(&self) -> IsoEntryReader<'_> { + IsoEntryReader { bytes_read: 0, entry: self, } } } -impl Drop for Iso9660Entry<'_> { +impl Drop for IsoEntry<'_> { fn drop(&mut self) { unsafe { libcdio_sys::iso9660_stat_free(self.stat.as_ptr()) } } } /// A type that implements [`io::Read`], for reading an ISO9660 entry. -pub struct Iso9660EntryReader<'a> { +pub struct IsoEntryReader<'a> { bytes_read: usize, - entry: &'a Iso9660Entry<'a>, + entry: &'a IsoEntry<'a>, } -impl io::Read for Iso9660EntryReader<'_> { +impl io::Read for IsoEntryReader<'_> { fn read(&mut self, buf: &mut [u8]) -> io::Result { let file_size = self.entry.total_size() as usize; let mut buf_read = 0; while self.bytes_read < file_size && buf_read < buf.len() { - let lsn = self.entry.lsn() + (self.bytes_read / Iso9660::BLOCK_SIZE) as i32; - let mut block = [0_u8; Iso9660::BLOCK_SIZE]; + let lsn = self.entry.lsn() + (self.bytes_read / Iso::BLOCK_SIZE) as i32; + let mut block = [0_u8; Iso::BLOCK_SIZE]; let ret = unsafe { libcdio_sys::iso9660_iso_seek_read( self.entry.iso.ptr.as_ptr(), @@ -177,7 +177,7 @@ impl io::Read for Iso9660EntryReader<'_> { }; // the returned value is either BLOCK_SIZE or zero on error, thus // excess bytes past the last read must be handled. - // cast is safe as Iso9660::BLOCK_SIZE < i16::MAX + // cast is safe as Iso::BLOCK_SIZE < i16::MAX if ret != block.len() as _ { return Err(io::Error::other(format!( "error reading block at lsn: {lsn}", @@ -200,7 +200,7 @@ impl io::Read for Iso9660EntryReader<'_> { } } -impl io::Seek for Iso9660EntryReader<'_> { +impl io::Seek for IsoEntryReader<'_> { fn seek(&mut self, pos: io::SeekFrom) -> io::Result { self.bytes_read = match pos { io::SeekFrom::Start(offset) => offset as usize, @@ -221,20 +221,20 @@ mod tests { use time::macros::datetime; use crate::iso9660::{ - Iso9660, + Iso, tests::{test_joliet_file, test_rockridge_file}, }; #[test] fn read_dir() { - let iso = Iso9660::new(test_joliet_file()).unwrap(); + let iso = Iso::new(test_joliet_file()).unwrap(); let entries = iso.read_dir("/").unwrap(); assert_eq!(entries.len(), 3); } #[test] fn filename() { - let iso = Iso9660::new(test_rockridge_file()).unwrap(); + let iso = Iso::new(test_rockridge_file()).unwrap(); let entries = iso.read_dir("/").unwrap(); let names: Vec<_> = entries.iter().map(|e| e.filename_raw().unwrap()).collect(); assert_eq!( @@ -245,7 +245,7 @@ mod tests { #[test] fn filename_translated() { - let iso = Iso9660::new(test_rockridge_file()).unwrap(); + let iso = Iso::new(test_rockridge_file()).unwrap(); let entries = iso.read_dir("/").unwrap(); let names: Vec<_> = entries.iter().map(|e| e.filename().unwrap()).collect(); assert_eq!( @@ -256,28 +256,28 @@ mod tests { #[test] fn entry() { - let iso = Iso9660::new(test_rockridge_file()).unwrap(); + let iso = Iso::new(test_rockridge_file()).unwrap(); let entry = iso.entry("/copy").unwrap(); assert_eq!(entry.filename().unwrap(), "copy"); } #[test] fn total_size() { - let iso = Iso9660::new(test_rockridge_file()).unwrap(); + let iso = Iso::new(test_rockridge_file()).unwrap(); let entry = iso.entry("/COPYING").unwrap(); assert_eq!(entry.total_size(), 17992); } #[test] fn lsn() { - let iso = Iso9660::new(test_rockridge_file()).unwrap(); + let iso = Iso::new(test_rockridge_file()).unwrap(); let entry = iso.entry("/COPYING").unwrap(); assert_eq!(entry.lsn(), 27); } #[test] fn is_dir() { - let iso = Iso9660::new(test_rockridge_file()).unwrap(); + let iso = Iso::new(test_rockridge_file()).unwrap(); let file = iso.entry("/COPYING").unwrap(); assert!(!file.is_dir()); @@ -287,7 +287,7 @@ mod tests { #[test] fn timestamp() { - let iso = Iso9660::new(test_rockridge_file()).unwrap(); + let iso = Iso::new(test_rockridge_file()).unwrap(); let entry = iso.entry("/COPYING").unwrap(); assert_eq!( entry.timestamp().unwrap(), @@ -297,7 +297,7 @@ mod tests { #[test] fn read() { - let iso = Iso9660::new(Path::new("../test-data/xa.iso")).unwrap(); + let iso = Iso::new(Path::new("../test-data/xa.iso")).unwrap(); let entry = iso.entry("copying").unwrap(); let gpl = std::fs::read_to_string("../COPYING").unwrap(); let mut reader = entry.reader(); diff --git a/libcdio-rs/src/iso9660/rock.rs b/libcdio-rs/src/iso9660/rock.rs index e9a09e5..b7f5c64 100644 --- a/libcdio-rs/src/iso9660/rock.rs +++ b/libcdio-rs/src/iso9660/rock.rs @@ -23,9 +23,9 @@ use file_mode::Mode; use libcdio_sys::{bool_3way_t_nope, bool_3way_t_yep, iso_rock_time_s}; use time::OffsetDateTime; -use crate::iso9660::{Iso9660, entry::Iso9660Entry, util}; +use crate::iso9660::{Iso, entry::IsoEntry, util}; -impl Iso9660 { +impl Iso { /// Checks if any file has Rock Ridge extensions. Returns `None` on error. /// This can be time consuming, therefore `file_limit` can be provided to /// limit the number of files to scan. @@ -42,7 +42,7 @@ impl Iso9660 { } } -impl Iso9660Entry<'_> { +impl IsoEntry<'_> { /// Rock Ridge extensions. /// `None` is returned if Rock ridge extensions are missing, or if it /// could not be determined. @@ -124,24 +124,24 @@ mod tests { #[test] fn have_rock_ridge() { - let iso = Iso9660::new(test_rockridge_file()).unwrap(); + let iso = Iso::new(test_rockridge_file()).unwrap(); assert!(iso.have_rock_ridge(None).unwrap()); } #[test] fn rock_ridge() { - let iso = Iso9660::new(test_rockridge_file()).unwrap(); + let iso = Iso::new(test_rockridge_file()).unwrap(); let entry = iso.entry("/COPYING").unwrap(); assert!(entry.rock_ridge().is_some()); - let iso = Iso9660::new(test_joliet_file()).unwrap(); + let iso = Iso::new(test_joliet_file()).unwrap(); let entry = iso.entry("/libcdio/COPYING").unwrap(); assert!(entry.rock_ridge().is_none()); } #[test] fn mode() { - let iso = Iso9660::new(test_rockridge_file()).unwrap(); + let iso = Iso::new(test_rockridge_file()).unwrap(); let entry = iso.entry("/zero").unwrap(); let mode = entry.rock_ridge().unwrap().mode; @@ -162,7 +162,7 @@ mod tests { #[test] fn symlink_to() { - let iso = Iso9660::new(test_rockridge_file()).unwrap(); + let iso = Iso::new(test_rockridge_file()).unwrap(); let entry = iso.entry("/COPYING").unwrap(); let rock = entry.rock_ridge().unwrap(); @@ -179,7 +179,7 @@ mod tests { #[test] fn hard_links() { - let iso = Iso9660::new(test_rockridge_file()).unwrap(); + let iso = Iso::new(test_rockridge_file()).unwrap(); let entry = iso.entry("/COPYING").unwrap(); let rock = entry.rock_ridge().unwrap(); assert_eq!(rock.hard_links, 1); @@ -191,7 +191,7 @@ mod tests { #[test] fn user_id() { - let iso = Iso9660::new(test_rockridge_file()).unwrap(); + let iso = Iso::new(test_rockridge_file()).unwrap(); let entry = iso.entry("/COPYING").unwrap(); let rock = entry.rock_ridge().unwrap(); assert_eq!(rock.user_id, 0); @@ -199,7 +199,7 @@ mod tests { #[test] fn group_id() { - let iso = Iso9660::new(test_rockridge_file()).unwrap(); + let iso = Iso::new(test_rockridge_file()).unwrap(); let entry = iso.entry("/COPYING").unwrap(); let rock = entry.rock_ridge().unwrap(); assert_eq!(rock.group_id, 0); @@ -207,7 +207,7 @@ mod tests { #[test] fn time() { - let iso = Iso9660::new(test_rockridge_file()).unwrap(); + let iso = Iso::new(test_rockridge_file()).unwrap(); let entry = iso.entry("/COPYING").unwrap(); let rock = entry.rock_ridge().unwrap(); assert_eq!( diff --git a/libcdio-rs/src/iso9660/xa.rs b/libcdio-rs/src/iso9660/xa.rs index 8e8a9a2..11a7fa3 100644 --- a/libcdio-rs/src/iso9660/xa.rs +++ b/libcdio-rs/src/iso9660/xa.rs @@ -19,9 +19,9 @@ use bitflags::bitflags; -use crate::iso9660::entry::Iso9660Entry; +use crate::iso9660::entry::IsoEntry; -impl Iso9660Entry<'_> { +impl IsoEntry<'_> { /// Return CD-ROM XA (eXtended Architecture) attributes. /// `None` is returned if the attributes are not present. pub fn xa(&self) -> Option { @@ -95,13 +95,13 @@ bitflags! { mod tests { use std::path::Path; - use crate::iso9660::Iso9660; + use crate::iso9660::Iso; use super::*; #[test] fn xa() { - let iso = Iso9660::new(Path::new("../test-data/xa.iso")).unwrap(); + let iso = Iso::new(Path::new("../test-data/xa.iso")).unwrap(); let entry = iso.entry("/copying").unwrap(); let xa = entry.xa().unwrap(); assert_eq!(xa.file_num, 0); diff --git a/libcdio-rs/src/lib.rs b/libcdio-rs/src/lib.rs index 657fdfe..90927d2 100644 --- a/libcdio-rs/src/lib.rs +++ b/libcdio-rs/src/lib.rs @@ -34,7 +34,7 @@ pub use crate::{drive::Drive, mmc::Mmc}; #[cfg(feature = "iso9660")] #[doc(inline)] -pub use crate::iso9660::Iso9660; +pub use crate::iso9660::Iso; #[cfg(feature = "udf")] #[doc(inline)] From 8d851082996f8f7fdd02c4ce1ef03d22cc45dffe Mon Sep 17 00:00:00 2001 From: Shiva Kiran Koninty Date: Mon, 17 Aug 2026 09:43:13 +0530 Subject: [PATCH 04/13] lib/iso9660: Merge `mod ds` into `mod util` --- libcdio-rs/src/iso9660.rs | 1 - libcdio-rs/src/iso9660/ds.rs | 65 --------------------------------- libcdio-rs/src/iso9660/entry.rs | 4 +- libcdio-rs/src/iso9660/util.rs | 48 +++++++++++++++++++++++- 4 files changed, 49 insertions(+), 69 deletions(-) delete mode 100644 libcdio-rs/src/iso9660/ds.rs diff --git a/libcdio-rs/src/iso9660.rs b/libcdio-rs/src/iso9660.rs index 2df5e94..312b625 100644 --- a/libcdio-rs/src/iso9660.rs +++ b/libcdio-rs/src/iso9660.rs @@ -17,7 +17,6 @@ //! ISO 9660 filesystem related routines. -mod ds; mod entry; mod rock; mod util; diff --git a/libcdio-rs/src/iso9660/ds.rs b/libcdio-rs/src/iso9660/ds.rs deleted file mode 100644 index 73bed1d..0000000 --- a/libcdio-rs/src/iso9660/ds.rs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (C) 2026 Shiva Kiran Koninty -// -// 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 . - -//! Data structure conversion methods from libcdio's ds.c - -use std::ffi::c_void; - -use libcdio_sys::_CdioList; - -/// Returns a vec of pointers to the data of the cdio list. -/// Frees the list nodes, without freeing the data. -/// # Safety -/// - `cdio_list` must not be null. -/// - The list data must be owned by the caller. -pub unsafe fn cdiolist_to_vec(cdio_list: *mut _CdioList) -> Vec<*mut c_void> { - let mut list = Vec::new(); - let mut cur = unsafe { libcdio_sys::_cdio_list_begin(cdio_list) }; - while !cur.is_null() { - let data = unsafe { libcdio_sys::_cdio_list_node_data(cur) }; - list.push(data); - cur = unsafe { libcdio_sys::_cdio_list_node_next(cur) }; - } - - unsafe { - libcdio_sys::_cdio_list_free(cdio_list, 0, None); - } - - list -} - -#[cfg(test)] -mod tests { - use std::ffi::CString; - - #[test] - fn cdiolist_to_vec() { - let a = CString::new("This is A").unwrap(); - let b = CString::new("This is B").unwrap(); - - let cdiolist = unsafe { libcdio_sys::_cdio_list_new() }; - unsafe { libcdio_sys::_cdio_list_append(cdiolist, a.into_raw().cast()) }; - unsafe { libcdio_sys::_cdio_list_append(cdiolist, b.into_raw().cast()) }; - - let list = unsafe { super::cdiolist_to_vec(cdiolist) }; - let a = unsafe { CString::from_raw(list[0].cast()) }; - let b = unsafe { CString::from_raw(list[1].cast()) }; - - assert_eq!(&a, c"This is A"); - assert_eq!(&b, c"This is B"); - } -} diff --git a/libcdio-rs/src/iso9660/entry.rs b/libcdio-rs/src/iso9660/entry.rs index 7af31b6..024244c 100644 --- a/libcdio-rs/src/iso9660/entry.rs +++ b/libcdio-rs/src/iso9660/entry.rs @@ -26,7 +26,7 @@ use std::{ use libcdio_sys::{iso9660_stat_s, iso9660_stat_s__STAT_DIR}; use time::OffsetDateTime; -use crate::iso9660::{Iso, ds, util}; +use crate::iso9660::{Iso, util}; impl Iso { /// Read directory at `path` and return a list of entries. @@ -40,7 +40,7 @@ impl Iso { return None; } // SAFETY: dirlist is not null and the data will be owned by `IsoEntry`. - let dirlist = unsafe { ds::cdiolist_to_vec(dirlist) }; + let dirlist = unsafe { util::cdiolist_to_vec(dirlist) }; let dirlist = dirlist .into_iter() .filter_map(|entry| { diff --git a/libcdio-rs/src/iso9660/util.rs b/libcdio-rs/src/iso9660/util.rs index 3c36f6b..9a3926e 100644 --- a/libcdio-rs/src/iso9660/util.rs +++ b/libcdio-rs/src/iso9660/util.rs @@ -15,8 +15,11 @@ // You should have received a copy of the GNU General Public License // along with libcdio-rs. If not, see . -//! Utility methods such as conversions +//! Utility and data structure conversion routines. +use std::ffi::c_void; + +use libcdio_sys::_CdioList; use time::{Date, OffsetDateTime, Time, UtcOffset, error}; /// Convert `tm` representing local time to a `OffsetDateTime`. @@ -38,3 +41,46 @@ pub(crate) fn convert_tm_local( .expect("could not obtain the system offset"), )) } + +/// Returns a vec of pointers to the data of the cdio list. +/// Frees the list nodes, without freeing the data. +/// # Safety +/// - `cdio_list` must not be null. +/// - The list data must be owned by the caller. +pub unsafe fn cdiolist_to_vec(cdio_list: *mut _CdioList) -> Vec<*mut c_void> { + let mut list = Vec::new(); + let mut cur = unsafe { libcdio_sys::_cdio_list_begin(cdio_list) }; + while !cur.is_null() { + let data = unsafe { libcdio_sys::_cdio_list_node_data(cur) }; + list.push(data); + cur = unsafe { libcdio_sys::_cdio_list_node_next(cur) }; + } + + unsafe { + libcdio_sys::_cdio_list_free(cdio_list, 0, None); + } + + list +} + +#[cfg(test)] +mod tests { + use std::ffi::CString; + + #[test] + fn cdiolist_to_vec() { + let a = CString::new("This is A").unwrap(); + let b = CString::new("This is B").unwrap(); + + let cdiolist = unsafe { libcdio_sys::_cdio_list_new() }; + unsafe { libcdio_sys::_cdio_list_append(cdiolist, a.into_raw().cast()) }; + unsafe { libcdio_sys::_cdio_list_append(cdiolist, b.into_raw().cast()) }; + + let list = unsafe { super::cdiolist_to_vec(cdiolist) }; + let a = unsafe { CString::from_raw(list[0].cast()) }; + let b = unsafe { CString::from_raw(list[1].cast()) }; + + assert_eq!(&a, c"This is A"); + assert_eq!(&b, c"This is B"); + } +} From 44063b7abc6f0034de66606f5f777ef99afc5882 Mon Sep 17 00:00:00 2001 From: Shiva Kiran Koninty Date: Mon, 17 Aug 2026 10:46:56 +0530 Subject: [PATCH 05/13] lib/iso9660: Add `IsoOpenError` --- libcdio-cli/src/iso-info/main.rs | 35 ++++++------- libcdio-cli/src/iso-read/main.rs | 13 ++--- libcdio-rs/src/iso9660.rs | 86 +++++++++++++++++++++----------- libcdio-rs/src/iso9660/entry.rs | 4 +- libcdio-rs/src/iso9660/xa.rs | 4 +- 5 files changed, 82 insertions(+), 60 deletions(-) diff --git a/libcdio-cli/src/iso-info/main.rs b/libcdio-cli/src/iso-info/main.rs index 57a792f..962ad66 100644 --- a/libcdio-cli/src/iso-info/main.rs +++ b/libcdio-cli/src/iso-info/main.rs @@ -58,28 +58,25 @@ fn main() -> Result<()> { extensions -= IsoExtensions::JolietLevel3; } - if let Some(iso) = Iso::builder(&file).extensions(extensions).build() { - 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.udf { + return print_udf_contents(file, &mut output); + } - print_joliet_level(&iso, &mut output).context("io error while printing joliet level")?; + let iso = Iso::builder(file.clone()).extensions(extensions).build()?; + print_iso9660_metadata(&iso, &file, &mut output) + .context("io error while printing iso9660 metadata")?; - if cli.iso9660 { - print_iso9660_contents(&iso, &mut output, !cli.no_rock_ridge, !cli.no_xa) - .context("error printing iso9660 contents")?; - } - } else if !cli.udf { - bail!("error opening iso9660 image: {}", file.display()); - }; + 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.udf { - print_udf_contents(file, &mut output)?; + print_joliet_level(&iso, &mut output).context("io error while printing joliet level")?; + + if cli.iso9660 { + print_iso9660_contents(&iso, &mut output, !cli.no_rock_ridge, !cli.no_xa) + .context("error printing iso9660 contents")?; } Ok(()) diff --git a/libcdio-cli/src/iso-read/main.rs b/libcdio-cli/src/iso-read/main.rs index 760d09d..b0c43d7 100644 --- a/libcdio-cli/src/iso-read/main.rs +++ b/libcdio-cli/src/iso-read/main.rs @@ -15,11 +15,7 @@ // You should have received a copy of the GNU General Public License // along with libcdio-cli. If not, see . -use std::{ - fs::File, - io, - path::{Path, PathBuf}, -}; +use std::{fs::File, io, path::PathBuf}; use anyhow::{Context, Result, bail}; use clap::Parser; @@ -47,7 +43,7 @@ fn main() -> Result<()> { if cli.udf { udf_extract(image, cli.extract, &mut output)?; } else { - iso9660_extract(&image, &cli.extract, &mut output)?; + iso9660_extract(image, &cli.extract, &mut output)?; } Ok(()) @@ -64,9 +60,8 @@ fn udf_extract(image: PathBuf, extract: String, output: &mut File) -> Result<()> } /// Extract given file from an ISO 9660 image. -fn iso9660_extract(image: &Path, extract: &str, output: &mut File) -> Result<()> { - let iso = Iso::new(image) - .with_context(|| format!("could not open image '{}' as iso9660", image.display()))?; +fn iso9660_extract(image: PathBuf, extract: &str, output: &mut File) -> Result<()> { + let iso = Iso::new(image.clone())?; let entry = iso.entry(extract).with_context(|| { format!( "could not open file '{}' from iso9660 image: {}", diff --git a/libcdio-rs/src/iso9660.rs b/libcdio-rs/src/iso9660.rs index 312b625..146c39a 100644 --- a/libcdio-rs/src/iso9660.rs +++ b/libcdio-rs/src/iso9660.rs @@ -24,11 +24,14 @@ mod xa; pub use entry::*; pub use rock::*; +use thiserror::Error; +use tracing::error; pub use xa::*; use std::{ - ffi::{CStr, CString, c_char}, - path::Path, + error::Error, + ffi::{CStr, CString, OsString, c_char}, + path::{Path, PathBuf}, ptr::{self, NonNull}, }; @@ -55,26 +58,30 @@ impl Iso { /// Open an ISO 9660 image for reading at given `path`, with all iso9660 /// extension flags enabled. Returns `None` on error. - pub fn new(path: &Path) -> Option { - let path = CString::new(path.to_str()?).ok()?; - - Self::open(&path, IsoExtensions::all()) + pub fn new(path: PathBuf) -> Result { + Self::open(path, IsoExtensions::all()) } - fn open(path: &CStr, extensions: IsoExtensions) -> Option { + fn open(path: PathBuf, extensions: IsoExtensions) -> Result { init_logger(); + let path = CString::new(path.into_os_string().as_encoded_bytes()) + .inspect_err(|err| error!(%err, "invalid ISO 9660 path")) + .map_err(|err| IsoOpenError::new(err.clone().into_vec(), err.into()))?; + // SAFETY: path is duplicated by the method, so its safe to drop afterwards let iso9660_ptr = unsafe { libcdio_sys::iso9660_open_ext(path.as_ptr(), extensions.bits()) }; - Some(Self { - ptr: NonNull::new(iso9660_ptr)?, - }) + NonNull::new(iso9660_ptr) + .map(|ptr| Self { ptr }) + .ok_or_else(|| { + IsoOpenError::new(path.into_bytes(), "iso9660_open_ext() returned NULL".into()) + }) } /// Returns a builder object. See [`IsoBuilder`]. - pub fn builder<'a>(path: &'a Path) -> IsoBuilder<'a> { + pub fn builder(path: PathBuf) -> IsoBuilder { IsoBuilder::new(path) } @@ -156,15 +163,41 @@ impl Drop for Iso { } } +#[derive(Debug, Error)] +#[error(transparent)] +pub struct IsoOpenError(Box); + +#[derive(Debug, Error)] +#[error("could not open ISO 9660 file at `{path}`")] +struct OpenErrRepr { + path: PathBuf, + source: Box, +} + +impl IsoOpenError { + /// Returns the path of the ISO 9660 file. + pub fn path(&self) -> &Path { + &self.0.path + } + + fn new(path_bytes: Vec, source: Box) -> Self { + Self(Box::new(OpenErrRepr { + // SAFETY: path_bytes originate from a PathBuf + path: unsafe { OsString::from_encoded_bytes_unchecked(path_bytes) }.into(), + source, + })) + } +} + /// A builder for [Iso]. #[derive(Clone, Debug)] -pub struct IsoBuilder<'a> { +pub struct IsoBuilder { extensions: IsoExtensions, - path: &'a Path, + path: PathBuf, } -impl<'a> IsoBuilder<'a> { - pub fn new(path: &'a Path) -> Self { +impl IsoBuilder { + pub fn new(path: PathBuf) -> Self { Self { path, extensions: IsoExtensions::empty(), @@ -179,10 +212,8 @@ impl<'a> IsoBuilder<'a> { /// Build the iso9660 type with the set options. /// Returns `None` on error. - pub fn build(self) -> Option { - let path = CString::new(self.path.to_str()?).ok()?; - - Iso::open(&path, self.extensions) + pub fn build(self) -> Result { + Iso::open(self.path.to_owned(), self.extensions) } } @@ -221,26 +252,25 @@ pub enum JolietLevel { pub(crate) mod tests { use super::*; - pub fn test_rockridge_file() -> &'static Path { - Path::new("../test-data/rock-ridge.iso") + pub fn test_rockridge_file() -> PathBuf { + PathBuf::from("../test-data/rock-ridge.iso") } - pub fn test_joliet_file() -> &'static Path { - Path::new("../test-data/joliet.iso") + pub fn test_joliet_file() -> PathBuf { + PathBuf::from("../test-data/joliet.iso") } #[test_log::test(test)] fn new() { - let iso = Iso::new(test_rockridge_file()); - assert!(iso.is_some()); + Iso::new(test_rockridge_file()).unwrap(); } #[test] fn builder() { let extensions = IsoExtensions::HighSierra & IsoExtensions::RockRidge; - let iso = Iso::builder(test_rockridge_file()) + Iso::builder(test_rockridge_file()) .extensions(extensions) - .build(); - assert!(iso.is_some()); + .build() + .unwrap(); } #[test] diff --git a/libcdio-rs/src/iso9660/entry.rs b/libcdio-rs/src/iso9660/entry.rs index 024244c..9aec36f 100644 --- a/libcdio-rs/src/iso9660/entry.rs +++ b/libcdio-rs/src/iso9660/entry.rs @@ -216,7 +216,7 @@ impl io::Seek for IsoEntryReader<'_> { #[cfg(test)] mod tests { - use std::{io::Read, path::Path}; + use std::{io::Read, path::PathBuf}; use time::macros::datetime; @@ -297,7 +297,7 @@ mod tests { #[test] fn read() { - let iso = Iso::new(Path::new("../test-data/xa.iso")).unwrap(); + let iso = Iso::new(PathBuf::from("../test-data/xa.iso")).unwrap(); let entry = iso.entry("copying").unwrap(); let gpl = std::fs::read_to_string("../COPYING").unwrap(); let mut reader = entry.reader(); diff --git a/libcdio-rs/src/iso9660/xa.rs b/libcdio-rs/src/iso9660/xa.rs index 11a7fa3..793c6bd 100644 --- a/libcdio-rs/src/iso9660/xa.rs +++ b/libcdio-rs/src/iso9660/xa.rs @@ -93,7 +93,7 @@ bitflags! { #[cfg(test)] mod tests { - use std::path::Path; + use std::path::PathBuf; use crate::iso9660::Iso; @@ -101,7 +101,7 @@ mod tests { #[test] fn xa() { - let iso = Iso::new(Path::new("../test-data/xa.iso")).unwrap(); + let iso = Iso::new(PathBuf::from("../test-data/xa.iso")).unwrap(); let entry = iso.entry("/copying").unwrap(); let xa = entry.xa().unwrap(); assert_eq!(xa.file_num, 0); From e8102761082d8fed382f459febeb631d31c5bacd Mon Sep 17 00:00:00 2001 From: Shiva Kiran Koninty Date: Mon, 17 Aug 2026 11:33:00 +0530 Subject: [PATCH 06/13] lib/iso9660: Add `IsoGetEntryError` --- libcdio-cli/src/iso-info/main.rs | 5 +-- libcdio-rs/src/iso9660/entry.rs | 66 ++++++++++++++++++++++++++------ 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/libcdio-cli/src/iso-info/main.rs b/libcdio-cli/src/iso-info/main.rs index 962ad66..5b02ecc 100644 --- a/libcdio-cli/src/iso-info/main.rs +++ b/libcdio-cli/src/iso-info/main.rs @@ -137,10 +137,7 @@ fn print_iso9660_contents( writeln!(out, "{}:", dir_path)?; - for entry in iso - .read_dir(&dir_path) - .with_context(|| format!("could not read entry '{}' from iso", dir_path))? - { + for entry in iso.read_dir(&dir_path)? { let rock_ridge = use_rock_ridge.then_some(entry.rock_ridge()).flatten(); let translated_name = entry.filename(); let entry_name = if rock_ridge.is_none() { diff --git a/libcdio-rs/src/iso9660/entry.rs b/libcdio-rs/src/iso9660/entry.rs index 9aec36f..bb38a74 100644 --- a/libcdio-rs/src/iso9660/entry.rs +++ b/libcdio-rs/src/iso9660/entry.rs @@ -18,12 +18,14 @@ //! ISO 9660 file/directory entry object. use std::{ + error::Error, ffi::{CStr, CString}, io, ptr::NonNull, }; use libcdio_sys::{iso9660_stat_s, iso9660_stat_s__STAT_DIR}; +use thiserror::Error; use time::OffsetDateTime; use crate::iso9660::{Iso, util}; @@ -33,11 +35,19 @@ impl Iso { /// /// Only '/' may be used for path separators. /// Returns `None` on error. - pub fn read_dir(&self, path: &str) -> Option>> { - let path = CString::new(path).ok()?; + pub fn read_dir(&self, path: &str) -> Result>, IsoGetEntryError> { + let path = CString::new(path).map_err(|err| { + IsoGetEntryError::new( + String::from_utf8(err.clone().into_vec()).expect("path was a valid string"), + err.into(), + ) + })?; let dirlist = unsafe { libcdio_sys::iso9660_ifs_readdir(self.ptr.as_ptr(), path.as_ptr()) }; if dirlist.is_null() { - return None; + return Err(IsoGetEntryError::new( + path.into_string().expect("path was a valid string"), + "iso9660_ifs_readdir() returned NULL".into(), + )); } // SAFETY: dirlist is not null and the data will be owned by `IsoEntry`. let dirlist = unsafe { util::cdiolist_to_vec(dirlist) }; @@ -51,18 +61,52 @@ impl Iso { }) .collect(); - Some(dirlist) + Ok(dirlist) } - /// Return entry for `path`. `None` is returned on error. - pub fn entry(&self, path: &str) -> Option> { - let path = CString::new(path).ok()?; + /// Returns ISO 9660 entry at internal `path`. + pub fn entry(&self, path: &str) -> Result, IsoGetEntryError> { + let path = CString::new(path).map_err(|err| { + IsoGetEntryError::new( + String::from_utf8(err.clone().into_vec()).expect("path was a valid string"), + err.into(), + ) + })?; let stat = unsafe { libcdio_sys::iso9660_ifs_stat(self.ptr.as_ptr(), path.as_ptr()) }; - Some(IsoEntry { - iso: self, - stat: NonNull::new(stat)?, - }) + NonNull::new(stat) + .ok_or_else(|| { + IsoGetEntryError::new( + path.into_string().expect("path was a valid string"), + "iso9660_ifs_stat() returned NULL".into(), + ) + }) + .map(|stat| IsoEntry { iso: self, stat }) + } +} + +#[derive(Debug, Error)] +#[error(transparent)] +pub struct IsoGetEntryError(Box); + +#[derive(Debug, Error)] +#[error("could not get ISO 9660 entry at `{path}`")] +struct GetEntryErrRepr { + path: String, + source: Box, +} + +impl IsoGetEntryError { + /// Returns the path of the ISO 9660 entry. + pub fn path(&self) -> &str { + &self.0.path + } + + fn new(path: impl Into, source: Box) -> Self { + Self(Box::new(GetEntryErrRepr { + path: path.into(), + source, + })) } } From 6c038fb1c581ae5d0b8a7d8c9565b83eaad3937b Mon Sep 17 00:00:00 2001 From: Shiva Kiran Koninty Date: Mon, 17 Aug 2026 12:44:50 +0530 Subject: [PATCH 07/13] lib/iso9660: Add `IsoInvalidEntryError` --- libcdio-cli/src/iso-info/main.rs | 14 +++----- libcdio-rs/src/iso9660/entry.rs | 59 ++++++++++++++++++++++++-------- 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/libcdio-cli/src/iso-info/main.rs b/libcdio-cli/src/iso-info/main.rs index 5b02ecc..435e868 100644 --- a/libcdio-cli/src/iso-info/main.rs +++ b/libcdio-cli/src/iso-info/main.rs @@ -139,15 +139,13 @@ fn print_iso9660_contents( for entry in iso.read_dir(&dir_path)? { let rock_ridge = use_rock_ridge.then_some(entry.rock_ridge()).flatten(); - let translated_name = entry.filename(); let entry_name = if rock_ridge.is_none() { - translated_name.as_deref() + entry.filename()? } else { - entry.filename_raw() - } - .with_context(|| format!("could not get file name of lsn: {}", entry.lsn()))?; + entry.filename_raw().map(String::from)? + }; - let full_path = dir_path.clone() + entry_name + "/"; + let full_path = dir_path.clone() + &entry_name + "/"; if entry.is_dir() && entry_name != "." && entry_name != ".." { dirs.push_back((full_path.clone(), depth + 1)); } @@ -188,9 +186,7 @@ fn print_iso9660_contents( { mtime } else { - entry - .timestamp() - .with_context(|| format!("got invalid timestamp: {}", full_path))? + entry.timestamp()? }; let local = UtcOffset::current_local_offset() diff --git a/libcdio-rs/src/iso9660/entry.rs b/libcdio-rs/src/iso9660/entry.rs index bb38a74..0044ce4 100644 --- a/libcdio-rs/src/iso9660/entry.rs +++ b/libcdio-rs/src/iso9660/entry.rs @@ -119,29 +119,33 @@ pub struct IsoEntry<'a> { impl IsoEntry<'_> { /// Returns the raw filename of the entry. - /// Returns `None` if the filename has non UTF-8 characters or on error. - pub fn filename_raw(&self) -> Option<&str> { + pub fn filename_raw(&self) -> Result<&str, IsoInvalidEntryError> { // SAFETY: self.entry is not null since its behind a NonNull let name = unsafe { (*self.stat.as_ptr()).filename.as_ptr() }; if name.is_null() { - return None; + return Err(IsoInvalidEntryError::new( + Default::default(), + "iso9660_stat_s.filename is NULL".into(), + )); }; - // SAFETY: The filename should be a null terminated string - let name = unsafe { CStr::from_ptr(name).to_str() }; - name.inspect_err(|err| tracing::error!(%err)).ok() + // SAFETY: The filename should be a null terminated string + unsafe { CStr::from_ptr(name).to_str() } + .map_err(|err| IsoInvalidEntryError::new(Default::default(), err.into())) } - /// Returns a filename in a format used for a listing. + /// Returns the entry's filename in a listing format. + /// /// - Lowercase name if no Joliet Extension interpretation. /// - Remove trailing ;1 or .;1 /// - Turn the other ; into version numbers. - /// - /// Returns `None` if the string has non UTF-8 characters or on error. - pub fn filename(&self) -> Option { + pub fn filename(&self) -> Result { let filename = unsafe { (*self.stat.as_ptr()).filename.as_ptr() }; if filename.is_null() { - return None; + return Err(IsoInvalidEntryError::new( + Default::default(), + "iso9660_stat_s.filename is NULL".into(), + )); } let filename = unsafe { CStr::from_ptr(filename) }; @@ -155,9 +159,12 @@ impl IsoEntry<'_> { joliet_level, ) }; + // iso9660_name_translate_ext will not return negative numbers, + // therefore the cast should be safe translated_name.truncate(len as usize); - String::from_utf8(translated_name).ok() + String::from_utf8(translated_name) + .map_err(|err| IsoInvalidEntryError::new(Default::default(), err.into())) } /// Multi-extent aware size, in bytes. @@ -176,10 +183,10 @@ impl IsoEntry<'_> { } /// Returns the timestamp on the entry. - /// `None` if the timestamp is invalid. - pub fn timestamp(&self) -> Option { + pub fn timestamp(&self) -> Result { let tm = unsafe { (*self.stat.as_ptr()).tm }; - util::convert_tm_local(tm).ok() + util::convert_tm_local(tm) + .map_err(|err| IsoInvalidEntryError::new(self.filename().unwrap_or_default(), err)) } /// A type that implements [`io::Read`], for reading an ISO9660 entry. @@ -198,6 +205,28 @@ impl Drop for IsoEntry<'_> { } } +#[derive(Debug, Error)] +#[error(transparent)] +pub struct IsoInvalidEntryError(Box); + +#[derive(Debug, Error)] +#[error("inavlid data in ISO 9660 entry named `{name}`")] +struct InvalidEntryErrRepr { + name: String, + source: Box, +} + +impl IsoInvalidEntryError { + /// Returns the name of the ISO 9660 entry. + pub fn name(&self) -> &str { + &self.0.name + } + + fn new(name: String, source: Box) -> Self { + Self(Box::new(InvalidEntryErrRepr { name, source })) + } +} + /// A type that implements [`io::Read`], for reading an ISO9660 entry. pub struct IsoEntryReader<'a> { bytes_read: usize, From 69b51653cdc27fc8cebe5a9cdf4e53db75c0f24a Mon Sep 17 00:00:00 2001 From: Shiva Kiran Koninty Date: Mon, 17 Aug 2026 13:25:58 +0530 Subject: [PATCH 08/13] lib/iso9660: Use `dyn Error` for `convert_tm_local()` --- libcdio-rs/src/iso9660/util.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/libcdio-rs/src/iso9660/util.rs b/libcdio-rs/src/iso9660/util.rs index 9a3926e..6893006 100644 --- a/libcdio-rs/src/iso9660/util.rs +++ b/libcdio-rs/src/iso9660/util.rs @@ -17,28 +17,31 @@ //! Utility and data structure conversion routines. -use std::ffi::c_void; +use std::{error::Error, ffi::c_void}; use libcdio_sys::_CdioList; -use time::{Date, OffsetDateTime, Time, UtcOffset, error}; +use time::{Date, OffsetDateTime, Time, UtcOffset}; /// Convert `tm` representing local time to a `OffsetDateTime`. pub(crate) fn convert_tm_local( tm: libcdio_sys::tm, -) -> Result { +) -> Result> { const TM_YEAR_OFFSET: i32 = 1900; const TM_ORDINAL_DAY_OFFSET: u16 = 1; let date = Date::from_ordinal_date( tm.tm_year + TM_YEAR_OFFSET, - tm.tm_yday as u16 + TM_ORDINAL_DAY_OFFSET, + u16::try_from(tm.tm_yday)? + TM_ORDINAL_DAY_OFFSET, + )?; + let time = Time::from_hms( + u8::try_from(tm.tm_hour)?, + u8::try_from(tm.tm_min)?, + u8::try_from(tm.tm_sec)?, )?; - let time = Time::from_hms(tm.tm_hour as _, tm.tm_min as _, tm.tm_sec as _)?; Ok(OffsetDateTime::new_in_offset( date, time, - UtcOffset::local_offset_at(OffsetDateTime::new_utc(date, time)) - .expect("could not obtain the system offset"), + UtcOffset::local_offset_at(OffsetDateTime::new_utc(date, time))?, )) } From c3d205531172c2bbbcef62970eb5fb245d1beb7b Mon Sep 17 00:00:00 2001 From: Shiva Kiran Koninty Date: Mon, 17 Aug 2026 14:59:31 +0530 Subject: [PATCH 09/13] lib/iso9660: Remove `Iso::builder()` and `IsoExtensions` Studying the source of libcdio C, `IsoExtensions` seem to only be a soft switch to make the code ignore the said extensions in a given ISO 9660 filesystem. As such, it doesn't provide much use. `Iso::builder()` can also be removed, since `Iso` now accepts only one input parameter. --- libcdio-cli/src/iso-info/cli.rs | 4 -- libcdio-cli/src/iso-info/main.rs | 13 +---- libcdio-rs/src/iso9660.rs | 96 +++++--------------------------- 3 files changed, 15 insertions(+), 98 deletions(-) diff --git a/libcdio-cli/src/iso-info/cli.rs b/libcdio-cli/src/iso-info/cli.rs index 258a8b7..bd9d404 100644 --- a/libcdio-cli/src/iso-info/cli.rs +++ b/libcdio-cli/src/iso-info/cli.rs @@ -31,10 +31,6 @@ pub struct Cli { #[arg(short = 'l', long, group = "listing")] pub iso9660: bool, - /// Do not use Joliet extensions - #[arg(long)] - pub no_joliet: bool, - /// Do not use Rock Ridge extensions #[arg(long)] pub no_rock_ridge: bool, diff --git a/libcdio-cli/src/iso-info/main.rs b/libcdio-cli/src/iso-info/main.rs index 435e868..146273f 100644 --- a/libcdio-cli/src/iso-info/main.rs +++ b/libcdio-cli/src/iso-info/main.rs @@ -25,10 +25,7 @@ use std::{ use anyhow::{Context, Result, bail}; use clap::Parser; -use libcdio_rs::{ - Iso, Udf, - iso9660::{IsoExtensions, XaFileAttributes}, -}; +use libcdio_rs::{Iso, Udf, iso9660::XaFileAttributes}; use time::{UtcOffset, format_description::BorrowedFormatItem, macros::format_description}; use tracing_subscriber::EnvFilter; @@ -51,18 +48,12 @@ fn main() -> Result<()> { 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", ); - let mut extensions = IsoExtensions::all(); - if cli.no_joliet { - extensions -= IsoExtensions::JolietLevel1; - extensions -= IsoExtensions::JolietLevel2; - extensions -= IsoExtensions::JolietLevel3; - } if cli.udf { return print_udf_contents(file, &mut output); } - let iso = Iso::builder(file.clone()).extensions(extensions).build()?; + let iso = Iso::new(file.clone())?; print_iso9660_metadata(&iso, &file, &mut output) .context("io error while printing iso9660 metadata")?; diff --git a/libcdio-rs/src/iso9660.rs b/libcdio-rs/src/iso9660.rs index 146c39a..8ac0c4b 100644 --- a/libcdio-rs/src/iso9660.rs +++ b/libcdio-rs/src/iso9660.rs @@ -35,14 +35,7 @@ use std::{ ptr::{self, NonNull}, }; -use bitflags::bitflags; -use libcdio_sys::{ - iso_extension_enum_s_ISO_EXTENSION_HIGH_SIERRA, - iso_extension_enum_s_ISO_EXTENSION_JOLIET_LEVEL1, - iso_extension_enum_s_ISO_EXTENSION_JOLIET_LEVEL2, - iso_extension_enum_s_ISO_EXTENSION_JOLIET_LEVEL3, - iso_extension_enum_s_ISO_EXTENSION_ROCK_RIDGE, iso9660_t, -}; +use libcdio_sys::iso9660_t; use num_enum::{IntoPrimitive, TryFromPrimitive}; use crate::logging::init_logger; @@ -59,19 +52,23 @@ impl Iso { /// Open an ISO 9660 image for reading at given `path`, with all iso9660 /// extension flags enabled. Returns `None` on error. pub fn new(path: PathBuf) -> Result { - Self::open(path, IsoExtensions::all()) - } - - fn open(path: PathBuf, extensions: IsoExtensions) -> Result { init_logger(); let path = CString::new(path.into_os_string().as_encoded_bytes()) .inspect_err(|err| error!(%err, "invalid ISO 9660 path")) .map_err(|err| IsoOpenError::new(err.clone().into_vec(), err.into()))?; - - // SAFETY: path is duplicated by the method, so its safe to drop afterwards - let iso9660_ptr = - unsafe { libcdio_sys::iso9660_open_ext(path.as_ptr(), extensions.bits()) }; + let iso9660_ptr = unsafe { + // enable all extensions + libcdio_sys::iso9660_open_ext( + path.as_ptr(), + (libcdio_sys::iso_extension_enum_s_ISO_EXTENSION_HIGH_SIERRA + | libcdio_sys::iso_extension_enum_s_ISO_EXTENSION_JOLIET_LEVEL1 + | libcdio_sys::iso_extension_enum_s_ISO_EXTENSION_JOLIET_LEVEL2 + | libcdio_sys::iso_extension_enum_s_ISO_EXTENSION_JOLIET_LEVEL3 + | libcdio_sys::iso_extension_enum_s_ISO_EXTENSION_ROCK_RIDGE) + as _, + ) + }; NonNull::new(iso9660_ptr) .map(|ptr| Self { ptr }) @@ -80,11 +77,6 @@ impl Iso { }) } - /// Returns a builder object. See [`IsoBuilder`]. - pub fn builder(path: PathBuf) -> IsoBuilder { - IsoBuilder::new(path) - } - /// Returns the Application Identifier. pub fn application(&self) -> Option { self.get_identifier(libcdio_sys::iso9660_ifs_get_application_id) @@ -142,9 +134,6 @@ impl Iso { } /// Returns the Joliet level. - /// # Note - /// [`Self`] must be constructed with the joliet extension enabled, - /// otherwise this will return `None` even if the file has Joliet. pub fn joliet_level(&self) -> Option { let joliet_level = unsafe { libcdio_sys::iso9660_ifs_get_joliet_level(self.ptr.as_ptr()) }; if joliet_level == 0 { @@ -189,56 +178,6 @@ impl IsoOpenError { } } -/// A builder for [Iso]. -#[derive(Clone, Debug)] -pub struct IsoBuilder { - extensions: IsoExtensions, - path: PathBuf, -} - -impl IsoBuilder { - pub fn new(path: PathBuf) -> Self { - Self { - path, - extensions: IsoExtensions::empty(), - } - } - - /// Set the extensions to be activated. This is set to be empty by default. - pub fn extensions(mut self, extensions: IsoExtensions) -> Self { - self.extensions = extensions; - self - } - - /// Build the iso9660 type with the set options. - /// Returns `None` on error. - pub fn build(self) -> Result { - Iso::open(self.path.to_owned(), self.extensions) - } -} - -bitflags! { - /// ISO 9660 Extensions. - /// # Examples - /// ```rust, no_run - /// use libcdio_rs::iso9660::IsoExtensions; - /// // pick HighSierra and RockRidge - /// let extensions = IsoExtensions::HighSierra & IsoExtensions::RockRidge; - /// // pick everything except RockRidge - /// let extensions = IsoExtensions::all() - IsoExtensions::RockRidge; - /// // pick nothing - /// let extensions = IsoExtensions::empty(); - /// ``` - #[derive(Clone, Copy, Debug)] - pub struct IsoExtensions: u8 { - const HighSierra = iso_extension_enum_s_ISO_EXTENSION_HIGH_SIERRA as _; - const JolietLevel1 = iso_extension_enum_s_ISO_EXTENSION_JOLIET_LEVEL1 as _; - const JolietLevel2 = iso_extension_enum_s_ISO_EXTENSION_JOLIET_LEVEL2 as _; - const JolietLevel3 = iso_extension_enum_s_ISO_EXTENSION_JOLIET_LEVEL3 as _; - const RockRidge = iso_extension_enum_s_ISO_EXTENSION_ROCK_RIDGE as _; - } -} - /// Joliet level. #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] @@ -264,15 +203,6 @@ pub(crate) mod tests { Iso::new(test_rockridge_file()).unwrap(); } - #[test] - fn builder() { - let extensions = IsoExtensions::HighSierra & IsoExtensions::RockRidge; - Iso::builder(test_rockridge_file()) - .extensions(extensions) - .build() - .unwrap(); - } - #[test] fn joliet_level() { let iso = Iso::new(test_joliet_file()).unwrap(); From 6c1f18d11562b5069cc57305c5fe9f380bff3bc0 Mon Sep 17 00:00:00 2001 From: Shiva Kiran Koninty Date: Mon, 17 Aug 2026 15:44:35 +0530 Subject: [PATCH 10/13] lib/iso9660: Add `RockRidgeSearchError` --- libcdio-cli/src/iso-info/main.rs | 6 +++--- libcdio-rs/src/iso9660/rock.rs | 14 ++++++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/libcdio-cli/src/iso-info/main.rs b/libcdio-cli/src/iso-info/main.rs index 146273f..72a496f 100644 --- a/libcdio-cli/src/iso-info/main.rs +++ b/libcdio-cli/src/iso-info/main.rs @@ -100,9 +100,9 @@ fn print_rock_ridge( mut out: impl io::Write, ) -> Result<(), io::Error> { let status = match iso.have_rock_ridge(file_limit) { - Some(true) => "yes", - Some(false) => "no", - None => "possibly not", + Ok(true) => "yes", + Ok(false) => "no", + _ => "possibly not", }; writeln!(out, "Rock Ridge : {}", status) } diff --git a/libcdio-rs/src/iso9660/rock.rs b/libcdio-rs/src/iso9660/rock.rs index b7f5c64..f8e53f2 100644 --- a/libcdio-rs/src/iso9660/rock.rs +++ b/libcdio-rs/src/iso9660/rock.rs @@ -21,6 +21,7 @@ use std::{ffi::CStr, mem::MaybeUninit}; use file_mode::Mode; use libcdio_sys::{bool_3way_t_nope, bool_3way_t_yep, iso_rock_time_s}; +use thiserror::Error; use time::OffsetDateTime; use crate::iso9660::{Iso, entry::IsoEntry, util}; @@ -29,19 +30,24 @@ impl Iso { /// Checks if any file has Rock Ridge extensions. Returns `None` on error. /// This can be time consuming, therefore `file_limit` can be provided to /// limit the number of files to scan. - pub fn have_rock_ridge(&self, file_limit: Option) -> Option { + pub fn have_rock_ridge(&self, file_limit: Option) -> Result { let file_limit = file_limit.unwrap_or(u64::MAX); let result = unsafe { libcdio_sys::iso9660_have_rr(self.ptr.as_ptr(), file_limit) }; #[allow(non_upper_case_globals)] match result { - bool_3way_t_yep => Some(true), - bool_3way_t_nope => Some(false), - _ => None, + bool_3way_t_yep => Ok(true), + bool_3way_t_nope => Ok(false), + _ => Err(RockRidgeSearchError), } } } +#[non_exhaustive] +#[derive(Debug, Error)] +#[error("error searching for rock ridge extensions: file limit reached")] +pub struct RockRidgeSearchError; + impl IsoEntry<'_> { /// Rock Ridge extensions. /// `None` is returned if Rock ridge extensions are missing, or if it From 1995a131606558ce2e80156bcf6e373f260487eb Mon Sep 17 00:00:00 2001 From: Shiva Kiran Koninty Date: Tue, 18 Aug 2026 13:28:04 +0530 Subject: [PATCH 11/13] lib/iso9660: Replace `&str` with `String` in `Iso::read_dir()` --- libcdio-cli/src/iso-info/main.rs | 2 +- libcdio-rs/src/iso9660/entry.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libcdio-cli/src/iso-info/main.rs b/libcdio-cli/src/iso-info/main.rs index 72a496f..7d251da 100644 --- a/libcdio-cli/src/iso-info/main.rs +++ b/libcdio-cli/src/iso-info/main.rs @@ -128,7 +128,7 @@ fn print_iso9660_contents( writeln!(out, "{}:", dir_path)?; - for entry in iso.read_dir(&dir_path)? { + for entry in iso.read_dir(dir_path.clone())? { let rock_ridge = use_rock_ridge.then_some(entry.rock_ridge()).flatten(); let entry_name = if rock_ridge.is_none() { entry.filename()? diff --git a/libcdio-rs/src/iso9660/entry.rs b/libcdio-rs/src/iso9660/entry.rs index 0044ce4..1862410 100644 --- a/libcdio-rs/src/iso9660/entry.rs +++ b/libcdio-rs/src/iso9660/entry.rs @@ -35,7 +35,7 @@ impl Iso { /// /// Only '/' may be used for path separators. /// Returns `None` on error. - pub fn read_dir(&self, path: &str) -> Result>, IsoGetEntryError> { + pub fn read_dir(&self, path: String) -> Result>, IsoGetEntryError> { let path = CString::new(path).map_err(|err| { IsoGetEntryError::new( String::from_utf8(err.clone().into_vec()).expect("path was a valid string"), @@ -301,14 +301,14 @@ mod tests { #[test] fn read_dir() { let iso = Iso::new(test_joliet_file()).unwrap(); - let entries = iso.read_dir("/").unwrap(); + let entries = iso.read_dir("/".to_owned()).unwrap(); assert_eq!(entries.len(), 3); } #[test] fn filename() { let iso = Iso::new(test_rockridge_file()).unwrap(); - let entries = iso.read_dir("/").unwrap(); + let entries = iso.read_dir("/".to_owned()).unwrap(); let names: Vec<_> = entries.iter().map(|e| e.filename_raw().unwrap()).collect(); assert_eq!( &names, @@ -319,7 +319,7 @@ mod tests { #[test] fn filename_translated() { let iso = Iso::new(test_rockridge_file()).unwrap(); - let entries = iso.read_dir("/").unwrap(); + let entries = iso.read_dir("/".to_owned()).unwrap(); let names: Vec<_> = entries.iter().map(|e| e.filename().unwrap()).collect(); assert_eq!( &names, From b11b1a73b40257ad8efe3aae13ea270ecdbf92e3 Mon Sep 17 00:00:00 2001 From: Shiva Kiran Koninty Date: Wed, 19 Aug 2026 10:52:05 +0530 Subject: [PATCH 12/13] lib/iso9660: Replace `&str` with `String` in `Iso::entry()` --- libcdio-cli/src/iso-read/main.rs | 15 ++++----------- libcdio-rs/src/iso9660/entry.rs | 16 ++++++++-------- libcdio-rs/src/iso9660/rock.rs | 28 ++++++++++++++-------------- libcdio-rs/src/iso9660/xa.rs | 2 +- 4 files changed, 27 insertions(+), 34 deletions(-) diff --git a/libcdio-cli/src/iso-read/main.rs b/libcdio-cli/src/iso-read/main.rs index b0c43d7..1c21c12 100644 --- a/libcdio-cli/src/iso-read/main.rs +++ b/libcdio-cli/src/iso-read/main.rs @@ -43,7 +43,7 @@ fn main() -> Result<()> { if cli.udf { udf_extract(image, cli.extract, &mut output)?; } else { - iso9660_extract(image, &cli.extract, &mut output)?; + iso9660_extract(image, cli.extract, &mut output)?; } Ok(()) @@ -60,18 +60,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: &str, output: &mut File) -> Result<()> { +fn iso9660_extract(image: PathBuf, extract: String, output: &mut File) -> Result<()> { let iso = Iso::new(image.clone())?; - let entry = iso.entry(extract).with_context(|| { - format!( - "could not open file '{}' from iso9660 image: {}", - extract, - image.display() - ) - })?; + let entry = iso.entry(extract)?; - io::copy(&mut entry.reader(), output) - .with_context(|| format!("error extracting file '{}' from iso9660", extract))?; + io::copy(&mut entry.reader(), output)?; Ok(()) } diff --git a/libcdio-rs/src/iso9660/entry.rs b/libcdio-rs/src/iso9660/entry.rs index 1862410..b9918d6 100644 --- a/libcdio-rs/src/iso9660/entry.rs +++ b/libcdio-rs/src/iso9660/entry.rs @@ -65,7 +65,7 @@ impl Iso { } /// Returns ISO 9660 entry at internal `path`. - pub fn entry(&self, path: &str) -> Result, IsoGetEntryError> { + pub fn entry(&self, path: String) -> Result, IsoGetEntryError> { let path = CString::new(path).map_err(|err| { IsoGetEntryError::new( String::from_utf8(err.clone().into_vec()).expect("path was a valid string"), @@ -330,38 +330,38 @@ mod tests { #[test] fn entry() { let iso = Iso::new(test_rockridge_file()).unwrap(); - let entry = iso.entry("/copy").unwrap(); + let entry = iso.entry("/copy".to_string()).unwrap(); assert_eq!(entry.filename().unwrap(), "copy"); } #[test] fn total_size() { let iso = Iso::new(test_rockridge_file()).unwrap(); - let entry = iso.entry("/COPYING").unwrap(); + let entry = iso.entry("/COPYING".to_string()).unwrap(); assert_eq!(entry.total_size(), 17992); } #[test] fn lsn() { let iso = Iso::new(test_rockridge_file()).unwrap(); - let entry = iso.entry("/COPYING").unwrap(); + let entry = iso.entry("/COPYING".to_string()).unwrap(); assert_eq!(entry.lsn(), 27); } #[test] fn is_dir() { let iso = Iso::new(test_rockridge_file()).unwrap(); - let file = iso.entry("/COPYING").unwrap(); + let file = iso.entry("/COPYING".to_string()).unwrap(); assert!(!file.is_dir()); - let dir = iso.entry("/copy").unwrap(); + let dir = iso.entry("/copy".to_string()).unwrap(); assert!(dir.is_dir()); } #[test] fn timestamp() { let iso = Iso::new(test_rockridge_file()).unwrap(); - let entry = iso.entry("/COPYING").unwrap(); + let entry = iso.entry("/COPYING".to_string()).unwrap(); assert_eq!( entry.timestamp().unwrap(), datetime!(2005-03-05 20:55:51.0 +05:30:00), @@ -371,7 +371,7 @@ mod tests { #[test] fn read() { let iso = Iso::new(PathBuf::from("../test-data/xa.iso")).unwrap(); - let entry = iso.entry("copying").unwrap(); + let entry = iso.entry("copying".to_string()).unwrap(); let gpl = std::fs::read_to_string("../COPYING").unwrap(); let mut reader = entry.reader(); diff --git a/libcdio-rs/src/iso9660/rock.rs b/libcdio-rs/src/iso9660/rock.rs index f8e53f2..db2e11b 100644 --- a/libcdio-rs/src/iso9660/rock.rs +++ b/libcdio-rs/src/iso9660/rock.rs @@ -137,11 +137,11 @@ mod tests { #[test] fn rock_ridge() { let iso = Iso::new(test_rockridge_file()).unwrap(); - let entry = iso.entry("/COPYING").unwrap(); + let entry = iso.entry("/COPYING".to_string()).unwrap(); assert!(entry.rock_ridge().is_some()); let iso = Iso::new(test_joliet_file()).unwrap(); - let entry = iso.entry("/libcdio/COPYING").unwrap(); + let entry = iso.entry("/libcdio/COPYING".to_string()).unwrap(); assert!(entry.rock_ridge().is_none()); } @@ -149,19 +149,19 @@ mod tests { fn mode() { let iso = Iso::new(test_rockridge_file()).unwrap(); - let entry = iso.entry("/zero").unwrap(); + let entry = iso.entry("/zero".to_string()).unwrap(); let mode = entry.rock_ridge().unwrap().mode; assert_eq!(&mode.to_string(), "cr--r--r--"); - let entry = iso.entry("/fd0").unwrap(); + let entry = iso.entry("/fd0".to_string()).unwrap(); let mode = entry.rock_ridge().unwrap().mode; assert_eq!(&mode.to_string(), "br--r--r--"); - let entry = iso.entry("/Copy2").unwrap(); + let entry = iso.entry("/Copy2".to_string()).unwrap(); let mode = entry.rock_ridge().unwrap().mode; assert_eq!(&mode.to_string(), "lr-xr-xr-x"); - let entry = iso.entry("/copy").unwrap(); + let entry = iso.entry("/copy".to_string()).unwrap(); let mode = entry.rock_ridge().unwrap().mode; assert_eq!(&mode.to_string(), "dr-xr-xr-x"); } @@ -170,15 +170,15 @@ mod tests { fn symlink_to() { let iso = Iso::new(test_rockridge_file()).unwrap(); - let entry = iso.entry("/COPYING").unwrap(); + let entry = iso.entry("/COPYING".to_string()).unwrap(); let rock = entry.rock_ridge().unwrap(); assert!(rock.symlink_to.is_none()); - let entry = iso.entry("/Copy2").unwrap(); + let entry = iso.entry("/Copy2".to_string()).unwrap(); let rock = entry.rock_ridge().unwrap(); assert_eq!(rock.symlink_to.unwrap(), "COPYING"); - let entry = iso.entry("/tmp/COPYING").unwrap(); + let entry = iso.entry("/tmp/COPYING".to_string()).unwrap(); let rock = entry.rock_ridge().unwrap(); assert_eq!(rock.symlink_to.unwrap(), "../copying/COPYING"); } @@ -186,11 +186,11 @@ mod tests { #[test] fn hard_links() { let iso = Iso::new(test_rockridge_file()).unwrap(); - let entry = iso.entry("/COPYING").unwrap(); + let entry = iso.entry("/COPYING".to_string()).unwrap(); let rock = entry.rock_ridge().unwrap(); assert_eq!(rock.hard_links, 1); - let entry = iso.entry("/copy").unwrap(); + let entry = iso.entry("/copy".to_string()).unwrap(); let rock = entry.rock_ridge().unwrap(); assert_eq!(rock.hard_links, 2); } @@ -198,7 +198,7 @@ mod tests { #[test] fn user_id() { let iso = Iso::new(test_rockridge_file()).unwrap(); - let entry = iso.entry("/COPYING").unwrap(); + let entry = iso.entry("/COPYING".to_string()).unwrap(); let rock = entry.rock_ridge().unwrap(); assert_eq!(rock.user_id, 0); } @@ -206,7 +206,7 @@ mod tests { #[test] fn group_id() { let iso = Iso::new(test_rockridge_file()).unwrap(); - let entry = iso.entry("/COPYING").unwrap(); + let entry = iso.entry("/COPYING".to_string()).unwrap(); let rock = entry.rock_ridge().unwrap(); assert_eq!(rock.group_id, 0); } @@ -214,7 +214,7 @@ mod tests { #[test] fn time() { let iso = Iso::new(test_rockridge_file()).unwrap(); - let entry = iso.entry("/COPYING").unwrap(); + let entry = iso.entry("/COPYING".to_string()).unwrap(); let rock = entry.rock_ridge().unwrap(); assert_eq!( rock.modify_time.unwrap(), diff --git a/libcdio-rs/src/iso9660/xa.rs b/libcdio-rs/src/iso9660/xa.rs index 793c6bd..3d2a902 100644 --- a/libcdio-rs/src/iso9660/xa.rs +++ b/libcdio-rs/src/iso9660/xa.rs @@ -102,7 +102,7 @@ mod tests { #[test] fn xa() { let iso = Iso::new(PathBuf::from("../test-data/xa.iso")).unwrap(); - let entry = iso.entry("/copying").unwrap(); + let entry = iso.entry("/copying".to_string()).unwrap(); let xa = entry.xa().unwrap(); assert_eq!(xa.file_num, 0); assert_eq!(xa.group_id, 3000); From fc5b1ff49048097d32c9c8655673646851062ca8 Mon Sep 17 00:00:00 2001 From: Shiva Kiran Koninty Date: Mon, 17 Aug 2026 15:50:32 +0530 Subject: [PATCH 13/13] lib/iso9660: Revise comments --- libcdio-rs/src/iso9660.rs | 27 ++++++++++++--------------- libcdio-rs/src/iso9660/entry.rs | 26 +++++++++++--------------- libcdio-rs/src/iso9660/rock.rs | 22 +++++++--------------- libcdio-rs/src/iso9660/xa.rs | 21 +++++++++++---------- 4 files changed, 41 insertions(+), 55 deletions(-) diff --git a/libcdio-rs/src/iso9660.rs b/libcdio-rs/src/iso9660.rs index 8ac0c4b..8e4f7db 100644 --- a/libcdio-rs/src/iso9660.rs +++ b/libcdio-rs/src/iso9660.rs @@ -15,19 +15,17 @@ // You should have received a copy of the GNU General Public License // along with libcdio-rs. If not, see . -//! ISO 9660 filesystem related routines. +//! Routines related to the ISO 9660 filesystem. + +pub use entry::*; +pub use rock::*; +pub use xa::*; mod entry; mod rock; mod util; mod xa; -pub use entry::*; -pub use rock::*; -use thiserror::Error; -use tracing::error; -pub use xa::*; - use std::{ error::Error, ffi::{CStr, CString, OsString, c_char}, @@ -37,10 +35,12 @@ use std::{ use libcdio_sys::iso9660_t; use num_enum::{IntoPrimitive, TryFromPrimitive}; +use thiserror::Error; +use tracing::error; use crate::logging::init_logger; -/// The main ISO 9660 type +/// An ISO 9660 filesystem instance. pub struct Iso { pub(crate) ptr: NonNull, } @@ -49,8 +49,7 @@ impl Iso { /// The number of bytes used by an ISO 9660 block. pub const BLOCK_SIZE: usize = 2048; - /// Open an ISO 9660 image for reading at given `path`, with all iso9660 - /// extension flags enabled. Returns `None` on error. + /// Opens an ISO 9660 image at given `path`. pub fn new(path: PathBuf) -> Result { init_logger(); @@ -82,15 +81,14 @@ impl Iso { self.get_identifier(libcdio_sys::iso9660_ifs_get_application_id) } - /// Helper for the methods that return iso9660 identifiers. + /// Helper for the methods that return ISO 9660 identifiers. fn get_identifier( &self, func: unsafe extern "C" fn(*mut iso9660_t, *mut *mut c_char) -> bool, ) -> Option { let mut identifier_ptr = ptr::null_mut(); - // SAFETY: The method allocates a string and points the identifier_ptr to it. - // It must be freed after use. + // SAFETY: identifier_ptr must be freed after use. let success = unsafe { func(self.ptr.as_ptr(), &raw mut identifier_ptr) }; if !success || identifier_ptr.is_null() { return None; @@ -99,8 +97,7 @@ impl Iso { let identifier = unsafe { CStr::from_ptr(identifier_ptr) }; let identifier = identifier.to_string_lossy().to_string(); - // SAFETY: application_id has been duplicated into a Rust string - // above, thus safe to free + // SAFETY: identifier_ptr is already copied to a Rust string. unsafe { libcdio_sys::cdio_free(identifier_ptr.cast()); } diff --git a/libcdio-rs/src/iso9660/entry.rs b/libcdio-rs/src/iso9660/entry.rs index b9918d6..b2f8782 100644 --- a/libcdio-rs/src/iso9660/entry.rs +++ b/libcdio-rs/src/iso9660/entry.rs @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with libcdio-rs. If not, see . -//! ISO 9660 file/directory entry object. +//! Routines related to ISO 9660 entries. use std::{ error::Error, @@ -31,10 +31,9 @@ use time::OffsetDateTime; use crate::iso9660::{Iso, util}; impl Iso { - /// Read directory at `path` and return a list of entries. + /// Returns a list of entries under `path`. /// - /// Only '/' may be used for path separators. - /// Returns `None` on error. + /// Only Unix-style `/` may be used as a separator. pub fn read_dir(&self, path: String) -> Result>, IsoGetEntryError> { let path = CString::new(path).map_err(|err| { IsoGetEntryError::new( @@ -64,7 +63,7 @@ impl Iso { Ok(dirlist) } - /// Returns ISO 9660 entry at internal `path`. + /// Returns ISO 9660 entry at `path`. pub fn entry(&self, path: String) -> Result, IsoGetEntryError> { let path = CString::new(path).map_err(|err| { IsoGetEntryError::new( @@ -119,6 +118,8 @@ pub struct IsoEntry<'a> { impl IsoEntry<'_> { /// Returns the raw filename of the entry. + /// + /// See [`Self::filename()`] pub fn filename_raw(&self) -> Result<&str, IsoInvalidEntryError> { // SAFETY: self.entry is not null since its behind a NonNull let name = unsafe { (*self.stat.as_ptr()).filename.as_ptr() }; @@ -134,11 +135,7 @@ impl IsoEntry<'_> { .map_err(|err| IsoInvalidEntryError::new(Default::default(), err.into())) } - /// Returns the entry's filename in a listing format. - /// - /// - Lowercase name if no Joliet Extension interpretation. - /// - Remove trailing ;1 or .;1 - /// - Turn the other ; into version numbers. + /// Returns the entry's filename. pub fn filename(&self) -> Result { let filename = unsafe { (*self.stat.as_ptr()).filename.as_ptr() }; if filename.is_null() { @@ -167,7 +164,7 @@ impl IsoEntry<'_> { .map_err(|err| IsoInvalidEntryError::new(Default::default(), err.into())) } - /// Multi-extent aware size, in bytes. + /// Returns Multi-extent aware file size, in bytes. pub fn total_size(&self) -> u64 { unsafe { (*self.stat.as_ptr()).total_size } } @@ -177,7 +174,7 @@ impl IsoEntry<'_> { unsafe { (*self.stat.as_ptr()).lsn } } - /// Returns `true` if self is a directory. + /// Returns `true` if the stat represents a directory. pub fn is_dir(&self) -> bool { unsafe { (*self.stat.as_ptr()).type_ == iso9660_stat_s__STAT_DIR } } @@ -189,8 +186,7 @@ impl IsoEntry<'_> { .map_err(|err| IsoInvalidEntryError::new(self.filename().unwrap_or_default(), err)) } - /// A type that implements [`io::Read`], for reading an ISO9660 entry. - /// Returns `None` on error. + /// Returns a type that implements [`io::Read`], for reading an ISO 9660 entry. pub fn reader(&self) -> IsoEntryReader<'_> { IsoEntryReader { bytes_read: 0, @@ -227,7 +223,7 @@ impl IsoInvalidEntryError { } } -/// A type that implements [`io::Read`], for reading an ISO9660 entry. +/// A type that implements [`io::Read`], for reading an ISO 9660 entry. pub struct IsoEntryReader<'a> { bytes_read: usize, entry: &'a IsoEntry<'a>, diff --git a/libcdio-rs/src/iso9660/rock.rs b/libcdio-rs/src/iso9660/rock.rs index db2e11b..dbc06b8 100644 --- a/libcdio-rs/src/iso9660/rock.rs +++ b/libcdio-rs/src/iso9660/rock.rs @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with libcdio-rs. If not, see . -//! ISO 9660 Rock Ridge extensions. +//! Routines related to ISO 9660 Rock Ridge extensions. use std::{ffi::CStr, mem::MaybeUninit}; @@ -27,7 +27,8 @@ use time::OffsetDateTime; use crate::iso9660::{Iso, entry::IsoEntry, util}; impl Iso { - /// Checks if any file has Rock Ridge extensions. Returns `None` on error. + /// Checks if any file has Rock Ridge extensions. + /// /// This can be time consuming, therefore `file_limit` can be provided to /// limit the number of files to scan. pub fn have_rock_ridge(&self, file_limit: Option) -> Result { @@ -49,16 +50,14 @@ impl Iso { pub struct RockRidgeSearchError; impl IsoEntry<'_> { - /// Rock Ridge extensions. - /// `None` is returned if Rock ridge extensions are missing, or if it - /// could not be determined. - pub fn rock_ridge(&self) -> Option { + /// Returns the Rock Ridge attributes of the entry. + pub fn rock_ridge(&self) -> Option { let rock = unsafe { (*self.stat.as_ptr()).rr }; if rock.b3_rock != bool_3way_t_yep { return None; } - Some(RockRidge { + Some(RockRidgeAttributes { create_time: convert_rock_timefield(rock.create), group_id: rock.st_gid, hard_links: rock.st_nlinks, @@ -84,20 +83,13 @@ impl IsoEntry<'_> { /// ISO 9660 Rock Ridge extensions. #[derive(Clone, Debug)] #[non_exhaustive] -pub struct RockRidge { - /// Create time +pub struct RockRidgeAttributes { pub create_time: Option, - /// Group ID pub group_id: u32, - /// Number of hard links pub hard_links: u32, - /// Unix file mode pub mode: Mode, - /// Modify time pub modify_time: Option, - /// Symlink target pub symlink_to: Option, - /// User ID pub user_id: u32, } diff --git a/libcdio-rs/src/iso9660/xa.rs b/libcdio-rs/src/iso9660/xa.rs index 3d2a902..0d62383 100644 --- a/libcdio-rs/src/iso9660/xa.rs +++ b/libcdio-rs/src/iso9660/xa.rs @@ -15,16 +15,15 @@ // You should have received a copy of the GNU General Public License // along with libcdio-rs. If not, see . -//! CD-ROM XA (eXtended Architecture) +//! Routines related to CD-ROM XA (eXtended Architecture). use bitflags::bitflags; use crate::iso9660::entry::IsoEntry; impl IsoEntry<'_> { - /// Return CD-ROM XA (eXtended Architecture) attributes. - /// `None` is returned if the attributes are not present. - pub fn xa(&self) -> Option { + /// Returns CD-ROM XA (eXtended Architecture) attributes of the entry. + pub fn xa(&self) -> Option { let have_xa = unsafe { (*self.stat.as_ptr()).b_xa }; if !have_xa { return None; @@ -33,7 +32,7 @@ impl IsoEntry<'_> { // SAFETY: The above check confirms that xa are present. let xa = unsafe { (*self.stat.as_ptr()).xa }; - Some(CdRomXa { + Some(XaAttributes { file_attr: XaFileAttributes::from_bits_retain(u16::from_be(xa.attributes)), file_num: u8::from_be(xa.filenum), group_id: u16::from_be(xa.group_id), @@ -43,10 +42,10 @@ impl IsoEntry<'_> { } } -/// CD-ROM XA (eXtended Architecture) attributes +/// CD-ROM XA (eXtended Architecture) attributes. #[derive(Clone, Debug)] #[non_exhaustive] -pub struct CdRomXa { +pub struct XaAttributes { pub file_attr: XaFileAttributes, pub file_num: u8, pub group_id: u16, @@ -54,8 +53,9 @@ pub struct CdRomXa { total_size: u64, } -impl CdRomXa { - /// Return multi extent size. +impl XaAttributes { + /// Returns multi extent size. + /// /// Returns `None` if not using Mode2/Form2 encoding. // TODO: Add unit test pub const fn mode2form2_size(&self) -> Option { @@ -74,7 +74,8 @@ impl CdRomXa { bitflags! { /// XA File Attributes. - /// For more information: https://psx-spx.consoledev.net/cdromformat/#cdrom-iso-file-and-directory-descriptors + /// + /// See: https://psx-spx.consoledev.net/cdromformat/#cdrom-iso-file-and-directory-descriptors #[derive(Clone, Copy, Debug)] pub struct XaFileAttributes: u16 { const OwnerRead = 1 << 0;