Skip to content

Commit

Permalink
Extract WASI-based runner code into its own module
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael-F-Bryan committed Mar 24, 2023
1 parent 4d6de92 commit c6de93c
Show file tree
Hide file tree
Showing 8 changed files with 312 additions and 342 deletions.
8 changes: 3 additions & 5 deletions lib/wasi/src/runners/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,17 +111,15 @@ impl WapmContainer {

/// Get the entire container as a single filesystem and a list of suggested
/// directories to preopen.
pub(crate) fn container_fs(&self) -> (Box<dyn FileSystem + Send + Sync>, Vec<String>) {
pub(crate) fn container_fs(&self) -> Arc<dyn FileSystem + Send + Sync> {
match &self.repr {
Repr::V1Mmap(mapped) => {
let fs = WebcFileSystem::init_all(Arc::clone(mapped));
let top_level_dirs = fs.top_level_dirs().clone();
(Box::new(fs), top_level_dirs)
Arc::new(fs)
}
Repr::V1Owned(owned) => {
let fs = WebcFileSystem::init_all(Arc::clone(owned));
let top_level_dirs = fs.top_level_dirs().clone();
(Box::new(fs), top_level_dirs)
Arc::new(fs)
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions lib/wasi/src/runners/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ mod runner;
pub mod emscripten;
#[cfg(feature = "webc_runner_rt_wasi")]
pub mod wasi;
#[cfg(any(feature = "webc_runner_rt_wasi", feature = "webc_runner_rt_wcgi"))]
mod wasi_common;
#[cfg(feature = "webc_runner_rt_wcgi")]
pub mod wcgi;

Expand All @@ -13,10 +15,8 @@ pub use self::{
runner::Runner,
};

use std::path::PathBuf;

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct MappedDirectory {
pub host: PathBuf,
pub host: std::path::PathBuf,
pub guest: String,
}
151 changes: 28 additions & 123 deletions lib/wasi/src/runners/wasi.rs
Original file line number Diff line number Diff line change
@@ -1,50 +1,39 @@
//! WebC container support for running WASI modules
use std::{
collections::HashMap,
path::{Path, PathBuf},
sync::Arc,
};
use std::sync::Arc;

use crate::{
runners::{MappedDirectory, WapmContainer},
PluggableRuntime, VirtualTaskManager,
};
use crate::{WasiEnv, WasiEnvBuilder};
use anyhow::{Context, Error};
use serde::{Deserialize, Serialize};
use virtual_fs::{FileSystem, OverlayFileSystem, RootFileSystemBuilder, TraceFileSystem};
use wasmer::{Module, Store};
use webc::metadata::{annotations::Wasi, Command};

use crate::{
runners::{wasi_common::CommonWasiOptions, MappedDirectory, WapmContainer},
PluggableRuntime, VirtualTaskManager, WasiEnvBuilder,
};

#[derive(Debug, Serialize, Deserialize)]
pub struct WasiRunner {
args: Vec<String>,
env: HashMap<String, String>,
forward_host_env: bool,
mapped_dirs: Vec<MappedDirectory>,
wasi: CommonWasiOptions,
#[serde(skip, default)]
store: Store,
#[serde(skip, default)]
tasks: Option<Arc<dyn VirtualTaskManager>>,
pub(crate) tasks: Option<Arc<dyn VirtualTaskManager>>,
}

impl WasiRunner {
/// Constructs a new `WasiRunner` given an `Store`
pub fn new(store: Store) -> Self {
Self {
args: Vec::new(),
env: HashMap::new(),
store,
mapped_dirs: Vec::new(),
forward_host_env: false,
wasi: CommonWasiOptions::default(),
tasks: None,
}
}

/// Returns the current arguments for this `WasiRunner`
pub fn get_args(&self) -> Vec<String> {
self.args.clone()
self.wasi.args.clone()
}

/// Builder method to provide CLI args to the runner
Expand All @@ -63,7 +52,7 @@ impl WasiRunner {
A: IntoIterator<Item = S>,
S: Into<String>,
{
self.args = args.into_iter().map(|s| s.into()).collect();
self.wasi.args = args.into_iter().map(|s| s.into()).collect();
}

/// Builder method to provide environment variables to the runner.
Expand All @@ -74,7 +63,7 @@ impl WasiRunner {

/// Provide environment variables to the runner.
pub fn set_env(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.env.insert(key.into(), value.into());
self.wasi.env.insert(key.into(), value.into());
}

pub fn with_envs<I, K, V>(mut self, envs: I) -> Self
Expand All @@ -94,7 +83,7 @@ impl WasiRunner {
V: Into<String>,
{
for (key, value) in envs {
self.env.insert(key.into(), value.into());
self.wasi.env.insert(key.into(), value.into());
}
}

Expand All @@ -104,15 +93,17 @@ impl WasiRunner {
}

pub fn set_forward_host_env(&mut self) {
self.forward_host_env = true;
self.wasi.forward_host_env = true;
}

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

Expand All @@ -128,30 +119,12 @@ impl WasiRunner {
fn prepare_webc_env(
&self,
container: &WapmContainer,
command: &str,
program_name: &str,
wasi: &Wasi,
) -> Result<WasiEnvBuilder, anyhow::Error> {
let mut builder = WasiEnv::builder(command).args(&self.args);

let fs = prepare_filesystem(&self.mapped_dirs, container)?;
builder.set_fs(fs);
// builder.set_fs(Box::new(TraceFileSystem(OverlayFileSystem::new(
// container_fs,
// [root_fs],
// ))));
// builder.set_fs(Box::new(TraceFileSystem(container_fs)));

builder.add_preopen_dir("/")?;
builder.add_preopen_dir(".")?;

if self.forward_host_env {
for (k, v) in std::env::vars() {
builder.add_env(k, v);
}
}

for (k, v) in &self.env {
builder.add_env(k, v);
}
let mut builder =
self.wasi
.prepare_webc_env(container.container_fs(), program_name, wasi)?;

if let Some(tasks) = &self.tasks {
let rt = PluggableRuntime::new(Arc::clone(tasks));
Expand All @@ -177,88 +150,20 @@ impl crate::runners::Runner for WasiRunner {
command: &Command,
container: &WapmContainer,
) -> Result<Self::Output, Error> {
let atom_name = match command.get_annotation("wasi")? {
Some(Wasi { atom, .. }) => atom,
None => command_name.to_string(),
};
let wasi = command
.get_annotation("wasi")?
.unwrap_or_else(|| Wasi::new(command_name));
let atom_name = &wasi.atom;
let atom = container
.get_atom(&atom_name)
.with_context(|| format!("Unable to get the \"{atom_name}\" atom"))?;

let mut module = Module::new(&self.store, atom)?;
module.set_name(&atom_name);

self.prepare_webc_env(container, &atom_name)?.run(module)?;
self.prepare_webc_env(container, &atom_name, &wasi)?
.run(module)?;

Ok(())
}
}

fn prepare_filesystem(
mapped_dirs: &[MappedDirectory],
container: &WapmContainer,
) -> Result<Box<dyn FileSystem + Send + Sync>, Error> {
let root_fs = RootFileSystemBuilder::default().build();

if !mapped_dirs.is_empty() {
let host_fs: Arc<dyn FileSystem + Send + Sync> = Arc::new(crate::default_fs_backing());

for mapped in mapped_dirs {
let MappedDirectory { host, guest } = mapped;
let guest = PathBuf::from(guest);
tracing::debug!(
guest=%guest.display(),
host=%host.display(),
"Mounting host folder",
);
root_fs
.mount(guest.clone(), &host_fs, host.clone())
.with_context(|| {
format!(
"Unable to mount \"{}\" to \"{}\"",
host.display(),
guest.display()
)
})?;
}
}

let (container_fs, preopen_dirs) = container.container_fs();

Ok(Box::new(OverlayFileSystem::new(root_fs, [container_fs])))
}

#[cfg(test)]
mod tests {
use tempfile::TempDir;

use super::*;

const PYTHON: &[u8] = include_bytes!("../../../c-api/examples/assets/python-0.1.0.wasmer");

#[test]
fn python_use_case() {
let temp = TempDir::new().unwrap();
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 {
guest: "/home".to_string(),
host: sub_dir,
}];
let container = WapmContainer::from_bytes(PYTHON.into()).unwrap();

let fs = prepare_filesystem(&mapping, &container).unwrap();

assert!(fs.metadata("/home/file.txt".as_ref()).unwrap().is_file());
assert!(fs.metadata("lib".as_ref()).unwrap().is_dir());
assert!(fs
.metadata("lib/python3.6/collections/__init__.py".as_ref())
.unwrap()
.is_file());
assert!(fs
.metadata("lib/python3.6/encodings/__init__.py".as_ref())
.unwrap()
.is_file());
}
}
Loading

0 comments on commit c6de93c

Please sign in to comment.