Skip to content

Commit

Permalink
Updated WasiRunner to allow mounting FileSystem instances as well…
Browse files Browse the repository at this point in the history
… as directories on the host system
  • Loading branch information
Michael-F-Bryan committed Jan 5, 2024
1 parent ad06d7e commit f9bf1c4
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 61 deletions.
16 changes: 4 additions & 12 deletions lib/wasix/src/runners/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,7 @@ mod wasi_common;
#[cfg(feature = "webc_runner_rt_wcgi")]
pub mod wcgi;

pub use self::{runner::Runner, wasi_common::MappedCommand};

/// A directory that should be mapped from the host filesystem into a WASI
/// instance (the "guest").
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct MappedDirectory {
/// The absolute path for a directory on the host filesystem.
pub host: std::path::PathBuf,
/// The absolute path specifying where the host directory should be mounted
/// inside the guest.
pub guest: String,
}
pub use self::{
runner::Runner,
wasi_common::{MappedCommand, MappedDirectory, MountedDirectory},
};
23 changes: 17 additions & 6 deletions lib/wasix/src/runners/wasi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ use std::{path::PathBuf, sync::Arc};

use anyhow::{Context, Error};
use tracing::Instrument;
use virtual_fs::{ArcBoxFile, TmpFileSystem, VirtualFile};
use virtual_fs::{ArcBoxFile, FileSystem, TmpFileSystem, VirtualFile};
use wasmer::Module;
use webc::metadata::{annotations::Wasi, Command};

use crate::{
bin_factory::BinaryPackage,
capabilities::Capabilities,
journal::{DynJournal, SnapshotTrigger},
runners::{wasi_common::CommonWasiOptions, MappedDirectory},
runners::{wasi_common::CommonWasiOptions, MappedDirectory, MountedDirectory},
runtime::{module_cache::ModuleHash, task_manager::VirtualTaskManagerExt},
Runtime, WasiEnvBuilder, WasiError, WasiRuntimeError,
};
Expand Down Expand Up @@ -98,14 +98,25 @@ impl WasiRunner {
self.wasi.forward_host_env = forward;
}

pub fn with_mapped_directories<I, D>(mut self, dirs: I) -> Self
pub fn with_mapped_directories<I, D>(self, dirs: I) -> Self
where
I: IntoIterator<Item = D>,
D: Into<MappedDirectory>,
{
self.wasi
.mapped_dirs
.extend(dirs.into_iter().map(|d| d.into()));
self.with_mounted_directories(dirs.into_iter().map(Into::into).map(MountedDirectory::from))
}

pub fn with_mounted_directories<I, D>(mut self, dirs: I) -> Self
where
I: IntoIterator<Item = D>,
D: Into<MountedDirectory>,
{
self.wasi.mounts.extend(dirs.into_iter().map(Into::into));
self
}

pub fn mount(&mut self, dest: String, fs: Arc<dyn FileSystem + Send + Sync>) -> &mut Self {
self.wasi.mounts.push(MountedDirectory { guest: dest, fs });
self
}

Expand Down
102 changes: 61 additions & 41 deletions lib/wasix/src/runners/wasi_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ pub(crate) struct CommonWasiOptions {
pub(crate) args: Vec<String>,
pub(crate) env: HashMap<String, String>,
pub(crate) forward_host_env: bool,
pub(crate) mapped_dirs: Vec<MappedDirectory>,
pub(crate) mapped_host_commands: Vec<MappedCommand>,
pub(crate) mounts: Vec<MountedDirectory>,
pub(crate) injected_packages: Vec<BinaryPackage>,
pub(crate) capabilities: Capabilities,
#[derivative(Debug = "ignore")]
Expand All @@ -52,12 +52,11 @@ impl CommonWasiOptions {
root_fs: Option<TmpFileSystem>,
) -> Result<(), anyhow::Error> {
let root_fs = root_fs.unwrap_or_else(|| RootFileSystemBuilder::default().build());

let fs = prepare_filesystem(root_fs, &self.mapped_dirs, container_fs, builder)?;
let fs = prepare_filesystem(root_fs, &self.mounts, container_fs)?;

builder.add_preopen_dir("/")?;

if self.mapped_dirs.iter().all(|m| m.guest != ".") {
if self.mounts.iter().all(|m| m.guest != ".") {
// The user hasn't mounted "." to anything, so let's map it to "/"
builder.add_map_dir(".", "/")?;
}
Expand Down Expand Up @@ -117,31 +116,24 @@ impl CommonWasiOptions {
// OverlayFileSystem<TmpFileSystem, [RelativeOrAbsolutePathHack<Arc<dyn FileSystem>>; 1]>;

fn build_directory_mappings(
builder: &mut WasiEnvBuilder,
root_fs: &mut TmpFileSystem,
host_fs: &Arc<dyn FileSystem + Send + Sync>,
mapped_dirs: &[MappedDirectory],
mounted_dirs: &[MountedDirectory],
) -> Result<(), anyhow::Error> {
for dir in mapped_dirs {
let MappedDirectory {
host: host_path,
for dir in mounted_dirs {
let MountedDirectory {
guest: guest_path,
fs,
} = dir;
let mut guest_path = PathBuf::from(guest_path);
tracing::debug!(
guest=%guest_path.display(),
host=%host_path.display(),
"Mounting host folder",
"Mounting",
);

if guest_path.is_relative() {
guest_path = apply_relative_path_mounting_hack(&guest_path);
}

let host_path = std::fs::canonicalize(host_path).with_context(|| {
format!("Unable to canonicalize host path '{}'", host_path.display())
})?;

let guest_path = root_fs
.canonicalize_unchecked(&guest_path)
.with_context(|| {
Expand All @@ -153,28 +145,18 @@ fn build_directory_mappings(

if guest_path == Path::new("/") {
root_fs
.mount_directory_entries(&guest_path, host_fs, &host_path)
.with_context(|| format!("Unable to mount \"{}\" to root", host_path.display(),))?;
.mount_directory_entries(&guest_path, fs, "/".as_ref())
.context("Unable to mount to root")?;
} else {
if let Some(parent) = guest_path.parent() {
create_dir_all(root_fs, parent).with_context(|| {
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_path)
.with_context(|| format!("Unable to preopen \"{}\"", guest_path.display()))?;
.mount(guest_path.clone(), fs, "/".into())
.with_context(|| format!("Unable to mount \"{}\"", guest_path.display()))?;
}
}

Expand All @@ -183,13 +165,11 @@ fn build_directory_mappings(

fn prepare_filesystem(
mut root_fs: TmpFileSystem,
mapped_dirs: &[MappedDirectory],
mounted_dirs: &[MountedDirectory],
container_fs: Option<Arc<dyn FileSystem + Send + Sync>>,
builder: &mut WasiEnvBuilder,
) -> Result<Box<dyn FileSystem + Send + Sync>, Error> {
if !mapped_dirs.is_empty() {
let host_fs: Arc<dyn FileSystem + Send + Sync> = Arc::new(crate::default_fs_backing());
build_directory_mappings(builder, &mut root_fs, &host_fs, mapped_dirs)?;
if !mounted_dirs.is_empty() {
build_directory_mappings(&mut root_fs, mounted_dirs)?;
}

// HACK(Michael-F-Bryan): The WebcVolumeFileSystem only accepts relative
Expand Down Expand Up @@ -257,6 +237,46 @@ fn create_dir_all(fs: &dyn FileSystem, path: &Path) -> Result<(), Error> {
Ok(())
}

/// A directory that should be mapped from the host filesystem into a WASI
/// instance (the "guest").
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct MappedDirectory {
/// The absolute path for a directory on the host filesystem.
pub host: std::path::PathBuf,
/// The absolute path specifying where the host directory should be mounted
/// inside the guest.
pub guest: String,
}

#[derive(Debug, Clone)]
pub struct MountedDirectory {
pub guest: String,
pub fs: Arc<dyn FileSystem + Send + Sync>,
}

impl From<MappedDirectory> for MountedDirectory {
fn from(value: MappedDirectory) -> Self {
let MappedDirectory { host, guest } = value;

// HACK: We don't have a FileSystem implementation that lets you mount
// just a single folder, so we're going to work around that using a mem
// fs and its mounting infrastructure.
let host_fs = Arc::new(crate::default_fs_backing()) as Arc<dyn FileSystem + Send + Sync>;
let temp_fs = virtual_fs::mem_fs::FileSystem::default();
if let Some(parent) = Path::new(&guest).parent() {
create_dir_all(&temp_fs, parent).unwrap();
}
temp_fs
.mount(PathBuf::from(&guest), &host_fs, host)
.unwrap();

MountedDirectory {
guest,
fs: Arc::new(temp_fs),
}
}
}

#[derive(Debug)]
struct RelativeOrAbsolutePathHack<F>(F);

Expand Down Expand Up @@ -330,6 +350,8 @@ mod tests {
use virtual_fs::WebcVolumeFileSystem;
use webc::Container;

use crate::runners::MappedDirectory;

use super::*;

const PYTHON: &[u8] = include_bytes!("../../../c-api/examples/assets/python-0.1.0.wasmer");
Expand Down Expand Up @@ -400,17 +422,15 @@ mod tests {
let sub_dir = temp.path().join("path").join("to");
std::fs::create_dir_all(&sub_dir).unwrap();
std::fs::write(sub_dir.join("file.txt"), b"Hello, World!").unwrap();
let mapping = [MappedDirectory {
let mapping = [MountedDirectory::from(MappedDirectory {
guest: "/home".to_string(),
host: sub_dir,
}];
})];
let container = Container::from_bytes(PYTHON).unwrap();
let webc_fs = WebcVolumeFileSystem::mount_all(&container);
let mut builder = WasiEnvBuilder::new("");

let root_fs = RootFileSystemBuilder::default().build();
let fs =
prepare_filesystem(root_fs, &mapping, Some(Arc::new(webc_fs)), &mut builder).unwrap();
let fs = prepare_filesystem(root_fs, &mapping, Some(Arc::new(webc_fs))).unwrap();

assert!(fs.metadata("/home/file.txt".as_ref()).unwrap().is_file());
assert!(fs.metadata("lib".as_ref()).unwrap().is_dir());
Expand Down
6 changes: 4 additions & 2 deletions lib/wasix/src/runners/wcgi/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,15 +237,17 @@ impl Config {
}

pub fn map_directory(&mut self, dir: MappedDirectory) -> &mut Self {
self.wasi.mapped_dirs.push(dir);
self.wasi.mounts.push(dir.into());
self
}

pub fn map_directories(
&mut self,
mappings: impl IntoIterator<Item = MappedDirectory>,
) -> &mut Self {
self.wasi.mapped_dirs.extend(mappings.into_iter());
for mapping in mappings {
self.map_directory(mapping);
}
self
}

Expand Down

0 comments on commit f9bf1c4

Please sign in to comment.