diff --git a/lib/virtual-fs/src/mem_fs/file_opener.rs b/lib/virtual-fs/src/mem_fs/file_opener.rs index a904fd458ff..b0cea785806 100644 --- a/lib/virtual-fs/src/mem_fs/file_opener.rs +++ b/lib/virtual-fs/src/mem_fs/file_opener.rs @@ -69,14 +69,15 @@ impl FileSystem { /// Inserts a arc file into the file system that references another file /// in another file system (does not copy the real data) - pub fn insert_arc_file( + pub fn insert_arc_file_at( &self, - path: PathBuf, + target_path: PathBuf, fs: Arc, + source_path: PathBuf, ) -> Result<()> { - let _ = crate::FileSystem::remove_file(self, path.as_path()); + let _ = crate::FileSystem::remove_file(self, target_path.as_path()); let (inode_of_parent, maybe_inode_of_file, name_of_file) = - self.insert_inode(path.as_path())?; + self.insert_inode(target_path.as_path())?; let inode_of_parent = match inode_of_parent { InodeResolution::Found(a) => a, @@ -95,7 +96,7 @@ impl FileSystem { let mut fs_lock = self.inner.write().map_err(|_| FsError::Lock)?; // Read the metadata or generate a dummy one - let meta = match fs.metadata(&path) { + let meta = match fs.metadata(&target_path) { Ok(meta) => meta, _ => { let time = time(); @@ -118,7 +119,7 @@ impl FileSystem { inode: inode_of_file, name: name_of_file, fs, - path, + path: source_path, metadata: meta, })); @@ -136,16 +137,27 @@ impl FileSystem { Ok(()) } - /// Inserts a arc directory into the file system that references another file + /// Inserts a arc file into the file system that references another file /// in another file system (does not copy the real data) - pub fn insert_arc_directory( + pub fn insert_arc_file( &self, - path: PathBuf, + target_path: PathBuf, fs: Arc, ) -> Result<()> { - let _ = crate::FileSystem::remove_dir(self, path.as_path()); + self.insert_arc_file_at(target_path.clone(), fs, target_path) + } + + /// Inserts a arc directory into the file system that references another file + /// in another file system (does not copy the real data) + pub fn insert_arc_directory_at( + &self, + target_path: PathBuf, + other: Arc, + source_path: PathBuf, + ) -> Result<()> { + let _ = crate::FileSystem::remove_dir(self, target_path.as_path()); let (inode_of_parent, maybe_inode_of_file, name_of_file) = - self.insert_inode(path.as_path())?; + self.insert_inode(target_path.as_path())?; let inode_of_parent = match inode_of_parent { InodeResolution::Found(a) => a, @@ -169,8 +181,8 @@ impl FileSystem { fs_lock.storage.insert(Node::ArcDirectory(ArcDirectoryNode { inode: inode_of_file, name: name_of_file, - fs, - path, + fs: other, + path: source_path, metadata: { let time = time(); Metadata { @@ -200,6 +212,16 @@ impl FileSystem { Ok(()) } + /// Inserts a arc directory into the file system that references another file + /// in another file system (does not copy the real data) + pub fn insert_arc_directory( + &self, + target_path: PathBuf, + other: Arc, + ) -> Result<()> { + self.insert_arc_directory_at(target_path.clone(), other, target_path) + } + /// Inserts a arc file into the file system that references another file /// in another file system (does not copy the real data) pub fn insert_device_file( diff --git a/lib/virtual-fs/src/mem_fs/filesystem.rs b/lib/virtual-fs/src/mem_fs/filesystem.rs index 356580b7b40..609fbdd8f6d 100644 --- a/lib/virtual-fs/src/mem_fs/filesystem.rs +++ b/lib/virtual-fs/src/mem_fs/filesystem.rs @@ -1,7 +1,7 @@ //! This module contains the [`FileSystem`] type itself. use super::*; -use crate::{DirEntry, FileType, FsError, Metadata, OpenOptions, ReadDir, Result}; +use crate::{DirEntry, FileSystem as _, FileType, FsError, Metadata, OpenOptions, ReadDir, Result}; use slab::Slab; use std::collections::VecDeque; use std::convert::identity; @@ -28,6 +28,67 @@ impl FileSystem { self } + /// Canonicalize a path without validating that it actually exists. + pub fn canonicalize_unchecked(&self, path: &Path) -> Result { + let lock = self.inner.read().map_err(|_| FsError::Lock)?; + lock.canonicalize_without_inode(path) + } + + /// Merge all items from a given source path (directory) of a different file + /// system into this file system. + /// + /// Individual files and directories of the given path are mounted. + /// + /// This function is not recursive, only the items in the source_path are + /// mounted. + /// + /// See [`Self::union`] for mounting all inodes recursively. + pub fn mount_directory_entries( + &self, + target_path: &Path, + other: &Arc, + source_path: &Path, + ) -> Result<()> { + let fs_lock = self.inner.read().map_err(|_| FsError::Lock)?; + + let (_target_path, root_inode) = match fs_lock.canonicalize(target_path) { + Ok((p, InodeResolution::Found(inode))) => (p, inode), + Ok((_p, InodeResolution::Redirect(..))) => { + return Err(FsError::AlreadyExists); + } + Err(_) => { + // Root directory does not exist, so we can just mount. + return self.mount(target_path.to_path_buf(), other, source_path.to_path_buf()); + } + }; + + let _root_node = match fs_lock.storage.get(root_inode).unwrap() { + Node::Directory(dir) => dir, + _ => { + return Err(FsError::AlreadyExists); + } + }; + + let source_path = fs_lock.canonicalize_without_inode(source_path)?; + + std::mem::drop(fs_lock); + + let source = other.read_dir(&source_path)?; + for entry in source.data { + let meta = entry.metadata?; + + let entry_target_path = target_path.join(entry.path.file_name().unwrap()); + + if meta.is_file() { + self.insert_arc_file_at(entry_target_path, other.clone(), entry.path)?; + } else if meta.is_dir() { + self.insert_arc_directory_at(entry_target_path, other.clone(), entry.path)?; + } + } + + Ok(()) + } + pub fn union(&self, other: &Arc) { // Iterate all the directories and files in the other filesystem // and create references back to them in this filesystem @@ -90,11 +151,11 @@ impl FileSystem { pub fn mount( &self, - path: PathBuf, + target_path: PathBuf, other: &Arc, - dst: PathBuf, + source_path: PathBuf, ) -> Result<()> { - if crate::FileSystem::read_dir(self, path.as_path()).is_ok() { + if crate::FileSystem::read_dir(self, target_path.as_path()).is_ok() { return Err(FsError::AlreadyExists); } @@ -104,7 +165,7 @@ impl FileSystem { // Canonicalize the path without checking the path exists, // because it's about to be created. - let path = guard.canonicalize_without_inode(path.as_path())?; + let path = guard.canonicalize_without_inode(target_path.as_path())?; // Check the path has a parent. let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?; @@ -136,7 +197,7 @@ impl FileSystem { inode: inode_of_directory, name: name_of_directory, fs: other.clone(), - path: dst, + path: source_path, metadata: { let time = time(); @@ -962,7 +1023,11 @@ impl DirectoryMustBeEmpty { #[cfg(test)] mod test_filesystem { - use crate::{mem_fs::*, ops, DirEntry, FileSystem as FS, FileType, FsError}; + use std::{borrow::Cow, path::Path}; + + use tokio::io::AsyncReadExt; + + use crate::{mem_fs::*, ops, DirEntry, FileOpener, FileSystem as FS, FileType, FsError}; macro_rules! path { ($path:expr) => { @@ -1731,4 +1796,40 @@ mod test_filesystem { assert!(ops::is_dir(&fs, "/top-level/nested")); assert!(ops::is_file(&fs, "/top-level/nested/another-file.txt")); } + + #[tokio::test] + async fn test_merge_flat() { + let main = FileSystem::default(); + + let other = FileSystem::default(); + crate::ops::create_dir_all(&other, "/a/x").unwrap(); + other + .insert_ro_file(&Path::new("/a/x/a.txt"), Cow::Borrowed(b"a")) + .unwrap(); + other + .insert_ro_file(&Path::new("/a/x/b.txt"), Cow::Borrowed(b"b")) + .unwrap(); + other + .insert_ro_file(&Path::new("/a/x/c.txt"), Cow::Borrowed(b"c")) + .unwrap(); + + let out = other.read_dir(&Path::new("/")).unwrap(); + dbg!(&out); + + let other: Arc = Arc::new(other); + + main.mount_directory_entries(&Path::new("/"), &other, &Path::new("/a")) + .unwrap(); + + let mut buf = Vec::new(); + + let mut f = main + .new_open_options() + .read(true) + .open(&Path::new("/x/a.txt")) + .unwrap(); + f.read_to_end(&mut buf).await.unwrap(); + + assert_eq!(buf, b"a"); + } } diff --git a/lib/virtual-fs/src/tmp_fs.rs b/lib/virtual-fs/src/tmp_fs.rs index ed1f4be0149..ebad58c4b7c 100644 --- a/lib/virtual-fs/src/tmp_fs.rs +++ b/lib/virtual-fs/src/tmp_fs.rs @@ -50,6 +50,11 @@ impl TmpFileSystem { ) -> Result<()> { self.fs.mount(src_path, other, dst_path) } + + /// Canonicalize a path without validating that it actually exists. + pub fn canonicalize_unchecked(&self, path: &Path) -> Result { + self.fs.canonicalize_unchecked(path) + } } impl FileSystem for TmpFileSystem { diff --git a/lib/wasi/src/runners/wasi_common.rs b/lib/wasi/src/runners/wasi_common.rs index 694add3d8d9..898fa8d9f21 100644 --- a/lib/wasi/src/runners/wasi_common.rs +++ b/lib/wasi/src/runners/wasi_common.rs @@ -95,37 +95,55 @@ fn prepare_filesystem( let host_fs: Arc = Arc::new(crate::default_fs_backing()); for dir in mapped_dirs { - let MappedDirectory { host, guest } = dir; - let mut guest = PathBuf::from(guest); + let MappedDirectory { + host: host_path, + guest: guest_path, + } = dir; + let mut guest_path = PathBuf::from(guest_path); tracing::debug!( - guest=%guest.display(), - host=%host.display(), + guest=%guest_path.display(), + host=%host_path.display(), "Mounting host folder", ); - if guest.is_relative() { - guest = apply_relative_path_mounting_hack(&guest); + if guest_path.is_relative() { + guest_path = apply_relative_path_mounting_hack(&guest_path); } - if let Some(parent) = guest.parent() { - create_dir_all(&root_fs, parent).with_context(|| { - format!("Unable to create the \"{}\" directory", parent.display()) + let host_path = root_fs + .canonicalize_unchecked(&host_path) + .with_context(|| { + format!("Unable to canonicalize path '{}'", host_path.display()) })?; - } - root_fs - .mount(guest.clone(), &host_fs, host.clone()) + let guest_path = root_fs + .canonicalize_unchecked(&guest_path) .with_context(|| { - format!( - "Unable to mount \"{}\" to \"{}\"", - host.display(), - guest.display() - ) + format!("Unable to canonicalize path '{}'", guest_path.display()) })?; + if &guest_path == &Path::new("/") { + } else { + if let Some(parent) = guest_path.parent() { + create_dir_all(&root_fs, parent).with_context(|| { + format!("Unable to create the \"{}\" directory", parent.display()) + })?; + } + + root_fs + .mount(guest_path.clone(), &host_fs, host_path.clone()) + .with_context(|| { + format!( + "Unable to mount \"{}\" to \"{}\"", + host_path.display(), + guest_path.display() + ) + })?; + } + builder - .add_preopen_dir(&guest) - .with_context(|| format!("Unable to preopen \"{}\"", guest.display()))?; + .add_preopen_dir(&guest_path) + .with_context(|| format!("Unable to preopen \"{}\"", guest_path.display()))?; } } @@ -156,7 +174,12 @@ fn prepare_filesystem( fn apply_relative_path_mounting_hack(original: &Path) -> PathBuf { debug_assert!(original.is_relative()); - let mapped_path = Path::new("/").join(original); + let root = Path::new("/"); + let mapped_path = if &original == &Path::new(".") { + root.to_path_buf() + } else { + root.join(original) + }; tracing::debug!( original_path=%original.display(),