1use core::char::decode_utf16;
4
5use super::directory::{DirectoryRecord, DirectoryRecordHeader, FileFlags};
6use super::io::{self, Read, Seek, SeekFrom};
7use super::volume::{
8 PrimaryVolumeDescriptor, SupplementaryVolumeDescriptor, VolumeDescriptor,
9 VolumeDescriptorHeader, VolumeDescriptorType,
10};
11use crate::joliet::JolietLevel;
12
13const DESCRIPTOR_SIZE: u64 = 2048;
14const FIRST_DESCRIPTOR: u64 = 16;
15const MIN_RECORD_SIZE: usize = core::mem::size_of::<DirectoryRecordHeader>() + 1;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum IsoNamespace {
20 Primary,
22 Joliet(JolietLevel),
24 Enhanced,
26}
27
28#[derive(Debug, Clone, Copy)]
30pub struct IsoRoot {
31 namespace: IsoNamespace,
32 extent: u32,
33 size: u32,
34 block_size: u16,
35}
36
37impl IsoRoot {
38 pub const fn namespace(self) -> IsoNamespace {
40 self.namespace
41 }
42
43 pub const fn size(self) -> u32 {
45 self.size
46 }
47
48 pub const fn block_size(self) -> u16 {
50 self.block_size
51 }
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum NameError {
57 InvalidEncoding,
59 BufferTooSmall,
61}
62
63impl core::fmt::Display for NameError {
64 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
65 match self {
66 Self::InvalidEncoding => f.write_str("invalid ISO filename encoding"),
67 Self::BufferTooSmall => f.write_str("filename output buffer is too small"),
68 }
69 }
70}
71
72impl core::error::Error for NameError {}
73
74#[derive(Debug, Clone, Copy)]
76pub struct IsoName<'a> {
77 raw: &'a [u8],
78 namespace: IsoNamespace,
79}
80
81impl<'a> IsoName<'a> {
82 pub const fn raw(self) -> &'a [u8] {
84 self.raw
85 }
86
87 pub const fn namespace(self) -> IsoNamespace {
89 self.namespace
90 }
91
92 pub fn matches(self, candidate: &str) -> bool {
94 if self.raw == [0] {
95 return candidate == ".";
96 }
97 if self.raw == [1] {
98 return candidate == "..";
99 }
100 match self.namespace {
101 IsoNamespace::Primary => {
102 strip_primary_version(self.raw).eq_ignore_ascii_case(candidate.as_bytes())
103 }
104 IsoNamespace::Joliet(_) => joliet_matches(strip_joliet_version(self.raw), candidate),
105 IsoNamespace::Enhanced => {
106 strip_primary_version(self.raw).eq_ignore_ascii_case(candidate.as_bytes())
107 }
108 }
109 }
110
111 pub fn decode_into(self, output: &mut [u8]) -> Result<&str, NameError> {
113 if self.raw == [0] {
114 return write_special(output, b".");
115 }
116 if self.raw == [1] {
117 return write_special(output, b"..");
118 }
119 match self.namespace {
120 IsoNamespace::Primary => {
121 let raw = strip_primary_version(self.raw);
122 if !raw.is_ascii() {
123 return Err(NameError::InvalidEncoding);
124 }
125 if raw.len() > output.len() {
126 return Err(NameError::BufferTooSmall);
127 }
128 output[..raw.len()].copy_from_slice(raw);
129 core::str::from_utf8(&output[..raw.len()]).map_err(|_| NameError::InvalidEncoding)
130 }
131 IsoNamespace::Joliet(_) => decode_joliet_into(strip_joliet_version(self.raw), output),
132 IsoNamespace::Enhanced => {
133 let raw = strip_primary_version(self.raw);
134 core::str::from_utf8(raw).map_err(|_| NameError::InvalidEncoding)?;
135 if raw.len() > output.len() {
136 return Err(NameError::BufferTooSmall);
137 }
138 output[..raw.len()].copy_from_slice(raw);
139 core::str::from_utf8(&output[..raw.len()]).map_err(|_| NameError::InvalidEncoding)
140 }
141 }
142 }
143}
144
145fn write_special<'a>(output: &'a mut [u8], value: &[u8]) -> Result<&'a str, NameError> {
146 if value.len() > output.len() {
147 return Err(NameError::BufferTooSmall);
148 }
149 output[..value.len()].copy_from_slice(value);
150 core::str::from_utf8(&output[..value.len()]).map_err(|_| NameError::InvalidEncoding)
151}
152
153fn strip_primary_version(raw: &[u8]) -> &[u8] {
154 if let Some(pos) = raw.iter().rposition(|byte| *byte == b';')
155 && pos + 1 < raw.len()
156 && raw[pos + 1..].iter().all(u8::is_ascii_digit)
157 {
158 &raw[..pos]
159 } else {
160 raw
161 }
162}
163
164fn strip_joliet_version(raw: &[u8]) -> &[u8] {
165 if raw.len() % 2 != 0 {
166 return raw;
167 }
168 let mut pos = raw.len();
169 while pos >= 2 && raw[pos - 2] == 0 && raw[pos - 1].is_ascii_digit() {
170 pos -= 2;
171 }
172 if pos + 2 <= raw.len() && pos >= 2 && raw[pos - 2..pos] == [0, b';'] {
173 &raw[..pos - 2]
174 } else {
175 raw
176 }
177}
178
179fn joliet_matches(raw: &[u8], candidate: &str) -> bool {
180 if raw.len() % 2 != 0 {
181 return false;
182 }
183 let units = raw
184 .chunks_exact(2)
185 .map(|pair| u16::from_be_bytes([pair[0], pair[1]]));
186 let mut expected = candidate.chars();
187 for decoded in decode_utf16(units) {
188 let Ok(ch) = decoded else {
189 return false;
190 };
191 if expected.next() != Some(ch) {
192 return false;
193 }
194 }
195 expected.next().is_none()
196}
197
198fn rrip_nm_into(su: &[u8], out: &mut [u8]) -> Option<usize> {
206 const NM_CONTINUE: u8 = 0x01;
207 const NM_CURRENT: u8 = 0x02;
208 const NM_PARENT: u8 = 0x04;
209
210 let mut written = 0usize;
211 let mut found = false;
212 let mut i = 0usize;
213 while i + 4 <= su.len() {
215 let len = su[i + 2] as usize;
216 if len < 4 || i + len > su.len() {
217 break;
218 }
219 if &su[i..i + 2] == b"ST" {
220 break; }
222 if &su[i..i + 2] == b"NM" && len >= 5 {
223 let flags = su[i + 4];
224 if flags & (NM_CURRENT | NM_PARENT) != 0 {
225 return None;
226 }
227 let content = &su[i + 5..i + len];
228 if written + content.len() > out.len() {
229 return None; }
231 out[written..written + content.len()].copy_from_slice(content);
232 written += content.len();
233 found = true;
234 if flags & NM_CONTINUE == 0 {
235 break;
236 }
237 }
238 i += len;
239 }
240
241 found.then_some(written)
242}
243
244fn decode_joliet_into<'a>(raw: &[u8], output: &'a mut [u8]) -> Result<&'a str, NameError> {
245 if raw.len() % 2 != 0 {
246 return Err(NameError::InvalidEncoding);
247 }
248 let units = raw
249 .chunks_exact(2)
250 .map(|pair| u16::from_be_bytes([pair[0], pair[1]]));
251 let mut written = 0;
252 for decoded in decode_utf16(units) {
253 let ch = decoded.map_err(|_| NameError::InvalidEncoding)?;
254 let needed = ch.len_utf8();
255 if written + needed > output.len() {
256 return Err(NameError::BufferTooSmall);
257 }
258 ch.encode_utf8(&mut output[written..written + needed]);
259 written += needed;
260 }
261 core::str::from_utf8(&output[..written]).map_err(|_| NameError::InvalidEncoding)
262}
263
264#[derive(Debug, Clone, Copy)]
265struct DirectoryLocation {
266 extent: u32,
267 size: u32,
268 block_size: u16,
269 namespace: IsoNamespace,
270}
271
272impl From<IsoRoot> for DirectoryLocation {
273 fn from(root: IsoRoot) -> Self {
274 Self {
275 extent: root.extent,
276 size: root.size,
277 block_size: root.block_size,
278 namespace: root.namespace,
279 }
280 }
281}
282
283#[derive(Debug, Clone, Copy)]
285pub struct IsoDirEntry {
286 record: DirectoryRecord,
287 directory: DirectoryLocation,
288 continuation_offset: Option<u32>,
289 total_size: u64,
290}
291
292impl IsoDirEntry {
293 pub const fn record(&self) -> &DirectoryRecord {
295 &self.record
296 }
297
298 pub fn name(&self) -> IsoName<'_> {
300 IsoName {
301 raw: self.record.name(),
302 namespace: self.directory.namespace,
303 }
304 }
305
306 pub fn is_directory(&self) -> bool {
308 self.record.is_directory()
309 }
310
311 pub fn is_file(&self) -> bool {
313 !self.is_directory()
314 }
315
316 pub const fn is_multi_extent(&self) -> bool {
318 self.continuation_offset.is_some()
319 }
320
321 pub const fn total_size(&self) -> u64 {
323 self.total_size
324 }
325
326 pub fn system_use(&self) -> &[u8] {
328 self.record.system_use()
329 }
330
331 pub fn rrip_name_into<'b>(&self, output: &'b mut [u8]) -> Option<Result<&'b str, NameError>> {
340 let len = rrip_nm_into(self.system_use(), output)?;
341 Some(core::str::from_utf8(&output[..len]).map_err(|_| NameError::InvalidEncoding))
342 }
343
344 pub fn rrip_name_matches(&self, candidate: &str) -> bool {
349 let mut buffer = [0u8; 255];
350 match rrip_nm_into(self.system_use(), &mut buffer) {
351 Some(len) => &buffer[..len] == candidate.as_bytes(),
352 None => false,
353 }
354 }
355}
356
357#[derive(Debug)]
359pub struct IsoReader<R> {
360 source: R,
361 primary: IsoRoot,
362 joliet: Option<IsoRoot>,
363 enhanced: Option<IsoRoot>,
364}
365
366impl<R> IsoReader<R> {
367 pub fn into_inner(self) -> R {
369 self.source
370 }
371
372 pub const fn primary_root(&self) -> IsoRoot {
374 self.primary
375 }
376
377 pub const fn joliet_root(&self) -> Option<IsoRoot> {
379 self.joliet
380 }
381
382 pub const fn enhanced_root(&self) -> Option<IsoRoot> {
384 self.enhanced
385 }
386
387 pub const fn preferred_root(&self) -> IsoRoot {
389 match (self.joliet, self.enhanced) {
390 (Some(root), _) => root,
391 (None, Some(root)) => root,
392 (None, None) => self.primary,
393 }
394 }
395
396 pub fn roots(&self) -> impl Iterator<Item = IsoRoot> {
398 [Some(self.primary), self.enhanced, self.joliet]
399 .into_iter()
400 .flatten()
401 }
402
403 pub fn root(&self, namespace: IsoNamespace) -> Option<IsoRoot> {
405 match namespace {
406 IsoNamespace::Primary => Some(self.primary),
407 IsoNamespace::Joliet(level) => self
408 .joliet
409 .filter(|root| root.namespace == IsoNamespace::Joliet(level)),
410 IsoNamespace::Enhanced => self.enhanced,
411 }
412 }
413}
414
415io_transform! {
416impl<R: Read + Seek> IsoReader<R> {
417 pub async fn open(mut source: R) -> io::Result<Self> {
419 let mut primary = None;
420 let mut joliet = None;
421 let mut enhanced = None;
422 let mut sector = FIRST_DESCRIPTOR;
423 loop {
424 source.seek(SeekFrom::Start(sector.checked_mul(DESCRIPTOR_SIZE).ok_or_else(overflow)?))
425 .await.map_err(io::Error::erase)?;
426 let mut bytes = [0_u8; DESCRIPTOR_SIZE as usize];
427 source.read_exact(&mut bytes).await?;
428 let header = VolumeDescriptorHeader::from_bytes(&bytes[..7]);
429 if !header.is_valid() {
430 return Err(invalid("invalid volume descriptor header"));
431 }
432 let ty = VolumeDescriptorType::from_u8(header.descriptor_type);
433 if ty == VolumeDescriptorType::VolumeSetTerminator { break; }
434 match VolumeDescriptor::new(bytes) {
435 VolumeDescriptor::Primary(descriptor) => primary = Some(root_from_primary(&descriptor)?),
436 VolumeDescriptor::Supplementary(descriptor) => {
437 if descriptor.header.version == 2 && descriptor.file_structure_version == 2 {
438 enhanced = Some(root_from_enhanced(&descriptor)?);
439 } else if let Some(root) = root_from_joliet(&descriptor)?
440 && joliet.map(|old: IsoRoot| joliet_level(old) < joliet_level(root)).unwrap_or(true)
441 { joliet = Some(root); }
442 }
443 _ => {}
444 }
445 sector = sector.checked_add(1).ok_or_else(overflow)?;
446 }
447 let primary = primary.ok_or_else(|| invalid("primary volume descriptor not found"))?;
448 Ok(Self { source, primary, joliet, enhanced })
449 }
450
451 pub fn open_dir(&mut self, root: IsoRoot) -> IsoDirReader<'_, R> {
453 IsoDirReader { image: self, directory: root.into(), offset: 0 }
454 }
455
456 pub async fn find_path(&mut self, path: &str) -> io::Result<Option<IsoDirEntry>> {
458 self.find_path_in(self.preferred_root(), path).await
459 }
460
461 pub async fn find_path_in(&mut self, root: IsoRoot, path: &str) -> io::Result<Option<IsoDirEntry>> {
463 let mut location = DirectoryLocation::from(root);
464 let mut components = hadris_path::VPath::with_separators(
465 path, hadris_path::Separators::SlashOrBackslash
466 ).components().filter_map(|component| match component {
467 hadris_path::Component::Root | hadris_path::Component::Current => None,
468 other => Some(other),
469 }).peekable();
470
471 while let Some(component) = components.next() {
472 let hadris_path::Component::Normal(name) = component else {
473 return Err(io::Error::new(io::ErrorKind::InvalidInput, "parent path components are not supported"));
474 };
475 let mut offset = 0;
476 let mut found = None;
477 while let Some(entry) = self.next_entry(location, &mut offset).await? {
478 let flags = FileFlags::from_bits_retain(entry.record.header().flags);
479 if !entry.record.is_special()
480 && !flags.contains(FileFlags::ASSOCIATED_FILE)
481 && entry.name().matches(name)
482 { found = Some(entry); break; }
483 }
484 let Some(entry) = found else { return Ok(None); };
485 if components.peek().is_none() { return Ok(Some(entry)); }
486 if !entry.is_directory() { return Ok(None); }
487 location = entry_directory(&entry)?;
488 }
489 Ok(None)
490 }
491
492 pub fn open_file<'a>(&'a mut self, entry: &IsoDirEntry) -> io::Result<IsoFileReader<'a, R>> {
494 validate_file(entry)?;
495 Ok(IsoFileReader {
496 image: self,
497 entry: *entry,
498 current_record: entry.record,
499 current_extent_start: 0,
500 continuation_cursor: entry.continuation_offset,
501 position: 0,
502 })
503 }
504
505 async fn next_entry(&mut self, directory: DirectoryLocation, offset: &mut u32) -> io::Result<Option<IsoDirEntry>> {
506 let Some(record) = self.next_raw(directory, offset).await? else { return Ok(None); };
507 let first_flags = FileFlags::from_bits_retain(record.header().flags);
508 let continuation_offset = first_flags.contains(FileFlags::NOT_FINAL).then_some(*offset);
509 let mut total_size = record.header().data_len.read() as u64;
510 if continuation_offset.is_some() {
511 loop {
512 let continuation = self.next_raw(directory, offset).await?
513 .ok_or_else(|| invalid("truncated multi-extent file"))?;
514 validate_continuation(&record, &continuation)?;
515 total_size = total_size.checked_add(continuation.header().data_len.read() as u64)
516 .ok_or_else(overflow)?;
517 if !FileFlags::from_bits_retain(continuation.header().flags).contains(FileFlags::NOT_FINAL) { break; }
518 }
519 }
520 Ok(Some(IsoDirEntry { record, directory, continuation_offset, total_size }))
521 }
522
523 async fn next_raw(&mut self, directory: DirectoryLocation, offset: &mut u32) -> io::Result<Option<DirectoryRecord>> {
524 let block = directory.block_size as u32;
525 while *offset < directory.size {
526 let base = byte_offset(directory.extent, directory.block_size)?;
527 let absolute = base.checked_add(*offset as u64).ok_or_else(overflow)?;
528 self.source.seek(SeekFrom::Start(absolute)).await.map_err(io::Error::erase)?;
529 let mut len = [0_u8; 1];
530 self.source.read_exact(&mut len).await?;
531 if len[0] == 0 {
532 let next = ((*offset / block) + 1).checked_mul(block).ok_or_else(overflow)?;
533 if next <= *offset { return Err(invalid("directory offset did not advance")); }
534 *offset = next.min(directory.size);
535 continue;
536 }
537 let len = len[0] as usize;
538 if len < MIN_RECORD_SIZE || (*offset % block) as usize + len > block as usize {
539 return Err(invalid("invalid directory record length"));
540 }
541 let mut header_bytes = [0_u8; core::mem::size_of::<DirectoryRecordHeader>()];
542 header_bytes[0] = len as u8;
543 self.source.read_exact(&mut header_bytes[1..]).await?;
544 let header: DirectoryRecordHeader = bytemuck::pod_read_unaligned(&header_bytes);
545 let name_end = MIN_RECORD_SIZE - 1 + header.file_identifier_len as usize;
546 if name_end > len { return Err(invalid("directory identifier exceeds record")); }
547 self.source.seek(SeekFrom::Start(absolute)).await.map_err(io::Error::erase)?;
548 let record = DirectoryRecord::parse(&mut self.source).await?;
549 *offset = offset.checked_add(len as u32).ok_or_else(overflow)?;
550 return Ok(Some(record));
551 }
552 Ok(None)
553 }
554
555}
556} pub struct IsoDirReader<'a, R> {
560 image: &'a mut IsoReader<R>,
561 directory: DirectoryLocation,
562 offset: u32,
563}
564
565io_transform! {
566impl<R: Read + Seek> IsoDirReader<'_, R> {
567 pub async fn next_entry(&mut self) -> io::Result<Option<IsoDirEntry>> {
569 self.image.next_entry(self.directory, &mut self.offset).await
570 }
571}
572} sync_only! {
575impl<R: Read + Seek> Iterator for IsoDirReader<'_, R> {
576 type Item = io::Result<IsoDirEntry>;
577 fn next(&mut self) -> Option<Self::Item> { self.next_entry().transpose() }
578}
579}
580
581pub struct IsoFileReader<'a, R> {
583 image: &'a mut IsoReader<R>,
584 entry: IsoDirEntry,
585 current_record: DirectoryRecord,
586 current_extent_start: u64,
587 continuation_cursor: Option<u32>,
588 position: u64,
589}
590
591impl<R> IsoFileReader<'_, R> {
592 pub const fn len(&self) -> u64 {
594 self.entry.total_size
595 }
596
597 pub const fn is_empty(&self) -> bool {
599 self.entry.total_size == 0
600 }
601
602 pub const fn position(&self) -> u64 {
604 self.position
605 }
606}
607
608io_transform! {
609impl<R: Read + Seek> IsoFileReader<'_, R> {
610 pub async fn read_chunk(&mut self, output: &mut [u8]) -> io::Result<usize> {
612 if self.position >= self.entry.total_size || output.is_empty() {
613 return Ok(0);
614 }
615
616 let wanted = output
617 .len()
618 .min((self.entry.total_size - self.position) as usize);
619 let mut written = 0;
620 while written < wanted {
621 let extent_len = self.current_record.header().data_len.read() as u64;
622 let within = self.position.checked_sub(self.current_extent_start)
623 .ok_or_else(|| invalid("file reader extent state is invalid"))?;
624 if within < extent_len {
625 let available = (extent_len - within) as usize;
626 let take = available.min(wanted - written);
627 let header = self.current_record.header();
628 let xar = header.extended_attr_record as u64
629 * self.entry.directory.block_size as u64;
630 let absolute = byte_offset(header.extent.read(), self.entry.directory.block_size)?
631 .checked_add(xar)
632 .and_then(|value| value.checked_add(within))
633 .ok_or_else(overflow)?;
634 self.image.source.seek(SeekFrom::Start(absolute)).await
635 .map_err(io::Error::erase)?;
636 self.image.source.read_exact(&mut output[written..written + take]).await?;
637 written += take;
638 self.position += take as u64;
639 }
640
641 if written == wanted {
642 break;
643 }
644 self.advance_extent(extent_len).await?;
645 }
646 Ok(written)
647 }
648
649 async fn advance_extent(&mut self, extent_len: u64) -> io::Result<()> {
650 self.current_extent_start = self.current_extent_start
651 .checked_add(extent_len)
652 .ok_or_else(overflow)?;
653 let Some(ref mut cursor) = self.continuation_cursor else {
654 return Err(invalid("file extent chain ended before its declared size"));
655 };
656 let next = self.image.next_raw(self.entry.directory, cursor).await?
657 .ok_or_else(|| invalid("truncated multi-extent file"))?;
658 validate_continuation(&self.entry.record, &next)?;
659 if !FileFlags::from_bits_retain(next.header().flags).contains(FileFlags::NOT_FINAL) {
660 self.continuation_cursor = None;
661 }
662 self.current_record = next;
663 Ok(())
664 }
665}
666} fn root_from_primary(descriptor: &PrimaryVolumeDescriptor) -> io::Result<IsoRoot> {
669 make_root(
670 IsoNamespace::Primary,
671 descriptor.logical_block_size.read(),
672 descriptor.dir_record.header.extent.read(),
673 descriptor.dir_record.header.data_len.read(),
674 descriptor.dir_record.header.extended_attr_record,
675 )
676}
677
678fn root_from_joliet(descriptor: &SupplementaryVolumeDescriptor) -> io::Result<Option<IsoRoot>> {
679 let Some(level) = JolietLevel::from_escape_sequence(&descriptor.escape_sequences) else {
680 return Ok(None);
681 };
682 Ok(Some(make_root(
683 IsoNamespace::Joliet(level),
684 descriptor.logical_block_size.read(),
685 descriptor.dir_record.header.extent.read(),
686 descriptor.dir_record.header.data_len.read(),
687 descriptor.dir_record.header.extended_attr_record,
688 )?))
689}
690
691fn root_from_enhanced(descriptor: &SupplementaryVolumeDescriptor) -> io::Result<IsoRoot> {
692 make_root(
693 IsoNamespace::Enhanced,
694 descriptor.logical_block_size.read(),
695 descriptor.dir_record.header.extent.read(),
696 descriptor.dir_record.header.data_len.read(),
697 descriptor.dir_record.header.extended_attr_record,
698 )
699}
700
701fn joliet_level(root: IsoRoot) -> JolietLevel {
702 match root.namespace {
703 IsoNamespace::Joliet(level) => level,
704 IsoNamespace::Primary | IsoNamespace::Enhanced => {
705 unreachable!("non-Joliet root used as Joliet root")
706 }
707 }
708}
709
710fn make_root(
711 namespace: IsoNamespace,
712 block_size: u16,
713 extent: u32,
714 size: u32,
715 xar_blocks: u8,
716) -> io::Result<IsoRoot> {
717 if block_size < 512 || !block_size.is_power_of_two() {
718 return Err(invalid("invalid logical block size"));
719 }
720 let extent = extent.checked_add(xar_blocks as u32).ok_or_else(overflow)?;
721 byte_offset(extent, block_size)?;
722 Ok(IsoRoot {
723 namespace,
724 extent,
725 size,
726 block_size,
727 })
728}
729
730fn entry_directory(entry: &IsoDirEntry) -> io::Result<DirectoryLocation> {
731 if !entry.is_directory() {
732 return Err(invalid("entry is not a directory"));
733 }
734 Ok(DirectoryLocation {
735 extent: entry
736 .record
737 .header()
738 .extent
739 .read()
740 .checked_add(entry.record.header().extended_attr_record as u32)
741 .ok_or_else(overflow)?,
742 size: entry.record.header().data_len.read(),
743 block_size: entry.directory.block_size,
744 namespace: entry.directory.namespace,
745 })
746}
747
748fn validate_file(entry: &IsoDirEntry) -> io::Result<()> {
749 if entry.is_directory() {
750 return Err(io::Error::new(
751 io::ErrorKind::InvalidInput,
752 "entry is a directory",
753 ));
754 }
755 let header = entry.record.header();
756 if header.file_unit_size != 0 || header.interleave_gap_size != 0 {
757 return Err(io::Error::new(
758 io::ErrorKind::Unsupported,
759 "interleaved files are unsupported",
760 ));
761 }
762 if header.volume_sequence_number.read() != 1 {
763 return Err(io::Error::new(
764 io::ErrorKind::Unsupported,
765 "multi-volume files are unsupported",
766 ));
767 }
768 Ok(())
769}
770
771fn validate_continuation(first: &DirectoryRecord, next: &DirectoryRecord) -> io::Result<()> {
772 if first.name() != next.name()
773 || next.is_directory()
774 || FileFlags::from_bits_retain(next.header().flags).contains(FileFlags::ASSOCIATED_FILE)
775 {
776 return Err(invalid("invalid multi-extent continuation"));
777 }
778 Ok(())
779}
780
781fn byte_offset(extent: u32, block_size: u16) -> io::Result<u64> {
782 (extent as u64)
783 .checked_mul(block_size as u64)
784 .ok_or_else(overflow)
785}
786
787fn invalid(message: &'static str) -> io::Error {
788 io::Error::new(io::ErrorKind::InvalidData, message)
789}
790fn overflow() -> io::Error {
791 invalid("ISO offset overflow")
792}