use core::ops::DerefMut;
use alloc::borrow::Cow;
use alloc::string::String;
use alloc::vec::Vec;
use super::super::directory::{
DirectoryRecord, DirectoryRecordHeader, DirectoryRef, FileFlags,
};
use super::super::io::{self, IsoCursor, LogicalSector, Read, Seek, SeekFrom};
use spin::Mutex;
use super::IsoImage;
use super::rrip::{self, RripMetadata, collect_su_entries};
#[derive(Debug, Clone, Copy)]
pub struct Extent {
pub sector: LogicalSector,
pub length: u32,
}
pub struct IsoDir<'a, T: Read + Seek> {
pub(crate) image: &'a IsoImage<T>,
pub(crate) directory: DirectoryRef,
}
sync_only! {
impl<'a, T: Read + Seek> IsoDir<'a, T> {
pub fn entries(&self) -> IsoDirIter<'_, T> {
let rrip_skip = if self.image.info.susp_info.rrip_detected {
Some(self.image.info.susp_info.bytes_skipped)
} else {
None
};
IsoDirIter {
image: self.image,
directory: self.directory,
offset: 0,
rrip_skip,
pending_associated: None,
}
}
pub fn raw_entries(&self) -> RawDirIter<'_, T> {
RawDirIter {
reader: &self.image.data,
directory: self.directory,
offset: 0,
}
}
}
}
#[derive(Debug, Clone)]
pub struct DirEntry {
pub record: DirectoryRecord,
pub rrip: Option<RripMetadata>,
pub additional_extents: Vec<Extent>,
pub associated_file: Option<Extent>,
}
impl DirEntry {
#[inline]
pub fn name(&self) -> &[u8] {
self.record.name()
}
pub fn display_name(&self) -> Cow<'_, str> {
if let Some(ref rrip) = self.rrip
&& let Some(ref nm) = rrip.alternate_name
{
return Cow::Borrowed(nm.as_str());
}
String::from_utf8_lossy(self.record.name())
}
#[inline]
pub fn header(&self) -> &DirectoryRecordHeader {
self.record.header()
}
#[inline]
pub fn is_directory(&self) -> bool {
if let Some(ref rrip) = self.rrip
&& rrip.child_link.is_some()
{
return true;
}
self.record.is_directory()
}
#[inline]
pub fn is_special(&self) -> bool {
self.record.is_special()
}
#[inline]
pub fn is_file(&self) -> bool {
!self.is_directory()
}
#[inline]
pub fn size(&self) -> usize {
self.record.size()
}
pub fn total_size(&self) -> u64 {
let first = self.record.header().data_len.read() as u64;
let rest: u64 = self.additional_extents.iter().map(|e| e.length as u64).sum();
first + rest
}
pub fn is_multi_extent(&self) -> bool {
!self.additional_extents.is_empty()
}
pub fn extents(&self) -> impl Iterator<Item = Extent> + '_ {
let header = self.record.header();
let first = Extent {
sector: LogicalSector(header.extent.read() as usize),
length: header.data_len.read(),
};
core::iter::once(first).chain(self.additional_extents.iter().copied())
}
pub fn has_associated_file(&self) -> bool {
self.associated_file.is_some()
}
pub fn extended_attr_len(&self) -> Option<u8> {
let len = self.record.header().extended_attr_record;
if len > 0 { Some(len) } else { None }
}
#[inline]
pub fn system_use(&self) -> &[u8] {
self.record.system_use()
}
}
io_transform! {
impl DirEntry {
pub async fn as_dir_ref<DATA: Read + Seek>(
&self,
image: &IsoImage<DATA>,
) -> io::Result<DirectoryRef> {
if let Some(ref rrip) = self.rrip
&& let Some(cl_sector) = rrip.child_link
{
return rrip::read_dir_size(image, LogicalSector(cl_sector as usize)).await;
}
self.record
.as_dir_ref()
.map_err(|_| io::Error::other("not a directory"))
}
}
}
sync_only! {
pub struct IsoDirIter<'a, T: Read + Seek> {
image: &'a IsoImage<T>,
directory: DirectoryRef,
offset: usize,
rrip_skip: Option<u8>,
pending_associated: Option<Extent>,
}
impl<T: Read + Seek> IsoDirIter<'_, T> {
pub fn offset(&self) -> usize {
self.offset
}
fn collect_additional_extents(&mut self) -> io::Result<Vec<Extent>> {
let mut extents = Vec::new();
const MAX_EXTENTS: usize = 4096;
loop {
if extents.len() >= MAX_EXTENTS {
break;
}
let record = match self.next_raw_record() {
Some(Ok(r)) => r,
Some(Err(e)) => return Err(e),
None => break,
};
let header = record.header();
extents.push(Extent {
sector: LogicalSector(header.extent.read() as usize),
length: header.data_len.read(),
});
if !FileFlags::from_bits_retain(header.flags).contains(FileFlags::NOT_FINAL) {
break;
}
}
Ok(extents)
}
fn next_raw_record(&mut self) -> Option<io::Result<DirectoryRecord>> {
use super::super::io::try_io_result_option;
let reader = &self.image.data;
let mut reader = reader.lock();
const SECTOR_SIZE: usize = 2048;
loop {
if self.offset >= self.directory.size {
return None;
}
try_io_result_option!(reader.seek(SeekFrom::Start(
(self.directory.extent.0 as u64) * SECTOR_SIZE as u64 + (self.offset as u64),
)));
let mut len_byte = [0u8; 1];
try_io_result_option!(reader.read_exact(&mut len_byte));
if len_byte[0] == 0 {
let current_sector_offset = self.offset % SECTOR_SIZE;
if current_sector_offset == 0 {
return None;
}
let bytes_to_skip = SECTOR_SIZE - current_sector_offset;
self.offset += bytes_to_skip;
continue;
}
try_io_result_option!(reader.seek(SeekFrom::Start(
(self.directory.extent.0 as u64) * SECTOR_SIZE as u64 + (self.offset as u64),
)));
let record = try_io_result_option!(DirectoryRecord::parse(reader.deref_mut()));
self.offset += record.size();
return Some(Ok(record));
}
}
}
impl<T: Read + Seek> Iterator for IsoDirIter<'_, T> {
type Item = io::Result<DirEntry>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let record = match self.next_raw_record()? {
Ok(r) => r,
Err(e) => return Some(Err(e)),
};
let flags = FileFlags::from_bits_retain(record.header().flags);
if flags.contains(FileFlags::ASSOCIATED_FILE) {
let header = record.header();
self.pending_associated = Some(Extent {
sector: LogicalSector(header.extent.read() as usize),
length: header.data_len.read(),
});
continue;
}
let additional_extents = if flags.contains(FileFlags::NOT_FINAL) {
match self.collect_additional_extents() {
Ok(extents) => extents,
Err(e) => return Some(Err(e)),
}
} else {
Vec::new()
};
let associated_file = self.pending_associated.take();
if let Some(bytes_to_skip) = self.rrip_skip {
let fields = match collect_su_entries(&record, self.image, bytes_to_skip) {
Ok(f) => f,
Err(e) => return Some(Err(e)),
};
let rrip = RripMetadata::from_fields(&fields);
if rrip.is_relocated {
continue;
}
return Some(Ok(DirEntry {
record,
rrip: Some(rrip),
additional_extents,
associated_file,
}));
} else {
return Some(Ok(DirEntry {
record,
rrip: None,
additional_extents,
associated_file,
}));
}
}
}
}
pub struct RawDirIter<'a, T: Read + Seek> {
pub(crate) reader: &'a Mutex<IsoCursor<T>>,
pub(crate) directory: DirectoryRef,
pub(crate) offset: usize,
}
impl<T: Read + Seek> RawDirIter<'_, T> {
pub fn offset(&self) -> usize {
self.offset
}
}
impl<T: Read + Seek> Iterator for RawDirIter<'_, T> {
type Item = io::Result<DirectoryRecord>;
fn next(&mut self) -> Option<Self::Item> {
use super::super::io::try_io_result_option;
let mut reader = self.reader.lock();
const SECTOR_SIZE: usize = 2048;
loop {
if self.offset >= self.directory.size {
return None;
}
try_io_result_option!(reader.seek(SeekFrom::Start(
(self.directory.extent.0 as u64) * SECTOR_SIZE as u64 + (self.offset as u64),
)));
let mut len_byte = [0u8; 1];
try_io_result_option!(reader.read_exact(&mut len_byte));
if len_byte[0] == 0 {
let current_sector_offset = self.offset % SECTOR_SIZE;
if current_sector_offset == 0 {
return None;
}
let bytes_to_skip = SECTOR_SIZE - current_sector_offset;
self.offset += bytes_to_skip;
continue;
}
try_io_result_option!(reader.seek(SeekFrom::Start(
(self.directory.extent.0 as u64) * SECTOR_SIZE as u64 + (self.offset as u64),
)));
let record = try_io_result_option!(DirectoryRecord::parse(reader.deref_mut()));
self.offset += record.size();
return Some(Ok(record));
}
}
}
}