Skip to main content

backhand/v3/
reader.rs

1//! Reader traits
2
3use no_std_io2::io::{Read, Seek};
4use std::collections::HashMap;
5use std::io::{BufRead, Cursor, SeekFrom, Write};
6
7use deku::prelude::*;
8use solana_nohash_hasher::IntMap;
9
10use super::export::Export;
11use super::fragment::Fragment;
12use super::inode::Inode;
13use super::metadata::METADATA_MAXSIZE;
14use super::squashfs::SuperBlock;
15use super::{fragment, metadata};
16use crate::error::BackhandError;
17use crate::kinds::Kind;
18
19/// Private struct containing logic to read the `Squashfs` section from a file
20#[derive(Debug)]
21pub(crate) struct SquashfsReaderWithOffset<R: BufReadSeek> {
22    io: R,
23    /// Offset from start of file to squashfs
24    offset: u64,
25}
26
27impl<R: BufReadSeek> SquashfsReaderWithOffset<R> {
28    pub fn new(mut io: R, offset: u64) -> std::io::Result<Self> {
29        io.seek(SeekFrom::Start(offset))?;
30        Ok(Self { io, offset })
31    }
32}
33
34impl<R: BufReadSeek> BufRead for SquashfsReaderWithOffset<R> {
35    fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
36        self.io.fill_buf()
37    }
38
39    fn consume(&mut self, amt: usize) {
40        self.io.consume(amt)
41    }
42}
43
44impl<R: BufReadSeek> Read for SquashfsReaderWithOffset<R> {
45    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
46        self.io.read(buf)
47    }
48}
49
50impl<R: BufReadSeek> Seek for SquashfsReaderWithOffset<R> {
51    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
52        let seek = match pos {
53            SeekFrom::Start(start) => SeekFrom::Start(self.offset + start),
54            seek => seek,
55        };
56        self.io.seek(seek).map(|x| x - self.offset)
57    }
58}
59
60/// Pseudo-Trait for BufRead + Seek
61pub trait BufReadSeek: BufRead + Seek + Send {}
62impl<T: BufRead + Seek + Send> BufReadSeek for T {}
63
64/// Pseudo-Trait for Write + Seek
65pub trait WriteSeek: Write + Seek {}
66impl<T: Write + Seek> WriteSeek for T {}
67
68impl<T: BufReadSeek> SquashFsReader for T {}
69
70/// Squashfs data extraction methods implemented over [`Read`] and [`Seek`]
71pub trait SquashFsReader: BufReadSeek {
72    /// Parse Inode Table into `Vec<(position_read, Inode)>`
73    fn inodes(
74        &mut self,
75        superblock: &SuperBlock,
76        kind: &Kind,
77    ) -> Result<IntMap<u32, Inode>, BackhandError> {
78        self.seek(SeekFrom::Start(superblock.inode_table_start))?;
79
80        // The directory inodes store the total, uncompressed size of the entire listing, including headers.
81        // Using this size, a SquashFS reader can determine if another header with further entries
82        // should be following once it reaches the end of a run.
83
84        let mut next = vec![];
85
86        let mut metadata_offsets = vec![];
87        let mut ret_vec = HashMap::default();
88        let start = self.stream_position()?;
89
90        while self.stream_position()? < superblock.directory_table_start {
91            metadata_offsets.push(self.stream_position()? - start);
92            // parse into metadata
93            let mut bytes = metadata::read_block(self, superblock, kind)?;
94
95            // parse as many inodes as you can
96            let mut inode_bytes = next;
97            inode_bytes.append(&mut bytes);
98            let mut c_inode_bytes = Cursor::new(inode_bytes.clone());
99            let mut container = Reader::new(&mut c_inode_bytes);
100
101            // store last successful read position
102            let mut container_bits_read = container.bits_read;
103            loop {
104                match Inode::from_reader_with_ctx(
105                    &mut container,
106                    (
107                        superblock.bytes_used,
108                        superblock.block_size,
109                        superblock.block_log,
110                        kind.inner.type_endian,
111                        kind.inner.bit_order.unwrap(),
112                    ),
113                ) {
114                    Ok(inode) => {
115                        ret_vec.insert(inode.header.inode_number, inode);
116                        container_bits_read = container.bits_read;
117                    }
118                    Err(e) => {
119                        if matches!(e, DekuError::Incomplete(_)) {
120                            // try next block, inodes can span multiple blocks!
121                            next = inode_bytes.clone()[(container_bits_read / 8)..].to_vec();
122                            break;
123                        } else {
124                            error!("Fatal error parsing inode: {:?}", e);
125                            return Err(BackhandError::Deku(e));
126                        }
127                    }
128                }
129            }
130        }
131
132        if ret_vec.len() != superblock.inode_count.try_into().unwrap() {
133            error!("Parsed {} inodes, expected {}", ret_vec.len(), superblock.inode_count);
134            return Err(BackhandError::CorruptedOrInvalidSquashfs);
135        }
136
137        Ok(ret_vec)
138    }
139
140    /// Extract the root `Inode` as a `BasicDirectory`
141    fn root_inode(&mut self, superblock: &SuperBlock, kind: &Kind) -> Result<Inode, BackhandError> {
142        let root_inode_start = (superblock.root_inode >> 16) as usize;
143        let root_inode_offset = (superblock.root_inode & 0xffff) as usize;
144        info!("root_inode_start:  0x{root_inode_start:02x?}");
145        info!("root_inode_offset: 0x{root_inode_offset:02x?}");
146        if (root_inode_start as u64) > superblock.bytes_used {
147            error!("root_inode_offset > bytes_used");
148            return Err(BackhandError::CorruptedOrInvalidSquashfs);
149        }
150
151        // Assumptions are made here that the root inode fits within two metadatas
152        let seek = superblock.inode_table_start + root_inode_start as u64;
153        self.seek(SeekFrom::Start(seek))?;
154        let mut bytes_01 = metadata::read_block(self, superblock, kind)?;
155
156        // try reading just one metdata block
157        if root_inode_offset > bytes_01.len() {
158            error!("root_inode_offset > bytes.len()");
159            return Err(BackhandError::CorruptedOrInvalidSquashfs);
160        }
161        let mut cursor = Cursor::new(&bytes_01[root_inode_offset..]);
162        let mut new_bytes = Reader::new(&mut cursor);
163        if let Ok(inode) = Inode::from_reader_with_ctx(
164            &mut new_bytes,
165            (
166                superblock.bytes_used,
167                superblock.block_size,
168                superblock.block_log,
169                kind.inner.type_endian,
170                kind.inner.bit_order.unwrap(),
171            ),
172        ) {
173            info!("ROOT: {:?}", inode);
174            return Ok(inode);
175        }
176
177        // if that doesn't work, we need another block
178        let bytes_02 = metadata::read_block(self, superblock, kind)?;
179        bytes_01.write_all(&bytes_02)?;
180        if root_inode_offset > bytes_01.len() {
181            error!("root_inode_offset > bytes.len()");
182            return Err(BackhandError::CorruptedOrInvalidSquashfs);
183        }
184
185        let mut cursor = Cursor::new(&bytes_01[root_inode_offset..]);
186        let mut new_bytes = Reader::new(&mut cursor);
187        match Inode::from_reader_with_ctx(
188            &mut new_bytes,
189            (
190                superblock.bytes_used,
191                superblock.block_size,
192                superblock.block_log,
193                kind.inner.type_endian,
194                kind.inner.bit_order.unwrap(),
195            ),
196        ) {
197            Ok(inode) => Ok(inode),
198            Err(e) => Err(e.into()),
199        }
200    }
201
202    /// Parse required number of `Metadata`s uncompressed blocks required for `Dir`s
203    fn dir_blocks(
204        &mut self,
205        superblock: &SuperBlock,
206        end_ptr: u64,
207        kind: &Kind,
208    ) -> Result<(IntMap<u64, u64>, Vec<u8>), BackhandError> {
209        let seek = superblock.directory_table_start;
210        self.seek(SeekFrom::Start(seek))?;
211        let mut block_map = IntMap::default();
212        let mut dir_data = Vec::new();
213
214        while self.stream_position()? != end_ptr {
215            let metadata_start = self.stream_position()?;
216            let bytes = metadata::read_block(self, superblock, kind)?;
217            let compressed_offset = metadata_start - seek;
218            let decompressed_offset = dir_data.len() as u64;
219            block_map.insert(compressed_offset, decompressed_offset);
220            dir_data.extend(bytes);
221        }
222
223        Ok((block_map, dir_data))
224    }
225
226    /// Parse Fragment Table
227    fn fragments(
228        &mut self,
229        superblock: &SuperBlock,
230        kind: &Kind,
231    ) -> Result<Option<(u64, Vec<Fragment>)>, BackhandError> {
232        let (ptr, table) = self.fragment_lookup_table(
233            superblock,
234            superblock.fragment_table_start,
235            u64::from(superblock.fragments) * fragment::SIZE as u64,
236            kind,
237        )?;
238        trace!("{:02x?}", table);
239        Ok(Some((ptr, table)))
240    }
241
242    /// Parse Export Table
243    fn export(
244        &mut self,
245        _superblock: &SuperBlock,
246        _kind: &Kind,
247    ) -> Result<Option<(u64, Vec<Export>)>, BackhandError> {
248        Ok(None)
249    }
250
251    /// Parse UID Table
252    fn uid(&mut self, superblock: &SuperBlock, kind: &Kind) -> Result<Vec<u16>, BackhandError> {
253        let ptr = superblock.uid_start;
254        let count = superblock.no_uids as u64;
255        self.seek(SeekFrom::Start(ptr))?;
256
257        // I wish self was Read here, but this works
258        let mut buf = vec![0u8; count as usize * core::mem::size_of::<u16>()];
259        self.read_exact(&mut buf)?;
260
261        let mut cursor = Cursor::new(buf);
262        let mut deku_reader = Reader::new(&mut cursor);
263        let mut table = Vec::with_capacity(count as usize);
264        for _ in 0..count {
265            let v = u16::from_reader_with_ctx(
266                &mut deku_reader,
267                (kind.inner.type_endian, kind.inner.bit_order.unwrap()),
268            )?;
269            table.push(v);
270        }
271
272        Ok(table)
273    }
274
275    /// Parse GUID Table
276    fn guid(&mut self, superblock: &SuperBlock, kind: &Kind) -> Result<Vec<u16>, BackhandError> {
277        let ptr = superblock.guid_start;
278        let count = superblock.no_guids as u64;
279        self.seek(SeekFrom::Start(ptr))?;
280
281        // I wish self was Read here, but this works
282        let mut buf = vec![0u8; count as usize * core::mem::size_of::<u16>()];
283        self.read_exact(&mut buf)?;
284
285        let mut cursor = Cursor::new(buf);
286        let mut deku_reader = Reader::new(&mut cursor);
287        let mut table = Vec::with_capacity(count as usize);
288        for _ in 0..count {
289            let v = u16::from_reader_with_ctx(
290                &mut deku_reader,
291                (kind.inner.type_endian, kind.inner.bit_order.unwrap()),
292            )?;
293            table.push(v);
294        }
295
296        Ok(table)
297    }
298
299    /// Parse Fragment Lookup Table (specialized for Fragment context)
300    fn fragment_lookup_table(
301        &mut self,
302        superblock: &SuperBlock,
303        seek: u64,
304        size: u64,
305        kind: &Kind,
306    ) -> Result<(u64, Vec<Fragment>), BackhandError> {
307        trace!(
308            "fragment_lookup_table: seek=0x{:x}, size={}, fragments={}",
309            seek, size, superblock.fragments
310        );
311
312        // V3 fragment table parsing follows the same pattern as v4:
313        // 1. Read index table that points to metadata blocks
314        // 2. Read metadata blocks to get fragment entries
315
316        // Calculate number of metadata blocks needed
317        let fragment_count = superblock.fragments as u64;
318        let fragment_bytes = fragment_count * fragment::SIZE as u64;
319        let metadata_block_count = fragment_bytes.div_ceil(METADATA_MAXSIZE as u64);
320
321        trace!(
322            "fragment_lookup_table: {} fragments need {} metadata blocks",
323            fragment_count, metadata_block_count
324        );
325
326        // Read the index table (pointers to metadata blocks)
327        self.seek(SeekFrom::Start(seek))?;
328        let index_size = metadata_block_count * core::mem::size_of::<u64>() as u64;
329        let mut index_buf = vec![0u8; index_size as usize];
330        self.read_exact(&mut index_buf)?;
331
332        // Parse the index table
333        let mut index_ptrs = vec![];
334        let mut cursor = Cursor::new(&index_buf);
335        let mut reader = Reader::new(&mut cursor);
336
337        for i in 0..metadata_block_count {
338            let ptr = u64::from_reader_with_ctx(
339                &mut reader,
340                (kind.inner.type_endian, kind.inner.bit_order.unwrap()),
341            )?;
342            trace!("Fragment metadata block {}: pointer 0x{:x}", i, ptr);
343            index_ptrs.push(ptr);
344        }
345
346        // Read fragments from metadata blocks
347        let mut ret_vec = vec![];
348        let mut fragments_read = 0;
349
350        for (i, &ptr) in index_ptrs.iter().enumerate() {
351            if fragments_read >= fragment_count {
352                break;
353            }
354
355            let fragments_in_this_block = core::cmp::min(
356                fragment_count - fragments_read,
357                METADATA_MAXSIZE as u64 / fragment::SIZE as u64,
358            );
359
360            trace!(
361                "Reading {} fragments from metadata block {} at 0x{:x}",
362                fragments_in_this_block, i, ptr
363            );
364
365            self.seek(SeekFrom::Start(ptr))?;
366            let block_fragments = self.fragment_metadata_with_count(superblock, ptr, 1, kind)?;
367
368            // Only take the fragments we need
369            let take_count =
370                core::cmp::min(block_fragments.len(), fragments_in_this_block as usize);
371            ret_vec.extend_from_slice(&block_fragments[..take_count]);
372            fragments_read += take_count as u64;
373        }
374
375        trace!("fragment_lookup_table: successfully read {} fragments", ret_vec.len());
376        Ok((seek, ret_vec))
377    }
378
379    /// Parse count of Fragment `Metadata` blocks
380    fn fragment_metadata_with_count(
381        &mut self,
382        superblock: &SuperBlock,
383        seek: u64,
384        count: u64,
385        kind: &Kind,
386    ) -> Result<Vec<Fragment>, BackhandError> {
387        trace!("fragment_metadata_with_count: seek=0x{:02x}, count={}", seek, count);
388        self.seek(SeekFrom::Start(seek))?;
389
390        let mut all_bytes = vec![];
391        for i in 0..count {
392            let pos_before = self.stream_position()?;
393            let mut bytes = metadata::read_block(self, superblock, kind)?;
394            let pos_after = self.stream_position()?;
395            trace!(
396                "fragment metadata block {}: pos 0x{:x} -> 0x{:x}, read {} decompressed bytes, first 20: {:02x?}",
397                i,
398                pos_before,
399                pos_after,
400                bytes.len(),
401                &bytes[..core::cmp::min(20, bytes.len())]
402            );
403            all_bytes.append(&mut bytes);
404        }
405
406        trace!(
407            "fragment_metadata_with_count: total decompressed bytes: {}, content: {:02x?}",
408            all_bytes.len(),
409            &all_bytes[..core::cmp::min(50, all_bytes.len())]
410        );
411
412        let mut ret_vec = vec![];
413        // Read until we fail to turn bytes into Fragment
414        let mut cursor = Cursor::new(&all_bytes);
415        let mut container = Reader::new(&mut cursor);
416        loop {
417            match Fragment::from_reader_with_ctx(
418                &mut container,
419                (kind.inner.type_endian, kind.inner.bit_order.unwrap()),
420            ) {
421                Ok(t) => {
422                    trace!("Parsed fragment: {:?}", t);
423                    ret_vec.push(t);
424                }
425                Err(e) => {
426                    trace!("Failed to parse more fragments: {:?}", e);
427                    break;
428                }
429            }
430        }
431
432        Ok(ret_vec)
433    }
434}