#[repr(u16)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum SquashfsCompressionType { Gzip = 1, Lzma = 2, Lzo = 3, Xz = 4, Lz4 = 5, Zstd = 6, } impl From for SquashfsCompressionType { fn from(value: u16) -> Self { match value { 1 => Self::Gzip, 2 => Self::Lzma, 3 => Self::Lzo, 4 => Self::Xz, 5 => Self::Lz4, 6 => Self::Zstd, _ => panic!("Unexpected Squashfs compression type!"), } } } #[repr(u16)] enum SquashfsFlags { UncompressedInodes = 0x0001, UncompressedDataBlocks = 0x0002, Reserved = 0x0004, UncompressedFragments = 0x0008, UnusedFragments = 0x0010, FragmentsAlwaysPresent = 0x0020, DeduplicatedData = 0x0040, PresentNFSTable = 0x0080, UncompressedXattrs = 0x0100, NoXattrs = 0x0200, PresentCompressorOptions = 0x0400, UncompressedIDTable = 0x0800, } #[allow(dead_code)] #[derive(Debug)] pub struct SquashfsFeatures { pub uncompressed_inodes: bool, pub uncompressed_data_blocks: bool, _reserved: bool, pub uncompressed_fragments: bool, pub unused_fragments: bool, pub fragments_always_present: bool, pub deduplicated_data: bool, pub nfs_table_present: bool, pub uncompressed_xattrs: bool, pub no_xattrs: bool, pub compressor_options_present: bool, pub uncompressed_id_table: bool, } #[repr(C, packed)] #[derive(Clone, Copy, Debug)] pub struct SquashfsSuperblock { magic: u32, // 0x73717368 inode_count: u32, // 0x02 mod_time: u32, // varies pub block_size: u32, // 0x20000 frag_count: u32, // 0x01 compressor: SquashfsCompressionType, // GZIP block_log: u16, // 0x11 flags: u16, // 0xC0 id_count: u16, // 0x01 ver_major: u16, // 0x04 ver_minor: u16, // 0x00 pub root_inode: u64, // bytes_used: u64, // 0x0103 pub id_table: u64, // 0x00FB pub xattr_table: u64, // 0xFFFFFFFFFFFFFFFF pub inode_table: u64, // 0x7B pub dir_table: u64, // 0xA4 pub frag_table: u64, // 0xD5 pub export_table: u64, // 0xED } impl SquashfsSuperblock { pub fn new(bytes: &[u8]) -> Result { let superblock = Self { magic: u32::from_le_bytes(bytes[0..4].try_into().unwrap()), inode_count: u32::from_le_bytes(bytes[4..8].try_into().unwrap()), mod_time: u32::from_le_bytes(bytes[8..12].try_into().unwrap()), block_size: u32::from_le_bytes(bytes[12..16].try_into().unwrap()), frag_count: u32::from_le_bytes(bytes[16..20].try_into().unwrap()), compressor: u16::from_le_bytes(bytes[20..22].try_into().unwrap()).into(), block_log: u16::from_le_bytes(bytes[22..24].try_into().unwrap()), flags: u16::from_le_bytes(bytes[24..26].try_into().unwrap()), id_count: u16::from_le_bytes(bytes[26..28].try_into().unwrap()), ver_major: u16::from_le_bytes(bytes[28..30].try_into().unwrap()), ver_minor: u16::from_le_bytes(bytes[30..32].try_into().unwrap()), root_inode: u64::from_le_bytes(bytes[32..40].try_into().unwrap()), bytes_used: u64::from_le_bytes(bytes[40..48].try_into().unwrap()), id_table: u64::from_le_bytes(bytes[48..56].try_into().unwrap()), xattr_table: u64::from_le_bytes(bytes[56..64].try_into().unwrap()), inode_table: u64::from_le_bytes(bytes[64..72].try_into().unwrap()), dir_table: u64::from_le_bytes(bytes[72..80].try_into().unwrap()), frag_table: u64::from_le_bytes(bytes[80..88].try_into().unwrap()), export_table: u64::from_le_bytes(bytes[88..96].try_into().unwrap()), }; if superblock.magic != 0x73717368 { return Err(()); } if superblock.ver_major != 4 || superblock.ver_minor != 0 { return Err(()); } if superblock.block_size > 1048576 { return Err(()); } if superblock.block_log > 20 { return Err(()); } if superblock.block_size != (1 << superblock.block_log) { return Err(()); } if superblock.block_size == 0 { return Err(()); } if ((superblock.block_size - 1) & superblock.block_size) != 0 { return Err(()); } return Ok(superblock); } pub fn compressor(&self) -> SquashfsCompressionType { self.compressor } pub fn features(&self) -> SquashfsFeatures { let uncompressed_inodes = (self.flags & SquashfsFlags::UncompressedInodes as u16) != 0; let uncompressed_data_blocks = (self.flags & SquashfsFlags::UncompressedDataBlocks as u16) != 0; let _reserved = (self.flags & SquashfsFlags::Reserved as u16) != 0; let uncompressed_fragments = (self.flags & SquashfsFlags::UncompressedFragments as u16) != 0; let unused_fragments = (self.flags & SquashfsFlags::UnusedFragments as u16) != 0; let fragments_always_present = (self.flags & SquashfsFlags::FragmentsAlwaysPresent as u16) != 0; let deduplicated_data = (self.flags & SquashfsFlags::DeduplicatedData as u16) != 0; let nfs_table_present = (self.flags & SquashfsFlags::PresentNFSTable as u16) != 0; let uncompressed_xattrs = (self.flags & SquashfsFlags::UncompressedXattrs as u16) != 0; let no_xattrs = (self.flags & SquashfsFlags::NoXattrs as u16) != 0; let compressor_options_present = (self.flags & SquashfsFlags::PresentCompressorOptions as u16) != 0; let uncompressed_id_table = (self.flags & SquashfsFlags::UncompressedIDTable as u16) != 0; return SquashfsFeatures { uncompressed_inodes, uncompressed_data_blocks, _reserved, uncompressed_fragments, unused_fragments, fragments_always_present, deduplicated_data, nfs_table_present, uncompressed_xattrs, no_xattrs, compressor_options_present, uncompressed_id_table, }; } }