squashfs_async/inodes/
mod.rs

1//! Inode table.
2//!
3//! See <https://dr-emann.github.io/squashfs/squashfs.html#_inode_table>
4mod file;
5pub use file::FileInode;
6use file::FileInodeDeser;
7use file::{BasicFile, ExtendedFile};
8mod directory;
9use directory::{BasicDirectory, ExtendedDirectory};
10pub use directory::{DirectoryInode, DirectoryTableLocation};
11mod symlink;
12
13use std::collections::BTreeMap;
14use std::io::SeekFrom;
15
16use deser::from_reader;
17use serde::Deserialize;
18use serde_repr::Deserialize_repr;
19use tokio::io::{AsyncReadExt, AsyncSeekExt};
20use tracing::*;
21
22use super::deser;
23use super::error::InodeTableError;
24use super::metadata::MetadataBlock;
25use super::superblock::SuperBlock;
26
27/// Reference to an inode, encoding block start and offset.
28#[derive(Debug, Copy, Clone, Deserialize)]
29pub struct InodeRef(u64);
30impl InodeRef {
31    fn block_start(&self) -> u64 {
32        self.0 >> 16
33    }
34    fn block_offset(&self) -> u64 {
35        self.0 & 0xFFFF
36    }
37}
38#[cfg(test)]
39mod test {
40    use super::*;
41    #[test]
42    fn inoderef_test() {
43        let iref = InodeRef(33489312);
44        assert_eq!(iref.block_start(), 511);
45        assert_eq!(iref.block_offset(), 416);
46    }
47}
48
49#[derive(Debug, Deserialize_repr)]
50#[repr(u16)]
51pub enum InodeType {
52    BasicDirectory = 1,
53    BasicFile,
54    BasicSymlink,
55    BasicBlockDevice,
56    BasicCharDevice,
57    BasicFifo,
58    BasicSocket,
59    ExtendedDirectory,
60    ExtendedFile,
61    ExtendedSymlink,
62    ExtendedBlockDevice,
63    ExtendedCharDevice,
64    ExtendedFifo,
65    ExtendedSocket,
66}
67impl InodeType {
68    pub(crate) fn is_dir(&self) -> bool {
69        matches!(self, Self::BasicDirectory | Self::ExtendedDirectory)
70    }
71}
72
73#[derive(Debug, Deserialize)]
74struct InodeHeader {
75    inode_type: InodeType,
76    _permissions: u16,
77    _uid_idx: u16,
78    _gid_idx: u16,
79    _modified_time: u32,
80    inode_number: u32,
81}
82from_reader!(InodeHeader, 16);
83
84/// Inode table
85#[derive(Default, Debug)]
86pub struct InodeTable {
87    // Change to BTreeMap once
88    // https://github.com/rust-lang/rust/pull/102680
89    // has been merged.
90    // https://github.com/dtolnay/async-trait/issues/215
91    pub directories: BTreeMap<u32, Box<dyn DirectoryInode + Send + Sync>>,
92    pub files: BTreeMap<u32, Box<dyn FileInode + Send + Sync>>,
93}
94impl std::fmt::Display for InodeTable {
95    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
96        write!(
97            f,
98            "Inode table with {} directories and {} files",
99            self.directories.len(),
100            self.files.len()
101        )
102    }
103}
104impl InodeTable {
105    pub fn ids(&self) -> impl Iterator<Item = u32> + '_ {
106        self.directories.keys().chain(self.files.keys()).copied()
107    }
108    async fn inode_table_bytes<'a>(
109        superblock: &'a SuperBlock,
110        mut r: impl crate::AsyncSeekBufRead + 'a,
111        inode_ref: Option<InodeRef>,
112    ) -> Result<impl crate::AsyncRead + 'a, InodeTableError> {
113        r.seek(SeekFrom::Start(
114            superblock.inode_table_start + inode_ref.map(|x| x.block_start()).unwrap_or_default(),
115        ))
116        .await
117        .map_err(InodeTableError::ReadFailure)?;
118        let r = MetadataBlock::from_reader_flatten(
119            r,
120            superblock.directory_table_start,
121            superblock.compression,
122        )
123        .await?;
124        let mut r = Box::pin(r);
125        if let Some(inode_ref) = inode_ref {
126            let r2 = &mut r;
127            tokio::io::copy(
128                &mut r2.take(inode_ref.block_offset()),
129                &mut tokio::io::sink(),
130            )
131            .await
132            .map_err(InodeTableError::ReadFailure)?;
133        }
134        Ok(r)
135    }
136    pub async fn read_root_inode(
137        inode_ref: InodeRef,
138        superblock: &SuperBlock,
139        mut r: impl crate::AsyncSeekBufRead,
140    ) -> Result<u32, InodeTableError> {
141        let mut r = Self::inode_table_bytes(superblock, &mut r, Some(inode_ref)).await?;
142        let header = InodeHeader::from_reader(&mut r)
143            .await
144            .map_err(|_| InodeTableError::InvalidHeader)?;
145        Ok(header.inode_number)
146    }
147    pub async fn from_reader(
148        superblock: &SuperBlock,
149        mut r: impl crate::AsyncSeekBufRead,
150    ) -> Result<Self, InodeTableError> {
151        debug!("Reading inode table");
152        let mut table = InodeTable::default();
153        let mut r = Self::inode_table_bytes(superblock, &mut r, None).await?;
154        loop {
155            let mut header = [0; 16];
156            let header = match r.read_exact(&mut header).await {
157                Ok(_) => InodeHeader::from_reader(&header[..])
158                    .await
159                    .map_err(|_| InodeTableError::InvalidHeader)?,
160                Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
161                    break;
162                }
163                Err(_) => {
164                    return Err(InodeTableError::InvalidHeader);
165                }
166            };
167            match header.inode_type {
168                InodeType::BasicFile => {
169                    let file = BasicFile::from_reader(&mut r, superblock).await?;
170                    table.files.insert(header.inode_number, Box::new(file));
171                }
172                InodeType::ExtendedFile => {
173                    let file = ExtendedFile::from_reader(&mut r, superblock).await?;
174                    table.files.insert(header.inode_number, Box::new(file));
175                }
176                InodeType::BasicDirectory => {
177                    let dir = BasicDirectory::from_reader(&mut r)
178                        .await
179                        .map_err(|_| InodeTableError::InvalidEntry)?;
180                    table.directories.insert(header.inode_number, Box::new(dir));
181                }
182                InodeType::ExtendedDirectory => {
183                    let dir = ExtendedDirectory::from_reader(&mut r).await?;
184                    table.directories.insert(header.inode_number, Box::new(dir));
185                }
186                InodeType::BasicSymlink => {
187                    symlink::Symlink::from_reader(&mut r).await?;
188                }
189                _ => {
190                    warn!("Skipping unsupposed inode of type {:?}", header.inode_type);
191                }
192            }
193        }
194        Ok(table)
195    }
196}