From 9d7499dde51d09b9cacfc351650321d383e466c2 Mon Sep 17 00:00:00 2001 From: Isaac Snow Date: Mon, 3 Jul 2023 19:18:57 -0700 Subject: [PATCH 01/20] wip it compiles --- lib/virtual-fs/src/arc_fs.rs | 4 ++++ lib/virtual-fs/src/empty_fs.rs | 4 ++++ lib/virtual-fs/src/host_fs.rs | 4 ++++ lib/virtual-fs/src/lib.rs | 14 ++++++++++---- lib/virtual-fs/src/mem_fs/filesystem.rs | 4 ++++ lib/virtual-fs/src/overlay_fs.rs | 4 ++++ lib/virtual-fs/src/passthru_fs.rs | 4 ++++ lib/virtual-fs/src/static_fs.rs | 3 +++ lib/virtual-fs/src/tmp_fs.rs | 4 ++++ lib/virtual-fs/src/trace_fs.rs | 5 +++++ lib/virtual-fs/src/union_fs.rs | 3 +++ lib/virtual-fs/src/webc_fs.rs | 3 +++ lib/virtual-fs/src/webc_volume_fs.rs | 4 ++++ 13 files changed, 56 insertions(+), 4 deletions(-) diff --git a/lib/virtual-fs/src/arc_fs.rs b/lib/virtual-fs/src/arc_fs.rs index 734774fc16c..435858b07da 100644 --- a/lib/virtual-fs/src/arc_fs.rs +++ b/lib/virtual-fs/src/arc_fs.rs @@ -38,6 +38,10 @@ impl FileSystem for ArcFileSystem { self.fs.metadata(path) } + fn symlink(&self, original: &Path, link: &Path) -> Result<()> { + self.fs.symlink(original, link) + } + fn symlink_metadata(&self, path: &Path) -> Result { self.fs.symlink_metadata(path) } diff --git a/lib/virtual-fs/src/empty_fs.rs b/lib/virtual-fs/src/empty_fs.rs index 04807ef4bad..2ef1c1e0ae8 100644 --- a/lib/virtual-fs/src/empty_fs.rs +++ b/lib/virtual-fs/src/empty_fs.rs @@ -32,6 +32,10 @@ impl FileSystem for EmptyFileSystem { Err(FsError::EntryNotFound) } + fn symlink(&self, original: &Path, link: &Path) -> Result<()> { + todo!() + } + fn symlink_metadata(&self, path: &Path) -> Result { Err(FsError::EntryNotFound) } diff --git a/lib/virtual-fs/src/host_fs.rs b/lib/virtual-fs/src/host_fs.rs index 2c42327e4fe..36bc89dd3ed 100644 --- a/lib/virtual-fs/src/host_fs.rs +++ b/lib/virtual-fs/src/host_fs.rs @@ -132,6 +132,10 @@ impl crate::FileSystem for FileSystem { .and_then(TryInto::try_into) .map_err(Into::into) } + + fn symlink(&self, original: &Path, link: &Path) -> Result<()> { + todo!() + } } impl TryInto for std::fs::Metadata { diff --git a/lib/virtual-fs/src/lib.rs b/lib/virtual-fs/src/lib.rs index 3a9b4d1cde8..49f3908531e 100644 --- a/lib/virtual-fs/src/lib.rs +++ b/lib/virtual-fs/src/lib.rs @@ -88,11 +88,9 @@ pub trait FileSystem: fmt::Debug + Send + Sync + 'static + Upcastable { fn remove_dir(&self, path: &Path) -> Result<()>; fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<()>>; fn metadata(&self, path: &Path) -> Result; - /// This method gets metadata without following symlinks in the path. - /// Currently identical to `metadata` because symlinks aren't implemented - /// yet. + fn symlink(&self, original: &Path, link: &Path) -> Result<()>; fn symlink_metadata(&self, path: &Path) -> Result { - self.metadata(path) + todo!() } fn remove_file(&self, path: &Path) -> Result<()>; @@ -136,6 +134,14 @@ where (**self).metadata(path) } + fn symlink(&self, original: &Path, link: &Path) -> Result<()> { + (**self).symlink(original, link) + } + + fn symlink_metadata(&self, path: &Path) -> Result { + todo!() + } + fn remove_file(&self, path: &Path) -> Result<()> { (**self).remove_file(path) } diff --git a/lib/virtual-fs/src/mem_fs/filesystem.rs b/lib/virtual-fs/src/mem_fs/filesystem.rs index 44cb1580d67..63e3256d5f7 100644 --- a/lib/virtual-fs/src/mem_fs/filesystem.rs +++ b/lib/virtual-fs/src/mem_fs/filesystem.rs @@ -605,6 +605,10 @@ impl crate::FileSystem for FileSystem { fn new_open_options(&self) -> OpenOptions { OpenOptions::new(self) } + + fn symlink(&self, original: &Path, link: &Path) -> Result<()> { + todo!() + } } impl fmt::Debug for FileSystem { diff --git a/lib/virtual-fs/src/overlay_fs.rs b/lib/virtual-fs/src/overlay_fs.rs index 2b10e29dafe..ff34781589a 100644 --- a/lib/virtual-fs/src/overlay_fs.rs +++ b/lib/virtual-fs/src/overlay_fs.rs @@ -348,6 +348,10 @@ where self.permission_error_or_not_found(path) } + fn symlink(&self, original: &Path, link: &Path) -> Result<(), FsError> { + todo!() + } + fn new_open_options(&self) -> OpenOptions<'_> { OpenOptions::new(self) } diff --git a/lib/virtual-fs/src/passthru_fs.rs b/lib/virtual-fs/src/passthru_fs.rs index 08a940ed0c5..d36b226d904 100644 --- a/lib/virtual-fs/src/passthru_fs.rs +++ b/lib/virtual-fs/src/passthru_fs.rs @@ -38,6 +38,10 @@ impl FileSystem for PassthruFileSystem { self.fs.metadata(path) } + fn symlink(&self, original: &Path, link: &Path) -> Result<()> { + self.symlink(original, link) + } + fn symlink_metadata(&self, path: &Path) -> Result { self.fs.symlink_metadata(path) } diff --git a/lib/virtual-fs/src/static_fs.rs b/lib/virtual-fs/src/static_fs.rs index e18da882ce0..04d3fc949ab 100644 --- a/lib/virtual-fs/src/static_fs.rs +++ b/lib/virtual-fs/src/static_fs.rs @@ -337,6 +337,9 @@ impl FileSystem for StaticFileSystem { fn new_open_options(&self) -> OpenOptions { OpenOptions::new(self) } + fn symlink(&self, original: &Path, link: &Path) -> Result<(), FsError> { + todo!() + } fn symlink_metadata(&self, path: &Path) -> Result { let path = normalizes_path(path); if let Some(fs_entry) = self diff --git a/lib/virtual-fs/src/tmp_fs.rs b/lib/virtual-fs/src/tmp_fs.rs index 46e47f0fe53..92c6ba55b49 100644 --- a/lib/virtual-fs/src/tmp_fs.rs +++ b/lib/virtual-fs/src/tmp_fs.rs @@ -81,6 +81,10 @@ impl FileSystem for TmpFileSystem { self.fs.metadata(path) } + fn symlink(&self, original: &Path, link: &Path) -> Result<()> { + self.fs.symlink(original, link) + } + fn symlink_metadata(&self, path: &Path) -> Result { self.fs.symlink_metadata(path) } diff --git a/lib/virtual-fs/src/trace_fs.rs b/lib/virtual-fs/src/trace_fs.rs index 627dca8a5b3..c31330e8924 100644 --- a/lib/virtual-fs/src/trace_fs.rs +++ b/lib/virtual-fs/src/trace_fs.rs @@ -77,6 +77,11 @@ where fn new_open_options(&self) -> crate::OpenOptions { crate::OpenOptions::new(self) } + + #[tracing::instrument(level = "trace", skip(self), err)] + fn symlink(&self, original: &std::path::Path, link: &std::path::Path) -> crate::Result<()> { + self.0.symlink(original, link) + } } impl FileOpener for TraceFileSystem diff --git a/lib/virtual-fs/src/union_fs.rs b/lib/virtual-fs/src/union_fs.rs index 57519f32584..aeb5adaebee 100644 --- a/lib/virtual-fs/src/union_fs.rs +++ b/lib/virtual-fs/src/union_fs.rs @@ -325,6 +325,9 @@ impl FileSystem for UnionFileSystem { } Err(ret_error) } + fn symlink(&self, original: &Path, link: &Path) -> Result<()> { + todo!() + } fn symlink_metadata(&self, path: &Path) -> Result { debug!("symlink_metadata: path={}", path.display()); let mut ret_error = FsError::EntryNotFound; diff --git a/lib/virtual-fs/src/webc_fs.rs b/lib/virtual-fs/src/webc_fs.rs index 0a67c739bfa..1586a174c02 100644 --- a/lib/virtual-fs/src/webc_fs.rs +++ b/lib/virtual-fs/src/webc_fs.rs @@ -396,6 +396,9 @@ where fn new_open_options(&self) -> OpenOptions { OpenOptions::new(self) } + fn symlink(&self, original: &Path, link: &Path) -> Result<(), FsError> { + todo!() + } fn symlink_metadata(&self, path: &Path) -> Result { let path = normalizes_path(path); if let Some(fs_entry) = self diff --git a/lib/virtual-fs/src/webc_volume_fs.rs b/lib/virtual-fs/src/webc_volume_fs.rs index 56d0a43907f..acc94abedea 100644 --- a/lib/virtual-fs/src/webc_volume_fs.rs +++ b/lib/virtual-fs/src/webc_volume_fs.rs @@ -134,6 +134,10 @@ impl FileSystem for WebcVolumeFileSystem { .ok_or(FsError::EntryNotFound) } + fn symlink(&self, original: &Path, link: &Path) -> Result<(), FsError> { + todo!() + } + fn remove_file(&self, path: &Path) -> Result<(), FsError> { let meta = self.metadata(path)?; From 67aab3f9a1bc9cec1cfca9f9998aa3671bc72635 Mon Sep 17 00:00:00 2001 From: Isaac Snow Date: Wed, 5 Jul 2023 13:53:07 -0700 Subject: [PATCH 02/20] empty fs impl --- lib/virtual-fs/src/empty_fs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/virtual-fs/src/empty_fs.rs b/lib/virtual-fs/src/empty_fs.rs index 2ef1c1e0ae8..765bcb6296e 100644 --- a/lib/virtual-fs/src/empty_fs.rs +++ b/lib/virtual-fs/src/empty_fs.rs @@ -33,7 +33,7 @@ impl FileSystem for EmptyFileSystem { } fn symlink(&self, original: &Path, link: &Path) -> Result<()> { - todo!() + Err(FsError::EntryNotFound) } fn symlink_metadata(&self, path: &Path) -> Result { From c1d5b62a6caa8c10b333959498bab1162bbe60de Mon Sep 17 00:00:00 2001 From: Isaac Snow Date: Wed, 5 Jul 2023 13:57:01 -0700 Subject: [PATCH 03/20] host fs impl --- lib/virtual-fs/src/host_fs.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/virtual-fs/src/host_fs.rs b/lib/virtual-fs/src/host_fs.rs index 36bc89dd3ed..e9144a228a3 100644 --- a/lib/virtual-fs/src/host_fs.rs +++ b/lib/virtual-fs/src/host_fs.rs @@ -134,7 +134,33 @@ impl crate::FileSystem for FileSystem { } fn symlink(&self, original: &Path, link: &Path) -> Result<()> { - todo!() + let exists = original.try_exists()?; + + if !exists { + return Err(FsError::EntryNotFound); + } + + if link.parent().is_none() { + return Err(FsError::BaseNotDirectory); + } + + #[cfg(taget_os = "windows")] + if original.is_dir() { + std::os::windows::fs::symlink_dir(original, link)?; + } else { + std::os::windows::fs::symlink_file(original, link)?; + } + + #[cfg(not(taget_os = "windows"))] + std::os::unix::fs::symlink(original, link)?; + + Ok(()) + } + + fn symlink_metadata(&self, path: &Path) -> Result { + fs::symlink_metadata(path) + .and_then(TryInto::try_into) + .map_err(Into::into) } } From 3d2bc34afee47c92fb37c2b3d6c3d8361fef53d2 Mon Sep 17 00:00:00 2001 From: Isaac Snow Date: Wed, 5 Jul 2023 14:17:31 -0700 Subject: [PATCH 04/20] wip mem fs --- lib/virtual-fs/src/mem_fs/mod.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/virtual-fs/src/mem_fs/mod.rs b/lib/virtual-fs/src/mem_fs/mod.rs index 8d16dd784eb..afc6d95c038 100644 --- a/lib/virtual-fs/src/mem_fs/mod.rs +++ b/lib/virtual-fs/src/mem_fs/mod.rs @@ -67,6 +67,21 @@ struct ArcDirectoryNode { metadata: Metadata, } +#[derive(Debug)] +struct SymlinkNode { + inode: Inode, + name: OsString, + original: SymlinkNodeConnection, + link: PathBuf, + metadata: Metadata, +} + +#[derive(Debug)] +enum SymlinkNodeConnection { + File(FileNode), + Directory(DirectoryNode), +} + #[derive(Debug)] enum Node { File(FileNode), @@ -75,6 +90,7 @@ enum Node { CustomFile(CustomFileNode), Directory(DirectoryNode), ArcDirectory(ArcDirectoryNode), + Symlink(SymlinkNode), } impl Node { From 6955f4d38dacb9b9376c6737d7734c56624c92a5 Mon Sep 17 00:00:00 2001 From: Isaac Snow Date: Wed, 5 Jul 2023 17:47:09 -0700 Subject: [PATCH 05/20] wip --- lib/virtual-fs/src/mem_fs/mod.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/virtual-fs/src/mem_fs/mod.rs b/lib/virtual-fs/src/mem_fs/mod.rs index afc6d95c038..8aac735f705 100644 --- a/lib/virtual-fs/src/mem_fs/mod.rs +++ b/lib/virtual-fs/src/mem_fs/mod.rs @@ -71,17 +71,10 @@ struct ArcDirectoryNode { struct SymlinkNode { inode: Inode, name: OsString, - original: SymlinkNodeConnection, - link: PathBuf, + link: Box, metadata: Metadata, } -#[derive(Debug)] -enum SymlinkNodeConnection { - File(FileNode), - Directory(DirectoryNode), -} - #[derive(Debug)] enum Node { File(FileNode), @@ -102,6 +95,7 @@ impl Node { Self::CustomFile(CustomFileNode { inode, .. }) => inode, Self::Directory(DirectoryNode { inode, .. }) => inode, Self::ArcDirectory(ArcDirectoryNode { inode, .. }) => inode, + Self::Symlink(SymlinkNode { inode, .. }) => inode, } } @@ -113,6 +107,7 @@ impl Node { Self::CustomFile(CustomFileNode { name, .. }) => name.as_os_str(), Self::Directory(DirectoryNode { name, .. }) => name.as_os_str(), Self::ArcDirectory(ArcDirectoryNode { name, .. }) => name.as_os_str(), + Self::Symlink(SymlinkNode { name, .. }) => name.as_os_str(), } } @@ -124,6 +119,7 @@ impl Node { Self::CustomFile(CustomFileNode { metadata, .. }) => metadata, Self::Directory(DirectoryNode { metadata, .. }) => metadata, Self::ArcDirectory(ArcDirectoryNode { metadata, .. }) => metadata, + Self::Symlink(SymlinkNode { metadata, .. }) => metadata, } } @@ -135,6 +131,7 @@ impl Node { Self::CustomFile(CustomFileNode { metadata, .. }) => metadata, Self::Directory(DirectoryNode { metadata, .. }) => metadata, Self::ArcDirectory(ArcDirectoryNode { metadata, .. }) => metadata, + Self::Symlink(SymlinkNode { metadata, .. }) => metadata, } } @@ -146,6 +143,7 @@ impl Node { Self::CustomFile(CustomFileNode { name, .. }) => *name = new_name, Self::Directory(DirectoryNode { name, .. }) => *name = new_name, Self::ArcDirectory(ArcDirectoryNode { name, .. }) => *name = new_name, + Self::Symlink(SymlinkNode { name, .. }) => *name = new_name, } } } From f9c114cd788437653508aec0d0a948bd82160116 Mon Sep 17 00:00:00 2001 From: Isaac Snow Date: Thu, 6 Jul 2023 10:33:10 -0700 Subject: [PATCH 06/20] ready to test --- lib/virtual-fs/src/mem_fs/filesystem.rs | 172 +++++++++++++++++++++++- lib/virtual-fs/src/mem_fs/mod.rs | 2 +- 2 files changed, 172 insertions(+), 2 deletions(-) diff --git a/lib/virtual-fs/src/mem_fs/filesystem.rs b/lib/virtual-fs/src/mem_fs/filesystem.rs index 63e3256d5f7..6a1118120da 100644 --- a/lib/virtual-fs/src/mem_fs/filesystem.rs +++ b/lib/virtual-fs/src/mem_fs/filesystem.rs @@ -607,7 +607,83 @@ impl crate::FileSystem for FileSystem { } fn symlink(&self, original: &Path, link: &Path) -> Result<()> { - todo!() + // Ensure original exists. + let _ = self.metadata(original)?; + + if self.metadata(link).is_ok() { + return Err(FsError::AlreadyExists); + } + + let (inode_of_parent, name_of_symlink) = { + // Read lock. + let guard = self.inner.read().map_err(|_| FsError::Lock)?; + + // Canonicalize the link path without checking the path exists, + // because it's about to be created. + let link = guard.canonicalize_without_inode(link)?; + + // Check the path has a parent. + let parent_of_path = link.parent().ok_or(FsError::BaseNotDirectory)?; + + // Check the directory name. + let name_of_directory = link + .file_name() + .ok_or(FsError::InvalidInput)? + .to_os_string(); + + // Find the parent inode. + let inode_of_parent = match guard.inode_of_parent(parent_of_path)? { + InodeResolution::Found(a) => a, + InodeResolution::Redirect(fs, mut path) => { + drop(guard); + path.push(name_of_directory); + return fs.create_dir(path.as_path()); + } + }; + + (inode_of_parent, name_of_directory) + }; + + if self.metadata(link).is_ok() { + return Err(FsError::AlreadyExists); + } + + { + // Write lock. + let mut fs = self.inner.write().map_err(|_| FsError::Lock)?; + + // Creating the directory in the storage. + let inode_of_symlink = fs.storage.vacant_entry().key(); + let real_inode_of_symlink = fs.storage.insert(Node::Symlink(SymlinkNode { + inode: inode_of_symlink, + name: name_of_symlink, + link: original.to_path_buf(), + metadata: { + let time = time(); + + Metadata { + ft: FileType { + symlink: true, + ..Default::default() + }, + accessed: time, + created: time, + modified: time, + len: 0, + } + }, + })); + + assert_eq!( + inode_of_symlink, real_inode_of_symlink, + "new symlink inode should have been correctly calculated", + ); + + // Adding the new directory to its parent. + fs.add_child_to_node(inode_of_parent, inode_of_symlink)?; + } + + Ok(()) } } @@ -965,6 +1041,7 @@ impl fmt::Debug for FileSystemInner { Node::CustomFile { .. } => "custom-file", Node::Directory { .. } => "dir", Node::ArcDirectory { .. } => "arc-dir", + Node::Symlink { .. } => "symlink", }, name = node.name().to_string_lossy(), indentation_symbol = " ", @@ -1853,4 +1930,97 @@ mod test_filesystem { assert_eq!(buf, b"a"); } + + #[test] + fn test_symlink() { + let fs = FileSystem::default(); + + fs.create_dir(path!("/foo")).unwrap(); + + assert!(false); + + { + let fs_inner = fs.inner.read().unwrap(); + assert_eq!( + fs_inner.storage.len(), + 2, + "storage contains the new directory" + ); + assert!( + matches!( + fs_inner.storage.get(ROOT_INODE), + Some(Node::Directory(DirectoryNode { + inode: ROOT_INODE, + name, + children, + .. + })) if name == "/" && children == &[1] + ), + "the root is updated and well-defined", + ); + assert!( + matches!( + fs_inner.storage.get(1), + Some(Node::Directory(DirectoryNode { + inode: 1, + name, + children, + .. + })) if name == "foo" && children.is_empty(), + ), + "the new directory is well-defined", + ); + } + + assert_eq!( + fs.create_dir(path!("/foo/bar")), + Ok(()), + "creating a sub-directory", + ); + + { + let fs_inner = fs.inner.read().unwrap(); + assert_eq!( + fs_inner.storage.len(), + 3, + "storage contains the new sub-directory", + ); + assert!( + matches!( + fs_inner.storage.get(ROOT_INODE), + Some(Node::Directory(DirectoryNode { + inode: ROOT_INODE, + name, + children, + .. + })) if name == "/" && children == &[1] + ), + "the root is updated again and well-defined", + ); + assert!( + matches!( + fs_inner.storage.get(1), + Some(Node::Directory(DirectoryNode { + inode: 1, + name, + children, + .. + })) if name == "foo" && children == &[2] + ), + "the new directory is updated and well-defined", + ); + assert!( + matches!( + fs_inner.storage.get(2), + Some(Node::Directory(DirectoryNode { + inode: 2, + name, + children, + .. + })) if name == "bar" && children.is_empty() + ), + "the new directory is well-defined", + ); + } + } } diff --git a/lib/virtual-fs/src/mem_fs/mod.rs b/lib/virtual-fs/src/mem_fs/mod.rs index 8aac735f705..cab25e01007 100644 --- a/lib/virtual-fs/src/mem_fs/mod.rs +++ b/lib/virtual-fs/src/mem_fs/mod.rs @@ -71,7 +71,7 @@ struct ArcDirectoryNode { struct SymlinkNode { inode: Inode, name: OsString, - link: Box, + link: PathBuf, metadata: Metadata, } From 67e64da70debc930a9d8be80a8783b0e74d62c28 Mon Sep 17 00:00:00 2001 From: Isaac Snow Date: Fri, 7 Jul 2023 10:04:47 -0700 Subject: [PATCH 07/20] test in a good spot --- lib/virtual-fs/src/mem_fs/filesystem.rs | 186 +++++++++++++++++------- 1 file changed, 131 insertions(+), 55 deletions(-) diff --git a/lib/virtual-fs/src/mem_fs/filesystem.rs b/lib/virtual-fs/src/mem_fs/filesystem.rs index 6a1118120da..bab8b14a85b 100644 --- a/lib/virtual-fs/src/mem_fs/filesystem.rs +++ b/lib/virtual-fs/src/mem_fs/filesystem.rs @@ -1931,20 +1931,40 @@ mod test_filesystem { assert_eq!(buf, b"a"); } - #[test] - fn test_symlink() { + #[tokio::test] + async fn test_symlink() { let fs = FileSystem::default(); - fs.create_dir(path!("/foo")).unwrap(); + assert!( + matches!(fs.create_dir(path!("/foo")), Ok(_)), + "creating dir `/foo`", + ); + assert!( + matches!( + fs.insert_ro_file(path!("/foo/bar"), Cow::Borrowed(b"a")), + Ok(_) + ), + "creating file `/foo/bar`", + ); + assert!( + matches!(fs.symlink(path!("/foo/bar"), path!("/baz")), Ok(_)), + "creating symlink `/foo/bar` -> `baz`", + ); + assert!( + matches!(fs.symlink(path!("/foo/bar"), path!("/qux")), Ok(_)), + "creating symlink `baz` -> `/foo/qux`", + ); - assert!(false); + // TODO: create a directory symlink, make sure it works, then change the symlink to point + // to a file. Additionally, try to read a child of the symlinked directory. `/ccc` -> + // `/foo`, then try to read `/ccc/bar` { let fs_inner = fs.inner.read().unwrap(); assert_eq!( fs_inner.storage.len(), - 2, - "storage contains the new directory" + 5, + "storage contains the new entries" ); assert!( matches!( @@ -1954,7 +1974,7 @@ mod test_filesystem { name, children, .. - })) if name == "/" && children == &[1] + })) if name == "/" && children == &[1, 3, 4] ), "the root is updated and well-defined", ); @@ -1966,61 +1986,117 @@ mod test_filesystem { name, children, .. - })) if name == "foo" && children.is_empty(), + })) if name == "foo" && children == &[2], ), "the new directory is well-defined", ); } - assert_eq!( - fs.create_dir(path!("/foo/bar")), - Ok(()), - "creating a sub-directory", + assert!( + matches!( + fs.metadata(path!("/baz")), + Ok(Metadata { + ft: FileType { + symlink, + .. + }, + .. + }) if symlink + ), + "metadata reads `/baz` as a symlink" ); - { - let fs_inner = fs.inner.read().unwrap(); - assert_eq!( - fs_inner.storage.len(), - 3, - "storage contains the new sub-directory", - ); - assert!( - matches!( - fs_inner.storage.get(ROOT_INODE), - Some(Node::Directory(DirectoryNode { - inode: ROOT_INODE, - name, - children, - .. - })) if name == "/" && children == &[1] - ), - "the root is updated again and well-defined", - ); - assert!( - matches!( - fs_inner.storage.get(1), - Some(Node::Directory(DirectoryNode { - inode: 1, - name, - children, - .. - })) if name == "foo" && children == &[2] - ), - "the new directory is updated and well-defined", - ); - assert!( - matches!( - fs_inner.storage.get(2), - Some(Node::Directory(DirectoryNode { - inode: 2, - name, - children, + assert!( + matches!( + fs.symlink_metadata(path!("/baz")), + Ok(Metadata { + ft: FileType { + symlink, .. - })) if name == "bar" && children.is_empty() - ), - "the new directory is well-defined", - ); - } + }, + .. + }) if symlink + ), + "symlink_metadata reads `/baz` as a file" + ); + + // let mut bar = String::new(); + // + // fs.new_open_options() + // .read(true) + // .open(&Path::new("/foo/bar")) + // .unwrap() + // .read_to_string(&mut bar) + // .await + // .unwrap(); + // + // let mut baz = String::new(); + // + // fs.new_open_options() + // .read(true) + // .open(&Path::new("/baz")) + // .unwrap() + // .read_to_string(&mut baz) + // .await + // .unwrap(); + // + // assert_eq!(bar, baz); + // + // let mut qux = String::new(); + // + // fs.new_open_options() + // .read(true) + // .open(&Path::new("/qux")) + // .unwrap() + // .read_to_string(&mut qux) + // .await + // .unwrap(); + // + // assert_eq!(bar, qux); + + // { + // let fs_inner = fs.inner.read().unwrap(); + // assert_eq!( + // fs_inner.storage.len(), + // 3, + // "storage contains the new sub-directory", + // ); + // assert!( + // matches!( + // fs_inner.storage.get(ROOT_INODE), + // Some(Node::Directory(DirectoryNode { + // inode: ROOT_INODE, + // name, + // children, + // .. + // })) if name == "/" && children == &[1] + // ), + // "the root is updated again and well-defined", + // ); + // assert!( + // matches!( + // fs_inner.storage.get(1), + // Some(Node::Directory(DirectoryNode { + // inode: 1, + // name, + // children, + // .. + // })) if name == "foo" && children == &[2] + // ), + // "the new directory is updated and well-defined", + // ); + // assert!( + // matches!( + // fs_inner.storage.get(2), + // Some(Node::Directory(DirectoryNode { + // inode: 2, + // name, + // children, + // .. + // })) if name == "bar" && children.is_empty() + // ), + // "the new directory is well-defined", + // ); + // } } } From 6d5b1098e13d925af5b0fb2008a2e4ba43a7ef7d Mon Sep 17 00:00:00 2001 From: Isaac Snow Date: Fri, 7 Jul 2023 12:25:53 -0700 Subject: [PATCH 08/20] metadata with symlink --- lib/virtual-fs/src/mem_fs/file_opener.rs | 4 +-- lib/virtual-fs/src/mem_fs/filesystem.rs | 45 +++++++++++++++--------- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/lib/virtual-fs/src/mem_fs/file_opener.rs b/lib/virtual-fs/src/mem_fs/file_opener.rs index 6128ce86b69..8f26495b868 100644 --- a/lib/virtual-fs/src/mem_fs/file_opener.rs +++ b/lib/virtual-fs/src/mem_fs/file_opener.rs @@ -367,7 +367,7 @@ impl crate::FileOpener for FileSystem { }; let mut cursor = 0u64; - let inode_of_file = match maybe_inode_of_file { + let inode_of_file = match dbg!(maybe_inode_of_file) { // The file already exists, and a _new_ one _must_ be // created; it's not OK. Some(_inode_of_file) if create_new => return Err(FsError::AlreadyExists), @@ -385,7 +385,7 @@ impl crate::FileOpener for FileSystem { let mut fs = self.inner.write().map_err(|_| FsError::Lock)?; let inode = fs.storage.get_mut(inode_of_file); - match inode { + match dbg!(inode) { Some(Node::File(FileNode { metadata, file, .. })) => { // Update the accessed time. metadata.accessed = time(); diff --git a/lib/virtual-fs/src/mem_fs/filesystem.rs b/lib/virtual-fs/src/mem_fs/filesystem.rs index bab8b14a85b..d54796623b1 100644 --- a/lib/virtual-fs/src/mem_fs/filesystem.rs +++ b/lib/virtual-fs/src/mem_fs/filesystem.rs @@ -238,6 +238,26 @@ impl FileSystem { Ok(()) } + + fn get_metadata(&self, path: &Path, follow_symlink: bool) -> Result { + // Read lock. + let guard = self.inner.read().map_err(|_| FsError::Lock)?; + + match guard.inode_of(path)? { + InodeResolution::Found(inode) => { + let node = guard.storage.get(inode).ok_or(FsError::UnknownError)?; + + match node { + Node::Symlink(SymlinkNode { link, .. }) if follow_symlink => self.metadata(link), + _ => Ok(node.metadata().clone()), + } + } + InodeResolution::Redirect(fs, path) => { + drop(guard); + fs.metadata(path.as_path()) + } + } + } } impl crate::FileSystem for FileSystem { @@ -529,20 +549,11 @@ impl crate::FileSystem for FileSystem { } fn metadata(&self, path: &Path) -> Result { - // Read lock. - let guard = self.inner.read().map_err(|_| FsError::Lock)?; - match guard.inode_of(path)? { - InodeResolution::Found(inode) => Ok(guard - .storage - .get(inode) - .ok_or(FsError::UnknownError)? - .metadata() - .clone()), - InodeResolution::Redirect(fs, path) => { - drop(guard); - fs.metadata(path.as_path()) - } - } + self.get_metadata(path, true) + } + + fn symlink_metadata(&self, path: &Path) -> Result { + self.get_metadata(path, false) } fn remove_file(&self, path: &Path) -> Result<()> { @@ -2001,9 +2012,9 @@ mod test_filesystem { .. }, .. - }) if symlink + }) if !symlink ), - "metadata reads `/baz` as a symlink" + "metadata reads `/baz` as a file" ); assert!( @@ -2017,7 +2028,7 @@ mod test_filesystem { .. }) if symlink ), - "symlink_metadata reads `/baz` as a file" + "symlink_metadata reads `/baz` as a symlink" ); // let mut bar = String::new(); From 1755fde349ec76ecb9482e05d7664d1d8dae28fa Mon Sep 17 00:00:00 2001 From: Isaac Snow Date: Fri, 7 Jul 2023 12:40:24 -0700 Subject: [PATCH 09/20] metadata --- lib/virtual-fs/src/mem_fs/filesystem.rs | 29 ++++++++++++++++--------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/lib/virtual-fs/src/mem_fs/filesystem.rs b/lib/virtual-fs/src/mem_fs/filesystem.rs index d54796623b1..390b0e8eec5 100644 --- a/lib/virtual-fs/src/mem_fs/filesystem.rs +++ b/lib/virtual-fs/src/mem_fs/filesystem.rs @@ -243,12 +243,14 @@ impl FileSystem { // Read lock. let guard = self.inner.read().map_err(|_| FsError::Lock)?; - match guard.inode_of(path)? { + match guard.inode_of(path, follow_symlink)? { InodeResolution::Found(inode) => { let node = guard.storage.get(inode).ok_or(FsError::UnknownError)?; match node { - Node::Symlink(SymlinkNode { link, .. }) if follow_symlink => self.metadata(link), + Node::Symlink(SymlinkNode { link, .. }) if follow_symlink => { + self.metadata(link) + } _ => Ok(node.metadata().clone()), } } @@ -733,7 +735,7 @@ impl InodeResolution { impl FileSystemInner { /// Get the inode associated to a path if it exists. - pub(super) fn inode_of(&self, path: &Path) -> Result { + pub(super) fn inode_of(&self, path: &Path, follow_symlink: bool) -> Result { // SAFETY: The root node always exists, so it's safe to unwrap here. let mut node = self.storage.get(ROOT_INODE).unwrap(); let mut components = path.components(); @@ -745,11 +747,18 @@ impl FileSystemInner { while let Some(component) = components.next() { node = match node { - Node::Directory(DirectoryNode { children, .. }) => children - .iter() - .filter_map(|inode| self.storage.get(*inode)) - .find(|node| node.name() == component.as_os_str()) - .ok_or(FsError::EntryNotFound)?, + Node::Directory(DirectoryNode { children, .. }) => { + let found = children + .iter() + .filter_map(|inode| self.storage.get(*inode)) + .find(|node| node.name() == component.as_os_str()) + .ok_or(FsError::EntryNotFound)?; + + match found { + Node::Symlink(SymlinkNode { link, .. }) if follow_symlink => return self.inode_of(link, true), + _ => found, + } + } Node::ArcDirectory(ArcDirectoryNode { fs, path: fs_path, .. }) => { @@ -770,7 +779,7 @@ impl FileSystemInner { /// Get the inode associated to a “parent path”. The returned /// inode necessarily represents a directory. pub(super) fn inode_of_parent(&self, parent_path: &Path) -> Result { - match self.inode_of(parent_path)? { + match self.inode_of(parent_path, true)? { InodeResolution::Found(inode_of_parent) => { // Ensure it is a directory. match self.storage.get(inode_of_parent) { @@ -976,7 +985,7 @@ impl FileSystemInner { /// * A normalized path exists in the file system. pub(super) fn canonicalize(&self, path: &Path) -> Result<(PathBuf, InodeResolution)> { let new_path = self.canonicalize_without_inode(path)?; - let inode = self.inode_of(&new_path)?; + let inode = self.inode_of(&new_path, true)?; Ok((new_path, inode)) } From 7c0846e1d968bde55b9a2b8d5f2c821a0906c5c0 Mon Sep 17 00:00:00 2001 From: Isaac Snow Date: Fri, 7 Jul 2023 13:35:55 -0700 Subject: [PATCH 10/20] mem_fs works! --- lib/virtual-fs/src/mem_fs/file_opener.rs | 20 +++- lib/virtual-fs/src/mem_fs/filesystem.rs | 139 +++++++++-------------- 2 files changed, 73 insertions(+), 86 deletions(-) diff --git a/lib/virtual-fs/src/mem_fs/file_opener.rs b/lib/virtual-fs/src/mem_fs/file_opener.rs index 8f26495b868..2aac5aa013e 100644 --- a/lib/virtual-fs/src/mem_fs/file_opener.rs +++ b/lib/virtual-fs/src/mem_fs/file_opener.rs @@ -367,7 +367,7 @@ impl crate::FileOpener for FileSystem { }; let mut cursor = 0u64; - let inode_of_file = match dbg!(maybe_inode_of_file) { + let inode_of_file = match maybe_inode_of_file { // The file already exists, and a _new_ one _must_ be // created; it's not OK. Some(_inode_of_file) if create_new => return Err(FsError::AlreadyExists), @@ -381,11 +381,23 @@ impl crate::FileOpener for FileSystem { } }; + // If node is a symlink, follow it. + { + // Read lock. + let fs = self.inner.read().map_err(|_| FsError::Lock)?; + + if let Some(Node::Symlink(SymlinkNode { link, .. })) = fs.storage.get(inode_of_file) { + let link = link.clone(); + drop(fs); + return self.open(&link, conf); + } + } + // Write lock. let mut fs = self.inner.write().map_err(|_| FsError::Lock)?; let inode = fs.storage.get_mut(inode_of_file); - match dbg!(inode) { + match inode { Some(Node::File(FileNode { metadata, file, .. })) => { // Update the accessed time. metadata.accessed = time(); @@ -456,6 +468,10 @@ impl crate::FileOpener for FileSystem { } } + Some(Node::Symlink(SymlinkNode { link, .. })) => { + return self.open(dbg!(link), conf); + } + None => return Err(FsError::EntryNotFound), _ => return Err(FsError::NotAFile), } diff --git a/lib/virtual-fs/src/mem_fs/filesystem.rs b/lib/virtual-fs/src/mem_fs/filesystem.rs index 390b0e8eec5..041327c2c0d 100644 --- a/lib/virtual-fs/src/mem_fs/filesystem.rs +++ b/lib/virtual-fs/src/mem_fs/filesystem.rs @@ -755,7 +755,9 @@ impl FileSystemInner { .ok_or(FsError::EntryNotFound)?; match found { - Node::Symlink(SymlinkNode { link, .. }) if follow_symlink => return self.inode_of(link, true), + Node::Symlink(SymlinkNode { link, .. }) if follow_symlink => { + return self.inode_of(link, true) + } _ => found, } } @@ -854,6 +856,7 @@ impl FileSystemInner { .filter_map(|(nth, inode)| self.storage.get(*inode).map(|node| (nth, node))) .find_map(|(nth, node)| match node { Node::File(FileNode { inode, name, .. }) + | Node::Symlink(SymlinkNode { inode, name, .. }) | Node::ReadOnlyFile(ReadOnlyFileNode { inode, name, .. }) | Node::CustomFile(CustomFileNode { inode, name, .. }) | Node::ArcFile(ArcFileNode { inode, name, .. }) @@ -1972,13 +1975,9 @@ mod test_filesystem { ); assert!( matches!(fs.symlink(path!("/foo/bar"), path!("/qux")), Ok(_)), - "creating symlink `baz` -> `/foo/qux`", + "creating symlink `qux` -> `/foo/bar`", ); - // TODO: create a directory symlink, make sure it works, then change the symlink to point - // to a file. Additionally, try to read a child of the symlinked directory. `/ccc` -> - // `/foo`, then try to read `/ccc/bar` - { let fs_inner = fs.inner.read().unwrap(); assert_eq!( @@ -2040,83 +2039,55 @@ mod test_filesystem { "symlink_metadata reads `/baz` as a symlink" ); - // let mut bar = String::new(); - // - // fs.new_open_options() - // .read(true) - // .open(&Path::new("/foo/bar")) - // .unwrap() - // .read_to_string(&mut bar) - // .await - // .unwrap(); - // - // let mut baz = String::new(); - // - // fs.new_open_options() - // .read(true) - // .open(&Path::new("/baz")) - // .unwrap() - // .read_to_string(&mut baz) - // .await - // .unwrap(); - // - // assert_eq!(bar, baz); - // - // let mut qux = String::new(); - // - // fs.new_open_options() - // .read(true) - // .open(&Path::new("/qux")) - // .unwrap() - // .read_to_string(&mut qux) - // .await - // .unwrap(); - // - // assert_eq!(bar, qux); - - // { - // let fs_inner = fs.inner.read().unwrap(); - // assert_eq!( - // fs_inner.storage.len(), - // 3, - // "storage contains the new sub-directory", - // ); - // assert!( - // matches!( - // fs_inner.storage.get(ROOT_INODE), - // Some(Node::Directory(DirectoryNode { - // inode: ROOT_INODE, - // name, - // children, - // .. - // })) if name == "/" && children == &[1] - // ), - // "the root is updated again and well-defined", - // ); - // assert!( - // matches!( - // fs_inner.storage.get(1), - // Some(Node::Directory(DirectoryNode { - // inode: 1, - // name, - // children, - // .. - // })) if name == "foo" && children == &[2] - // ), - // "the new directory is updated and well-defined", - // ); - // assert!( - // matches!( - // fs_inner.storage.get(2), - // Some(Node::Directory(DirectoryNode { - // inode: 2, - // name, - // children, - // .. - // })) if name == "bar" && children.is_empty() - // ), - // "the new directory is well-defined", - // ); - // } + let mut bar = String::new(); + + fs.new_open_options() + .read(true) + .open(&Path::new("/foo/bar")) + .unwrap() + .read_to_string(&mut bar) + .await + .unwrap(); + + let mut baz = String::new(); + + fs.new_open_options() + .read(true) + .open(&Path::new("/baz")) + .unwrap() + .read_to_string(&mut baz) + .await + .unwrap(); + + assert_eq!(bar, baz); + + let mut qux = String::new(); + + fs.new_open_options() + .read(true) + .open(&Path::new("/qux")) + .unwrap() + .read_to_string(&mut qux) + .await + .unwrap(); + + assert_eq!(bar, qux); + + assert!( + matches!(fs.symlink(path!("/foo"), path!("/quux")), Ok(_)), + "creating symlink `/quux` -> `/foo`", + ); + + let mut quux_bar = String::new(); + + fs.new_open_options() + .read(true) + .open(&Path::new("/quux/bar")) + .unwrap() + .read_to_string(&mut quux_bar) + .await + .unwrap(); + + assert_eq!(bar, quux_bar); } } From d70375bd9251dfe4d2a3dbb52c088192c05a91cc Mon Sep 17 00:00:00 2001 From: Isaac Snow Date: Fri, 7 Jul 2023 13:37:45 -0700 Subject: [PATCH 11/20] clean up --- lib/virtual-fs/src/mem_fs/filesystem.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/lib/virtual-fs/src/mem_fs/filesystem.rs b/lib/virtual-fs/src/mem_fs/filesystem.rs index 041327c2c0d..f46c598c235 100644 --- a/lib/virtual-fs/src/mem_fs/filesystem.rs +++ b/lib/virtual-fs/src/mem_fs/filesystem.rs @@ -244,16 +244,12 @@ impl FileSystem { let guard = self.inner.read().map_err(|_| FsError::Lock)?; match guard.inode_of(path, follow_symlink)? { - InodeResolution::Found(inode) => { - let node = guard.storage.get(inode).ok_or(FsError::UnknownError)?; - - match node { - Node::Symlink(SymlinkNode { link, .. }) if follow_symlink => { - self.metadata(link) - } - _ => Ok(node.metadata().clone()), - } - } + InodeResolution::Found(inode) => Ok(guard + .storage + .get(inode) + .ok_or(FsError::UnknownError)? + .metadata() + .clone()), InodeResolution::Redirect(fs, path) => { drop(guard); fs.metadata(path.as_path()) From 49da7168d86cc397bfca0896a2389c7b59571171 Mon Sep 17 00:00:00 2001 From: Isaac Snow Date: Fri, 7 Jul 2023 18:43:31 -0700 Subject: [PATCH 12/20] solid test case --- lib/virtual-fs/src/mem_fs/filesystem.rs | 313 ++++++++++++++++++++++++ lib/virtual-fs/src/overlay_fs.rs | 55 ++++- 2 files changed, 367 insertions(+), 1 deletion(-) diff --git a/lib/virtual-fs/src/mem_fs/filesystem.rs b/lib/virtual-fs/src/mem_fs/filesystem.rs index f46c598c235..4a5297277e7 100644 --- a/lib/virtual-fs/src/mem_fs/filesystem.rs +++ b/lib/virtual-fs/src/mem_fs/filesystem.rs @@ -892,6 +892,7 @@ impl FileSystemInner { .filter_map(|(nth, inode)| self.storage.get(*inode).map(|node| (nth, node))) .find_map(|(nth, node)| match node { Node::File(FileNode { inode, name, .. }) + | Node::Symlink(SymlinkNode { inode, name, .. }) | Node::Directory(DirectoryNode { inode, name, .. }) | Node::ReadOnlyFile(ReadOnlyFileNode { inode, name, .. }) | Node::CustomFile(CustomFileNode { inode, name, .. }) @@ -2085,5 +2086,317 @@ mod test_filesystem { .unwrap(); assert_eq!(bar, quux_bar); + + fs.rename(path!("/baz"), path!("/a")).await.unwrap(); + + assert!( + matches!( + fs.symlink_metadata(path!("/a")), + Ok(Metadata { + ft: FileType { + symlink, + .. + }, + .. + }) if symlink + ), + "renamed symlink is still a symlink" + ); + + let mut a = String::new(); + + fs.new_open_options() + .read(true) + .open(&Path::new("/a")) + .unwrap() + .read_to_string(&mut a) + .await + .unwrap(); + + assert_eq!(bar, a); + + fs.remove_file(path!("/foo/bar")).unwrap(); + + assert!( + matches!(fs.symlink_metadata(path!("/a")), Ok(_)), + "symlink of removed file still exists" + ); + + assert!( + matches!(fs.metadata(path!("/a")), Err(FsError::EntryNotFound)), + "metadata can't be read for removed file" + ); + } + + #[tokio::test] + async fn test_symlink1() { + let fs = FileSystem::default(); + + macro_rules! read_to_string { + ($path:expr) => {{ + let mut a = String::new(); + + fs.new_open_options() + .read(true) + .open(path!($path)) + .unwrap() + .read_to_string(&mut a) + .await + .unwrap(); + + a + }}; + } + + macro_rules! assert_file_contents_eq { + ($a:expr, $b:expr) => { + assert_eq!(read_to_string!($a), read_to_string!($b)); + }; + } + + fs.create_dir(path!("/a")).unwrap(); + fs.insert_ro_file(path!("/a/b"), Cow::Borrowed(b"0")) + .unwrap(); + + fs.symlink(path!("/a/b"), path!("/c")).unwrap(); + + assert_file_contents_eq!("/a/b", "/c"); + + fs.symlink(path!("/c"), path!("/d")).unwrap(); + + assert_file_contents_eq!("/a/b", "/d"); + + assert!(fs.metadata(path!("/c")).unwrap().file_type().is_file()); + + assert!(matches!( + fs.metadata(path!("/c")), + Ok(Metadata { + ft: FileType { + file, + symlink, + .. + }, + .. + }) if file && !symlink + )); + + assert!(matches!( + fs.symlink_metadata(path!("/c")), + Ok(Metadata { + ft: FileType { + file, + symlink, + .. + }, + .. + }) if !file && symlink + )); + + fs.remove_file(path!("/c")).unwrap(); + fs.create_dir(path!("/c")).unwrap(); + + assert!(matches!( + fs.metadata(path!("/d")), + Ok(Metadata { + ft: FileType { + file, + symlink, + dir, + .. + }, + .. + }) if dir && !file && !symlink + )); + + fs.new_open_options() + .create(true) + .open(path!("/d/e")) + .unwrap(); + + // let nf = tempfile::Builder::new() + // .rand_bytes(10) + // .tempfile_in(dir.path()) + // .unwrap(); + // + // std::os::unix::fs::symlink(nf.path(), "s3").unwrap(); + // + // let m1 = std::fs::read_to_string(std::path::PathBuf::from("s3").join(nf.path())).unwrap(); + // + // assert_eq!(m1, std::fs::read_to_string(nf.path()).unwrap()); + // + // std::fs::rename("s3", "s4").unwrap(); + // + // let m2 = std::fs::read_to_string(std::path::PathBuf::from("s4").join(nf.path())).unwrap(); + // + // assert_eq!(m1, m2); + // + // std::fs::rename(dir.path(), "moved").unwrap(); + // + // assert!(std::fs::read_to_string("s4/main.rs").is_err()); + // + // std::fs::rename("s1", "etc").unwrap(); + // + // assert!(std::fs::symlink_metadata("s2").is_ok()); + // assert!(std::fs::metadata("s2").is_err()); + // + // assert!(std::fs::read_to_string("s2").is_err()); + // + // let nd = tempfile::Builder::new().tempdir().unwrap(); + // + // std::os::unix::fs::symlink(nd.path(), "sd").unwrap(); + // + // let nf_in_symlink_dir = tempfile::Builder::new() + // .rand_bytes(10) + // .tempfile_in("sd") + // .unwrap(); + // + // assert_eq!( + // std::fs::read_to_string(nf_in_symlink_dir.path()).unwrap(), + // std::fs::read_to_string( + // std::path::PathBuf::from("sd").join(nf_in_symlink_dir.path().file_name().unwrap()) + // ) + // .unwrap() + // ); + // + // let nf_in_symlink_dir = tempfile::Builder::new() + // .rand_bytes(10) + // .tempfile_in(nd.path()) + // .unwrap(); + // + // assert_eq!( + // std::fs::read_to_string(nf_in_symlink_dir.path()).unwrap(), + // std::fs::read_to_string( + // std::path::PathBuf::from("sd").join(nf_in_symlink_dir.path().file_name().unwrap()) + // ) + // .unwrap() + // ); + } + + #[tokio::test] + async fn test_symlink2() { + let fs = FileSystem::default(); + + macro_rules! path { + ($path:expr) => { + &std::path::PathBuf::from("/").join($path) + }; + } + + macro_rules! create_file_with_contents { + ($path:expr, $contents:expr) => { + fs.insert_ro_file(path!($path), Cow::Borrowed($contents)) + .unwrap(); + }; + } + + macro_rules! read_to_string { + ($path:expr) => {{ + let mut a = String::new(); + + fs.new_open_options() + .read(true) + .open(path!($path)) + .unwrap() + .read_to_string(&mut a) + .await + .unwrap(); + + a + }}; + } + + macro_rules! symlink { + ($original:expr, $link:expr) => { + fs.symlink(path!($original), path!($link)) + }; + } + + macro_rules! rename { + ($from:expr, $to:expr) => { + std::fs::rename(path!($from), path!($to)).unwrap() + }; + } + + macro_rules! metadata { + ($path:expr) => { + fs.metadata(path!($path)) + }; + } + + macro_rules! symlink_metadata { + ($path:expr) => { + fs.symlink_metadata(path!($path)) + }; + } + + macro_rules! assert_file_contents_eq { + ($a:expr, $b:expr) => { + assert_eq!(read_to_string!($a), read_to_string!($b)); + }; + } + + fs.create_dir(path!("a")).unwrap(); + + create_file_with_contents!("a/a", b"aa"); + + symlink!("a/a", "b").unwrap(); + symlink!("b", "aa_b").unwrap(); + + assert!(matches!( + metadata!("b"), + Ok(Metadata { ft, .. }) if ft.file && !ft.symlink + )); + + assert!(matches!( + symlink_metadata!("b"), + Ok(Metadata { ft, .. }) if !ft.file && ft.symlink + )); + + assert_file_contents_eq!("a/a", "b"); + assert_file_contents_eq!("a/a", "aa_b"); + + rename!("b", "bb"); + + assert_file_contents_eq!("a/a", "bb"); + + assert!(matches!(metadata!("ab__aa_b"), Err(FsError::EntryNotFound))); + + assert!(symlink!("a/b", "ab__aa_b").is_ok()); + + assert!(matches!(metadata!("ab__aa_b"), Err(FsError::EntryNotFound))); + + rename!("a/a", "a/b"); + + assert!(matches!( + symlink!("a/b", "ab__aa_b"), + Err(FsError::AlreadyExists) + )); + + assert!(matches!(metadata!("aa_b"), Err(FsError::EntryNotFound))); + + std::fs::create_dir(path!("b")).unwrap(); + + assert!(matches!( + metadata!("aa_b"), + Ok(Metadata { ft, .. }) if ft.dir && !ft.symlink + )); + + assert!(matches!( + symlink_metadata!("aa_b"), + Ok(Metadata { ft, .. }) if !ft.dir && ft.symlink + )); + + create_file_with_contents!("aa_b/foo", b"foo"); + + assert_file_contents_eq!("b/foo", "aa_b/foo"); + + rename!("b", "bbb"); + + assert!(matches!(metadata!("aa_b/foo"), Err(FsError::EntryNotFound))); + + assert!(matches!( + symlink_metadata!("aa_b/foo"), + Err(FsError::EntryNotFound) + )); } } diff --git a/lib/virtual-fs/src/overlay_fs.rs b/lib/virtual-fs/src/overlay_fs.rs index ff34781589a..8670e7296c5 100644 --- a/lib/virtual-fs/src/overlay_fs.rs +++ b/lib/virtual-fs/src/overlay_fs.rs @@ -323,6 +323,35 @@ where Err(FsError::EntryNotFound) } + fn symlink_metadata(&self, path: &Path) -> Result { + // Whiteout files can not be read, they are just markers + if ops::is_white_out(path).is_some() { + return Err(FsError::EntryNotFound); + } + + // Check if the file is in the primary + match self.primary.symlink_metadata(path) { + Ok(meta) => return Ok(meta), + Err(e) if should_continue(e) => {} + Err(e) => return Err(e), + } + + // There might be a whiteout, search for this + if ops::has_white_out(&self.primary, path) { + return Err(FsError::EntryNotFound); + } + + // Otherwise scan the secondaries + for fs in self.secondaries.filesystems() { + match fs.symlink_metadata(path) { + Err(e) if should_continue(e) => continue, + other => return other, + } + } + + Err(FsError::EntryNotFound) + } + fn remove_file(&self, path: &Path) -> Result<(), FsError> { // It is not possible to delete whiteout files directly, instead // one must delete the original file @@ -349,7 +378,31 @@ where } fn symlink(&self, original: &Path, link: &Path) -> Result<(), FsError> { - todo!() + // You can not create directories that use the whiteout prefix + if ops::is_white_out(link).is_some() { + return Err(FsError::InvalidInput); + } + + // It could be the case that the directory was earlier hidden in the secondaries + // by a whiteout file, hence we need to make sure those are cleared out. + ops::remove_white_out(self.primary.as_ref(), link); + + // Make sure the parent tree is in place on the primary, this is to cover the + // scenario where the secondaries has a parent structure that is not yet in the + // primary and the primary needs it to create a sub-directory + if let Some(parent) = link.parent() { + if self.read_dir(parent).is_ok() { + ops::create_dir_all(&self.primary, parent).ok(); + } + } + + // Create the directory in the primary + match self.primary.create_dir(link) { + Err(e) if should_continue(e) => {} + other => return other, + } + + self.permission_error_or_not_found(link) } fn new_open_options(&self) -> OpenOptions<'_> { From 983aa90e3ec3728d2aabe1b63d92f4e37fc1953f Mon Sep 17 00:00:00 2001 From: Isaac Snow Date: Fri, 7 Jul 2023 18:45:52 -0700 Subject: [PATCH 13/20] good start for test --- lib/virtual-fs/src/mem_fs/filesystem.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/virtual-fs/src/mem_fs/filesystem.rs b/lib/virtual-fs/src/mem_fs/filesystem.rs index 4a5297277e7..9e1c9cdd196 100644 --- a/lib/virtual-fs/src/mem_fs/filesystem.rs +++ b/lib/virtual-fs/src/mem_fs/filesystem.rs @@ -2313,7 +2313,7 @@ mod test_filesystem { macro_rules! rename { ($from:expr, $to:expr) => { - std::fs::rename(path!($from), path!($to)).unwrap() + fs.rename(path!($from), path!($to)).await.unwrap() }; } @@ -2374,7 +2374,7 @@ mod test_filesystem { assert!(matches!(metadata!("aa_b"), Err(FsError::EntryNotFound))); - std::fs::create_dir(path!("b")).unwrap(); + fs.create_dir(path!("b")).unwrap(); assert!(matches!( metadata!("aa_b"), From 7dea54cdad0e5f62d1f155d555e363f39e229a3a Mon Sep 17 00:00:00 2001 From: Isaac Snow Date: Sat, 8 Jul 2023 12:49:43 -0700 Subject: [PATCH 14/20] test passes --- lib/virtual-fs/src/mem_fs/filesystem.rs | 350 ++---------------------- 1 file changed, 24 insertions(+), 326 deletions(-) diff --git a/lib/virtual-fs/src/mem_fs/filesystem.rs b/lib/virtual-fs/src/mem_fs/filesystem.rs index 9e1c9cdd196..9884daa60fd 100644 --- a/lib/virtual-fs/src/mem_fs/filesystem.rs +++ b/lib/virtual-fs/src/mem_fs/filesystem.rs @@ -616,9 +616,6 @@ impl crate::FileSystem for FileSystem { } fn symlink(&self, original: &Path, link: &Path) -> Result<()> { - // Ensure original exists. - let _ = self.metadata(original)?; - if self.metadata(link).is_ok() { return Err(FsError::AlreadyExists); } @@ -738,7 +735,7 @@ impl FileSystemInner { match components.next() { Some(Component::RootDir) => {} - _ => return Err(FsError::BaseNotDirectory), + _ => return dbg!(Err(FsError::BaseNotDirectory)), } while let Some(component) = components.next() { @@ -767,6 +764,10 @@ impl FileSystemInner { } return Ok(InodeResolution::Redirect(fs.clone(), path)); } + // Symlink appears to be a directory, so we'll follow it despite `follow_symlink`. + Node::Symlink(SymlinkNode { link, .. }) => { + return self.inode_of(link, follow_symlink) + } _ => return Err(FsError::BaseNotDirectory), }; } @@ -1141,7 +1142,7 @@ impl DirectoryMustBeEmpty { mod test_filesystem { use std::{borrow::Cow, path::Path}; - use tokio::io::AsyncReadExt; + use tokio::io::{AsyncReadExt, AsyncWriteExt}; use crate::{mem_fs::*, ops, DirEntry, FileSystem as FS, FileType, FsError}; @@ -1955,327 +1956,6 @@ mod test_filesystem { async fn test_symlink() { let fs = FileSystem::default(); - assert!( - matches!(fs.create_dir(path!("/foo")), Ok(_)), - "creating dir `/foo`", - ); - assert!( - matches!( - fs.insert_ro_file(path!("/foo/bar"), Cow::Borrowed(b"a")), - Ok(_) - ), - "creating file `/foo/bar`", - ); - assert!( - matches!(fs.symlink(path!("/foo/bar"), path!("/baz")), Ok(_)), - "creating symlink `/foo/bar` -> `baz`", - ); - assert!( - matches!(fs.symlink(path!("/foo/bar"), path!("/qux")), Ok(_)), - "creating symlink `qux` -> `/foo/bar`", - ); - - { - let fs_inner = fs.inner.read().unwrap(); - assert_eq!( - fs_inner.storage.len(), - 5, - "storage contains the new entries" - ); - assert!( - matches!( - fs_inner.storage.get(ROOT_INODE), - Some(Node::Directory(DirectoryNode { - inode: ROOT_INODE, - name, - children, - .. - })) if name == "/" && children == &[1, 3, 4] - ), - "the root is updated and well-defined", - ); - assert!( - matches!( - fs_inner.storage.get(1), - Some(Node::Directory(DirectoryNode { - inode: 1, - name, - children, - .. - })) if name == "foo" && children == &[2], - ), - "the new directory is well-defined", - ); - } - - assert!( - matches!( - fs.metadata(path!("/baz")), - Ok(Metadata { - ft: FileType { - symlink, - .. - }, - .. - }) if !symlink - ), - "metadata reads `/baz` as a file" - ); - - assert!( - matches!( - fs.symlink_metadata(path!("/baz")), - Ok(Metadata { - ft: FileType { - symlink, - .. - }, - .. - }) if symlink - ), - "symlink_metadata reads `/baz` as a symlink" - ); - - let mut bar = String::new(); - - fs.new_open_options() - .read(true) - .open(&Path::new("/foo/bar")) - .unwrap() - .read_to_string(&mut bar) - .await - .unwrap(); - - let mut baz = String::new(); - - fs.new_open_options() - .read(true) - .open(&Path::new("/baz")) - .unwrap() - .read_to_string(&mut baz) - .await - .unwrap(); - - assert_eq!(bar, baz); - - let mut qux = String::new(); - - fs.new_open_options() - .read(true) - .open(&Path::new("/qux")) - .unwrap() - .read_to_string(&mut qux) - .await - .unwrap(); - - assert_eq!(bar, qux); - - assert!( - matches!(fs.symlink(path!("/foo"), path!("/quux")), Ok(_)), - "creating symlink `/quux` -> `/foo`", - ); - - let mut quux_bar = String::new(); - - fs.new_open_options() - .read(true) - .open(&Path::new("/quux/bar")) - .unwrap() - .read_to_string(&mut quux_bar) - .await - .unwrap(); - - assert_eq!(bar, quux_bar); - - fs.rename(path!("/baz"), path!("/a")).await.unwrap(); - - assert!( - matches!( - fs.symlink_metadata(path!("/a")), - Ok(Metadata { - ft: FileType { - symlink, - .. - }, - .. - }) if symlink - ), - "renamed symlink is still a symlink" - ); - - let mut a = String::new(); - - fs.new_open_options() - .read(true) - .open(&Path::new("/a")) - .unwrap() - .read_to_string(&mut a) - .await - .unwrap(); - - assert_eq!(bar, a); - - fs.remove_file(path!("/foo/bar")).unwrap(); - - assert!( - matches!(fs.symlink_metadata(path!("/a")), Ok(_)), - "symlink of removed file still exists" - ); - - assert!( - matches!(fs.metadata(path!("/a")), Err(FsError::EntryNotFound)), - "metadata can't be read for removed file" - ); - } - - #[tokio::test] - async fn test_symlink1() { - let fs = FileSystem::default(); - - macro_rules! read_to_string { - ($path:expr) => {{ - let mut a = String::new(); - - fs.new_open_options() - .read(true) - .open(path!($path)) - .unwrap() - .read_to_string(&mut a) - .await - .unwrap(); - - a - }}; - } - - macro_rules! assert_file_contents_eq { - ($a:expr, $b:expr) => { - assert_eq!(read_to_string!($a), read_to_string!($b)); - }; - } - - fs.create_dir(path!("/a")).unwrap(); - fs.insert_ro_file(path!("/a/b"), Cow::Borrowed(b"0")) - .unwrap(); - - fs.symlink(path!("/a/b"), path!("/c")).unwrap(); - - assert_file_contents_eq!("/a/b", "/c"); - - fs.symlink(path!("/c"), path!("/d")).unwrap(); - - assert_file_contents_eq!("/a/b", "/d"); - - assert!(fs.metadata(path!("/c")).unwrap().file_type().is_file()); - - assert!(matches!( - fs.metadata(path!("/c")), - Ok(Metadata { - ft: FileType { - file, - symlink, - .. - }, - .. - }) if file && !symlink - )); - - assert!(matches!( - fs.symlink_metadata(path!("/c")), - Ok(Metadata { - ft: FileType { - file, - symlink, - .. - }, - .. - }) if !file && symlink - )); - - fs.remove_file(path!("/c")).unwrap(); - fs.create_dir(path!("/c")).unwrap(); - - assert!(matches!( - fs.metadata(path!("/d")), - Ok(Metadata { - ft: FileType { - file, - symlink, - dir, - .. - }, - .. - }) if dir && !file && !symlink - )); - - fs.new_open_options() - .create(true) - .open(path!("/d/e")) - .unwrap(); - - // let nf = tempfile::Builder::new() - // .rand_bytes(10) - // .tempfile_in(dir.path()) - // .unwrap(); - // - // std::os::unix::fs::symlink(nf.path(), "s3").unwrap(); - // - // let m1 = std::fs::read_to_string(std::path::PathBuf::from("s3").join(nf.path())).unwrap(); - // - // assert_eq!(m1, std::fs::read_to_string(nf.path()).unwrap()); - // - // std::fs::rename("s3", "s4").unwrap(); - // - // let m2 = std::fs::read_to_string(std::path::PathBuf::from("s4").join(nf.path())).unwrap(); - // - // assert_eq!(m1, m2); - // - // std::fs::rename(dir.path(), "moved").unwrap(); - // - // assert!(std::fs::read_to_string("s4/main.rs").is_err()); - // - // std::fs::rename("s1", "etc").unwrap(); - // - // assert!(std::fs::symlink_metadata("s2").is_ok()); - // assert!(std::fs::metadata("s2").is_err()); - // - // assert!(std::fs::read_to_string("s2").is_err()); - // - // let nd = tempfile::Builder::new().tempdir().unwrap(); - // - // std::os::unix::fs::symlink(nd.path(), "sd").unwrap(); - // - // let nf_in_symlink_dir = tempfile::Builder::new() - // .rand_bytes(10) - // .tempfile_in("sd") - // .unwrap(); - // - // assert_eq!( - // std::fs::read_to_string(nf_in_symlink_dir.path()).unwrap(), - // std::fs::read_to_string( - // std::path::PathBuf::from("sd").join(nf_in_symlink_dir.path().file_name().unwrap()) - // ) - // .unwrap() - // ); - // - // let nf_in_symlink_dir = tempfile::Builder::new() - // .rand_bytes(10) - // .tempfile_in(nd.path()) - // .unwrap(); - // - // assert_eq!( - // std::fs::read_to_string(nf_in_symlink_dir.path()).unwrap(), - // std::fs::read_to_string( - // std::path::PathBuf::from("sd").join(nf_in_symlink_dir.path().file_name().unwrap()) - // ) - // .unwrap() - // ); - } - - #[tokio::test] - async fn test_symlink2() { - let fs = FileSystem::default(); - macro_rules! path { ($path:expr) => { &std::path::PathBuf::from("/").join($path) @@ -2398,5 +2078,23 @@ mod test_filesystem { symlink_metadata!("aa_b/foo"), Err(FsError::EntryNotFound) )); + + symlink!("bbb", "bbbb").unwrap(); + + let mut file = fs + .new_open_options() + .create(true) + .write(true) + .open(path!("bbbb/bar")) + .unwrap(); + + file.write(b"foobarbazqux").await.unwrap(); + + assert_file_contents_eq!("bbb/bar", "bbbb/bar"); + + fs.insert_ro_file(path!("bbbb/baz"), Cow::Borrowed(b"baz")) + .unwrap(); + + assert_file_contents_eq!("bbb/baz", "bbbb/baz"); } } From a8a57095b42626491d826a68c0fb628ff35e5f73 Mon Sep 17 00:00:00 2001 From: Isaac Snow Date: Sat, 8 Jul 2023 14:30:42 -0700 Subject: [PATCH 15/20] a little more --- lib/virtual-fs/Cargo.toml | 1 + lib/virtual-fs/src/arc_fs.rs | 2 + lib/virtual-fs/src/empty_fs.rs | 2 + lib/virtual-fs/src/host_fs.rs | 2 + lib/virtual-fs/src/lib.rs | 19 +++- lib/virtual-fs/src/mem_fs/file_opener.rs | 2 + lib/virtual-fs/src/mem_fs/filesystem.rs | 67 ++++++++++-- lib/virtual-fs/src/mem_fs/mod.rs | 7 ++ lib/virtual-fs/src/overlay_fs.rs | 130 ++++++++++------------- lib/virtual-fs/src/passthru_fs.rs | 4 +- lib/virtual-fs/src/static_fs.rs | 34 ------ lib/virtual-fs/src/tmp_fs.rs | 2 + lib/virtual-fs/src/trace_fs.rs | 7 ++ lib/virtual-fs/src/union_fs.rs | 80 +++++++------- lib/virtual-fs/src/webc_fs.rs | 62 ++++------- lib/virtual-fs/src/webc_volume_fs.rs | 4 - 16 files changed, 224 insertions(+), 201 deletions(-) diff --git a/lib/virtual-fs/Cargo.toml b/lib/virtual-fs/Cargo.toml index d315020f907..ab55461f60e 100644 --- a/lib/virtual-fs/Cargo.toml +++ b/lib/virtual-fs/Cargo.toml @@ -49,3 +49,4 @@ enable-serde = ["typetag"] no-time = [] # Enables memory tracking/limiting functionality for the in-memory filesystem. tracking = [] +symlink = [] diff --git a/lib/virtual-fs/src/arc_fs.rs b/lib/virtual-fs/src/arc_fs.rs index 435858b07da..ed903bf0b16 100644 --- a/lib/virtual-fs/src/arc_fs.rs +++ b/lib/virtual-fs/src/arc_fs.rs @@ -38,10 +38,12 @@ impl FileSystem for ArcFileSystem { self.fs.metadata(path) } + #[cfg(feature = "symlink")] fn symlink(&self, original: &Path, link: &Path) -> Result<()> { self.fs.symlink(original, link) } + #[cfg(feature = "symlink")] fn symlink_metadata(&self, path: &Path) -> Result { self.fs.symlink_metadata(path) } diff --git a/lib/virtual-fs/src/empty_fs.rs b/lib/virtual-fs/src/empty_fs.rs index 765bcb6296e..29e231fedb8 100644 --- a/lib/virtual-fs/src/empty_fs.rs +++ b/lib/virtual-fs/src/empty_fs.rs @@ -32,10 +32,12 @@ impl FileSystem for EmptyFileSystem { Err(FsError::EntryNotFound) } + #[cfg(feature = "symlink")] fn symlink(&self, original: &Path, link: &Path) -> Result<()> { Err(FsError::EntryNotFound) } + #[cfg(feature = "symlink")] fn symlink_metadata(&self, path: &Path) -> Result { Err(FsError::EntryNotFound) } diff --git a/lib/virtual-fs/src/host_fs.rs b/lib/virtual-fs/src/host_fs.rs index e9144a228a3..6c7980618e0 100644 --- a/lib/virtual-fs/src/host_fs.rs +++ b/lib/virtual-fs/src/host_fs.rs @@ -133,6 +133,7 @@ impl crate::FileSystem for FileSystem { .map_err(Into::into) } + #[cfg(feature = "symlink")] fn symlink(&self, original: &Path, link: &Path) -> Result<()> { let exists = original.try_exists()?; @@ -157,6 +158,7 @@ impl crate::FileSystem for FileSystem { Ok(()) } + #[cfg(feature = "symlink")] fn symlink_metadata(&self, path: &Path) -> Result { fs::symlink_metadata(path) .and_then(TryInto::try_into) diff --git a/lib/virtual-fs/src/lib.rs b/lib/virtual-fs/src/lib.rs index 49f3908531e..127102b67af 100644 --- a/lib/virtual-fs/src/lib.rs +++ b/lib/virtual-fs/src/lib.rs @@ -88,13 +88,24 @@ pub trait FileSystem: fmt::Debug + Send + Sync + 'static + Upcastable { fn remove_dir(&self, path: &Path) -> Result<()>; fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<()>>; fn metadata(&self, path: &Path) -> Result; - fn symlink(&self, original: &Path, link: &Path) -> Result<()>; + + #[cfg(feature = "symlink")] fn symlink_metadata(&self, path: &Path) -> Result { - todo!() + self.metadata(path) + } + #[cfg(not(feature = "symlink"))] + fn symlink_metadata(&self, _path: &Path) -> Result { + unimplemented!() } + fn remove_file(&self, path: &Path) -> Result<()>; fn new_open_options(&self) -> OpenOptions; + + #[cfg(feature = "symlink")] + fn symlink(&self, _original: &Path, _link: &Path) -> Result<()> { + unimplemented!() + } } impl dyn FileSystem + 'static { @@ -134,12 +145,14 @@ where (**self).metadata(path) } + #[cfg(feature = "symlink")] fn symlink(&self, original: &Path, link: &Path) -> Result<()> { (**self).symlink(original, link) } + #[cfg(feature = "symlink")] fn symlink_metadata(&self, path: &Path) -> Result { - todo!() + (**self).symlink_metadata(path) } fn remove_file(&self, path: &Path) -> Result<()> { diff --git a/lib/virtual-fs/src/mem_fs/file_opener.rs b/lib/virtual-fs/src/mem_fs/file_opener.rs index 2aac5aa013e..d5af2b2fa93 100644 --- a/lib/virtual-fs/src/mem_fs/file_opener.rs +++ b/lib/virtual-fs/src/mem_fs/file_opener.rs @@ -382,6 +382,7 @@ impl crate::FileOpener for FileSystem { }; // If node is a symlink, follow it. + #[cfg(feature = "symlink")] { // Read lock. let fs = self.inner.read().map_err(|_| FsError::Lock)?; @@ -468,6 +469,7 @@ impl crate::FileOpener for FileSystem { } } + #[cfg(feature = "symlink")] Some(Node::Symlink(SymlinkNode { link, .. })) => { return self.open(dbg!(link), conf); } diff --git a/lib/virtual-fs/src/mem_fs/filesystem.rs b/lib/virtual-fs/src/mem_fs/filesystem.rs index 9884daa60fd..88df9c10a60 100644 --- a/lib/virtual-fs/src/mem_fs/filesystem.rs +++ b/lib/virtual-fs/src/mem_fs/filesystem.rs @@ -239,11 +239,20 @@ impl FileSystem { Ok(()) } - fn get_metadata(&self, path: &Path, follow_symlink: bool) -> Result { + fn get_metadata( + &self, + path: &Path, + + #[cfg(feature = "symlink")] follow_symlink: bool, + ) -> Result { // Read lock. let guard = self.inner.read().map_err(|_| FsError::Lock)?; - match guard.inode_of(path, follow_symlink)? { + match guard.inode_of( + path, + #[cfg(feature = "symlink")] + follow_symlink, + )? { InodeResolution::Found(inode) => Ok(guard .storage .get(inode) @@ -547,11 +556,20 @@ impl crate::FileSystem for FileSystem { } fn metadata(&self, path: &Path) -> Result { - self.get_metadata(path, true) + self.get_metadata( + path, + #[cfg(feature = "symlink")] + true, + ) } + #[cfg(feature = "symlink")] fn symlink_metadata(&self, path: &Path) -> Result { - self.get_metadata(path, false) + self.get_metadata( + path, + #[cfg(feature = "symlink")] + false, + ) } fn remove_file(&self, path: &Path) -> Result<()> { @@ -615,6 +633,7 @@ impl crate::FileSystem for FileSystem { OpenOptions::new(self) } + #[cfg(feature = "symlink")] fn symlink(&self, original: &Path, link: &Path) -> Result<()> { if self.metadata(link).is_ok() { return Err(FsError::AlreadyExists); @@ -728,7 +747,11 @@ impl InodeResolution { impl FileSystemInner { /// Get the inode associated to a path if it exists. - pub(super) fn inode_of(&self, path: &Path, follow_symlink: bool) -> Result { + pub(super) fn inode_of( + &self, + path: &Path, + #[cfg(feature = "symlink")] follow_symlink: bool, + ) -> Result { // SAFETY: The root node always exists, so it's safe to unwrap here. let mut node = self.storage.get(ROOT_INODE).unwrap(); let mut components = path.components(); @@ -748,6 +771,7 @@ impl FileSystemInner { .ok_or(FsError::EntryNotFound)?; match found { + #[cfg(feature = "symlink")] Node::Symlink(SymlinkNode { link, .. }) if follow_symlink => { return self.inode_of(link, true) } @@ -765,6 +789,7 @@ impl FileSystemInner { return Ok(InodeResolution::Redirect(fs.clone(), path)); } // Symlink appears to be a directory, so we'll follow it despite `follow_symlink`. + #[cfg(feature = "symlink")] Node::Symlink(SymlinkNode { link, .. }) => { return self.inode_of(link, follow_symlink) } @@ -778,7 +803,11 @@ impl FileSystemInner { /// Get the inode associated to a “parent path”. The returned /// inode necessarily represents a directory. pub(super) fn inode_of_parent(&self, parent_path: &Path) -> Result { - match self.inode_of(parent_path, true)? { + match self.inode_of( + parent_path, + #[cfg(feature = "symlink")] + true, + )? { InodeResolution::Found(inode_of_parent) => { // Ensure it is a directory. match self.storage.get(inode_of_parent) { @@ -853,7 +882,6 @@ impl FileSystemInner { .filter_map(|(nth, inode)| self.storage.get(*inode).map(|node| (nth, node))) .find_map(|(nth, node)| match node { Node::File(FileNode { inode, name, .. }) - | Node::Symlink(SymlinkNode { inode, name, .. }) | Node::ReadOnlyFile(ReadOnlyFileNode { inode, name, .. }) | Node::CustomFile(CustomFileNode { inode, name, .. }) | Node::ArcFile(ArcFileNode { inode, name, .. }) @@ -861,6 +889,14 @@ impl FileSystemInner { { Some(Some((nth, InodeResolution::Found(*inode)))) } + // NOTE: this is the same as above but you can't disable a single case in an or + // match arm. + #[cfg(feature = "symlink")] + Node::Symlink(SymlinkNode { inode, name, .. }) + if name.as_os_str() == name_of_file => + { + Some(Some((nth, InodeResolution::Found(*inode)))) + } _ => None, }) .or(Some(None)) @@ -893,7 +929,6 @@ impl FileSystemInner { .filter_map(|(nth, inode)| self.storage.get(*inode).map(|node| (nth, node))) .find_map(|(nth, node)| match node { Node::File(FileNode { inode, name, .. }) - | Node::Symlink(SymlinkNode { inode, name, .. }) | Node::Directory(DirectoryNode { inode, name, .. }) | Node::ReadOnlyFile(ReadOnlyFileNode { inode, name, .. }) | Node::CustomFile(CustomFileNode { inode, name, .. }) @@ -902,6 +937,14 @@ impl FileSystemInner { { Some(Some((nth, InodeResolution::Found(*inode)))) } + // NOTE: this is the same as above but you can't disable a single case in an or + // match arm. + #[cfg(feature = "symlink")] + Node::Symlink(SymlinkNode { inode, name, .. }) + if name.as_os_str() == name_of => + { + Some(Some((nth, InodeResolution::Found(*inode)))) + } _ => None, }) .or(Some(None)) @@ -986,7 +1029,11 @@ impl FileSystemInner { /// * A normalized path exists in the file system. pub(super) fn canonicalize(&self, path: &Path) -> Result<(PathBuf, InodeResolution)> { let new_path = self.canonicalize_without_inode(path)?; - let inode = self.inode_of(&new_path, true)?; + let inode = self.inode_of( + &new_path, + #[cfg(feature = "symlink")] + true, + )?; Ok((new_path, inode)) } @@ -1062,6 +1109,7 @@ impl fmt::Debug for FileSystemInner { Node::CustomFile { .. } => "custom-file", Node::Directory { .. } => "dir", Node::ArcDirectory { .. } => "arc-dir", + #[cfg(feature = "symlink")] Node::Symlink { .. } => "symlink", }, name = node.name().to_string_lossy(), @@ -1952,6 +2000,7 @@ mod test_filesystem { assert_eq!(buf, b"a"); } + #[cfg(feature = "symlink")] #[tokio::test] async fn test_symlink() { let fs = FileSystem::default(); diff --git a/lib/virtual-fs/src/mem_fs/mod.rs b/lib/virtual-fs/src/mem_fs/mod.rs index cab25e01007..2bc57df8e36 100644 --- a/lib/virtual-fs/src/mem_fs/mod.rs +++ b/lib/virtual-fs/src/mem_fs/mod.rs @@ -67,6 +67,7 @@ struct ArcDirectoryNode { metadata: Metadata, } +#[cfg(feature = "symlink")] #[derive(Debug)] struct SymlinkNode { inode: Inode, @@ -83,6 +84,7 @@ enum Node { CustomFile(CustomFileNode), Directory(DirectoryNode), ArcDirectory(ArcDirectoryNode), + #[cfg(feature = "symlink")] Symlink(SymlinkNode), } @@ -95,6 +97,7 @@ impl Node { Self::CustomFile(CustomFileNode { inode, .. }) => inode, Self::Directory(DirectoryNode { inode, .. }) => inode, Self::ArcDirectory(ArcDirectoryNode { inode, .. }) => inode, + #[cfg(feature = "symlink")] Self::Symlink(SymlinkNode { inode, .. }) => inode, } } @@ -107,6 +110,7 @@ impl Node { Self::CustomFile(CustomFileNode { name, .. }) => name.as_os_str(), Self::Directory(DirectoryNode { name, .. }) => name.as_os_str(), Self::ArcDirectory(ArcDirectoryNode { name, .. }) => name.as_os_str(), + #[cfg(feature = "symlink")] Self::Symlink(SymlinkNode { name, .. }) => name.as_os_str(), } } @@ -119,6 +123,7 @@ impl Node { Self::CustomFile(CustomFileNode { metadata, .. }) => metadata, Self::Directory(DirectoryNode { metadata, .. }) => metadata, Self::ArcDirectory(ArcDirectoryNode { metadata, .. }) => metadata, + #[cfg(feature = "symlink")] Self::Symlink(SymlinkNode { metadata, .. }) => metadata, } } @@ -131,6 +136,7 @@ impl Node { Self::CustomFile(CustomFileNode { metadata, .. }) => metadata, Self::Directory(DirectoryNode { metadata, .. }) => metadata, Self::ArcDirectory(ArcDirectoryNode { metadata, .. }) => metadata, + #[cfg(feature = "symlink")] Self::Symlink(SymlinkNode { metadata, .. }) => metadata, } } @@ -143,6 +149,7 @@ impl Node { Self::CustomFile(CustomFileNode { name, .. }) => *name = new_name, Self::Directory(DirectoryNode { name, .. }) => *name = new_name, Self::ArcDirectory(ArcDirectoryNode { name, .. }) => *name = new_name, + #[cfg(feature = "symlink")] Self::Symlink(SymlinkNode { name, .. }) => *name = new_name, } } diff --git a/lib/virtual-fs/src/overlay_fs.rs b/lib/virtual-fs/src/overlay_fs.rs index 8670e7296c5..64f24e701f1 100644 --- a/lib/virtual-fs/src/overlay_fs.rs +++ b/lib/virtual-fs/src/overlay_fs.rs @@ -295,61 +295,16 @@ where } fn metadata(&self, path: &Path) -> Result { - // Whiteout files can not be read, they are just markers - if ops::is_white_out(path).is_some() { - return Err(FsError::EntryNotFound); - } - - // Check if the file is in the primary - match self.primary.metadata(path) { - Ok(meta) => return Ok(meta), - Err(e) if should_continue(e) => {} - Err(e) => return Err(e), - } - - // There might be a whiteout, search for this - if ops::has_white_out(&self.primary, path) { - return Err(FsError::EntryNotFound); - } - - // Otherwise scan the secondaries - for fs in self.secondaries.filesystems() { - match fs.metadata(path) { - Err(e) if should_continue(e) => continue, - other => return other, - } - } - - Err(FsError::EntryNotFound) + self.get_metadata( + path, + #[cfg(feature = "symlink")] + true, + ) } + #[cfg(feature = "symlink")] fn symlink_metadata(&self, path: &Path) -> Result { - // Whiteout files can not be read, they are just markers - if ops::is_white_out(path).is_some() { - return Err(FsError::EntryNotFound); - } - - // Check if the file is in the primary - match self.primary.symlink_metadata(path) { - Ok(meta) => return Ok(meta), - Err(e) if should_continue(e) => {} - Err(e) => return Err(e), - } - - // There might be a whiteout, search for this - if ops::has_white_out(&self.primary, path) { - return Err(FsError::EntryNotFound); - } - - // Otherwise scan the secondaries - for fs in self.secondaries.filesystems() { - match fs.symlink_metadata(path) { - Err(e) if should_continue(e) => continue, - other => return other, - } - } - - Err(FsError::EntryNotFound) + self.get_metadata(path, false) } fn remove_file(&self, path: &Path) -> Result<(), FsError> { @@ -377,36 +332,65 @@ where self.permission_error_or_not_found(path) } - fn symlink(&self, original: &Path, link: &Path) -> Result<(), FsError> { - // You can not create directories that use the whiteout prefix - if ops::is_white_out(link).is_some() { - return Err(FsError::InvalidInput); - } + #[cfg(feature = "symlink")] + fn symlink(&self, _original: &Path, _link: &Path) -> Result<(), FsError> { + todo!() + } - // It could be the case that the directory was earlier hidden in the secondaries - // by a whiteout file, hence we need to make sure those are cleared out. - ops::remove_white_out(self.primary.as_ref(), link); + fn new_open_options(&self) -> OpenOptions<'_> { + OpenOptions::new(self) + } +} - // Make sure the parent tree is in place on the primary, this is to cover the - // scenario where the secondaries has a parent structure that is not yet in the - // primary and the primary needs it to create a sub-directory - if let Some(parent) = link.parent() { - if self.read_dir(parent).is_ok() { - ops::create_dir_all(&self.primary, parent).ok(); +impl OverlayFileSystem +where + P: FileSystem + Send + 'static, + S: for<'a> FileSystems<'a> + Send + Sync + 'static, + for<'a> <>::Iter as IntoIterator>::IntoIter: Send, +{ + fn get_metadata( + &self, + path: &Path, + #[cfg(feature = "symlink")] follow_symlink: bool, + ) -> Result { + #[cfg(not(feature = "symlink"))] + let read_metadata = |fs: &dyn FileSystem| fs.metadata(path); + + #[cfg(feature = "symlink")] + let read_metadata = |fs: &dyn FileSystem| { + if follow_symlink { + fs.metadata(path) + } else { + fs.symlink_metadata(path) } + }; + + // Whiteout files can not be read, they are just markers + if ops::is_white_out(path).is_some() { + return Err(FsError::EntryNotFound); } - // Create the directory in the primary - match self.primary.create_dir(link) { + // Check if the file is in the primary + match read_metadata(&self.primary) { + Ok(meta) => return Ok(meta), Err(e) if should_continue(e) => {} - other => return other, + Err(e) => return Err(e), } - self.permission_error_or_not_found(link) - } + // There might be a whiteout, search for this + if ops::has_white_out(&self.primary, path) { + return Err(FsError::EntryNotFound); + } - fn new_open_options(&self) -> OpenOptions<'_> { - OpenOptions::new(self) + // Otherwise scan the secondaries + for fs in self.secondaries.filesystems() { + match read_metadata(fs) { + Err(e) if should_continue(e) => continue, + other => return other, + } + } + + Err(FsError::EntryNotFound) } } diff --git a/lib/virtual-fs/src/passthru_fs.rs b/lib/virtual-fs/src/passthru_fs.rs index d36b226d904..61637c05326 100644 --- a/lib/virtual-fs/src/passthru_fs.rs +++ b/lib/virtual-fs/src/passthru_fs.rs @@ -38,10 +38,12 @@ impl FileSystem for PassthruFileSystem { self.fs.metadata(path) } + #[cfg(feature = "symlink")] fn symlink(&self, original: &Path, link: &Path) -> Result<()> { - self.symlink(original, link) + self.fs.symlink(original, link) } + #[cfg(feature = "symlink")] fn symlink_metadata(&self, path: &Path) -> Result { self.fs.symlink_metadata(path) } diff --git a/lib/virtual-fs/src/static_fs.rs b/lib/virtual-fs/src/static_fs.rs index 04d3fc949ab..11765cba3e8 100644 --- a/lib/virtual-fs/src/static_fs.rs +++ b/lib/virtual-fs/src/static_fs.rs @@ -337,40 +337,6 @@ impl FileSystem for StaticFileSystem { fn new_open_options(&self) -> OpenOptions { OpenOptions::new(self) } - fn symlink(&self, original: &Path, link: &Path) -> Result<(), FsError> { - todo!() - } - fn symlink_metadata(&self, path: &Path) -> Result { - let path = normalizes_path(path); - if let Some(fs_entry) = self - .volumes - .values() - .find_map(|v| v.get_file_entry(&path).ok()) - { - Ok(Metadata { - ft: translate_file_type(FsEntryType::File), - accessed: 0, - created: 0, - modified: 0, - len: fs_entry.get_len(), - }) - } else if self - .volumes - .values() - .find_map(|v| v.read_dir(&path).ok()) - .is_some() - { - Ok(Metadata { - ft: translate_file_type(FsEntryType::Dir), - accessed: 0, - created: 0, - modified: 0, - len: 0, - }) - } else { - self.memory.symlink_metadata(Path::new(&path)) - } - } } fn normalizes_path(path: &Path) -> String { diff --git a/lib/virtual-fs/src/tmp_fs.rs b/lib/virtual-fs/src/tmp_fs.rs index 92c6ba55b49..00c407146b8 100644 --- a/lib/virtual-fs/src/tmp_fs.rs +++ b/lib/virtual-fs/src/tmp_fs.rs @@ -81,10 +81,12 @@ impl FileSystem for TmpFileSystem { self.fs.metadata(path) } + #[cfg(feature = "symlink")] fn symlink(&self, original: &Path, link: &Path) -> Result<()> { self.fs.symlink(original, link) } + #[cfg(feature = "symlink")] fn symlink_metadata(&self, path: &Path) -> Result { self.fs.symlink_metadata(path) } diff --git a/lib/virtual-fs/src/trace_fs.rs b/lib/virtual-fs/src/trace_fs.rs index c31330e8924..e0a8404698f 100644 --- a/lib/virtual-fs/src/trace_fs.rs +++ b/lib/virtual-fs/src/trace_fs.rs @@ -78,10 +78,17 @@ where crate::OpenOptions::new(self) } + #[cfg(feature = "symlink")] #[tracing::instrument(level = "trace", skip(self), err)] fn symlink(&self, original: &std::path::Path, link: &std::path::Path) -> crate::Result<()> { self.0.symlink(original, link) } + + #[cfg(feature = "symlink")] + #[tracing::instrument(level = "trace", skip(self), err)] + fn symlink_metadata(&self, path: &std::path::Path) -> crate::Result { + self.0.symlink_metadata(path) + } } impl FileOpener for TraceFileSystem diff --git a/lib/virtual-fs/src/union_fs.rs b/lib/virtual-fs/src/union_fs.rs index aeb5adaebee..ea7faa4011c 100644 --- a/lib/virtual-fs/src/union_fs.rs +++ b/lib/virtual-fs/src/union_fs.rs @@ -303,37 +303,64 @@ impl FileSystem for UnionFileSystem { } fn metadata(&self, path: &Path) -> Result { debug!("metadata: path={}", path.display()); + self.get_metadata( + path, + #[cfg(feature = "symlink")] + true, + ) + } + #[cfg(feature = "symlink")] + fn symlink_metadata(&self, path: &Path) -> Result { + debug!("symlink_metadata: path={}", path.display()); + self.get_metadata(path, false) + } + #[cfg(feature = "symlink")] + fn symlink(&self, _original: &Path, _link: &Path) -> Result<()> { + todo!() + } + fn remove_file(&self, path: &Path) -> Result<()> { + println!("remove_file: path={}", path.display()); let mut ret_error = FsError::EntryNotFound; let path = path.to_string_lossy(); for (path, mount) in filter_mounts(&self.mounts, path.as_ref()) { - match mount.fs.metadata(Path::new(path.as_str())) { + match mount.fs.remove_file(Path::new(path.as_str())) { Ok(ret) => { return Ok(ret); } Err(err) => { - // This fixes a bug when attempting to create the directory /usr when it does not exist - // on the x86 version of memfs - // TODO: patch virtual-fs and remove - if let FsError::NotAFile = &err { - ret_error = FsError::EntryNotFound; - } else { - debug!("metadata failed: (path={}) - {}", path, err); - ret_error = err; - } + println!("returning error {err:?}"); + ret_error = err; } } } Err(ret_error) } - fn symlink(&self, original: &Path, link: &Path) -> Result<()> { - todo!() + fn new_open_options(&self) -> OpenOptions { + OpenOptions::new(self) } - fn symlink_metadata(&self, path: &Path) -> Result { - debug!("symlink_metadata: path={}", path.display()); +} + +impl UnionFileSystem { + fn get_metadata( + &self, + path: &Path, + #[cfg(feature = "symlink")] follow_symlink: bool, + ) -> Result { + debug!("metadata: path={}", path.display()); let mut ret_error = FsError::EntryNotFound; let path = path.to_string_lossy(); - for (path_inner, mount) in filter_mounts(&self.mounts, path.as_ref()) { - match mount.fs.symlink_metadata(Path::new(path_inner.as_str())) { + for (path, mount) in filter_mounts(&self.mounts, path.as_ref()) { + #[cfg(feature = "symlink")] + let maybe_metadata = if follow_symlink { + mount.fs.metadata(Path::new(path.as_str())) + } else { + mount.fs.symlink_metadata(Path::new(path.as_str())) + }; + + #[cfg(not(feature = "symlink"))] + let maybe_metadata = mount.fs.metadata(Path::new(path.as_str())); + + match maybe_metadata { Ok(ret) => { return Ok(ret); } @@ -350,29 +377,8 @@ impl FileSystem for UnionFileSystem { } } } - debug!("symlink_metadata: failed={}", ret_error); Err(ret_error) } - fn remove_file(&self, path: &Path) -> Result<()> { - println!("remove_file: path={}", path.display()); - let mut ret_error = FsError::EntryNotFound; - let path = path.to_string_lossy(); - for (path, mount) in filter_mounts(&self.mounts, path.as_ref()) { - match mount.fs.remove_file(Path::new(path.as_str())) { - Ok(ret) => { - return Ok(ret); - } - Err(err) => { - println!("returning error {err:?}"); - ret_error = err; - } - } - } - Err(ret_error) - } - fn new_open_options(&self) -> OpenOptions { - OpenOptions::new(self) - } } fn filter_mounts( diff --git a/lib/virtual-fs/src/webc_fs.rs b/lib/virtual-fs/src/webc_fs.rs index 1586a174c02..f85e3e837a7 100644 --- a/lib/virtual-fs/src/webc_fs.rs +++ b/lib/virtual-fs/src/webc_fs.rs @@ -346,37 +346,15 @@ where }) } fn metadata(&self, path: &Path) -> Result { - let path = normalizes_path(path); - if let Some(fs_entry) = self - .volumes - .iter() - .filter_map(|v| v.get_file_entry(&path).ok()) - .next() - { - Ok(Metadata { - ft: translate_file_type(FsEntryType::File), - accessed: 0, - created: 0, - modified: 0, - len: fs_entry.get_len(), - }) - } else if self - .volumes - .iter() - .filter_map(|v| v.read_dir(&path).ok()) - .next() - .is_some() - { - Ok(Metadata { - ft: translate_file_type(FsEntryType::Dir), - accessed: 0, - created: 0, - modified: 0, - len: 0, - }) - } else { - self.memory.metadata(Path::new(&path)) - } + self.get_metadata(path, true) + } + #[cfg(feature = "symlink")] + fn symlink_metadata(&self, path: &Path) -> Result { + self.get_metadata(path, false) + } + #[cfg(feature = "symlink")] + fn symlink(&self, original: &Path, link: &Path) -> Result<(), FsError> { + self.memory.symlink(original, link) } fn remove_file(&self, path: &Path) -> Result<(), FsError> { let path = normalizes_path(path); @@ -396,16 +374,19 @@ where fn new_open_options(&self) -> OpenOptions { OpenOptions::new(self) } - fn symlink(&self, original: &Path, link: &Path) -> Result<(), FsError> { - todo!() - } - fn symlink_metadata(&self, path: &Path) -> Result { +} + +impl WebcFileSystem +where + T: std::fmt::Debug + Send + Sync + 'static, + T: Deref>, +{ + fn get_metadata(&self, path: &Path, follow_symlink: bool) -> Result { let path = normalizes_path(path); if let Some(fs_entry) = self .volumes .iter() - .filter_map(|v| v.get_file_entry(&path).ok()) - .next() + .find_map(|v| v.get_file_entry(&path).ok()) { Ok(Metadata { ft: translate_file_type(FsEntryType::File), @@ -417,8 +398,7 @@ where } else if self .volumes .iter() - .filter_map(|v| v.read_dir(&path).ok()) - .next() + .find_map(|v| v.read_dir(&path).ok()) .is_some() { Ok(Metadata { @@ -428,8 +408,10 @@ where modified: 0, len: 0, }) - } else { + } else if follow_symlink { self.memory.symlink_metadata(Path::new(&path)) + } else { + self.memory.metadata(Path::new(&path)) } } } diff --git a/lib/virtual-fs/src/webc_volume_fs.rs b/lib/virtual-fs/src/webc_volume_fs.rs index acc94abedea..56d0a43907f 100644 --- a/lib/virtual-fs/src/webc_volume_fs.rs +++ b/lib/virtual-fs/src/webc_volume_fs.rs @@ -134,10 +134,6 @@ impl FileSystem for WebcVolumeFileSystem { .ok_or(FsError::EntryNotFound) } - fn symlink(&self, original: &Path, link: &Path) -> Result<(), FsError> { - todo!() - } - fn remove_file(&self, path: &Path) -> Result<(), FsError> { let meta = self.metadata(path)?; From 60b72310088afbb136afe29b932d390f63480fdf Mon Sep 17 00:00:00 2001 From: Isaac Snow Date: Sat, 8 Jul 2023 14:33:58 -0700 Subject: [PATCH 16/20] fmt --- lib/virtual-fs/src/mem_fs/file_opener.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/virtual-fs/src/mem_fs/file_opener.rs b/lib/virtual-fs/src/mem_fs/file_opener.rs index d5af2b2fa93..10e67ce3209 100644 --- a/lib/virtual-fs/src/mem_fs/file_opener.rs +++ b/lib/virtual-fs/src/mem_fs/file_opener.rs @@ -387,7 +387,9 @@ impl crate::FileOpener for FileSystem { // Read lock. let fs = self.inner.read().map_err(|_| FsError::Lock)?; - if let Some(Node::Symlink(SymlinkNode { link, .. })) = fs.storage.get(inode_of_file) { + if let Some(Node::Symlink(SymlinkNode { link, .. })) = + fs.storage.get(inode_of_file) + { let link = link.clone(); drop(fs); return self.open(&link, conf); From dbe647521799555e4d1556be34156aef4f6e0e9c Mon Sep 17 00:00:00 2001 From: Isaac Snow Date: Sat, 8 Jul 2023 14:44:21 -0700 Subject: [PATCH 17/20] remove some stuff --- lib/virtual-fs/src/host_fs.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/virtual-fs/src/host_fs.rs b/lib/virtual-fs/src/host_fs.rs index 6c7980618e0..2f4e92ed7ac 100644 --- a/lib/virtual-fs/src/host_fs.rs +++ b/lib/virtual-fs/src/host_fs.rs @@ -135,16 +135,6 @@ impl crate::FileSystem for FileSystem { #[cfg(feature = "symlink")] fn symlink(&self, original: &Path, link: &Path) -> Result<()> { - let exists = original.try_exists()?; - - if !exists { - return Err(FsError::EntryNotFound); - } - - if link.parent().is_none() { - return Err(FsError::BaseNotDirectory); - } - #[cfg(taget_os = "windows")] if original.is_dir() { std::os::windows::fs::symlink_dir(original, link)?; From 695b465a1aaf85be99c38dc3a5245ba0a0fa3e2e Mon Sep 17 00:00:00 2001 From: Isaac Snow Date: Sat, 8 Jul 2023 14:47:52 -0700 Subject: [PATCH 18/20] asdf --- lib/virtual-fs/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/virtual-fs/src/lib.rs b/lib/virtual-fs/src/lib.rs index 127102b67af..6b195c063c9 100644 --- a/lib/virtual-fs/src/lib.rs +++ b/lib/virtual-fs/src/lib.rs @@ -90,6 +90,9 @@ pub trait FileSystem: fmt::Debug + Send + Sync + 'static + Upcastable { fn metadata(&self, path: &Path) -> Result; #[cfg(feature = "symlink")] + /// This method gets metadata without following symlinks in the path. + /// Currently identical to `metadata` because symlinks aren't implemented + /// yet. fn symlink_metadata(&self, path: &Path) -> Result { self.metadata(path) } From 7193b2baae262ba5c933c586fce28a52d1eecfb4 Mon Sep 17 00:00:00 2001 From: Isaac Snow Date: Thu, 13 Jul 2023 08:40:15 -0700 Subject: [PATCH 19/20] fix test when feature not enabled --- lib/virtual-fs/src/webc_fs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/virtual-fs/src/webc_fs.rs b/lib/virtual-fs/src/webc_fs.rs index f85e3e837a7..5cf9dd210f2 100644 --- a/lib/virtual-fs/src/webc_fs.rs +++ b/lib/virtual-fs/src/webc_fs.rs @@ -408,7 +408,7 @@ where modified: 0, len: 0, }) - } else if follow_symlink { + } else if !follow_symlink { self.memory.symlink_metadata(Path::new(&path)) } else { self.memory.metadata(Path::new(&path)) From 6abc0bdc5738fb3f42f3b87cf3cc48f02ebde8b3 Mon Sep 17 00:00:00 2001 From: Isaac Snow Date: Thu, 13 Jul 2023 09:40:22 -0700 Subject: [PATCH 20/20] fix feature flag --- lib/virtual-fs/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/virtual-fs/src/lib.rs b/lib/virtual-fs/src/lib.rs index 08e85c4ec46..840e1471085 100644 --- a/lib/virtual-fs/src/lib.rs +++ b/lib/virtual-fs/src/lib.rs @@ -91,14 +91,14 @@ pub trait FileSystem: fmt::Debug + Send + Sync + 'static + Upcastable { fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<()>>; fn metadata(&self, path: &Path) -> Result; - #[cfg(feature = "symlink")] + #[cfg(not(feature = "symlink"))] /// This method gets metadata without following symlinks in the path. /// Currently identical to `metadata` because symlinks aren't implemented /// yet. fn symlink_metadata(&self, path: &Path) -> Result { self.metadata(path) } - #[cfg(not(feature = "symlink"))] + #[cfg(feature = "symlink")] fn symlink_metadata(&self, _path: &Path) -> Result { unimplemented!() }