diff --git a/lib/virtual-fs/src/arc_fs.rs b/lib/virtual-fs/src/arc_fs.rs index 734774fc16c..605f814ddc8 100644 --- a/lib/virtual-fs/src/arc_fs.rs +++ b/lib/virtual-fs/src/arc_fs.rs @@ -18,6 +18,10 @@ impl ArcFileSystem { } impl FileSystem for ArcFileSystem { + fn readlink(&self, path: &Path) -> Result { + self.fs.readlink(path) + } + fn read_dir(&self, path: &Path) -> Result { self.fs.read_dir(path) } diff --git a/lib/virtual-fs/src/empty_fs.rs b/lib/virtual-fs/src/empty_fs.rs index ce5be69637c..6745c337453 100644 --- a/lib/virtual-fs/src/empty_fs.rs +++ b/lib/virtual-fs/src/empty_fs.rs @@ -12,6 +12,10 @@ pub struct EmptyFileSystem {} #[allow(unused_variables)] impl FileSystem for EmptyFileSystem { + fn readlink(&self, path: &Path) -> Result { + Err(FsError::EntryNotFound) + } + fn read_dir(&self, path: &Path) -> Result { // Special-case the root path by returning an empty iterator. // An empty file system should still be readable, just not contain diff --git a/lib/virtual-fs/src/host_fs.rs b/lib/virtual-fs/src/host_fs.rs index 43048b2dc21..b39a0e791d8 100644 --- a/lib/virtual-fs/src/host_fs.rs +++ b/lib/virtual-fs/src/host_fs.rs @@ -52,6 +52,10 @@ impl FileSystem { } impl crate::FileSystem for FileSystem { + fn readlink(&self, path: &Path) -> Result { + fs::read_link(path).map_err(Into::into) + } + fn read_dir(&self, path: &Path) -> Result { let read_dir = fs::read_dir(path)?; let mut data = read_dir @@ -153,6 +157,12 @@ impl crate::FileSystem for FileSystem { .and_then(TryInto::try_into) .map_err(Into::into) } + + fn symlink_metadata(&self, path: &Path) -> Result { + fs::symlink_metadata(path) + .and_then(TryInto::try_into) + .map_err(Into::into) + } } impl TryInto for std::fs::Metadata { diff --git a/lib/virtual-fs/src/lib.rs b/lib/virtual-fs/src/lib.rs index f3a4f66d2a0..dd23826d89c 100644 --- a/lib/virtual-fs/src/lib.rs +++ b/lib/virtual-fs/src/lib.rs @@ -91,6 +91,7 @@ pub trait ClonableVirtualFile: VirtualFile + Clone {} pub use ops::{copy_reference, copy_reference_ext}; pub trait FileSystem: fmt::Debug + Send + Sync + 'static + Upcastable { + fn readlink(&self, path: &Path) -> Result; fn read_dir(&self, path: &Path) -> Result; fn create_dir(&self, path: &Path) -> Result<()>; fn remove_dir(&self, path: &Path) -> Result<()>; @@ -99,9 +100,7 @@ pub trait FileSystem: fmt::Debug + Send + Sync + 'static + Upcastable { /// 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) - } + fn symlink_metadata(&self, path: &Path) -> Result; fn remove_file(&self, path: &Path) -> Result<()>; fn new_open_options(&self) -> OpenOptions; @@ -128,6 +127,10 @@ where (**self).read_dir(path) } + fn readlink(&self, path: &Path) -> Result { + (**self).readlink(path) + } + fn create_dir(&self, path: &Path) -> Result<()> { (**self).create_dir(path) } @@ -144,6 +147,10 @@ where (**self).metadata(path) } + fn symlink_metadata(&self, path: &Path) -> Result { + (**self).symlink_metadata(path) + } + 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 3c1258048c4..97d5c6679a0 100644 --- a/lib/virtual-fs/src/mem_fs/filesystem.rs +++ b/lib/virtual-fs/src/mem_fs/filesystem.rs @@ -257,6 +257,18 @@ impl FileSystem { } impl crate::FileSystem for FileSystem { + fn readlink(&self, path: &Path) -> Result { + // Read lock. + let guard = self.inner.read().map_err(|_| FsError::Lock)?; + + // Canonicalize the path. + let (_, inode_of_directory) = guard.canonicalize(path)?; + match inode_of_directory { + InodeResolution::Found(_) => Err(FsError::InvalidInput), + InodeResolution::Redirect(fs, path) => fs.readlink(path.as_path()), + } + } + fn read_dir(&self, path: &Path) -> Result { // Read lock. let guard = self.inner.read().map_err(|_| FsError::Lock)?; @@ -591,6 +603,23 @@ impl crate::FileSystem for FileSystem { } } + fn symlink_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.symlink_metadata(path.as_path()) + } + } + } + fn remove_file(&self, path: &Path) -> Result<()> { let (inode_of_parent, position, inode_of_file) = { // Read lock. diff --git a/lib/virtual-fs/src/overlay_fs.rs b/lib/virtual-fs/src/overlay_fs.rs index cdee8580e21..e243327f33d 100644 --- a/lib/virtual-fs/src/overlay_fs.rs +++ b/lib/virtual-fs/src/overlay_fs.rs @@ -113,6 +113,35 @@ where S: for<'a> FileSystems<'a> + Send + Sync + 'static, for<'a> <>::Iter as IntoIterator>::IntoIter: Send, { + fn readlink(&self, path: &Path) -> crate::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.readlink(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.readlink(path) { + Err(e) if should_continue(e) => continue, + other => return other, + } + } + + Err(FsError::EntryNotFound) + } + fn read_dir(&self, path: &Path) -> Result { let mut entries = Vec::new(); let mut had_at_least_one_success = false; @@ -328,6 +357,35 @@ where Err(FsError::EntryNotFound) } + fn symlink_metadata(&self, path: &Path) -> crate::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 diff --git a/lib/virtual-fs/src/passthru_fs.rs b/lib/virtual-fs/src/passthru_fs.rs index 08a940ed0c5..0d138a90075 100644 --- a/lib/virtual-fs/src/passthru_fs.rs +++ b/lib/virtual-fs/src/passthru_fs.rs @@ -18,6 +18,10 @@ impl PassthruFileSystem { } impl FileSystem for PassthruFileSystem { + fn readlink(&self, path: &Path) -> Result { + self.fs.readlink(path) + } + fn read_dir(&self, path: &Path) -> Result { self.fs.read_dir(path) } diff --git a/lib/virtual-fs/src/scoped_directory_fs.rs b/lib/virtual-fs/src/scoped_directory_fs.rs index 43b3b020093..ac292fef82a 100644 --- a/lib/virtual-fs/src/scoped_directory_fs.rs +++ b/lib/virtual-fs/src/scoped_directory_fs.rs @@ -51,6 +51,11 @@ impl ScopedDirectoryFileSystem { } impl FileSystem for ScopedDirectoryFileSystem { + fn readlink(&self, path: &Path) -> crate::Result { + let path = self.prepare_path(path); + self.inner.readlink(&path) + } + fn read_dir(&self, path: &Path) -> Result { let path = self.prepare_path(path); @@ -94,6 +99,11 @@ impl FileSystem for ScopedDirectoryFileSystem { self.inner.metadata(&path) } + fn symlink_metadata(&self, path: &Path) -> crate::Result { + let path = self.prepare_path(path); + self.inner.symlink_metadata(&path) + } + fn remove_file(&self, path: &Path) -> Result<(), FsError> { let path = self.prepare_path(path); self.inner.remove_file(&path) diff --git a/lib/virtual-fs/src/static_fs.rs b/lib/virtual-fs/src/static_fs.rs index 767741ec743..9a45702a4a6 100644 --- a/lib/virtual-fs/src/static_fs.rs +++ b/lib/virtual-fs/src/static_fs.rs @@ -238,6 +238,20 @@ fn transform_into_read_dir(path: &Path, fs_entries: &[FsEntry<'_>]) -> crate::Re } impl FileSystem for StaticFileSystem { + fn readlink(&self, path: &Path) -> crate::Result { + let path = normalizes_path(path); + if self + .volumes + .values() + .find_map(|v| v.get_file_entry(&path).ok()) + .is_some() + { + Err(FsError::InvalidInput) + } else { + self.memory.readlink(Path::new(&path)) + } + } + fn read_dir(&self, path: &Path) -> Result { let path = normalizes_path(path); for volume in self.volumes.values() { diff --git a/lib/virtual-fs/src/tmp_fs.rs b/lib/virtual-fs/src/tmp_fs.rs index 46e47f0fe53..0f0667f402c 100644 --- a/lib/virtual-fs/src/tmp_fs.rs +++ b/lib/virtual-fs/src/tmp_fs.rs @@ -61,6 +61,10 @@ impl TmpFileSystem { } impl FileSystem for TmpFileSystem { + fn readlink(&self, path: &Path) -> Result { + self.fs.readlink(path) + } + fn read_dir(&self, path: &Path) -> Result { self.fs.read_dir(path) } diff --git a/lib/virtual-fs/src/trace_fs.rs b/lib/virtual-fs/src/trace_fs.rs index 86abc0687bd..44aa5bbbbc5 100644 --- a/lib/virtual-fs/src/trace_fs.rs +++ b/lib/virtual-fs/src/trace_fs.rs @@ -39,6 +39,11 @@ impl FileSystem for TraceFileSystem where F: FileSystem, { + #[tracing::instrument(level = "trace", skip(self), err)] + fn readlink(&self, path: &std::path::Path) -> crate::Result { + self.0.readlink(path) + } + #[tracing::instrument(level = "trace", skip(self), err)] fn read_dir(&self, path: &std::path::Path) -> crate::Result { self.0.read_dir(path) @@ -68,6 +73,11 @@ where self.0.metadata(path) } + #[tracing::instrument(level = "trace", skip(self), err)] + fn symlink_metadata(&self, path: &std::path::Path) -> crate::Result { + self.0.symlink_metadata(path) + } + #[tracing::instrument(level = "trace", skip(self), err)] fn remove_file(&self, path: &std::path::Path) -> crate::Result<()> { self.0.remove_file(path) diff --git a/lib/virtual-fs/src/union_fs.rs b/lib/virtual-fs/src/union_fs.rs index 635a92c62bc..f9b3b95ca00 100644 --- a/lib/virtual-fs/src/union_fs.rs +++ b/lib/virtual-fs/src/union_fs.rs @@ -206,6 +206,32 @@ impl UnionFileSystem { } impl FileSystem for UnionFileSystem { + fn readlink(&self, path: &Path) -> Result { + debug!("readlink: 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.readlink(Path::new(path_inner.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!("readlink failed: (path={}) - {}", path, err); + ret_error = err; + } + } + } + } + debug!("readlink: failed={}", ret_error); + Err(ret_error) + } + fn read_dir(&self, path: &Path) -> Result { debug!("read_dir: path={}", path.display()); self.read_dir_internal(path) diff --git a/lib/virtual-fs/src/webc_fs.rs b/lib/virtual-fs/src/webc_fs.rs index ba521564fc5..eba2387b673 100644 --- a/lib/virtual-fs/src/webc_fs.rs +++ b/lib/virtual-fs/src/webc_fs.rs @@ -304,6 +304,10 @@ where T: std::fmt::Debug + Send + Sync + 'static, T: Deref>, { + fn readlink(&self, _path: &Path) -> crate::Result { + Err(FsError::InvalidInput) + } + fn read_dir(&self, path: &Path) -> Result { let path = normalizes_path(path); let read_dir_result = self diff --git a/lib/virtual-fs/src/webc_volume_fs.rs b/lib/virtual-fs/src/webc_volume_fs.rs index ce107cf65dd..f75dffff213 100644 --- a/lib/virtual-fs/src/webc_volume_fs.rs +++ b/lib/virtual-fs/src/webc_volume_fs.rs @@ -49,6 +49,10 @@ impl WebcVolumeFileSystem { } impl FileSystem for WebcVolumeFileSystem { + fn readlink(&self, _path: &Path) -> crate::Result { + Err(FsError::InvalidInput) + } + fn read_dir(&self, path: &Path) -> Result { let meta = self.metadata(path)?; @@ -134,6 +138,10 @@ impl FileSystem for WebcVolumeFileSystem { .ok_or(FsError::EntryNotFound) } + fn symlink_metadata(&self, path: &Path) -> crate::Result { + self.metadata(path) + } + fn remove_file(&self, path: &Path) -> Result<(), FsError> { let meta = self.metadata(path)?; diff --git a/lib/wasix/src/fs/mod.rs b/lib/wasix/src/fs/mod.rs index 3e5efed659a..24751ceec1e 100644 --- a/lib/wasix/src/fs/mod.rs +++ b/lib/wasix/src/fs/mod.rs @@ -308,6 +308,13 @@ impl WasiFsRoot { } impl FileSystem for WasiFsRoot { + fn readlink(&self, path: &Path) -> virtual_fs::Result { + match self { + WasiFsRoot::Sandbox(fs) => fs.readlink(path), + WasiFsRoot::Backing(fs) => fs.readlink(path), + } + } + fn read_dir(&self, path: &Path) -> virtual_fs::Result { match self { WasiFsRoot::Sandbox(fs) => fs.read_dir(path), @@ -992,7 +999,8 @@ impl WasiFs { } } else if file_type.is_symlink() { should_insert = false; - let link_value = file.read_link().map_err(map_io_err)?; + let link_value = + self.root_fs.readlink(&file).ok().ok_or(Errno::Noent)?; debug!("attempting to decompose path {:?}", link_value); let (pre_open_dir_fd, relative_path) = if link_value.is_relative() { @@ -2026,6 +2034,9 @@ impl FallbackFileSystem { } impl FileSystem for FallbackFileSystem { + fn readlink(&self, _path: &Path) -> virtual_fs::Result { + Self::fail() + } fn read_dir(&self, _path: &Path) -> Result { Self::fail(); } diff --git a/lib/wasix/src/runners/wasi_common.rs b/lib/wasix/src/runners/wasi_common.rs index f2b2bb9bf36..1a48482c1fc 100644 --- a/lib/wasix/src/runners/wasi_common.rs +++ b/lib/wasix/src/runners/wasi_common.rs @@ -302,6 +302,10 @@ impl RelativeOrAbsolutePathHack { } impl virtual_fs::FileSystem for RelativeOrAbsolutePathHack { + fn readlink(&self, path: &Path) -> virtual_fs::Result { + self.execute(path, |fs, p| fs.readlink(p)) + } + fn read_dir(&self, path: &Path) -> virtual_fs::Result { self.execute(path, |fs, p| fs.read_dir(p)) } @@ -324,6 +328,10 @@ impl virtual_fs::FileSystem for RelativeOrAbsolutePathHack { self.execute(path, |fs, p| fs.metadata(p)) } + fn symlink_metadata(&self, path: &Path) -> virtual_fs::Result { + self.execute(path, |fs, p| fs.symlink_metadata(p)) + } + fn remove_file(&self, path: &Path) -> virtual_fs::Result<()> { self.execute(path, |fs, p| fs.remove_file(p)) } diff --git a/lib/wasix/src/runtime/package_loader/load_package_tree.rs b/lib/wasix/src/runtime/package_loader/load_package_tree.rs index a7c14e06b98..0b1f3a82e4e 100644 --- a/lib/wasix/src/runtime/package_loader/load_package_tree.rs +++ b/lib/wasix/src/runtime/package_loader/load_package_tree.rs @@ -528,6 +528,11 @@ where F: FileSystem, M: Fn(&Path) -> Result + Send + Sync + 'static, { + fn readlink(&self, path: &Path) -> virtual_fs::Result { + let path = self.path(path)?; + self.inner.readlink(&path) + } + fn read_dir(&self, path: &Path) -> virtual_fs::Result { let path = self.path(path)?; self.inner.read_dir(&path) @@ -558,6 +563,11 @@ where self.inner.metadata(&path) } + fn symlink_metadata(&self, path: &Path) -> virtual_fs::Result { + let path = self.path(path)?; + self.inner.symlink_metadata(&path) + } + fn remove_file(&self, path: &Path) -> virtual_fs::Result<()> { let path = self.path(path)?; self.inner.remove_file(&path) diff --git a/lib/wasix/src/syscalls/wasi/path_create_directory.rs b/lib/wasix/src/syscalls/wasi/path_create_directory.rs index b7263ff21c5..bf20379e261 100644 --- a/lib/wasix/src/syscalls/wasi/path_create_directory.rs +++ b/lib/wasix/src/syscalls/wasi/path_create_directory.rs @@ -84,6 +84,7 @@ pub(crate) fn path_create_directory_internal( return Err(Errno::Inval); } + let mut created_dir = false; let mut cur_dir_inode = working_dir.inode; for comp in &path_vec { let processing_cur_dir_inode = cur_dir_inode.clone(); @@ -125,6 +126,7 @@ pub(crate) fn path_create_directory_internal( return Err(Errno::Notdir); } } else { + created_dir = true; state.fs_create_dir(&adjusted_path)?; } let kind = Kind::Dir { @@ -160,5 +162,9 @@ pub(crate) fn path_create_directory_internal( } } - Ok(()) + if created_dir { + Ok(()) + } else { + Err(Errno::Exist) + } } diff --git a/tests/wasi-fyi/fs_create_dir-new-directory.rs b/tests/wasi-fyi/fs_create_dir-new-directory.rs index e9f99d69ece..56b3db4dc7b 100644 --- a/tests/wasi-fyi/fs_create_dir-new-directory.rs +++ b/tests/wasi-fyi/fs_create_dir-new-directory.rs @@ -2,4 +2,5 @@ use std::fs; fn main() { assert!(fs::create_dir("/fyi/fs_create_dir-new-directory.dir/new_directory").is_ok()); + assert!(fs::remove_dir("/fyi/fs_create_dir-new-directory.dir/new_directory").is_ok()); } diff --git a/tests/wasi-fyi/fs_create_dir_all-existing.rs b/tests/wasi-fyi/fs_create_dir_all-existing.rs new file mode 100644 index 00000000000..cd2a087c2c6 --- /dev/null +++ b/tests/wasi-fyi/fs_create_dir_all-existing.rs @@ -0,0 +1,7 @@ +use std::fs; + +fn main() { + assert!( + fs::create_dir_all("/fyi/fs_create_dir-existing-directory.dir/existing_directory").is_ok() + ); +} diff --git a/tests/wasi-fyi/fs_create_dir_all-new-directories.rs b/tests/wasi-fyi/fs_create_dir_all-new-directories.rs new file mode 100644 index 00000000000..0c46e5b14fa --- /dev/null +++ b/tests/wasi-fyi/fs_create_dir_all-new-directories.rs @@ -0,0 +1,6 @@ +use std::fs; + +fn main() { + assert!(fs::create_dir_all("/fyi/fs_create_dir_all-new-directories.dir/new_directory").is_ok()); + assert!(fs::remove_dir_all("/fyi/fs_create_dir_all-new-directories.dir/new_directory").is_ok()); +} diff --git a/tests/wasi-fyi/fs_rename-directory.rs b/tests/wasi-fyi/fs_rename-directory.rs index ed04b887c67..af4ca249799 100644 --- a/tests/wasi-fyi/fs_rename-directory.rs +++ b/tests/wasi-fyi/fs_rename-directory.rs @@ -5,4 +5,5 @@ fn main() { let new_path = "/fyi/fs_rename-directory.dir/new_directory"; assert!(fs::rename(old_path, new_path).is_ok()); + assert!(fs::rename(new_path, old_path).is_ok()); } diff --git a/tests/wasi-fyi/fs_rename-file.rs b/tests/wasi-fyi/fs_rename-file.rs index e598ce2dbb3..4a304d30b70 100644 --- a/tests/wasi-fyi/fs_rename-file.rs +++ b/tests/wasi-fyi/fs_rename-file.rs @@ -11,4 +11,6 @@ fn main() { let metadata = fs::metadata(new_path).unwrap(); assert!(metadata.is_file()); + + assert!(fs::rename(new_path, old_path).is_ok()); } diff --git a/tests/wasi-fyi/ported_readlink.rs b/tests/wasi-fyi/ported_readlink.rs index 7bc3d7b0fba..2ad013f3043 100644 --- a/tests/wasi-fyi/ported_readlink.rs +++ b/tests/wasi-fyi/ported_readlink.rs @@ -6,9 +6,6 @@ use std::io::Read; fn main() { let sym_link_path = "/hamlet/bookmarks/2019-07-16"; - let p = std::path::Path::new(sym_link_path); - dbg!(&p); - println!("{}", p.exists()); let link_path = std::fs::read_link(sym_link_path).expect("Could not read link"); println!("{}", link_path.to_string_lossy()); diff --git a/tests/wasi-fyi/ported_readlink.stdout b/tests/wasi-fyi/ported_readlink.stdout index e69de29bb2d..547b33b18d2 100644 --- a/tests/wasi-fyi/ported_readlink.stdout +++ b/tests/wasi-fyi/ported_readlink.stdout @@ -0,0 +1,4 @@ +../act1/scene2.txt +SCENE II. A room of state in the castle. + + Enter KING CLAUDIUS, QUEEN GERTRUDE, HAMLET, POLONIUS, LAERTES, VOLTIMAND, CORNELI diff --git a/tests/wasi-fyi/test.sh b/tests/wasi-fyi/test.sh index 4085c2c024e..5d5214b7b24 100755 --- a/tests/wasi-fyi/test.sh +++ b/tests/wasi-fyi/test.sh @@ -6,7 +6,7 @@ bash build.sh status=0 # Define skip list as an array -SKIP_LIST=("ported_readlink.wasm" "fs_create_dir-existing-directory.wasm") +SKIP_LIST=() # List and process .foo files for file in *.wasm; do