1use crate::{FileAttributes, FileType, INode, SetFileAttributes};
2
3use std::collections::HashMap;
4use std::ffi::{OsStr, OsString};
5use std::path::Path;
6
7pub type DirChildren = HashMap<OsString, INode>;
8pub const ROOT_INODE: INode = INode(1);
9
10pub trait Attributable {
11 fn getattrs(&self) -> FileAttributes;
12}
13
14pub trait Filelike: Attributable {}
16
17#[derive(Debug)]
18pub struct Directory {
19 children: DirChildren,
20 attrs: FileAttributes,
21}
22
23impl Directory {
24 pub fn apply_attrs(&mut self, attrs: SetFileAttributes) {
25 self.attrs.apply_attrs(attrs)
26 }
27}
28
29impl Default for Directory {
30 fn default() -> Directory {
31 Directory {
32 children: DirChildren::default(),
33 attrs: FileAttributes::builder()
34 .mode(libc::S_IFDIR)
35 .size(std::mem::size_of::<Directory>() as u64)
36 .build(),
37 }
38 }
39}
40
41impl Attributable for Directory {
42 fn getattrs(&self) -> FileAttributes {
43 self.attrs
44 }
45}
46
47impl Directory {
48 pub fn get(&self, name: &OsStr) -> Option<&INode> {
49 self.children.get(name)
50 }
51
52 pub fn children(&self) -> DirIter<'_> {
53 DirIter {
54 iter: self.children.iter(),
55 }
56 }
57}
58
59pub struct DirIter<'a> {
60 iter: std::collections::hash_map::Iter<'a, OsString, INode>,
61}
62
63impl<'a> Iterator for DirIter<'a> {
64 type Item = (&'a OsString, INode);
65
66 fn next(&mut self) -> Option<Self::Item> {
67 self.iter.next().map(|(name, ino)| (name, *ino))
69 }
70}
71
72#[derive(Debug)]
73pub struct INodeEntry<F> {
74 parent: Option<INode>,
75 kind: INodeKind<F>,
76}
77
78impl<F> INodeEntry<F> {
79 pub fn kind(&self) -> &INodeKind<F> {
80 &self.kind
81 }
82
83 pub fn kind_mut(&mut self) -> &mut INodeKind<F> {
84 &mut self.kind
85 }
86
87 pub fn file_type(&self) -> FileType {
88 match self.kind() {
89 INodeKind::Directory(_) => FileType::Directory,
90 INodeKind::File(_) => FileType::Regular,
91 }
92 }
93
94 pub fn parent(&self) -> Option<INode> {
95 self.parent
96 }
97
98 pub fn as_dir(&self) -> Option<&Directory> {
99 match self.kind() {
100 INodeKind::Directory(ref dir) => Some(dir),
101 _ => None,
102 }
103 }
104
105 pub fn as_dir_mut(&mut self) -> Option<&mut Directory> {
106 match self.kind_mut() {
107 INodeKind::Directory(dir) => Some(dir),
108 _ => None,
109 }
110 }
111
112 pub fn as_file(&self) -> Option<&F> {
113 match &self.kind() {
114 INodeKind::File(file) => Some(file),
115 _ => None,
116 }
117 }
118
119 pub fn as_file_mut(&mut self) -> Option<&mut F> {
120 match self.kind_mut() {
121 INodeKind::File(file) => Some(file),
122 _ => None,
123 }
124 }
125
126 pub fn children(&self) -> Option<&DirChildren> {
127 match self.kind() {
128 INodeKind::Directory(dir) => Some(&dir.children),
129 _ => None,
130 }
131 }
132}
133
134impl<T: Attributable> INodeEntry<T> {
135 pub fn getattrs(&self) -> FileAttributes {
136 match self.kind() {
137 INodeKind::Directory(dir) => dir.getattrs(),
138 INodeKind::File(file) => file.getattrs(),
139 }
140 }
141}
142
143pub trait IntoINodeEntry<F> {
144 fn with_parent(self, parent: INode) -> INodeEntry<F>;
145}
146
147impl<F: Filelike> IntoINodeEntry<F> for F {
148 fn with_parent(self, parent: INode) -> INodeEntry<F> {
149 INodeEntry {
150 parent: Some(parent),
151 kind: INodeKind::File(self),
152 }
153 }
154}
155
156impl<F> IntoINodeEntry<F> for Directory {
157 fn with_parent(self, parent: INode) -> INodeEntry<F> {
158 INodeEntry {
159 parent: Some(parent),
160 kind: INodeKind::Directory(self),
161 }
162 }
163}
164
165impl<F> IntoINodeEntry<F> for INodeEntry<F> {
166 fn with_parent(mut self, parent: INode) -> INodeEntry<F> {
167 self.parent = Some(parent);
168 self
169 }
170}
171
172#[derive(Debug)]
173pub enum INodeKind<F> {
174 Directory(Directory),
175 File(F),
176}
177
178#[derive(Debug)]
182pub struct INodeTable<F> {
183 map: HashMap<INode, INodeEntry<F>>,
184 cur_ino: INode,
185}
186
187impl<F> INodeTable<F> {
188 pub fn push_entry<E: IntoINodeEntry<F>>(
189 &mut self,
190 parent: INode,
191 name: OsString,
192 entry: E,
193 ) -> Option<INode> {
194 let ino = self.next_open_inode();
195 let parent_dir = self.map.get_mut(&parent)?.as_dir_mut()?;
196
197 parent_dir.children.insert(name, ino);
198 self.map.insert(ino, entry.with_parent(parent));
199
200 Some(ino)
201 }
202
203 pub fn get<T: Into<INode>>(&self, ino: T) -> Option<&INodeEntry<F>> {
204 self.map.get(&ino.into())
205 }
206
207 pub fn get_mut<T: Into<INode>>(&mut self, ino: T) -> Option<&mut INodeEntry<F>> {
208 self.map.get_mut(&ino.into())
209 }
210
211 pub fn lookup<T: AsRef<Path>>(&self, path: T) -> Option<(INode, &INodeEntry<F>)> {
225 let mut parent_ino = ROOT_INODE;
226 let mut parent = self.get(ROOT_INODE)?;
227
228 for component in path.as_ref().components() {
229 let path: &Path = component.as_ref();
230 let path_str = path.to_string_lossy();
231
232 match path_str.as_ref() {
233 "/" if parent_ino == ROOT_INODE => continue, _ => {
235 parent_ino = *parent.as_dir()?.get(path.as_os_str())?;
236 parent = self.get(parent_ino)?;
237 }
238 }
239 }
240
241 let ino = parent_ino;
242 let entry = parent;
243
244 Some((ino, entry))
245 }
246
247 pub fn lookup_mut<T: AsRef<Path>>(&mut self, path: T) -> Option<(INode, &mut INodeEntry<F>)> {
249 let inode = self.lookup(path).map(|x| x.0);
250
251 inode
252 .and_then(|ino| self.get_mut(ino))
253 .map(|x| (inode.unwrap(), x))
254 }
255
256 fn next_open_inode(&mut self) -> INode {
257 let ino = self.cur_ino;
258 self.cur_ino = ino.next_inode();
259 ino
260 }
261}
262
263impl<F> Default for INodeTable<F> {
264 fn default() -> INodeTable<F> {
265 let mut h = HashMap::with_capacity(24);
266 h.insert(
267 ROOT_INODE,
268 INodeEntry {
269 parent: None,
270 kind: INodeKind::Directory(Directory::default()),
271 },
272 );
273
274 INodeTable {
275 map: h,
276 cur_ino: ROOT_INODE.next_inode(),
277 }
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284
285 #[derive(Default, Debug)]
286 struct BlankFile {}
287
288 impl IntoINodeEntry<BlankFile> for BlankFile {
289 fn with_parent(self, parent: INode) -> INodeEntry<BlankFile> {
290 INodeEntry {
291 parent: Some(parent),
292 kind: INodeKind::File(self),
293 }
294 }
295 }
296
297 fn blank_table() -> INodeTable<BlankFile> {
298 INodeTable::<BlankFile>::default()
299 }
300
301 #[test]
302 fn omit_root_slash_lookup() {
303 let mut fs = blank_table();
304 let _ = fs.push_entry(ROOT_INODE, "root file".into(), BlankFile::default());
305 let _ = fs.push_entry(ROOT_INODE, "root dir".into(), Directory::default());
306
307 let file = fs.lookup("/root file").unwrap();
308
309 assert_eq!(
310 fs.lookup("root file").unwrap().0,
311 file.0,
312 "omitting / from paths returns different results"
313 );
314
315 assert!(
316 file.1.as_file().is_some(),
317 "expected file, returned directory"
318 );
319 }
320
321 #[test]
322 fn check_proper_parenting() {
323 let mut fs = blank_table();
324
325 let dir_ino = fs
326 .push_entry(ROOT_INODE, "dir".into(), Directory::default())
327 .unwrap();
328
329 let file_ino = fs
330 .push_entry(dir_ino, "file".into(), BlankFile::default())
331 .unwrap();
332
333 let file = fs
334 .get(file_ino)
335 .expect("file was not added to the inode map");
336
337 assert!(file.parent().is_some(), "file has no parent set");
338
339 assert_eq!(
340 fs.get(file_ino).unwrap().parent().unwrap(),
341 dir_ino,
342 "file's parent is not set correctly"
343 );
344 }
345
346 #[test]
347 fn default_table_has_root() {
348 let fs = INodeTable::<()>::default();
349
350 assert!(fs.get(ROOT_INODE).is_some(), "ROOT_INODE does not exist");
351
352 assert_eq!(
353 fs.get(ROOT_INODE).unwrap().parent(),
354 None,
355 "ROOT_INODE does not have a parent"
356 );
357 }
358
359 #[test]
361 fn ensure_lookup_equals_lookup_mut() {
362 let mut fs = blank_table();
363
364 {
365 let dir1 = fs
367 .push_entry(ROOT_INODE, "dir1".into(), Directory::default())
368 .unwrap();
369
370 let dir2 = fs
371 .push_entry(dir1, "dir2".into(), Directory::default())
372 .unwrap();
373
374 let dir3 = fs
375 .push_entry(dir2, "dir3".into(), Directory::default())
376 .unwrap();
377
378 let _file1 = fs
379 .push_entry(dir3, "file1".into(), BlankFile::default())
380 .unwrap();
381 };
382
383 macro_rules! check {
385 ($path:expr) => {{
386 let path: OsString = ($path).into();
387
388 assert!(fs.lookup(&path).is_some(), concat!(stringify!($path), " could not be looked up"));
389
390 assert_eq!(
391 fs.lookup(&path).unwrap().0,
392 fs.lookup_mut(&path).unwrap().0,
393 concat!(stringify!($path), " differs between immutable and mutable lookups")
394 )
395 }};
396
397 [$path:expr, $($others:expr),*$(,)?] => {{
398 check!($path);
399 check!($($others),+);
400 }};
401 }
402
403 check![
404 "/dir1",
405 "/dir1/dir2",
406 "/dir1/dir2/dir3",
407 "/dir1/dir2/dir3/file1",
408 "dir1",
409 "dir1/dir2",
410 "dir1/dir2/dir3",
411 "dir1/dir2/dir3/file1",
412 ];
413 }
414}