squashfs_async/
directory_table.rs

1//! Directory table parsing
2//!
3//! See <https://dr-emann.github.io/squashfs/squashfs.html#_directory_table>
4use std::collections::hash_map::DefaultHasher;
5use std::collections::HashMap;
6use std::hash::{Hash, Hasher};
7use std::io::SeekFrom;
8
9use deser::from_reader;
10use itertools::Itertools;
11use serde::Deserialize;
12use tokio::io::{AsyncReadExt, AsyncSeekExt};
13use tracing::*;
14
15use super::deser;
16use super::error::DirectoryTableError;
17use super::inodes::{DirectoryInode, InodeType};
18use super::metadata::MetadataBlock;
19use super::superblock::SuperBlock;
20
21#[derive(Debug, Deserialize)]
22struct Header {
23    entries: u32,
24    inode_table_offset: u32,
25    inode_number_base: u32,
26}
27from_reader!(Header, 12);
28
29#[derive(Debug, Deserialize)]
30struct EntryInternal {
31    inode_metadata_offset: u16,
32    inode_offset: i16,
33    r#type: InodeType,
34    name_size: u16,
35    #[serde(skip)]
36    name: String,
37}
38
39/// Directory table entry
40#[derive(Debug)]
41pub struct Entry {
42    _inode_metadata_offset: u32,
43    pub inode: u32,
44    pub r#type: InodeType,
45    pub name: String,
46}
47impl Entry {
48    pub fn is_dir(&self) -> bool {
49        self.r#type.is_dir()
50    }
51}
52impl std::fmt::Display for Entry {
53    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
54        write!(
55            f,
56            "{}{}",
57            self.name,
58            self.is_dir().then_some("/").unwrap_or_default()
59        )
60    }
61}
62impl Entry {
63    fn from(header: &Header, entry: EntryInternal) -> Self {
64        Self {
65            _inode_metadata_offset: header.inode_table_offset + entry.inode_metadata_offset as u32,
66            name: entry.name,
67            r#type: entry.r#type,
68            inode: (header.inode_number_base as i32 + entry.inode_offset as i32) as u32,
69        }
70    }
71}
72impl EntryInternal {
73    async fn from_reader(mut r: impl crate::AsyncRead) -> Result<Self, DirectoryTableError> {
74        let mut entry: Self = deser::bincode_deser_from(&mut r, 8)
75            .await
76            .map_err(|_| DirectoryTableError::InvalidEntry)?;
77        entry.name = deser::bincode_deser_string_from(r, entry.name_size as usize + 1)
78            .await
79            .map_err(|_| DirectoryTableError::InvalidEntry)?;
80        Ok(entry)
81    }
82}
83
84fn index_hash(s: &str) -> u64 {
85    let mut hasher = DefaultHasher::new();
86    s.hash(&mut hasher);
87    hasher.finish()
88}
89/// Table for one directory
90#[derive(Default, Debug)]
91pub struct DirectoryTable {
92    pub entries: Vec<Entry>,
93    /// inode-to-index (into `entries` for fast access)
94    index: HashMap<u64, Vec<usize>>,
95}
96impl DirectoryTable {
97    pub fn find(&self, name: &str) -> Option<&Entry> {
98        self.index
99            .get(&index_hash(name))
100            .into_iter()
101            .flatten()
102            .map(|i| &self.entries[*i])
103            .find(|e| e.name == name)
104    }
105    async fn from_reader(mut r: impl crate::AsyncRead) -> Result<Self, DirectoryTableError> {
106        // Read entries
107        let mut entries = vec![];
108        let mut header = [0; 12];
109        loop {
110            // Read header
111            let header = match r.read_exact(&mut header).await {
112                Ok(_) => Header::from_reader(&header[..])
113                    .await
114                    .map_err(|_| DirectoryTableError::InvalidHeader)?,
115                Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
116                    break;
117                }
118                Err(_) => return Err(DirectoryTableError::InvalidHeader),
119            };
120            debug!("Directory table header {:?}", header);
121            // Read entries
122            for _ in 0..header.entries + 1 {
123                let entry = EntryInternal::from_reader(&mut r).await?;
124                entries.push(Entry::from(&header, entry));
125            }
126        }
127        Ok(DirectoryTable {
128            index: entries
129                .iter()
130                .enumerate()
131                .map(|(i, e)| (index_hash(&e.name), i))
132                .into_group_map(),
133            entries,
134        })
135    }
136    #[allow(clippy::borrowed_box)]
137    pub async fn from_reader_directory(
138        directory: &Box<dyn DirectoryInode + Send + Sync>,
139        superblock: &SuperBlock,
140        mut r: impl crate::AsyncSeekBufRead,
141    ) -> Result<Self, DirectoryTableError> {
142        let loc = directory.table_location();
143        r.seek(SeekFrom::Start(
144            superblock.directory_table_start + loc.start,
145        ))
146        .await
147        .map_err(DirectoryTableError::ReadFailure)?;
148        let r = MetadataBlock::from_reader_flatten(
149            r,
150            superblock.fragment_table_start,
151            superblock.compression,
152        )
153        .await?;
154        let mut r = Box::pin(r);
155        // Get the section of the metadata block corresponding to the directory
156        let r2 = &mut r;
157        tokio::io::copy(&mut r2.take(loc.offset), &mut tokio::io::sink())
158            .await
159            .map_err(DirectoryTableError::ReadFailure)?;
160        let r = r.take(loc.file_size);
161        Self::from_reader(r).await
162    }
163}