1use alloc::borrow::Cow;
2use alloc::string::String;
3use alloc::vec::Vec;
4
5use super::super::directory::FileFlags;
6use super::super::directory::{DirectoryRecord, DirectoryRecordHeader, DirectoryRef};
7use super::super::io::{self, LogicalSector, Read, Seek};
8
9use super::IsoImage;
10use super::rrip::{self, RripMetadata};
11
12sync_only! {
13use core::ops::DerefMut;
14use super::super::io::{IsoCursor, SeekFrom};
15use super::rrip::collect_su_entries;
16use spin::Mutex;
17}
18
19#[derive(Debug, Clone, Copy)]
21pub struct Extent {
22 pub sector: LogicalSector,
24 pub length: u32,
26}
27
28pub struct IsoDir<'a, T: Read + Seek> {
32 pub(crate) image: &'a IsoImage<T>,
33 pub(crate) directory: DirectoryRef,
34}
35
36sync_only! {
37impl<'a, T: Read + Seek> IsoDir<'a, T> {
38 pub fn entries(&self) -> IsoDirIter<'_, T> {
40 let rrip_skip = if self.image.info.susp_info.rrip_detected {
41 Some(self.image.info.susp_info.bytes_skipped)
42 } else {
43 None
44 };
45 IsoDirIter {
46 image: self.image,
47 directory: self.directory,
48 offset: 0,
49 rrip_skip,
50 pending_associated: None,
51 }
52 }
53
54 pub fn raw_entries(&self) -> RawDirIter<'_, T> {
56 RawDirIter {
57 reader: &self.image.data,
58 directory: self.directory,
59 offset: 0,
60 }
61 }
62}
63} #[derive(Debug, Clone)]
73pub struct DirEntry {
74 pub record: DirectoryRecord,
76 pub rrip: Option<RripMetadata>,
78 pub additional_extents: Vec<Extent>,
80 pub associated_file: Option<Extent>,
86}
87
88impl DirEntry {
89 #[inline]
90 pub fn name(&self) -> &[u8] {
92 self.record.name()
93 }
94
95 pub fn display_name(&self) -> Cow<'_, str> {
97 if let Some(ref rrip) = self.rrip
98 && let Some(ref nm) = rrip.alternate_name
99 {
100 return Cow::Borrowed(nm.as_str());
101 }
102 String::from_utf8_lossy(self.record.name())
103 }
104
105 pub fn matches_name(&self, name: &str) -> bool {
111 if let Some(rrip_name) = self
112 .rrip
113 .as_ref()
114 .and_then(|metadata| metadata.alternate_name.as_deref())
115 {
116 return rrip_name == name;
117 }
118
119 let raw_name = self.record.name();
120 if raw_name.len().is_multiple_of(2) {
121 let units: Vec<u16> = raw_name
122 .chunks_exact(2)
123 .map(|bytes| u16::from_be_bytes([bytes[0], bytes[1]]))
124 .collect();
125 if let Ok(joliet_name) = String::from_utf16(&units) {
126 let joliet_name = match joliet_name.rsplit_once(';') {
127 Some((base, version))
128 if !version.is_empty()
129 && version.bytes().all(|byte| byte.is_ascii_digit()) =>
130 {
131 base
132 }
133 _ => &joliet_name,
134 };
135 if joliet_name == name {
136 return true;
137 }
138 }
139 }
140
141 let display_name: String = self
142 .display_name()
143 .chars()
144 .filter(|character| *character != '\0')
145 .collect();
146 let display_name = match display_name.rsplit_once(';') {
147 Some((base, version))
148 if !version.is_empty() && version.bytes().all(|b| b.is_ascii_digit()) =>
149 {
150 base
151 }
152 _ => &display_name,
153 };
154 display_name.eq_ignore_ascii_case(name)
155 }
156
157 #[inline]
158 pub fn header(&self) -> &DirectoryRecordHeader {
160 self.record.header()
161 }
162
163 #[inline]
166 pub fn is_directory(&self) -> bool {
167 if let Some(ref rrip) = self.rrip
168 && rrip.child_link.is_some()
169 {
170 return true;
171 }
172 self.record.is_directory()
173 }
174
175 #[inline]
176 pub fn is_special(&self) -> bool {
178 self.record.is_special()
179 }
180
181 #[inline]
182 pub fn is_file(&self) -> bool {
184 !self.is_directory()
185 }
186
187 #[inline]
190 pub fn size(&self) -> usize {
191 self.record.size()
192 }
193
194 pub fn total_size(&self) -> u64 {
199 let first = self.record.header().data_len.read() as u64;
200 let rest: u64 = self
201 .additional_extents
202 .iter()
203 .map(|e| e.length as u64)
204 .sum();
205 first + rest
206 }
207
208 pub fn is_multi_extent(&self) -> bool {
210 !self.additional_extents.is_empty()
211 }
212
213 pub fn extents(&self) -> impl Iterator<Item = Extent> + '_ {
218 let header = self.record.header();
219 let first = Extent {
220 sector: LogicalSector(
221 header.extent.read() as usize + header.extended_attr_record as usize,
222 ),
223 length: header.data_len.read(),
224 };
225 core::iter::once(first).chain(self.additional_extents.iter().copied())
226 }
227
228 pub fn has_associated_file(&self) -> bool {
230 self.associated_file.is_some()
231 }
232
233 pub fn extended_attr_len(&self) -> Option<u8> {
240 let len = self.record.header().extended_attr_record;
241 if len > 0 { Some(len) } else { None }
242 }
243
244 #[inline]
245 pub fn system_use(&self) -> &[u8] {
247 self.record.system_use()
248 }
249}
250
251io_transform! {
252impl DirEntry {
253 pub async fn as_dir_ref<DATA: Read + Seek>(
258 &self,
259 image: &IsoImage<DATA>,
260 ) -> io::Result<DirectoryRef> {
261 if let Some(ref rrip) = self.rrip
262 && let Some(cl_sector) = rrip.child_link
263 {
264 return rrip::read_dir_size(image, LogicalSector(cl_sector as usize)).await;
265 }
266 let mut directory = self
267 .record
268 .as_dir_ref()
269 .map_err(|_| io::Error::other("not a directory"))?;
270 directory.extent += self.record.header().extended_attr_record as usize;
271 Ok(directory)
272 }
273}
274} io_transform! {
277impl<T: Read + Seek> IsoDir<'_, T> {
278 pub async fn read_entries(&self) -> io::Result<Vec<DirEntry>> {
283 const SECTOR_SIZE: usize = 2048;
284 let image_len = {
288 let mut data = self.image.data.lock();
289 data.seek(io::SeekFrom::End(0))
290 .await
291 .map_err(io::Error::erase)?
292 };
293 if self.directory.size as u64 > image_len {
294 return Err(io::Error::new(
295 io::ErrorKind::InvalidData,
296 "directory claims more data than the image contains",
297 ));
298 }
299 let mut bytes = alloc::vec![0_u8; self.directory.size];
300 self.image
301 .read_bytes_at(self.directory.extent.0 as u64 * SECTOR_SIZE as u64, &mut bytes)
302 .await?;
303 let mut cursor = hadris_io::Cursor::new(bytes.as_slice());
304 let mut offset = 0_usize;
305 let mut entries = Vec::new();
306 let mut associated_file = None;
307
308 while offset < bytes.len() {
309 if bytes[offset] == 0 {
310 offset = (offset / SECTOR_SIZE + 1) * SECTOR_SIZE;
311 continue;
312 }
313 cursor.seek(io::SeekFrom::Start(offset as u64)).await?;
314 let record = DirectoryRecord::parse(&mut cursor).await?;
315 offset += record.size();
316 let flags = FileFlags::from_bits_retain(record.header().flags);
317 if record.header().file_unit_size != 0 || record.header().interleave_gap_size != 0 {
318 return Err(io::Error::new(
319 io::ErrorKind::Unsupported,
320 "interleaved ISO entries are not supported",
321 ));
322 }
323 if flags.contains(FileFlags::ASSOCIATED_FILE) {
324 associated_file = Some(Extent {
325 sector: LogicalSector(
326 record.header().extent.read() as usize
327 + record.header().extended_attr_record as usize,
328 ),
329 length: record.header().data_len.read(),
330 });
331 continue;
332 }
333
334 let mut additional_extents = Vec::new();
335 if flags.contains(FileFlags::NOT_FINAL) {
336 const MAX_EXTENTS: usize = 4096;
337 while additional_extents.len() < MAX_EXTENTS && offset < bytes.len() {
338 if bytes[offset] == 0 {
339 offset = (offset / SECTOR_SIZE + 1) * SECTOR_SIZE;
340 continue;
341 }
342 cursor.seek(io::SeekFrom::Start(offset as u64)).await?;
343 let continuation = DirectoryRecord::parse(&mut cursor).await?;
344 offset += continuation.size();
345 let header = continuation.header();
346 if continuation.name() != record.name()
347 || (header.flags ^ record.header().flags)
348 & !FileFlags::NOT_FINAL.bits()
349 != 0
350 || header.volume_sequence_number.read()
351 != record.header().volume_sequence_number.read()
352 || header.file_unit_size != 0
353 || header.interleave_gap_size != 0
354 {
355 return Err(io::Error::new(
356 io::ErrorKind::InvalidData,
357 "invalid ISO multi-extent continuation",
358 ));
359 }
360 additional_extents.push(Extent {
361 sector: LogicalSector(
362 header.extent.read() as usize + header.extended_attr_record as usize,
363 ),
364 length: header.data_len.read(),
365 });
366 if !FileFlags::from_bits_retain(header.flags)
367 .contains(FileFlags::NOT_FINAL)
368 {
369 break;
370 }
371 }
372 }
373
374 let rrip = if self.image.info.susp_info.rrip_detected {
375 let fields = rrip::collect_su_entries(
376 &record,
377 self.image,
378 self.image.info.susp_info.bytes_skipped,
379 )
380 .await?;
381 let metadata = RripMetadata::from_fields(&fields);
382 if metadata.is_relocated {
383 continue;
384 }
385 Some(metadata)
386 } else {
387 None
388 };
389 entries.push(DirEntry {
390 record,
391 rrip,
392 additional_extents,
393 associated_file: associated_file.take(),
394 });
395 }
396 Ok(entries)
397 }
398
399 pub async fn find(&self, name: &str) -> io::Result<Option<DirEntry>> {
401 Ok(self
402 .read_entries()
403 .await?
404 .into_iter()
405 .find(|entry| entry.matches_name(name)))
406 }
407}
408} sync_only! {
413
414pub struct IsoDirIter<'a, T: Read + Seek> {
420 image: &'a IsoImage<T>,
421 directory: DirectoryRef,
422 offset: usize,
423 rrip_skip: Option<u8>,
424 pending_associated: Option<Extent>,
426}
427
428impl<T: Read + Seek> IsoDirIter<'_, T> {
429 pub fn offset(&self) -> usize {
431 self.offset
432 }
433
434 fn collect_additional_extents(
438 &mut self,
439 first: &DirectoryRecord,
440 ) -> io::Result<Vec<Extent>> {
441 let mut extents = Vec::new();
442 const MAX_EXTENTS: usize = 4096;
444
445 loop {
446 if extents.len() >= MAX_EXTENTS {
447 break;
448 }
449
450 let record = match self.next_raw_record() {
451 Some(Ok(r)) => r,
452 Some(Err(e)) => return Err(e),
453 None => break,
454 };
455
456 let header = record.header();
457 if record.name() != first.name()
458 || (header.flags ^ first.header().flags) & !FileFlags::NOT_FINAL.bits() != 0
459 || header.volume_sequence_number.read()
460 != first.header().volume_sequence_number.read()
461 || header.file_unit_size != 0
462 || header.interleave_gap_size != 0
463 {
464 return Err(io::Error::new(
465 io::ErrorKind::InvalidData,
466 "invalid ISO multi-extent continuation",
467 ));
468 }
469 extents.push(Extent {
470 sector: LogicalSector(
471 header.extent.read() as usize + header.extended_attr_record as usize,
472 ),
473 length: header.data_len.read(),
474 });
475
476 if !FileFlags::from_bits_retain(header.flags).contains(FileFlags::NOT_FINAL) {
478 break;
479 }
480 }
481
482 Ok(extents)
483 }
484
485 fn next_raw_record(&mut self) -> Option<io::Result<DirectoryRecord>> {
487 use super::super::io::try_io_result_option;
488 let reader = &self.image.data;
489 let mut reader = reader.lock();
490
491 const SECTOR_SIZE: usize = 2048;
492
493 loop {
494 if self.offset >= self.directory.size {
495 return None;
496 }
497
498 try_io_result_option!(reader.seek(SeekFrom::Start(
499 (self.directory.extent.0 as u64) * SECTOR_SIZE as u64 + (self.offset as u64),
500 )));
501
502 let mut len_byte = [0u8; 1];
503 try_io_result_option!(reader.read_exact(&mut len_byte));
504
505 if len_byte[0] == 0 {
506 let current_sector_offset = self.offset % SECTOR_SIZE;
507 if current_sector_offset == 0 {
508 return None;
509 }
510 let bytes_to_skip = SECTOR_SIZE - current_sector_offset;
511 self.offset += bytes_to_skip;
512 continue;
513 }
514
515 try_io_result_option!(reader.seek(SeekFrom::Start(
516 (self.directory.extent.0 as u64) * SECTOR_SIZE as u64 + (self.offset as u64),
517 )));
518
519 let record = try_io_result_option!(DirectoryRecord::parse(reader.deref_mut()));
520 self.offset += record.size();
521
522 return Some(Ok(record));
523 }
524 }
525}
526
527impl<T: Read + Seek> Iterator for IsoDirIter<'_, T> {
528 type Item = io::Result<DirEntry>;
529
530 fn next(&mut self) -> Option<Self::Item> {
531 loop {
532 let record = match self.next_raw_record()? {
533 Ok(r) => r,
534 Err(e) => return Some(Err(e)),
535 };
536
537 let flags = FileFlags::from_bits_retain(record.header().flags);
538 if record.header().file_unit_size != 0 || record.header().interleave_gap_size != 0 {
539 return Some(Err(io::Error::new(
540 io::ErrorKind::Unsupported,
541 "interleaved ISO entries are not supported",
542 )));
543 }
544
545 if flags.contains(FileFlags::ASSOCIATED_FILE) {
548 let header = record.header();
549 self.pending_associated = Some(Extent {
550 sector: LogicalSector(
551 header.extent.read() as usize + header.extended_attr_record as usize,
552 ),
553 length: header.data_len.read(),
554 });
555 continue;
556 }
557
558 let additional_extents = if flags.contains(FileFlags::NOT_FINAL) {
560 match self.collect_additional_extents(&record) {
561 Ok(extents) => extents,
562 Err(e) => return Some(Err(e)),
563 }
564 } else {
565 Vec::new()
566 };
567
568 let associated_file = self.pending_associated.take();
569
570 if let Some(bytes_to_skip) = self.rrip_skip {
571 let fields = match collect_su_entries(&record, self.image, bytes_to_skip) {
573 Ok(f) => f,
574 Err(e) => return Some(Err(e)),
575 };
576 let rrip = RripMetadata::from_fields(&fields);
577
578 if rrip.is_relocated {
580 continue;
581 }
582
583 return Some(Ok(DirEntry {
584 record,
585 rrip: Some(rrip),
586 additional_extents,
587 associated_file,
588 }));
589 } else {
590 return Some(Ok(DirEntry {
592 record,
593 rrip: None,
594 additional_extents,
595 associated_file,
596 }));
597 }
598 }
599 }
600}
601
602pub struct RawDirIter<'a, T: Read + Seek> {
606 pub(crate) reader: &'a Mutex<IsoCursor<T>>,
607 pub(crate) directory: DirectoryRef,
608 pub(crate) offset: usize,
609}
610
611impl<T: Read + Seek> RawDirIter<'_, T> {
612 pub fn offset(&self) -> usize {
614 self.offset
615 }
616}
617
618impl<T: Read + Seek> Iterator for RawDirIter<'_, T> {
619 type Item = io::Result<DirectoryRecord>;
620 fn next(&mut self) -> Option<Self::Item> {
621 use super::super::io::try_io_result_option;
622 let mut reader = self.reader.lock();
623
624 const SECTOR_SIZE: usize = 2048;
625
626 loop {
627 if self.offset >= self.directory.size {
628 return None;
629 }
630
631 try_io_result_option!(reader.seek(SeekFrom::Start(
632 (self.directory.extent.0 as u64) * SECTOR_SIZE as u64 + (self.offset as u64),
633 )));
634
635 let mut len_byte = [0u8; 1];
636 try_io_result_option!(reader.read_exact(&mut len_byte));
637
638 if len_byte[0] == 0 {
639 let current_sector_offset = self.offset % SECTOR_SIZE;
640 if current_sector_offset == 0 {
641 return None;
642 }
643 let bytes_to_skip = SECTOR_SIZE - current_sector_offset;
644 self.offset += bytes_to_skip;
645 continue;
646 }
647
648 try_io_result_option!(reader.seek(SeekFrom::Start(
649 (self.directory.extent.0 as u64) * SECTOR_SIZE as u64 + (self.offset as u64),
650 )));
651
652 let record = try_io_result_option!(DirectoryRecord::parse(reader.deref_mut()));
653 self.offset += record.size();
654
655 return Some(Ok(record));
656 }
657 }
658}
659
660}