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    // Helper for default stat in tests
77    fn default_stat() -> Stat {
78        Stat {
79            st_mode: 0o755,
80            st_uid: 0,
81            st_gid: 0,
82            st_mtim_sec: 0,
83            xattrs: RefCell::new(BTreeMap::new()),
84        }
85    }
86
87    #[test]
88    fn test_insert_and_get_leaf() {
89        let mut dir = Directory::<Sha256HashValue>::new(default_stat());
90        let leaf = new_leaf_file(10);
91        dir.insert(OsStr::new("file.txt"), Inode::Leaf(Rc::clone(&leaf)));
92        assert_eq!(dir.entries.len(), 1);
93
94        let retrieved_leaf_rc = dir.ref_leaf(OsStr::new("file.txt")).unwrap();
95        assert!(Rc::ptr_eq(&retrieved_leaf_rc, &leaf));
96
97        let regular_file_content = dir.get_file(OsStr::new("file.txt")).unwrap();
98        assert!(matches!(
99            regular_file_content,
100            super::RegularFile::Inline(_)
101        ));
102    }
103
104    #[test]
105    fn test_insert_and_get_directory() {
106        let mut dir = Directory::<Sha256HashValue>::new(default_stat());
107        let sub_dir_inode = new_dir_inode(20);
108        dir.insert(OsStr::new("subdir"), sub_dir_inode);
109        assert_eq!(dir.entries.len(), 1);
110
111        let retrieved_subdir = dir.get_directory(OsStr::new("subdir")).unwrap();
112        assert_eq!(retrieved_subdir.stat.st_mtim_sec, 20);
113
114        let retrieved_subdir_opt = dir
115            .get_directory_opt(OsStr::new("subdir"))
116            .unwrap()
117            .unwrap();
118        assert_eq!(retrieved_subdir_opt.stat.st_mtim_sec, 20);
119    }
120}