Skip to main content

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/// A read-only view of a directory paired with its leaves table, specialized for composefs regular files.
43pub type DirectoryRef<'a, T> = generic_tree::DirectoryRef<'a, RegularFile<T>>;
44
45#[cfg(test)]
46mod tests {
47    use std::{collections::BTreeMap, ffi::OsStr};
48
49    use super::*;
50    use crate::fsverity::Sha256HashValue;
51    use crate::generic_tree::LeafId;
52
53    // Helper to create a Stat with a specific mtime
54    fn stat_with_mtime(mtime: i64) -> Stat {
55        Stat {
56            st_mode: 0o755,
57            st_uid: 1000,
58            st_gid: 1000,
59            st_mtim_sec: mtime,
60            st_mtim_nsec: 0,
61            xattrs: BTreeMap::new(),
62        }
63    }
64
65    // Helper to create an empty Directory Inode with a specific mtime
66    fn new_dir_inode(mtime: i64) -> Inode<Sha256HashValue> {
67        Inode::Directory(Box::new(Directory {
68            stat: stat_with_mtime(mtime),
69            entries: BTreeMap::new(),
70        }))
71    }
72
73    // Helper for default stat in tests
74    fn default_stat() -> Stat {
75        Stat {
76            st_mode: 0o755,
77            st_uid: 0,
78            st_gid: 0,
79            st_mtim_sec: 0,
80            st_mtim_nsec: 0,
81            xattrs: BTreeMap::new(),
82        }
83    }
84
85    #[test]
86    fn test_insert_and_get_leaf() {
87        let mut leaves: Vec<Leaf<Sha256HashValue>> = Vec::new();
88        let leaf_id = LeafId(leaves.len());
89        leaves.push(Leaf {
90            stat: stat_with_mtime(10),
91            content: LeafContent::Regular(super::RegularFile::Inline(Default::default())),
92        });
93
94        let mut dir = Directory::<Sha256HashValue>::new(default_stat());
95        dir.insert(OsStr::new("file.txt"), Inode::leaf(leaf_id));
96        assert_eq!(dir.entries.len(), 1);
97
98        let retrieved_id = dir.leaf_id(OsStr::new("file.txt")).unwrap();
99        assert_eq!(retrieved_id, leaf_id);
100
101        let regular_file_content = dir.get_file(OsStr::new("file.txt"), &leaves).unwrap();
102        assert!(matches!(
103            regular_file_content,
104            super::RegularFile::Inline(_)
105        ));
106    }
107
108    #[test]
109    fn test_insert_and_get_directory() {
110        let mut dir = Directory::<Sha256HashValue>::new(default_stat());
111        let sub_dir_inode = new_dir_inode(20);
112        dir.insert(OsStr::new("subdir"), sub_dir_inode);
113        assert_eq!(dir.entries.len(), 1);
114
115        let retrieved_subdir = dir.get_directory(OsStr::new("subdir")).unwrap();
116        assert_eq!(retrieved_subdir.stat.st_mtim_sec, 20);
117
118        let retrieved_subdir_opt = dir
119            .get_directory_opt(OsStr::new("subdir"))
120            .unwrap()
121            .unwrap();
122        assert_eq!(retrieved_subdir_opt.stat.st_mtim_sec, 20);
123    }
124}