composefs/
tree.rs

1//! A filesystem tree which stores regular files using the composefs strategy
2//! of inlining small files, and having an external fsverity reference for
3//! larger ones.
4
5use crate::fsverity::FsVerityHashValue;
6
7pub use crate::generic_tree::{self, ImageError, Stat};
8
9/// Represents a regular file's content storage strategy in composefs.
10///
11/// Files can be stored inline for small content or externally referenced
12/// for larger files using fsverity hashing.
13#[derive(Debug, Clone)]
14pub enum RegularFile<ObjectID: FsVerityHashValue> {
15    /// File content stored inline as raw bytes.
16    Inline(Box<[u8]>),
17    /// File stored externally, referenced by fsverity hash and size.
18    ///
19    /// The tuple contains (fsverity hash, file size in bytes).
20    External(ObjectID, u64),
21}
22
23// Re-export generic types. Note that we don't need to re-write
24// the generic constraint T: FsVerityHashValue here because it will
25// be transitively enforced.
26
27/// Content of a leaf node in the filesystem tree, specialized for composefs regular files.
28pub type LeafContent<T> = generic_tree::LeafContent<RegularFile<T>>;
29
30/// A leaf node in the filesystem tree (file, symlink, or device), specialized for composefs regular files.
31pub type Leaf<T> = generic_tree::Leaf<RegularFile<T>>;
32
33/// A directory in the filesystem tree, specialized for composefs regular files.
34pub type Directory<T> = generic_tree::Directory<RegularFile<T>>;
35
36/// An inode representing either a directory or a leaf node, specialized for composefs regular files.
37pub type Inode<T> = generic_tree::Inode<RegularFile<T>>;
38
39/// A complete filesystem tree, specialized for composefs regular files.
40pub type FileSystem<T> = generic_tree::FileSystem<RegularFile<T>>;
41
42#[cfg(test)]
43mod tests {
44    use std::{cell::RefCell, collections::BTreeMap, ffi::OsStr, rc::Rc};
45
46    use super::*;
47    use crate::fsverity::Sha256HashValue;
48
49    // Helper to create a Stat with a specific mtime
50    fn stat_with_mtime(mtime: i64) -> Stat {
51        Stat {
52            st_mode: 0o755,
53            st_uid: 1000,
54            st_gid: 1000,
55            st_mtim_sec: mtime,
56            xattrs: RefCell::new(BTreeMap::new()),
57        }
58    }
59
60    // Helper to create an empty Directory Inode with a specific mtime
61    fn new_dir_inode(mtime: i64) -> Inode<Sha256HashValue> {
62        Inode::Directory(Box::new(Directory {
63            stat: stat_with_mtime(mtime),
64            entries: BTreeMap::new(),
65        }))
66    }
67
68    // Helper to create a simple Leaf (e.g., an empty inline file)
69    fn new_leaf_file(mtime: i64) -> Rc<Leaf<Sha256HashValue>> {
70        Rc::new(Leaf {
71            stat: stat_with_mtime(mtime),
72            content: LeafContent::Regular(super::RegularFile::Inline(Default::default())),
73        })
74    }
75
76    #[test]
77    fn test_insert_and_get_leaf() {
78        let mut dir = Directory::<Sha256HashValue>::default();
79        let leaf = new_leaf_file(10);
80        dir.insert(OsStr::new("file.txt"), Inode::Leaf(Rc::clone(&leaf)));
81        assert_eq!(dir.entries.len(), 1);
82
83        let retrieved_leaf_rc = dir.ref_leaf(OsStr::new("file.txt")).unwrap();
84        assert!(Rc::ptr_eq(&retrieved_leaf_rc, &leaf));
85
86        let regular_file_content = dir.get_file(OsStr::new("file.txt")).unwrap();
87        assert!(matches!(
88            regular_file_content,
89            super::RegularFile::Inline(_)
90        ));
91    }
92
93    #[test]
94    fn test_insert_and_get_directory() {
95        let mut dir = Directory::<Sha256HashValue>::default();
96        let sub_dir_inode = new_dir_inode(20);
97        dir.insert(OsStr::new("subdir"), sub_dir_inode);
98        assert_eq!(dir.entries.len(), 1);
99
100        let retrieved_subdir = dir.get_directory(OsStr::new("subdir")).unwrap();
101        assert_eq!(retrieved_subdir.stat.st_mtim_sec, 20);
102
103        let retrieved_subdir_opt = dir
104            .get_directory_opt(OsStr::new("subdir"))
105            .unwrap()
106            .unwrap();
107        assert_eq!(retrieved_subdir_opt.stat.st_mtim_sec, 20);
108    }
109}