1use serde::{Deserialize, Serialize};
6use std::collections::BTreeMap;
7use thiserror::Error;
8
9pub const ROOT_INODE_NUM: u64 = 2;
11
12pub const S_IFDIR: u16 = 0x4000;
13pub const S_IFREG: u16 = 0x8000;
14pub const S_IFLNK: u16 = 0xa000;
15pub const S_IFMT: u16 = 0xf000;
16
17#[derive(Error, Debug)]
18pub enum MetadataError {
19 #[error("Node not found")]
20 NotFound,
21 #[error("Node is not a directory")]
22 NotDir,
23 #[error("Failed to deserialize metadata (corrupt?)")]
24 FailedToDeserialize,
25}
26
27#[derive(Serialize, Deserialize)]
28pub struct Metadata {
29 nodes: BTreeMap<u64, Node>,
30}
31
32impl Metadata {
33 pub fn new() -> Self {
34 Self { nodes: BTreeMap::new() }
35 }
36
37 pub fn lookup(&self, parent: u64, name: &str) -> Result<u64, MetadataError> {
39 self.nodes
40 .get(&parent)
41 .ok_or(MetadataError::NotFound)?
42 .directory()
43 .ok_or(MetadataError::NotDir)?
44 .children
45 .get(name)
46 .ok_or(MetadataError::NotFound)
47 .cloned()
48 }
49
50 pub fn get(&self, inode_num: u64) -> Option<&Node> {
52 self.nodes.get(&inode_num)
53 }
54
55 pub fn deserialize(bytes: &[u8]) -> Result<Self, MetadataError> {
57 bincode::deserialize(bytes).map_err(|_| MetadataError::FailedToDeserialize)
58 }
59
60 pub fn serialize(&self) -> Vec<u8> {
62 bincode::serialize(&self).unwrap()
63 }
64
65 pub fn add_child(&mut self, path: &[&str], inode_num: u64) {
67 for component in path {
68 assert!(
69 *component != "."
70 && *component != ".."
71 && !component.contains(|c| c == '/' || c == '\0'),
72 "Invalid path component {component:?} in {path:?}"
73 );
74 }
75
76 let mut node = self.nodes.get_mut(&ROOT_INODE_NUM).unwrap().directory_mut().unwrap();
77 let mut iter = path.iter();
78 let name = iter.next_back().unwrap();
79 for component in iter {
80 let inode_num = *node.children.get_mut(*component).unwrap();
81 node = self.nodes.get_mut(&inode_num).unwrap().directory_mut().unwrap();
82 }
83 node.children.insert(name.to_string(), inode_num);
84 }
85
86 pub fn insert_directory(
88 &mut self,
89 inode_num: u64,
90 mode: u16,
91 uid: u16,
92 gid: u16,
93 extended_attributes: ExtendedAttributes,
94 ) {
95 assert_eq!(mode & S_IFMT, S_IFDIR);
96 self.nodes.insert(
97 inode_num,
98 Node {
99 info: NodeInfo::Directory(Directory { children: BTreeMap::new() }),
100 mode,
101 uid,
102 gid,
103 extended_attributes,
104 },
105 );
106 }
107
108 pub fn insert_file(
110 &mut self,
111 inode_num: u64,
112 mode: u16,
113 uid: u16,
114 gid: u16,
115 extended_attributes: ExtendedAttributes,
116 ) {
117 assert_eq!(mode & S_IFMT, S_IFREG);
118 self.nodes.insert(
119 inode_num,
120 Node { info: NodeInfo::File(File), mode, uid, gid, extended_attributes },
121 );
122 }
123
124 pub fn insert_symlink(
126 &mut self,
127 inode_num: u64,
128 target: String,
129 mode: u16,
130 uid: u16,
131 gid: u16,
132 extended_attributes: ExtendedAttributes,
133 ) {
134 assert_eq!(mode & S_IFMT, S_IFLNK);
135 self.nodes.insert(
136 inode_num,
137 Node {
138 info: NodeInfo::Symlink(Symlink { target }),
139 mode,
140 uid,
141 gid,
142 extended_attributes,
143 },
144 );
145 }
146}
147
148pub type ExtendedAttributes = BTreeMap<Box<[u8]>, Box<[u8]>>;
149
150#[derive(Serialize, Deserialize)]
151pub struct Node {
152 info: NodeInfo,
153 pub mode: u16,
154 pub uid: u16,
155 pub gid: u16,
156 pub extended_attributes: ExtendedAttributes,
157}
158
159impl Node {
160 pub fn info(&self) -> &NodeInfo {
161 &self.info
162 }
163
164 pub fn directory(&self) -> Option<&Directory> {
165 match &self.info {
166 NodeInfo::Directory(d) => Some(d),
167 _ => None,
168 }
169 }
170
171 pub fn directory_mut(&mut self) -> Option<&mut Directory> {
172 match &mut self.info {
173 NodeInfo::Directory(d) => Some(d),
174 _ => None,
175 }
176 }
177
178 pub fn file(&self) -> Option<&File> {
179 match &self.info {
180 NodeInfo::File(f) => Some(f),
181 _ => None,
182 }
183 }
184
185 pub fn symlink(&self) -> Option<&Symlink> {
186 match &self.info {
187 NodeInfo::Symlink(s) => Some(s),
188 _ => None,
189 }
190 }
191}
192
193#[derive(Debug, Serialize, Deserialize)]
194pub enum NodeInfo {
195 Directory(Directory),
196 File(File),
197 Symlink(Symlink),
198}
199
200#[derive(Debug, Serialize, Deserialize)]
201pub struct Directory {
202 pub children: BTreeMap<String, u64>,
203}
204
205#[derive(Debug, Serialize, Deserialize)]
206pub struct File;
207
208#[derive(Debug, Serialize, Deserialize)]
209pub struct Symlink {
210 pub target: String,
211}
212
213#[cfg(test)]
214mod tests {
215 use super::{Metadata, NodeInfo, ROOT_INODE_NUM, S_IFDIR, S_IFLNK, S_IFREG};
216 use assert_matches::assert_matches;
217 use std::collections::BTreeMap;
218
219 #[test]
220 fn test_serialize_and_deserialize() {
221 let mut m = Metadata::new();
222 let xattr: BTreeMap<_, _> =
223 [((*b"a").into(), (*b"apple").into()), ((*b"b").into(), (*b"ball").into())].into();
224 m.insert_directory(ROOT_INODE_NUM, S_IFDIR | 0o755, 2, 3, Default::default());
225 m.insert_directory(3, S_IFDIR | 0o775, 2, 3, xattr.clone());
226 m.add_child(&["foo"], 3);
227 m.insert_file(4, S_IFREG | 0o644, 2, 3, xattr.clone());
228 m.add_child(&["foo", "bar"], 4);
229 m.insert_symlink(5, "symlink-target".to_string(), S_IFLNK | 0o777, 2, 3, xattr.clone());
230 m.add_child(&["foo", "baz"], 5);
231
232 let m = Metadata::deserialize(&m.serialize()).expect("deserialize failed");
233 let node = m.get(ROOT_INODE_NUM).expect("root not found");
234 assert_matches!(node.info(), NodeInfo::Directory(_));
235 assert_eq!(node.mode, S_IFDIR | 0o755);
236 assert_eq!(node.uid, 2);
237 assert_eq!(node.gid, 3);
238 assert_eq!(node.extended_attributes, [].into());
239
240 assert_eq!(m.lookup(ROOT_INODE_NUM, "foo").expect("foo not found"), 3);
241 let node = m.get(3).expect("root not found");
242 assert_matches!(node.info(), NodeInfo::Directory(_));
243 assert_eq!(node.mode, S_IFDIR | 0o775);
244 assert_eq!(node.uid, 2);
245 assert_eq!(node.gid, 3);
246 assert_eq!(&node.extended_attributes, &xattr);
247
248 assert_eq!(m.lookup(3, "bar").expect("foo/bar not found"), 4);
249 let node = m.get(4).expect("root not found");
250 assert_matches!(node.info(), NodeInfo::File(_));
251 assert_eq!(node.mode, S_IFREG | 0o644);
252 assert_eq!(node.uid, 2);
253 assert_eq!(node.gid, 3);
254 assert_eq!(&node.extended_attributes, &xattr);
255
256 assert_eq!(m.lookup(3, "baz").expect("foo/baz not found"), 5);
257 let node = m.get(5).expect("root not found");
258 assert_matches!(node.info(), NodeInfo::Symlink(_));
259 assert_eq!(node.mode, S_IFLNK | 0o777);
260 assert_eq!(node.uid, 2);
261 assert_eq!(node.gid, 3);
262 assert_eq!(&node.extended_attributes, &xattr);
263 }
264
265 #[test]
266 fn test_serialization_is_deterministic() {
267 let build_fs = || {
269 let mut m = Metadata::new();
270 let xattr: BTreeMap<_, _> =
271 [((*b"a").into(), (*b"apple").into()), ((*b"b").into(), (*b"ball").into())].into();
272 m.insert_directory(ROOT_INODE_NUM, S_IFDIR | 0o755, 2, 3, Default::default());
273 m.insert_directory(3, S_IFDIR | 0o775, 2, 3, xattr.clone());
274 m.add_child(&["foo"], 3);
275 m.insert_file(4, S_IFREG | 0o644, 2, 3, xattr.clone());
276 m.add_child(&["foo", "bar"], 4);
277 m.insert_symlink(5, "symlink-target".to_string(), S_IFLNK | 0o777, 2, 3, xattr.clone());
278 m.add_child(&["foo", "baz"], 5);
279 m
280 };
281
282 let data1 = build_fs().serialize();
284 let data2 = build_fs().serialize();
285 assert_eq!(data1, data2);
286 }
287}