From 24d8ba0540beebf8112af6fa72d3edfced222c2a Mon Sep 17 00:00:00 2001 From: Daiki Ueno Date: Sun, 26 Jul 2020 17:00:18 +0200 Subject: [PATCH] keep-runtime: Implement resource bundling in Wasm file This implements the proposal to embed application resources in the Wasm file through a custom section. For more details of see: https://github.com/enarx/rfcs/pull/29 --- keep-runtime/Cargo.toml | 8 ++-- keep-runtime/src/main.rs | 1 + keep-runtime/src/virtfs.rs | 82 ++++++++++++++++++++++++++++++++++++ keep-runtime/src/workload.rs | 79 ++++++++++++++++++++++++++++++++++ 4 files changed, 167 insertions(+), 3 deletions(-) create mode 100644 keep-runtime/src/virtfs.rs diff --git a/keep-runtime/Cargo.toml b/keep-runtime/Cargo.toml index 184a5fd37..496514a21 100644 --- a/keep-runtime/Cargo.toml +++ b/keep-runtime/Cargo.toml @@ -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" diff --git a/keep-runtime/src/main.rs b/keep-runtime/src/main.rs index 018cf5af7..e2ddf68e7 100644 --- a/keep-runtime/src/main.rs +++ b/keep-runtime/src/main.rs @@ -21,6 +21,7 @@ #![deny(missing_docs)] #![deny(clippy::all)] +mod virtfs; mod workload; use log::info; diff --git a/keep-runtime/src/virtfs.rs b/keep-runtime/src/virtfs.rs new file mode 100644 index 000000000..36537cd4b --- /dev/null +++ b/keep-runtime/src/virtfs.rs @@ -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, +} + +impl VecFileContents { + pub fn new(content: Vec) -> 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 { + 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 { + Err(types::Errno::Inval) + } + + fn pread(&self, buf: &mut [u8], offset: types::Filesize) -> Result { + 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 { + Err(types::Errno::Inval) + } +} + +pub fn populate_directory( + mut dir: &mut VirtualDirEntry, + path: impl AsRef, +) -> 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) +} diff --git a/keep-runtime/src/workload.rs b/keep-runtime/src/workload.rs index 302c24334..fe6e6892f 100644 --- a/keep-runtime/src/workload.rs +++ b/keep-runtime/src/workload.rs @@ -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 @@ -26,6 +34,74 @@ impl From for Error { /// Result type used throughout the library. pub type Result = std::result::Result; +fn populate_entry(mut dir: &mut VirtualDirEntry, entry: &mut tar::Entry) -> 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 = 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, U: AsRef<[u8]>, V: std::borrow::Borrow<(U, U)>>( bytes: impl AsRef<[u8]>, @@ -41,6 +117,9 @@ pub fn run, 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);