Multiple changes required to implement the wasmer terminal on the browser

- Split functionality out of WasiEnv so that it can support multi-threading
- Added methods to the VFS File Trait that supporting polling
- Implemented basic time functionality for WASI
- Incorported a yield callback for when WASI processes idle
- Improved the error handling on WASI IO calls
- Reduce the verbose logging on some critical WASI calls (write/read)
- Implemented the missing poll functionality for WASI processes
- Moved the syspoll functionality behind a feature flag to default to WASI method
- Refactored the thread sleeping functionality for WASI processes
- Fixed the files system benchmark which was not compiling
- Modified the file system trait so that it is SYNC and thus can handle multiple threads
- Removed the large mutex around filesystem state and implemented granular locks instead
  (this is needed to fix a deadlock scenario on the terminal)
- Split the inodes object apart from the state to fix the deadlock scenario.
- Few minor fixes to some warnings when not using certain features
- Sleeping will now call a callback that can be used by the runtime operator when
  a WASI thread goes to sleep (for instance to do other work)
- Fixed a bug where paths that exist on the real file system are leaking into VFS
- Timing functions now properly return a time precision on WASI
- Some improved macros for error handling within syscalls (wasi_try_ok!)
- Refactored the remove_directory WASI function which was not working properly
- Refactored the unlink WASI function which was not working properly
- Refactored the poll WASI function which was not working properly
- Updates some of the tests to make them compile again
- Rewrote the OutputCapturer so that it does leak into the internals
This commit is contained in:
Johnathan Sharratt
2021-10-28 12:09:00 +01:00
committed by ptitSeb
parent 9af113ca86
commit 7c532813e7
30 changed files with 2160 additions and 1305 deletions

52
Cargo.lock generated
View File

@@ -307,6 +307,21 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"js-sys",
"libc",
"num-integer",
"num-traits",
"time 0.1.43",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "clang-sys"
version = "1.3.3"
@@ -665,6 +680,17 @@ dependencies = [
"syn",
]
[[package]]
name = "derivative"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "derive_arbitrary"
version = "1.1.0"
@@ -1387,6 +1413,16 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.15"
@@ -2358,6 +2394,16 @@ dependencies = [
"once_cell",
]
[[package]]
name = "time"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "time"
version = "0.2.27"
@@ -2978,7 +3024,7 @@ dependencies = [
"lazy_static",
"libc",
"log",
"time",
"time 0.2.27",
"wasmer",
]
@@ -3176,6 +3222,8 @@ version = "2.3.0"
dependencies = [
"bincode",
"cfg-if 1.0.0",
"chrono",
"derivative",
"generational-arena",
"getrandom",
"libc",
@@ -3211,7 +3259,7 @@ version = "2.3.0"
dependencies = [
"byteorder",
"serde",
"time",
"time 0.2.27",
"wasmer-derive",
"wasmer-types",
]

View File

@@ -40,7 +40,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Creating `WasiEnv`...");
// First, we create the `WasiEnv`
let mut wasi_env = WasiState::new("hello")
let wasi_env = WasiState::new("hello")
// .args(&["world"])
// .env("KEY", "Value")
.finalize()?;
@@ -48,7 +48,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Instantiating module with WASI imports...");
// Then, we get the import object related to our WASI
// and attach it to the Wasm instance.
let import_object = wasi_env.import_object(&module)?;
let mut wasi_thread = wasi_env.new_thread();
let import_object = wasi_thread.import_object(&module)?;
let instance = Instance::new(&module, &import_object)?;
println!("Call WASI `_start` function...");

View File

@@ -38,7 +38,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// First, we create the `WasiEnv` with the stdio pipes
let input = Pipe::new();
let output = Pipe::new();
let mut wasi_env = WasiState::new("hello")
let wasi_env = WasiState::new("hello")
.stdin(Box::new(input))
.stdout(Box::new(output))
.finalize()?;
@@ -46,7 +46,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Instantiating module with WASI imports...");
// Then, we get the import object related to our WASI
// and attach it to the Wasm instance.
let import_object = wasi_env.import_object(&module)?;
let mut wasi_thread = wasi_env.new_thread();
let import_object = wasi_thread.import_object(&module)?;
let instance = Instance::new(&module, &import_object)?;
let msg = "racecar go zoom";

View File

@@ -237,10 +237,8 @@ impl Memory {
Ok(Pages(new_pages))
}
/// Used by tests
#[doc(hidden)]
pub fn uint8view(&self) -> js_sys::Uint8Array {
self.view.clone()
pub(crate) fn uint8view(&self) -> js_sys::Uint8Array {
js_sys::Uint8Array::new(&self.vm_memory.memory.buffer())
}
pub(crate) fn from_vm_export(store: &Store, vm_memory: VMMemory) -> Self {
@@ -276,16 +274,17 @@ impl Memory {
/// This method is guaranteed to be safe (from the host side) in the face of
/// concurrent writes.
pub fn read(&self, offset: u64, buf: &mut [u8]) -> Result<(), MemoryAccessError> {
let view = self.uint8view();
let offset: u32 = offset.try_into().map_err(|_| MemoryAccessError::Overflow)?;
let len: u32 = buf
.len()
.try_into()
.map_err(|_| MemoryAccessError::Overflow)?;
let end = offset.checked_add(len).ok_or(MemoryAccessError::Overflow)?;
if end > self.view.length() {
if end > view.length() {
Err(MemoryAccessError::HeapOutOfBounds)?;
}
self.view.subarray(offset, end).copy_to(buf);
view.subarray(offset, end).copy_to(buf);
Ok(())
}
@@ -304,13 +303,14 @@ impl Memory {
offset: u64,
buf: &'a mut [MaybeUninit<u8>],
) -> Result<&'a mut [u8], MemoryAccessError> {
let view = self.uint8view();
let offset: u32 = offset.try_into().map_err(|_| MemoryAccessError::Overflow)?;
let len: u32 = buf
.len()
.try_into()
.map_err(|_| MemoryAccessError::Overflow)?;
let end = offset.checked_add(len).ok_or(MemoryAccessError::Overflow)?;
if end > self.view.length() {
if end > view.length() {
Err(MemoryAccessError::HeapOutOfBounds)?;
}
@@ -321,7 +321,7 @@ impl Memory {
}
let buf = unsafe { slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, buf.len()) };
self.view.subarray(offset, end).copy_to(buf);
view.subarray(offset, end).copy_to(buf);
Ok(buf)
}
@@ -333,16 +333,18 @@ impl Memory {
/// This method is guaranteed to be safe (from the host side) in the face of
/// concurrent reads/writes.
pub fn write(&self, offset: u64, data: &[u8]) -> Result<(), MemoryAccessError> {
let view = self.uint8view();
let offset: u32 = offset.try_into().map_err(|_| MemoryAccessError::Overflow)?;
let len: u32 = data
.len()
.try_into()
.map_err(|_| MemoryAccessError::Overflow)?;
let view = self.uint8view();
let end = offset.checked_add(len).ok_or(MemoryAccessError::Overflow)?;
if end > self.view.length() {
if end > view.length() {
Err(MemoryAccessError::HeapOutOfBounds)?;
}
self.view.subarray(offset, end).copy_from(data);
view.subarray(offset, end).copy_from(data);
Ok(())
}
}

View File

@@ -28,6 +28,7 @@ pub struct TypedFunction<Args = (), Rets = ()> {
}
unsafe impl<Args, Rets> Send for TypedFunction<Args, Rets> {}
unsafe impl<Args, Rets> Sync for TypedFunction<Args, Rets> {}
impl<Args, Rets> TypedFunction<Args, Rets>
where

View File

@@ -6,7 +6,7 @@ use super::super::{
wasi::wasi_env_t,
};
use wasmer_api::{Exportable, Extern};
use wasmer_wasi::{generate_import_object_from_env, get_wasi_version};
use wasmer_wasi::{generate_import_object_from_thread, get_wasi_version};
/// Unstable non-standard type wrapping `wasm_extern_t` with the
/// addition of two `wasm_name_t` respectively for the module name and
@@ -170,7 +170,8 @@ fn wasi_get_unordered_imports_inner(
let version = c_try!(get_wasi_version(&module.inner, false)
.ok_or("could not detect a WASI version on the given module"));
let import_object = generate_import_object_from_env(store, wasi_env.inner.clone(), version);
let thread = wasi_env.inner.new_thread();
let import_object = generate_import_object_from_thread(store, thread, version);
imports.set_buffer(
import_object

View File

@@ -19,7 +19,7 @@ use std::os::raw::c_char;
use std::slice;
use wasmer_api::{Exportable, Extern};
use wasmer_wasi::{
generate_import_object_from_env, get_wasi_version, WasiEnv, WasiFile, WasiState,
generate_import_object_from_thread, get_wasi_version, WasiEnv, WasiFile, WasiState,
WasiStateBuilder, WasiVersion,
};
@@ -229,7 +229,8 @@ pub unsafe extern "C" fn wasi_env_read_stderr(
) -> isize {
let inner_buffer = slice::from_raw_parts_mut(buffer as *mut _, buffer_len as usize);
let mut state = env.inner.state();
let stderr = if let Ok(stderr) = state.fs.stderr_mut() {
let inodes = state.inodes.write().unwrap();
let stderr = if let Ok(stderr) = inodes.stderr_mut(&state.fs.fd_map) {
if let Some(stderr) = stderr.as_mut() {
stderr
} else {
@@ -345,7 +346,8 @@ fn wasi_get_imports_inner(
let version = c_try!(get_wasi_version(&module.inner, false)
.ok_or("could not detect a WASI version on the given module"));
let import_object = generate_import_object_from_env(store, wasi_env.inner.clone(), version);
let mut thread = wasi_env.inner.new_thread();
let import_object = generate_import_object_from_thread(store, thread, version);
imports.set_buffer(c_try!(module
.inner

View File

@@ -1,3 +1,4 @@
#![allow(unused_imports)]
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
@@ -54,7 +55,7 @@ pub fn store_cache_native(c: &mut Criterion) {
let tmp_dir = TempDir::new().unwrap();
let mut fs_cache = FileSystemCache::new(tmp_dir.path()).unwrap();
let compiler = Singlepass::default();
let store = Store::new(&Native::new(compiler).engine());
let store = Store::new(&Universal::new(compiler).engine());
let module = Module::new(
&store,
std::fs::read("../../lib/c-api/examples/assets/qjs.wasm").unwrap(),
@@ -73,7 +74,7 @@ pub fn load_cache_native(c: &mut Criterion) {
let tmp_dir = TempDir::new().unwrap();
let mut fs_cache = FileSystemCache::new(tmp_dir.path()).unwrap();
let compiler = Singlepass::default();
let store = Store::new(&Native::new(compiler).engine());
let store = Store::new(&Universal::new(compiler).engine());
let module = Module::new(
&store,
std::fs::read("../../lib/c-api/examples/assets/qjs.wasm").unwrap(),

View File

@@ -107,13 +107,15 @@ pub struct CompilerOptions {
llvm: bool,
/// Enable compiler internal verification.
#[allow(unused)]
#[structopt(long)]
#[allow(dead_code)]
enable_verifier: bool,
/// LLVM debug directory, where IR and object files will be written to.
#[allow(unused)]
#[cfg(feature = "llvm")]
#[structopt(long, parse(from_os_str))]
#[cfg_attr(feature = "llvm", structopt(long, parse(from_os_str)))]
llvm_debug_dir: Option<PathBuf>,
#[structopt(flatten)]

View File

@@ -41,7 +41,7 @@ pub struct Wasi {
/// Enable experimental IO devices
#[cfg(feature = "experimental-io-devices")]
#[structopt(long = "enable-experimental-io-devices")]
#[cfg_attr(feature = "experimental-io-devices", structopt(long = "enable-experimental-io-devices"))]
enable_experimental_io_devices: bool,
/// Allow WASI modules to import multiple versions of WASI without a warning.
@@ -94,6 +94,7 @@ impl Wasi {
}
let mut wasi_env = wasi_state_builder.finalize()?;
let mut wasi_thread = wasi_env.new_thread();
let import_object = wasi_env.import_object_for_all_wasi_versions(module)?;
let instance = Instance::new(module, &import_object)?;
Ok(instance)

View File

@@ -177,7 +177,7 @@ impl TryInto<Metadata> for fs::Metadata {
pub struct FileOpener;
impl crate::FileOpener for FileOpener {
fn open(&mut self, path: &Path, conf: &OpenOptionsConfig) -> Result<Box<dyn VirtualFile>> {
fn open(&mut self, path: &Path, conf: &OpenOptionsConfig) -> Result<Box<dyn VirtualFile + Sync>> {
// TODO: handle create implying write, etc.
let read = conf.read();
let write = conf.write();
@@ -193,7 +193,7 @@ impl crate::FileOpener for FileOpener {
.map_err(Into::into)
.map(|file| {
Box::new(File::new(file, path.to_owned(), read, write, append))
as Box<dyn VirtualFile>
as Box<dyn VirtualFile + Sync>
})
}
}

View File

@@ -51,7 +51,7 @@ impl dyn FileSystem + 'static {
}
pub trait FileOpener {
fn open(&mut self, path: &Path, conf: &OpenOptionsConfig) -> Result<Box<dyn VirtualFile>>;
fn open(&mut self, path: &Path, conf: &OpenOptionsConfig) -> Result<Box<dyn VirtualFile + Sync>>;
}
#[derive(Debug, Clone)]
@@ -111,6 +111,10 @@ impl OpenOptions {
},
}
}
pub fn options(&mut self, options: OpenOptionsConfig) -> &mut Self {
self.conf = options;
self
}
pub fn read(&mut self, read: bool) -> &mut Self {
self.conf.read = read;
@@ -142,7 +146,7 @@ impl OpenOptions {
self
}
pub fn open<P: AsRef<Path>>(&mut self, path: P) -> Result<Box<dyn VirtualFile>> {
pub fn open<P: AsRef<Path>>(&mut self, path: P) -> Result<Box<dyn VirtualFile + Sync>> {
self.opener.open(path.as_ref(), &self.conf)
}
}
@@ -177,7 +181,28 @@ pub trait VirtualFile: fmt::Debug + Send + Write + Read + Seek + 'static + Upcas
}
/// Returns the number of bytes available. This function must not block
fn bytes_available(&self) -> Result<usize>;
fn bytes_available(&self) -> Result<usize> {
return Ok(self.bytes_available_read()?.unwrap_or(0usize)
+ self.bytes_available_write()?.unwrap_or(0usize));
}
/// Returns the number of bytes available. This function must not block
/// Defaults to `None` which means the number of bytes is unknown
fn bytes_available_read(&self) -> Result<Option<usize>> {
Ok(None)
}
/// Returns the number of bytes available. This function must not block
/// Defaults to `None` which means the number of bytes is unknown
fn bytes_available_write(&self) -> Result<Option<usize>> {
Ok(None)
}
/// Indicates if the file is opened or closed. This function must not block
/// Defaults to a status of being constantly open
fn is_open(&self) -> bool {
true
}
/// Used for polling. Default returns `None` because this method cannot be implemented for most types
/// Returns the underlying host fd

View File

@@ -10,7 +10,7 @@ pub struct FileOpener {
}
impl crate::FileOpener for FileOpener {
fn open(&mut self, path: &Path, conf: &OpenOptionsConfig) -> Result<Box<dyn VirtualFile>> {
fn open(&mut self, path: &Path, conf: &OpenOptionsConfig) -> Result<Box<dyn VirtualFile + Sync>> {
let read = conf.read();
let mut write = conf.write();
let append = conf.append();

View File

@@ -3,7 +3,7 @@ use std::collections::{BTreeSet, VecDeque};
use std::convert::TryInto;
use std::io::{Read, Seek, SeekFrom, Write};
use tracing::debug;
use wasmer_wasi::types::*;
use wasmer_wasi::{types::*, WasiInodes};
use wasmer_wasi::{Fd, VirtualFile, WasiFs, WasiFsError, ALL_RIGHTS, VIRTUAL_ROOT_FD};
use minifb::{Key, KeyRepeat, MouseButton, Scale, Window, WindowOptions};
@@ -426,7 +426,7 @@ impl VirtualFile for FrameBuffer {
}
}
pub fn initialize(fs: &mut WasiFs) -> Result<(), String> {
pub fn initialize(inodes: &mut WasiInodes, fs: &mut WasiFs) -> Result<(), String> {
let frame_buffer_file = Box::new(FrameBuffer {
fb_type: FrameBufferFileType::Buffer,
cursor: 0,
@@ -446,6 +446,7 @@ pub fn initialize(fs: &mut WasiFs) -> Result<(), String> {
let base_dir_fd = unsafe {
fs.open_dir_all(
inodes,
VIRTUAL_ROOT_FD,
"_wasmer/dev/fb0".to_string(),
ALL_RIGHTS,
@@ -457,6 +458,7 @@ pub fn initialize(fs: &mut WasiFs) -> Result<(), String> {
let _fd = fs
.open_file_at(
inodes,
base_dir_fd,
input_file,
Fd::READ,
@@ -471,6 +473,7 @@ pub fn initialize(fs: &mut WasiFs) -> Result<(), String> {
let _fd = fs
.open_file_at(
inodes,
base_dir_fd,
frame_buffer_file,
Fd::READ | Fd::WRITE,
@@ -485,6 +488,7 @@ pub fn initialize(fs: &mut WasiFs) -> Result<(), String> {
let _fd = fs
.open_file_at(
inodes,
base_dir_fd,
resolution_file,
Fd::READ | Fd::WRITE,
@@ -499,6 +503,7 @@ pub fn initialize(fs: &mut WasiFs) -> Result<(), String> {
let _fd = fs
.open_file_at(
inodes,
base_dir_fd,
draw_file,
Fd::READ | Fd::WRITE,

View File

@@ -25,6 +25,8 @@ wasmer-vfs = { path = "../vfs", version = "=2.3.0", default-features = false }
typetag = { version = "0.1", optional = true }
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
bincode = { version = "1.3", optional = true }
chrono = { version = "^0.4", features = [ "wasmbind" ], optional = true }
derivative = { version = "^2" }
[target.'cfg(unix)'.dependencies]
libc = { version = "^0.2", default-features = false }
@@ -43,9 +45,10 @@ tracing-wasm = "0.2"
default = ["sys-default"]
sys = ["wasmer/sys"]
sys-default = ["wasmer/sys-default", "sys", "logging", "host-fs"]
sys-default = ["wasmer/sys-default", "sys", "logging", "host-fs", "sys-poll"]
sys-poll = []
js = ["wasmer/js", "mem-fs", "wasmer-vfs/no-time", "getrandom/js"]
js = ["wasmer/js", "mem-fs", "wasmer-vfs/no-time", "getrandom/js", "chrono"]
js-default = ["js", "wasmer/js-default"]
test-js = ["js", "wasmer/js-default", "wasmer/wat"]

View File

@@ -66,7 +66,8 @@ let wasi_env = WasiState::new("command-name")
.finalize()?;
// Generate an `ImportObject`.
let import_object = wasi_env.import_object(&module)?;
let mut wasi_thread = wasi_env.new_thread();
let import_object = wasi_thread.import_object(&module)?;
// Let's instantiate the module with the imports.
let instance = Instance::new(&module, &import_object)?;

View File

@@ -42,7 +42,7 @@ mod utils;
use crate::syscalls::*;
pub use crate::state::{
Fd, Pipe, Stderr, Stdin, Stdout, WasiFs, WasiState, WasiStateBuilder, WasiStateCreationError,
Fd, Pipe, Stderr, Stdin, Stdout, WasiFs, WasiInodes, WasiState, WasiStateBuilder, WasiStateCreationError,
ALL_RIGHTS, VIRTUAL_ROOT_FD,
};
pub use crate::syscalls::types;
@@ -52,13 +52,17 @@ pub use wasmer_vfs::FsError as WasiFsError;
#[deprecated(since = "2.1.0", note = "Please use `wasmer_vfs::VirtualFile`")]
pub use wasmer_vfs::VirtualFile as WasiFile;
pub use wasmer_vfs::{FsError, VirtualFile};
use wasmer_wasi_types::__WASI_CLOCK_MONOTONIC;
use derivative::*;
use std::ops::Deref;
use thiserror::Error;
use wasmer::{
imports, Function, Imports, LazyInit, Memory, MemoryAccessError, Module, Store, WasmerEnv,
};
use std::sync::{Arc, Mutex, MutexGuard};
use std::time::Duration;
use std::sync::{atomic::AtomicU32, atomic::Ordering, Arc, RwLockReadGuard, RwLockWriteGuard};
/// This is returned in `RuntimeError`.
/// Use `downcast` or `downcast_ref` to retrieve the `ExitCode`.
@@ -70,32 +74,112 @@ pub enum WasiError {
UnknownWasiVersion,
}
/// The environment provided to the WASI imports.
/// WASI processes can have multiple threads attached to the same environment
#[derive(Debug, Clone, WasmerEnv)]
pub struct WasiEnv {
/// Shared state of the WASI system. Manages all the data that the
/// executing WASI program can see.
///
/// Be careful when using this in host functions that call into Wasm:
/// if the lock is held and the Wasm calls into a host function that tries
/// to lock this mutex, the program will deadlock.
pub state: Arc<Mutex<WasiState>>,
pub struct WasiThread {
/// ID of this thread
id: u32,
/// Provides access to the WASI environment
env: WasiEnv,
#[wasmer(export)]
memory: LazyInit<Memory>,
}
impl WasiEnv {
pub fn new(state: WasiState) -> Self {
Self {
state: Arc::new(Mutex::new(state)),
memory: LazyInit::new(),
/// The WASI thread dereferences into the WASI environment
impl Deref for WasiThread {
type Target = WasiEnv;
fn deref(&self) -> &WasiEnv {
&self.env
}
}
impl WasiThread {
/// Returns the unique ID of this thread
pub fn thread_id(&self) -> u32 {
self.id
}
/// Yields execution
pub fn yield_callback(&self) -> Result<(), WasiError> {
if let Some(callback) = self.on_yield.as_ref() {
callback(self)?;
}
Ok(())
}
// Yields execution
pub fn yield_now(&self) -> Result<(), WasiError> {
self.yield_callback()?;
Ok(())
}
// Sleeps for a period of time
pub fn sleep(&self, duration: Duration) -> Result<(), WasiError> {
let duration = duration.as_nanos();
let start = platform_clock_time_get(__WASI_CLOCK_MONOTONIC, 1_000_000).unwrap() as u128;
self.yield_now()?;
loop {
let now = platform_clock_time_get(__WASI_CLOCK_MONOTONIC, 1_000_000).unwrap() as u128;
let delta = match now.checked_sub(start) {
Some(a) => a,
None => { break; }
};
if delta >= duration {
break;
}
let remaining = match duration.checked_sub(delta) {
Some(a) => Duration::from_nanos(a as u64),
None => { break; }
};
std::thread::sleep(remaining.min(Duration::from_millis(10)));
self.yield_now()?;
}
Ok(())
}
/// Get a reference to the memory
pub fn memory(&self) -> &Memory {
self.memory_ref()
.expect("Memory should be set on `WasiEnv` first")
}
// Copy the lazy reference so that when its initialized during the
// export phase that all the other references get a copy of it
pub fn memory_clone(&self) -> LazyInit<Memory> {
self.memory.clone()
}
pub(crate) fn get_memory_and_wasi_state(&self, _mem_index: u32) -> (&Memory, &WasiState) {
let memory = self.memory();
let state = self.state.deref();
(memory, state)
}
pub(crate) fn get_memory_and_wasi_state_and_inodes(
&self,
_mem_index: u32,
) -> (&Memory, &WasiState, RwLockReadGuard<WasiInodes>) {
let memory = self.memory();
let state = self.state.deref();
let inodes = state.inodes.read().unwrap();
(memory, state, inodes)
}
pub(crate) fn get_memory_and_wasi_state_and_inodes_mut(
&self,
_mem_index: u32,
) -> (&Memory, &WasiState, RwLockWriteGuard<WasiInodes>) {
let memory = self.memory();
let state = self.state.deref();
let inodes = state.inodes.write().unwrap();
(memory, state, inodes)
}
/// Get an `Imports` for a specific version of WASI detected in the module.
pub fn import_object(&mut self, module: &Module) -> Result<Imports, WasiError> {
let wasi_version = get_wasi_version(module, false).ok_or(WasiError::UnknownWasiVersion)?;
Ok(generate_import_object_from_env(
Ok(generate_import_object_from_thread(
module.store(),
self.clone(),
wasi_version,
@@ -114,157 +198,197 @@ impl WasiEnv {
let mut resolver = Imports::new();
for version in wasi_versions.iter() {
let new_import_object =
generate_import_object_from_env(module.store(), self.clone(), *version);
generate_import_object_from_thread(module.store(), self.clone(), *version);
for ((n, m), e) in new_import_object.into_iter() {
resolver.define(&n, &m, e);
}
}
Ok(resolver)
}
}
/// The environment provided to the WASI imports.
#[derive(Derivative, Clone)]
#[derivative(Debug)]
pub struct WasiEnv {
/// Represents a reference to the memory
memory: LazyInit<Memory>,
/// Shared state of the WASI system. Manages all the data that the
/// executing WASI program can see.
///
/// Holding a read lock across WASM calls now is allowed
pub state: Arc<WasiState>,
/// Optional callback thats invoked whenever a syscall is made
/// which is used to make callbacks to the process without breaking
/// the single threaded WASM modules
#[derivative(Debug = "ignore")]
pub(crate) on_yield: Option<Arc<dyn Fn(&WasiThread) -> Result<(), WasiError> + Send + Sync + 'static>>,
/// The thread ID seed is used to generate unique thread identifiers
/// for each WasiThread. These are needed for multithreading code that needs
/// this information in the syscalls
pub(crate) thread_id_seed: Arc<AtomicU32>,
}
impl WasiEnv {
pub fn new(state: WasiState) -> Self {
Self {
state: Arc::new(state),
memory: LazyInit::new(),
on_yield: None,
thread_id_seed: Arc::new(AtomicU32::new(1u32)),
}
}
/// Creates a new thread only this wasi environment
pub fn new_thread(&self) -> WasiThread {
WasiThread {
id: self.thread_id_seed.fetch_add(1, Ordering::Relaxed),
env: self.clone(),
memory: self.memory_clone(),
}
}
/// Get the WASI state
///
/// Be careful when using this in host functions that call into Wasm:
/// if the lock is held and the Wasm calls into a host function that tries
/// to lock this mutex, the program will deadlock.
pub fn state(&self) -> MutexGuard<WasiState> {
self.state.lock().unwrap()
pub fn state(&self) -> &WasiState {
self.state.deref()
}
/// Get a reference to the memory
pub fn memory(&self) -> &Memory {
self.memory_ref()
self.memory
.get_ref()
.expect("Memory should be set on `WasiEnv` first")
}
pub(crate) fn get_memory_and_wasi_state(
&self,
_mem_index: u32,
) -> (&Memory, MutexGuard<WasiState>) {
let memory = self.memory();
let state = self.state.lock().unwrap();
(memory, state)
/// Copy the lazy reference so that when it's initialized during the
/// export phase, all the other references get a copy of it
pub fn memory_clone(&self) -> LazyInit<Memory> {
self.memory.clone()
}
}
/// Create an [`Imports`] with an existing [`WasiEnv`]. `WasiEnv`
/// needs a [`WasiState`], that can be constructed from a
/// [`WasiStateBuilder`](state::WasiStateBuilder).
pub fn generate_import_object_from_env(
pub fn generate_import_object_from_thread(
store: &Store,
wasi_env: WasiEnv,
thread: WasiThread,
version: WasiVersion,
) -> Imports {
match version {
WasiVersion::Snapshot0 => generate_import_object_snapshot0(store, wasi_env),
WasiVersion::Snapshot0 => generate_import_object_snapshot0(store, thread),
WasiVersion::Snapshot1 | WasiVersion::Latest => {
generate_import_object_snapshot1(store, wasi_env)
generate_import_object_snapshot1(store, thread)
}
}
}
/// Combines a state generating function with the import list for legacy WASI
fn generate_import_object_snapshot0(store: &Store, env: WasiEnv) -> Imports {
fn generate_import_object_snapshot0(store: &Store, thread: WasiThread) -> Imports {
imports! {
"wasi_unstable" => {
"args_get" => Function::new_native_with_env(store, env.clone(), args_get),
"args_sizes_get" => Function::new_native_with_env(store, env.clone(), args_sizes_get),
"clock_res_get" => Function::new_native_with_env(store, env.clone(), clock_res_get),
"clock_time_get" => Function::new_native_with_env(store, env.clone(), clock_time_get),
"environ_get" => Function::new_native_with_env(store, env.clone(), environ_get),
"environ_sizes_get" => Function::new_native_with_env(store, env.clone(), environ_sizes_get),
"fd_advise" => Function::new_native_with_env(store, env.clone(), fd_advise),
"fd_allocate" => Function::new_native_with_env(store, env.clone(), fd_allocate),
"fd_close" => Function::new_native_with_env(store, env.clone(), fd_close),
"fd_datasync" => Function::new_native_with_env(store, env.clone(), fd_datasync),
"fd_fdstat_get" => Function::new_native_with_env(store, env.clone(), fd_fdstat_get),
"fd_fdstat_set_flags" => Function::new_native_with_env(store, env.clone(), fd_fdstat_set_flags),
"fd_fdstat_set_rights" => Function::new_native_with_env(store, env.clone(), fd_fdstat_set_rights),
"fd_filestat_get" => Function::new_native_with_env(store, env.clone(), legacy::snapshot0::fd_filestat_get),
"fd_filestat_set_size" => Function::new_native_with_env(store, env.clone(), fd_filestat_set_size),
"fd_filestat_set_times" => Function::new_native_with_env(store, env.clone(), fd_filestat_set_times),
"fd_pread" => Function::new_native_with_env(store, env.clone(), fd_pread),
"fd_prestat_get" => Function::new_native_with_env(store, env.clone(), fd_prestat_get),
"fd_prestat_dir_name" => Function::new_native_with_env(store, env.clone(), fd_prestat_dir_name),
"fd_pwrite" => Function::new_native_with_env(store, env.clone(), fd_pwrite),
"fd_read" => Function::new_native_with_env(store, env.clone(), fd_read),
"fd_readdir" => Function::new_native_with_env(store, env.clone(), fd_readdir),
"fd_renumber" => Function::new_native_with_env(store, env.clone(), fd_renumber),
"fd_seek" => Function::new_native_with_env(store, env.clone(), legacy::snapshot0::fd_seek),
"fd_sync" => Function::new_native_with_env(store, env.clone(), fd_sync),
"fd_tell" => Function::new_native_with_env(store, env.clone(), fd_tell),
"fd_write" => Function::new_native_with_env(store, env.clone(), fd_write),
"path_create_directory" => Function::new_native_with_env(store, env.clone(), path_create_directory),
"path_filestat_get" => Function::new_native_with_env(store, env.clone(), legacy::snapshot0::path_filestat_get),
"path_filestat_set_times" => Function::new_native_with_env(store, env.clone(), path_filestat_set_times),
"path_link" => Function::new_native_with_env(store, env.clone(), path_link),
"path_open" => Function::new_native_with_env(store, env.clone(), path_open),
"path_readlink" => Function::new_native_with_env(store, env.clone(), path_readlink),
"path_remove_directory" => Function::new_native_with_env(store, env.clone(), path_remove_directory),
"path_rename" => Function::new_native_with_env(store, env.clone(), path_rename),
"path_symlink" => Function::new_native_with_env(store, env.clone(), path_symlink),
"path_unlink_file" => Function::new_native_with_env(store, env.clone(), path_unlink_file),
"poll_oneoff" => Function::new_native_with_env(store, env.clone(), legacy::snapshot0::poll_oneoff),
"proc_exit" => Function::new_native_with_env(store, env.clone(), proc_exit),
"proc_raise" => Function::new_native_with_env(store, env.clone(), proc_raise),
"random_get" => Function::new_native_with_env(store, env.clone(), random_get),
"sched_yield" => Function::new_native_with_env(store, env.clone(), sched_yield),
"sock_recv" => Function::new_native_with_env(store, env.clone(), sock_recv),
"sock_send" => Function::new_native_with_env(store, env.clone(), sock_send),
"sock_shutdown" => Function::new_native_with_env(store, env, sock_shutdown),
"args_get" => Function::new_native_with_env(store, thread.clone(), args_get),
"args_sizes_get" => Function::new_native_with_env(store, thread.clone(), args_sizes_get),
"clock_res_get" => Function::new_native_with_env(store, thread.clone(), clock_res_get),
"clock_time_get" => Function::new_native_with_env(store, thread.clone(), clock_time_get),
"environ_get" => Function::new_native_with_env(store, thread.clone(), environ_get),
"environ_sizes_get" => Function::new_native_with_env(store, thread.clone(), environ_sizes_get),
"fd_advise" => Function::new_native_with_env(store, thread.clone(), fd_advise),
"fd_allocate" => Function::new_native_with_env(store, thread.clone(), fd_allocate),
"fd_close" => Function::new_native_with_env(store, thread.clone(), fd_close),
"fd_datasync" => Function::new_native_with_env(store, thread.clone(), fd_datasync),
"fd_fdstat_get" => Function::new_native_with_env(store, thread.clone(), fd_fdstat_get),
"fd_fdstat_set_flags" => Function::new_native_with_env(store, thread.clone(), fd_fdstat_set_flags),
"fd_fdstat_set_rights" => Function::new_native_with_env(store, thread.clone(), fd_fdstat_set_rights),
"fd_filestat_get" => Function::new_native_with_env(store, thread.clone(), legacy::snapshot0::fd_filestat_get),
"fd_filestat_set_size" => Function::new_native_with_env(store, thread.clone(), fd_filestat_set_size),
"fd_filestat_set_times" => Function::new_native_with_env(store, thread.clone(), fd_filestat_set_times),
"fd_pread" => Function::new_native_with_env(store, thread.clone(), fd_pread),
"fd_prestat_get" => Function::new_native_with_env(store, thread.clone(), fd_prestat_get),
"fd_prestat_dir_name" => Function::new_native_with_env(store, thread.clone(), fd_prestat_dir_name),
"fd_pwrite" => Function::new_native_with_env(store, thread.clone(), fd_pwrite),
"fd_read" => Function::new_native_with_env(store, thread.clone(), fd_read),
"fd_readdir" => Function::new_native_with_env(store, thread.clone(), fd_readdir),
"fd_renumber" => Function::new_native_with_env(store, thread.clone(), fd_renumber),
"fd_seek" => Function::new_native_with_env(store, thread.clone(), legacy::snapshot0::fd_seek),
"fd_sync" => Function::new_native_with_env(store, thread.clone(), fd_sync),
"fd_tell" => Function::new_native_with_env(store, thread.clone(), fd_tell),
"fd_write" => Function::new_native_with_env(store, thread.clone(), fd_write),
"path_create_directory" => Function::new_native_with_env(store, thread.clone(), path_create_directory),
"path_filestat_get" => Function::new_native_with_env(store, thread.clone(), legacy::snapshot0::path_filestat_get),
"path_filestat_set_times" => Function::new_native_with_env(store, thread.clone(), path_filestat_set_times),
"path_link" => Function::new_native_with_env(store, thread.clone(), path_link),
"path_open" => Function::new_native_with_env(store, thread.clone(), path_open),
"path_readlink" => Function::new_native_with_env(store, thread.clone(), path_readlink),
"path_remove_directory" => Function::new_native_with_env(store, thread.clone(), path_remove_directory),
"path_rename" => Function::new_native_with_env(store, thread.clone(), path_rename),
"path_symlink" => Function::new_native_with_env(store, thread.clone(), path_symlink),
"path_unlink_file" => Function::new_native_with_env(store, thread.clone(), path_unlink_file),
"poll_oneoff" => Function::new_native_with_env(store, thread.clone(), legacy::snapshot0::poll_oneoff),
"proc_exit" => Function::new_native_with_env(store, thread.clone(), proc_exit),
"proc_raise" => Function::new_native_with_env(store, thread.clone(), proc_raise),
"random_get" => Function::new_native_with_env(store, thread.clone(), random_get),
"sched_yield" => Function::new_native_with_env(store, thread.clone(), sched_yield),
"sock_recv" => Function::new_native_with_env(store, thread.clone(), sock_recv),
"sock_send" => Function::new_native_with_env(store, thread.clone(), sock_send),
"sock_shutdown" => Function::new_native_with_env(store, thread, sock_shutdown),
},
}
}
/// Combines a state generating function with the import list for snapshot 1
fn generate_import_object_snapshot1(store: &Store, env: WasiEnv) -> Imports {
fn generate_import_object_snapshot1(store: &Store, thread: WasiThread) -> Imports {
imports! {
"wasi_snapshot_preview1" => {
"args_get" => Function::new_native_with_env(store, env.clone(), args_get),
"args_sizes_get" => Function::new_native_with_env(store, env.clone(), args_sizes_get),
"clock_res_get" => Function::new_native_with_env(store, env.clone(), clock_res_get),
"clock_time_get" => Function::new_native_with_env(store, env.clone(), clock_time_get),
"environ_get" => Function::new_native_with_env(store, env.clone(), environ_get),
"environ_sizes_get" => Function::new_native_with_env(store, env.clone(), environ_sizes_get),
"fd_advise" => Function::new_native_with_env(store, env.clone(), fd_advise),
"fd_allocate" => Function::new_native_with_env(store, env.clone(), fd_allocate),
"fd_close" => Function::new_native_with_env(store, env.clone(), fd_close),
"fd_datasync" => Function::new_native_with_env(store, env.clone(), fd_datasync),
"fd_fdstat_get" => Function::new_native_with_env(store, env.clone(), fd_fdstat_get),
"fd_fdstat_set_flags" => Function::new_native_with_env(store, env.clone(), fd_fdstat_set_flags),
"fd_fdstat_set_rights" => Function::new_native_with_env(store, env.clone(), fd_fdstat_set_rights),
"fd_filestat_get" => Function::new_native_with_env(store, env.clone(), fd_filestat_get),
"fd_filestat_set_size" => Function::new_native_with_env(store, env.clone(), fd_filestat_set_size),
"fd_filestat_set_times" => Function::new_native_with_env(store, env.clone(), fd_filestat_set_times),
"fd_pread" => Function::new_native_with_env(store, env.clone(), fd_pread),
"fd_prestat_get" => Function::new_native_with_env(store, env.clone(), fd_prestat_get),
"fd_prestat_dir_name" => Function::new_native_with_env(store, env.clone(), fd_prestat_dir_name),
"fd_pwrite" => Function::new_native_with_env(store, env.clone(), fd_pwrite),
"fd_read" => Function::new_native_with_env(store, env.clone(), fd_read),
"fd_readdir" => Function::new_native_with_env(store, env.clone(), fd_readdir),
"fd_renumber" => Function::new_native_with_env(store, env.clone(), fd_renumber),
"fd_seek" => Function::new_native_with_env(store, env.clone(), fd_seek),
"fd_sync" => Function::new_native_with_env(store, env.clone(), fd_sync),
"fd_tell" => Function::new_native_with_env(store, env.clone(), fd_tell),
"fd_write" => Function::new_native_with_env(store, env.clone(), fd_write),
"path_create_directory" => Function::new_native_with_env(store, env.clone(), path_create_directory),
"path_filestat_get" => Function::new_native_with_env(store, env.clone(), path_filestat_get),
"path_filestat_set_times" => Function::new_native_with_env(store, env.clone(), path_filestat_set_times),
"path_link" => Function::new_native_with_env(store, env.clone(), path_link),
"path_open" => Function::new_native_with_env(store, env.clone(), path_open),
"path_readlink" => Function::new_native_with_env(store, env.clone(), path_readlink),
"path_remove_directory" => Function::new_native_with_env(store, env.clone(), path_remove_directory),
"path_rename" => Function::new_native_with_env(store, env.clone(), path_rename),
"path_symlink" => Function::new_native_with_env(store, env.clone(), path_symlink),
"path_unlink_file" => Function::new_native_with_env(store, env.clone(), path_unlink_file),
"poll_oneoff" => Function::new_native_with_env(store, env.clone(), poll_oneoff),
"proc_exit" => Function::new_native_with_env(store, env.clone(), proc_exit),
"proc_raise" => Function::new_native_with_env(store, env.clone(), proc_raise),
"random_get" => Function::new_native_with_env(store, env.clone(), random_get),
"sched_yield" => Function::new_native_with_env(store, env.clone(), sched_yield),
"sock_recv" => Function::new_native_with_env(store, env.clone(), sock_recv),
"sock_send" => Function::new_native_with_env(store, env.clone(), sock_send),
"sock_shutdown" => Function::new_native_with_env(store, env, sock_shutdown),
"args_get" => Function::new_native_with_env(store, thread.clone(), args_get),
"args_sizes_get" => Function::new_native_with_env(store, thread.clone(), args_sizes_get),
"clock_res_get" => Function::new_native_with_env(store, thread.clone(), clock_res_get),
"clock_time_get" => Function::new_native_with_env(store, thread.clone(), clock_time_get),
"environ_get" => Function::new_native_with_env(store, thread.clone(), environ_get),
"environ_sizes_get" => Function::new_native_with_env(store, thread.clone(), environ_sizes_get),
"fd_advise" => Function::new_native_with_env(store, thread.clone(), fd_advise),
"fd_allocate" => Function::new_native_with_env(store, thread.clone(), fd_allocate),
"fd_close" => Function::new_native_with_env(store, thread.clone(), fd_close),
"fd_datasync" => Function::new_native_with_env(store, thread.clone(), fd_datasync),
"fd_fdstat_get" => Function::new_native_with_env(store, thread.clone(), fd_fdstat_get),
"fd_fdstat_set_flags" => Function::new_native_with_env(store, thread.clone(), fd_fdstat_set_flags),
"fd_fdstat_set_rights" => Function::new_native_with_env(store, thread.clone(), fd_fdstat_set_rights),
"fd_filestat_get" => Function::new_native_with_env(store, thread.clone(), fd_filestat_get),
"fd_filestat_set_size" => Function::new_native_with_env(store, thread.clone(), fd_filestat_set_size),
"fd_filestat_set_times" => Function::new_native_with_env(store, thread.clone(), fd_filestat_set_times),
"fd_pread" => Function::new_native_with_env(store, thread.clone(), fd_pread),
"fd_prestat_get" => Function::new_native_with_env(store, thread.clone(), fd_prestat_get),
"fd_prestat_dir_name" => Function::new_native_with_env(store, thread.clone(), fd_prestat_dir_name),
"fd_pwrite" => Function::new_native_with_env(store, thread.clone(), fd_pwrite),
"fd_read" => Function::new_native_with_env(store, thread.clone(), fd_read),
"fd_readdir" => Function::new_native_with_env(store, thread.clone(), fd_readdir),
"fd_renumber" => Function::new_native_with_env(store, thread.clone(), fd_renumber),
"fd_seek" => Function::new_native_with_env(store, thread.clone(), fd_seek),
"fd_sync" => Function::new_native_with_env(store, thread.clone(), fd_sync),
"fd_tell" => Function::new_native_with_env(store, thread.clone(), fd_tell),
"fd_write" => Function::new_native_with_env(store, thread.clone(), fd_write),
"path_create_directory" => Function::new_native_with_env(store, thread.clone(), path_create_directory),
"path_filestat_get" => Function::new_native_with_env(store, thread.clone(), path_filestat_get),
"path_filestat_set_times" => Function::new_native_with_env(store, thread.clone(), path_filestat_set_times),
"path_link" => Function::new_native_with_env(store, thread.clone(), path_link),
"path_open" => Function::new_native_with_env(store, thread.clone(), path_open),
"path_readlink" => Function::new_native_with_env(store, thread.clone(), path_readlink),
"path_remove_directory" => Function::new_native_with_env(store, thread.clone(), path_remove_directory),
"path_rename" => Function::new_native_with_env(store, thread.clone(), path_rename),
"path_symlink" => Function::new_native_with_env(store, thread.clone(), path_symlink),
"path_unlink_file" => Function::new_native_with_env(store, thread.clone(), path_unlink_file),
"poll_oneoff" => Function::new_native_with_env(store, thread.clone(), poll_oneoff),
"proc_exit" => Function::new_native_with_env(store, thread.clone(), proc_exit),
"proc_raise" => Function::new_native_with_env(store, thread.clone(), proc_raise),
"random_get" => Function::new_native_with_env(store, thread.clone(), random_get),
"sched_yield" => Function::new_native_with_env(store, thread.clone(), sched_yield),
"sock_recv" => Function::new_native_with_env(store, thread.clone(), sock_recv),
"sock_send" => Function::new_native_with_env(store, thread.clone(), sock_send),
"sock_shutdown" => Function::new_native_with_env(store, thread, sock_shutdown),
}
}
}

View File

@@ -16,9 +16,40 @@ macro_rules! wasi_try {
}
}
}};
($expr:expr, $e:expr) => {{
let opt: Option<_> = $expr;
wasi_try!(opt.ok_or($e))
}
/// Like the `try!` macro or `?` syntax: returns the value if the computation
/// succeeded or returns the error value. Results are wrapped in an Ok
macro_rules! wasi_try_ok {
($expr:expr) => {{
let res: Result<_, crate::syscalls::types::__wasi_errno_t> = $expr;
match res {
Ok(val) => {
tracing::trace!("wasi::wasi_try_ok::val: {:?}", val);
val
}
Err(err) => {
tracing::trace!("wasi::wasi_try_ok::err: {:?}", err);
return Ok(err);
}
}
}};
($expr:expr, $thread:expr) => {{
let res: Result<_, crate::syscalls::types::__wasi_errno_t> = $expr;
match res {
Ok(val) => {
tracing::trace!("wasi::wasi_try_ok::val: {:?}", val);
val
}
Err(err) => {
if err == __WASI_EINTR {
$thread.yield_callback()?;
}
tracing::trace!("wasi::wasi_try_ok::err: {:?}", err);
return Ok(err);
}
}
}};
}
@@ -29,6 +60,17 @@ macro_rules! wasi_try_mem {
}};
}
/// Like `wasi_try` but converts a `MemoryAccessError` to a __wasi_errno_t`.
macro_rules! wasi_try_mem_ok {
($expr:expr) => {{
wasi_try_ok!($expr.map_err($crate::mem_error_to_wasi))
}};
($expr:expr, $thread:expr) => {{
wasi_try_ok!($expr.map_err($crate::mem_error_to_wasi), $thread)
}};
}
/// Reads a string from Wasm memory.
macro_rules! get_input_str {
($memory:expr, $data:expr, $len:expr) => {{

View File

@@ -2,8 +2,13 @@
use crate::state::{default_fs_backing, WasiFs, WasiState};
use crate::syscalls::types::{__WASI_STDERR_FILENO, __WASI_STDIN_FILENO, __WASI_STDOUT_FILENO};
use crate::WasiEnv;
use crate::{WasiEnv, WasiInodes, WasiThread, WasiError};
use generational_arena::Arena;
use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::RwLock;
use thiserror::Error;
use wasmer_vfs::{FsError, VirtualFile};
@@ -40,11 +45,12 @@ pub struct WasiStateBuilder {
preopens: Vec<PreopenedDir>,
vfs_preopens: Vec<String>,
#[allow(clippy::type_complexity)]
setup_fs_fn: Option<Box<dyn Fn(&mut WasiFs) -> Result<(), String> + Send>>,
stdout_override: Option<Box<dyn VirtualFile>>,
stderr_override: Option<Box<dyn VirtualFile>>,
stdin_override: Option<Box<dyn VirtualFile>>,
setup_fs_fn: Option<Box<dyn Fn(&mut WasiInodes, &mut WasiFs) -> Result<(), String> + Send>>,
stdout_override: Option<Box<dyn VirtualFile + Sync>>,
stderr_override: Option<Box<dyn VirtualFile + Sync>>,
stdin_override: Option<Box<dyn VirtualFile + Sync>>,
fs_override: Option<Box<dyn wasmer_vfs::FileSystem>>,
on_yield: Option<Arc<dyn Fn(&WasiThread) -> Result<(), WasiError> + Send + Sync + 'static>>,
}
impl std::fmt::Debug for WasiStateBuilder {
@@ -55,6 +61,7 @@ impl std::fmt::Debug for WasiStateBuilder {
.field("envs", &self.envs)
.field("preopens", &self.preopens)
.field("setup_fs_fn exists", &self.setup_fs_fn.is_some())
.field("on_yield exists", &self.on_yield.is_some())
.field("stdout_override exists", &self.stdout_override.is_some())
.field("stderr_override exists", &self.stderr_override.is_some())
.field("stdin_override exists", &self.stdin_override.is_some())
@@ -277,7 +284,7 @@ impl WasiStateBuilder {
/// Overwrite the default WASI `stdout`, if you want to hold on to the
/// original `stdout` use [`WasiFs::swap_file`] after building.
pub fn stdout(&mut self, new_file: Box<dyn VirtualFile>) -> &mut Self {
pub fn stdout(&mut self, new_file: Box<dyn VirtualFile + Sync>) -> &mut Self {
self.stdout_override = Some(new_file);
self
@@ -285,7 +292,7 @@ impl WasiStateBuilder {
/// Overwrite the default WASI `stderr`, if you want to hold on to the
/// original `stderr` use [`WasiFs::swap_file`] after building.
pub fn stderr(&mut self, new_file: Box<dyn VirtualFile>) -> &mut Self {
pub fn stderr(&mut self, new_file: Box<dyn VirtualFile + Sync>) -> &mut Self {
self.stderr_override = Some(new_file);
self
@@ -293,7 +300,7 @@ impl WasiStateBuilder {
/// Overwrite the default WASI `stdin`, if you want to hold on to the
/// original `stdin` use [`WasiFs::swap_file`] after building.
pub fn stdin(&mut self, new_file: Box<dyn VirtualFile>) -> &mut Self {
pub fn stdin(&mut self, new_file: Box<dyn VirtualFile + Sync>) -> &mut Self {
self.stdin_override = Some(new_file);
self
@@ -312,13 +319,27 @@ impl WasiStateBuilder {
// TODO: improve ergonomics on this function
pub fn setup_fs(
&mut self,
setup_fs_fn: Box<dyn Fn(&mut WasiFs) -> Result<(), String> + Send>,
setup_fs_fn: Box<dyn Fn(&mut WasiInodes, &mut WasiFs) -> Result<(), String> + Send>,
) -> &mut Self {
self.setup_fs_fn = Some(setup_fs_fn);
self
}
/// Sets a callback that will be invoked whenever the process yields execution.
///
/// This is useful if the background tasks and/or callbacks are to be
/// executed whenever the WASM process goes idle
pub fn on_yield<F>(&mut self, callback: F) -> &mut Self
where
F: Fn(&WasiThread) -> Result<(), WasiError>,
F: Send + Sync + 'static,
{
self.on_yield = Some(Arc::new(callback));
self
}
/// Consumes the [`WasiStateBuilder`] and produces a [`WasiState`]
///
/// Returns the error from `WasiFs::new` if there's an error
@@ -404,34 +425,50 @@ impl WasiStateBuilder {
let fs_backing = self.fs_override.take().unwrap_or_else(default_fs_backing);
// self.preopens are checked in [`PreopenDirBuilder::build`]
let mut wasi_fs = WasiFs::new_with_preopen(&self.preopens, &self.vfs_preopens, fs_backing)
let inodes = RwLock::new(crate::state::WasiInodes {
arena: Arena::new(),
orphan_fds: HashMap::new(),
});
let wasi_fs = {
let mut inodes = inodes.write().unwrap();
// self.preopens are checked in [`PreopenDirBuilder::build`]
let mut wasi_fs = WasiFs::new_with_preopen(
inodes.deref_mut(),
&self.preopens,
&self.vfs_preopens,
fs_backing,
)
.map_err(WasiStateCreationError::WasiFsCreationError)?;
// set up the file system, overriding base files and calling the setup function
if let Some(stdin_override) = self.stdin_override.take() {
wasi_fs
.swap_file(__WASI_STDIN_FILENO, stdin_override)
.swap_file(inodes.deref(), __WASI_STDIN_FILENO, stdin_override)
.map_err(WasiStateCreationError::FileSystemError)?;
}
if let Some(stdout_override) = self.stdout_override.take() {
wasi_fs
.swap_file(__WASI_STDOUT_FILENO, stdout_override)
.swap_file(inodes.deref(), __WASI_STDOUT_FILENO, stdout_override)
.map_err(WasiStateCreationError::FileSystemError)?;
}
if let Some(stderr_override) = self.stderr_override.take() {
wasi_fs
.swap_file(__WASI_STDERR_FILENO, stderr_override)
.swap_file(inodes.deref(), __WASI_STDERR_FILENO, stderr_override)
.map_err(WasiStateCreationError::FileSystemError)?;
}
if let Some(f) = &self.setup_fs_fn {
f(&mut wasi_fs).map_err(WasiStateCreationError::WasiFsSetupError)?;
f(inodes.deref_mut(), &mut wasi_fs).map_err(WasiStateCreationError::WasiFsSetupError)?;
}
wasi_fs
};
Ok(WasiState {
fs: wasi_fs,
inodes,
args: self.args.clone(),
envs: self
.envs
@@ -461,7 +498,9 @@ impl WasiStateBuilder {
pub fn finalize(&mut self) -> Result<WasiEnv, WasiStateCreationError> {
let state = self.build()?;
Ok(WasiEnv::new(state))
let mut env = WasiEnv::new(state);
env.on_yield = self.on_yield.clone();
Ok(env)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,11 +2,12 @@
use crate::syscalls::types::*;
#[cfg(feature = "enable-serde")]
use serde::{Deserialize, Serialize};
#[cfg(unix)]
#[cfg(all(unix, feature = "sys-poll"))]
use std::convert::TryInto;
use std::{
collections::VecDeque,
io::{self, Read, Seek, Write},
time::Duration,
};
#[cfg(feature = "host-fs")]
@@ -137,7 +138,7 @@ pub fn iterate_poll_events(pes: PollEventSet) -> PollEventIter {
PollEventIter { pes, i: 0 }
}
#[cfg(unix)]
#[cfg(all(unix, feature = "sys-poll"))]
fn poll_event_set_to_platform_poll_events(mut pes: PollEventSet) -> i16 {
let mut out = 0;
for i in 0..16 {
@@ -154,7 +155,7 @@ fn poll_event_set_to_platform_poll_events(mut pes: PollEventSet) -> i16 {
out
}
#[cfg(unix)]
#[cfg(all(unix, feature = "sys-poll"))]
fn platform_poll_events_to_pollevent_set(mut num: i16) -> PollEventSet {
let mut peb = PollEventBuilder::new();
for i in 0..16 {
@@ -171,6 +172,7 @@ fn platform_poll_events_to_pollevent_set(mut num: i16) -> PollEventSet {
peb.build()
}
#[allow(dead_code)]
impl PollEventBuilder {
pub fn new() -> PollEventBuilder {
PollEventBuilder { inner: 0 }
@@ -186,11 +188,12 @@ impl PollEventBuilder {
}
}
#[cfg(unix)]
#[cfg(all(unix, feature = "sys-poll"))]
pub(crate) fn poll(
selfs: &[&dyn VirtualFile],
selfs: &[&(dyn VirtualFile + Sync)],
events: &[PollEventSet],
seen_events: &mut [PollEventSet],
timeout: Duration,
) -> Result<u32, FsError> {
if !(selfs.len() == events.len() && events.len() == seen_events.len()) {
return Err(FsError::InvalidInput);
@@ -205,7 +208,7 @@ pub(crate) fn poll(
revents: 0,
})
.collect::<Vec<_>>();
let result = unsafe { libc::poll(fds.as_mut_ptr(), selfs.len() as _, 1) };
let result = unsafe { libc::poll(fds.as_mut_ptr(), selfs.len() as _, timeout.as_millis() as i32) };
if result < 0 {
// TODO: check errno and return value
@@ -219,13 +222,67 @@ pub(crate) fn poll(
Ok(result.try_into().unwrap())
}
#[cfg(not(unix))]
#[cfg(any(not(unix), not(feature = "sys-poll")))]
pub(crate) fn poll(
_selfs: &[&dyn VirtualFile],
_events: &[PollEventSet],
_seen_events: &mut [PollEventSet],
) -> Result<(), FsError> {
unimplemented!("VirtualFile::poll is not implemented for non-Unix-like targets yet");
files: &[&(dyn VirtualFile + Sync)],
events: &[PollEventSet],
seen_events: &mut [PollEventSet],
timeout: Duration,
) -> Result<u32, FsError> {
if !(files.len() == events.len() && events.len() == seen_events.len()) {
tracing::debug!("the slice length of 'files', 'events' and 'seen_events' must be the same (files={}, events={}, seen_events={})", files.len(), events.len(), seen_events.len());
return Err(FsError::InvalidInput);
}
let mut ret = 0;
for n in 0..files.len() {
let mut builder = PollEventBuilder::new();
let file = files[n];
let can_read = file
.bytes_available_read()?
.map(|_| true)
.unwrap_or(false);
let can_write = file
.bytes_available_write()?
.map(|s| s > 0)
.unwrap_or(false);
let is_closed = file.is_open() == false;
tracing::debug!("poll_evt can_read={} can_write={} is_closed={}", can_read, can_write, is_closed);
for event in iterate_poll_events(events[n]) {
match event {
PollEvent::PollIn if can_read => {
builder = builder.add(PollEvent::PollIn);
}
PollEvent::PollOut if can_write => {
builder = builder.add(PollEvent::PollOut);
}
PollEvent::PollHangUp if is_closed => {
builder = builder.add(PollEvent::PollHangUp);
}
PollEvent::PollInvalid if is_closed => {
builder = builder.add(PollEvent::PollInvalid);
}
PollEvent::PollError if is_closed => {
builder = builder.add(PollEvent::PollError);
}
_ => {}
}
}
let revents = builder.build();
if revents != 0 {
ret += 1;
}
seen_events[n] = revents;
}
if ret == 0 && timeout > Duration::ZERO {
return Err(FsError::WouldBlock);
}
Ok(ret)
}
pub trait WasiPath {}

View File

@@ -1,6 +1,6 @@
use crate::syscalls;
use crate::syscalls::types::{self, snapshot0};
use crate::{mem_error_to_wasi, WasiEnv};
use crate::{mem_error_to_wasi, WasiEnv, WasiError, WasiThread};
use wasmer::WasmPtr;
/// Wrapper around `syscalls::fd_filestat_get` with extra logic to handle the size
@@ -10,11 +10,11 @@ use wasmer::WasmPtr;
/// Wasm memory. If the memory clobbered by the current syscall is also used by
/// that syscall, then it may break.
pub fn fd_filestat_get(
env: &WasiEnv,
thread: &WasiThread,
fd: types::__wasi_fd_t,
buf: WasmPtr<snapshot0::__wasi_filestat_t>,
) -> types::__wasi_errno_t {
let memory = env.memory();
let memory = thread.memory();
// transmute the WasmPtr<T1> into a WasmPtr<T2> where T2 > T1, this will read extra memory.
// The edge case of this cenv.mausing an OOB is not handled, if the new field is OOB, then the entire
@@ -26,10 +26,10 @@ pub fn fd_filestat_get(
// Set up complete, make the call with the pointer that will write to the
// struct and some unrelated memory after the struct.
let result = syscalls::fd_filestat_get(env, fd, new_buf);
let result = syscalls::fd_filestat_get(thread, fd, new_buf);
// reborrow memory
let memory = env.memory();
let memory = thread.memory();
// get the values written to memory
let new_filestat = wasi_try_mem!(new_buf.deref(memory).read());
@@ -59,7 +59,7 @@ pub fn fd_filestat_get(
/// Wrapper around `syscalls::path_filestat_get` with extra logic to handle the size
/// difference of `wasi_filestat_t`
pub fn path_filestat_get(
env: &WasiEnv,
thread: &WasiThread,
fd: types::__wasi_fd_t,
flags: types::__wasi_lookupflags_t,
path: WasmPtr<u8>,
@@ -67,14 +67,14 @@ pub fn path_filestat_get(
buf: WasmPtr<snapshot0::__wasi_filestat_t>,
) -> types::__wasi_errno_t {
// see `fd_filestat_get` in this file for an explanation of this strange behavior
let memory = env.memory();
let memory = thread.memory();
let new_buf: WasmPtr<types::__wasi_filestat_t> = buf.cast();
let new_filestat_setup: types::__wasi_filestat_t = wasi_try_mem!(new_buf.read(memory));
let result = syscalls::path_filestat_get(env, fd, flags, path, path_len, new_buf);
let result = syscalls::path_filestat_get(thread, fd, flags, path, path_len, new_buf);
let memory = env.memory();
let memory = thread.memory();
let new_filestat = wasi_try_mem!(new_buf.deref(memory).read());
let old_stat = snapshot0::__wasi_filestat_t {
st_dev: new_filestat.st_dev,
@@ -96,12 +96,12 @@ pub fn path_filestat_get(
/// Wrapper around `syscalls::fd_seek` with extra logic to remap the values
/// of `__wasi_whence_t`
pub fn fd_seek(
env: &WasiEnv,
thread: &WasiThread,
fd: types::__wasi_fd_t,
offset: types::__wasi_filedelta_t,
whence: snapshot0::__wasi_whence_t,
newoffset: WasmPtr<types::__wasi_filesize_t>,
) -> types::__wasi_errno_t {
) -> Result<types::__wasi_errno_t, WasiError> {
let new_whence = match whence {
snapshot0::__WASI_WHENCE_CUR => types::__WASI_WHENCE_CUR,
snapshot0::__WASI_WHENCE_END => types::__WASI_WHENCE_END,
@@ -109,34 +109,34 @@ pub fn fd_seek(
// if it's invalid, let the new fd_seek handle it
_ => whence,
};
syscalls::fd_seek(env, fd, offset, new_whence, newoffset)
syscalls::fd_seek(thread, fd, offset, new_whence, newoffset)
}
/// Wrapper around `syscalls::poll_oneoff` with extra logic to add the removed
/// userdata field back
pub fn poll_oneoff(
env: &WasiEnv,
thread: &WasiThread,
in_: WasmPtr<snapshot0::__wasi_subscription_t>,
out_: WasmPtr<types::__wasi_event_t>,
nsubscriptions: u32,
nevents: WasmPtr<u32>,
) -> types::__wasi_errno_t {
) -> Result<types::__wasi_errno_t, WasiError> {
// in this case the new type is smaller than the old type, so it all fits into memory,
// we just need to readjust and copy it
// we start by adjusting `in_` into a format that the new code can understand
let memory = env.memory();
let in_origs = wasi_try_mem!(in_.slice(memory, nsubscriptions));
let in_origs = wasi_try_mem!(in_origs.read_to_vec());
let memory = thread.memory();
let in_origs = wasi_try_mem_ok!(in_.slice(memory, nsubscriptions));
let in_origs = wasi_try_mem_ok!(in_origs.read_to_vec());
// get a pointer to the smaller new type
let in_new_type_ptr: WasmPtr<types::__wasi_subscription_t> = in_.cast();
for (in_sub_new, orig) in wasi_try_mem!(in_new_type_ptr.slice(memory, nsubscriptions))
for (in_sub_new, orig) in wasi_try_mem_ok!(in_new_type_ptr.slice(memory, nsubscriptions))
.iter()
.zip(in_origs.iter())
{
wasi_try_mem!(in_sub_new.write(types::__wasi_subscription_t {
wasi_try_mem_ok!(in_sub_new.write(types::__wasi_subscription_t {
userdata: orig.userdata,
type_: orig.type_,
u: if orig.type_ == types::__WASI_EVENTTYPE_CLOCK {
@@ -157,16 +157,16 @@ pub fn poll_oneoff(
}
// make the call
let result = syscalls::poll_oneoff(env, in_new_type_ptr, out_, nsubscriptions, nevents);
let result = syscalls::poll_oneoff(thread, in_new_type_ptr, out_, nsubscriptions, nevents);
// replace the old values of in, in case the calling code reuses the memory
let memory = env.memory();
let memory = thread.memory();
for (in_sub, orig) in wasi_try_mem!(in_.slice(memory, nsubscriptions))
for (in_sub, orig) in wasi_try_mem_ok!(in_.slice(memory, nsubscriptions))
.iter()
.zip(in_origs.into_iter())
{
wasi_try_mem!(in_sub.write(orig));
wasi_try_mem_ok!(in_sub.write(orig));
}
result

File diff suppressed because it is too large Load Diff

View File

@@ -9,13 +9,13 @@ use wasmer::WasmRef;
pub fn platform_clock_res_get(
clock_id: __wasi_clockid_t,
resolution: WasmRef<__wasi_timestamp_t>,
) -> __wasi_errno_t {
) -> Result<i64, __wasi_errno_t> {
let unix_clock_id = match clock_id {
__WASI_CLOCK_MONOTONIC => CLOCK_MONOTONIC,
__WASI_CLOCK_PROCESS_CPUTIME_ID => CLOCK_PROCESS_CPUTIME_ID,
__WASI_CLOCK_REALTIME => CLOCK_REALTIME,
__WASI_CLOCK_THREAD_CPUTIME_ID => CLOCK_THREAD_CPUTIME_ID,
_ => return __WASI_EINVAL,
_ => return Err(__WASI_EINVAL),
};
let (output, timespec_out) = unsafe {
@@ -27,23 +27,19 @@ pub fn platform_clock_res_get(
};
let t_out = (timespec_out.tv_sec * 1_000_000_000).wrapping_add(timespec_out.tv_nsec);
wasi_try_mem!(resolution.write(t_out as __wasi_timestamp_t));
// TODO: map output of clock_getres to __wasi_errno_t
__WASI_ESUCCESS
Ok(t_out)
}
pub fn platform_clock_time_get(
clock_id: __wasi_clockid_t,
precision: __wasi_timestamp_t,
time: WasmRef<__wasi_timestamp_t>,
) -> __wasi_errno_t {
) -> Result<i64, __wasi_errno_t> {
let unix_clock_id = match clock_id {
__WASI_CLOCK_MONOTONIC => CLOCK_MONOTONIC,
__WASI_CLOCK_PROCESS_CPUTIME_ID => CLOCK_PROCESS_CPUTIME_ID,
__WASI_CLOCK_REALTIME => CLOCK_REALTIME,
__WASI_CLOCK_THREAD_CPUTIME_ID => CLOCK_THREAD_CPUTIME_ID,
_ => return __WASI_EINVAL,
_ => return Err(__WASI_EINVAL),
};
let (output, timespec_out) = unsafe {
@@ -58,8 +54,5 @@ pub fn platform_clock_time_get(
};
let t_out = (timespec_out.tv_sec * 1_000_000_000).wrapping_add(timespec_out.tv_nsec);
wasi_try_mem!(time.write(t_out as __wasi_timestamp_t));
// TODO: map output of clock_gettime to __wasi_errno_t
__WASI_ESUCCESS
Ok(t_out)
}

View File

@@ -1,25 +1,26 @@
use crate::syscalls::types::*;
use chrono::prelude::*;
use std::mem;
use wasmer::WasmRef;
pub fn platform_clock_res_get(
clock_id: __wasi_clockid_t,
resolution: WasmRef<__wasi_timestamp_t>,
) -> __wasi_errno_t {
let t_out = 1 * 1_000_000_000;
wasi_try_mem!(resolution.write(t_out as __wasi_timestamp_t));
// TODO: map output of clock_getres to __wasi_errno_t
__WASI_ESUCCESS
) -> Result<i64, __wasi_errno_t> {
let t_out = match clock_id {
__WASI_CLOCK_MONOTONIC => 10_000_000,
__WASI_CLOCK_REALTIME => 1,
__WASI_CLOCK_PROCESS_CPUTIME_ID => 1,
__WASI_CLOCK_THREAD_CPUTIME_ID => 1,
_ => return Err(__WASI_EINVAL),
};
Ok(t_out)
}
pub fn platform_clock_time_get(
clock_id: __wasi_clockid_t,
precision: __wasi_timestamp_t,
time: WasmRef<__wasi_timestamp_t>,
) -> __wasi_errno_t {
let t_out = 1 * 1_000_000_000;
wasi_try_mem!(time.write(t_out as __wasi_timestamp_t));
__WASI_ESUCCESS
) -> Result<i64, __wasi_errno_t> {
let new_time: DateTime<Local> = Local::now();
Ok(new_time.timestamp_nanos() as i64)
}

View File

@@ -5,7 +5,7 @@ use wasmer::WasmRef;
pub fn platform_clock_res_get(
clock_id: __wasi_clockid_t,
resolution: WasmRef<__wasi_timestamp_t>,
) -> __wasi_errno_t {
) -> Result<i64, __wasi_errno_t> {
let resolution_val = match clock_id {
// resolution of monotonic clock at 10ms, from:
// https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-gettickcount64
@@ -13,34 +13,32 @@ pub fn platform_clock_res_get(
// TODO: verify or compute this
__WASI_CLOCK_REALTIME => 1,
__WASI_CLOCK_PROCESS_CPUTIME_ID => {
return __WASI_EINVAL;
return Err(__WASI_EINVAL);
}
__WASI_CLOCK_THREAD_CPUTIME_ID => {
return __WASI_EINVAL;
return Err(__WASI_EINVAL);
}
_ => return __WASI_EINVAL,
_ => return Err(__WASI_EINVAL),
};
wasi_try_mem!(resolution.write(resolution_val));
__WASI_ESUCCESS
Ok(resolution_val)
}
pub fn platform_clock_time_get(
clock_id: __wasi_clockid_t,
precision: __wasi_timestamp_t,
time: WasmRef<__wasi_timestamp_t>,
) -> __wasi_errno_t {
) -> Result<i64, __wasi_errno_t> {
let nanos = match clock_id {
__WASI_CLOCK_MONOTONIC => {
let tick_ms = unsafe { winapi::um::sysinfoapi::GetTickCount64() };
tick_ms * 1_000_000
}
__WASI_CLOCK_REALTIME => {
let duration = wasi_try!(std::time::SystemTime::now()
let duration = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|e| {
debug!("Error in wasi::platform_clock_time_get: {:?}", e);
__WASI_EIO
}));
})?;
duration.as_nanos() as u64
}
__WASI_CLOCK_PROCESS_CPUTIME_ID => {
@@ -49,8 +47,7 @@ pub fn platform_clock_time_get(
__WASI_CLOCK_THREAD_CPUTIME_ID => {
unimplemented!("wasi::platform_clock_time_get(__WASI_CLOCK_THREAD_CPUTIME_ID, ..)")
}
_ => return __WASI_EINVAL,
_ => return Err(__WASI_EINVAL),
};
wasi_try_mem!(time.write(nanos));
__WASI_ESUCCESS
Ok(nanos as i64)
}

View File

@@ -1,5 +1,6 @@
use std::collections::BTreeSet;
use wasmer::Module;
use super::types::*;
#[allow(dead_code)]
/// Check if a provided module is compiled for some version of WASI.
@@ -8,6 +9,32 @@ pub fn is_wasi_module(module: &Module) -> bool {
get_wasi_version(module, false).is_some()
}
pub fn map_io_err(err: std::io::Error) -> __wasi_errno_t {
use std::io::ErrorKind;
match err.kind() {
ErrorKind::NotFound => __WASI_ENOENT,
ErrorKind::PermissionDenied => __WASI_EPERM,
ErrorKind::ConnectionRefused => __WASI_ECONNREFUSED,
ErrorKind::ConnectionReset => __WASI_ECONNRESET,
ErrorKind::ConnectionAborted => __WASI_ECONNABORTED,
ErrorKind::NotConnected => __WASI_ENOTCONN,
ErrorKind::AddrInUse => __WASI_EADDRINUSE,
ErrorKind::AddrNotAvailable => __WASI_EADDRNOTAVAIL,
ErrorKind::BrokenPipe => __WASI_EPIPE,
ErrorKind::AlreadyExists => __WASI_EEXIST,
ErrorKind::WouldBlock => __WASI_EAGAIN,
ErrorKind::InvalidInput => __WASI_EIO,
ErrorKind::InvalidData => __WASI_EIO,
ErrorKind::TimedOut => __WASI_ETIMEDOUT,
ErrorKind::WriteZero => __WASI_EIO,
ErrorKind::Interrupted => __WASI_EINTR,
ErrorKind::Other => __WASI_EIO,
ErrorKind::UnexpectedEof => __WASI_EIO,
ErrorKind::Unsupported => __WASI_ENOTSUP,
_ => __WASI_EIO,
}
}
/// The version of WASI. This is determined by the imports namespace
/// string.
#[derive(Debug, Clone, Copy, Eq)]

View File

@@ -39,14 +39,15 @@ fn test_stdout() {
// Create the `WasiEnv`.
// let stdout = Stdout::default();
let mut wasi_env = WasiState::new("command-name")
let wasi_env = WasiState::new("command-name")
.args(&["Gordon"])
// .stdout(Box::new(stdout))
.finalize()
.unwrap();
// Generate an `ImportObject`.
let import_object = wasi_env.import_object(&module).unwrap();
let mut wasi_thread = wasi_env.new_thread();
let import_object = wasi_thread.import_object(&module).unwrap();
// Let's instantiate the module with the imports.
let instance = Instance::new(&module, &import_object).unwrap();
@@ -81,13 +82,14 @@ fn test_env() {
.env("TEST", "VALUE")
.env("TEST2", "VALUE2");
// panic!("envs: {:?}", wasi_state_builder.envs);
let mut wasi_env = wasi_state_builder
let wasi_env = wasi_state_builder
// .stdout(Box::new(stdout))
.finalize()
.unwrap();
// Generate an `ImportObject`.
let import_object = wasi_env.import_object(&module).unwrap();
let mut wasi_thread = wasi_env.new_thread();
let import_object = wasi_thread.import_object(&module).unwrap();
// Let's instantiate the module with the imports.
let instance = Instance::new(&module, &import_object).unwrap();

View File

@@ -99,7 +99,7 @@ impl Config {
}
}
pub fn compiler_config(&self, canonicalize_nans: bool) -> Box<dyn CompilerConfig> {
pub fn compiler_config(&self, #[allow(unused_variables)] canonicalize_nans: bool) -> Box<dyn CompilerConfig> {
match &self.compiler {
#[cfg(feature = "cranelift")]
Compiler::Cranelift => {
@@ -135,6 +135,7 @@ impl Config {
}
}
#[allow(dead_code)]
fn add_middlewares(&self, config: &mut dyn CompilerConfig) {
for middleware in self.middlewares.iter() {
config.push_middleware(middleware.clone());

View File

@@ -1,12 +1,13 @@
use anyhow::Context;
use std::fs::{read_dir, File, OpenOptions, ReadDir};
use std::io::{self, Read, Seek, Write};
use std::sync::{Arc, Mutex, mpsc};
use std::path::{Path, PathBuf};
use wasmer::{Imports, Instance, Module, Store};
use wasmer_vfs::{host_fs, mem_fs, FileSystem};
use wasmer_wasi::types::{__wasi_filesize_t, __wasi_timestamp_t};
use wasmer_wasi::{
generate_import_object_from_env, get_wasi_version, FsError, Pipe, VirtualFile, WasiEnv,
generate_import_object_from_thread, get_wasi_version, FsError, Pipe, VirtualFile, WasiEnv,
WasiState, WasiVersion,
};
use wast::parser::{self, Parse, ParseBuffer, Parser};
@@ -39,12 +40,12 @@ pub struct WasiTest<'a> {
// TODO: add `test_fs` here to sandbox better
const BASE_TEST_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../wasi-wast/wasi/");
fn get_stdout_output(wasi_state: &WasiState) -> anyhow::Result<String> {
let stdout_boxed = wasi_state.fs.stdout()?.as_ref().unwrap();
let stdout = (&**stdout_boxed)
.downcast_ref::<OutputCapturerer>()
.unwrap();
let stdout_str = std::str::from_utf8(&stdout.output)?;
fn get_stdio_output(rx: &mpsc::Receiver<Vec<u8>>) -> anyhow::Result<String> {
let mut stdio = Vec::new();
while let Ok(mut buf) = rx.try_recv() {
stdio.append(&mut buf);
}
let stdout_str = std::str::from_utf8(&stdio[..])?;
#[cfg(target_os = "windows")]
// normalize line endings
return Ok(stdout_str.replace("\r\n", "\n"));
@@ -53,21 +54,6 @@ fn get_stdout_output(wasi_state: &WasiState) -> anyhow::Result<String> {
return Ok(stdout_str.to_string());
}
fn get_stderr_output(wasi_state: &WasiState) -> anyhow::Result<String> {
let stderr_boxed = wasi_state.fs.stderr()?.as_ref().unwrap();
let stderr = (&**stderr_boxed)
.downcast_ref::<OutputCapturerer>()
.unwrap();
let stderr_str = std::str::from_utf8(&stderr.output)?;
#[cfg(target_os = "windows")]
// normalize line endings
return Ok(stderr_str.replace("\r\n", "\n"));
#[cfg(not(target_os = "windows"))]
return Ok(stderr_str.to_string());
}
#[allow(dead_code)]
impl<'a> WasiTest<'a> {
/// Turn a WASI WAST string into a list of tokens.
@@ -95,16 +81,18 @@ impl<'a> WasiTest<'a> {
wasm_module.read_to_end(&mut out)?;
out
};
let module = Module::new(store, &wasm_bytes)?;
let (env, _tempdirs) = self.create_wasi_env(filesystem_kind)?;
let module = Module::new(&store, &wasm_bytes)?;
let (env, _tempdirs, stdout_rx, stderr_rx) = self.create_wasi_env(filesystem_kind)?;
let imports = self.get_imports(store, &module, env.clone())?;
let instance = Instance::new(&module, &imports)?;
let start = instance.exports.get_function("_start")?;
if let Some(stdin) = &self.stdin {
let mut state = env.state();
let wasi_stdin = state.fs.stdin_mut()?.as_mut().unwrap();
let state = env.state();
let inodes = state.inodes.write().unwrap();
let mut wasi_stdin = inodes.stdin_mut(&state.fs.fd_map)?;
let wasi_stdin = wasi_stdin.as_mut().unwrap();
// Then we can write to it!
write!(wasi_stdin, "{}", stdin.stream)?;
}
@@ -113,9 +101,8 @@ impl<'a> WasiTest<'a> {
match start.call(&[]) {
Ok(_) => {}
Err(e) => {
let wasi_state = env.state();
let stdout_str = get_stdout_output(&wasi_state)?;
let stderr_str = get_stderr_output(&wasi_state)?;
let stdout_str = get_stdio_output(&stdout_rx)?;
let stderr_str = get_stdio_output(&stderr_rx)?;
Err(e).with_context(|| {
format!(
"failed to run WASI `_start` function: failed with stdout: \"{}\"\nstderr: \"{}\"",
@@ -126,15 +113,13 @@ impl<'a> WasiTest<'a> {
}
}
let wasi_state = env.state();
if let Some(expected_stdout) = &self.assert_stdout {
let stdout_str = get_stdout_output(&wasi_state)?;
let stdout_str = get_stdio_output(&stdout_rx)?;
assert_eq!(stdout_str, expected_stdout.expected);
}
if let Some(expected_stderr) = &self.assert_stderr {
let stderr_str = get_stderr_output(&wasi_state)?;
let stderr_str = get_stdio_output(&stderr_rx)?;
assert_eq!(stderr_str, expected_stderr.expected);
}
@@ -145,7 +130,7 @@ impl<'a> WasiTest<'a> {
fn create_wasi_env(
&self,
filesystem_kind: WasiFileSystemKind,
) -> anyhow::Result<(WasiEnv, Vec<tempfile::TempDir>)> {
) -> anyhow::Result<(WasiEnv, Vec<tempfile::TempDir>, mpsc::Receiver<Vec<u8>>, mpsc::Receiver<Vec<u8>>)> {
let mut builder = WasiState::new(self.wasm_path);
let stdin_pipe = Pipe::new();
@@ -216,15 +201,17 @@ impl<'a> WasiTest<'a> {
}
}
let (stdout, stdout_rx) = OutputCapturerer::new();
let (stderr, stderr_rx) = OutputCapturerer::new();
let out = builder
.args(&self.args)
// adding this causes some tests to fail. TODO: investigate this
//.env("RUST_BACKTRACE", "1")
.stdout(Box::new(OutputCapturerer::new()))
.stderr(Box::new(OutputCapturerer::new()))
.stdout(Box::new(stdout))
.stderr(Box::new(stderr))
.finalize()?;
Ok((out, host_temp_dirs_to_not_drop))
Ok((out, host_temp_dirs_to_not_drop, stdout_rx, stderr_rx))
}
/// Get the correct [`WasiVersion`] from the Wasm [`Module`].
@@ -237,8 +224,9 @@ impl<'a> WasiTest<'a> {
/// Get the correct WASI import object for the given module and set it up with the
/// [`WasiEnv`].
fn get_imports(&self, store: &Store, module: &Module, env: WasiEnv) -> anyhow::Result<Imports> {
let thread = env.new_thread();
let version = self.get_version(module)?;
Ok(generate_import_object_from_env(store, env, version))
Ok(generate_import_object_from_thread(store, thread, version))
}
}
@@ -528,14 +516,15 @@ mod test {
}
}
#[derive(Debug)]
#[derive(Debug, Clone)]
struct OutputCapturerer {
output: Vec<u8>,
output: Arc<Mutex<mpsc::Sender<Vec<u8>>>>,
}
impl OutputCapturerer {
fn new() -> Self {
Self { output: vec![] }
fn new() -> (Self, mpsc::Receiver<Vec<u8>>) {
let (tx, rx) = mpsc::channel();
(Self { output: Arc::new(Mutex::new(tx)) }, rx)
}
}
@@ -575,18 +564,30 @@ impl Seek for OutputCapturerer {
}
impl Write for OutputCapturerer {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.output.extend_from_slice(buf);
self.output.lock().unwrap().send(buf.to_vec())
.map_err(|err| io::Error::new(
io::ErrorKind::BrokenPipe, err.to_string(),
))?;
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
self.output.extend_from_slice(buf);
self.output.lock().unwrap().send(buf.to_vec())
.map_err(|err| io::Error::new(
io::ErrorKind::BrokenPipe, err.to_string(),
))?;
Ok(())
}
fn write_fmt(&mut self, fmt: std::fmt::Arguments) -> io::Result<()> {
self.output.write_fmt(fmt)
let mut buf = Vec::<u8>::new();
buf.write_fmt(fmt)?;
self.output.lock().unwrap().send(buf)
.map_err(|err| io::Error::new(
io::ErrorKind::BrokenPipe, err.to_string(),
))?;
Ok(())
}
}