1use super::directory::DirectoryRef;
2use super::io::{self, IsoCursor, LogicalSector, Read, Seek};
3use super::path::{PathTableEntry, PathTableInfo, PathTableRef};
4use super::volume::{PrimaryVolumeDescriptor, VolumeDescriptorList};
5use crate::file::EntryType;
6use crate::joliet::JolietLevel;
7use hadris_common::types::endian::Endian;
8#[cfg(not(feature = "alloc"))]
9use hadris_common::types::no_alloc::ArrayVec;
10use hadris_path::{Component, Separators, VPath};
11pub use volume::VolumeDescriptorIter;
12
13mod boot;
14mod directory;
15pub use boot::*;
16pub use directory::{DirEntry, Extent, IsoDir};
17sync_only! {
18 pub use directory::{IsoDirIter, RawDirIter};
19}
20use spin::Mutex;
21
22mod rrip;
23pub(crate) use rrip::SuspInfo;
24pub use rrip::*;
25
26mod volume;
27
28pub enum FilenameType {
30 Builtin,
32 Joliet,
34}
35
36#[derive(Debug)]
37pub struct IsoImageInfo {
39 block_size: usize,
40 sector_size: usize,
41 root_dirs: RootDirs,
42 boot_catalog: Option<u32>,
43 path_table: PathTableRef,
44 pub(crate) susp_info: SuspInfo,
45 has_evd: bool,
47 #[cfg(feature = "alloc")]
50 path_table_cache: alloc::vec::Vec<PathTableEntry>,
51}
52
53impl IsoImageInfo {
54 pub fn block_size(&self) -> usize {
56 self.block_size
57 }
58
59 pub fn sector_size(&self) -> usize {
61 self.sector_size
62 }
63}
64
65#[derive(Debug)]
66pub struct RootDirs {
68 #[cfg(not(feature = "alloc"))]
69 dirs: ArrayVec<RootDir, 8>,
70 #[cfg(feature = "alloc")]
71 dirs: alloc::vec::Vec<RootDir>,
72}
73
74impl RootDirs {
75 fn new() -> Self {
76 Self {
77 #[cfg(not(feature = "alloc"))]
78 dirs: ArrayVec::new(),
79 #[cfg(feature = "alloc")]
80 dirs: alloc::vec::Vec::new(),
81 }
82 }
83
84 pub fn len(&self) -> usize {
86 self.dirs.len()
87 }
88
89 pub fn is_empty(&self) -> bool {
91 self.dirs.is_empty()
92 }
93
94 pub fn iter(&self) -> core::slice::Iter<'_, RootDir> {
96 self.dirs.iter()
97 }
98
99 pub fn get(&self, ty: EntryType) -> Option<RootDir> {
101 self.dirs.iter().copied().find(|root| root.ty == ty)
102 }
103
104 pub fn try_best_choice(&self) -> Option<RootDir> {
106 if self.dirs.is_empty() {
107 return None;
108 }
109 let mut best = (0, EntryType::default());
110 for (idx, dir) in self.dirs.iter().enumerate() {
111 if dir.ty > best.1 {
112 best = (idx, dir.ty);
113 }
114 }
115 Some(self.dirs[best.0])
116 }
117
118 pub fn best_choice(&self) -> RootDir {
125 self.try_best_choice()
126 .expect("ISO image contains no directory trees!")
127 }
128}
129
130impl<'a> IntoIterator for &'a RootDirs {
131 type Item = &'a RootDir;
132 type IntoIter = core::slice::Iter<'a, RootDir>;
133
134 fn into_iter(self) -> Self::IntoIter {
135 self.iter()
136 }
137}
138
139#[derive(Debug, Default, Clone, Copy)]
140pub struct RootDir {
142 ty: EntryType,
143 dir_ref: DirectoryRef,
144}
145
146impl RootDir {
147 pub fn entry_type(&self) -> EntryType {
149 self.ty
150 }
151
152 pub fn iter<'a, DATA: Read + Seek>(&self, iso: &'a IsoImage<DATA>) -> IsoDir<'a, DATA> {
154 IsoDir {
155 image: iso,
156 directory: self.dir_ref,
157 }
158 }
159
160 pub fn dir_ref(&self) -> DirectoryRef {
162 self.dir_ref
163 }
164}
165
166#[repr(u8)]
167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
168pub enum PathSeparator {
170 ForwardSlash = b'/',
172 Backslash = b'\\',
174}
175
176impl PathSeparator {
177 pub fn as_char(self) -> char {
179 self as u8 as char
180 }
181}
182
183#[derive(Debug)]
187pub struct IsoImage<DATA: Seek> {
188 pub(crate) data: Mutex<IsoCursor<DATA>>,
189 pub(crate) info: IsoImageInfo,
190}
191
192impl<DATA: Seek> IsoImage<DATA> {
193 pub fn into_inner(self) -> DATA {
195 self.data.into_inner().data
196 }
197}
198
199io_transform! {
200impl<DATA: Read + Seek> IsoImage<DATA> {
201 pub async fn open(data: DATA) -> io::Result<Self> {
203 let sector_size = 2048;
204 let mut data = IsoCursor::new(data, sector_size);
205 data.seek_sector(LogicalSector(16)).await?;
206 let mut root_dirs = RootDirs::new();
207 let volume_descriptors = VolumeDescriptorList::parse(&mut data).await?;
208 let pvd = volume_descriptors.try_primary().ok_or_else(|| {
209 io::Error::new(
210 io::ErrorKind::InvalidData,
211 "volume descriptor sequence has no primary descriptor",
212 )
213 })?;
214 if !pvd.volume_space_size.is_consistent()
215 || !pvd.volume_set_size.is_consistent()
216 || !pvd.volume_sequence_number.is_consistent()
217 || !pvd.logical_block_size.is_consistent()
218 || !pvd.path_table_size.is_consistent()
219 || !pvd.dir_record.header.extent.is_consistent()
220 || !pvd.dir_record.header.data_len.is_consistent()
221 || !pvd
222 .dir_record
223 .header
224 .volume_sequence_number
225 .is_consistent()
226 {
227 return Err(io::Error::new(
228 io::ErrorKind::InvalidData,
229 "primary volume descriptor has inconsistent redundant endian fields",
230 ));
231 }
232 let block_size = pvd.logical_block_size.read() as usize;
233 if block_size != 2048 {
238 return Err(io::Error::new(
239 io::ErrorKind::Unsupported,
240 "ISO logical block size other than 2048 is not supported by IsoImage",
241 ));
242 }
243 let root_extent = LogicalSector(
244 pvd.dir_record.header.extent.read() as usize
245 + pvd.dir_record.header.extended_attr_record as usize,
246 );
247 let root_size = pvd.dir_record.header.data_len.read() as usize;
248 let root_dir = DirectoryRef {
249 extent: root_extent,
250 size: root_size,
251 };
252
253 let susp_info = rrip::detect_susp_rrip(&mut data, root_extent).await?;
255 let supports_rrip = susp_info.rrip_detected;
256
257 root_dirs.dirs.push(RootDir {
258 ty: EntryType::Level1 {
259 supports_lowercase: false,
260 supports_rrip,
261 },
262 dir_ref: root_dir,
263 });
264
265 let path_table = PathTableRef {
266 lpt: LogicalSector(pvd.type_l_path_table.get() as usize),
267 mpt: LogicalSector(pvd.type_m_path_table.get() as usize),
268 size: pvd.path_table_size.read() as u64,
269 };
270
271 #[cfg(feature = "alloc")]
273 let path_table_cache = {
274 use crate::types::EndianType;
275 let pt_start = if cfg!(target_endian = "little") {
276 path_table.lpt
277 } else {
278 path_table.mpt
279 };
280 let start_byte = pt_start.0 as u64 * sector_size as u64;
281 let end_byte = start_byte + path_table.size;
282 data.seek(super::io::SeekFrom::Start(start_byte))
283 .await
284 .map_err(super::io::Error::erase)?;
285 let mut entries = alloc::vec::Vec::new();
286 let mut pos = start_byte;
287 while pos < end_byte {
288 let entry = PathTableEntry::parse(&mut data, EndianType::NativeEndian).await?;
289 pos += entry.size() as u64;
290 entries.push(entry);
291 }
292 entries
293 };
294
295 let mut info = IsoImageInfo {
296 block_size,
297 sector_size,
298 root_dirs,
299 boot_catalog: None,
300 path_table,
301 susp_info,
302 has_evd: false,
303 #[cfg(feature = "alloc")]
304 path_table_cache,
305 };
306
307 for svd in volume_descriptors.supplementary() {
308 if svd.header.version == 1 {
309 for &level in JolietLevel::all() {
311 if svd.escape_sequences == level.escape_sequence() {
312 info.root_dirs.dirs.push(RootDir {
313 ty: EntryType::Joliet {
314 level,
315 supports_rrip: false,
316 },
317 dir_ref: DirectoryRef {
318 extent: LogicalSector(
319 svd.dir_record.header.extent.read() as usize
320 + svd.dir_record.header.extended_attr_record as usize,
321 ),
322 size: svd.dir_record.header.data_len.read() as usize,
323 },
324 });
325 }
326 }
327 } else if svd.file_structure_version == 2 {
328 info.has_evd = true;
330 info.root_dirs.dirs.push(RootDir {
331 ty: EntryType::Level3 {
332 supports_lowercase: true,
333 supports_rrip: false,
334 },
335 dir_ref: DirectoryRef {
336 extent: LogicalSector(
337 svd.dir_record.header.extent.read() as usize
338 + svd.dir_record.header.extended_attr_record as usize,
339 ),
340 size: svd.dir_record.header.data_len.read() as usize,
341 },
342 });
343 }
344 }
345
346 if let Some(boot_record) = volume_descriptors.boot_record() {
347 info.boot_catalog = Some(boot_record.catalog_ptr.get());
348 }
349
350 Ok(Self {
351 data: Mutex::new(data),
352 info,
353 })
354 }
355
356 pub async fn read_bytes_at(&self, byte_offset: u64, buf: &mut [u8]) -> io::Result<()> {
358 let mut data = self.data.lock();
359 data.seek(super::io::SeekFrom::Start(byte_offset))
360 .await
361 .map_err(super::io::Error::erase)?;
362 data.read_exact(buf).await?;
363 Ok(())
364 }
365
366 pub async fn find_path(&self, path: &str) -> io::Result<Option<DirEntry>> {
372 let mut components = VPath::with_separators(path, Separators::SlashOrBackslash)
373 .components()
374 .filter_map(|component| match component {
375 Component::Root | Component::Current => None,
376 Component::Parent => Some(Err(io::Error::other(
377 "parent path components are not supported",
378 ))),
379 Component::Normal(component) => Some(Ok(component)),
380 })
381 .peekable();
382 let mut directory = self.open_dir(self.root_dir().dir_ref());
383
384 while let Some(component) = components.next() {
385 let component = component?;
386 let Some(entry) = directory.find(component).await? else {
387 return Ok(None);
388 };
389 if components.peek().is_none() {
390 return Ok(Some(entry));
391 }
392 if !entry.is_directory() {
393 return Ok(None);
394 }
395 directory = self.open_dir(entry.as_dir_ref(self).await?);
396 }
397 Ok(None)
398 }
399
400 #[cfg(feature = "alloc")]
406 pub async fn read_file(&self, entry: &directory::DirEntry) -> io::Result<alloc::vec::Vec<u8>> {
407 if entry.header().file_unit_size != 0 || entry.header().interleave_gap_size != 0 {
408 return Err(io::Error::new(
409 io::ErrorKind::Unsupported,
410 "interleaved ISO files are not supported",
411 ));
412 }
413 let total = entry.total_size();
414 let image_len = {
420 let mut data = self.data.lock();
421 data.seek(super::io::SeekFrom::End(0))
422 .await
423 .map_err(super::io::Error::erase)?
424 };
425 if total > image_len {
426 return Err(io::Error::new(
427 io::ErrorKind::InvalidData,
428 "directory entry claims more data than the image contains",
429 ));
430 }
431 let mut buf = alloc::vec![0u8; total as usize];
432
433 if entry.is_multi_extent() {
434 let mut offset = 0usize;
435 for extent in entry.extents() {
436 let len = extent.length as usize;
437 let byte_offset = extent.sector.0 as u64 * 2048;
438 self.read_bytes_at(byte_offset, &mut buf[offset..offset + len]).await?;
439 offset += len;
440 }
441 } else {
442 let header = entry.header();
443 let byte_offset = (header.extent.read() as u64
444 + header.extended_attr_record as u64)
445 * 2048;
446 let len = header.data_len.read() as usize;
447 self.read_bytes_at(byte_offset, &mut buf[..len]).await?;
448 }
449
450 Ok(buf)
451 }
452}
453} impl<DATA: Read + Seek> IsoImage<DATA> {
456 pub fn root_dir(&self) -> RootDir {
458 self.root_dirs().best_choice()
459 }
460
461 pub fn root_dirs(&self) -> &RootDirs {
463 &self.info.root_dirs
464 }
465
466 pub fn supports_rrip(&self) -> bool {
468 self.info.susp_info.rrip_detected
469 }
470
471 pub fn has_evd(&self) -> bool {
473 self.info.has_evd
474 }
475
476 pub fn open_dir(&self, dir_ref: DirectoryRef) -> IsoDir<'_, DATA> {
478 IsoDir {
479 image: self,
480 directory: dir_ref,
481 }
482 }
483
484 pub fn path_table(&self) -> PathTableInfo {
486 PathTableInfo {
487 path_table: self.info.path_table,
488 }
489 }
490
491 pub fn read_volume_descriptors(&self) -> VolumeDescriptorIter<'_, DATA> {
496 VolumeDescriptorIter {
497 data: &self.data,
498 current_sector: LogicalSector(16),
499 done: false,
500 }
501 }
502
503 #[cfg(feature = "alloc")]
508 pub fn path_table_entries(&self) -> &[PathTableEntry] {
509 &self.info.path_table_cache
510 }
511}
512
513io_transform! {
514impl<DATA: Read + Seek> IsoImage<DATA> {
515 pub async fn read_pvd(&self) -> io::Result<PrimaryVolumeDescriptor> {
520 let mut descriptors = self.read_volume_descriptors();
521 while let Some(descriptor) = descriptors.next_descriptor().await? {
522 if let super::volume::VolumeDescriptor::Primary(pvd) = descriptor {
523 return Ok(pvd);
524 }
525 }
526 Err(io::Error::new(
527 io::ErrorKind::InvalidData,
528 "primary volume descriptor not found",
529 ))
530 }
531}
532}