squashfs_async/
superblock.rs

1use serde::Deserialize;
2use serde_repr::Deserialize_repr;
3use tracing::*;
4
5use super::error::DecompressError;
6use super::inodes::InodeRef;
7use super::metadata::MetadataBlock;
8use super::Error;
9
10/// Compression algorithm
11#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize_repr)]
12#[repr(u16)]
13pub enum Compression {
14    Gzip = 1,
15    Lzma,
16    Lzd,
17    Xz,
18    Lz4,
19    Zstd,
20}
21
22bitflags::bitflags! {
23    #[derive(Deserialize)]
24    struct SuperBlockFlags: u16 {
25        const UNCOMPRESSED_INODES = 0x0001;
26        const UNCOMPRESSED_DATA = 0x0002;
27        const CHECK = 0x0004;
28        const UNCOMPRESSED_FRAGMENTS = 0x0008;
29        const NO_FRAGMENTS = 0x0010;
30        const ALWAYS_FRAGMENTS = 0x0020;
31        const DUPLICATES = 0x0040;
32        const EXPORTABLE = 0x0080;
33        const UNCOMPRESSED_XATTRS = 0x0100;
34        const NO_XATTRS = 0x0200;
35        const COMPRESSOR_OPTIONS = 0x0400;
36        const UNCOMPRESSED_IDS = 0x0800;
37    }
38}
39#[derive(Debug)]
40pub enum CompressionOptions {
41    Zstd,
42    Gzip,
43    Xz,
44}
45impl CompressionOptions {
46    fn from_metadata(compression: Compression, block: MetadataBlock) -> Result<Self, Error> {
47        match compression {
48            Compression::Zstd => {
49                if block.compressed_size != 4 {
50                    return Err(Error::InvalidBufferSize);
51                }
52                Ok(Self::Zstd)
53            }
54            Compression::Gzip => {
55                if block.compressed_size != 8 {
56                    return Err(Error::InvalidBufferSize);
57                }
58                Ok(Self::Gzip)
59            }
60            Compression::Xz => {
61                if block.compressed_size != 8 {
62                    return Err(Error::InvalidBufferSize);
63                }
64                Ok(Self::Xz)
65            }
66            // TODO: Other compression schemes
67            _ => Err(DecompressError::UnsupportedCompression(compression).into()),
68        }
69    }
70}
71/// Superblock, containing archive metadata.
72///
73/// See <https://dr-emann.github.io/squashfs/squashfs.html#_the_superblock>
74#[derive(Debug, Deserialize)]
75pub struct SuperBlock {
76    magic: u32,
77    pub inode_count: u32,
78    _modification_time: u32,
79    pub block_size: u32,
80    pub fragment_entry_count: u32,
81    pub compression: Compression,
82    _block_log: u16,
83    flags: SuperBlockFlags,
84    _id_lookupcount: u16,
85    version_major: u16,
86    version_minor: u16,
87    pub root_inode: InodeRef,
88    /// Without padding
89    pub bytes_used: u64,
90    _id_table_start: u64,
91    _xattr_id_table_start: u64,
92    pub inode_table_start: u64,
93    pub directory_table_start: u64,
94    pub fragment_table_start: u64,
95    _export_table_start: u64,
96    #[serde(skip)]
97    pub compression_options: Option<CompressionOptions>,
98}
99impl SuperBlock {
100    pub async fn from_reader(mut r: impl crate::AsyncSeekBufRead) -> Result<Self, Error> {
101        debug!("Reading superblock");
102        let mut superblock: Self = super::deser::bincode_deser_from(&mut r, 96)
103            .await
104            .map_err(|_| Error::InvalidSuperblock)?;
105
106        if superblock.magic != 0x73717368
107            || superblock.version_major != 4
108            || superblock.version_minor != 0
109        {
110            return Err(Error::InvalidSuperblock);
111        }
112        if superblock
113            .flags
114            .contains(SuperBlockFlags::COMPRESSOR_OPTIONS)
115        {
116            let block = MetadataBlock::from_reader(&mut r, superblock.compression).await?;
117            superblock.compression_options = Some(CompressionOptions::from_metadata(
118                superblock.compression,
119                block,
120            )?);
121        }
122        debug!("{:?}", superblock);
123        Ok(superblock)
124    }
125    pub fn tables_length(&self) -> u64 {
126        self.bytes_used - self.inode_table_start
127    }
128}