use super::directory::DirectoryRef;
use super::io::{self, IsoCursor, LogicalSector, Read, Seek};
use super::path::{PathTableEntry, PathTableInfo, PathTableRef};
use super::volume::{PrimaryVolumeDescriptor, VolumeDescriptorList};
use crate::file::EntryType;
use crate::joliet::JolietLevel;
use hadris_common::types::endian::Endian;
#[cfg(not(feature = "alloc"))]
use hadris_common::types::no_alloc::ArrayVec;
use hadris_path::{Component, Separators, VPath};
pub use volume::VolumeDescriptorIter;
mod boot;
mod directory;
pub use boot::*;
pub use directory::{DirEntry, Extent, IsoDir};
sync_only! {
pub use directory::{IsoDirIter, RawDirIter};
}
use spin::Mutex;
mod rrip;
pub(crate) use rrip::SuspInfo;
pub use rrip::*;
mod volume;
pub enum FilenameType {
Builtin,
Joliet,
}
#[derive(Debug)]
pub struct IsoImageInfo {
block_size: usize,
sector_size: usize,
root_dirs: RootDirs,
boot_catalog: Option<u32>,
path_table: PathTableRef,
pub(crate) susp_info: SuspInfo,
has_evd: bool,
#[cfg(feature = "alloc")]
path_table_cache: alloc::vec::Vec<PathTableEntry>,
}
impl IsoImageInfo {
pub fn block_size(&self) -> usize {
self.block_size
}
pub fn sector_size(&self) -> usize {
self.sector_size
}
}
#[derive(Debug)]
pub struct RootDirs {
#[cfg(not(feature = "alloc"))]
dirs: ArrayVec<RootDir, 8>,
#[cfg(feature = "alloc")]
dirs: alloc::vec::Vec<RootDir>,
}
impl RootDirs {
fn new() -> Self {
Self {
#[cfg(not(feature = "alloc"))]
dirs: ArrayVec::new(),
#[cfg(feature = "alloc")]
dirs: alloc::vec::Vec::new(),
}
}
pub fn len(&self) -> usize {
self.dirs.len()
}
pub fn is_empty(&self) -> bool {
self.dirs.is_empty()
}
pub fn iter(&self) -> core::slice::Iter<'_, RootDir> {
self.dirs.iter()
}
pub fn get(&self, ty: EntryType) -> Option<RootDir> {
self.dirs.iter().copied().find(|root| root.ty == ty)
}
pub fn try_best_choice(&self) -> Option<RootDir> {
if self.dirs.is_empty() {
return None;
}
let mut best = (0, EntryType::default());
for (idx, dir) in self.dirs.iter().enumerate() {
if dir.ty > best.1 {
best = (idx, dir.ty);
}
}
Some(self.dirs[best.0])
}
pub fn best_choice(&self) -> RootDir {
self.try_best_choice()
.expect("ISO image contains no directory trees!")
}
}
impl<'a> IntoIterator for &'a RootDirs {
type Item = &'a RootDir;
type IntoIter = core::slice::Iter<'a, RootDir>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct RootDir {
ty: EntryType,
dir_ref: DirectoryRef,
}
impl RootDir {
pub fn entry_type(&self) -> EntryType {
self.ty
}
pub fn iter<'a, DATA: Read + Seek>(&self, iso: &'a IsoImage<DATA>) -> IsoDir<'a, DATA> {
IsoDir {
image: iso,
directory: self.dir_ref,
}
}
pub fn dir_ref(&self) -> DirectoryRef {
self.dir_ref
}
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PathSeparator {
ForwardSlash = b'/',
Backslash = b'\\',
}
impl PathSeparator {
pub fn as_char(self) -> char {
self as u8 as char
}
}
#[derive(Debug)]
pub struct IsoImage<DATA: Seek> {
pub(crate) data: Mutex<IsoCursor<DATA>>,
pub(crate) info: IsoImageInfo,
}
impl<DATA: Seek> IsoImage<DATA> {
pub fn into_inner(self) -> DATA {
self.data.into_inner().data
}
}
io_transform! {
impl<DATA: Read + Seek> IsoImage<DATA> {
pub async fn open(data: DATA) -> io::Result<Self> {
let sector_size = 2048;
let mut data = IsoCursor::new(data, sector_size);
data.seek_sector(LogicalSector(16)).await?;
let mut root_dirs = RootDirs::new();
let volume_descriptors = VolumeDescriptorList::parse(&mut data).await?;
let pvd = volume_descriptors.try_primary().ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"volume descriptor sequence has no primary descriptor",
)
})?;
if !pvd.volume_space_size.is_consistent()
|| !pvd.volume_set_size.is_consistent()
|| !pvd.volume_sequence_number.is_consistent()
|| !pvd.logical_block_size.is_consistent()
|| !pvd.path_table_size.is_consistent()
|| !pvd.dir_record.header.extent.is_consistent()
|| !pvd.dir_record.header.data_len.is_consistent()
|| !pvd
.dir_record
.header
.volume_sequence_number
.is_consistent()
{
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"primary volume descriptor has inconsistent redundant endian fields",
));
}
let block_size = pvd.logical_block_size.read() as usize;
if block_size != 2048 {
return Err(io::Error::new(
io::ErrorKind::Unsupported,
"ISO logical block size other than 2048 is not supported by IsoImage",
));
}
let root_extent = LogicalSector(
pvd.dir_record.header.extent.read() as usize
+ pvd.dir_record.header.extended_attr_record as usize,
);
let root_size = pvd.dir_record.header.data_len.read() as usize;
let root_dir = DirectoryRef {
extent: root_extent,
size: root_size,
};
let susp_info = rrip::detect_susp_rrip(&mut data, root_extent).await?;
let supports_rrip = susp_info.rrip_detected;
root_dirs.dirs.push(RootDir {
ty: EntryType::Level1 {
supports_lowercase: false,
supports_rrip,
},
dir_ref: root_dir,
});
let path_table = PathTableRef {
lpt: LogicalSector(pvd.type_l_path_table.get() as usize),
mpt: LogicalSector(pvd.type_m_path_table.get() as usize),
size: pvd.path_table_size.read() as u64,
};
#[cfg(feature = "alloc")]
let path_table_cache = {
use crate::types::EndianType;
let pt_start = if cfg!(target_endian = "little") {
path_table.lpt
} else {
path_table.mpt
};
let start_byte = pt_start.0 as u64 * sector_size as u64;
let end_byte = start_byte + path_table.size;
data.seek(super::io::SeekFrom::Start(start_byte))
.await
.map_err(super::io::Error::erase)?;
let mut entries = alloc::vec::Vec::new();
let mut pos = start_byte;
while pos < end_byte {
let entry = PathTableEntry::parse(&mut data, EndianType::NativeEndian).await?;
pos += entry.size() as u64;
entries.push(entry);
}
entries
};
let mut info = IsoImageInfo {
block_size,
sector_size,
root_dirs,
boot_catalog: None,
path_table,
susp_info,
has_evd: false,
#[cfg(feature = "alloc")]
path_table_cache,
};
for svd in volume_descriptors.supplementary() {
if svd.header.version == 1 {
for &level in JolietLevel::all() {
if svd.escape_sequences == level.escape_sequence() {
info.root_dirs.dirs.push(RootDir {
ty: EntryType::Joliet {
level,
supports_rrip: false,
},
dir_ref: DirectoryRef {
extent: LogicalSector(
svd.dir_record.header.extent.read() as usize
+ svd.dir_record.header.extended_attr_record as usize,
),
size: svd.dir_record.header.data_len.read() as usize,
},
});
}
}
} else if svd.file_structure_version == 2 {
info.has_evd = true;
info.root_dirs.dirs.push(RootDir {
ty: EntryType::Level3 {
supports_lowercase: true,
supports_rrip: false,
},
dir_ref: DirectoryRef {
extent: LogicalSector(
svd.dir_record.header.extent.read() as usize
+ svd.dir_record.header.extended_attr_record as usize,
),
size: svd.dir_record.header.data_len.read() as usize,
},
});
}
}
if let Some(boot_record) = volume_descriptors.boot_record() {
info.boot_catalog = Some(boot_record.catalog_ptr.get());
}
Ok(Self {
data: Mutex::new(data),
info,
})
}
pub async fn read_bytes_at(&self, byte_offset: u64, buf: &mut [u8]) -> io::Result<()> {
let mut data = self.data.lock();
data.seek(super::io::SeekFrom::Start(byte_offset))
.await
.map_err(super::io::Error::erase)?;
data.read_exact(buf).await?;
Ok(())
}
pub async fn find_path(&self, path: &str) -> io::Result<Option<DirEntry>> {
let mut components = VPath::with_separators(path, Separators::SlashOrBackslash)
.components()
.filter_map(|component| match component {
Component::Root | Component::Current => None,
Component::Parent => Some(Err(io::Error::other(
"parent path components are not supported",
))),
Component::Normal(component) => Some(Ok(component)),
})
.peekable();
let mut directory = self.open_dir(self.root_dir().dir_ref());
while let Some(component) = components.next() {
let component = component?;
let Some(entry) = directory.find(component).await? else {
return Ok(None);
};
if components.peek().is_none() {
return Ok(Some(entry));
}
if !entry.is_directory() {
return Ok(None);
}
directory = self.open_dir(entry.as_dir_ref(self).await?);
}
Ok(None)
}
#[cfg(feature = "alloc")]
pub async fn read_file(&self, entry: &directory::DirEntry) -> io::Result<alloc::vec::Vec<u8>> {
if entry.header().file_unit_size != 0 || entry.header().interleave_gap_size != 0 {
return Err(io::Error::new(
io::ErrorKind::Unsupported,
"interleaved ISO files are not supported",
));
}
let total = entry.total_size();
let image_len = {
let mut data = self.data.lock();
data.seek(super::io::SeekFrom::End(0))
.await
.map_err(super::io::Error::erase)?
};
if total > image_len {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"directory entry claims more data than the image contains",
));
}
let mut buf = alloc::vec![0u8; total as usize];
if entry.is_multi_extent() {
let mut offset = 0usize;
for extent in entry.extents() {
let len = extent.length as usize;
let byte_offset = extent.sector.0 as u64 * 2048;
self.read_bytes_at(byte_offset, &mut buf[offset..offset + len]).await?;
offset += len;
}
} else {
let header = entry.header();
let byte_offset = (header.extent.read() as u64
+ header.extended_attr_record as u64)
* 2048;
let len = header.data_len.read() as usize;
self.read_bytes_at(byte_offset, &mut buf[..len]).await?;
}
Ok(buf)
}
}
}
impl<DATA: Read + Seek> IsoImage<DATA> {
pub fn root_dir(&self) -> RootDir {
self.root_dirs().best_choice()
}
pub fn root_dirs(&self) -> &RootDirs {
&self.info.root_dirs
}
pub fn supports_rrip(&self) -> bool {
self.info.susp_info.rrip_detected
}
pub fn has_evd(&self) -> bool {
self.info.has_evd
}
pub fn open_dir(&self, dir_ref: DirectoryRef) -> IsoDir<'_, DATA> {
IsoDir {
image: self,
directory: dir_ref,
}
}
pub fn path_table(&self) -> PathTableInfo {
PathTableInfo {
path_table: self.info.path_table,
}
}
pub fn read_volume_descriptors(&self) -> VolumeDescriptorIter<'_, DATA> {
VolumeDescriptorIter {
data: &self.data,
current_sector: LogicalSector(16),
done: false,
}
}
#[cfg(feature = "alloc")]
pub fn path_table_entries(&self) -> &[PathTableEntry] {
&self.info.path_table_cache
}
}
io_transform! {
impl<DATA: Read + Seek> IsoImage<DATA> {
pub async fn read_pvd(&self) -> io::Result<PrimaryVolumeDescriptor> {
let mut descriptors = self.read_volume_descriptors();
while let Some(descriptor) = descriptors.next_descriptor().await? {
if let super::volume::VolumeDescriptor::Primary(pvd) = descriptor {
return Ok(pvd);
}
}
Err(io::Error::new(
io::ErrorKind::InvalidData,
"primary volume descriptor not found",
))
}
}
}