backhand/filesystem/
reader.rs

1use std::sync::{Mutex, RwLock};
2
3use super::node::Nodes;
4use crate::compressor::{CompressionOptions, Compressor};
5use crate::data::DataSize;
6use crate::error::BackhandError;
7use crate::fragment::Fragment;
8use crate::id::Id;
9use crate::kinds::Kind;
10use crate::reader::BufReadSeek;
11use crate::squashfs::Cache;
12use crate::{Node, Squashfs, SquashfsFileReader};
13
14#[cfg(not(feature = "parallel"))]
15use crate::filesystem::reader_no_parallel::{SquashfsRawData, SquashfsReadFile};
16#[cfg(feature = "parallel")]
17use crate::filesystem::reader_parallel::{SquashfsRawData, SquashfsReadFile};
18
19/// Representation of SquashFS filesystem after read from image
20/// - Use [`Self::from_reader`] to read into `Self` from a `reader`
21///
22/// # Read direct into [`Self`]
23/// Usual workflow, reading from image into a default squashfs [`Self`]. See [InnerNode] for more
24/// details for `.nodes`.
25/// ```rust,no_run
26/// # use std::fs::File;
27/// # use std::io::BufReader;
28/// # use backhand::{
29/// #     FilesystemReader, InnerNode, Squashfs, SquashfsBlockDevice, SquashfsCharacterDevice,
30/// #     SquashfsDir, SquashfsSymlink,
31/// # };
32/// // Read into filesystem
33/// let file = BufReader::new(File::open("image.squashfs").unwrap());
34/// let filesystem = FilesystemReader::from_reader(file).unwrap();
35///
36/// // Iterate through nodes
37/// // (See src/bin/unsquashfs.rs for more examples on extraction)
38/// for node in filesystem.files() {
39///     // extract
40///     match &node.inner {
41///         InnerNode::File(_) => (),
42///         InnerNode::Symlink(_) => (),
43///         InnerNode::Dir(_) => (),
44///         InnerNode::CharacterDevice(_) => (),
45///         InnerNode::BlockDevice(_) => (),
46///         InnerNode::NamedPipe => (),
47///         InnerNode::Socket => (),
48///     }
49/// }
50/// ```
51///
52/// # Read from [`Squashfs`]
53/// Performance wise, you may want to read into a [`Squashfs`] first, if for instance you are
54/// optionally not extracting and only listing some Superblock fields.
55/// ```rust,no_run
56/// # use std::fs::File;
57/// # use std::io::BufReader;
58/// # use backhand::{
59/// #     FilesystemReader, InnerNode, Squashfs, SquashfsBlockDevice, SquashfsCharacterDevice,
60/// #     SquashfsDir, SquashfsSymlink,
61/// # };
62/// // Read into Squashfs
63/// let file = BufReader::new(File::open("image.squashfs").unwrap());
64/// let squashfs = Squashfs::from_reader_with_offset(file, 0).unwrap();
65///
66/// // Display the Superblock info
67/// let superblock = squashfs.superblock;
68/// println!("{superblock:#08x?}");
69///
70/// // Now read into filesystem
71/// let filesystem = squashfs.into_filesystem_reader().unwrap();
72/// ```
73/// [InnerNode]: [`crate::InnerNode`]
74pub struct FilesystemReader<'b> {
75    pub kind: Kind,
76    /// The size of a data block in bytes. Must be a power of two between 4096 (4k) and 1048576 (1 MiB).
77    pub block_size: u32,
78    /// The log2 of the block size. If the two fields do not agree, the archive is considered corrupted.
79    pub block_log: u16,
80    /// Compressor used for data
81    pub compressor: Compressor,
82    /// Optional Compressor used for data stored in image
83    pub compression_options: Option<CompressionOptions>,
84    /// Last modification time of the archive. Count seconds since 00:00, Jan 1st 1970 UTC (not counting leap seconds).
85    /// This is unsigned, so it expires in the year 2106 (as opposed to 2038).
86    pub mod_time: u32,
87    /// ID's stored for gui(s) and uid(s)
88    pub id_table: Vec<Id>,
89    /// Fragments Lookup Table
90    pub fragments: Option<Vec<Fragment>>,
91    /// All files and directories in filesystem
92    pub root: Nodes<SquashfsFileReader>,
93    /// File reader
94    pub(crate) reader: Mutex<Box<dyn BufReadSeek + 'b>>,
95    /// Cache used in the decompression
96    pub(crate) cache: RwLock<Cache>,
97    /// Superblock Flag to remove duplicate flags
98    pub(crate) no_duplicate_files: bool,
99}
100
101impl<'b> FilesystemReader<'b> {
102    /// Call [`Squashfs::from_reader`], then [`Squashfs::into_filesystem_reader`]
103    ///
104    /// With default kind: [`crate::kind::LE_V4_0`] and offset `0`.
105    pub fn from_reader<R>(reader: R) -> Result<Self, BackhandError>
106    where
107        R: BufReadSeek + 'b,
108    {
109        let squashfs = Squashfs::from_reader_with_offset(reader, 0)?;
110        squashfs.into_filesystem_reader()
111    }
112
113    /// Same as [`Self::from_reader`], but seek'ing to `offset` in `reader` before reading
114    pub fn from_reader_with_offset<R>(reader: R, offset: u64) -> Result<Self, BackhandError>
115    where
116        R: BufReadSeek + 'b,
117    {
118        let squashfs = Squashfs::from_reader_with_offset(reader, offset)?;
119        squashfs.into_filesystem_reader()
120    }
121
122    /// Same as [`Self::from_reader_with_offset`], but setting custom `kind`
123    pub fn from_reader_with_offset_and_kind<R>(
124        reader: R,
125        offset: u64,
126        kind: Kind,
127    ) -> Result<Self, BackhandError>
128    where
129        R: BufReadSeek + 'b,
130    {
131        let squashfs = Squashfs::from_reader_with_offset_and_kind(reader, offset, kind)?;
132        squashfs.into_filesystem_reader()
133    }
134
135    /// Return a file handler for this file
136    pub fn file<'a>(&'a self, file: &'a SquashfsFileReader) -> FilesystemReaderFile<'a, 'b> {
137        FilesystemReaderFile::new(self, file)
138    }
139
140    /// Iterator of all files, including the root
141    ///
142    /// # Example
143    /// Used when extracting a file from the image, for example using [`FilesystemReaderFile`]:
144    /// ```rust,no_run
145    /// # use std::fs::File;
146    /// # use std::io::BufReader;
147    /// # use backhand::{
148    /// #     FilesystemReader, InnerNode, Squashfs, SquashfsBlockDevice, SquashfsCharacterDevice,
149    /// #     SquashfsDir, SquashfsSymlink,
150    /// # };
151    /// # let file = BufReader::new(File::open("image.squashfs").unwrap());
152    /// # let filesystem = FilesystemReader::from_reader(file).unwrap();
153    /// // [snip: creating FilesystemReader]
154    ///
155    /// for node in filesystem.files() {
156    ///     // extract
157    ///     match &node.inner {
158    ///         InnerNode::File(file) => {
159    ///             let mut reader = filesystem
160    ///                 .file(&file)
161    ///                 .reader();
162    ///             // Then, do something with the reader
163    ///         },
164    ///         _ => (),
165    ///     }
166    /// }
167    /// ```
168    pub fn files(&self) -> impl Iterator<Item = &Node<SquashfsFileReader>> {
169        self.root.nodes.iter()
170    }
171}
172
173/// Filesystem handle for file
174#[derive(Copy, Clone)]
175pub struct FilesystemReaderFile<'a, 'b> {
176    pub(crate) system: &'a FilesystemReader<'b>,
177    pub(crate) file: &'a SquashfsFileReader,
178}
179
180impl<'a, 'b> FilesystemReaderFile<'a, 'b> {
181    pub fn new(system: &'a FilesystemReader<'b>, file: &'a SquashfsFileReader) -> Self {
182        Self { system, file }
183    }
184
185    /// Create [`SquashfsReadFile`] that impls [`std::io::Read`] from [`FilesystemReaderFile`].
186    /// This can be used to then call functions from [`std::io::Read`]
187    /// to de-compress and read the data from this file.
188    ///
189    /// [Read::read]: std::io::Read::read
190    /// [Vec::clear]: Vec::clear
191    pub fn reader(&self) -> SquashfsReadFile<'a, 'b> {
192        self.raw_data_reader().into_reader()
193    }
194
195    pub fn fragment(&self) -> Option<&'a Fragment> {
196        if self.file.frag_index() == 0xffffffff {
197            None
198        } else {
199            self.system.fragments.as_ref().map(|fragments| &fragments[self.file.frag_index()])
200        }
201    }
202
203    pub(crate) fn raw_data_reader(&self) -> SquashfsRawData<'a, 'b> {
204        SquashfsRawData::new(Self { system: self.system, file: self.file })
205    }
206}
207
208impl<'a> IntoIterator for FilesystemReaderFile<'a, '_> {
209    type IntoIter = BlockIterator<'a>;
210    type Item = <BlockIterator<'a> as Iterator>::Item;
211
212    fn into_iter(self) -> Self::IntoIter {
213        BlockIterator { blocks: self.file.block_sizes(), fragment: self.fragment() }
214    }
215}
216
217pub enum BlockFragment<'a> {
218    Block(&'a DataSize),
219    Fragment(&'a Fragment),
220}
221
222pub struct BlockIterator<'a> {
223    pub blocks: &'a [DataSize],
224    pub fragment: Option<&'a Fragment>,
225}
226
227impl<'a> Iterator for BlockIterator<'a> {
228    type Item = BlockFragment<'a>;
229
230    fn next(&mut self) -> Option<Self::Item> {
231        self.blocks
232            .split_first()
233            .map(|(first, rest)| {
234                self.blocks = rest;
235                BlockFragment::Block(first)
236            })
237            .or_else(|| self.fragment.take().map(BlockFragment::Fragment))
238    }
239}