Skip to content

Commit

Permalink
Improve --mapdir mounting
Browse files Browse the repository at this point in the history
This improves --mapdir mounting in multiple ways:

* mounted paths are always canonicalized to absolute paths
  (both guest and host path)

* mapping a directory into the root of the file system ( '/' ) now works
  by individually mounting all direct descendants of the mounted
  directory
  • Loading branch information
theduke authored and Michael-F-Bryan committed May 31, 2023
1 parent 7d16f2a commit a5ff298
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 40 deletions.
48 changes: 35 additions & 13 deletions lib/virtual-fs/src/mem_fs/file_opener.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn crate::FileSystem + Send + Sync>,
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,
Expand All @@ -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();
Expand All @@ -118,7 +119,7 @@ impl FileSystem {
inode: inode_of_file,
name: name_of_file,
fs,
path,
path: source_path,
metadata: meta,
}));

Expand All @@ -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<dyn crate::FileSystem + Send + Sync>,
) -> 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<dyn crate::FileSystem + Send + Sync>,
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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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<dyn crate::FileSystem + Send + Sync>,
) -> 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(
Expand Down
115 changes: 108 additions & 7 deletions lib/virtual-fs/src/mem_fs/filesystem.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -28,6 +28,67 @@ impl FileSystem {
self
}

/// Canonicalize a path without validating that it actually exists.
pub fn canonicalize_unchecked(&self, path: &Path) -> Result<PathBuf> {
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<dyn crate::FileSystem + Send + Sync>,
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<dyn crate::FileSystem + Send + Sync>) {
// Iterate all the directories and files in the other filesystem
// and create references back to them in this filesystem
Expand Down Expand Up @@ -90,11 +151,11 @@ impl FileSystem {

pub fn mount(
&self,
path: PathBuf,
target_path: PathBuf,
other: &Arc<dyn crate::FileSystem + Send + Sync>,
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);
}

Expand All @@ -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)?;
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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<dyn crate::FileSystem + Send + Sync> = 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");
}
}
5 changes: 5 additions & 0 deletions lib/virtual-fs/src/tmp_fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PathBuf> {
self.fs.canonicalize_unchecked(path)
}
}

impl FileSystem for TmpFileSystem {
Expand Down
63 changes: 43 additions & 20 deletions lib/wasi/src/runners/wasi_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,37 +95,55 @@ fn prepare_filesystem(
let host_fs: Arc<dyn FileSystem + Send + Sync> = 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()))?;
}
}

Expand Down Expand Up @@ -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(),
Expand Down

0 comments on commit a5ff298

Please sign in to comment.