Skip to main content

simply_fuse/
basic.rs

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
14/// Represents an object that acts like a file on the filesystem
15pub 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        // Deref since INode is cheap to copy
68        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/// A generic INodeTable which allows indexing by paths and inodes
179///
180/// Maps `F` as a "File" type
181#[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    /// Looks up a path. Will function with or without a leading slash
212    /// ```
213    /// # use simply_fuse::basic::{ROOT_INODE, INodeTable, Directory, INodeEntry};
214    /// let mut tbl = INodeTable::<()>::default();
215    /// let test_dir_inode = tbl.push_entry(ROOT_INODE, "example directory".into(),
216    /// Directory::default()).unwrap();
217    ///
218    /// let root = tbl.lookup("/").unwrap();
219    /// let test_dir = tbl.lookup("example directory").unwrap();
220    ///
221    /// assert_eq!(root.0, ROOT_INODE);
222    /// assert_eq!(test_dir.0, test_dir_inode);
223    /// ```
224    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, // path starts with "/"
234                _ => {
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    /// See `lookup` for details
248    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    /// This test should never fail. If it does, we likely have some much bigger problems somewhere
360    #[test]
361    fn ensure_lookup_equals_lookup_mut() {
362        let mut fs = blank_table();
363
364        {
365            // ensure we don't use these later
366            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        // totally didn't write this solely to mess around with macros
384        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}