Skip to main content

hadris_iso/
path.rs

1use hadris_fixed::FixedBytes;
2
3use super::io::{self, Error, LogicalSector, Read, Write};
4use crate::types::EndianType;
5
6sync_only! {
7#[cfg(feature = "alloc")]
8use core::ops::DerefMut;
9#[cfg(feature = "alloc")]
10use spin::Mutex;
11#[cfg(feature = "alloc")]
12use super::io::{Seek, SeekFrom};
13#[cfg(feature = "alloc")]
14use super::read::IsoImage;
15}
16
17/// Path Table record header (ECMA-119 9.4).
18///
19/// @hadris-spec ECMA-119:9.4
20/// @hadris-compliance partial
21/// @hadris-note Both L- and M-type path tables are written and read; the optional secondary path tables are not populated.
22/// @hadris-fuzz iso_read
23#[repr(C)]
24#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
25pub struct PathTableEntryHeader {
26    /// The `len` field.
27    pub len: u8,
28    /// The `extended_attr_record` field.
29    pub extended_attr_record: u8,
30    /// The `parent_lba` field.
31    pub parent_lba: [u8; 4],
32    /// The `parent_directory_number` field.
33    pub parent_directory_number: [u8; 2],
34}
35
36impl PathTableEntryHeader {
37    /// Performs the `from_bytes` operation.
38    pub fn from_bytes(bytes: &[u8]) -> Self {
39        *bytemuck::from_bytes(bytes)
40    }
41}
42
43#[derive(Debug, Clone)]
44/// Represents PathTableEntry.
45pub struct PathTableEntry<const N: usize = 256> {
46    /// The `length` field.
47    pub length: u8,
48    /// The `extended_attr_record` field.
49    pub extended_attr_record: u8,
50    /// The `parent_lba` field.
51    pub parent_lba: u32,
52    /// The `parent_index` field.
53    pub parent_index: u16,
54    /// The `name` field.
55    pub name: FixedBytes<N>,
56}
57
58impl PathTableEntry {
59    /// Performs the `size` operation.
60    pub fn size(&self) -> usize {
61        (size_of::<PathTableEntryHeader>() + self.name.len() + 1) & !1
62    }
63}
64
65io_transform! {
66impl PathTableEntry {
67    /// Performs the `parse` operation.
68    pub async fn parse<T: Read>(reader: &mut T, endian: EndianType) -> Result<Self, Error> {
69        let mut buf = [0; size_of::<PathTableEntryHeader>()];
70        reader.read_exact(&mut buf).await?;
71        let header = PathTableEntryHeader::from_bytes(&buf);
72        let mut name = FixedBytes::with_size(header.len as usize);
73        reader.read_exact(name.as_bytes_mut()).await?;
74        if header.len % 2 == 1 {
75            // Read the padding byte
76            reader.read_exact(&mut [0]).await?;
77        }
78
79        Ok(Self {
80            length: header.len,
81            extended_attr_record: header.extended_attr_record,
82            parent_lba: endian.read_u32(header.parent_lba),
83            parent_index: endian.read_u16(header.parent_directory_number),
84            name,
85        })
86    }
87
88    /// Performs the `write` operation.
89    pub async fn write<W: Write>(&self, writer: &mut W, endian: EndianType) -> io::Result<()> {
90        let header = PathTableEntryHeader {
91            len: self.name.len() as u8,
92            extended_attr_record: 0,
93            parent_lba: endian.u32_bytes(self.parent_lba),
94            parent_directory_number: endian.u16_bytes(self.parent_index),
95        };
96        writer.write_all(bytemuck::bytes_of(&header)).await?;
97        writer.write_all(self.name.as_bytes()).await?;
98        assert_eq!(header.len as usize, self.name.len());
99        if header.len % 2 == 1 {
100            writer.write_all(&[0]).await?;
101        }
102        Ok(())
103    }
104}
105} // io_transform!
106
107#[derive(Debug, Clone, Copy)]
108/// Represents PathTableRef.
109pub struct PathTableRef {
110    pub(crate) lpt: LogicalSector,
111    pub(crate) mpt: LogicalSector,
112    pub(crate) size: u64,
113}
114
115/// Path table information (requires alloc for iterator support)
116#[cfg(feature = "alloc")]
117pub struct PathTableInfo {
118    pub(crate) path_table: PathTableRef,
119}
120
121#[cfg(feature = "alloc")]
122impl PathTableInfo {
123    /// Returns the underlying path-table locations and encoded length.
124    pub fn reference(&self) -> &PathTableRef {
125        &self.path_table
126    }
127}
128
129impl PathTableRef {
130    /// Returns the little-endian path-table sector.
131    pub fn little_endian_sector(&self) -> LogicalSector {
132        self.lpt
133    }
134
135    /// Returns the big-endian path-table sector.
136    pub fn big_endian_sector(&self) -> LogicalSector {
137        self.mpt
138    }
139
140    /// Returns the encoded path-table length in bytes.
141    pub fn len(&self) -> u64 {
142        self.size
143    }
144
145    /// Returns whether the encoded path table is empty.
146    pub fn is_empty(&self) -> bool {
147        self.size == 0
148    }
149}
150
151sync_only! {
152#[cfg(feature = "alloc")]
153impl PathTableInfo {
154    /// Performs the `entries` operation.
155    pub fn entries<'a, DATA: Read + Seek>(
156        &self,
157        image: &'a IsoImage<DATA>,
158    ) -> PathTableEntryIter<'a, DATA> {
159        let start = if cfg!(target_endian = "little") {
160            self.path_table.lpt
161        } else {
162            self.path_table.mpt
163        };
164        // Path table starts at the given sector, convert to byte offset
165        let start_byte = (start.0 as u64) * 2048;
166        let end_byte = start_byte + self.path_table.size;
167        PathTableEntryIter {
168            data: &image.data,
169            current: start_byte,
170            end: end_byte,
171        }
172    }
173}
174
175#[cfg(feature = "alloc")]
176/// Represents PathTableEntryIter.
177pub struct PathTableEntryIter<'a, DATA: Read + Seek> {
178    data: &'a Mutex<super::io::IsoCursor<DATA>>,
179    current: u64,
180    end: u64,
181}
182
183#[cfg(feature = "alloc")]
184impl<DATA: Read + Seek> Iterator for PathTableEntryIter<'_, DATA> {
185    type Item = io::Result<PathTableEntry>;
186
187    /// Undefined if continued reading after IO error
188    fn next(&mut self) -> Option<Self::Item> {
189        use super::io::try_io_result_option as try_io;
190        if self.current >= self.end {
191            return None;
192        }
193        let mut data = self.data.lock();
194        try_io!(data
195            .seek(SeekFrom::Start(self.current))
196            .map_err(Error::erase));
197        let entry = try_io!(PathTableEntry::parse(
198            data.deref_mut(),
199            EndianType::NativeEndian,
200        ));
201        self.current += entry.size() as u64;
202
203        Some(Ok(entry))
204    }
205}
206} // sync_only!