Skip to content

Commit

Permalink
keep-runtime: Implement resource bundling in Wasm file
Browse files Browse the repository at this point in the history
This implements the proposal to embed application resources in the
Wasm file through a custom section.  For more details of see:
enarx-archive/rfcs#29
  • Loading branch information
ueno committed Jul 26, 2020
1 parent f1f6227 commit 24d8ba0
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 3 deletions.
8 changes: 5 additions & 3 deletions keep-runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ license = "Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
wasmtime = "0.18.0"
wasmtime-wasi = "0.18.0"
wasi-common = "0.18.0"
wasmtime = { version = "0.19.0", default-features = false }
wasmtime-wasi = { version = "0.19.1", default-features = false }
wasi-common = { version = "0.19.1", default-features = false }
wasmparser = "0.60.2"
tar = "0.4"

env_logger = "0.7"
log = "0.4"
Expand Down
1 change: 1 addition & 0 deletions keep-runtime/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#![deny(missing_docs)]
#![deny(clippy::all)]

mod virtfs;
mod workload;

use log::info;
Expand Down
82 changes: 82 additions & 0 deletions keep-runtime/src/virtfs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: Apache-2.0

use std::collections::HashMap;
use std::convert::TryInto;
use std::path::{Component, Path};
use wasi_common::virtfs::{FileContents, VirtualDirEntry};
use wasi_common::wasi::{types, Result};

/// Copied from wasi-common/src/virtfs.rs.
pub struct VecFileContents {
content: Vec<u8>,
}

impl VecFileContents {
pub fn new(content: Vec<u8>) -> Self {
Self { content }
}
}

impl FileContents for VecFileContents {
fn max_size(&self) -> types::Filesize {
std::usize::MAX as types::Filesize
}

fn size(&self) -> types::Filesize {
self.content.len() as types::Filesize
}

fn resize(&mut self, _new_size: types::Filesize) -> Result<()> {
Err(types::Errno::Inval)
}

fn preadv(&self, iovs: &mut [std::io::IoSliceMut], offset: types::Filesize) -> Result<usize> {
let mut read_total = 0usize;
for iov in iovs.iter_mut() {
let read = self.pread(iov, offset + read_total as types::Filesize)?;
read_total = read_total.checked_add(read).expect("FileContents::preadv must not be called when reads could total to more bytes than the return value can hold");
}
Ok(read_total)
}

fn pwritev(&mut self, _iovs: &[std::io::IoSlice], _offset: types::Filesize) -> Result<usize> {
Err(types::Errno::Inval)
}

fn pread(&self, buf: &mut [u8], offset: types::Filesize) -> Result<usize> {
let offset: usize = offset.try_into().map_err(|_| types::Errno::Inval)?;

let data_remaining = self.content.len().saturating_sub(offset);

let read_count = std::cmp::min(buf.len(), data_remaining);

(&mut buf[..read_count]).copy_from_slice(&self.content[offset..][..read_count]);
Ok(read_count)
}

fn pwrite(&mut self, _buf: &[u8], _offset: types::Filesize) -> Result<usize> {
Err(types::Errno::Inval)
}
}

pub fn populate_directory(
mut dir: &mut VirtualDirEntry,
path: impl AsRef<Path>,
) -> Result<&mut VirtualDirEntry> {
for component in path.as_ref().components() {
let name = match component {
Component::Normal(first) => first.to_str().unwrap().to_string(),
_ => return Err(types::Errno::Inval),
};
match dir {
VirtualDirEntry::Directory(ref mut map) => {
if !map.contains_key(&name) {
map.insert(name.clone(), VirtualDirEntry::Directory(HashMap::new()));
}
dir = map.get_mut(&name).unwrap();
}
_ => unreachable!(),
}
}
Ok(dir)
}
79 changes: 79 additions & 0 deletions keep-runtime/src/workload.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
// SPDX-License-Identifier: Apache-2.0

use std::io::Read;
use wasi_common::virtfs::VirtualDirEntry;
use wasmparser::{Chunk, Parser, Payload::*};

const RESOURCES_SECTION: &str = ".enarx.resources";

/// The error codes of workload execution.
#[derive(Debug)]
pub enum Error {
/// invalid workload format
InvalidFormat,
/// import module not found
ImportModuleNotFound(String),
/// import field not found
Expand All @@ -26,6 +34,74 @@ impl From<std::io::Error> for Error {
/// Result type used throughout the library.
pub type Result<T> = std::result::Result<T, Error>;

fn populate_entry<R: Read>(mut dir: &mut VirtualDirEntry, entry: &mut tar::Entry<R>) -> Result<()> {
match entry.header().entry_type() {
tar::EntryType::Regular => {
let path = entry.header().path()?;
let parent = {
if let Some(parent) = path.parent() {
crate::virtfs::populate_directory(&mut dir, parent)
.or(Err(Error::InvalidFormat))?
} else {
dir
}
};

match parent {
VirtualDirEntry::Directory(ref mut map) => {
let name = path.file_name().unwrap().to_str().unwrap().to_string();
let mut content: Vec<u8> = Vec::new();
entry.read_to_end(&mut content)?;
let content = crate::virtfs::VecFileContents::new(content);
map.insert(name, VirtualDirEntry::File(Box::new(content)));
}
_ => unreachable!(),
}
}
tar::EntryType::Directory => {
let path = entry.header().path()?;
let _ =
crate::virtfs::populate_directory(&mut dir, path).or(Err(Error::InvalidFormat))?;
}
_ => {}
}
Ok(())
}

fn populate_virtfs(root: &mut VirtualDirEntry, bytes: &[u8]) -> Result<()> {
let mut offset: usize = 0;
let mut parser = Parser::new(offset as u64);
loop {
let (consumed, payload) = match parser
.parse(&bytes[offset..], true)
.or(Err(Error::RuntimeError))?
{
Chunk::Parsed { consumed, payload } => (consumed, payload),
// this state isn't possible with `eof = true`
Chunk::NeedMoreData(_) => unreachable!(),
};

offset += consumed;

match payload {
End => break,
CustomSection { name, data, .. } if name == RESOURCES_SECTION => {
let mut ar = tar::Archive::new(data);
for entry in ar.entries()? {
let mut entry = entry?;
populate_entry(root, &mut entry)?;
}
}
CodeSectionStart { size, .. } | ModuleCodeSectionStart { size, .. } => {
parser.skip_section();
offset += size as usize;
}
_ => {}
}
}
Ok(())
}

/// Runs a WebAssembly workload.
pub fn run<T: AsRef<[u8]>, U: AsRef<[u8]>, V: std::borrow::Borrow<(U, U)>>(
bytes: impl AsRef<[u8]>,
Expand All @@ -41,6 +117,9 @@ pub fn run<T: AsRef<[u8]>, U: AsRef<[u8]>, V: std::borrow::Borrow<(U, U)>>(
// Instantiate WASI
let mut builder = wasi_common::WasiCtxBuilder::new();
builder.args(args).envs(envs);
let mut root = VirtualDirEntry::empty_directory();
populate_virtfs(&mut root, bytes.as_ref())?;
builder.preopened_virt(root, ".");
let ctx = builder.build().or(Err(Error::RuntimeError))?;
let wasi_snapshot_preview1 = wasmtime_wasi::Wasi::new(&store, ctx);

Expand Down

0 comments on commit 24d8ba0

Please sign in to comment.