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