Skip to main content

orbis_pfs/directory/
mod.rs

1use snafu::{OptionExt, ResultExt, ensure};
2
3use self::dirent::Dirent;
4use crate::Pfs;
5use crate::file::File;
6use crate::inode::Inode;
7use std::collections::BTreeMap;
8use std::sync::Arc;
9
10pub mod dirent;
11
12/// Errors of [`Directory::open()`].
13#[derive(Debug, snafu::Snafu)]
14#[non_exhaustive]
15pub enum OpenError {
16    #[snafu(display("inode #{inode} is not valid"))]
17    InvalidInode { inode: usize },
18
19    #[snafu(display("cannot read block #{block}"))]
20    ReadBlock { block: u32, source: std::io::Error },
21
22    #[snafu(display("cannot read directory entry"))]
23    ReadDirEntry { source: dirent::ReadError },
24
25    #[snafu(display("dirent #{dirent} in block #{block} has invalid size"))]
26    DirentInvalidSize { block: u32, dirent: usize },
27
28    #[snafu(display("dirent #{dirent} in block #{block} has unknown type"))]
29    DirentUnknownType { block: u32, dirent: usize },
30}
31
32/// Represents a directory in the PFS.
33///
34/// Use [`open()`][Self::open] to read the directory contents.
35///
36/// # Example
37///
38/// ```no_run
39/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
40/// # let data = vec![];
41/// let pfs = orbis_pfs::open_slice(&data, None)?;
42/// let root = pfs.root();
43///
44/// // List all entries in the root directory
45/// for (name, entry) in root.open()? {
46///     let name_str = String::from_utf8_lossy(&name);
47///     match entry {
48///         orbis_pfs::directory::DirEntry::Directory(_) => println!("[DIR]  {}", name_str),
49///         orbis_pfs::directory::DirEntry::File(f) => println!("[FILE] {} ({} bytes)", name_str, f.len()),
50///         _ => {},
51///     }
52/// }
53/// # Ok(())
54/// # }
55/// ```
56#[derive(Clone)]
57#[must_use]
58pub struct Directory<'a> {
59    pfs: Arc<Pfs<'a>>,
60    inode: usize,
61}
62
63impl<'a> std::fmt::Debug for Directory<'a> {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        f.debug_struct("Directory")
66            .field("inode", &self.inode)
67            .field("mode", &self.mode())
68            .finish_non_exhaustive()
69    }
70}
71
72impl<'a> Directory<'a> {
73    pub(super) fn new(pfs: Arc<Pfs<'a>>, inode: usize) -> Self {
74        Self { pfs, inode }
75    }
76
77    #[must_use]
78    pub fn mode(&self) -> u16 {
79        self.inode_ref().mode()
80    }
81
82    #[must_use]
83    pub fn flags(&self) -> u32 {
84        self.inode_ref().flags().value()
85    }
86
87    /// Returns the last access time as seconds since the Unix epoch.
88    #[must_use]
89    pub fn atime(&self) -> u64 {
90        self.inode_ref().atime()
91    }
92
93    /// Returns the last modification time as seconds since the Unix epoch.
94    #[must_use]
95    pub fn mtime(&self) -> u64 {
96        self.inode_ref().mtime()
97    }
98
99    /// Returns the last metadata change time as seconds since the Unix epoch.
100    #[must_use]
101    pub fn ctime(&self) -> u64 {
102        self.inode_ref().ctime()
103    }
104
105    /// Returns the creation time as seconds since the Unix epoch.
106    #[must_use]
107    pub fn birthtime(&self) -> u64 {
108        self.inode_ref().birthtime()
109    }
110
111    /// Returns the sub-second nanosecond component of [`mtime()`](Self::mtime).
112    #[must_use]
113    pub fn mtimensec(&self) -> u32 {
114        self.inode_ref().mtimensec()
115    }
116
117    /// Returns the sub-second nanosecond component of [`atime()`](Self::atime).
118    #[must_use]
119    pub fn atimensec(&self) -> u32 {
120        self.inode_ref().atimensec()
121    }
122
123    /// Returns the sub-second nanosecond component of [`ctime()`](Self::ctime).
124    #[must_use]
125    pub fn ctimensec(&self) -> u32 {
126        self.inode_ref().ctimensec()
127    }
128
129    /// Returns the sub-second nanosecond component of [`birthtime()`](Self::birthtime).
130    #[must_use]
131    pub fn birthnsec(&self) -> u32 {
132        self.inode_ref().birthnsec()
133    }
134
135    #[must_use]
136    pub fn uid(&self) -> u32 {
137        self.inode_ref().uid()
138    }
139
140    #[must_use]
141    pub fn gid(&self) -> u32 {
142        self.inode_ref().gid()
143    }
144
145    /// Opens this directory and reads its entries.
146    ///
147    /// Returns a collection of directory entries (files and subdirectories).
148    pub fn open(&self) -> Result<DirEntries<'a>, OpenError> {
149        let blocks = self.pfs.block_map(self.inode);
150        let block_size = self.pfs.block_size;
151        let img = self.pfs.image();
152
153        // Read all dirents.
154        let mut items: BTreeMap<Vec<u8>, DirEntry<'a>> = BTreeMap::new();
155        let mut block_data = vec![0; block_size as usize];
156
157        for &block_num in blocks {
158            // Read block data via positional read.
159            let offset = (block_num as u64) * (block_size as u64);
160
161            img.read_exact_at(offset, &mut block_data)
162                .context(ReadBlockSnafu { block: block_num })?;
163
164            // Read dirents in the block.
165            let mut next = block_data.as_slice();
166
167            for num in 0_usize.. {
168                // Read dirent.
169                let dirent = match Dirent::read(&mut next) {
170                    Ok(v) => v,
171                    Err(dirent::ReadError::TooSmall | dirent::ReadError::EndOfEntry) => {
172                        break;
173                    }
174                    err => err.context(ReadDirEntrySnafu)?,
175                };
176
177                // Skip remaining padding.
178                next = next
179                    .get(dirent.padding_size()..)
180                    .context(DirentInvalidSizeSnafu {
181                        block: block_num,
182                        dirent: num,
183                    })?;
184
185                // Check if inode valid.
186                let inode = dirent.inode();
187                ensure!(inode < self.pfs.inode_count(), InvalidInodeSnafu { inode });
188
189                // Construct object.
190                let entry = match dirent.ty() {
191                    Dirent::FILE => DirEntry::File(File::new(self.pfs.clone(), inode)),
192                    Dirent::DIRECTORY => {
193                        DirEntry::Directory(Directory::new(self.pfs.clone(), inode))
194                    }
195                    Dirent::SELF | Dirent::PARENT => continue,
196                    _ => {
197                        return Err(DirentUnknownTypeSnafu {
198                            block: block_num,
199                            dirent: num,
200                        }
201                        .build());
202                    }
203                };
204
205                items.insert(dirent.name().to_vec(), entry);
206            }
207        }
208
209        Ok(DirEntries { items })
210    }
211
212    fn inode_ref(&self) -> &Inode {
213        self.pfs.inode(self.inode)
214    }
215}
216
217/// Represents a collection of entries in a directory.
218///
219/// This type provides access to the files and subdirectories within a directory.
220/// It can be iterated over or queried by name.
221#[derive(Debug)]
222#[must_use]
223pub struct DirEntries<'a> {
224    items: BTreeMap<Vec<u8>, DirEntry<'a>>,
225}
226
227impl<'a> DirEntries<'a> {
228    /// Returns the number of entries in the directory.
229    #[must_use]
230    pub fn len(&self) -> usize {
231        self.items.len()
232    }
233
234    /// Returns `true` if the directory is empty.
235    #[must_use]
236    pub fn is_empty(&self) -> bool {
237        self.items.is_empty()
238    }
239
240    /// Returns a reference to the entry with the given name.
241    #[must_use]
242    pub fn get(&self, name: &[u8]) -> Option<&DirEntry<'a>> {
243        self.items.get(name)
244    }
245
246    /// Removes and returns the entry with the given name.
247    pub fn remove(&mut self, name: &[u8]) -> Option<DirEntry<'a>> {
248        self.items.remove(name)
249    }
250
251    /// Returns an iterator over the entries.
252    pub fn iter(&self) -> DirEntriesIter<'_, 'a> {
253        DirEntriesIter {
254            inner: self.items.iter(),
255        }
256    }
257
258    /// Returns an iterator over the entry names.
259    pub fn names(&self) -> impl Iterator<Item = &[u8]> {
260        self.items.keys().map(|k| k.as_slice())
261    }
262}
263
264impl<'a> IntoIterator for DirEntries<'a> {
265    type Item = (Vec<u8>, DirEntry<'a>);
266    type IntoIter = DirEntriesOwnedIter<'a>;
267
268    fn into_iter(self) -> Self::IntoIter {
269        DirEntriesOwnedIter {
270            inner: self.items.into_iter(),
271        }
272    }
273}
274
275impl<'b, 'a> IntoIterator for &'b DirEntries<'a> {
276    type Item = (&'b [u8], &'b DirEntry<'a>);
277    type IntoIter = DirEntriesIter<'b, 'a>;
278
279    fn into_iter(self) -> Self::IntoIter {
280        self.iter()
281    }
282}
283
284/// An iterator over directory entries by reference.
285#[derive(Debug)]
286#[must_use = "iterators are lazy and do nothing unless consumed"]
287pub struct DirEntriesIter<'b, 'a> {
288    inner: std::collections::btree_map::Iter<'b, Vec<u8>, DirEntry<'a>>,
289}
290
291impl<'b, 'a> Iterator for DirEntriesIter<'b, 'a> {
292    type Item = (&'b [u8], &'b DirEntry<'a>);
293
294    fn next(&mut self) -> Option<Self::Item> {
295        self.inner.next().map(|(k, v)| (k.as_slice(), v))
296    }
297
298    fn size_hint(&self) -> (usize, Option<usize>) {
299        self.inner.size_hint()
300    }
301}
302
303impl ExactSizeIterator for DirEntriesIter<'_, '_> {}
304
305/// An owning iterator over directory entries.
306#[derive(Debug)]
307#[must_use = "iterators are lazy and do nothing unless consumed"]
308pub struct DirEntriesOwnedIter<'a> {
309    inner: std::collections::btree_map::IntoIter<Vec<u8>, DirEntry<'a>>,
310}
311
312impl<'a> Iterator for DirEntriesOwnedIter<'a> {
313    type Item = (Vec<u8>, DirEntry<'a>);
314
315    fn next(&mut self) -> Option<Self::Item> {
316        self.inner.next()
317    }
318
319    fn size_hint(&self) -> (usize, Option<usize>) {
320        self.inner.size_hint()
321    }
322}
323
324impl ExactSizeIterator for DirEntriesOwnedIter<'_> {}
325
326/// Represents an entry in a directory (either a file or subdirectory).
327#[derive(Debug, Clone)]
328#[non_exhaustive]
329pub enum DirEntry<'a> {
330    /// A subdirectory.
331    Directory(Directory<'a>),
332    /// A file.
333    File(File<'a>),
334}