mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-16 17:18:57 +00:00
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:
committed by
ptitSeb
parent
9af113ca86
commit
7c532813e7
52
Cargo.lock
generated
52
Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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...");
|
||||
|
||||
@@ -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";
|
||||
|
||||
22
lib/api/src/js/externals/memory.rs
vendored
22
lib/api/src/js/externals/memory.rs
vendored
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
5
lib/cache/benches/bench_filesystem_cache.rs
vendored
5
lib/cache/benches/bench_filesystem_cache.rs
vendored
@@ -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(),
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"]
|
||||
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) => {{
|
||||
|
||||
@@ -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
@@ -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 {}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user