1use std::path::{Path, PathBuf};
6
7pub type InodeNumber = u32;
8
9#[derive(Debug, Clone, Copy)]
11pub struct BlockRange {
12 pub start: u32,
13 pub end: u32,
14}
15
16pub struct FileTreeNode {
18 pub inode: InodeNumber,
19 pub name: String,
20 pub children: Vec<usize>,
22 pub parent: Option<usize>,
24 pub blocks: Option<BlockRange>,
26 pub additional_blocks: Vec<BlockRange>,
28 pub link: Option<InodeNumber>,
30}
31
32pub struct FileTree {
34 nodes: Vec<FileTreeNode>,
35 root: usize,
36}
37
38impl FileTree {
39 pub fn new(root_inode: InodeNumber, name: &str) -> Self {
41 let root = FileTreeNode {
42 inode: root_inode,
43 name: name.to_string(),
44 children: Vec::new(),
45 parent: None,
46 blocks: None,
47 additional_blocks: Vec::new(),
48 link: None,
49 };
50 Self {
51 nodes: vec![root],
52 root: 0,
53 }
54 }
55
56 #[inline]
58 pub fn root(&self) -> usize {
59 self.root
60 }
61
62 #[inline]
64 pub fn node(&self, idx: usize) -> &FileTreeNode {
65 &self.nodes[idx]
66 }
67
68 #[inline]
70 pub fn node_mut(&mut self, idx: usize) -> &mut FileTreeNode {
71 &mut self.nodes[idx]
72 }
73
74 pub fn lookup(&self, path: &Path) -> Option<usize> {
80 let mut current = self.root;
81
82 for component in path.components() {
83 let name = component.as_os_str().to_str()?;
84
85 if name == "/" || name.is_empty() {
87 continue;
88 }
89
90 let node = &self.nodes[current];
91 let found = node.children.iter().find(|&&child_idx| {
92 self.nodes[child_idx].name == name
93 });
94
95 match found {
96 Some(&child_idx) => current = child_idx,
97 None => return None,
98 }
99 }
100
101 Some(current)
102 }
103
104 pub fn add_child(&mut self, parent: usize, mut node: FileTreeNode) -> usize {
106 let idx = self.nodes.len();
107 node.parent = Some(parent);
108 self.nodes.push(node);
109 self.nodes[parent].children.push(idx);
110 idx
111 }
112
113 pub fn remove_child(&mut self, parent: usize, name: &str) {
118 let pos = self.nodes[parent]
119 .children
120 .iter()
121 .position(|&child_idx| self.nodes[child_idx].name == name);
122
123 if let Some(pos) = pos {
124 self.nodes[parent].children.remove(pos);
125 }
126 }
127
128 pub fn node_path(&self, idx: usize) -> PathBuf {
130 let mut parts = Vec::new();
131 let mut current = idx;
132
133 loop {
134 parts.push(self.nodes[current].name.as_str());
135 match self.nodes[current].parent {
136 Some(parent) => current = parent,
137 None => break,
138 }
139 }
140
141 parts.reverse();
142
143 let mut path = PathBuf::new();
144 for part in &parts {
145 path.push(part);
146 }
147 path
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn test_new_tree_has_root() {
157 let tree = FileTree::new(2, "/");
158 assert_eq!(tree.root(), 0);
159 assert_eq!(tree.node(0).inode, 2);
160 assert_eq!(tree.node(0).name, "/");
161 }
162
163 #[test]
164 fn test_add_and_lookup() {
165 let mut tree = FileTree::new(2, "/");
166 let child = FileTreeNode {
167 inode: 11,
168 name: "etc".to_string(),
169 children: Vec::new(),
170 parent: None,
171 blocks: None,
172 additional_blocks: Vec::new(),
173 link: None,
174 };
175 let etc_idx = tree.add_child(tree.root(), child);
176
177 let grandchild = FileTreeNode {
178 inode: 12,
179 name: "passwd".to_string(),
180 children: Vec::new(),
181 parent: None,
182 blocks: None,
183 additional_blocks: Vec::new(),
184 link: None,
185 };
186 tree.add_child(etc_idx, grandchild);
187
188 assert_eq!(tree.lookup(Path::new("/etc/passwd")), Some(2));
190
191 assert_eq!(tree.lookup(Path::new("etc/passwd")), Some(2));
193
194 assert_eq!(tree.lookup(Path::new("/etc")), Some(1));
196
197 assert_eq!(tree.lookup(Path::new("/")), Some(0));
199
200 assert_eq!(tree.lookup(Path::new("/etc/shadow")), None);
202 }
203
204 #[test]
205 fn test_remove_child() {
206 let mut tree = FileTree::new(2, "/");
207 let child_a = FileTreeNode {
208 inode: 11,
209 name: "a".to_string(),
210 children: Vec::new(),
211 parent: None,
212 blocks: None,
213 additional_blocks: Vec::new(),
214 link: None,
215 };
216 let child_b = FileTreeNode {
217 inode: 12,
218 name: "b".to_string(),
219 children: Vec::new(),
220 parent: None,
221 blocks: None,
222 additional_blocks: Vec::new(),
223 link: None,
224 };
225 tree.add_child(tree.root(), child_a);
226 tree.add_child(tree.root(), child_b);
227
228 assert_eq!(tree.node(tree.root()).children.len(), 2);
229
230 tree.remove_child(tree.root(), "a");
231 assert_eq!(tree.node(tree.root()).children.len(), 1);
232 assert_eq!(tree.node(tree.node(tree.root()).children[0]).name, "b");
233 }
234
235 #[test]
236 fn test_node_path() {
237 let mut tree = FileTree::new(2, "/");
238 let etc = FileTreeNode {
239 inode: 11,
240 name: "etc".to_string(),
241 children: Vec::new(),
242 parent: None,
243 blocks: None,
244 additional_blocks: Vec::new(),
245 link: None,
246 };
247 let etc_idx = tree.add_child(tree.root(), etc);
248
249 let passwd = FileTreeNode {
250 inode: 12,
251 name: "passwd".to_string(),
252 children: Vec::new(),
253 parent: None,
254 blocks: None,
255 additional_blocks: Vec::new(),
256 link: None,
257 };
258 let passwd_idx = tree.add_child(etc_idx, passwd);
259
260 let path = tree.node_path(passwd_idx);
261 assert_eq!(path, PathBuf::from("/etc/passwd"));
262 }
263}