diff --git a/lib/api/src/js/externals/memory.rs b/lib/api/src/js/externals/memory.rs index d7c9460d3a1..eb9eb7317d1 100644 --- a/lib/api/src/js/externals/memory.rs +++ b/lib/api/src/js/externals/memory.rs @@ -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], ) -> 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(()) } } diff --git a/lib/cache/benches/bench_filesystem_cache.rs b/lib/cache/benches/bench_filesystem_cache.rs index f9adc74b3d6..9560d429615 100644 --- a/lib/cache/benches/bench_filesystem_cache.rs +++ b/lib/cache/benches/bench_filesystem_cache.rs @@ -54,7 +54,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 +73,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(), diff --git a/lib/cli/src/commands/run/wasi.rs b/lib/cli/src/commands/run/wasi.rs index dca9c9d8943..81f2e670dbf 100644 --- a/lib/cli/src/commands/run/wasi.rs +++ b/lib/cli/src/commands/run/wasi.rs @@ -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. diff --git a/lib/vfs/src/host_fs.rs b/lib/vfs/src/host_fs.rs index 961f6685be0..e058839ed8e 100644 --- a/lib/vfs/src/host_fs.rs +++ b/lib/vfs/src/host_fs.rs @@ -177,7 +177,7 @@ impl TryInto for fs::Metadata { pub struct FileOpener; impl crate::FileOpener for FileOpener { - fn open(&mut self, path: &Path, conf: &OpenOptionsConfig) -> Result> { + fn open(&mut self, path: &Path, conf: &OpenOptionsConfig) -> Result> { // 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 + as Box }) } } diff --git a/lib/vfs/src/lib.rs b/lib/vfs/src/lib.rs index f1274a0f655..eaf01692c16 100644 --- a/lib/vfs/src/lib.rs +++ b/lib/vfs/src/lib.rs @@ -51,7 +51,7 @@ impl dyn FileSystem + 'static { } pub trait FileOpener { - fn open(&mut self, path: &Path, conf: &OpenOptionsConfig) -> Result>; + fn open(&mut self, path: &Path, conf: &OpenOptionsConfig) -> Result>; } #[derive(Debug, Clone)] @@ -142,7 +142,7 @@ impl OpenOptions { self } - pub fn open>(&mut self, path: P) -> Result> { + pub fn open>(&mut self, path: P) -> Result> { self.opener.open(path.as_ref(), &self.conf) } } diff --git a/lib/vfs/src/mem_fs/file_opener.rs b/lib/vfs/src/mem_fs/file_opener.rs index 689feb5aafd..6e80dbd1be6 100644 --- a/lib/vfs/src/mem_fs/file_opener.rs +++ b/lib/vfs/src/mem_fs/file_opener.rs @@ -10,7 +10,7 @@ pub struct FileOpener { } impl crate::FileOpener for FileOpener { - fn open(&mut self, path: &Path, conf: &OpenOptionsConfig) -> Result> { + fn open(&mut self, path: &Path, conf: &OpenOptionsConfig) -> Result> { let read = conf.read(); let mut write = conf.write(); let append = conf.append(); diff --git a/lib/wasi-experimental-io-devices/src/lib.rs b/lib/wasi-experimental-io-devices/src/lib.rs index 7c85358d818..2df64af5977 100644 --- a/lib/wasi-experimental-io-devices/src/lib.rs +++ b/lib/wasi-experimental-io-devices/src/lib.rs @@ -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, diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index 6ffda3c848b..9780ffedff7 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/src/lib.rs @@ -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,15 @@ 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 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`. @@ -84,12 +86,95 @@ pub struct WasiEnv { memory: LazyInit, } -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 { + 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) { + 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) { + 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. @@ -121,14 +206,56 @@ impl WasiEnv { } 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, + /// 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, + /// 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 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, +} + +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 { - self.state.lock().unwrap() + pub fn state(&self) -> &WasiState { + self.state.deref() } /// Get a reference to the memory diff --git a/lib/wasi/src/macros.rs b/lib/wasi/src/macros.rs index e2488668e08..b7ad74c2893 100644 --- a/lib/wasi/src/macros.rs +++ b/lib/wasi/src/macros.rs @@ -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) => {{ diff --git a/lib/wasi/src/state/builder.rs b/lib/wasi/src/state/builder.rs index ec437b7a5b1..a9aa58ecd11 100644 --- a/lib/wasi/src/state/builder.rs +++ b/lib/wasi/src/state/builder.rs @@ -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, vfs_preopens: Vec, #[allow(clippy::type_complexity)] - setup_fs_fn: Option Result<(), String> + Send>>, - stdout_override: Option>, - stderr_override: Option>, - stdin_override: Option>, + setup_fs_fn: Option Result<(), String> + Send>>, + stdout_override: Option>, + stderr_override: Option>, + stdin_override: Option>, fs_override: Option>, + on_yield: Option Result<(), WasiError> + Send + Sync + 'static>>, } impl std::fmt::Debug for WasiStateBuilder { @@ -277,7 +283,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) -> &mut Self { + pub fn stdout(&mut self, new_file: Box) -> &mut Self { self.stdout_override = Some(new_file); self @@ -285,7 +291,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) -> &mut Self { + pub fn stderr(&mut self, new_file: Box) -> &mut Self { self.stderr_override = Some(new_file); self @@ -293,7 +299,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) -> &mut Self { + pub fn stdin(&mut self, new_file: Box) -> &mut Self { self.stdin_override = Some(new_file); self @@ -312,13 +318,27 @@ impl WasiStateBuilder { // TODO: improve ergonomics on this function pub fn setup_fs( &mut self, - setup_fs_fn: Box Result<(), String> + Send>, + setup_fs_fn: Box 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(&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 @@ -407,34 +427,50 @@ impl WasiStateBuilder { .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) - .map_err(WasiStateCreationError::FileSystemError)?; - } + // 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(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) - .map_err(WasiStateCreationError::FileSystemError)?; - } + if let Some(stdout_override) = self.stdout_override.take() { + wasi_fs + .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) - .map_err(WasiStateCreationError::FileSystemError)?; - } + if let Some(stderr_override) = self.stderr_override.take() { + wasi_fs + .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)?; - } + if let Some(f) = &self.setup_fs_fn { + 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 diff --git a/lib/wasi/src/state/mod.rs b/lib/wasi/src/state/mod.rs index a8e1d0b30e1..ce2b1971082 100644 --- a/lib/wasi/src/state/mod.rs +++ b/lib/wasi/src/state/mod.rs @@ -28,11 +28,15 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::{ borrow::Borrow, - cell::Cell, io::Write, + ops::{Deref, DerefMut}, path::{Path, PathBuf}, + sync::{ + atomic::{AtomicU32, AtomicU64, Ordering}, + RwLock, RwLockReadGuard, RwLockWriteGuard, + }, }; -use tracing::debug; +use tracing::{debug, trace}; use wasmer_vfs::{FileSystem, FsError, OpenOptions, VirtualFile}; @@ -62,10 +66,59 @@ pub const MAX_SYMLINKS: u32 = 128; #[derive(Debug)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct InodeVal { - pub stat: __wasi_filestat_t, + pub stat: RwLock<__wasi_filestat_t>, pub is_preopened: bool, pub name: String, - pub kind: Kind, + pub kind: RwLock, +} + +impl InodeVal { + pub fn read(&self) -> RwLockReadGuard { + self.kind.read().unwrap() + } + + pub fn write(&self) -> RwLockWriteGuard { + self.kind.write().unwrap() + } +} + +#[derive(Debug)] +pub struct InodeValFileReadGuard<'a> { + pub(crate) guard: RwLockReadGuard<'a, Kind>, +} + +impl<'a> Deref for InodeValFileReadGuard<'a> { + type Target = Option>; + fn deref(&self) -> &Self::Target { + if let Kind::File { handle, .. } = self.guard.deref() { + return handle; + } + unreachable!() + } +} + +#[derive(Debug)] +pub struct InodeValFileWriteGuard<'a> { + pub(crate) guard: RwLockWriteGuard<'a, Kind>, +} + +impl<'a> Deref for InodeValFileWriteGuard<'a> { + type Target = Option>; + fn deref(&self) -> &Self::Target { + if let Kind::File { handle, .. } = self.guard.deref() { + return handle; + } + unreachable!() + } +} + +impl<'a> DerefMut for InodeValFileWriteGuard<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + if let Kind::File { handle, .. } = self.guard.deref_mut() { + return handle; + } + unreachable!() + } } /// The core of the filesystem abstraction. Includes directories, @@ -75,7 +128,7 @@ pub struct InodeVal { pub enum Kind { File { /// The open file, if it's open - handle: Option>, + handle: Option>, /// The path on the host system where the file is located /// This is deprecated and will be removed soon path: PathBuf, @@ -120,7 +173,7 @@ pub enum Kind { }, } -#[derive(Debug)] +#[derive(Debug, Clone)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct Fd { pub rights: __wasi_rights_t, @@ -154,20 +207,136 @@ impl Fd { pub const CREATE: u16 = 16; } +#[derive(Debug)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub struct WasiInodes { + pub arena: Arena, + pub orphan_fds: HashMap, +} + +impl WasiInodes { + /// gets either a normal inode or an orphaned inode + pub fn get_inodeval( + &self, + inode: generational_arena::Index, + ) -> Result<&InodeVal, __wasi_errno_t> { + if let Some(iv) = self.arena.get(inode) { + Ok(iv) + } else { + self.orphan_fds.get(&inode).ok_or(__WASI_EBADF) + } + } + + /// gets either a normal inode or an orphaned inode + pub fn get_inodeval_mut( + &mut self, + inode: generational_arena::Index, + ) -> Result<&mut InodeVal, __wasi_errno_t> { + if let Some(iv) = self.arena.get_mut(inode) { + Ok(iv) + } else { + self.orphan_fds.get_mut(&inode).ok_or(__WASI_EBADF) + } + } + + /// Get the `VirtualFile` object at stdout + pub fn stdout( + &self, + fd_map: &RwLock>, + ) -> Result { + self.std_dev_get(fd_map, __WASI_STDOUT_FILENO) + } + /// Get the `VirtualFile` object at stdout mutably + pub fn stdout_mut( + &self, + fd_map: &RwLock>, + ) -> Result { + self.std_dev_get_mut(fd_map, __WASI_STDOUT_FILENO) + } + + /// Get the `VirtualFile` object at stderr + pub fn stderr( + &self, + fd_map: &RwLock>, + ) -> Result { + self.std_dev_get(fd_map, __WASI_STDERR_FILENO) + } + /// Get the `VirtualFile` object at stderr mutably + pub fn stderr_mut( + &self, + fd_map: &RwLock>, + ) -> Result { + self.std_dev_get_mut(fd_map, __WASI_STDERR_FILENO) + } + + /// Get the `VirtualFile` object at stdin + pub fn stdin( + &self, + fd_map: &RwLock>, + ) -> Result { + self.std_dev_get(fd_map, __WASI_STDIN_FILENO) + } + /// Get the `VirtualFile` object at stdin mutably + pub fn stdin_mut( + &self, + fd_map: &RwLock>, + ) -> Result { + self.std_dev_get_mut(fd_map, __WASI_STDIN_FILENO) + } + + /// Internal helper function to get a standard device handle. + /// Expects one of `__WASI_STDIN_FILENO`, `__WASI_STDOUT_FILENO`, `__WASI_STDERR_FILENO`. + fn std_dev_get( + &self, + fd_map: &RwLock>, + fd: __wasi_fd_t, + ) -> Result { + if let Some(fd) = fd_map.read().unwrap().get(&fd) { + let guard = self.arena[fd.inode].read(); + if let Kind::File { .. } = guard.deref() { + Ok(InodeValFileReadGuard { guard }) + } else { + // Our public API should ensure that this is not possible + unreachable!("Non-file found in standard device location") + } + } else { + // this should only trigger if we made a mistake in this crate + Err(FsError::NoDevice) + } + } + /// Internal helper function to mutably get a standard device handle. + /// Expects one of `__WASI_STDIN_FILENO`, `__WASI_STDOUT_FILENO`, `__WASI_STDERR_FILENO`. + fn std_dev_get_mut( + &self, + fd_map: &RwLock>, + fd: __wasi_fd_t, + ) -> Result { + if let Some(fd) = fd_map.read().unwrap().get(&fd) { + let guard = self.arena[fd.inode].write(); + if let Kind::File { .. } = guard.deref() { + Ok(InodeValFileWriteGuard { guard }) + } else { + // Our public API should ensure that this is not possible + unreachable!("Non-file found in standard device location") + } + } else { + // this should only trigger if we made a mistake in this crate + Err(FsError::NoDevice) + } + } +} + /// Warning, modifying these fields directly may cause invariants to break and /// should be considered unsafe. These fields may be made private in a future release #[derive(Debug)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct WasiFs { //pub repo: Repo, - pub preopen_fds: Vec, + pub preopen_fds: RwLock>, pub name_map: HashMap, - pub inodes: Arena, - pub fd_map: HashMap, - pub next_fd: Cell, - inode_counter: Cell, - /// for fds still open after the file has been deleted - pub orphan_fds: HashMap, + pub fd_map: RwLock>, + pub next_fd: AtomicU32, + inode_counter: AtomicU64, #[cfg_attr(feature = "enable-serde", serde(skip, default = "default_fs_backing"))] pub fs_backing: Box, } @@ -224,11 +393,12 @@ impl FileSystem for FallbackFileSystem { impl WasiFs { /// Created for the builder API. like `new` but with more information pub(crate) fn new_with_preopen( + inodes: &mut WasiInodes, preopens: &[PreopenedDir], vfs_preopens: &[String], fs_backing: Box, ) -> Result { - let (mut wasi_fs, root_inode) = Self::new_init(fs_backing)?; + let (wasi_fs, root_inode) = Self::new_init(fs_backing, inodes)?; for preopen_name in vfs_preopens { let kind = Kind::Dir { @@ -250,7 +420,7 @@ impl WasiFs { | __WASI_RIGHT_POLL_FD_READWRITE | __WASI_RIGHT_SOCK_SHUTDOWN; let inode = wasi_fs - .create_inode(kind, true, preopen_name.clone()) + .create_inode(inodes, kind, true, preopen_name.clone()) .map_err(|e| { format!( "Failed to create inode for preopened dir (name `{}`): WASI error code: {}", @@ -261,17 +431,20 @@ impl WasiFs { let fd = wasi_fs .create_fd(rights, rights, 0, fd_flags, inode) .map_err(|e| format!("Could not open fd for file {:?}: {}", preopen_name, e))?; - if let Kind::Root { entries } = &mut wasi_fs.inodes[root_inode].kind { - let existing_entry = entries.insert(preopen_name.clone(), inode); - if existing_entry.is_some() { - return Err(format!( - "Found duplicate entry for alias `{}`", - preopen_name - )); + { + let mut guard = inodes.arena[root_inode].write(); + if let Kind::Root { entries } = guard.deref_mut() { + let existing_entry = entries.insert(preopen_name.clone(), inode); + if existing_entry.is_some() { + return Err(format!( + "Found duplicate entry for alias `{}`", + preopen_name + )); + } + assert!(existing_entry.is_none()) } - assert!(existing_entry.is_none()) } - wasi_fs.preopen_fds.push(fd); + wasi_fs.preopen_fds.write().unwrap().push(fd); } for PreopenedDir { @@ -352,9 +525,9 @@ impl WasiFs { rights }; let inode = if let Some(alias) = &alias { - wasi_fs.create_inode(kind, true, alias.clone()) + wasi_fs.create_inode(inodes, kind, true, alias.clone()) } else { - wasi_fs.create_inode(kind, true, path.to_string_lossy().into_owned()) + wasi_fs.create_inode(inodes, kind, true, path.to_string_lossy().into_owned()) } .map_err(|e| { format!( @@ -379,19 +552,22 @@ impl WasiFs { let fd = wasi_fs .create_fd(rights, rights, 0, fd_flags, inode) .map_err(|e| format!("Could not open fd for file {:?}: {}", path, e))?; - if let Kind::Root { entries } = &mut wasi_fs.inodes[root_inode].kind { - let key = if let Some(alias) = &alias { - alias.clone() - } else { - path.to_string_lossy().into_owned() - }; - let existing_entry = entries.insert(key.clone(), inode); - if existing_entry.is_some() { - return Err(format!("Found duplicate entry for alias `{}`", key)); + { + let mut guard = inodes.arena[root_inode].write(); + if let Kind::Root { entries } = guard.deref_mut() { + let key = if let Some(alias) = &alias { + alias.clone() + } else { + path.to_string_lossy().into_owned() + }; + let existing_entry = entries.insert(key.clone(), inode); + if existing_entry.is_some() { + return Err(format!("Found duplicate entry for alias `{}`", key)); + } + assert!(existing_entry.is_none()) } - assert!(existing_entry.is_none()) } - wasi_fs.preopen_fds.push(fd); + wasi_fs.preopen_fds.write().unwrap().push(fd); } Ok(wasi_fs) @@ -399,22 +575,22 @@ impl WasiFs { /// Private helper function to init the filesystem, called in `new` and /// `new_with_preopen` - fn new_init(fs_backing: Box) -> Result<(Self, Inode), String> { + fn new_init( + fs_backing: Box, + inodes: &mut WasiInodes, + ) -> Result<(Self, Inode), String> { debug!("Initializing WASI filesystem"); - let inodes = Arena::new(); - let mut wasi_fs = Self { - preopen_fds: vec![], + let wasi_fs = Self { + preopen_fds: RwLock::new(vec![]), name_map: HashMap::new(), - inodes, - fd_map: HashMap::new(), - next_fd: Cell::new(3), - inode_counter: Cell::new(1024), - orphan_fds: HashMap::new(), + fd_map: RwLock::new(HashMap::new()), + next_fd: AtomicU32::new(3), + inode_counter: AtomicU64::new(1024), fs_backing, }; - wasi_fs.create_stdin(); - wasi_fs.create_stdout(); - wasi_fs.create_stderr(); + wasi_fs.create_stdin(inodes); + wasi_fs.create_stdout(inodes); + wasi_fs.create_stderr(inodes); // create virtual root let root_inode = { @@ -436,83 +612,20 @@ impl WasiFs { & (!__WASI_RIGHT_PATH_SYMLINK) & (!__WASI_RIGHT_PATH_UNLINK_FILE) & (!__WASI_RIGHT_PATH_REMOVE_DIRECTORY)*/; - let inode = wasi_fs.create_virtual_root(); + let inode = wasi_fs.create_virtual_root(inodes); let fd = wasi_fs .create_fd(root_rights, root_rights, 0, Fd::READ, inode) .map_err(|e| format!("Could not create root fd: {}", e))?; - wasi_fs.preopen_fds.push(fd); + wasi_fs.preopen_fds.write().unwrap().push(fd); inode }; Ok((wasi_fs, root_inode)) } - /// Get the `VirtualFile` object at stdout - pub fn stdout(&self) -> Result<&Option>, FsError> { - self.std_dev_get(__WASI_STDOUT_FILENO) - } - /// Get the `VirtualFile` object at stdout mutably - pub fn stdout_mut(&mut self) -> Result<&mut Option>, FsError> { - self.std_dev_get_mut(__WASI_STDOUT_FILENO) - } - - /// Get the `VirtualFile` object at stderr - pub fn stderr(&self) -> Result<&Option>, FsError> { - self.std_dev_get(__WASI_STDERR_FILENO) - } - /// Get the `VirtualFile` object at stderr mutably - pub fn stderr_mut(&mut self) -> Result<&mut Option>, FsError> { - self.std_dev_get_mut(__WASI_STDERR_FILENO) - } - - /// Get the `VirtualFile` object at stdin - pub fn stdin(&self) -> Result<&Option>, FsError> { - self.std_dev_get(__WASI_STDIN_FILENO) - } - /// Get the `VirtualFile` object at stdin mutably - pub fn stdin_mut(&mut self) -> Result<&mut Option>, FsError> { - self.std_dev_get_mut(__WASI_STDIN_FILENO) - } - - /// Internal helper function to get a standard device handle. - /// Expects one of `__WASI_STDIN_FILENO`, `__WASI_STDOUT_FILENO`, `__WASI_STDERR_FILENO`. - fn std_dev_get(&self, fd: __wasi_fd_t) -> Result<&Option>, FsError> { - if let Some(fd) = self.fd_map.get(&fd) { - if let Kind::File { ref handle, .. } = self.inodes[fd.inode].kind { - Ok(handle) - } else { - // Our public API should ensure that this is not possible - unreachable!("Non-file found in standard device location") - } - } else { - // this should only trigger if we made a mistake in this crate - Err(FsError::NoDevice) - } - } - /// Internal helper function to mutably get a standard device handle. - /// Expects one of `__WASI_STDIN_FILENO`, `__WASI_STDOUT_FILENO`, `__WASI_STDERR_FILENO`. - fn std_dev_get_mut( - &mut self, - fd: __wasi_fd_t, - ) -> Result<&mut Option>, FsError> { - if let Some(fd) = self.fd_map.get_mut(&fd) { - if let Kind::File { ref mut handle, .. } = self.inodes[fd.inode].kind { - Ok(handle) - } else { - // Our public API should ensure that this is not possible - unreachable!("Non-file found in standard device location") - } - } else { - // this should only trigger if we made a mistake in this crate - Err(FsError::NoDevice) - } - } - /// Returns the next available inode index for creating a new inode. - fn get_next_inode_index(&mut self) -> u64 { - let next = self.inode_counter.get(); - self.inode_counter.set(next + 1); - next + fn get_next_inode_index(&self) -> u64 { + self.inode_counter.fetch_add(1, Ordering::AcqRel) } /// This function is like create dir all, but it also opens it. @@ -527,22 +640,23 @@ impl WasiFs { #[allow(dead_code)] pub unsafe fn open_dir_all( &mut self, + inodes: &mut WasiInodes, base: __wasi_fd_t, name: String, rights: __wasi_rights_t, rights_inheriting: __wasi_rights_t, flags: __wasi_fdflags_t, ) -> Result<__wasi_fd_t, FsError> { - let base_fd = self.get_fd(base).map_err(fs_error_from_wasi_err)?; // TODO: check permissions here? probably not, but this should be // an explicit choice, so justify it in a comment when we remove this one - let mut cur_inode = base_fd.inode; + let mut cur_inode = self.get_fd_inode(base).map_err(fs_error_from_wasi_err)?; let path: &Path = Path::new(&name); //let n_components = path.components().count(); for c in path.components() { let segment_name = c.as_os_str().to_string_lossy().to_string(); - match &self.inodes[cur_inode].kind { + let guard = inodes.arena[cur_inode].read(); + match guard.deref() { Kind::Dir { ref entries, .. } | Kind::Root { ref entries } => { if let Some(_entry) = entries.get(&segment_name) { // TODO: this should be fixed @@ -555,19 +669,27 @@ impl WasiFs { entries: HashMap::new(), }; - let inode = - self.create_inode_with_default_stat(kind, false, segment_name.clone()); + drop(guard); + let inode = self.create_inode_with_default_stat( + inodes, + kind, + false, + segment_name.clone(), + ); + // reborrow to insert - match &mut self.inodes[cur_inode].kind { - Kind::Dir { - ref mut entries, .. - } - | Kind::Root { ref mut entries } => { - entries.insert(segment_name, inode); + { + let mut guard = inodes.arena[cur_inode].write(); + match guard.deref_mut() { + Kind::Dir { + ref mut entries, .. + } + | Kind::Root { ref mut entries } => { + entries.insert(segment_name, inode); + } + _ => unreachable!("Dir or Root became not Dir or Root"), } - _ => unreachable!("Dir or Root became not Dir or Root"), } - cur_inode = inode; } _ => return Err(FsError::BaseNotDirectory), @@ -591,20 +713,21 @@ impl WasiFs { #[allow(dead_code)] pub fn open_file_at( &mut self, + inodes: &mut WasiInodes, base: __wasi_fd_t, - file: Box, + file: Box, open_flags: u16, name: String, rights: __wasi_rights_t, rights_inheriting: __wasi_rights_t, flags: __wasi_fdflags_t, ) -> Result<__wasi_fd_t, FsError> { - let base_fd = self.get_fd(base).map_err(fs_error_from_wasi_err)?; // TODO: check permissions here? probably not, but this should be // an explicit choice, so justify it in a comment when we remove this one - let base_inode = base_fd.inode; + let base_inode = self.get_fd_inode(base).map_err(fs_error_from_wasi_err)?; - match &self.inodes[base_inode].kind { + let guard = inodes.arena[base_inode].read(); + match guard.deref() { Kind::Dir { ref entries, .. } | Kind::Root { ref entries } => { if let Some(_entry) = entries.get(&name) { // TODO: eventually change the logic here to allow overwrites @@ -614,21 +737,25 @@ impl WasiFs { let kind = Kind::File { handle: Some(file), path: PathBuf::from(""), - fd: Some(self.next_fd.get()), + fd: Some(self.next_fd.load(Ordering::Acquire)), }; + drop(guard); let inode = self - .create_inode(kind, false, name.clone()) + .create_inode(inodes, kind, false, name.clone()) .map_err(|_| FsError::IOError)?; - // reborrow to insert - match &mut self.inodes[base_inode].kind { - Kind::Dir { - ref mut entries, .. - } - | Kind::Root { ref mut entries } => { - entries.insert(name, inode); + + { + let mut guard = inodes.arena[base_inode].write(); + match guard.deref_mut() { + Kind::Dir { + ref mut entries, .. + } + | Kind::Root { ref mut entries } => { + entries.insert(name, inode); + } + _ => unreachable!("Dir or Root became not Dir or Root"), } - _ => unreachable!("Dir or Root became not Dir or Root"), } self.create_fd(rights, rights_inheriting, flags, open_flags, inode) @@ -643,26 +770,29 @@ impl WasiFs { /// TODO: add examples #[allow(dead_code)] pub fn swap_file( - &mut self, + &self, + inodes: &WasiInodes, fd: __wasi_fd_t, - file: Box, - ) -> Result>, FsError> { + file: Box, + ) -> Result>, FsError> { let mut ret = Some(file); match fd { __WASI_STDIN_FILENO => { - std::mem::swap(self.stdin_mut()?, &mut ret); + let mut target = inodes.stdin_mut(&self.fd_map)?; + std::mem::swap(target.deref_mut(), &mut ret); } __WASI_STDOUT_FILENO => { - std::mem::swap(self.stdout_mut()?, &mut ret); + let mut target = inodes.stdout_mut(&self.fd_map)?; + std::mem::swap(target.deref_mut(), &mut ret); } __WASI_STDERR_FILENO => { - std::mem::swap(self.stderr_mut()?, &mut ret); + let mut target = inodes.stderr_mut(&self.fd_map)?; + std::mem::swap(target.deref_mut(), &mut ret); } _ => { - let base_fd = self.get_fd(fd).map_err(fs_error_from_wasi_err)?; - let base_inode = base_fd.inode; - - match &mut self.inodes[base_inode].kind { + let base_inode = self.get_fd_inode(fd).map_err(fs_error_from_wasi_err)?; + let mut guard = inodes.arena[base_inode].write(); + match guard.deref_mut() { Kind::File { ref mut handle, .. } => { std::mem::swap(handle, &mut ret); } @@ -676,15 +806,19 @@ impl WasiFs { /// refresh size from filesystem pub(crate) fn filestat_resync_size( - &mut self, + &self, + inodes: &WasiInodes, fd: __wasi_fd_t, ) -> Result<__wasi_filesize_t, __wasi_errno_t> { - let fd = self.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)?; - match &mut self.inodes[fd.inode].kind { + let inode = self.get_fd_inode(fd)?; + let mut guard = inodes.arena[inode].write(); + match guard.deref_mut() { Kind::File { handle, .. } => { if let Some(h) = handle { let new_size = h.size(); - self.inodes[fd.inode].stat.st_size = new_size; + drop(guard); + + inodes.arena[inode].stat.write().unwrap().st_size = new_size; Ok(new_size as __wasi_filesize_t) } else { Err(__WASI_EBADF) @@ -709,7 +843,8 @@ impl WasiFs { /// /// TODO: write more tests for this code fn get_inode_at_path_inner( - &mut self, + &self, + inodes: &mut WasiInodes, base: __wasi_fd_t, path: &str, mut symlink_count: u32, @@ -719,10 +854,9 @@ impl WasiFs { return Err(__WASI_EMLINK); } - let base_dir = self.get_fd(base)?; let path: &Path = Path::new(path); - let mut cur_inode = base_dir.inode; + let mut cur_inode = self.get_fd_inode(base)?; let n_components = path.components().count(); // TODO: rights checks 'path_iter: for (i, component) in path.components().enumerate() { @@ -731,7 +865,8 @@ impl WasiFs { // for each component traverse file structure // loading inodes as necessary 'symlink_resolution: while symlink_count < MAX_SYMLINKS { - match &mut self.inodes[cur_inode].kind { + let mut guard = inodes.arena[cur_inode].write(); + match guard.deref_mut() { Kind::Buffer { .. } => unimplemented!("state::get_inode_at_path for buffers"), Kind::Dir { ref mut entries, @@ -795,7 +930,7 @@ impl WasiFs { debug!("attempting to decompose path {:?}", link_value); let (pre_open_dir_fd, relative_path) = if link_value.is_relative() { - self.path_into_pre_open_and_relative_path(&file)? + self.path_into_pre_open_and_relative_path(inodes, &file)? } else { unimplemented!("Absolute symlinks are not yet supported"); }; @@ -831,7 +966,9 @@ impl WasiFs { path: file.clone(), fd: None, }; + drop(guard); let new_inode = self.create_inode_with_stat( + inodes, kind, false, file.to_string_lossy().to_string(), @@ -840,9 +977,11 @@ impl WasiFs { ..__wasi_filestat_t::default() }, ); + + let mut guard = inodes.arena[cur_inode].write(); if let Kind::Dir { ref mut entries, .. - } = &mut self.inodes[cur_inode].kind + } = guard.deref_mut() { entries.insert( component.as_os_str().to_string_lossy().to_string(), @@ -860,12 +999,18 @@ impl WasiFs { unimplemented!("state::get_inode_at_path unknown file type: not file, directory, or symlink"); }; - let new_inode = - self.create_inode(kind, false, file.to_string_lossy().to_string())?; + drop(guard); + let new_inode = self.create_inode( + inodes, + kind, + false, + file.to_string_lossy().to_string(), + )?; if should_insert { + let mut guard = inodes.arena[cur_inode].write(); if let Kind::Dir { ref mut entries, .. - } = &mut self.inodes[cur_inode].kind + } = guard.deref_mut() { entries.insert( component.as_os_str().to_string_lossy().to_string(), @@ -920,7 +1065,9 @@ impl WasiFs { base.to_string_lossy().to_string() }; debug!("Following symlink recursively"); + drop(guard); let symlink_inode = self.get_inode_at_path_inner( + inodes, new_base_dir, &new_path, symlink_count + 1, @@ -929,7 +1076,8 @@ impl WasiFs { cur_inode = symlink_inode; // if we're at the very end and we found a file, then we're done // TODO: figure out if this should also happen for directories? - if let Kind::File { .. } = &self.inodes[cur_inode].kind { + let guard = inodes.arena[cur_inode].read(); + if let Kind::File { .. } = guard.deref() { // check if on last step if last_component { break 'symlink_resolution; @@ -956,6 +1104,7 @@ impl WasiFs { /// In the case of a tie, the later preopened fd is preferred. fn path_into_pre_open_and_relative_path<'path>( &self, + inodes: &WasiInodes, path: &'path Path, ) -> Result<(__wasi_fd_t, &'path Path), __wasi_errno_t> { enum BaseFdAndRelPath<'a> { @@ -977,9 +1126,11 @@ impl WasiFs { } let mut res = BaseFdAndRelPath::None; // for each preopened directory - for po_fd in &self.preopen_fds { - let po_inode = self.fd_map[po_fd].inode; - let po_path = match &self.inodes[po_inode].kind { + let preopen_fds = self.preopen_fds.read().unwrap(); + for po_fd in preopen_fds.deref() { + let po_inode = self.fd_map.read().unwrap()[po_fd].inode; + let guard = inodes.arena[po_inode].read(); + let po_path = match guard.deref() { Kind::Dir { path, .. } => &**path, Kind::Root { .. } => Path::new("/"), _ => unreachable!("Preopened FD that's not a directory or the root"), @@ -1010,17 +1161,18 @@ impl WasiFs { /// expects inode to point to a directory pub(crate) fn path_depth_from_fd( &self, + inodes: &WasiInodes, fd: __wasi_fd_t, inode: Inode, ) -> Result { let mut counter = 0; - let base_fd = self.get_fd(fd)?; - let base_inode = base_fd.inode; + let base_inode = self.get_fd_inode(fd)?; let mut cur_inode = inode; while cur_inode != base_inode { counter += 1; - match &self.inodes[cur_inode].kind { + let guard = inodes.arena[cur_inode].read(); + match guard.deref() { Kind::Dir { parent, .. } => { if let Some(p) = parent { cur_inode = *p; @@ -1040,18 +1192,20 @@ impl WasiFs { // symlink so // This will be resolved when we have tests asserting the correct behavior pub(crate) fn get_inode_at_path( - &mut self, + &self, + inodes: &mut WasiInodes, base: __wasi_fd_t, path: &str, follow_symlinks: bool, ) -> Result { - self.get_inode_at_path_inner(base, path, 0, follow_symlinks) + self.get_inode_at_path_inner(inodes, base, path, 0, follow_symlinks) } /// Returns the parent Dir or Root that the file at a given path is in and the file name /// stripped off pub(crate) fn get_parent_inode_at_path( - &mut self, + &self, + inodes: &mut WasiInodes, base: __wasi_fd_t, path: &Path, follow_symlinks: bool, @@ -1067,31 +1221,45 @@ impl WasiFs { for comp in components.rev() { parent_dir.push(comp); } - self.get_inode_at_path(base, &parent_dir.to_string_lossy(), follow_symlinks) + self.get_inode_at_path(inodes, base, &parent_dir.to_string_lossy(), follow_symlinks) .map(|v| (v, new_entity_name)) } - pub fn get_fd(&self, fd: __wasi_fd_t) -> Result<&Fd, __wasi_errno_t> { - self.fd_map.get(&fd).ok_or(__WASI_EBADF) + pub fn get_fd(&self, fd: __wasi_fd_t) -> Result { + self.fd_map + .read() + .unwrap() + .get(&fd) + .ok_or(__WASI_EBADF) + .map(|a| a.clone()) } - /// gets either a normal inode or an orphaned inode - pub fn get_inodeval_mut(&mut self, fd: __wasi_fd_t) -> Result<&mut InodeVal, __wasi_errno_t> { - let inode = self.get_fd(fd)?.inode; - if let Some(iv) = self.inodes.get_mut(inode) { - Ok(iv) - } else { - self.orphan_fds.get_mut(&inode).ok_or(__WASI_EBADF) - } + pub fn get_fd_inode( + &self, + fd: __wasi_fd_t, + ) -> Result { + self.fd_map + .read() + .unwrap() + .get(&fd) + .ok_or(__WASI_EBADF) + .map(|a| a.inode) } - pub fn filestat_fd(&self, fd: __wasi_fd_t) -> Result<__wasi_filestat_t, __wasi_errno_t> { - let fd = self.get_fd(fd)?; - - Ok(self.inodes[fd.inode].stat) + pub fn filestat_fd( + &self, + inodes: &WasiInodes, + fd: __wasi_fd_t, + ) -> Result<__wasi_filestat_t, __wasi_errno_t> { + let inode = self.get_fd_inode(fd)?; + Ok(inodes.arena[inode].stat.read().unwrap().deref().clone()) } - pub fn fdstat(&self, fd: __wasi_fd_t) -> Result<__wasi_fdstat_t, __wasi_errno_t> { + pub fn fdstat( + &self, + inodes: &WasiInodes, + fd: __wasi_fd_t, + ) -> Result<__wasi_fdstat_t, __wasi_errno_t> { match fd { __WASI_STDIN_FILENO => { return Ok(__wasi_fdstat_t { @@ -1129,11 +1297,11 @@ impl WasiFs { _ => (), } let fd = self.get_fd(fd)?; - debug!("fdstat: {:?}", fd); + let guard = inodes.arena[fd.inode].read(); Ok(__wasi_fdstat_t { - fs_filetype: match self.inodes[fd.inode].kind { + fs_filetype: match guard.deref() { Kind::File { .. } => __WASI_FILETYPE_REGULAR_FILE, Kind::Dir { .. } => __WASI_FILETYPE_DIRECTORY, Kind::Symlink { .. } => __WASI_FILETYPE_SYMBOLIC_LINK, @@ -1145,11 +1313,15 @@ impl WasiFs { }) } - pub fn prestat_fd(&self, fd: __wasi_fd_t) -> Result<__wasi_prestat_t, __wasi_errno_t> { - let fd = self.fd_map.get(&fd).ok_or(__WASI_EBADF)?; + pub fn prestat_fd( + &self, + inodes: &WasiInodes, + fd: __wasi_fd_t, + ) -> Result<__wasi_prestat_t, __wasi_errno_t> { + let inode = self.get_fd_inode(fd)?; + trace!("in prestat_fd {:?}", self.get_fd(fd)?); - debug!("in prestat_fd {:?}", fd); - let inode_val = &self.inodes[fd.inode]; + let inode_val = &inodes.arena[inode]; if inode_val.is_preopened { Ok(__wasi_prestat_t { @@ -1165,30 +1337,29 @@ impl WasiFs { } } - pub fn flush(&mut self, fd: __wasi_fd_t) -> Result<(), __wasi_errno_t> { + pub fn flush(&self, inodes: &WasiInodes, fd: __wasi_fd_t) -> Result<(), __wasi_errno_t> { match fd { __WASI_STDIN_FILENO => (), - __WASI_STDOUT_FILENO => self - .stdout_mut() + __WASI_STDOUT_FILENO => inodes + .stdout_mut(&self.fd_map) .map_err(fs_error_into_wasi_err)? .as_mut() - .and_then(|f| f.flush().ok()) - .ok_or(__WASI_EIO)?, - __WASI_STDERR_FILENO => self - .stderr_mut() + .and_then(|f| Some(f.flush().map_err(map_io_err))) + .unwrap_or_else(|| Err(__WASI_EIO))?, + __WASI_STDERR_FILENO => inodes + .stderr_mut(&self.fd_map) .map_err(fs_error_into_wasi_err)? .as_mut() .and_then(|f| f.flush().ok()) .ok_or(__WASI_EIO)?, _ => { - let fd = self.fd_map.get(&fd).ok_or(__WASI_EBADF)?; + let fd = self.get_fd(fd)?; if fd.rights & __WASI_RIGHT_FD_DATASYNC == 0 { return Err(__WASI_EACCES); } - let inode = &mut self.inodes[fd.inode]; - - match &mut inode.kind { + let mut guard = inodes.arena[fd.inode].write(); + match guard.deref_mut() { Kind::File { handle, .. } => { if let Some(file) = handle { file.flush().map_err(|_| __WASI_EIO)? @@ -1209,29 +1380,32 @@ impl WasiFs { /// Creates an inode and inserts it given a Kind and some extra data pub(crate) fn create_inode( - &mut self, + &self, + inodes: &mut WasiInodes, kind: Kind, is_preopened: bool, name: String, ) -> Result { - let stat = self.get_stat_for_kind(&kind).ok_or(__WASI_EIO)?; - Ok(self.create_inode_with_stat(kind, is_preopened, name, stat)) + let stat = self.get_stat_for_kind(inodes, &kind)?; + Ok(self.create_inode_with_stat(inodes, kind, is_preopened, name, stat)) } /// Creates an inode and inserts it given a Kind, does not assume the file exists. pub(crate) fn create_inode_with_default_stat( - &mut self, + &self, + inodes: &mut WasiInodes, kind: Kind, is_preopened: bool, name: String, ) -> Inode { let stat = __wasi_filestat_t::default(); - self.create_inode_with_stat(kind, is_preopened, name, stat) + self.create_inode_with_stat(inodes, kind, is_preopened, name, stat) } /// Creates an inode with the given filestat and inserts it. pub(crate) fn create_inode_with_stat( - &mut self, + &self, + inodes: &mut WasiInodes, kind: Kind, is_preopened: bool, name: String, @@ -1239,25 +1413,24 @@ impl WasiFs { ) -> Inode { stat.st_ino = self.get_next_inode_index(); - self.inodes.insert(InodeVal { - stat, + inodes.arena.insert(InodeVal { + stat: RwLock::new(stat), is_preopened, name, - kind, + kind: RwLock::new(kind), }) } pub fn create_fd( - &mut self, + &self, rights: __wasi_rights_t, rights_inheriting: __wasi_rights_t, flags: __wasi_fdflags_t, open_flags: u16, inode: Inode, ) -> Result<__wasi_fd_t, __wasi_errno_t> { - let idx = self.next_fd.get(); - self.next_fd.set(idx + 1); - self.fd_map.insert( + let idx = self.next_fd.fetch_add(1, Ordering::AcqRel); + self.fd_map.write().unwrap().insert( idx, Fd { rights, @@ -1279,11 +1452,11 @@ impl WasiFs { /// # Safety /// - The caller must ensure that all references to the specified inode have /// been removed from the filesystem. - pub unsafe fn remove_inode(&mut self, inode: Inode) -> Option { - self.inodes.remove(inode) + pub unsafe fn remove_inode(&self, inodes: &mut WasiInodes, inode: Inode) -> Option { + inodes.arena.remove(inode) } - fn create_virtual_root(&mut self) -> Inode { + fn create_virtual_root(&self, inodes: &mut WasiInodes) -> Inode { let stat = __wasi_filestat_t { st_filetype: __WASI_FILETYPE_DIRECTORY, st_ino: self.get_next_inode_index(), @@ -1293,16 +1466,17 @@ impl WasiFs { entries: HashMap::new(), }; - self.inodes.insert(InodeVal { - stat, + inodes.arena.insert(InodeVal { + stat: RwLock::new(stat), is_preopened: true, name: "/".to_string(), - kind: root_kind, + kind: RwLock::new(root_kind), }) } - fn create_stdout(&mut self) { + fn create_stdout(&self, inodes: &mut WasiInodes) { self.create_std_dev_inner( + inodes, Box::new(Stdout::default()), "stdout", __WASI_STDOUT_FILENO, @@ -1310,8 +1484,9 @@ impl WasiFs { __WASI_FDFLAG_APPEND, ); } - fn create_stdin(&mut self) { + fn create_stdin(&self, inodes: &mut WasiInodes) { self.create_std_dev_inner( + inodes, Box::new(Stdin::default()), "stdin", __WASI_STDIN_FILENO, @@ -1319,8 +1494,9 @@ impl WasiFs { 0, ); } - fn create_stderr(&mut self) { + fn create_stderr(&self, inodes: &mut WasiInodes) { self.create_std_dev_inner( + inodes, Box::new(Stderr::default()), "stderr", __WASI_STDERR_FILENO, @@ -1330,8 +1506,9 @@ impl WasiFs { } fn create_std_dev_inner( - &mut self, - handle: Box, + &self, + inodes: &mut WasiInodes, + handle: Box, name: &'static str, raw_fd: __wasi_fd_t, rights: __wasi_rights_t, @@ -1347,13 +1524,15 @@ impl WasiFs { handle: Some(handle), path: "".into(), }; - let inode = self.inodes.insert(InodeVal { - stat, - is_preopened: true, - name: name.to_string(), - kind, - }); - self.fd_map.insert( + let inode = { + inodes.arena.insert(InodeVal { + stat: RwLock::new(stat), + is_preopened: true, + name: name.to_string(), + kind: RwLock::new(kind), + }) + }; + self.fd_map.write().unwrap().insert( raw_fd, Fd { rights, @@ -1367,7 +1546,11 @@ impl WasiFs { ); } - pub fn get_stat_for_kind(&self, kind: &Kind) -> Option<__wasi_filestat_t> { + pub fn get_stat_for_kind( + &self, + inodes: &WasiInodes, + kind: &Kind, + ) -> Result<__wasi_filestat_t, __wasi_errno_t> { let md = match kind { Kind::File { handle, path, .. } => match handle { Some(wf) => { @@ -1389,9 +1572,10 @@ impl WasiFs { path_to_symlink, .. } => { - let base_po_inode = &self.fd_map[base_po_dir].inode; - let base_po_inode_v = &self.inodes[*base_po_inode]; - match &base_po_inode_v.kind { + let base_po_inode = &self.fd_map.read().unwrap()[base_po_dir].inode; + let base_po_inode_v = &inodes.arena[*base_po_inode]; + let guard = base_po_inode_v.read(); + match guard.deref() { Kind::Root { .. } => { self.fs_backing.symlink_metadata(path_to_symlink).ok()? } @@ -1423,11 +1607,17 @@ impl WasiFs { } /// Closes an open FD, handling all details such as FD being preopen - pub(crate) fn close_fd(&mut self, fd: __wasi_fd_t) -> Result<(), __wasi_errno_t> { - let inodeval_mut = self.get_inodeval_mut(fd)?; - let is_preopened = inodeval_mut.is_preopened; + pub(crate) fn close_fd( + &self, + inodes: &WasiInodes, + fd: __wasi_fd_t, + ) -> Result<(), __wasi_errno_t> { + let inode = self.get_fd_inode(fd)?; + let inodeval = inodes.get_inodeval(inode)?; + let is_preopened = inodeval.is_preopened; - match &mut inodeval_mut.kind { + let mut guard = inodeval.write(); + match guard.deref_mut() { Kind::File { ref mut handle, .. } => { let mut empty_handle = None; std::mem::swap(handle, &mut empty_handle); @@ -1440,22 +1630,27 @@ impl WasiFs { .to_string_lossy() .to_string(); if let Some(p) = *parent { - match &mut self.inodes[p].kind { + drop(guard); + let mut guard = inodes.arena[p].write(); + match guard.deref_mut() { Kind::Dir { entries, .. } | Kind::Root { entries } => { - self.fd_map.remove(&fd).unwrap(); + self.fd_map.write().unwrap().remove(&fd).unwrap(); if is_preopened { let mut idx = None; - for (i, po_fd) in self.preopen_fds.iter().enumerate() { - if *po_fd == fd { - idx = Some(i); - break; + { + let preopen_fds = self.preopen_fds.read().unwrap(); + for (i, po_fd) in preopen_fds.iter().enumerate() { + if *po_fd == fd { + idx = Some(i); + break; + } } } if let Some(i) = idx { // only remove entry properly if this is the original preopen FD // calling `path_open` can give you an fd to the same inode as a preopen fd entries.remove(&key); - self.preopen_fds.remove(i); + self.preopen_fds.write().unwrap().remove(i); // Maybe recursively closes fds if original preopen? } } @@ -1559,6 +1754,7 @@ impl WasiState { #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct WasiState { pub fs: WasiFs, + pub inodes: RwLock, pub args: Vec>, pub envs: Vec>, } diff --git a/lib/wasi/src/state/types.rs b/lib/wasi/src/state/types.rs index 5276de8d364..dc6353195c6 100644 --- a/lib/wasi/src/state/types.rs +++ b/lib/wasi/src/state/types.rs @@ -7,6 +7,7 @@ use std::convert::TryInto; use std::{ collections::VecDeque, io::{self, Read, Seek, Write}, + time::Duration, }; #[cfg(feature = "host-fs")] @@ -187,9 +188,10 @@ impl PollEventBuilder { #[cfg(unix)] pub(crate) fn poll( - selfs: &[&dyn VirtualFile], + selfs: &[&(dyn VirtualFile + Sync)], events: &[PollEventSet], seen_events: &mut [PollEventSet], + timeout: Duration, ) -> Result { if !(selfs.len() == events.len() && events.len() == seen_events.len()) { return Err(FsError::InvalidInput); @@ -204,7 +206,7 @@ pub(crate) fn poll( revents: 0, }) .collect::>(); - 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 @@ -220,11 +222,65 @@ pub(crate) fn poll( #[cfg(not(unix))] 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 { + 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 {} diff --git a/lib/wasi/src/syscalls/legacy/snapshot0.rs b/lib/wasi/src/syscalls/legacy/snapshot0.rs index 1ed33a2989e..b0f3947c83c 100644 --- a/lib/wasi/src/syscalls/legacy/snapshot0.rs +++ b/lib/wasi/src/syscalls/legacy/snapshot0.rs @@ -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 @@ -101,7 +101,7 @@ pub fn fd_seek( offset: types::__wasi_filedelta_t, whence: snapshot0::__wasi_whence_t, newoffset: WasmPtr, -) -> types::__wasi_errno_t { +) -> Result { let new_whence = match whence { snapshot0::__WASI_WHENCE_CUR => types::__WASI_WHENCE_CUR, snapshot0::__WASI_WHENCE_END => types::__WASI_WHENCE_END, @@ -120,23 +120,23 @@ pub fn poll_oneoff( out_: WasmPtr, nsubscriptions: u32, nevents: WasmPtr, -) -> types::__wasi_errno_t { +) -> Result { // 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 = 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 { @@ -162,11 +162,11 @@ pub fn poll_oneoff( // replace the old values of in, in case the calling code reuses the memory let memory = env.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 diff --git a/lib/wasi/src/syscalls/mod.rs b/lib/wasi/src/syscalls/mod.rs index 94a69f8cd7b..4fca46974b7 100644 --- a/lib/wasi/src/syscalls/mod.rs +++ b/lib/wasi/src/syscalls/mod.rs @@ -26,11 +26,14 @@ use crate::{ virtual_file_type_to_wasi_file_type, Fd, Inode, InodeVal, Kind, PollEvent, PollEventBuilder, WasiState, MAX_SYMLINKS, }, - WasiEnv, WasiError, + WasiEnv, WasiError, WasiThread, }; use std::borrow::Borrow; use std::convert::{Infallible, TryInto}; use std::io::{self, Read, Seek, Write}; +use std::time::Duration; +use std::ops::{Deref, DerefMut}; +use std::sync::atomic::Ordering; use tracing::{debug, trace}; use wasmer::{Memory, RuntimeError, Value, WasmPtr, WasmSlice}; use wasmer_vfs::{FsError, VirtualFile}; @@ -63,7 +66,6 @@ fn write_bytes_inner( let bytes = bytes.read_to_vec().map_err(mem_error_to_wasi)?; write_loc.write_all(&bytes).map_err(|_| __WASI_EIO)?; - // TODO: handle failure more accurately bytes_written += iov_inner.buf_len; } Ok(bytes_written) @@ -120,7 +122,7 @@ fn write_buffer_array( let mut current_buffer_offset = 0; for ((i, sub_buffer), ptr) in from.iter().enumerate().zip(ptrs.iter()) { - debug!("ptr: {:?}, subbuffer: {:?}", ptr, sub_buffer); + trace!("ptr: {:?}, subbuffer: {:?}", ptr, sub_buffer); let new_ptr = WasmPtr::new(buffer.offset() + current_buffer_offset); wasi_try_mem!(ptr.write(new_ptr)); @@ -157,7 +159,7 @@ pub fn args_get( argv_buf: WasmPtr, ) -> __wasi_errno_t { debug!("wasi::args_get"); - let (memory, mut state) = env.get_memory_and_wasi_state(0); + let (memory, state) = thread.get_memory_and_wasi_state(0); let result = write_buffer_array(memory, &*state.args, argv, argv_buf); @@ -188,7 +190,7 @@ pub fn args_sizes_get( argv_buf_size: WasmPtr, ) -> __wasi_errno_t { debug!("wasi::args_sizes_get"); - let (memory, mut state) = env.get_memory_and_wasi_state(0); + let (memory, state) = thread.get_memory_and_wasi_state(0); let argc = argc.deref(memory); let argv_buf_size = argv_buf_size.deref(memory); @@ -220,7 +222,9 @@ pub fn clock_res_get( let memory = env.memory(); let out_addr = resolution.deref(memory); - platform_clock_res_get(clock_id, out_addr) + let t_out = wasi_try!(platform_clock_res_get(clock_id, out_addr)); + wasi_try_mem!(resolution.write(memory, t_out as __wasi_timestamp_t)); + __WASI_ESUCCESS } /// ### `clock_time_get()` @@ -241,13 +245,17 @@ pub fn clock_time_get( ) -> __wasi_errno_t { debug!( "wasi::clock_time_get clock_id: {}, precision: {}", - clock_id, precision + clock_id, + precision ); let memory = env.memory(); let out_addr = time.deref(memory); - let result = platform_clock_time_get(clock_id, precision, out_addr); - debug!( + let t_out = wasi_try!(platform_clock_time_get(clock_id, precision)); + wasi_try_mem!(time.write(memory, t_out as __wasi_timestamp_t)); + + let result = __WASI_ESUCCESS; + trace!( "time: {} => {}", wasi_try_mem!(time.deref(memory).read()), result @@ -272,8 +280,8 @@ pub fn environ_get( "wasi::environ_get. Environ: {:?}, environ_buf: {:?}", environ, environ_buf ); - let (memory, mut state) = env.get_memory_and_wasi_state(0); - debug!(" -> State envs: {:?}", state.envs); + let (memory, state) = thread.get_memory_and_wasi_state(0); + trace!(" -> State envs: {:?}", state.envs); write_buffer_array(memory, &*state.envs, environ, environ_buf) } @@ -290,8 +298,8 @@ pub fn environ_sizes_get( environ_count: WasmPtr, environ_buf_size: WasmPtr, ) -> __wasi_errno_t { - debug!("wasi::environ_sizes_get"); - let (memory, mut state) = env.get_memory_and_wasi_state(0); + trace!("wasi::environ_sizes_get"); + let (memory, state) = thread.get_memory_and_wasi_state(0); let environ_count = environ_count.deref(memory); let environ_buf_size = environ_buf_size.deref(memory); @@ -301,9 +309,10 @@ pub fn environ_sizes_get( wasi_try_mem!(environ_count.write(env_var_count)); wasi_try_mem!(environ_buf_size.write(env_buf_size)); - debug!( + trace!( "env_var_count: {}, env_buf_size: {}", - env_var_count, env_buf_size + env_var_count, + env_buf_size ); __WASI_ESUCCESS @@ -350,30 +359,32 @@ pub fn fd_allocate( len: __wasi_filesize_t, ) -> __wasi_errno_t { debug!("wasi::fd_allocate"); - let (memory, mut state) = env.get_memory_and_wasi_state(0); + let (memory, state, inodes) = thread.get_memory_and_wasi_state_and_inodes(0); let fd_entry = wasi_try!(state.fs.get_fd(fd)); let inode = fd_entry.inode; if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_ALLOCATE) { return __WASI_EACCES; } - let new_size = wasi_try!(offset.checked_add(len), __WASI_EINVAL); - - match &mut state.fs.inodes[inode].kind { - Kind::File { handle, .. } => { - if let Some(handle) = handle { - wasi_try!(handle.set_len(new_size).map_err(fs_error_into_wasi_err)); - } else { - return __WASI_EBADF; + let new_size = wasi_try!(offset.checked_add(len).ok_or(__WASI_EINVAL)); + { + let mut guard = inodes.arena[inode].write(); + match guard.deref_mut() { + Kind::File { handle, .. } => { + if let Some(handle) = handle { + wasi_try!(handle.set_len(new_size).map_err(fs_error_into_wasi_err)); + } else { + return __WASI_EBADF; + } } + Kind::Buffer { buffer } => { + buffer.resize(new_size as usize, 0); + } + Kind::Symlink { .. } => return __WASI_EBADF, + Kind::Dir { .. } | Kind::Root { .. } => return __WASI_EISDIR, } - Kind::Buffer { buffer } => { - buffer.resize(new_size as usize, 0); - } - Kind::Symlink { .. } => return __WASI_EBADF, - Kind::Dir { .. } | Kind::Root { .. } => return __WASI_EISDIR, } - state.fs.inodes[inode].stat.st_size = new_size; + inodes.arena[inode].stat.write().unwrap().st_size = new_size; debug!("New file size: {}", new_size); __WASI_ESUCCESS @@ -391,11 +402,11 @@ pub fn fd_allocate( /// If `fd` is invalid or not open pub fn fd_close(env: &WasiEnv, fd: __wasi_fd_t) -> __wasi_errno_t { debug!("wasi::fd_close: fd={}", fd); - let (memory, mut state) = env.get_memory_and_wasi_state(0); + let (memory, state, inodes) = thread.get_memory_and_wasi_state_and_inodes(0); let fd_entry = wasi_try!(state.fs.get_fd(fd)); - wasi_try!(state.fs.close_fd(fd)); + wasi_try!(state.fs.close_fd(inodes.deref(), fd)); __WASI_ESUCCESS } @@ -407,13 +418,13 @@ pub fn fd_close(env: &WasiEnv, fd: __wasi_fd_t) -> __wasi_errno_t { /// The file descriptor to sync pub fn fd_datasync(env: &WasiEnv, fd: __wasi_fd_t) -> __wasi_errno_t { debug!("wasi::fd_datasync"); - let (memory, mut state) = env.get_memory_and_wasi_state(0); + let (memory, state, inodes) = thread.get_memory_and_wasi_state_and_inodes(0); let fd_entry = wasi_try!(state.fs.get_fd(fd)); if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_DATASYNC) { return __WASI_EACCES; } - if let Err(e) = state.fs.flush(fd) { + if let Err(e) = state.fs.flush(inodes.deref(), fd) { e } else { __WASI_ESUCCESS @@ -438,10 +449,8 @@ pub fn fd_fdstat_get( fd, buf_ptr.offset() ); - let (memory, mut state) = env.get_memory_and_wasi_state(0); - let fd_entry = wasi_try!(state.fs.get_fd(fd)); - - let stat = wasi_try!(state.fs.fdstat(fd)); + let (memory, state, inodes) = thread.get_memory_and_wasi_state_and_inodes(0); + let stat = wasi_try!(state.fs.fdstat(inodes.deref(), fd)); let buf = buf_ptr.deref(memory); wasi_try_mem!(buf.write(stat)); @@ -462,8 +471,9 @@ pub fn fd_fdstat_set_flags( flags: __wasi_fdflags_t, ) -> __wasi_errno_t { debug!("wasi::fd_fdstat_set_flags"); - let (memory, mut state) = env.get_memory_and_wasi_state(0); - let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); + let (memory, state) = thread.get_memory_and_wasi_state(0); + let mut fd_map = state.fs.fd_map.write().unwrap(); + let fd_entry = wasi_try!(fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_FDSTAT_SET_FLAGS) { return __WASI_EACCES; @@ -489,8 +499,9 @@ pub fn fd_fdstat_set_rights( fs_rights_inheriting: __wasi_rights_t, ) -> __wasi_errno_t { debug!("wasi::fd_fdstat_set_rights"); - let (memory, mut state) = env.get_memory_and_wasi_state(0); - let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); + let (memory, state) = thread.get_memory_and_wasi_state(0); + let mut fd_map = state.fs.fd_map.write().unwrap(); + let fd_entry = wasi_try!(fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); // ensure new rights are a subset of current rights if fd_entry.rights | fs_rights_base != fd_entry.rights @@ -519,13 +530,13 @@ pub fn fd_filestat_get( buf: WasmPtr<__wasi_filestat_t>, ) -> __wasi_errno_t { debug!("wasi::fd_filestat_get"); - let (memory, mut state) = env.get_memory_and_wasi_state(0); + let (memory, state, inodes) = thread.get_memory_and_wasi_state_and_inodes(0); let fd_entry = wasi_try!(state.fs.get_fd(fd)); if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_FILESTAT_GET) { return __WASI_EACCES; } - let stat = wasi_try!(state.fs.filestat_fd(fd)); + let stat = wasi_try!(state.fs.filestat_fd(inodes.deref(), fd)); let buf = buf.deref(memory); wasi_try_mem!(buf.write(stat)); @@ -546,7 +557,7 @@ pub fn fd_filestat_set_size( st_size: __wasi_filesize_t, ) -> __wasi_errno_t { debug!("wasi::fd_filestat_set_size"); - let (memory, mut state) = env.get_memory_and_wasi_state(0); + let (memory, state, inodes) = thread.get_memory_and_wasi_state_and_inodes(0); let fd_entry = wasi_try!(state.fs.get_fd(fd)); let inode = fd_entry.inode; @@ -554,21 +565,24 @@ pub fn fd_filestat_set_size( return __WASI_EACCES; } - match &mut state.fs.inodes[inode].kind { - Kind::File { handle, .. } => { - if let Some(handle) = handle { - wasi_try!(handle.set_len(st_size).map_err(fs_error_into_wasi_err)); - } else { - return __WASI_EBADF; + { + let mut guard = inodes.arena[inode].write(); + match guard.deref_mut() { + Kind::File { handle, .. } => { + if let Some(handle) = handle { + wasi_try!(handle.set_len(st_size).map_err(fs_error_into_wasi_err)); + } else { + return __WASI_EBADF; + } } + Kind::Buffer { buffer } => { + buffer.resize(st_size as usize, 0); + } + Kind::Symlink { .. } => return __WASI_EBADF, + Kind::Dir { .. } | Kind::Root { .. } => return __WASI_EISDIR, } - Kind::Buffer { buffer } => { - buffer.resize(st_size as usize, 0); - } - Kind::Symlink { .. } => return __WASI_EBADF, - Kind::Dir { .. } | Kind::Root { .. } => return __WASI_EISDIR, } - state.fs.inodes[inode].stat.st_size = st_size; + inodes.arena[inode].stat.write().unwrap().st_size = st_size; __WASI_ESUCCESS } @@ -590,8 +604,8 @@ pub fn fd_filestat_set_times( fst_flags: __wasi_fstflags_t, ) -> __wasi_errno_t { debug!("wasi::fd_filestat_set_times"); - let (memory, mut state) = env.get_memory_and_wasi_state(0); - let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); + let (memory, state, inodes) = thread.get_memory_and_wasi_state_and_inodes(0); + let fd_entry = wasi_try!(state.fs.get_fd(fd)); if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_FILESTAT_SET_TIMES) { return __WASI_EACCES; @@ -605,7 +619,7 @@ pub fn fd_filestat_set_times( } let inode_idx = fd_entry.inode; - let inode = &mut state.fs.inodes[inode_idx]; + let inode = &inodes.arena[inode_idx]; if fst_flags & __WASI_FILESTAT_SET_ATIM != 0 || fst_flags & __WASI_FILESTAT_SET_ATIM_NOW != 0 { let time_to_set = if fst_flags & __WASI_FILESTAT_SET_ATIM != 0 { @@ -613,7 +627,7 @@ pub fn fd_filestat_set_times( } else { wasi_try!(get_current_time_in_nanos()) }; - inode.stat.st_atim = time_to_set; + inode.stat.write().unwrap().st_atim = time_to_set; } if fst_flags & __WASI_FILESTAT_SET_MTIM != 0 || fst_flags & __WASI_FILESTAT_SET_MTIM_NOW != 0 { @@ -622,7 +636,7 @@ pub fn fd_filestat_set_times( } else { wasi_try!(get_current_time_in_nanos()) }; - inode.stat.st_mtim = time_to_set; + inode.stat.write().unwrap().st_mtim = time_to_set; } __WASI_ESUCCESS @@ -650,27 +664,31 @@ pub fn fd_pread( iovs_len: u32, offset: __wasi_filesize_t, nread: WasmPtr, -) -> __wasi_errno_t { - debug!("wasi::fd_pread: fd={}, offset={}", fd, offset); - let (memory, mut state) = env.get_memory_and_wasi_state(0); +) -> Result<__wasi_errno_t, WasiError> { + trace!("wasi::fd_pread: fd={}, offset={}", fd, offset); + let (memory, state, inodes) = thread.get_memory_and_wasi_state_and_inodes(0); - let iovs = wasi_try_mem!(iovs.slice(memory, iovs_len)); + let iovs = wasi_try_mem_ok!(iovs.slice(memory, iovs_len)); let nread_ref = nread.deref(memory); + let fd_entry = wasi_try_ok!(state.fs.get_fd(fd)); let bytes_read = match fd { __WASI_STDIN_FILENO => { - if let Some(ref mut stdin) = - wasi_try!(state.fs.stdin_mut().map_err(fs_error_into_wasi_err)) - { - wasi_try!(read_bytes(stdin, memory, iovs)) + let mut guard = wasi_try_ok!( + inodes + .stdin_mut(&state.fs.fd_map) + .map_err(fs_error_into_wasi_err), + thread + ); + if let Some(ref mut stdin) = guard.deref_mut() { + wasi_try_ok!(read_bytes(stdin, memory, iovs), thread) } else { - return __WASI_EBADF; + return Ok(__WASI_EBADF); } } - __WASI_STDOUT_FILENO => return __WASI_EINVAL, - __WASI_STDERR_FILENO => return __WASI_EINVAL, + __WASI_STDOUT_FILENO => return Ok(__WASI_EINVAL), + __WASI_STDERR_FILENO => return Ok(__WASI_EINVAL), _ => { - let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); let inode = fd_entry.inode; if !(has_rights(fd_entry.rights, __WASI_RIGHT_FD_READ) @@ -680,32 +698,37 @@ pub fn fd_pread( "Invalid rights on {:X}: expected READ and SEEK", fd_entry.rights ); - return __WASI_EACCES; + return Ok(__WASI_EACCES); } - match &mut state.fs.inodes[inode].kind { + let mut guard = inodes.arena[inode].write(); + match guard.deref_mut() { Kind::File { handle, .. } => { if let Some(h) = handle { - wasi_try!( - h.seek(std::io::SeekFrom::Start(offset as u64)).ok(), - __WASI_EIO + wasi_try_ok!( + h.seek(std::io::SeekFrom::Start(offset as u64)) + .map_err(map_io_err), + thread ); - wasi_try!(read_bytes(h, memory, iovs)) + wasi_try_ok!(read_bytes(h, memory, iovs), thread) } else { - return __WASI_EINVAL; + return Ok(__WASI_EINVAL); } } - Kind::Dir { .. } | Kind::Root { .. } => return __WASI_EISDIR, + Kind::Dir { .. } | Kind::Root { .. } => return Ok(__WASI_EISDIR), Kind::Symlink { .. } => unimplemented!("Symlinks in wasi::fd_pread"), Kind::Buffer { buffer } => { - wasi_try!(read_bytes(&buffer[(offset as usize)..], memory, iovs)) + wasi_try_ok!( + read_bytes(&buffer[(offset as usize)..], memory, iovs), + thread + ) } } } }; - wasi_try_mem!(nread_ref.write(bytes_read)); + wasi_try_mem_ok!(nread_ref.write(bytes_read)); debug!("Success: {} bytes read", bytes_read); - __WASI_ESUCCESS + Ok(__WASI_ESUCCESS) } /// ### `fd_prestat_get()` @@ -721,12 +744,12 @@ pub fn fd_prestat_get( fd: __wasi_fd_t, buf: WasmPtr<__wasi_prestat_t>, ) -> __wasi_errno_t { - debug!("wasi::fd_prestat_get: fd={}", fd); - let (memory, mut state) = env.get_memory_and_wasi_state(0); + trace!("wasi::fd_prestat_get: fd={}", fd); + let (memory, state, inodes) = thread.get_memory_and_wasi_state_and_inodes(0); let prestat_ptr = buf.deref(memory); - wasi_try_mem!(prestat_ptr.write(wasi_try!(state.fs.prestat_fd(fd)))); + wasi_try_mem!(prestat_ptr.write(wasi_try!(state.fs.prestat_fd(inodes.deref(), fd)))); __WASI_ESUCCESS } @@ -737,20 +760,22 @@ pub fn fd_prestat_dir_name( path: WasmPtr, path_len: u32, ) -> __wasi_errno_t { - debug!( + trace!( "wasi::fd_prestat_dir_name: fd={}, path_len={}", - fd, path_len + fd, + path_len ); - let (memory, mut state) = env.get_memory_and_wasi_state(0); + let (memory, state, inodes) = thread.get_memory_and_wasi_state_and_inodes(0); let path_chars = wasi_try_mem!(path.slice(memory, path_len)); - let real_fd = wasi_try!(state.fs.fd_map.get(&fd).ok_or(__WASI_EBADF)); - let inode_val = &state.fs.inodes[real_fd.inode]; + let real_inode = wasi_try!(state.fs.get_fd_inode(fd)); + let inode_val = &inodes.arena[real_inode]; // check inode-val.is_preopened? - debug!("=> inode: {:?}", inode_val); - match inode_val.kind { + trace!("=> inode: {:?}", inode_val); + let guard = inode_val.read(); + match guard.deref() { Kind::Dir { .. } | Kind::Root { .. } => { // TODO: verify this: null termination, etc if inode_val.name.len() < path_len as usize { @@ -759,7 +784,7 @@ pub fn fd_prestat_dir_name( .write_slice(inode_val.name.as_bytes())); wasi_try_mem!(path_chars.index(inode_val.name.len() as u64).write(0)); - debug!("=> result: \"{}\"", inode_val.name); + trace!("=> result: \"{}\"", inode_val.name); __WASI_ESUCCESS } else { @@ -791,73 +816,85 @@ pub fn fd_pwrite( iovs_len: u32, offset: __wasi_filesize_t, nwritten: WasmPtr, -) -> __wasi_errno_t { - debug!("wasi::fd_pwrite"); +) -> Result<__wasi_errno_t, WasiError> { + trace!("wasi::fd_pwrite"); // TODO: refactor, this is just copied from `fd_write`... - let (memory, mut state) = env.get_memory_and_wasi_state(0); - let iovs_arr = wasi_try_mem!(iovs.slice(memory, iovs_len)); + let (memory, state, inodes) = thread.get_memory_and_wasi_state_and_inodes(0); + let iovs_arr = wasi_try_mem_ok!(iovs.slice(memory, iovs_len)); let nwritten_ref = nwritten.deref(memory); + let fd_entry = wasi_try_ok!(state.fs.get_fd(fd)); let bytes_written = match fd { - __WASI_STDIN_FILENO => return __WASI_EINVAL, + __WASI_STDIN_FILENO => return Ok(__WASI_EINVAL), __WASI_STDOUT_FILENO => { - if let Some(ref mut stdout) = - wasi_try!(state.fs.stdout_mut().map_err(fs_error_into_wasi_err)) - { - wasi_try!(write_bytes(stdout, memory, iovs_arr)) + let mut guard = wasi_try_ok!( + inodes + .stdout_mut(&state.fs.fd_map) + .map_err(fs_error_into_wasi_err), + thread + ); + if let Some(ref mut stdout) = guard.deref_mut() { + wasi_try_ok!(write_bytes(stdout, memory, iovs_arr), thread) } else { - return __WASI_EBADF; + return Ok(__WASI_EBADF); } } __WASI_STDERR_FILENO => { - if let Some(ref mut stderr) = - wasi_try!(state.fs.stderr_mut().map_err(fs_error_into_wasi_err)) - { - wasi_try!(write_bytes(stderr, memory, iovs_arr)) + let mut guard = wasi_try_ok!( + inodes + .stderr_mut(&state.fs.fd_map) + .map_err(fs_error_into_wasi_err), + thread + ); + if let Some(ref mut stderr) = guard.deref_mut() { + wasi_try_ok!(write_bytes(stderr, memory, iovs_arr), thread) } else { - return __WASI_EBADF; + return Ok(__WASI_EBADF); } } _ => { - let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); - if !(has_rights(fd_entry.rights, __WASI_RIGHT_FD_WRITE) && has_rights(fd_entry.rights, __WASI_RIGHT_FD_SEEK)) { - return __WASI_EACCES; + return Ok(__WASI_EACCES); } let inode_idx = fd_entry.inode; - let inode = &mut state.fs.inodes[inode_idx]; + let inode = &inodes.arena[inode_idx]; - match &mut inode.kind { + let mut guard = inode.write(); + match guard.deref_mut() { Kind::File { handle, .. } => { if let Some(handle) = handle { - handle.seek(std::io::SeekFrom::Start(offset as u64)); - wasi_try!(write_bytes(handle, memory, iovs_arr)) + wasi_try_ok!( + handle + .seek(std::io::SeekFrom::Start(offset as u64)) + .map_err(map_io_err), + thread + ); + wasi_try_ok!(write_bytes(handle, memory, iovs_arr), thread) } else { - return __WASI_EINVAL; + return Ok(__WASI_EINVAL); } } Kind::Dir { .. } | Kind::Root { .. } => { // TODO: verify - return __WASI_EISDIR; + return Ok(__WASI_EISDIR); } Kind::Symlink { .. } => unimplemented!("Symlinks in wasi::fd_pwrite"), Kind::Buffer { buffer } => { - wasi_try!(write_bytes( - &mut buffer[(offset as usize)..], - memory, - iovs_arr - )) + wasi_try_ok!( + write_bytes(&mut buffer[(offset as usize)..], memory, iovs_arr), + thread + ) } } } }; - wasi_try_mem!(nwritten_ref.write(bytes_written)); + wasi_try_mem_ok!(nwritten_ref.write(bytes_written)); - __WASI_ESUCCESS + Ok(__WASI_ESUCCESS) } /// ### `fd_read()` @@ -878,66 +915,78 @@ pub fn fd_read( iovs: WasmPtr<__wasi_iovec_t>, iovs_len: u32, nread: WasmPtr, -) -> __wasi_errno_t { - debug!("wasi::fd_read: fd={}", fd); - let (memory, mut state) = env.get_memory_and_wasi_state(0); +) -> Result<__wasi_errno_t, WasiError> { + trace!("wasi::fd_read: fd={}", fd); + let (memory, state, inodes) = thread.get_memory_and_wasi_state_and_inodes(0); - let iovs_arr = wasi_try_mem!(iovs.slice(memory, iovs_len)); + let iovs_arr = wasi_try_mem_ok!(iovs.slice(memory, iovs_len)); let nread_ref = nread.deref(memory); + let fd_entry = wasi_try_ok!(state.fs.get_fd(fd)); let bytes_read = match fd { __WASI_STDIN_FILENO => { - if let Some(ref mut stdin) = - wasi_try!(state.fs.stdin_mut().map_err(fs_error_into_wasi_err)) - { - wasi_try!(read_bytes(stdin, memory, iovs_arr)) + let mut guard = wasi_try_ok!( + inodes + .stdin_mut(&state.fs.fd_map) + .map_err(fs_error_into_wasi_err), + thread + ); + if let Some(ref mut stdin) = guard.deref_mut() { + wasi_try_ok!(read_bytes(stdin, memory, iovs_arr), thread) } else { - return __WASI_EBADF; + return Ok(__WASI_EBADF); } } - __WASI_STDOUT_FILENO | __WASI_STDERR_FILENO => return __WASI_EINVAL, + __WASI_STDOUT_FILENO | __WASI_STDERR_FILENO => return Ok(__WASI_EINVAL), _ => { - let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); - if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_READ) { // TODO: figure out the error to return when lacking rights - return __WASI_EACCES; + return Ok(__WASI_EACCES); } let offset = fd_entry.offset as usize; let inode_idx = fd_entry.inode; - let inode = &mut state.fs.inodes[inode_idx]; - - let bytes_read = match &mut inode.kind { - Kind::File { handle, .. } => { - if let Some(handle) = handle { - handle.seek(std::io::SeekFrom::Start(offset as u64)); - wasi_try!(read_bytes(handle, memory, iovs_arr)) - } else { - return __WASI_EINVAL; + let inode = &inodes.arena[inode_idx]; + + let bytes_read = { + let mut guard = inode.write(); + match guard.deref_mut() { + Kind::File { handle, .. } => { + if let Some(handle) = handle { + wasi_try_ok!( + handle + .seek(std::io::SeekFrom::Start(offset as u64)) + .map_err(map_io_err), + thread + ); + wasi_try_ok!(read_bytes(handle, memory, iovs_arr), thread) + } else { + return Ok(__WASI_EINVAL); + } + } + Kind::Dir { .. } | Kind::Root { .. } => { + // TODO: verify + return Ok(__WASI_EISDIR); + } + Kind::Symlink { .. } => unimplemented!("Symlinks in wasi::fd_read"), + Kind::Buffer { buffer } => { + wasi_try_ok!(read_bytes(&buffer[offset..], memory, iovs_arr), thread) } - } - Kind::Dir { .. } | Kind::Root { .. } => { - // TODO: verify - return __WASI_EISDIR; - } - Kind::Symlink { .. } => unimplemented!("Symlinks in wasi::fd_read"), - Kind::Buffer { buffer } => { - wasi_try!(read_bytes(&buffer[offset..], memory, iovs_arr)) } }; // reborrow - let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); + let mut fd_map = state.fs.fd_map.write().unwrap(); + let fd_entry = wasi_try_ok!(fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); fd_entry.offset += bytes_read as u64; bytes_read } }; - wasi_try_mem!(nread_ref.write(bytes_read)); + wasi_try_mem_ok!(nread_ref.write(bytes_read)); - __WASI_ESUCCESS + Ok(__WASI_ESUCCESS) } /// ### `fd_readdir()` @@ -963,77 +1012,76 @@ pub fn fd_readdir( cookie: __wasi_dircookie_t, bufused: WasmPtr, ) -> __wasi_errno_t { - debug!("wasi::fd_readdir"); - let (memory, mut state) = env.get_memory_and_wasi_state(0); + trace!("wasi::fd_readdir"); + let (memory, state, inodes) = thread.get_memory_and_wasi_state_and_inodes(0); // TODO: figure out how this is supposed to work; // is it supposed to pack the buffer full every time until it can't? or do one at a time? let buf_arr = wasi_try_mem!(buf.slice(memory, buf_len)); let bufused_ref = bufused.deref(memory); - let working_dir = wasi_try!(state.fs.fd_map.get(&fd).ok_or(__WASI_EBADF)); + let working_dir = wasi_try!(state.fs.get_fd(fd)); let mut cur_cookie = cookie; let mut buf_idx = 0; - let entries: Vec<(String, u8, u64)> = match &state.fs.inodes[working_dir.inode].kind { - Kind::Dir { path, entries, .. } => { - debug!("Reading dir {:?}", path); - // TODO: refactor this code - // we need to support multiple calls, - // simple and obviously correct implementation for now: - // maintain consistent order via lexacographic sorting - let fs_info = wasi_try!(wasi_try!(state.fs_read_dir(path)) - .collect::, _>>() - .map_err(|_| __WASI_EIO)); - let mut entry_vec = wasi_try!(fs_info - .into_iter() - .map(|entry| { - let filename = entry.file_name().to_string_lossy().to_string(); - debug!("Getting file: {:?}", filename); - let filetype = virtual_file_type_to_wasi_file_type( - entry.file_type().map_err(|_| __WASI_EIO)?, - ); - Ok(( - filename, filetype, 0, // TODO: inode - )) - }) - .collect::, _>>()); - entry_vec.extend( - entries - .iter() - .filter(|(_, inode)| state.fs.inodes[**inode].is_preopened) - .map(|(name, inode)| { - let entry = &state.fs.inodes[*inode]; - ( - entry.name.to_string(), - entry.stat.st_filetype, - entry.stat.st_ino, - ) - }), - ); - entry_vec.sort_by(|a, b| a.0.cmp(&b.0)); - entry_vec - } - Kind::Root { entries } => { - debug!("Reading root"); - let sorted_entries = { - let mut entry_vec: Vec<(String, Inode)> = - entries.iter().map(|(a, b)| (a.clone(), *b)).collect(); + let entries: Vec<(String, u8, u64)> = { + let guard = inodes.arena[working_dir.inode].read(); + match guard.deref() { + Kind::Dir { path, entries, .. } => { + debug!("Reading dir {:?}", path); + // TODO: refactor this code + // we need to support multiple calls, + // simple and obviously correct implementation for now: + // maintain consistent order via lexacographic sorting + let fs_info = wasi_try!(wasi_try!(state.fs_read_dir(path)) + .collect::, _>>() + .map_err(|e| fs_error_into_wasi_err(e))); + let mut entry_vec = wasi_try!(fs_info + .into_iter() + .map(|entry| { + let filename = entry.file_name().to_string_lossy().to_string(); + debug!("Getting file: {:?}", filename); + let filetype = virtual_file_type_to_wasi_file_type( + entry.file_type().map_err(fs_error_into_wasi_err)?, + ); + Ok(( + filename, filetype, 0, // TODO: inode + )) + }) + .collect::, _>>()); + entry_vec.extend( + entries + .iter() + .filter(|(_, inode)| inodes.arena[**inode].is_preopened) + .map(|(name, inode)| { + let entry = &inodes.arena[*inode]; + let stat = entry.stat.read().unwrap(); + (entry.name.to_string(), stat.st_filetype, stat.st_ino) + }), + ); entry_vec.sort_by(|a, b| a.0.cmp(&b.0)); entry_vec - }; - sorted_entries - .into_iter() - .map(|(name, inode)| { - let entry = &state.fs.inodes[inode]; - ( - format!("/{}", entry.name), - entry.stat.st_filetype, - entry.stat.st_ino, - ) - }) - .collect() + } + Kind::Root { entries } => { + debug!("Reading root"); + let sorted_entries = { + let mut entry_vec: Vec<(String, Inode)> = + entries.iter().map(|(a, b)| (a.clone(), *b)).collect(); + entry_vec.sort_by(|a, b| a.0.cmp(&b.0)); + entry_vec + }; + sorted_entries + .into_iter() + .map(|(name, inode)| { + let entry = &inodes.arena[inode]; + let stat = entry.stat.read().unwrap(); + (format!("/{}", entry.name), stat.st_filetype, stat.st_ino) + }) + .collect() + } + Kind::File { .. } | Kind::Symlink { .. } | Kind::Buffer { .. } => { + return __WASI_ENOTDIR + } } - Kind::File { .. } | Kind::Symlink { .. } | Kind::Buffer { .. } => return __WASI_ENOTDIR, }; for (entry_path_str, wasi_file_type, ino) in entries.iter().skip(cookie as usize) { @@ -1081,16 +1129,19 @@ pub fn fd_readdir( /// Location to copy file descriptor to pub fn fd_renumber(env: &WasiEnv, from: __wasi_fd_t, to: __wasi_fd_t) -> __wasi_errno_t { debug!("wasi::fd_renumber: from={}, to={}", from, to); - let (memory, mut state) = env.get_memory_and_wasi_state(0); - let fd_entry = wasi_try!(state.fs.fd_map.get(&from).ok_or(__WASI_EBADF)); + let (memory, state) = thread.get_memory_and_wasi_state(0); + + let mut fd_map = state.fs.fd_map.write().unwrap(); + let fd_entry = wasi_try!(fd_map.get_mut(&from).ok_or(__WASI_EBADF)); + let new_fd_entry = Fd { // TODO: verify this is correct rights: fd_entry.rights_inheriting, ..*fd_entry }; - state.fs.fd_map.insert(to, new_fd_entry); - state.fs.fd_map.remove(&from); + fd_map.insert(to, new_fd_entry); + fd_map.remove(&from); __WASI_ESUCCESS } @@ -1112,34 +1163,40 @@ pub fn fd_seek( offset: __wasi_filedelta_t, whence: __wasi_whence_t, newoffset: WasmPtr<__wasi_filesize_t>, -) -> __wasi_errno_t { - debug!("wasi::fd_seek: fd={}, offset={}", fd, offset); - let (memory, mut state) = env.get_memory_and_wasi_state(0); +) -> Result<__wasi_errno_t, WasiError> { + trace!("wasi::fd_seek: fd={}, offset={}", fd, offset); + let (memory, state, inodes) = thread.get_memory_and_wasi_state_and_inodes(0); let new_offset_ref = newoffset.deref(memory); - - let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); + let fd_entry = wasi_try_ok!(state.fs.get_fd(fd)); if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_SEEK) { - return __WASI_EACCES; + return Ok(__WASI_EACCES); } // TODO: handle case if fd is a dir? match whence { - __WASI_WHENCE_CUR => fd_entry.offset = (fd_entry.offset as i64 + offset) as u64, + __WASI_WHENCE_CUR => { + let mut fd_map = state.fs.fd_map.write().unwrap(); + let fd_entry = wasi_try_ok!(fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); + fd_entry.offset = (fd_entry.offset as i64 + offset) as u64 + } __WASI_WHENCE_END => { use std::io::SeekFrom; let inode_idx = fd_entry.inode; - match state.fs.inodes[inode_idx].kind { + let mut guard = inodes.arena[inode_idx].write(); + match guard.deref_mut() { Kind::File { ref mut handle, .. } => { if let Some(handle) = handle { - let end = wasi_try!(handle.seek(SeekFrom::End(0)).ok().ok_or(__WASI_EIO)); - // TODO: handle case if fd_entry.offset uses 64 bits of a u64 + let end = + wasi_try_ok!(handle.seek(SeekFrom::End(0)).map_err(map_io_err), thread); - // reborrow - let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); + // TODO: handle case if fd_entry.offset uses 64 bits of a u64 + drop(guard); + let mut fd_map = state.fs.fd_map.write().unwrap(); + let fd_entry = wasi_try_ok!(fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); fd_entry.offset = (end as i64 + offset) as u64; } else { - return __WASI_EINVAL; + return Ok(__WASI_EINVAL); } } Kind::Symlink { .. } => { @@ -1147,23 +1204,27 @@ pub fn fd_seek( } Kind::Dir { .. } | Kind::Root { .. } => { // TODO: check this - return __WASI_EINVAL; + return Ok(__WASI_EINVAL); } Kind::Buffer { .. } => { // seeking buffers probably makes sense // TODO: implement this - return __WASI_EINVAL; + return Ok(__WASI_EINVAL); } } } - __WASI_WHENCE_SET => fd_entry.offset = offset as u64, - _ => return __WASI_EINVAL, + __WASI_WHENCE_SET => { + let mut fd_map = state.fs.fd_map.write().unwrap(); + let fd_entry = wasi_try_ok!(fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); + fd_entry.offset = offset as u64 + } + _ => return Ok(__WASI_EINVAL), } // reborrow - let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); - wasi_try_mem!(new_offset_ref.write(fd_entry.offset)); + let fd_entry = wasi_try_ok!(state.fs.get_fd(fd)); + wasi_try_mem_ok!(new_offset_ref.write(fd_entry.offset)); - __WASI_ESUCCESS + Ok(__WASI_ESUCCESS) } /// ### `fd_sync()` @@ -1178,7 +1239,7 @@ pub fn fd_seek( pub fn fd_sync(env: &WasiEnv, fd: __wasi_fd_t) -> __wasi_errno_t { debug!("wasi::fd_sync"); debug!("=> fd={}", fd); - let (memory, mut state) = env.get_memory_and_wasi_state(0); + let (memory, state, inodes) = thread.get_memory_and_wasi_state_and_inodes(0); let fd_entry = wasi_try!(state.fs.get_fd(fd)); if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_SYNC) { return __WASI_EACCES; @@ -1186,16 +1247,19 @@ pub fn fd_sync(env: &WasiEnv, fd: __wasi_fd_t) -> __wasi_errno_t { let inode = fd_entry.inode; // TODO: implement this for more than files - match &mut state.fs.inodes[inode].kind { - Kind::File { handle, .. } => { - if let Some(h) = handle { - wasi_try!(h.sync_to_disk().map_err(fs_error_into_wasi_err)); - } else { - return __WASI_EINVAL; + { + let mut guard = inodes.arena[inode].write(); + match guard.deref_mut() { + Kind::File { handle, .. } => { + if let Some(h) = handle { + wasi_try!(h.sync_to_disk().map_err(fs_error_into_wasi_err)); + } else { + return __WASI_EINVAL; + } } + Kind::Root { .. } | Kind::Dir { .. } => return __WASI_EISDIR, + Kind::Buffer { .. } | Kind::Symlink { .. } => return __WASI_EINVAL, } - Kind::Root { .. } | Kind::Dir { .. } => return __WASI_EISDIR, - Kind::Buffer { .. } | Kind::Symlink { .. } => return __WASI_EINVAL, } __WASI_ESUCCESS @@ -1215,10 +1279,10 @@ pub fn fd_tell( offset: WasmPtr<__wasi_filesize_t>, ) -> __wasi_errno_t { debug!("wasi::fd_tell"); - let (memory, mut state) = env.get_memory_and_wasi_state(0); + let (memory, state) = thread.get_memory_and_wasi_state(0); let offset_ref = offset.deref(memory); - let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); + let fd_entry = wasi_try!(state.fs.get_fd(fd)); if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_TELL) { return __WASI_EACCES; @@ -1249,81 +1313,92 @@ pub fn fd_write( iovs: WasmPtr<__wasi_ciovec_t>, iovs_len: u32, nwritten: WasmPtr, -) -> __wasi_errno_t { - // If we are writing to stdout or stderr - // we skip debug to not pollute the stdout/err - // and do debugging happily after :) - if fd != __WASI_STDOUT_FILENO && fd != __WASI_STDERR_FILENO { - debug!("wasi::fd_write: fd={}", fd); - } else { - trace!("wasi::fd_write: fd={}", fd); - } - let (memory, mut state) = env.get_memory_and_wasi_state(0); - let iovs_arr = wasi_try_mem!(iovs.slice(memory, iovs_len)); +) -> Result<__wasi_errno_t, WasiError> { + trace!("wasi::fd_write: fd={}", fd); + let (memory, state, inodes) = thread.get_memory_and_wasi_state_and_inodes(0); + let iovs_arr = wasi_try_mem_ok!(iovs.slice(memory, iovs_len)); let nwritten_ref = nwritten.deref(memory); + let fd_entry = wasi_try_ok!(state.fs.get_fd(fd)); let bytes_written = match fd { - __WASI_STDIN_FILENO => return __WASI_EINVAL, + __WASI_STDIN_FILENO => return Ok(__WASI_EINVAL), __WASI_STDOUT_FILENO => { - if let Some(ref mut stdout) = - wasi_try!(state.fs.stdout_mut().map_err(fs_error_into_wasi_err)) - { - wasi_try!(write_bytes(stdout, memory, iovs_arr)) + let mut guard = wasi_try_ok!( + inodes + .stdout_mut(&state.fs.fd_map) + .map_err(fs_error_into_wasi_err), + thread + ); + if let Some(ref mut stdout) = guard.deref_mut() { + wasi_try_ok!(write_bytes(stdout, memory, iovs_arr), thread) } else { - return __WASI_EBADF; + return Ok(__WASI_EBADF); } } __WASI_STDERR_FILENO => { - if let Some(ref mut stderr) = - wasi_try!(state.fs.stderr_mut().map_err(fs_error_into_wasi_err)) - { - wasi_try!(write_bytes(stderr, memory, iovs_arr)) + let mut guard = wasi_try_ok!( + inodes + .stderr_mut(&state.fs.fd_map) + .map_err(fs_error_into_wasi_err), + thread + ); + if let Some(ref mut stderr) = guard.deref_mut() { + wasi_try_ok!(write_bytes(stderr, memory, iovs_arr), thread) } else { - return __WASI_EBADF; + return Ok(__WASI_EBADF); } } _ => { - let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); - if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_WRITE) { - return __WASI_EACCES; + return Ok(__WASI_EACCES); } let offset = fd_entry.offset as usize; let inode_idx = fd_entry.inode; - let inode = &mut state.fs.inodes[inode_idx]; - - let bytes_written = match &mut inode.kind { - Kind::File { handle, .. } => { - if let Some(handle) = handle { - handle.seek(std::io::SeekFrom::Start(offset as u64)); - wasi_try!(write_bytes(handle, memory, iovs_arr)) - } else { - return __WASI_EINVAL; + let inode = &inodes.arena[inode_idx]; + + let bytes_written = { + let mut guard = inode.write(); + match guard.deref_mut() { + Kind::File { handle, .. } => { + if let Some(handle) = handle { + wasi_try_ok!( + handle + .seek(std::io::SeekFrom::Start(offset as u64)) + .map_err(map_io_err), + thread + ); + wasi_try_ok!(write_bytes(handle, memory, iovs_arr), thread) + } else { + return Ok(__WASI_EINVAL); + } + } + Kind::Dir { .. } | Kind::Root { .. } => { + // TODO: verify + return Ok(__WASI_EISDIR); + } + Kind::Symlink { .. } => unimplemented!("Symlinks in wasi::fd_write"), + Kind::Buffer { buffer } => { + wasi_try_ok!(write_bytes(&mut buffer[offset..], memory, iovs_arr), thread) } - } - Kind::Dir { .. } | Kind::Root { .. } => { - // TODO: verify - return __WASI_EISDIR; - } - Kind::Symlink { .. } => unimplemented!("Symlinks in wasi::fd_write"), - Kind::Buffer { buffer } => { - wasi_try!(write_bytes(&mut buffer[offset..], memory, iovs_arr)) } }; // reborrow - let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); - fd_entry.offset += bytes_written as u64; - wasi_try!(state.fs.filestat_resync_size(fd)); + { + let mut fd_map = state.fs.fd_map.write().unwrap(); + let fd_entry = wasi_try_ok!(fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); + fd_entry.offset += bytes_written as u64; + } + wasi_try_ok!(state.fs.filestat_resync_size(inodes.deref(), fd), thread); bytes_written } }; - wasi_try_mem!(nwritten_ref.write(bytes_written)); + wasi_try_mem_ok!(nwritten_ref.write(bytes_written)); - __WASI_ESUCCESS + Ok(__WASI_ESUCCESS) } /// ### `path_create_directory()` @@ -1346,11 +1421,14 @@ pub fn path_create_directory( path_len: u32, ) -> __wasi_errno_t { debug!("wasi::path_create_directory"); - let (memory, mut state) = env.get_memory_and_wasi_state(0); + let (memory, state, mut inodes) = thread.get_memory_and_wasi_state_and_inodes_mut(0); let working_dir = wasi_try!(state.fs.get_fd(fd)); - if let Kind::Root { .. } = &state.fs.inodes[working_dir.inode].kind { - return __WASI_EACCES; + { + let guard = inodes.arena[working_dir.inode].read(); + if let Kind::Root { .. } = guard.deref() { + return __WASI_EACCES; + } } if !has_rights(working_dir.rights, __WASI_RIGHT_PATH_CREATE_DIRECTORY) { return __WASI_EACCES; @@ -1377,7 +1455,8 @@ pub fn path_create_directory( let mut cur_dir_inode = working_dir.inode; for comp in &path_vec { debug!("Creating dir {}", comp); - match &mut state.fs.inodes[cur_dir_inode].kind { + let mut guard = inodes.arena[cur_dir_inode].write(); + match guard.deref_mut() { Kind::Dir { ref mut entries, path, @@ -1397,11 +1476,22 @@ pub fn path_create_directory( cur_dir_inode = *child; } else { let mut adjusted_path = path.clone(); + drop(guard); + // TODO: double check this doesn't risk breaking the sandbox adjusted_path.push(comp); - if adjusted_path.exists() && !adjusted_path.is_dir() { - return __WASI_ENOTDIR; - } else if !adjusted_path.exists() { + if let Ok(adjusted_path_stat) = path_filestat_get_internal( + memory, + state, + inodes.deref_mut(), + fd, + 0, + &adjusted_path.to_string_lossy(), + ) { + if adjusted_path_stat.st_filetype != __WASI_FILETYPE_DIRECTORY { + return __WASI_ENOTDIR; + } + } else { wasi_try!(state.fs_create_dir(&adjusted_path)); } let kind = Kind::Dir { @@ -1409,13 +1499,22 @@ pub fn path_create_directory( path: adjusted_path, entries: Default::default(), }; - let new_inode = wasi_try!(state.fs.create_inode(kind, false, comp.to_string())); + let new_inode = wasi_try!(state.fs.create_inode( + inodes.deref_mut(), + kind, + false, + comp.to_string() + )); + // reborrow to insert - if let Kind::Dir { - ref mut entries, .. - } = &mut state.fs.inodes[cur_dir_inode].kind { - entries.insert(comp.to_string(), new_inode); + let mut guard = inodes.arena[cur_dir_inode].write(); + if let Kind::Dir { + ref mut entries, .. + } = guard.deref_mut() + { + entries.insert(comp.to_string(), new_inode); + } } cur_dir_inode = new_inode; } @@ -1451,36 +1550,72 @@ pub fn path_filestat_get( buf: WasmPtr<__wasi_filestat_t>, ) -> __wasi_errno_t { debug!("wasi::path_filestat_get"); - let (memory, mut state) = env.get_memory_and_wasi_state(0); + let (memory, state, mut inodes) = thread.get_memory_and_wasi_state_and_inodes_mut(0); - let root_dir = wasi_try!(state.fs.get_fd(fd)); - - if !has_rights(root_dir.rights, __WASI_RIGHT_PATH_FILESTAT_GET) { - return __WASI_EACCES; - } let path_string = unsafe { get_input_str!(memory, path, path_len) }; - debug!("=> base_fd: {}, path: {}", fd, &path_string); - - let file_inode = wasi_try!(state.fs.get_inode_at_path( + let stat = wasi_try!(path_filestat_get_internal( + memory, + state, + inodes.deref_mut(), fd, - &path_string, - flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0, + flags, + &path_string )); - let stat = if state.fs.inodes[file_inode].is_preopened { - state.fs.inodes[file_inode].stat - } else { - wasi_try!(state - .fs - .get_stat_for_kind(&state.fs.inodes[file_inode].kind) - .ok_or(__WASI_EIO)) - }; wasi_try_mem!(buf.deref(memory).write(stat)); __WASI_ESUCCESS } +/// ### `path_filestat_get()` +/// Access metadata about a file or directory +/// Inputs: +/// - `__wasi_fd_t fd` +/// The directory that `path` is relative to +/// - `__wasi_lookupflags_t flags` +/// Flags to control how `path` is understood +/// - `const char *path` +/// String containing the file path +/// - `u32 path_len` +/// The length of the `path` string +/// Output: +/// - `__wasi_file_stat_t *buf` +/// The location where the metadata will be stored +pub fn path_filestat_get_internal( + memory: &Memory, + state: &WasiState, + inodes: &mut crate::WasiInodes, + fd: __wasi_fd_t, + flags: __wasi_lookupflags_t, + path_string: &str, +) -> Result<__wasi_filestat_t, __wasi_errno_t> { + let root_dir = state.fs.get_fd(fd)?; + + if !has_rights(root_dir.rights, __WASI_RIGHT_PATH_FILESTAT_GET) { + return Err(__WASI_EACCES); + } + debug!("=> base_fd: {}, path: {}", fd, path_string); + + let file_inode = state.fs.get_inode_at_path( + inodes, + fd, + path_string, + flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0, + )?; + if inodes.arena[file_inode].is_preopened { + Ok(inodes.arena[file_inode] + .stat + .read() + .unwrap() + .deref() + .clone()) + } else { + let guard = inodes.arena[file_inode].read(); + state.fs.get_stat_for_kind(inodes.deref(), guard.deref()) + } +} + /// ### `path_filestat_set_times()` /// Update time metadata on a file or directory /// Inputs: @@ -1509,7 +1644,7 @@ pub fn path_filestat_set_times( fst_flags: __wasi_fstflags_t, ) -> __wasi_errno_t { debug!("wasi::path_filestat_set_times"); - let (memory, mut state) = env.get_memory_and_wasi_state(0); + let (memory, state, mut inodes) = thread.get_memory_and_wasi_state_and_inodes_mut(0); let fd_entry = wasi_try!(state.fs.get_fd(fd)); let fd_inode = fd_entry.inode; if !has_rights(fd_entry.rights, __WASI_RIGHT_PATH_FILESTAT_SET_TIMES) { @@ -1526,16 +1661,17 @@ pub fn path_filestat_set_times( debug!("=> base_fd: {}, path: {}", fd, &path_string); let file_inode = wasi_try!(state.fs.get_inode_at_path( + inodes.deref_mut(), fd, &path_string, flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0, )); - let stat = wasi_try!(state - .fs - .get_stat_for_kind(&state.fs.inodes[file_inode].kind) - .ok_or(__WASI_EIO)); + let stat = { + let guard = inodes.arena[file_inode].read(); + wasi_try!(state.fs.get_stat_for_kind(inodes.deref(), guard.deref())) + }; - let inode = &mut state.fs.inodes[fd_inode]; + let inode = &inodes.arena[fd_inode]; if fst_flags & __WASI_FILESTAT_SET_ATIM != 0 || fst_flags & __WASI_FILESTAT_SET_ATIM_NOW != 0 { let time_to_set = if fst_flags & __WASI_FILESTAT_SET_ATIM != 0 { @@ -1543,7 +1679,7 @@ pub fn path_filestat_set_times( } else { wasi_try!(get_current_time_in_nanos()) }; - inode.stat.st_atim = time_to_set; + inode.stat.write().unwrap().st_atim = time_to_set; } if fst_flags & __WASI_FILESTAT_SET_MTIM != 0 || fst_flags & __WASI_FILESTAT_SET_MTIM_NOW != 0 { let time_to_set = if fst_flags & __WASI_FILESTAT_SET_MTIM != 0 { @@ -1551,7 +1687,7 @@ pub fn path_filestat_set_times( } else { wasi_try!(get_current_time_in_nanos()) }; - inode.stat.st_mtim = time_to_set; + inode.stat.write().unwrap().st_mtim = time_to_set; } __WASI_ESUCCESS @@ -1588,7 +1724,7 @@ pub fn path_link( if old_flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0 { debug!(" - will follow symlinks when opening path"); } - let (memory, mut state) = env.get_memory_and_wasi_state(0); + let (memory, state, mut inodes) = thread.get_memory_and_wasi_state_and_inodes_mut(0); let old_path_str = unsafe { get_input_str!(memory, old_path, old_path_len) }; let new_path_str = unsafe { get_input_str!(memory, new_path, new_path_len) }; let source_fd = wasi_try!(state.fs.get_fd(old_fd)); @@ -1605,30 +1741,39 @@ pub fn path_link( } let source_inode = wasi_try!(state.fs.get_inode_at_path( + inodes.deref_mut(), old_fd, &old_path_str, old_flags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0, )); let target_path_arg = std::path::PathBuf::from(&new_path_str); - let (target_parent_inode, new_entry_name) = - wasi_try!(state - .fs - .get_parent_inode_at_path(new_fd, &target_path_arg, false)); + let (target_parent_inode, new_entry_name) = wasi_try!(state.fs.get_parent_inode_at_path( + inodes.deref_mut(), + new_fd, + &target_path_arg, + false + )); - if state.fs.inodes[source_inode].stat.st_nlink == __wasi_linkcount_t::max_value() { + if inodes.arena[source_inode].stat.write().unwrap().st_nlink == __wasi_linkcount_t::max_value() + { return __WASI_EMLINK; } - match &mut state.fs.inodes[target_parent_inode].kind { - Kind::Dir { entries, .. } => { - if entries.contains_key(&new_entry_name) { - return __WASI_EEXIST; + { + let mut guard = inodes.arena[target_parent_inode].write(); + match guard.deref_mut() { + Kind::Dir { entries, .. } => { + if entries.contains_key(&new_entry_name) { + return __WASI_EEXIST; + } + entries.insert(new_entry_name, source_inode); + } + Kind::Root { .. } => return __WASI_EINVAL, + Kind::File { .. } | Kind::Symlink { .. } | Kind::Buffer { .. } => { + return __WASI_ENOTDIR } - entries.insert(new_entry_name, source_inode); } - Kind::Root { .. } => return __WASI_EINVAL, - Kind::File { .. } | Kind::Symlink { .. } | Kind::Buffer { .. } => return __WASI_ENOTDIR, } - state.fs.inodes[source_inode].stat.st_nlink += 1; + inodes.arena[source_inode].stat.write().unwrap().st_nlink += 1; __WASI_ESUCCESS } @@ -1673,7 +1818,7 @@ pub fn path_open( if dirflags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0 { debug!(" - will follow symlinks when opening path"); } - let (memory, mut state) = env.get_memory_and_wasi_state(0); + let (memory, state, mut inodes) = thread.get_memory_and_wasi_state_and_inodes_mut(0); /* TODO: find actual upper bound on name size (also this is a path, not a name :think-fish:) */ if path_len > 1024 * 1024 { return __WASI_ENAMETOOLONG; @@ -1700,6 +1845,7 @@ pub fn path_open( let path_arg = std::path::PathBuf::from(&path_string); let maybe_inode = state.fs.get_inode_at_path( + inodes.deref_mut(), dirfd, &path_string, dirflags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0, @@ -1713,7 +1859,8 @@ pub fn path_open( let mut open_options = state.fs_new_open_options(); let inode = if let Ok(inode) = maybe_inode { // Happy path, we found the file we're trying to open - match &mut state.fs.inodes[inode].kind { + let mut guard = inodes.arena[inode].write(); + match guard.deref_mut() { Kind::File { ref mut handle, path, @@ -1728,9 +1875,10 @@ pub fn path_open( if o_flags & __WASI_O_DIRECTORY != 0 { return __WASI_ENOTDIR; } - if o_flags & __WASI_O_EXCL != 0 && path.exists() { + if o_flags & __WASI_O_EXCL != 0 { return __WASI_EEXIST; } + let write_permission = adjusted_rights & __WASI_RIGHT_FD_WRITE != 0; // append, truncate, and create all require the permission to write let (append_permission, truncate_permission, create_permission) = @@ -1765,12 +1913,7 @@ pub fn path_open( .map_err(fs_error_into_wasi_err))); } Kind::Buffer { .. } => unimplemented!("wasi::path_open for Buffer type files"), - Kind::Dir { .. } | Kind::Root { .. } => { - // TODO: adjust these to be correct - if o_flags & __WASI_O_EXCL != 0 && path_arg.exists() { - return __WASI_EEXIST; - } - } + Kind::Dir { .. } | Kind::Root { .. } => {} Kind::Symlink { base_po_dir, path_to_symlink, @@ -1793,18 +1936,22 @@ pub fn path_open( // strip end file name let (parent_inode, new_entity_name) = wasi_try!(state.fs.get_parent_inode_at_path( + inodes.deref_mut(), dirfd, &path_arg, dirflags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0 )); - let new_file_host_path = match &state.fs.inodes[parent_inode].kind { - Kind::Dir { path, .. } => { - let mut new_path = path.clone(); - new_path.push(&new_entity_name); - new_path + let new_file_host_path = { + let guard = inodes.arena[parent_inode].read(); + match guard.deref() { + Kind::Dir { path, .. } => { + let mut new_path = path.clone(); + new_path.push(&new_entity_name); + new_path + } + Kind::Root { .. } => return __WASI_EACCES, + _ => return __WASI_EINVAL, } - Kind::Root { .. } => return __WASI_EACCES, - _ => return __WASI_EINVAL, }; // once we got the data we need from the parent, we lookup the host file // todo: extra check that opening with write access is okay @@ -1832,14 +1979,22 @@ pub fn path_open( path: new_file_host_path, fd: None, }; - wasi_try!(state.fs.create_inode(kind, false, new_entity_name.clone())) + wasi_try!(state.fs.create_inode( + inodes.deref_mut(), + kind, + false, + new_entity_name.clone() + )) }; - if let Kind::Dir { - ref mut entries, .. - } = &mut state.fs.inodes[parent_inode].kind { - entries.insert(new_entity_name, new_inode); + let mut guard = inodes.arena[parent_inode].write(); + if let Kind::Dir { + ref mut entries, .. + } = guard.deref_mut() + { + entries.insert(new_entity_name, new_inode); + } } new_inode @@ -1848,10 +2003,9 @@ pub fn path_open( } }; - debug!( - "inode {:?} value {:#?} found!", - inode, state.fs.inodes[inode] - ); + { + debug!("inode {:?} value {:#?} found!", inode, inodes.arena[inode]); + } // TODO: check and reduce these // TODO: ensure a mutable fd to root can never be opened @@ -1895,30 +2049,36 @@ pub fn path_readlink( buf_used: WasmPtr, ) -> __wasi_errno_t { debug!("wasi::path_readlink"); - let (memory, mut state) = env.get_memory_and_wasi_state(0); + let (memory, state, mut inodes) = thread.get_memory_and_wasi_state_and_inodes_mut(0); - let base_dir = wasi_try!(state.fs.fd_map.get(&dir_fd).ok_or(__WASI_EBADF)); + let base_dir = wasi_try!(state.fs.get_fd(dir_fd)); if !has_rights(base_dir.rights, __WASI_RIGHT_PATH_READLINK) { return __WASI_EACCES; } let path_str = unsafe { get_input_str!(memory, path, path_len) }; - let inode = wasi_try!(state.fs.get_inode_at_path(dir_fd, &path_str, false)); - - if let Kind::Symlink { relative_path, .. } = &state.fs.inodes[inode].kind { - let rel_path_str = relative_path.to_string_lossy(); - debug!("Result => {:?}", rel_path_str); - let bytes = rel_path_str.as_bytes(); - if bytes.len() >= buf_len as usize { - return __WASI_EOVERFLOW; - } + let inode = wasi_try!(state + .fs + .get_inode_at_path(inodes.deref_mut(), dir_fd, &path_str, false)); - let out = wasi_try_mem!(buf.slice(memory, bytes.len() as u32)); - wasi_try_mem!(out.write_slice(bytes)); - // should we null terminate this? + { + let guard = inodes.arena[inode].read(); + if let Kind::Symlink { relative_path, .. } = guard.deref() { + let rel_path_str = relative_path.to_string_lossy(); + debug!("Result => {:?}", rel_path_str); + let bytes = rel_path_str.bytes(); + if bytes.len() >= buf_len as usize { + return __WASI_EOVERFLOW; + } + let bytes: Vec<_> = bytes.collect(); - wasi_try_mem!(buf_used.deref(memory).write(bytes.len() as u32)); - } else { - return __WASI_EINVAL; + let out = wasi_try_mem!(buf.slice(memory, bytes.len() as u32)); + wasi_try_mem!(out.write_slice(&bytes[..])); + // should we null terminate this? + + wasi_try_mem!(buf_used.deref(memory).write(bytes.len() as u32)); + } else { + return __WASI_EINVAL; + } } __WASI_ESUCCESS @@ -1933,47 +2093,58 @@ pub fn path_remove_directory( ) -> __wasi_errno_t { // TODO check if fd is a dir, ensure it's within sandbox, etc. debug!("wasi::path_remove_directory"); - let (memory, mut state) = env.get_memory_and_wasi_state(0); + let (memory, state, mut inodes) = thread.get_memory_and_wasi_state_and_inodes_mut(0); - let base_dir = wasi_try!(state.fs.fd_map.get(&fd), __WASI_EBADF); + let base_dir = wasi_try!(state.fs.get_fd(fd)); let path_str = unsafe { get_input_str!(memory, path, path_len) }; - let inode = wasi_try!(state.fs.get_inode_at_path(fd, &path_str, false)); - let (parent_inode, childs_name) = - wasi_try!(state - .fs - .get_parent_inode_at_path(fd, std::path::Path::new(&path_str), false)); + let inode = wasi_try!(state + .fs + .get_inode_at_path(inodes.deref_mut(), fd, &path_str, false)); + let (parent_inode, childs_name) = wasi_try!(state.fs.get_parent_inode_at_path( + inodes.deref_mut(), + fd, + std::path::Path::new(&path_str), + false + )); - let host_path_to_remove = match &state.fs.inodes[inode].kind { - Kind::Dir { entries, path, .. } => { - if !entries.is_empty() || wasi_try!(state.fs_read_dir(path)).count() != 0 { - return __WASI_ENOTEMPTY; + let host_path_to_remove = { + let guard = inodes.arena[inode].read(); + match guard.deref() { + Kind::Dir { entries, path, .. } => { + if !entries.is_empty() || wasi_try!(state.fs_read_dir(path)).count() != 0 { + return __WASI_ENOTEMPTY; + } + path.clone() } - path.clone() + Kind::Root { .. } => return __WASI_EACCES, + _ => return __WASI_ENOTDIR, } - Kind::Root { .. } => return __WASI_EACCES, - _ => return __WASI_ENOTDIR, }; - match &mut state.fs.inodes[parent_inode].kind { - Kind::Dir { - ref mut entries, .. - } => { - let removed_inode = wasi_try!(entries.remove(&childs_name).ok_or(__WASI_EINVAL)); - // TODO: make this a debug assert in the future - assert!(inode == removed_inode); + { + let mut guard = inodes.arena[parent_inode].write(); + match guard.deref_mut() { + Kind::Dir { + ref mut entries, .. + } => { + let removed_inode = wasi_try!(entries.remove(&childs_name).ok_or(__WASI_EINVAL)); + // TODO: make this a debug assert in the future + assert!(inode == removed_inode); + } + Kind::Root { .. } => return __WASI_EACCES, + _ => unreachable!( + "Internal logic error in wasi::path_remove_directory, parent is not a directory" + ), } - Kind::Root { .. } => return __WASI_EACCES, - _ => unreachable!( - "Internal logic error in wasi::path_remove_directory, parent is not a directory" - ), } if state.fs_remove_dir(host_path_to_remove).is_err() { // reinsert to prevent FS from being in bad state + let mut guard = inodes.arena[parent_inode].write(); if let Kind::Dir { ref mut entries, .. - } = &mut state.fs.inodes[parent_inode].kind + } = guard.deref_mut() { entries.insert(childs_name, inode); } @@ -2012,7 +2183,7 @@ pub fn path_rename( "wasi::path_rename: old_fd = {}, new_fd = {}", old_fd, new_fd ); - let (memory, mut state) = env.get_memory_and_wasi_state(0); + let (memory, state, mut inodes) = thread.get_memory_and_wasi_state_and_inodes_mut(0); let source_str = unsafe { get_input_str!(memory, old_path, old_path_len) }; let source_path = std::path::Path::new(&source_str); let target_str = unsafe { get_input_str!(memory, new_path, new_path_len) }; @@ -2031,84 +2202,110 @@ pub fn path_rename( } let (source_parent_inode, source_entry_name) = - wasi_try!(state.fs.get_parent_inode_at_path(old_fd, source_path, true)); + wasi_try!(state + .fs + .get_parent_inode_at_path(inodes.deref_mut(), old_fd, source_path, true)); let (target_parent_inode, target_entry_name) = - wasi_try!(state.fs.get_parent_inode_at_path(new_fd, target_path, true)); + wasi_try!(state + .fs + .get_parent_inode_at_path(inodes.deref_mut(), new_fd, target_path, true)); - let host_adjusted_target_path = match &state.fs.inodes[target_parent_inode].kind { - Kind::Dir { entries, path, .. } => { - if entries.contains_key(&target_entry_name) { - return __WASI_EEXIST; + let host_adjusted_target_path = { + let guard = inodes.arena[target_parent_inode].read(); + match guard.deref() { + Kind::Dir { entries, path, .. } => { + if entries.contains_key(&target_entry_name) { + return __WASI_EEXIST; + } + let mut out_path = path.clone(); + out_path.push(std::path::Path::new(&target_entry_name)); + out_path + } + Kind::Root { .. } => return __WASI_ENOTCAPABLE, + Kind::Symlink { .. } | Kind::File { .. } | Kind::Buffer { .. } => { + unreachable!("Fatal internal logic error: parent of inode is not a directory") } - let mut out_path = path.clone(); - out_path.push(std::path::Path::new(&target_entry_name)); - out_path - } - Kind::Root { .. } => return __WASI_ENOTCAPABLE, - Kind::Symlink { .. } | Kind::File { .. } | Kind::Buffer { .. } => { - unreachable!("Fatal internal logic error: parent of inode is not a directory") } }; - let source_entry = match &mut state.fs.inodes[source_parent_inode].kind { - Kind::Dir { entries, .. } => { - wasi_try!(entries.remove(&source_entry_name), __WASI_ENOENT) - } - Kind::Root { .. } => return __WASI_ENOTCAPABLE, - Kind::Symlink { .. } | Kind::File { .. } | Kind::Buffer { .. } => { - unreachable!("Fatal internal logic error: parent of inode is not a directory") + let source_entry = { + let mut guard = inodes.arena[source_parent_inode].write(); + match guard.deref_mut() { + Kind::Dir { entries, .. } => { + wasi_try!(entries.remove(&source_entry_name).ok_or(__WASI_ENOENT)) + } + Kind::Root { .. } => return __WASI_ENOTCAPABLE, + Kind::Symlink { .. } | Kind::File { .. } | Kind::Buffer { .. } => { + unreachable!("Fatal internal logic error: parent of inode is not a directory") + } } }; - match &mut state.fs.inodes[source_entry].kind { - Kind::File { - handle, ref path, .. - } => { - // TODO: investigate why handle is not always there, it probably should be. - // My best guess is the fact that a handle means currently open and a path - // just means reference to host file on disk. But ideally those concepts - // could just be unified even if there's a `Box` which just - // implements the logic of "I'm not actually a file, I'll try to be as needed". - let result = if let Some(h) = handle { - state.fs_rename(&source_path, &host_adjusted_target_path) - } else { - let path_clone = path.clone(); - let out = state.fs_rename(&path_clone, &host_adjusted_target_path); - if let Kind::File { ref mut path, .. } = &mut state.fs.inodes[source_entry].kind { - *path = host_adjusted_target_path; + { + let mut guard = inodes.arena[source_entry].write(); + match guard.deref_mut() { + Kind::File { + handle, ref path, .. + } => { + // TODO: investigate why handle is not always there, it probably should be. + // My best guess is the fact that a handle means currently open and a path + // just means reference to host file on disk. But ideally those concepts + // could just be unified even if there's a `Box` which just + // implements the logic of "I'm not actually a file, I'll try to be as needed". + let result = if let Some(h) = handle { + drop(guard); + state.fs_rename(&source_path, &host_adjusted_target_path) } else { - unreachable!() + let path_clone = path.clone(); + drop(guard); + let out = state.fs_rename(&path_clone, &host_adjusted_target_path); + { + let mut guard = inodes.arena[source_entry].write(); + if let Kind::File { ref mut path, .. } = guard.deref_mut() { + *path = host_adjusted_target_path; + } else { + unreachable!() + } + } + out + }; + // if the above operation failed we have to revert the previous change and then fail + if let Err(e) = result.clone() { + let mut guard = inodes.arena[source_parent_inode].write(); + if let Kind::Dir { entries, .. } = guard.deref_mut() { + entries.insert(source_entry_name, source_entry); + return e; + } } - out - }; - // if the above operation failed we have to revert the previous change and then fail - if let Err(e) = result { - if let Kind::Dir { entries, .. } = &mut state.fs.inodes[source_parent_inode].kind { - entries.insert(source_entry_name, source_entry); + } + Kind::Dir { ref path, .. } => { + let cloned_path = path.clone(); + if let Err(e) = state.fs_rename(cloned_path, &host_adjusted_target_path) { return e; } + { + drop(guard); + let mut guard = inodes.arena[source_entry].write(); + if let Kind::Dir { path, .. } = guard.deref_mut() { + *path = host_adjusted_target_path; + } + } } + Kind::Buffer { .. } => {} + Kind::Symlink { .. } => {} + Kind::Root { .. } => unreachable!("The root can not be moved"), } - Kind::Dir { ref path, .. } => { - let cloned_path = path.clone(); - if let Err(e) = state.fs_rename(cloned_path, &host_adjusted_target_path) { - return e; - } - if let Kind::Dir { path, .. } = &mut state.fs.inodes[source_entry].kind { - *path = host_adjusted_target_path; - } - } - Kind::Buffer { .. } => {} - Kind::Symlink { .. } => {} - Kind::Root { .. } => unreachable!("The root can not be moved"), } - if let Kind::Dir { entries, .. } = &mut state.fs.inodes[target_parent_inode].kind { - let result = entries.insert(target_entry_name, source_entry); - assert!( - result.is_none(), - "Fatal error: race condition on filesystem detected or internal logic error" - ); + { + let mut guard = inodes.arena[target_parent_inode].write(); + if let Kind::Dir { entries, .. } = guard.deref_mut() { + let result = entries.insert(target_entry_name, source_entry); + assert!( + result.is_none(), + "Fatal error: race condition on filesystem detected or internal logic error" + ); + } } __WASI_ESUCCESS @@ -2136,7 +2333,7 @@ pub fn path_symlink( new_path_len: u32, ) -> __wasi_errno_t { debug!("wasi::path_symlink"); - let (memory, mut state) = env.get_memory_and_wasi_state(0); + let (memory, state, mut inodes) = thread.get_memory_and_wasi_state_and_inodes_mut(0); let old_path_str = unsafe { get_input_str!(memory, old_path, old_path_len) }; let new_path_str = unsafe { get_input_str!(memory, new_path, new_path_len) }; let base_fd = wasi_try!(state.fs.get_fd(fd)); @@ -2146,23 +2343,34 @@ pub fn path_symlink( // get the depth of the parent + 1 (UNDER INVESTIGATION HMMMMMMMM THINK FISH ^ THINK FISH) let old_path_path = std::path::Path::new(&old_path_str); - let (source_inode, _) = wasi_try!(state.fs.get_parent_inode_at_path(fd, old_path_path, true)); - let depth = wasi_try!(state.fs.path_depth_from_fd(fd, source_inode)) - 1; + let (source_inode, _) = + wasi_try!(state + .fs + .get_parent_inode_at_path(inodes.deref_mut(), fd, old_path_path, true)); + let depth = wasi_try!(state + .fs + .path_depth_from_fd(inodes.deref(), fd, source_inode)) + - 1; let new_path_path = std::path::Path::new(&new_path_str); let (target_parent_inode, entry_name) = - wasi_try!(state.fs.get_parent_inode_at_path(fd, new_path_path, true)); + wasi_try!(state + .fs + .get_parent_inode_at_path(inodes.deref_mut(), fd, new_path_path, true)); // short circuit if anything is wrong, before we create an inode - match &state.fs.inodes[target_parent_inode].kind { - Kind::Dir { entries, .. } => { - if entries.contains_key(&entry_name) { - return __WASI_EEXIST; + { + let guard = inodes.arena[target_parent_inode].read(); + match guard.deref() { + Kind::Dir { entries, .. } => { + if entries.contains_key(&entry_name) { + return __WASI_EEXIST; + } + } + Kind::Root { .. } => return __WASI_ENOTCAPABLE, + Kind::File { .. } | Kind::Symlink { .. } | Kind::Buffer { .. } => { + unreachable!("get_parent_inode_at_path returned something other than a Dir or Root") } - } - Kind::Root { .. } => return __WASI_ENOTCAPABLE, - Kind::File { .. } | Kind::Symlink { .. } | Kind::Buffer { .. } => { - unreachable!("get_parent_inode_at_path returned something other than a Dir or Root") } } @@ -2183,15 +2391,21 @@ pub fn path_symlink( path_to_symlink: std::path::PathBuf::from(new_path_str), relative_path, }; - let new_inode = state - .fs - .create_inode_with_default_stat(kind, false, entry_name.clone()); + let new_inode = state.fs.create_inode_with_default_stat( + inodes.deref_mut(), + kind, + false, + entry_name.clone(), + ); - if let Kind::Dir { - ref mut entries, .. - } = &mut state.fs.inodes[target_parent_inode].kind { - entries.insert(entry_name, new_inode); + let mut guard = inodes.arena[target_parent_inode].write(); + if let Kind::Dir { + ref mut entries, .. + } = guard.deref_mut() + { + entries.insert(entry_name, new_inode); + } } __WASI_ESUCCESS @@ -2213,74 +2427,89 @@ pub fn path_unlink_file( path_len: u32, ) -> __wasi_errno_t { debug!("wasi::path_unlink_file"); - let (memory, mut state) = env.get_memory_and_wasi_state(0); + let (memory, state, mut inodes) = thread.get_memory_and_wasi_state_and_inodes_mut(0); - let base_dir = wasi_try!(state.fs.fd_map.get(&fd).ok_or(__WASI_EBADF)); + let base_dir = wasi_try!(state.fs.get_fd(fd)); if !has_rights(base_dir.rights, __WASI_RIGHT_PATH_UNLINK_FILE) { return __WASI_EACCES; } let path_str = unsafe { get_input_str!(memory, path, path_len) }; debug!("Requested file: {}", path_str); - let inode = wasi_try!(state.fs.get_inode_at_path(fd, &path_str, false)); - let (parent_inode, childs_name) = - wasi_try!(state - .fs - .get_parent_inode_at_path(fd, std::path::Path::new(&path_str), false)); + let inode = wasi_try!(state + .fs + .get_inode_at_path(inodes.deref_mut(), fd, &path_str, false)); + let (parent_inode, childs_name) = wasi_try!(state.fs.get_parent_inode_at_path( + inodes.deref_mut(), + fd, + std::path::Path::new(&path_str), + false + )); - let removed_inode = match &mut state.fs.inodes[parent_inode].kind { - Kind::Dir { - ref mut entries, .. - } => { - let removed_inode = wasi_try!(entries.remove(&childs_name).ok_or(__WASI_EINVAL)); - // TODO: make this a debug assert in the future - assert!(inode == removed_inode); - debug_assert!(state.fs.inodes[inode].stat.st_nlink > 0); - removed_inode + let removed_inode = { + let mut guard = inodes.arena[parent_inode].write(); + match guard.deref_mut() { + Kind::Dir { + ref mut entries, .. + } => { + let removed_inode = wasi_try!(entries.remove(&childs_name).ok_or(__WASI_EINVAL)); + // TODO: make this a debug assert in the future + assert!(inode == removed_inode); + debug_assert!(inodes.arena[inode].stat.read().unwrap().st_nlink > 0); + removed_inode + } + Kind::Root { .. } => return __WASI_EACCES, + _ => unreachable!( + "Internal logic error in wasi::path_unlink_file, parent is not a directory" + ), } - Kind::Root { .. } => return __WASI_EACCES, - _ => unreachable!( - "Internal logic error in wasi::path_unlink_file, parent is not a directory" - ), }; - state.fs.inodes[removed_inode].stat.st_nlink -= 1; - if state.fs.inodes[removed_inode].stat.st_nlink == 0 { - match &mut state.fs.inodes[removed_inode].kind { - Kind::File { handle, path, .. } => { - if let Some(h) = handle { - wasi_try!(h.unlink().map_err(fs_error_into_wasi_err)); - } else { - // File is closed - // problem with the abstraction, we can't call unlink because there's no handle - // drop mutable borrow on `path` - let path = path.clone(); - wasi_try!(state.fs_remove_file(path)); + let st_nlink = { + let mut guard = inodes.arena[removed_inode].stat.write().unwrap(); + guard.st_nlink -= 1; + guard.st_nlink + }; + if st_nlink == 0 { + { + let mut guard = inodes.arena[removed_inode].write(); + match guard.deref_mut() { + Kind::File { handle, path, .. } => { + if let Some(h) = handle { + wasi_try!(h.unlink().map_err(fs_error_into_wasi_err)); + } else { + // File is closed + // problem with the abstraction, we can't call unlink because there's no handle + // drop mutable borrow on `path` + let path = path.clone(); + wasi_try!(state.fs_remove_file(path)); + } } + Kind::Dir { .. } | Kind::Root { .. } => return __WASI_EISDIR, + Kind::Symlink { .. } => { + // TODO: actually delete real symlinks and do nothing for virtual symlinks + } + _ => unimplemented!("wasi::path_unlink_file for Buffer"), } - Kind::Dir { .. } | Kind::Root { .. } => return __WASI_EISDIR, - Kind::Symlink { .. } => { - // TODO: actually delete real symlinks and do nothing for virtual symlinks - } - _ => unimplemented!("wasi::path_unlink_file for Buffer"), } // TODO: test this on Windows and actually make it portable // make the file an orphan fd if the fd is still open - let fd_is_orphaned = if let Kind::File { handle, .. } = &state.fs.inodes[removed_inode].kind - { - handle.is_some() - } else { - false + let fd_is_orphaned = { + let guard = inodes.arena[removed_inode].read(); + if let Kind::File { handle, .. } = guard.deref() { + handle.is_some() + } else { + false + } }; - let removed_inode_val = unsafe { state.fs.remove_inode(removed_inode) }; + let removed_inode_val = unsafe { state.fs.remove_inode(inodes.deref_mut(), removed_inode) }; assert!( removed_inode_val.is_some(), "Inode could not be removed because it doesn't exist" ); if fd_is_orphaned { - state - .fs + inodes .orphan_fds .insert(removed_inode, removed_inode_val.unwrap()); } @@ -2308,34 +2537,33 @@ pub fn poll_oneoff( out_: WasmPtr<__wasi_event_t>, nsubscriptions: u32, nevents: WasmPtr, -) -> __wasi_errno_t { - debug!("wasi::poll_oneoff"); - debug!(" => nsubscriptions = {}", nsubscriptions); - let (memory, mut state) = env.get_memory_and_wasi_state(0); +) -> Result<__wasi_errno_t, WasiError> { + trace!("wasi::poll_oneoff"); + trace!(" => nsubscriptions = {}", nsubscriptions); + let (memory, state, inodes) = thread.get_memory_and_wasi_state_and_inodes(0); - let subscription_array = wasi_try_mem!(in_.slice(memory, nsubscriptions)); - let event_array = wasi_try_mem!(out_.slice(memory, nsubscriptions)); + let subscription_array = wasi_try_mem_ok!(in_.slice(memory, nsubscriptions)); + let event_array = wasi_try_mem_ok!(out_.slice(memory, nsubscriptions)); let mut events_seen = 0; let out_ptr = nevents.deref(memory); - let mut fds = vec![]; + let mut fd_guards = vec![]; let mut clock_subs = vec![]; let mut in_events = vec![]; - let mut total_ns_slept = 0; + let mut time_to_sleep = Duration::from_millis(5); for sub in subscription_array.iter() { - let s: WasiSubscription = wasi_try!(wasi_try_mem!(sub.read()).try_into()); + let s: WasiSubscription = wasi_try_ok!(wasi_try_mem_ok!(sub.read()).try_into()); let mut peb = PollEventBuilder::new(); - let mut ns_to_sleep = 0; - + let fd = match s.event_type { EventType::Read(__wasi_subscription_fs_readwrite_t { fd }) => { match fd { __WASI_STDIN_FILENO | __WASI_STDOUT_FILENO | __WASI_STDERR_FILENO => (), _ => { - let fd_entry = wasi_try!(state.fs.get_fd(fd)); + let fd_entry = wasi_try_ok!(state.fs.get_fd(fd), thread); if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_READ) { - return __WASI_EACCES; + return Ok(__WASI_EACCES); } } } @@ -2346,10 +2574,9 @@ pub fn poll_oneoff( match fd { __WASI_STDIN_FILENO | __WASI_STDOUT_FILENO | __WASI_STDERR_FILENO => (), _ => { - let fd_entry = wasi_try!(state.fs.get_fd(fd)); - + let fd_entry = wasi_try_ok!(state.fs.get_fd(fd), thread); if !has_rights(fd_entry.rights, __WASI_RIGHT_FD_WRITE) { - return __WASI_EACCES; + return Ok(__WASI_EACCES); } } } @@ -2357,11 +2584,13 @@ pub fn poll_oneoff( Some(fd) } EventType::Clock(clock_info) => { - if clock_info.clock_id == __WASI_CLOCK_REALTIME { + if clock_info.clock_id == __WASI_CLOCK_REALTIME + || clock_info.clock_id == __WASI_CLOCK_MONOTONIC + { // this is a hack // TODO: do this properly - ns_to_sleep = clock_info.timeout; - clock_subs.push(clock_info); + time_to_sleep = Duration::from_nanos(clock_info.timeout); + clock_subs.push((clock_info, s.user_data)); None } else { unimplemented!("Polling not implemented for clocks yet"); @@ -2370,64 +2599,104 @@ pub fn poll_oneoff( }; if let Some(fd) = fd { - let wasi_file_ref: &dyn VirtualFile = match fd { - __WASI_STDERR_FILENO => wasi_try!( - wasi_try!(state.fs.stderr().map_err(fs_error_into_wasi_err)).as_ref(), - __WASI_EBADF - ) - .as_ref(), - __WASI_STDIN_FILENO => wasi_try!( - wasi_try!(state.fs.stdin().map_err(fs_error_into_wasi_err)).as_ref(), - __WASI_EBADF - ) - .as_ref(), - __WASI_STDOUT_FILENO => wasi_try!( - wasi_try!(state.fs.stdout().map_err(fs_error_into_wasi_err)).as_ref(), - __WASI_EBADF - ) - .as_ref(), + let wasi_file_ref = match fd { + __WASI_STDERR_FILENO => { + wasi_try_ok!( + inodes + .stderr(&state.fs.fd_map) + .map_err(fs_error_into_wasi_err), + thread + ) + } + __WASI_STDIN_FILENO => { + wasi_try_ok!( + inodes + .stdin(&state.fs.fd_map) + .map_err(fs_error_into_wasi_err), + thread + ) + } + __WASI_STDOUT_FILENO => { + wasi_try_ok!( + inodes + .stdout(&state.fs.fd_map) + .map_err(fs_error_into_wasi_err), + thread + ) + } _ => { - let fd_entry = wasi_try!(state.fs.get_fd(fd)); + let fd_entry = wasi_try_ok!(state.fs.get_fd(fd), thread); let inode = fd_entry.inode; if !has_rights(fd_entry.rights, __WASI_RIGHT_POLL_FD_READWRITE) { - return __WASI_EACCES; + return Ok(__WASI_EACCES); } - match &state.fs.inodes[inode].kind { - Kind::File { handle, .. } => { - if let Some(h) = handle { - h.as_ref() - } else { - return __WASI_EBADF; + { + let guard = inodes.arena[inode].read(); + match guard.deref() { + Kind::File { handle, .. } => { + if let Some(h) = handle { + crate::state::InodeValFileReadGuard { guard } + } else { + return Ok(__WASI_EBADF); + } + } + Kind::Dir { .. } + | Kind::Root { .. } + | Kind::Buffer { .. } + | Kind::Symlink { .. } => { + unimplemented!("polling read on non-files not yet supported") } - } - Kind::Dir { .. } - | Kind::Root { .. } - | Kind::Buffer { .. } - | Kind::Symlink { .. } => { - unimplemented!("polling read on non-files not yet supported") } } } }; - fds.push(wasi_file_ref); - } else { - let remaining_ns = ns_to_sleep as i64 - total_ns_slept as i64; - if remaining_ns > 0 { - debug!("Sleeping for {} nanoseconds", remaining_ns); - let duration = std::time::Duration::from_nanos(remaining_ns as u64); - std::thread::sleep(duration); - total_ns_slept += remaining_ns; - } + fd_guards.push(wasi_file_ref); } } + + let fds = { + let mut f = vec![]; + for fd in fd_guards.iter() { + f.push(wasi_try_ok!(fd.as_ref().ok_or(__WASI_EBADF)).deref()); + } + f + }; + let mut seen_events = vec![Default::default(); in_events.len()]; - wasi_try!(poll( - fds.as_slice(), - in_events.as_slice(), - seen_events.as_mut_slice() - ) - .map_err(fs_error_into_wasi_err)); + + let start = platform_clock_time_get(__WASI_CLOCK_MONOTONIC, 1_000_000).unwrap() as u128; + let mut triggered = 0; + while triggered == 0 { + let now = platform_clock_time_get(__WASI_CLOCK_MONOTONIC, 1_000_000).unwrap() as u128; + let delta = match now.checked_sub(start) { + Some(a) => Duration::from_nanos(a as u64), + None => Duration::ZERO + }; + match poll( + fds.as_slice(), + in_events.as_slice(), + seen_events.as_mut_slice(), + Duration::from_millis(1), + ) + { + Ok(0) => { + thread.yield_now()?; + }, + Ok(a) => { + triggered = a; + }, + Err(FsError::WouldBlock) => { + thread.sleep(Duration::from_millis(1))?; + }, + Err(err) => { + return Ok(fs_error_into_wasi_err(err)); + } + }; + if delta > time_to_sleep { + break; + } + } for (i, seen_event) in seen_events.into_iter().enumerate() { let mut flags = 0; @@ -2440,21 +2709,31 @@ pub fn poll_oneoff( PollEvent::PollHangUp => flags = __WASI_EVENT_FD_READWRITE_HANGUP, PollEvent::PollInvalid => error = __WASI_EINVAL, PollEvent::PollIn => { - bytes_available = - wasi_try!(fds[i].bytes_available().map_err(fs_error_into_wasi_err)); + bytes_available = wasi_try_ok!( + fds[i] + .bytes_available_read() + .map_err(fs_error_into_wasi_err), + thread + ) + .unwrap_or(0usize); error = __WASI_ESUCCESS; } PollEvent::PollOut => { - bytes_available = - wasi_try!(fds[i].bytes_available().map_err(fs_error_into_wasi_err)); + bytes_available = wasi_try_ok!( + fds[i] + .bytes_available_write() + .map_err(fs_error_into_wasi_err), + thread + ) + .unwrap_or(0usize); error = __WASI_ESUCCESS; } } } let event = __wasi_event_t { - userdata: wasi_try_mem!(subscription_array.index(i as u64).read()).userdata, + userdata: wasi_try_mem_ok!(subscription_array.index(i as u64).read()).userdata, error, - type_: wasi_try_mem!(subscription_array.index(i as u64).read()).type_, + type_: wasi_try_mem_ok!(subscription_array.index(i as u64).read()).type_, u: unsafe { __wasi_event_u { fd_readwrite: __wasi_event_fd_readwrite_t { @@ -2464,48 +2743,38 @@ pub fn poll_oneoff( } }, }; - wasi_try_mem!(event_array.index(events_seen as u64).write(event)); + wasi_try_mem_ok!(event_array.index(events_seen as u64).write(event)); events_seen += 1; } - for clock_info in clock_subs { - let event = __wasi_event_t { - // TOOD: review userdata value - userdata: 0, - error: __WASI_ESUCCESS, - type_: __WASI_EVENTTYPE_CLOCK, - u: unsafe { - __wasi_event_u { - fd_readwrite: __wasi_event_fd_readwrite_t { - nbytes: 0, - flags: 0, - }, - } - }, - }; - wasi_try_mem!(event_array.index(events_seen as u64).write(event)); - events_seen += 1; + if triggered <= 0 { + for (clock_info, userdata) in clock_subs { + let event = __wasi_event_t { + userdata, + error: __WASI_ESUCCESS, + type_: __WASI_EVENTTYPE_CLOCK, + u: unsafe { + __wasi_event_u { + fd_readwrite: __wasi_event_fd_readwrite_t { + nbytes: 0, + flags: 0, + }, + } + }, + }; + wasi_try_mem_ok!(event_array.index(events_seen as u64).write(event)); + events_seen += 1; + } } - wasi_try_mem!(out_ptr.write(events_seen as u32)); - __WASI_ESUCCESS + wasi_try_mem_ok!(out_ptr.write(events_seen as u32)); + Ok(__WASI_ESUCCESS) } -#[cfg(feature = "js")] -pub fn poll_oneoff( - env: &WasiEnv, - in_: WasmPtr<__wasi_subscription_t>, - out_: WasmPtr<__wasi_event_t>, - nsubscriptions: u32, - nevents: WasmPtr, -) -> __wasi_errno_t { - unimplemented!(); -} - -pub fn proc_exit(env: &WasiEnv, code: __wasi_exitcode_t) -> Result<(), WasiError> { +pub fn proc_exit(thread: &WasiThread, code: __wasi_exitcode_t) -> Result<(), WasiError> { debug!("wasi::proc_exit, {}", code); Err(WasiError::Exit(code)) } -pub fn proc_raise(env: &WasiEnv, sig: __wasi_signal_t) -> __wasi_errno_t { +pub fn proc_raise(thread: &WasiThread, sig: __wasi_signal_t) -> __wasi_errno_t { debug!("wasi::proc_raise"); unimplemented!("wasi::proc_raise") } @@ -2517,9 +2786,9 @@ pub fn proc_raise(env: &WasiEnv, sig: __wasi_signal_t) -> __wasi_errno_t { /// A pointer to a buffer where the random bytes will be written /// - `size_t buf_len` /// The number of bytes that will be written -pub fn random_get(env: &WasiEnv, buf: u32, buf_len: u32) -> __wasi_errno_t { - debug!("wasi::random_get buf_len: {}", buf_len); - let memory = env.memory(); +pub fn random_get(thread: &WasiThread, buf: u32, buf_len: u32) -> __wasi_errno_t { + trace!("wasi::random_get buf_len: {}", buf_len); + let memory = thread.memory(); let mut u8_buffer = vec![0; buf_len as usize]; let res = getrandom::getrandom(&mut u8_buffer); match res { @@ -2534,10 +2803,10 @@ pub fn random_get(env: &WasiEnv, buf: u32, buf_len: u32) -> __wasi_errno_t { /// ### `sched_yield()` /// Yields execution of the thread -pub fn sched_yield(env: &WasiEnv) -> __wasi_errno_t { - debug!("wasi::sched_yield"); - ::std::thread::yield_now(); - __WASI_ESUCCESS +pub fn sched_yield(thread: &WasiThread) -> Result<__wasi_errno_t, WasiError> { + trace!("wasi::sched_yield"); + thread.yield_now()?; + Ok(__WASI_ESUCCESS) } pub fn sock_recv( @@ -2549,7 +2818,7 @@ pub fn sock_recv( ro_datalen: WasmPtr, ro_flags: WasmPtr<__wasi_roflags_t>, ) -> __wasi_errno_t { - debug!("wasi::sock_recv"); + trace!("wasi::sock_recv"); unimplemented!("wasi::sock_recv") } pub fn sock_send( @@ -2560,10 +2829,14 @@ pub fn sock_send( si_flags: __wasi_siflags_t, so_datalen: WasmPtr, ) -> __wasi_errno_t { - debug!("wasi::sock_send"); + trace!("wasi::sock_send"); unimplemented!("wasi::sock_send") } -pub fn sock_shutdown(env: &WasiEnv, sock: __wasi_fd_t, how: __wasi_sdflags_t) -> __wasi_errno_t { - debug!("wasi::sock_shutdown"); +pub fn sock_shutdown( + thread: &WasiThread, + sock: __wasi_fd_t, + how: __wasi_sdflags_t, +) -> __wasi_errno_t { + trace!("wasi::sock_shutdown"); unimplemented!("wasi::sock_shutdown") -} +} \ No newline at end of file diff --git a/lib/wasi/src/syscalls/unix/mod.rs b/lib/wasi/src/syscalls/unix/mod.rs index 2b494e589d5..9e3a5e35d16 100644 --- a/lib/wasi/src/syscalls/unix/mod.rs +++ b/lib/wasi/src/syscalls/unix/mod.rs @@ -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 { 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 { 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) } diff --git a/lib/wasi/src/syscalls/wasm32.rs b/lib/wasi/src/syscalls/wasm32.rs index 73cadb1b0e6..43b0b72ec4f 100644 --- a/lib/wasi/src/syscalls/wasm32.rs +++ b/lib/wasi/src/syscalls/wasm32.rs @@ -5,21 +5,21 @@ 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 { + 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 { + let new_time: DateTime = Local::now(); + Ok(new_time.timestamp_nanos() as i64) } diff --git a/lib/wasi/src/syscalls/windows.rs b/lib/wasi/src/syscalls/windows.rs index 31fd8f42a1b..c461fa44ca8 100644 --- a/lib/wasi/src/syscalls/windows.rs +++ b/lib/wasi/src/syscalls/windows.rs @@ -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 { 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 { 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) } diff --git a/tests/lib/wast/src/wasi_wast.rs b/tests/lib/wast/src/wasi_wast.rs index f0f260b0f73..b84d4199141 100644 --- a/tests/lib/wast/src/wasi_wast.rs +++ b/tests/lib/wast/src/wasi_wast.rs @@ -1,6 +1,7 @@ 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::PathBuf; use wasmer::{Imports, Instance, Module, Store}; use wasmer_vfs::{host_fs, mem_fs, FileSystem}; @@ -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 { - let stdout_boxed = wasi_state.fs.stdout()?.as_ref().unwrap(); - let stdout = (&**stdout_boxed) - .downcast_ref::() - .unwrap(); - let stdout_str = std::str::from_utf8(&stdout.output)?; +fn get_stdio_output(rx: &mpsc::Receiver>) -> anyhow::Result { + 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 { return Ok(stdout_str.to_string()); } -fn get_stderr_output(wasi_state: &WasiState) -> anyhow::Result { - let stderr_boxed = wasi_state.fs.stderr()?.as_ref().unwrap(); - let stderr = (&**stderr_boxed) - .downcast_ref::() - .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. @@ -96,15 +82,17 @@ impl<'a> WasiTest<'a> { out }; let module = Module::new(&store, &wasm_bytes)?; - let (env, _tempdirs) = self.create_wasi_env(filesystem_kind)?; + 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)> { + ) -> anyhow::Result<(WasiEnv, Vec, mpsc::Receiver>, mpsc::Receiver>)> { 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`]. @@ -528,14 +515,15 @@ mod test { } } -#[derive(Debug)] +#[derive(Debug, Clone)] struct OutputCapturerer { - output: Vec, + output: Arc>>>, } impl OutputCapturerer { - fn new() -> Self { - Self { output: vec![] } + fn new() -> (Self, mpsc::Receiver>) { + let (tx, rx) = mpsc::channel(); + (Self { output: Arc::new(Mutex::new(tx)) }, rx) } } @@ -575,18 +563,30 @@ impl Seek for OutputCapturerer { } impl Write for OutputCapturerer { fn write(&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(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::::new(); + buf.write_fmt(fmt)?; + self.output.lock().unwrap().send(buf) + .map_err(|err| io::Error::new( + io::ErrorKind::BrokenPipe, err.to_string(), + ))?; + Ok(()) } }