Skip to main content

backhand/v4/filesystem/
node.rs

1use core::fmt;
2use core::num::NonZeroUsize;
3use no_std_io2::io::Read;
4use std::fs::File;
5use std::io::BufReader;
6use std::path::{Path, PathBuf};
7use std::sync::{Arc, Mutex};
8
9use crate::error::BackhandError;
10use crate::v4::data::Added;
11use crate::v4::filesystem::normalize_squashfs_path;
12use crate::v4::inode::{BasicFile, ExtendedFile, InodeHeader};
13use crate::{DataSize, FilesystemReaderFile, Id};
14
15/// File information for Node
16#[derive(Debug, PartialEq, Eq, Default, Clone, Copy)]
17pub struct NodeHeader {
18    pub permissions: u16,
19    /// actual value
20    pub uid: u32,
21    /// actual value
22    pub gid: u32,
23    pub mtime: u32,
24}
25
26impl NodeHeader {
27    pub fn new(permissions: u16, uid: u32, gid: u32, mtime: u32) -> Self {
28        Self { permissions, uid, gid, mtime }
29    }
30}
31
32impl NodeHeader {
33    pub fn from_inode(inode_header: InodeHeader, id_table: &[Id]) -> Result<Self, BackhandError> {
34        let uid = id_table.get(inode_header.uid as usize).ok_or(BackhandError::InvalidIdTable)?;
35        let gid = id_table.get(inode_header.gid as usize).ok_or(BackhandError::InvalidIdTable)?;
36        Ok(Self {
37            permissions: inode_header.permissions,
38            uid: uid.num,
39            gid: gid.num,
40            mtime: inode_header.mtime,
41        })
42    }
43}
44
45/// Filesystem Node
46#[derive(Clone, Debug)]
47pub struct Node<T> {
48    pub fullpath: PathBuf,
49    pub header: NodeHeader,
50    pub inner: InnerNode<T>,
51}
52
53impl<T> PartialEq for Node<T> {
54    fn eq(&self, other: &Self) -> bool {
55        self.fullpath.eq(&other.fullpath)
56    }
57}
58impl<T> Eq for Node<T> {}
59impl<T> PartialOrd for Node<T> {
60    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
61        Some(self.cmp(other))
62    }
63}
64impl<T> Ord for Node<T> {
65    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
66        self.fullpath.cmp(&other.fullpath)
67    }
68}
69
70impl<T> Node<T> {
71    pub(crate) fn new(fullpath: PathBuf, header: NodeHeader, inner: InnerNode<T>) -> Self {
72        Self { fullpath, header, inner }
73    }
74
75    pub fn new_root(header: NodeHeader) -> Self {
76        let fullpath = PathBuf::from("/");
77        let inner = InnerNode::Dir(SquashfsDir::default());
78        Self { fullpath, header, inner }
79    }
80}
81
82/// Filesystem node
83#[derive(Debug, Clone, PartialEq, Eq)]
84pub enum InnerNode<T> {
85    /// Either [`SquashfsFileReader`] or [`SquashfsFileWriter`]
86    File(T),
87    Symlink(SquashfsSymlink),
88    Dir(SquashfsDir),
89    CharacterDevice(SquashfsCharacterDevice),
90    BlockDevice(SquashfsBlockDevice),
91    NamedPipe,
92    Socket,
93}
94
95/// Unread file for filesystem
96#[derive(Debug, PartialEq, Eq, Clone)]
97pub enum SquashfsFileReader {
98    Basic(BasicFile),
99    Extended(ExtendedFile),
100}
101
102impl SquashfsFileReader {
103    pub fn file_len(&self) -> usize {
104        match self {
105            SquashfsFileReader::Basic(basic) => basic.file_size as usize,
106            SquashfsFileReader::Extended(extended) => extended.file_size as usize,
107        }
108    }
109
110    pub fn frag_index(&self) -> usize {
111        match self {
112            SquashfsFileReader::Basic(basic) => basic.frag_index as usize,
113            SquashfsFileReader::Extended(extended) => extended.frag_index as usize,
114        }
115    }
116
117    pub fn block_sizes(&self) -> &[DataSize] {
118        match self {
119            SquashfsFileReader::Basic(basic) => &basic.block_sizes,
120            SquashfsFileReader::Extended(extended) => &extended.block_sizes,
121        }
122    }
123
124    pub fn blocks_start(&self) -> u64 {
125        match self {
126            SquashfsFileReader::Basic(basic) => basic.blocks_start as u64,
127            SquashfsFileReader::Extended(extended) => extended.blocks_start,
128        }
129    }
130
131    pub fn block_offset(&self) -> u32 {
132        match self {
133            SquashfsFileReader::Basic(basic) => basic.block_offset,
134            SquashfsFileReader::Extended(extended) => extended.block_offset,
135        }
136    }
137}
138
139/// File on disk that is opened only when the image is written
140///
141/// A [`FilesystemWriter`](crate::FilesystemWriter) can contain many thousands of files. If each
142/// one held an open [`File`], the image build could go over the limit of open file descriptors of
143/// the process. This holds the path instead and opens the file during the write.
144#[derive(Debug, Clone, PartialEq, Eq)]
145pub struct LazyFile {
146    path: PathBuf,
147}
148
149impl LazyFile {
150    pub fn new(path: impl Into<PathBuf>) -> Self {
151        Self { path: path.into() }
152    }
153
154    /// Path of the file on disk
155    pub fn path(&self) -> &Path {
156        &self.path
157    }
158
159    /// Open the file for the write of the image
160    pub(crate) fn open(&self) -> Result<BufReader<File>, BackhandError> {
161        Ok(BufReader::new(File::open(&self.path)?))
162    }
163}
164
165/// Read file from other SquashfsFile or an user file
166pub enum SquashfsFileWriter<'a, 'b, 'c> {
167    UserDefined(Arc<Mutex<dyn Read + 'c>>),
168    /// File on disk, opened only when the image is written
169    LazyFile(LazyFile),
170    SquashfsFile(FilesystemReaderFile<'a, 'b>),
171    Consumed(usize, Added),
172}
173
174impl fmt::Debug for SquashfsFileWriter<'_, '_, '_> {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        f.debug_struct("FileWriter").finish()
177    }
178}
179
180/// Symlink for filesystem
181#[derive(Debug, PartialEq, Eq, Clone)]
182pub struct SquashfsSymlink {
183    pub link: PathBuf,
184}
185
186/// Directory for filesystem
187#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
188pub struct SquashfsDir {}
189
190/// Character Device for filesystem
191#[derive(Debug, PartialEq, Eq, Clone, Copy)]
192pub struct SquashfsCharacterDevice {
193    pub device_number: u32,
194}
195
196/// Block Device for filesystem
197#[derive(Debug, PartialEq, Eq, Clone, Copy)]
198pub struct SquashfsBlockDevice {
199    pub device_number: u32,
200}
201
202#[derive(Debug, Clone)]
203pub struct Nodes<T> {
204    pub nodes: Vec<Node<T>>,
205}
206
207impl<T> Nodes<T> {
208    pub fn new_root(header: NodeHeader) -> Self {
209        Self { nodes: vec![Node::new_root(header)] }
210    }
211
212    pub fn root(&self) -> &Node<T> {
213        &self.nodes[0]
214    }
215
216    pub fn root_mut(&mut self) -> &mut Node<T> {
217        &mut self.nodes[0]
218    }
219
220    pub fn node_mut<S: AsRef<Path>>(&mut self, path: S) -> Option<&mut Node<T>> {
221        //the search path root prefix is optional, so remove it if present to
222        //not affect the search
223        let find_path = normalize_squashfs_path(path.as_ref()).ok()?;
224        self.nodes
225            .binary_search_by(|node| node.fullpath.cmp(&find_path))
226            .ok()
227            .map(|found| &mut self.nodes[found])
228    }
229
230    pub fn insert(&mut self, node: Node<T>) -> Result<(), BackhandError> {
231        let path = &node.fullpath;
232        let parent = node.fullpath.parent().ok_or(BackhandError::InvalidFilePath)?;
233
234        //check if the parent exists and is a dir
235        let parent = self.node_mut(parent).ok_or(BackhandError::InvalidFilePath)?;
236        match &parent.inner {
237            InnerNode::Dir(_) => {}
238            _ => return Err(BackhandError::InvalidFilePath),
239        }
240
241        match self.nodes.binary_search_by(|node| node.fullpath.as_path().cmp(path)) {
242            //file with this fullpath already exists
243            Ok(_index) => Err(BackhandError::DuplicatedFileName),
244            //file don't exists, insert it at this location
245            Err(index) => {
246                self.nodes.insert(index, node);
247                Ok(())
248            }
249        }
250    }
251
252    fn inner_children_of(&self, node_index: usize) -> Option<&[Node<T>]> {
253        let parent = &self.nodes[node_index];
254        let children_start = node_index + 1;
255        let unbounded_children = self.nodes.get(children_start..)?;
256        let children_len = unbounded_children
257            .iter()
258            .enumerate()
259            .find(|(_, node)| !node.fullpath.starts_with(&parent.fullpath))
260            .map(|(index, _)| index)
261            .unwrap_or(unbounded_children.len());
262        Some(&unbounded_children[..children_len])
263    }
264
265    pub fn node(&self, node_index: NonZeroUsize) -> Option<&Node<T>> {
266        self.nodes.get(node_index.get() - 1)
267    }
268
269    pub fn children_of(
270        &self,
271        node_index: NonZeroUsize,
272    ) -> impl Iterator<Item = (NonZeroUsize, &Node<T>)> {
273        self.inner_children_of(node_index.get() - 1).unwrap_or(&[]).iter().enumerate().map(
274            move |(index, node)| (NonZeroUsize::new(node_index.get() + index + 1).unwrap(), node),
275        )
276    }
277}