wasmer_vfs/mem_fs/
filesystem.rs

1//! This module contains the [`FileSystem`] type itself.
2
3use super::*;
4use crate::{DirEntry, FileType, FsError, Metadata, OpenOptions, ReadDir, Result};
5use slab::Slab;
6use std::convert::identity;
7use std::ffi::OsString;
8use std::fmt;
9use std::path::{Component, Path, PathBuf};
10use std::sync::{Arc, RwLock};
11
12/// The in-memory file system!
13///
14/// It's a thin wrapper around [`FileSystemInner`]. This `FileSystem`
15/// type can be cloned, it's a light copy of the `FileSystemInner`
16/// (which is behind a `Arc` + `RwLock`.
17#[derive(Clone, Default)]
18pub struct FileSystem {
19    pub(super) inner: Arc<RwLock<FileSystemInner>>,
20}
21
22impl crate::FileSystem for FileSystem {
23    fn read_dir(&self, path: &Path) -> Result<ReadDir> {
24        // Read lock.
25        let fs = self.inner.try_read().map_err(|_| FsError::Lock)?;
26
27        // Canonicalize the path.
28        let (path, inode_of_directory) = fs.canonicalize(path)?;
29
30        // Check it's a directory and fetch the immediate children as `DirEntry`.
31        let inode = fs.storage.get(inode_of_directory);
32        let children = match inode {
33            Some(Node::Directory { children, .. }) => children
34                .iter()
35                .filter_map(|inode| fs.storage.get(*inode))
36                .map(|node| DirEntry {
37                    path: {
38                        let mut entry_path = path.to_path_buf();
39                        entry_path.push(node.name());
40
41                        entry_path
42                    },
43                    metadata: Ok(node.metadata().clone()),
44                })
45                .collect(),
46
47            _ => return Err(FsError::InvalidInput),
48        };
49
50        Ok(ReadDir::new(children))
51    }
52
53    fn create_dir(&self, path: &Path) -> Result<()> {
54        let (inode_of_parent, name_of_directory) = {
55            // Read lock.
56            let fs = self.inner.try_read().map_err(|_| FsError::Lock)?;
57
58            // Canonicalize the path without checking the path exists,
59            // because it's about to be created.
60            let path = fs.canonicalize_without_inode(path)?;
61
62            // Check the path has a parent.
63            let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?;
64
65            // Check the directory name.
66            let name_of_directory = path
67                .file_name()
68                .ok_or(FsError::InvalidInput)?
69                .to_os_string();
70
71            // Find the parent inode.
72            let inode_of_parent = fs.inode_of_parent(parent_of_path)?;
73
74            (inode_of_parent, name_of_directory)
75        };
76
77        {
78            // Write lock.
79            let mut fs = self.inner.try_write().map_err(|_| FsError::Lock)?;
80
81            // Creating the directory in the storage.
82            let inode_of_directory = fs.storage.vacant_entry().key();
83            let real_inode_of_directory = fs.storage.insert(Node::Directory {
84                inode: inode_of_directory,
85                name: name_of_directory,
86                children: Vec::new(),
87                metadata: {
88                    let time = time();
89
90                    Metadata {
91                        ft: FileType {
92                            dir: true,
93                            ..Default::default()
94                        },
95                        accessed: time,
96                        created: time,
97                        modified: time,
98                        len: 0,
99                    }
100                },
101            });
102
103            assert_eq!(
104                inode_of_directory, real_inode_of_directory,
105                "new directory inode should have been correctly calculated",
106            );
107
108            // Adding the new directory to its parent.
109            fs.add_child_to_node(inode_of_parent, inode_of_directory)?;
110        }
111
112        Ok(())
113    }
114
115    fn remove_dir(&self, path: &Path) -> Result<()> {
116        let (inode_of_parent, position, inode_of_directory) = {
117            // Read lock.
118            let fs = self.inner.try_read().map_err(|_| FsError::Lock)?;
119
120            // Canonicalize the path.
121            let (path, _) = fs.canonicalize(path)?;
122
123            // Check the path has a parent.
124            let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?;
125
126            // Check the directory name.
127            let name_of_directory = path
128                .file_name()
129                .ok_or(FsError::InvalidInput)?
130                .to_os_string();
131
132            // Find the parent inode.
133            let inode_of_parent = fs.inode_of_parent(parent_of_path)?;
134
135            // Get the child index to remove in the parent node, in
136            // addition to the inode of the directory to remove.
137            let (position, inode_of_directory) = fs.as_parent_get_position_and_inode_of_directory(
138                inode_of_parent,
139                &name_of_directory,
140                DirectoryMustBeEmpty::Yes,
141            )?;
142
143            (inode_of_parent, position, inode_of_directory)
144        };
145
146        {
147            // Write lock.
148            let mut fs = self.inner.try_write().map_err(|_| FsError::Lock)?;
149
150            // Remove the directory from the storage.
151            fs.storage.remove(inode_of_directory);
152
153            // Remove the child from the parent directory.
154            fs.remove_child_from_node(inode_of_parent, position)?;
155        }
156
157        Ok(())
158    }
159
160    fn rename(&self, from: &Path, to: &Path) -> Result<()> {
161        let (
162            (position_of_from, inode, inode_of_from_parent),
163            (inode_of_to_parent, name_of_to),
164            inode_dest,
165        ) = {
166            // Read lock.
167            let fs = self.inner.try_read().map_err(|_| FsError::Lock)?;
168
169            let from = fs.canonicalize_without_inode(from)?;
170            let to = fs.canonicalize_without_inode(to)?;
171
172            // Check the paths have parents.
173            let parent_of_from = from.parent().ok_or(FsError::BaseNotDirectory)?;
174            let parent_of_to = to.parent().ok_or(FsError::BaseNotDirectory)?;
175
176            // Check the names.
177            let name_of_from = from
178                .file_name()
179                .ok_or(FsError::InvalidInput)?
180                .to_os_string();
181            let name_of_to = to.file_name().ok_or(FsError::InvalidInput)?.to_os_string();
182
183            // Find the parent inodes.
184            let inode_of_from_parent = fs.inode_of_parent(parent_of_from)?;
185            let inode_of_to_parent = fs.inode_of_parent(parent_of_to)?;
186
187            // Find the inode of the dest file if it exists
188            let maybe_position_and_inode_of_file =
189                fs.as_parent_get_position_and_inode_of_file(inode_of_to_parent, &name_of_to)?;
190
191            // Get the child indexes to update in the parent nodes, in
192            // addition to the inode of the directory to update.
193            let (position_of_from, inode) = fs
194                .as_parent_get_position_and_inode(inode_of_from_parent, &name_of_from)?
195                .ok_or(FsError::NotAFile)?;
196
197            (
198                (position_of_from, inode, inode_of_from_parent),
199                (inode_of_to_parent, name_of_to),
200                maybe_position_and_inode_of_file,
201            )
202        };
203
204        {
205            // Write lock.
206            let mut fs = self.inner.try_write().map_err(|_| FsError::Lock)?;
207
208            if let Some((position, inode_of_file)) = inode_dest {
209                // Remove the file from the storage.
210                fs.storage.remove(inode_of_file);
211
212                // Remove the child from the parent directory.
213                fs.remove_child_from_node(inode_of_to_parent, position)?;
214            }
215
216            // Update the file name, and update the modified time.
217            fs.update_node_name(inode, name_of_to)?;
218
219            // The parents are different. Let's update them.
220            if inode_of_from_parent != inode_of_to_parent {
221                // Remove the file from its parent, and update the
222                // modified time.
223                fs.remove_child_from_node(inode_of_from_parent, position_of_from)?;
224
225                // Add the file to its new parent, and update the modified
226                // time.
227                fs.add_child_to_node(inode_of_to_parent, inode)?;
228            }
229            // Otherwise, we need to at least update the modified time of the parent.
230            else {
231                let inode = fs.storage.get_mut(inode_of_from_parent);
232                match inode {
233                    Some(Node::Directory {
234                        metadata: Metadata { modified, .. },
235                        ..
236                    }) => *modified = time(),
237                    _ => return Err(FsError::UnknownError),
238                }
239            }
240        }
241
242        Ok(())
243    }
244
245    fn metadata(&self, path: &Path) -> Result<Metadata> {
246        // Read lock.
247        let fs = self.inner.try_read().map_err(|_| FsError::Lock)?;
248
249        Ok(fs
250            .storage
251            .get(fs.inode_of(path)?)
252            .ok_or(FsError::UnknownError)?
253            .metadata()
254            .clone())
255    }
256
257    fn remove_file(&self, path: &Path) -> Result<()> {
258        let (inode_of_parent, position, inode_of_file) = {
259            // Read lock.
260            let fs = self.inner.try_read().map_err(|_| FsError::Lock)?;
261
262            // Canonicalize the path.
263            let path = fs.canonicalize_without_inode(path)?;
264
265            // Check the path has a parent.
266            let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?;
267
268            // Check the file name.
269            let name_of_file = path
270                .file_name()
271                .ok_or(FsError::InvalidInput)?
272                .to_os_string();
273
274            // Find the parent inode.
275            let inode_of_parent = fs.inode_of_parent(parent_of_path)?;
276
277            // Find the inode of the file if it exists, along with its position.
278            let maybe_position_and_inode_of_file =
279                fs.as_parent_get_position_and_inode_of_file(inode_of_parent, &name_of_file)?;
280
281            match maybe_position_and_inode_of_file {
282                Some((position, inode_of_file)) => (inode_of_parent, position, inode_of_file),
283                None => return Err(FsError::NotAFile),
284            }
285        };
286
287        {
288            // Write lock.
289            let mut fs = self.inner.try_write().map_err(|_| FsError::Lock)?;
290
291            // Remove the file from the storage.
292            fs.storage.remove(inode_of_file);
293
294            // Remove the child from the parent directory.
295            fs.remove_child_from_node(inode_of_parent, position)?;
296        }
297
298        Ok(())
299    }
300
301    fn new_open_options(&self) -> OpenOptions {
302        OpenOptions::new(Box::new(FileOpener {
303            filesystem: self.clone(),
304        }))
305    }
306}
307
308impl fmt::Debug for FileSystem {
309    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
310        let fs: &FileSystemInner = &self.inner.read().unwrap();
311
312        fs.fmt(formatter)
313    }
314}
315
316/// The core of the file system. It contains a collection of `Node`s,
317/// indexed by their respective `Inode` in a slab.
318pub(super) struct FileSystemInner {
319    pub(super) storage: Slab<Node>,
320}
321
322impl FileSystemInner {
323    /// Get the inode associated to a path if it exists.
324    pub(super) fn inode_of(&self, path: &Path) -> Result<Inode> {
325        // SAFETY: The root node always exists, so it's safe to unwrap here.
326        let mut node = self.storage.get(ROOT_INODE).unwrap();
327        let mut components = path.components();
328
329        match components.next() {
330            Some(Component::RootDir) | None => {}
331            _ => return Err(FsError::BaseNotDirectory),
332        }
333
334        for component in components {
335            node = match node {
336                Node::Directory { children, .. } => children
337                    .iter()
338                    .filter_map(|inode| self.storage.get(*inode))
339                    .find(|node| node.name() == component.as_os_str())
340                    .ok_or(FsError::NotAFile)?,
341                _ => return Err(FsError::BaseNotDirectory),
342            };
343        }
344
345        Ok(node.inode())
346    }
347
348    /// Get the inode associated to a “parent path”. The returned
349    /// inode necessarily represents a directory.
350    pub(super) fn inode_of_parent(&self, parent_path: &Path) -> Result<Inode> {
351        let inode_of_parent = self.inode_of(parent_path)?;
352
353        // Ensure it is a directory.
354        match self.storage.get(inode_of_parent) {
355            Some(Node::Directory { .. }) => Ok(inode_of_parent),
356            _ => Err(FsError::BaseNotDirectory),
357        }
358    }
359
360    /// From the inode of a parent node (so, a directory), returns the
361    /// child index of `name_of_directory` along with its inode.
362    pub(super) fn as_parent_get_position_and_inode_of_directory(
363        &self,
364        inode_of_parent: Inode,
365        name_of_directory: &OsString,
366        directory_must_be_empty: DirectoryMustBeEmpty,
367    ) -> Result<(usize, Inode)> {
368        match self.storage.get(inode_of_parent) {
369            Some(Node::Directory { children, .. }) => children
370                .iter()
371                .enumerate()
372                .filter_map(|(nth, inode)| self.storage.get(*inode).map(|node| (nth, node)))
373                .find_map(|(nth, node)| match node {
374                    Node::Directory {
375                        inode,
376                        name,
377                        children,
378                        ..
379                    } if name.as_os_str() == name_of_directory => {
380                        if directory_must_be_empty.no() || children.is_empty() {
381                            Some(Ok((nth, *inode)))
382                        } else {
383                            Some(Err(FsError::DirectoryNotEmpty))
384                        }
385                    }
386
387                    _ => None,
388                })
389                .ok_or(FsError::InvalidInput)
390                .and_then(identity), // flatten
391            _ => Err(FsError::BaseNotDirectory),
392        }
393    }
394
395    /// From the inode of a parent node (so, a directory), returns the
396    /// child index of `name_of_file` along with its inode.
397    pub(super) fn as_parent_get_position_and_inode_of_file(
398        &self,
399        inode_of_parent: Inode,
400        name_of_file: &OsString,
401    ) -> Result<Option<(usize, Inode)>> {
402        match self.storage.get(inode_of_parent) {
403            Some(Node::Directory { children, .. }) => children
404                .iter()
405                .enumerate()
406                .filter_map(|(nth, inode)| self.storage.get(*inode).map(|node| (nth, node)))
407                .find_map(|(nth, node)| match node {
408                    Node::File { inode, name, .. } if name.as_os_str() == name_of_file => {
409                        Some(Some((nth, *inode)))
410                    }
411
412                    _ => None,
413                })
414                .or(Some(None))
415                .ok_or(FsError::InvalidInput),
416
417            _ => Err(FsError::BaseNotDirectory),
418        }
419    }
420
421    /// From the inode of a parent node (so, a directory), returns the
422    /// child index of `name_of` along with its inode, whatever the
423    /// type of inode is (directory or file).
424    fn as_parent_get_position_and_inode(
425        &self,
426        inode_of_parent: Inode,
427        name_of: &OsString,
428    ) -> Result<Option<(usize, Inode)>> {
429        match self.storage.get(inode_of_parent) {
430            Some(Node::Directory { children, .. }) => children
431                .iter()
432                .enumerate()
433                .filter_map(|(nth, inode)| self.storage.get(*inode).map(|node| (nth, node)))
434                .find_map(|(nth, node)| match node {
435                    Node::File { inode, name, .. } | Node::Directory { inode, name, .. }
436                        if name.as_os_str() == name_of =>
437                    {
438                        Some(Some((nth, *inode)))
439                    }
440
441                    _ => None,
442                })
443                .or(Some(None))
444                .ok_or(FsError::InvalidInput),
445
446            _ => Err(FsError::BaseNotDirectory),
447        }
448    }
449
450    /// Set a new name for the node represented by `inode`.
451    pub(super) fn update_node_name(&mut self, inode: Inode, new_name: OsString) -> Result<()> {
452        let node = self.storage.get_mut(inode).ok_or(FsError::UnknownError)?;
453
454        node.set_name(new_name);
455        node.metadata_mut().modified = time();
456
457        Ok(())
458    }
459
460    /// Add a child to a directory node represented by `inode`.
461    ///
462    /// This function also updates the modified time of the directory.
463    ///
464    /// # Safety
465    ///
466    /// `inode` must represents an existing directory.
467    pub(super) fn add_child_to_node(&mut self, inode: Inode, new_child: Inode) -> Result<()> {
468        match self.storage.get_mut(inode) {
469            Some(Node::Directory {
470                children,
471                metadata: Metadata { modified, .. },
472                ..
473            }) => {
474                children.push(new_child);
475                *modified = time();
476
477                Ok(())
478            }
479            _ => Err(FsError::UnknownError),
480        }
481    }
482
483    /// Remove the child at position `position` of a directory node
484    /// represented by `inode`.
485    ///
486    /// This function also updates the modified time of the directory.
487    ///
488    /// # Safety
489    ///
490    /// `inode` must represents an existing directory.
491    pub(super) fn remove_child_from_node(&mut self, inode: Inode, position: usize) -> Result<()> {
492        match self.storage.get_mut(inode) {
493            Some(Node::Directory {
494                children,
495                metadata: Metadata { modified, .. },
496                ..
497            }) => {
498                children.remove(position);
499                *modified = time();
500
501                Ok(())
502            }
503            _ => Err(FsError::UnknownError),
504        }
505    }
506
507    /// Canonicalize a path, i.e. try to resolve to a canonical,
508    /// absolute form of the path with all intermediate components
509    /// normalized:
510    ///
511    /// * A path must starts with a root (`/`),
512    /// * A path can contain `..` or `.` components,
513    /// * A path must not contain a Windows prefix (`C:` or `\\server`),
514    /// * A normalized path exists in the file system.
515    pub(super) fn canonicalize(&self, path: &Path) -> Result<(PathBuf, Inode)> {
516        let new_path = self.canonicalize_without_inode(path)?;
517        let inode = self.inode_of(&new_path)?;
518
519        Ok((new_path, inode))
520    }
521
522    /// Like `Self::canonicalize` but without returning the inode of
523    /// the path, which means that there is no guarantee that the path
524    /// exists in the file system.
525    pub(super) fn canonicalize_without_inode(&self, path: &Path) -> Result<PathBuf> {
526        let mut components = path.components();
527
528        match components.next() {
529            Some(Component::RootDir) => {}
530            _ => return Err(FsError::InvalidInput),
531        }
532
533        let mut new_path = PathBuf::with_capacity(path.as_os_str().len());
534        new_path.push("/");
535
536        for component in components {
537            match component {
538                // That's an error to get a `RootDir` a second time.
539                Component::RootDir => return Err(FsError::UnknownError),
540
541                // Nothing to do on `new_path`.
542                Component::CurDir => (),
543
544                // Pop the lastly inserted component on `new_path` if
545                // any, otherwise it's an error.
546                Component::ParentDir => {
547                    if !new_path.pop() {
548                        return Err(FsError::InvalidInput);
549                    }
550                }
551
552                // A normal
553                Component::Normal(name) => {
554                    new_path.push(name);
555                }
556
557                // We don't support Windows path prefix.
558                Component::Prefix(_) => return Err(FsError::InvalidInput),
559            }
560        }
561
562        Ok(new_path)
563    }
564}
565
566impl fmt::Debug for FileSystemInner {
567    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
568        writeln!(
569            formatter,
570            "\n{inode:<8}    {ty:<4}    name",
571            inode = "inode",
572            ty = "type",
573        )?;
574
575        fn debug(
576            nodes: Vec<&Node>,
577            slf: &FileSystemInner,
578            formatter: &mut fmt::Formatter<'_>,
579            indentation: usize,
580        ) -> fmt::Result {
581            for node in nodes {
582                writeln!(
583                    formatter,
584                    "{inode:<8}    {ty:<4}   {indentation_symbol:indentation_width$}{name}",
585                    inode = node.inode(),
586                    ty = match node {
587                        Node::File { .. } => "file",
588                        Node::Directory { .. } => "dir",
589                    },
590                    name = node.name().to_string_lossy(),
591                    indentation_symbol = " ",
592                    indentation_width = indentation * 2 + 1,
593                )?;
594
595                if let Node::Directory { children, .. } = node {
596                    debug(
597                        children
598                            .iter()
599                            .filter_map(|inode| slf.storage.get(*inode))
600                            .collect(),
601                        slf,
602                        formatter,
603                        indentation + 1,
604                    )?;
605                }
606            }
607
608            Ok(())
609        }
610
611        debug(
612            vec![self.storage.get(ROOT_INODE).unwrap()],
613            self,
614            formatter,
615            0,
616        )
617    }
618}
619
620impl Default for FileSystemInner {
621    fn default() -> Self {
622        let time = time();
623
624        let mut slab = Slab::new();
625        slab.insert(Node::Directory {
626            inode: ROOT_INODE,
627            name: OsString::from("/"),
628            children: Vec::new(),
629            metadata: Metadata {
630                ft: FileType {
631                    dir: true,
632                    ..Default::default()
633                },
634                accessed: time,
635                created: time,
636                modified: time,
637                len: 0,
638            },
639        });
640
641        Self { storage: slab }
642    }
643}
644
645#[cfg(test)]
646mod test_filesystem {
647    use crate::{mem_fs::*, DirEntry, FileSystem as FS, FileType, FsError};
648
649    macro_rules! path {
650        ($path:expr) => {
651            std::path::Path::new($path)
652        };
653
654        (buf $path:expr) => {
655            std::path::PathBuf::from($path)
656        };
657    }
658
659    #[test]
660    fn test_new_filesystem() {
661        let fs = FileSystem::default();
662        let fs_inner = fs.inner.read().unwrap();
663
664        assert_eq!(fs_inner.storage.len(), 1, "storage has a root");
665        assert!(
666            matches!(
667                fs_inner.storage.get(ROOT_INODE),
668                Some(Node::Directory {
669                    inode: ROOT_INODE,
670                    name,
671                    children,
672                    ..
673                }) if name == "/" && children.is_empty(),
674            ),
675            "storage has a well-defined root",
676        );
677    }
678
679    #[test]
680    fn test_create_dir() {
681        let fs = FileSystem::default();
682
683        assert_eq!(
684            fs.create_dir(path!("/")),
685            Err(FsError::BaseNotDirectory),
686            "creating a directory that has no parent",
687        );
688
689        assert_eq!(fs.create_dir(path!("/foo")), Ok(()), "creating a directory",);
690
691        {
692            let fs_inner = fs.inner.read().unwrap();
693            assert_eq!(
694                fs_inner.storage.len(),
695                2,
696                "storage contains the new directory"
697            );
698            assert!(
699                matches!(
700                    fs_inner.storage.get(ROOT_INODE),
701                    Some(Node::Directory {
702                        inode: ROOT_INODE,
703                        name,
704                        children,
705                        ..
706                    }) if name == "/" && children == &[1]
707                ),
708                "the root is updated and well-defined",
709            );
710            assert!(
711                matches!(
712                    fs_inner.storage.get(1),
713                    Some(Node::Directory {
714                        inode: 1,
715                        name,
716                        children,
717                        ..
718                    }) if name == "foo" && children.is_empty(),
719                ),
720                "the new directory is well-defined",
721            );
722        }
723
724        assert_eq!(
725            fs.create_dir(path!("/foo/bar")),
726            Ok(()),
727            "creating a sub-directory",
728        );
729
730        {
731            let fs_inner = fs.inner.read().unwrap();
732            assert_eq!(
733                fs_inner.storage.len(),
734                3,
735                "storage contains the new sub-directory",
736            );
737            assert!(
738                matches!(
739                    fs_inner.storage.get(ROOT_INODE),
740                    Some(Node::Directory {
741                        inode: ROOT_INODE,
742                        name,
743                        children,
744                        ..
745                    }) if name == "/" && children == &[1]
746                ),
747                "the root is updated again and well-defined",
748            );
749            assert!(
750                matches!(
751                    fs_inner.storage.get(1),
752                    Some(Node::Directory {
753                        inode: 1,
754                        name,
755                        children,
756                        ..
757                    }) if name == "foo" && children == &[2]
758                ),
759                "the new directory is updated and well-defined",
760            );
761            assert!(
762                matches!(
763                    fs_inner.storage.get(2),
764                    Some(Node::Directory {
765                        inode: 2,
766                        name,
767                        children,
768                        ..
769                    }) if name == "bar" && children.is_empty()
770                ),
771                "the new directory is well-defined",
772            );
773        }
774    }
775
776    #[test]
777    fn test_remove_dir() {
778        let fs = FileSystem::default();
779
780        assert_eq!(
781            fs.remove_dir(path!("/")),
782            Err(FsError::BaseNotDirectory),
783            "removing a directory that has no parent",
784        );
785
786        assert_eq!(
787            fs.remove_dir(path!("/foo")),
788            Err(FsError::NotAFile),
789            "cannot remove a directory that doesn't exist",
790        );
791
792        assert_eq!(fs.create_dir(path!("/foo")), Ok(()), "creating a directory",);
793
794        assert_eq!(
795            fs.create_dir(path!("/foo/bar")),
796            Ok(()),
797            "creating a sub-directory",
798        );
799
800        {
801            let fs_inner = fs.inner.read().unwrap();
802            assert_eq!(
803                fs_inner.storage.len(),
804                3,
805                "storage contains all the directories",
806            );
807        }
808
809        assert_eq!(
810            fs.remove_dir(path!("/foo")),
811            Err(FsError::DirectoryNotEmpty),
812            "removing a directory that has children",
813        );
814
815        assert_eq!(
816            fs.remove_dir(path!("/foo/bar")),
817            Ok(()),
818            "removing a sub-directory",
819        );
820
821        assert_eq!(fs.remove_dir(path!("/foo")), Ok(()), "removing a directory",);
822
823        {
824            let fs_inner = fs.inner.read().unwrap();
825            assert_eq!(
826                fs_inner.storage.len(),
827                1,
828                "storage contains all the directories",
829            );
830        }
831    }
832
833    #[test]
834    fn test_rename() {
835        let fs = FileSystem::default();
836
837        assert_eq!(
838            fs.rename(path!("/"), path!("/bar")),
839            Err(FsError::BaseNotDirectory),
840            "renaming a directory that has no parent",
841        );
842        assert_eq!(
843            fs.rename(path!("/foo"), path!("/")),
844            Err(FsError::BaseNotDirectory),
845            "renaming to a directory that has no parent",
846        );
847
848        assert_eq!(fs.create_dir(path!("/foo")), Ok(()));
849        assert_eq!(fs.create_dir(path!("/foo/qux")), Ok(()));
850
851        assert_eq!(
852            fs.rename(path!("/foo"), path!("/bar/baz")),
853            Err(FsError::NotAFile),
854            "renaming to a directory that has parent that doesn't exist",
855        );
856
857        assert_eq!(fs.create_dir(path!("/bar")), Ok(()));
858
859        assert!(
860            matches!(
861                fs.new_open_options()
862                    .write(true)
863                    .create_new(true)
864                    .open(path!("/bar/hello1.txt")),
865                Ok(_),
866            ),
867            "creating a new file (`hello1.txt`)",
868        );
869        assert!(
870            matches!(
871                fs.new_open_options()
872                    .write(true)
873                    .create_new(true)
874                    .open(path!("/bar/hello2.txt")),
875                Ok(_),
876            ),
877            "creating a new file (`hello2.txt`)",
878        );
879
880        {
881            let fs_inner = fs.inner.read().unwrap();
882
883            assert_eq!(fs_inner.storage.len(), 6, "storage has all files");
884            assert!(
885                matches!(
886                    fs_inner.storage.get(ROOT_INODE),
887                    Some(Node::Directory {
888                        inode: ROOT_INODE,
889                        name,
890                        children,
891                        ..
892                    }) if name == "/" && children == &[1, 3]
893                ),
894                "`/` contains `foo` and `bar`",
895            );
896            assert!(
897                matches!(
898                    fs_inner.storage.get(1),
899                    Some(Node::Directory {
900                        inode: 1,
901                        name,
902                        children,
903                        ..
904                    }) if name == "foo" && children == &[2]
905                ),
906                "`foo` contains `qux`",
907            );
908            assert!(
909                matches!(
910                    fs_inner.storage.get(2),
911                    Some(Node::Directory {
912                        inode: 2,
913                        name,
914                        children,
915                        ..
916                    }) if name == "qux" && children.is_empty()
917                ),
918                "`qux` is empty",
919            );
920            assert!(
921                matches!(
922                    fs_inner.storage.get(3),
923                    Some(Node::Directory {
924                        inode: 3,
925                        name,
926                        children,
927                        ..
928                    }) if name == "bar" && children == &[4, 5]
929                ),
930                "`bar` is contains `hello.txt`",
931            );
932            assert!(
933                matches!(
934                    fs_inner.storage.get(4),
935                    Some(Node::File {
936                        inode: 4,
937                        name,
938                        ..
939                    }) if name == "hello1.txt"
940                ),
941                "`hello1.txt` exists",
942            );
943            assert!(
944                matches!(
945                    fs_inner.storage.get(5),
946                    Some(Node::File {
947                        inode: 5,
948                        name,
949                        ..
950                    }) if name == "hello2.txt"
951                ),
952                "`hello2.txt` exists",
953            );
954        }
955
956        assert_eq!(
957            fs.rename(path!("/bar/hello2.txt"), path!("/foo/world2.txt")),
958            Ok(()),
959            "renaming (and moving) a file",
960        );
961
962        assert_eq!(
963            fs.rename(path!("/foo"), path!("/bar/baz")),
964            Ok(()),
965            "renaming a directory",
966        );
967
968        assert_eq!(
969            fs.rename(path!("/bar/hello1.txt"), path!("/bar/world1.txt")),
970            Ok(()),
971            "renaming a file (in the same directory)",
972        );
973
974        {
975            let fs_inner = fs.inner.read().unwrap();
976
977            dbg!(&fs_inner);
978
979            assert_eq!(
980                fs_inner.storage.len(),
981                6,
982                "storage has still all directories"
983            );
984            assert!(
985                matches!(
986                    fs_inner.storage.get(ROOT_INODE),
987                    Some(Node::Directory {
988                        inode: ROOT_INODE,
989                        name,
990                        children,
991                        ..
992                    }) if name == "/" && children == &[3]
993                ),
994                "`/` contains `bar`",
995            );
996            assert!(
997                matches!(
998                    fs_inner.storage.get(1),
999                    Some(Node::Directory {
1000                        inode: 1,
1001                        name,
1002                        children,
1003                        ..
1004                    }) if name == "baz" && children == &[2, 5]
1005                ),
1006                "`foo` has been renamed to `baz` and contains `qux` and `world2.txt`",
1007            );
1008            assert!(
1009                matches!(
1010                    fs_inner.storage.get(2),
1011                    Some(Node::Directory {
1012                        inode: 2,
1013                        name,
1014                        children,
1015                        ..
1016                    }) if name == "qux" && children.is_empty()
1017                ),
1018                "`qux` is empty",
1019            );
1020            assert!(
1021                matches!(
1022                    fs_inner.storage.get(3),
1023                    Some(Node::Directory {
1024                        inode: 3,
1025                        name,
1026                        children,
1027                        ..
1028                    }) if name == "bar" && children == &[4, 1]
1029                ),
1030                "`bar` contains `bar` (ex `foo`)  and `world1.txt` (ex `hello1`)",
1031            );
1032            assert!(
1033                matches!(
1034                    fs_inner.storage.get(4),
1035                    Some(Node::File {
1036                        inode: 4,
1037                        name,
1038                        ..
1039                    }) if name == "world1.txt"
1040                ),
1041                "`hello1.txt` has been renamed to `world1.txt`",
1042            );
1043            assert!(
1044                matches!(
1045                    fs_inner.storage.get(5),
1046                    Some(Node::File {
1047                        inode: 5,
1048                        name,
1049                        ..
1050                    }) if name == "world2.txt"
1051                ),
1052                "`hello2.txt` has been renamed to `world2.txt`",
1053            );
1054        }
1055    }
1056
1057    #[test]
1058    fn test_metadata() {
1059        use std::thread::sleep;
1060        use std::time::Duration;
1061
1062        let fs = FileSystem::default();
1063        let root_metadata = fs.metadata(path!("/"));
1064
1065        assert!(matches!(
1066            root_metadata,
1067            Ok(Metadata {
1068                ft: FileType { dir: true, .. },
1069                accessed,
1070                created,
1071                modified,
1072                len: 0
1073            }) if accessed == created && created == modified && modified > 0
1074        ));
1075
1076        assert_eq!(fs.create_dir(path!("/foo")), Ok(()));
1077
1078        let foo_metadata = fs.metadata(path!("/foo"));
1079        assert!(foo_metadata.is_ok());
1080        let foo_metadata = foo_metadata.unwrap();
1081
1082        assert!(matches!(
1083            foo_metadata,
1084            Metadata {
1085                ft: FileType { dir: true, .. },
1086                accessed,
1087                created,
1088                modified,
1089                len: 0
1090            } if accessed == created && created == modified && modified > 0
1091        ));
1092
1093        sleep(Duration::from_secs(3));
1094
1095        assert_eq!(fs.rename(path!("/foo"), path!("/bar")), Ok(()));
1096
1097        assert!(
1098            matches!(
1099                fs.metadata(path!("/bar")),
1100                Ok(Metadata {
1101                    ft: FileType { dir: true, .. },
1102                    accessed,
1103                    created,
1104                    modified,
1105                    len: 0
1106                }) if
1107                    accessed == foo_metadata.accessed &&
1108                    created == foo_metadata.created &&
1109                    modified > foo_metadata.modified
1110            ),
1111            "the modified time is updated when file is renamed",
1112        );
1113        assert!(
1114            matches!(
1115                fs.metadata(path!("/")),
1116                Ok(Metadata {
1117                    ft: FileType { dir: true, .. },
1118                    accessed,
1119                    created,
1120                    modified,
1121                    len: 0
1122                }) if
1123                    accessed == foo_metadata.accessed &&
1124                    created == foo_metadata.created &&
1125                    modified > foo_metadata.modified
1126            ),
1127            "the modified time of the parent is updated when file is renamed",
1128        );
1129    }
1130
1131    #[test]
1132    fn test_remove_file() {
1133        let fs = FileSystem::default();
1134
1135        assert!(
1136            matches!(
1137                fs.new_open_options()
1138                    .write(true)
1139                    .create_new(true)
1140                    .open(path!("/foo.txt")),
1141                Ok(_)
1142            ),
1143            "creating a new file",
1144        );
1145
1146        {
1147            let fs_inner = fs.inner.read().unwrap();
1148
1149            assert_eq!(fs_inner.storage.len(), 2, "storage has all files");
1150            assert!(
1151                matches!(
1152                    fs_inner.storage.get(ROOT_INODE),
1153                    Some(Node::Directory {
1154                        inode: ROOT_INODE,
1155                        name,
1156                        children,
1157                        ..
1158                    }) if name == "/" && children == &[1]
1159                ),
1160                "`/` contains `foo.txt`",
1161            );
1162            assert!(
1163                matches!(
1164                    fs_inner.storage.get(1),
1165                    Some(Node::File {
1166                        inode: 1,
1167                        name,
1168                        ..
1169                    }) if name == "foo.txt"
1170                ),
1171                "`foo.txt` exists and is a file",
1172            );
1173        }
1174
1175        assert_eq!(
1176            fs.remove_file(path!("/foo.txt")),
1177            Ok(()),
1178            "removing a file that exists",
1179        );
1180
1181        {
1182            let fs_inner = fs.inner.read().unwrap();
1183
1184            assert_eq!(fs_inner.storage.len(), 1, "storage no longer has the file");
1185            assert!(
1186                matches!(
1187                    fs_inner.storage.get(ROOT_INODE),
1188                    Some(Node::Directory {
1189                        inode: ROOT_INODE,
1190                        name,
1191                        children,
1192                        ..
1193                    }) if name == "/" && children.is_empty()
1194                ),
1195                "`/` is empty",
1196            );
1197        }
1198
1199        assert_eq!(
1200            fs.remove_file(path!("/foo.txt")),
1201            Err(FsError::NotAFile),
1202            "removing a file that exists",
1203        );
1204    }
1205
1206    #[test]
1207    fn test_readdir() {
1208        let fs = FileSystem::default();
1209
1210        assert_eq!(fs.create_dir(path!("/foo")), Ok(()), "creating `foo`");
1211        assert_eq!(fs.create_dir(path!("/foo/sub")), Ok(()), "creating `sub`");
1212        assert_eq!(fs.create_dir(path!("/bar")), Ok(()), "creating `bar`");
1213        assert_eq!(fs.create_dir(path!("/baz")), Ok(()), "creating `bar`");
1214        assert!(
1215            matches!(
1216                fs.new_open_options()
1217                    .write(true)
1218                    .create_new(true)
1219                    .open(path!("/a.txt")),
1220                Ok(_)
1221            ),
1222            "creating `a.txt`",
1223        );
1224        assert!(
1225            matches!(
1226                fs.new_open_options()
1227                    .write(true)
1228                    .create_new(true)
1229                    .open(path!("/b.txt")),
1230                Ok(_)
1231            ),
1232            "creating `b.txt`",
1233        );
1234
1235        let readdir = fs.read_dir(path!("/"));
1236
1237        assert!(readdir.is_ok(), "reading the directory `/`");
1238
1239        let mut readdir = readdir.unwrap();
1240
1241        assert!(
1242            matches!(
1243                readdir.next(),
1244                Some(Ok(DirEntry {
1245                    path,
1246                    metadata: Ok(Metadata { ft, .. }),
1247                }))
1248                    if path == path!(buf "/foo") && ft.is_dir()
1249            ),
1250            "checking entry #1",
1251        );
1252        assert!(
1253            matches!(
1254                readdir.next(),
1255                Some(Ok(DirEntry {
1256                    path,
1257                    metadata: Ok(Metadata { ft, .. }),
1258                }))
1259                    if path == path!(buf "/bar") && ft.is_dir()
1260            ),
1261            "checking entry #2",
1262        );
1263        assert!(
1264            matches!(
1265                readdir.next(),
1266                Some(Ok(DirEntry {
1267                    path,
1268                    metadata: Ok(Metadata { ft, .. }),
1269                }))
1270                    if path == path!(buf "/baz") && ft.is_dir()
1271            ),
1272            "checking entry #3",
1273        );
1274        assert!(
1275            matches!(
1276                readdir.next(),
1277                Some(Ok(DirEntry {
1278                    path,
1279                    metadata: Ok(Metadata { ft, .. }),
1280                }))
1281                    if path == path!(buf "/a.txt") && ft.is_file()
1282            ),
1283            "checking entry #4",
1284        );
1285        assert!(
1286            matches!(
1287                readdir.next(),
1288                Some(Ok(DirEntry {
1289                    path,
1290                    metadata: Ok(Metadata { ft, .. }),
1291                }))
1292                    if path == path!(buf "/b.txt") && ft.is_file()
1293            ),
1294            "checking entry #5",
1295        );
1296        assert!(matches!(readdir.next(), None), "no more entries");
1297    }
1298
1299    #[test]
1300    fn test_canonicalize() {
1301        let fs = FileSystem::default();
1302
1303        assert_eq!(fs.create_dir(path!("/foo")), Ok(()), "creating `foo`");
1304        assert_eq!(fs.create_dir(path!("/foo/bar")), Ok(()), "creating `bar`");
1305        assert_eq!(
1306            fs.create_dir(path!("/foo/bar/baz")),
1307            Ok(()),
1308            "creating `baz`",
1309        );
1310        assert_eq!(
1311            fs.create_dir(path!("/foo/bar/baz/qux")),
1312            Ok(()),
1313            "creating `qux`",
1314        );
1315        assert!(
1316            matches!(
1317                fs.new_open_options()
1318                    .write(true)
1319                    .create_new(true)
1320                    .open(path!("/foo/bar/baz/qux/hello.txt")),
1321                Ok(_)
1322            ),
1323            "creating `hello.txt`",
1324        );
1325
1326        let fs_inner = fs.inner.read().unwrap();
1327
1328        assert_eq!(
1329            fs_inner.canonicalize(path!("/")),
1330            Ok((path!(buf "/"), ROOT_INODE)),
1331            "canonicalizing `/`",
1332        );
1333        assert_eq!(
1334            fs_inner.canonicalize(path!("foo")),
1335            Err(FsError::InvalidInput),
1336            "canonicalizing `foo`",
1337        );
1338        assert_eq!(
1339            fs_inner.canonicalize(path!("/././././foo/")),
1340            Ok((path!(buf "/foo"), 1)),
1341            "canonicalizing `/././././foo/`",
1342        );
1343        assert_eq!(
1344            fs_inner.canonicalize(path!("/foo/bar//")),
1345            Ok((path!(buf "/foo/bar"), 2)),
1346            "canonicalizing `/foo/bar//`",
1347        );
1348        assert_eq!(
1349            fs_inner.canonicalize(path!("/foo/bar/../bar")),
1350            Ok((path!(buf "/foo/bar"), 2)),
1351            "canonicalizing `/foo/bar/../bar`",
1352        );
1353        assert_eq!(
1354            fs_inner.canonicalize(path!("/foo/bar/../..")),
1355            Ok((path!(buf "/"), ROOT_INODE)),
1356            "canonicalizing `/foo/bar/../..`",
1357        );
1358        assert_eq!(
1359            fs_inner.canonicalize(path!("/foo/bar/../../..")),
1360            Err(FsError::InvalidInput),
1361            "canonicalizing `/foo/bar/../../..`",
1362        );
1363        assert_eq!(
1364            fs_inner.canonicalize(path!("C:/foo/")),
1365            Err(FsError::InvalidInput),
1366            "canonicalizing `C:/foo/`",
1367        );
1368        assert_eq!(
1369            fs_inner.canonicalize(path!(
1370                "/foo/./../foo/bar/../../foo/bar/./baz/./../baz/qux/../../baz/./qux/hello.txt"
1371            )),
1372            Ok((path!(buf "/foo/bar/baz/qux/hello.txt"), 5)),
1373            "canonicalizing a crazily stupid path name",
1374        );
1375    }
1376}
1377
1378#[allow(dead_code)] // The `No` variant.
1379pub(super) enum DirectoryMustBeEmpty {
1380    Yes,
1381    No,
1382}
1383
1384impl DirectoryMustBeEmpty {
1385    pub(super) fn yes(&self) -> bool {
1386        matches!(self, Self::Yes)
1387    }
1388
1389    pub(super) fn no(&self) -> bool {
1390        !self.yes()
1391    }
1392}