diff --git a/Cargo.lock b/Cargo.lock index d408f1238a8..b36842a027e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -586,6 +586,16 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147be55d677052dabc6b22252d5dd0fd4c29c8c27aa4f2fbef0f94aa003b406f" +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -1260,6 +1270,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.1.0" @@ -1738,6 +1763,19 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -2316,6 +2354,24 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nix" version = "0.24.3" @@ -2475,6 +2531,51 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "openssl" +version = "0.10.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd2523381e46256e40930512c7fd25562b9eae4812cb52078f155e87217c9d1e" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176be2629957c157240f68f61f2d0053ad3a4ecfdd9ebf1e6521d18d9635cf67" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "orbclient" version = "0.3.42" @@ -3028,11 +3129,13 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "log", "mime", "mime_guess", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -3042,6 +3145,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "tokio", + "tokio-native-tls", "tokio-rustls", "tokio-util", "tower-service", @@ -3253,6 +3357,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -3334,6 +3447,29 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "security-framework" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "0.9.0" @@ -4032,6 +4168,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.23.4" @@ -4379,6 +4525,7 @@ dependencies = [ "derivative", "filetime", "fs_extra", + "getrandom", "indexmap", "lazy_static", "libc", @@ -5119,6 +5266,7 @@ name = "wasmer-integration-tests-cli" version = "3.2.0-alpha.1" dependencies = [ "anyhow", + "derivative", "dirs", "flate2", "hex", @@ -5127,10 +5275,12 @@ dependencies = [ "object 0.30.3", "pretty_assertions", "rand", + "reqwest", "serde", "tar", "target-lexicon 0.12.6", "tempfile", + "tokio", ] [[package]] diff --git a/lib/api/src/function_env.rs b/lib/api/src/function_env.rs index 7a4551c6abd..89c1de7e0d4 100644 --- a/lib/api/src/function_env.rs +++ b/lib/api/src/function_env.rs @@ -102,9 +102,12 @@ pub struct FunctionEnvMut<'a, T: 'a> { pub(crate) func_env: FunctionEnv, } -impl<'a, T> Debug for FunctionEnvMut<'a, T> { +impl<'a, T> Debug for FunctionEnvMut<'a, T> +where + T: Send + Debug + 'static, +{ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "env_mut") + self.func_env.as_ref(&self.store_mut).fmt(f) } } diff --git a/lib/api/src/js/externals/global.rs b/lib/api/src/js/externals/global.rs index 5e2f93b703d..d9be955f60a 100644 --- a/lib/api/src/js/externals/global.rs +++ b/lib/api/src/js/externals/global.rs @@ -70,19 +70,19 @@ impl Global { let ty = self.handle.ty; let raw = match ty.ty { Type::I32 => RawValue { - i32: value.as_f64().unwrap() as _, + i32: value.as_f64().unwrap_or_default() as _, }, Type::I64 => RawValue { - i64: value.as_f64().unwrap() as _, + i64: value.as_f64().unwrap_or_default() as _, }, Type::F32 => RawValue { - f32: value.as_f64().unwrap() as _, + f32: value.as_f64().unwrap_or_default() as _, }, Type::F64 => RawValue { - f64: value.as_f64().unwrap(), + f64: value.as_f64().unwrap_or_default(), }, Type::V128 => RawValue { - u128: value.as_f64().unwrap() as _, + u128: value.as_f64().unwrap_or_default() as _, }, Type::FuncRef => { unimplemented!(); diff --git a/lib/api/src/js/module.rs b/lib/api/src/js/module.rs index 7fa896a0916..ad39596b820 100644 --- a/lib/api/src/js/module.rs +++ b/lib/api/src/js/module.rs @@ -56,6 +56,12 @@ pub struct Module { unsafe impl Send for Module {} unsafe impl Sync for Module {} +impl From for JsValue { + fn from(val: Module) -> Self { + Self::from(val.module) + } +} + impl Module { pub(crate) fn from_binary( _engine: &impl AsEngineRef, diff --git a/lib/api/src/js/vm.rs b/lib/api/src/js/vm.rs index 10ebd67dd6e..3c9efcf4dcd 100644 --- a/lib/api/src/js/vm.rs +++ b/lib/api/src/js/vm.rs @@ -62,11 +62,12 @@ impl VMMemory { pub fn duplicate(&self) -> Result { let new_memory = crate::js::externals::memory::Memory::js_memory_from_type(&self.ty)?; - #[cfg(feature = "tracing")] - trace!("memory copy started"); - let src = crate::js::externals::memory_view::MemoryView::new_raw(&self.memory); let amount = src.data_size() as usize; + + #[cfg(feature = "tracing")] + trace!(%amount, "memory copy started"); + let mut dst = crate::js::externals::memory_view::MemoryView::new_raw(&new_memory); let dst_size = dst.data_size() as usize; @@ -111,6 +112,12 @@ impl From for JsValue { } } +impl From for (JsValue, MemoryType) { + fn from(value: VMMemory) -> Self { + (JsValue::from(value.memory), value.ty) + } +} + /// The shared memory is the same as the normal memory pub type VMSharedMemory = VMMemory; diff --git a/lib/api/src/module.rs b/lib/api/src/module.rs index de4b3731cc3..47de2dd8b4d 100644 --- a/lib/api/src/module.rs +++ b/lib/api/src/module.rs @@ -397,3 +397,10 @@ impl fmt::Debug for Module { .finish() } } + +#[cfg(feature = "js")] +impl From for wasm_bindgen::JsValue { + fn from(value: Module) -> Self { + wasm_bindgen::JsValue::from(value.0) + } +} diff --git a/lib/api/src/sys/mem_access.rs b/lib/api/src/sys/mem_access.rs index ad045eedc5d..502a556dded 100644 --- a/lib/api/src/sys/mem_access.rs +++ b/lib/api/src/sys/mem_access.rs @@ -17,9 +17,11 @@ where .ok_or(MemoryAccessError::Overflow)?; if end > slice.buffer.0.len as u64 { #[cfg(feature = "tracing")] - warn!( + tracing::warn!( "attempted to read ({} bytes) beyond the bounds of the memory view ({} > {})", - total_len, end, slice.buffer.0.len + total_len, + end, + slice.buffer.0.len ); return Err(MemoryAccessError::HeapOutOfBounds); } @@ -47,9 +49,11 @@ where .ok_or(MemoryAccessError::Overflow)?; if end > ptr.buffer.0.len as u64 { #[cfg(feature = "tracing")] - warn!( + tracing::warn!( "attempted to read ({} bytes) beyond the bounds of the memory view ({} > {})", - total_len, end, ptr.buffer.0.len + total_len, + end, + ptr.buffer.0.len ); return Err(MemoryAccessError::HeapOutOfBounds); } diff --git a/lib/cli/Cargo.toml b/lib/cli/Cargo.toml index 98f31c99863..bee665e1ba0 100644 --- a/lib/cli/Cargo.toml +++ b/lib/cli/Cargo.toml @@ -110,7 +110,7 @@ default = [ "wasmer-artifact-create", "static-artifact-create", "webc_runner", - "tracing" + "tracing", ] cache = ["wasmer-cache"] cache-blake3-pure = ["wasmer-cache/blake3-pure"] diff --git a/lib/cli/src/commands/run.rs b/lib/cli/src/commands/run.rs index 3e8b9787f13..610637558b1 100644 --- a/lib/cli/src/commands/run.rs +++ b/lib/cli/src/commands/run.rs @@ -193,7 +193,7 @@ impl RunWithPathBuf { } } - fn inner_module_run(&self, store: &mut Store, instance: Instance) -> Result<()> { + fn inner_module_run(&self, store: &mut Store, instance: Instance) -> Result { // If this module exports an _initialize function, run that first. if let Ok(initialize) = instance.exports.get_function("_initialize") { initialize @@ -216,12 +216,12 @@ impl RunWithPathBuf { let start: Function = self.try_find_function(&instance, "_start", &[])?; let result = start.call(store, &[]); #[cfg(feature = "wasi")] - self.wasi.handle_result(result)?; + return self.wasi.handle_result(result); #[cfg(not(feature = "wasi"))] - result?; + return Ok(result?); } - Ok(()) + Ok(0) } fn inner_execute(&self) -> Result<()> { @@ -326,6 +326,7 @@ impl RunWithPathBuf { let res = self.inner_module_run(&mut store, instance); ctx.cleanup(&mut store, None); + res } // not WASI @@ -334,7 +335,12 @@ impl RunWithPathBuf { self.inner_module_run(&mut store, instance) } } - }; + }.map(|exit_code| { + std::io::stdout().flush().ok(); + std::io::stderr().flush().ok(); + std::process::exit(exit_code); + }); + #[cfg(not(feature = "wasi"))] let ret = { let instance = Instance::new(&module, &imports! {})?; diff --git a/lib/cli/src/commands/run/wasi.rs b/lib/cli/src/commands/run/wasi.rs index 5bd7a31ca3c..ead6eac338b 100644 --- a/lib/cli/src/commands/run/wasi.rs +++ b/lib/cli/src/commands/run/wasi.rs @@ -7,6 +7,8 @@ use std::{collections::BTreeSet, path::Path}; use virtual_fs::FileSystem; use virtual_fs::{DeviceFile, PassthruFileSystem, RootFileSystemBuilder}; use wasmer::{AsStoreMut, Instance, Module, RuntimeError, Value}; +use wasmer_wasix::os::tty_sys::SysTty; +use wasmer_wasix::os::TtyBridge; use wasmer_wasix::types::__WASI_STDIN_FILENO; use wasmer_wasix::{ default_fs_backing, get_wasi_versions, PluggableRuntimeImplementation, WasiEnv, WasiError, @@ -42,6 +44,11 @@ pub struct Wasi { #[clap(long = "use", name = "USE")] uses: Vec, + /// List of webc packages that are explicitely included for execution + /// Note: these packages will be used instead of those in the registry + #[clap(long = "include-webc", name = "WEBC")] + include_webcs: Vec, + /// List of injected atoms #[clap(long = "map-command", name = "MAPCMD")] map_commands: Vec, @@ -60,6 +67,10 @@ pub struct Wasi { #[clap(long = "net")] pub networking: bool, + /// Disables the TTY bridge + #[clap(long = "no-tty")] + pub no_tty: bool, + /// Allow instances to send http requests. /// /// Access to domains is granted by default. @@ -126,6 +137,12 @@ impl Wasi { rt.set_networking_implementation(virtual_net::UnsupportedVirtualNetworking::default()); } + if !self.no_tty { + let tty = Arc::new(SysTty::default()); + tty.reset(); + rt.set_tty(tty) + } + let engine = store.as_store_mut().engine().clone(); rt.set_engine(Some(engine)); @@ -134,6 +151,7 @@ impl Wasi { .args(args) .envs(self.env_vars.clone()) .uses(self.uses.clone()) + .include_webcs(self.include_webcs.clone()) .map_commands(map_commands); let mut builder = if wasmer_wasix::is_wasix_module(module) { @@ -184,14 +202,13 @@ impl Wasi { } /// Helper function for handling the result of a Wasi _start function. - pub fn handle_result(&self, result: Result, RuntimeError>) -> Result<()> { + pub fn handle_result(&self, result: Result, RuntimeError>) -> Result { match result { - Ok(_) => Ok(()), + Ok(_) => Ok(0), Err(err) => { let err: anyhow::Error = match err.downcast::() { Ok(WasiError::Exit(exit_code)) => { - // We should exit with the provided exit code - std::process::exit(exit_code as _); + return Ok(exit_code.raw()); } Ok(err) => err.into(), Err(err) => err.into(), diff --git a/lib/cli/src/commands/wasmer_create_exe_main.c b/lib/cli/src/commands/wasmer_create_exe_main.c index 716c5c46a69..2840dbc3f69 100644 --- a/lib/cli/src/commands/wasmer_create_exe_main.c +++ b/lib/cli/src/commands/wasmer_create_exe_main.c @@ -232,7 +232,7 @@ int main(int argc, char *argv[]) { wasm_message_t retrieved_message; // TODO: this is a shitty solution, but it's good enough for now wasm_trap_message(trap, &retrieved_message); - if (strcmp(retrieved_message.data, "WASI exited with code: 0") == 0) { + if (strcmp(retrieved_message.data, "WASI exited with code: ExitCode::success (error 0)") == 0) { wasm_trap_delete(trap); } else { fprintf(stderr, "%s", retrieved_message.data); diff --git a/lib/types/src/memory.rs b/lib/types/src/memory.rs index 415d2f88cd2..87132363523 100644 --- a/lib/types/src/memory.rs +++ b/lib/types/src/memory.rs @@ -70,11 +70,13 @@ pub unsafe trait MemorySize: Copy { + TryFrom + TryFrom + TryFrom + + TryFrom + TryInto + TryInto + TryInto + TryInto + TryInto + + TryInto + TryFrom + Add + Sum diff --git a/lib/vfs/Cargo.toml b/lib/vfs/Cargo.toml index 24a40d778f6..2cd89312488 100644 --- a/lib/vfs/Cargo.toml +++ b/lib/vfs/Cargo.toml @@ -24,6 +24,12 @@ tokio = { version = "1", features = [ "io-util", "sync", "macros" ], default_fea pin-project-lite = "0.2.9" indexmap = "1.9.2" +[target.'cfg(not(all(target_arch = "wasm32", target_os = "unknown")))'.dependencies] +getrandom = { version = "0.2" } + +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] +getrandom = { version = "0.2", features = [ "js" ] } + [dev-dependencies] pretty_assertions = "1.3.0" tempfile = "3.4.0" diff --git a/lib/vfs/src/builder.rs b/lib/vfs/src/builder.rs index 3c1351782f5..f5c0b06d4bd 100644 --- a/lib/vfs/src/builder.rs +++ b/lib/vfs/src/builder.rs @@ -1,3 +1,4 @@ +use crate::random_file::RandomFile; use crate::{FileSystem, VirtualFile}; use std::path::{Path, PathBuf}; use tracing::*; @@ -81,6 +82,10 @@ impl RootFileSystemBuilder { let _ = tmp .new_open_options_ext() .insert_device_file(PathBuf::from("/dev/zero"), Box::new(ZeroFile::default())); + let _ = tmp.new_open_options_ext().insert_device_file( + PathBuf::from("/dev/urandom"), + Box::new(RandomFile::default()), + ); let _ = tmp.new_open_options_ext().insert_device_file( PathBuf::from("/dev/stdin"), self.stdin diff --git a/lib/vfs/src/lib.rs b/lib/vfs/src/lib.rs index 1f0fd36f5d9..28d43f6fb99 100644 --- a/lib/vfs/src/lib.rs +++ b/lib/vfs/src/lib.rs @@ -24,6 +24,7 @@ pub mod host_fs; pub mod mem_fs; pub mod null_file; pub mod passthru_fs; +pub mod random_file; pub mod special_file; pub mod tmp_fs; pub mod union_fs; diff --git a/lib/vfs/src/random_file.rs b/lib/vfs/src/random_file.rs new file mode 100644 index 00000000000..5722859ad00 --- /dev/null +++ b/lib/vfs/src/random_file.rs @@ -0,0 +1,88 @@ +//! Used for /dev/zero - infinitely returns zero +//! which is useful for commands like `dd if=/dev/zero of=bigfile.img size=1G` + +use std::io::{self, *}; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite}; + +use crate::VirtualFile; + +#[derive(Debug, Default)] +pub struct RandomFile {} + +impl AsyncSeek for RandomFile { + fn start_seek(self: Pin<&mut Self>, _position: SeekFrom) -> io::Result<()> { + Ok(()) + } + fn poll_complete(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(0)) + } +} + +impl AsyncWrite for RandomFile { + fn poll_write( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Poll::Ready(Ok(buf.len())) + } + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + fn poll_write_vectored( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + bufs: &[IoSlice<'_>], + ) -> Poll> { + Poll::Ready(Ok(bufs.len())) + } + fn is_write_vectored(&self) -> bool { + false + } +} + +impl AsyncRead for RandomFile { + fn poll_read( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> Poll> { + let mut data = vec![0u8; buf.remaining()]; + getrandom::getrandom(&mut data).ok(); + buf.put_slice(&data[..]); + Poll::Ready(Ok(())) + } +} + +impl VirtualFile for RandomFile { + fn last_accessed(&self) -> u64 { + 0 + } + fn last_modified(&self) -> u64 { + 0 + } + fn created_time(&self) -> u64 { + 0 + } + fn size(&self) -> u64 { + 0 + } + fn set_len(&mut self, _new_size: u64) -> crate::Result<()> { + Ok(()) + } + fn unlink(&mut self) -> crate::Result<()> { + Ok(()) + } + fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(0)) + } + fn poll_write_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(0)) + } +} diff --git a/lib/vm/src/mmap.rs b/lib/vm/src/mmap.rs index 11fa061a204..abeb21a666b 100644 --- a/lib/vm/src/mmap.rs +++ b/lib/vm/src/mmap.rs @@ -243,6 +243,12 @@ impl Mmap { unsafe { slice::from_raw_parts(self.ptr as *const u8, self.accessible_size) } } + /// Return the allocated memory as a slice of u8. + pub fn as_slice_arbitary(&self, size: usize) -> &[u8] { + let size = usize::min(size, self.total_size); + unsafe { slice::from_raw_parts(self.ptr as *const u8, size) } + } + /// Return the allocated memory as a mutable slice of u8. pub fn as_mut_slice(&mut self) -> &mut [u8] { unsafe { slice::from_raw_parts_mut(self.ptr as *mut u8, self.total_size) } @@ -253,6 +259,12 @@ impl Mmap { unsafe { slice::from_raw_parts_mut(self.ptr as *mut u8, self.accessible_size) } } + /// Return the allocated memory as a mutable slice of u8. + pub fn as_mut_slice_arbitary(&mut self, size: usize) -> &mut [u8] { + let size = usize::min(size, self.total_size); + unsafe { slice::from_raw_parts_mut(self.ptr as *mut u8, size) } + } + /// Return the allocated memory as a pointer to u8. pub fn as_ptr(&self) -> *const u8 { self.ptr as *const u8 @@ -274,10 +286,17 @@ impl Mmap { } /// Duplicate in a new memory mapping. - pub fn duplicate(&mut self, _size_hint: Option) -> Result { - let mut new = Self::accessible_reserved(self.accessible_size, self.total_size)?; - new.as_mut_slice_accessible() - .copy_from_slice(self.as_slice_accessible()); + pub fn duplicate(&mut self, size_hint: Option) -> Result { + // NOTE: accessible_size != used size as the value is not + // automatically updated when the pre-provisioned space is used + let mut copy_size = self.accessible_size; + if let Some(size_hint) = size_hint { + copy_size = usize::max(copy_size, size_hint); + } + + let mut new = Self::accessible_reserved(copy_size, self.total_size)?; + new.as_mut_slice_arbitary(copy_size) + .copy_from_slice(self.as_slice_arbitary(copy_size)); Ok(new) } } diff --git a/lib/wasi-types/schema/wasi/typenames.wit b/lib/wasi-types/schema/wasi/typenames.wit index e2ca633e970..e532f1991f9 100644 --- a/lib/wasi-types/schema/wasi/typenames.wit +++ b/lib/wasi-types/schema/wasi/typenames.wit @@ -8,6 +8,7 @@ /// /// Note: This is similar to `size_t` in POSIX. // TODO: This is defined as `usize` in the original witx file. Should verify that this type is good as it is defined here +// TODO: This is not good as it breaks 64bit builds - it will need to be externalized type size = u32 /// Non-negative file size or length of a region within a file. @@ -247,6 +248,10 @@ enum errno { notcapable, /// Cannot send after socket shutdown. shutdown, + /// Memory access violation. + memviolation, + /// An unknown error has occured + unknown, } enum bus-errno { @@ -813,12 +818,17 @@ record option-cid { cid: cid } +record option-pid { + tag: option-tag, + pid: pid +} + record option-fd { tag: option-tag, fd: fd } -type exit-code = u32 +type exit-code = errno type event-fd-flags = u16 @@ -1132,6 +1142,7 @@ record option-timestamp { } enum signal { + sigunknown, sighup, sigint, sigquit, @@ -1147,6 +1158,7 @@ enum signal { sigpipe, sigalrm, sigterm, + sigstkflt, sigchld, sigcont, sigstop, @@ -1205,3 +1217,64 @@ enum timeout { connect, accept, } + +/// join flags. +flags join-flags { + /// Non-blocking join on the process + non-blocking, + /// Return if a process is stopped + wake-stopped +} + +/// What has happened with the proccess when we joined on it +enum join-status-type { + /// Nothing has happened + nothing, + /// The process has exited by a normal exit code + exit-normal, + /// The process was terminated by a signal + exit-signal, + /// The process was stopped by a signal and can be resumed with SIGCONT + stopped +} + +/// Represents an errno and a signal +record errno-signal { + /// The exit code that was returned + exit-code: errno, + /// The signal that was returned + signal: signal +} + +/// The contents of an `event`. +variant join-status { + // TODO: wit appears to not have support for tag type + //(@witx tag $join_status_type) + nothing(u8), + exit-normal(errno), + exit-signal(errno-signal), + stopped(signal) +} + +/// thread state flags +flags thread-state-flags { + // TODO: wit doesnt appear to support repr + // flags (@witx repr u16) + + // tsd_used + tsd-used, + // dlerror_flag + dlerror-flag +} + +/// Represents the thread start object +record thread-start { + // Address where the stack starts + stack: size, + // Address where the TLS starts + tls-base: size, + // Function that will be invoked when the thread starts + start-funct: size, + // Arguments to pass the callback function + start-args: size, +} diff --git a/lib/wasi-types/src/wasi/bindings.rs b/lib/wasi-types/src/wasi/bindings.rs index 5a77787379e..fff5ac48d60 100644 --- a/lib/wasi-types/src/wasi/bindings.rs +++ b/lib/wasi-types/src/wasi/bindings.rs @@ -1,8 +1,10 @@ use std::mem::MaybeUninit; -use wasmer::ValueType; +use wasmer::{MemorySize, ValueType}; // TODO: Remove once bindings generate wai_bindgen_rust::bitflags::bitflags! (temp hack) use wai_bindgen_rust as wit_bindgen_rust; +use super::ExitCode; + #[doc = " Type names used by low-level WASI interfaces."] #[doc = " An array size."] #[doc = " "] @@ -90,7 +92,7 @@ impl core::fmt::Debug for Clockid { #[doc = " API; some are used in higher-level library layers, and others are provided"] #[doc = " merely for alignment with POSIX."] #[repr(u16)] -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Errno { #[doc = " No error occurred. System call completed successfully."] Success, @@ -248,6 +250,10 @@ pub enum Errno { Notcapable, #[doc = " Cannot send after socket shutdown."] Shutdown, + #[doc = " Memory access violation."] + Memviolation, + #[doc = " An unknown error has occured"] + Unknown, } impl Errno { pub fn name(&self) -> &'static str { @@ -330,6 +336,8 @@ impl Errno { Errno::Xdev => "xdev", Errno::Notcapable => "notcapable", Errno::Shutdown => "shutdown", + Errno::Memviolation => "memviolation", + Errno::Unknown => "unknown", } } pub fn message(&self) -> &'static str { @@ -412,6 +420,8 @@ impl Errno { Errno::Xdev => "Cross-device link.", Errno::Notcapable => "Extension: Capabilities insufficient.", Errno::Shutdown => "Cannot send after socket shutdown.", + Errno::Memviolation => "Memory access violation.", + Errno::Unknown => "An unknown error has occured", } } } @@ -533,7 +543,104 @@ impl core::fmt::Display for BusErrno { } } impl std::error::Error for BusErrno {} -wai_bindgen_rust::bitflags::bitflags! { # [doc = " File descriptor rights, determining which actions may be performed."] pub struct Rights : u64 { # [doc = " The right to invoke `fd_datasync`."] # [doc = " "] # [doc = " If `rights::path_open` is set, includes the right to invoke"] # [doc = " `path_open` with `fdflags::dsync`."] const FD_DATASYNC = 1 << 0 ; # [doc = " The right to invoke `fd_read` and `sock_recv`."] # [doc = " "] # [doc = " If `rights::fd_seek` is set, includes the right to invoke `fd_pread`."] const FD_READ = 1 << 1 ; # [doc = " The right to invoke `fd_seek`. This flag implies `rights::fd_tell`."] const FD_SEEK = 1 << 2 ; # [doc = " The right to invoke `fd_fdstat_set_flags`."] const FD_FDSTAT_SET_FLAGS = 1 << 3 ; # [doc = " The right to invoke `fd_sync`."] # [doc = " "] # [doc = " If `rights::path_open` is set, includes the right to invoke"] # [doc = " `path_open` with `fdflags::rsync` and `fdflags::dsync`."] const FD_SYNC = 1 << 4 ; # [doc = " The right to invoke `fd_seek` in such a way that the file offset"] # [doc = " remains unaltered (i.e., `whence::cur` with offset zero), or to"] # [doc = " invoke `fd_tell`."] const FD_TELL = 1 << 5 ; # [doc = " The right to invoke `fd_write` and `sock_send`."] # [doc = " If `rights::fd_seek` is set, includes the right to invoke `fd_pwrite`."] const FD_WRITE = 1 << 6 ; # [doc = " The right to invoke `fd_advise`."] const FD_ADVISE = 1 << 7 ; # [doc = " The right to invoke `fd_allocate`."] const FD_ALLOCATE = 1 << 8 ; # [doc = " The right to invoke `path_create_directory`."] const PATH_CREATE_DIRECTORY = 1 << 9 ; # [doc = " If `rights::path_open` is set, the right to invoke `path_open` with `oflags::creat`."] const PATH_CREATE_FILE = 1 << 10 ; # [doc = " The right to invoke `path_link` with the file descriptor as the"] # [doc = " source directory."] const PATH_LINK_SOURCE = 1 << 11 ; # [doc = " The right to invoke `path_link` with the file descriptor as the"] # [doc = " target directory."] const PATH_LINK_TARGET = 1 << 12 ; # [doc = " The right to invoke `path_open`."] const PATH_OPEN = 1 << 13 ; # [doc = " The right to invoke `fd_readdir`."] const FD_READDIR = 1 << 14 ; # [doc = " The right to invoke `path_readlink`."] const PATH_READLINK = 1 << 15 ; # [doc = " The right to invoke `path_rename` with the file descriptor as the source directory."] const PATH_RENAME_SOURCE = 1 << 16 ; # [doc = " The right to invoke `path_rename` with the file descriptor as the target directory."] const PATH_RENAME_TARGET = 1 << 17 ; # [doc = " The right to invoke `path_filestat_get`."] const PATH_FILESTAT_GET = 1 << 18 ; # [doc = " The right to change a file's size (there is no `path_filestat_set_size`)."] # [doc = " If `rights::path_open` is set, includes the right to invoke `path_open` with `oflags::trunc`."] const PATH_FILESTAT_SET_SIZE = 1 << 19 ; # [doc = " The right to invoke `path_filestat_set_times`."] const PATH_FILESTAT_SET_TIMES = 1 << 20 ; # [doc = " The right to invoke `fd_filestat_get`."] const FD_FILESTAT_GET = 1 << 21 ; # [doc = " The right to invoke `fd_filestat_set_size`."] const FD_FILESTAT_SET_SIZE = 1 << 22 ; # [doc = " The right to invoke `fd_filestat_set_times`."] const FD_FILESTAT_SET_TIMES = 1 << 23 ; # [doc = " The right to invoke `path_symlink`."] const PATH_SYMLINK = 1 << 24 ; # [doc = " The right to invoke `path_remove_directory`."] const PATH_REMOVE_DIRECTORY = 1 << 25 ; # [doc = " The right to invoke `path_unlink_file`."] const PATH_UNLINK_FILE = 1 << 26 ; # [doc = " If `rights::fd_read` is set, includes the right to invoke `poll_oneoff` to subscribe to `eventtype::fd_read`."] # [doc = " If `rights::fd_write` is set, includes the right to invoke `poll_oneoff` to subscribe to `eventtype::fd_write`."] const POLL_FD_READWRITE = 1 << 27 ; # [doc = " The right to invoke `sock_shutdown`."] const SOCK_SHUTDOWN = 1 << 28 ; # [doc = " TODO: Found in wasmer-wasi-types rust project, but not in wasi-snapshot0"] const SOCK_ACCEPT = 1 << 29 ; # [doc = " TODO: Found in wasmer-wasi-types rust project, but not in wasi-snapshot0"] const SOCK_CONNECT = 1 << 30 ; # [doc = " TODO: Found in wasmer-wasi-types rust project, but not in wasi-snapshot0"] const SOCK_LISTEN = 1 << 31 ; # [doc = " TODO: Found in wasmer-wasi-types rust project, but not in wasi-snapshot0"] const SOCK_BIND = 1 << 32 ; # [doc = " TODO: Found in wasmer-wasi-types rust project, but not in wasi-snapshot0"] const SOCK_RECV = 1 << 33 ; # [doc = " TODO: Found in wasmer-wasi-types rust project, but not in wasi-snapshot0"] const SOCK_SEND = 1 << 34 ; # [doc = " TODO: Found in wasmer-wasi-types rust project, but not in wasi-snapshot0"] const SOCK_ADDR_LOCAL = 1 << 35 ; # [doc = " TODO: Found in wasmer-wasi-types rust project, but not in wasi-snapshot0"] const SOCK_ADDR_REMOTE = 1 << 36 ; # [doc = " TODO: Found in wasmer-wasi-types rust project, but not in wasi-snapshot0"] const SOCK_RECV_FROM = 1 << 37 ; # [doc = " TODO: Found in wasmer-wasi-types rust project, but not in wasi-snapshot0"] const SOCK_SEND_TO = 1 << 38 ; } } +wai_bindgen_rust::bitflags::bitflags! { + #[doc = " File descriptor rights, determining which actions may be performed."] + pub struct Rights : u64 { + #[doc = " The right to invoke `fd_datasync`."] + #[doc = " "] + #[doc = " If `rights::path_open` is set, includes the right to invoke"] + #[doc = " `path_open` with `fdflags::dsync`."] + const FD_DATASYNC = 1 << 0; + #[doc = " The right to invoke `fd_read` and `sock_recv`."] + #[doc = " "] + #[doc = " If `rights::fd_seek` is set, includes the right to invoke `fd_pread`."] + const FD_READ = 1 << 1; + #[doc = " The right to invoke `fd_seek`. This flag implies `rights::fd_tell`."] + const FD_SEEK = 1 << 2; + #[doc = " The right to invoke `fd_fdstat_set_flags`."] + const FD_FDSTAT_SET_FLAGS = 1 << 3; + #[doc = " The right to invoke `fd_sync`."] + #[doc = " "] + #[doc = " If `rights::path_open` is set, includes the right to invoke"] + #[doc = " `path_open` with `fdflags::rsync` and `fdflags::dsync`."] + const FD_SYNC = 1 << 4; + #[doc = " The right to invoke `fd_seek` in such a way that the file offset"] + #[doc = " remains unaltered (i.e., `whence::cur` with offset zero), or to"] + #[doc = " invoke `fd_tell`."] + const FD_TELL = 1 << 5; + #[doc = " The right to invoke `fd_write` and `sock_send`."] + #[doc = " If `rights::fd_seek` is set, includes the right to invoke `fd_pwrite`."] + const FD_WRITE = 1 << 6; + #[doc = " The right to invoke `fd_advise`."] + const FD_ADVISE = 1 << 7; + #[doc = " The right to invoke `fd_allocate`."] + const FD_ALLOCATE = 1 << 8; + #[doc = " The right to invoke `path_create_directory`."] + const PATH_CREATE_DIRECTORY = 1 << 9; + #[doc = " If `rights::path_open` is set, the right to invoke `path_open` with `oflags::creat`."] + const PATH_CREATE_FILE = 1 << 10; + #[doc = " The right to invoke `path_link` with the file descriptor as the"] + #[doc = " source directory."] + const PATH_LINK_SOURCE = 1 << 11; + #[doc = " The right to invoke `path_link` with the file descriptor as the"] + #[doc = " target directory."] + const PATH_LINK_TARGET = 1 << 12; + #[doc = " The right to invoke `path_open`."] + const PATH_OPEN = 1 << 13; + #[doc = " The right to invoke `fd_readdir`."] + const FD_READDIR = 1 << 14; + #[doc = " The right to invoke `path_readlink`."] + const PATH_READLINK = 1 << 15; + #[doc = " The right to invoke `path_rename` with the file descriptor as the source directory."] + const PATH_RENAME_SOURCE = 1 << 16; + #[doc = " The right to invoke `path_rename` with the file descriptor as the target directory."] + const PATH_RENAME_TARGET = 1 << 17; + #[doc = " The right to invoke `path_filestat_get`."] + const PATH_FILESTAT_GET = 1 << 18; + #[doc = " The right to change a file's size (there is no `path_filestat_set_size`)."] + #[doc = " If `rights::path_open` is set, includes the right to invoke `path_open` with `oflags::trunc`."] + const PATH_FILESTAT_SET_SIZE = 1 << 19; + #[doc = " The right to invoke `path_filestat_set_times`."] + const PATH_FILESTAT_SET_TIMES = 1 << 20; + #[doc = " The right to invoke `fd_filestat_get`."] + const FD_FILESTAT_GET = 1 << 21; + #[doc = " The right to invoke `fd_filestat_set_size`."] + const FD_FILESTAT_SET_SIZE = 1 << 22; + #[doc = " The right to invoke `fd_filestat_set_times`."] + const FD_FILESTAT_SET_TIMES = 1 << 23; + #[doc = " The right to invoke `path_symlink`."] + const PATH_SYMLINK = 1 << 24; + #[doc = " The right to invoke `path_remove_directory`."] + const PATH_REMOVE_DIRECTORY = 1 << 25; + #[doc = " The right to invoke `path_unlink_file`."] + const PATH_UNLINK_FILE = 1 << 26; + #[doc = " If `rights::fd_read` is set, includes the right to invoke `poll_oneoff` to subscribe to `eventtype::fd_read`."] + #[doc = " If `rights::fd_write` is set, includes the right to invoke `poll_oneoff` to subscribe to `eventtype::fd_write`."] + const POLL_FD_READWRITE = 1 << 27; + #[doc = " The right to invoke `sock_shutdown`."] + const SOCK_SHUTDOWN = 1 << 28; + #[doc = " TODO: Found in wasmer-wasi-types rust project, but not in wasi-snapshot0"] + const SOCK_ACCEPT = 1 << 29; + #[doc = " TODO: Found in wasmer-wasi-types rust project, but not in wasi-snapshot0"] + const SOCK_CONNECT = 1 << 30; + #[doc = " TODO: Found in wasmer-wasi-types rust project, but not in wasi-snapshot0"] + const SOCK_LISTEN = 1 << 31; + #[doc = " TODO: Found in wasmer-wasi-types rust project, but not in wasi-snapshot0"] + const SOCK_BIND = 1 << 32; + #[doc = " TODO: Found in wasmer-wasi-types rust project, but not in wasi-snapshot0"] + const SOCK_RECV = 1 << 33; + #[doc = " TODO: Found in wasmer-wasi-types rust project, but not in wasi-snapshot0"] + const SOCK_SEND = 1 << 34; + #[doc = " TODO: Found in wasmer-wasi-types rust project, but not in wasi-snapshot0"] + const SOCK_ADDR_LOCAL = 1 << 35; + #[doc = " TODO: Found in wasmer-wasi-types rust project, but not in wasi-snapshot0"] + const SOCK_ADDR_REMOTE = 1 << 36; + # [doc = " TODO: Found in wasmer-wasi-types rust project, but not in wasi-snapshot0"] + const SOCK_RECV_FROM = 1 << 37; + #[doc = " TODO: Found in wasmer-wasi-types rust project, but not in wasi-snapshot0"] + const SOCK_SEND_TO = 1 << 38; + } +} impl Rights { #[doc = " Convert from a raw integer, preserving any unknown bits. See"] #[doc = " "] @@ -654,7 +761,23 @@ impl core::fmt::Debug for Advice { } } } -wai_bindgen_rust::bitflags::bitflags! { # [doc = " File descriptor flags."] pub struct Fdflags : u16 { # [doc = " Append mode: Data written to the file is always appended to the file's end."] const APPEND = 1 << 0 ; # [doc = " Write according to synchronized I/O data integrity completion. Only the data stored in the file is synchronized."] const DSYNC = 1 << 1 ; # [doc = " Non-blocking mode."] const NONBLOCK = 1 << 2 ; # [doc = " Synchronized read I/O operations."] const RSYNC = 1 << 3 ; # [doc = " Write according to synchronized I/O file integrity completion. In"] # [doc = " addition to synchronizing the data stored in the file, the implementation"] # [doc = " may also synchronously update the file's metadata."] const SYNC = 1 << 4 ; } } +wai_bindgen_rust::bitflags::bitflags! { + #[doc = " File descriptor flags."] + pub struct Fdflags : u16 { + #[doc = " Append mode: Data written to the file is always appended to the file's end."] + const APPEND = 1 << 0; + #[doc = " Write according to synchronized I/O data integrity completion. Only the data stored in the file is synchronized."] + const DSYNC = 1 << 1; + #[doc = " Non-blocking mode."] + const NONBLOCK = 1 << 2; + #[doc = " Synchronized read I/O operations."] + const RSYNC = 1 << 3; + #[doc = " Write according to synchronized I/O file integrity completion. In"] + #[doc = " addition to synchronizing the data stored in the file, the implementation"] + #[doc = " may also synchronously update the file's metadata."] + const SYNC = 1 << 4; + } +} impl Fdflags { #[doc = " Convert from a raw integer, preserving any unknown bits. See"] #[doc = " "] @@ -686,7 +809,21 @@ impl core::fmt::Debug for Fdstat { .finish() } } -wai_bindgen_rust::bitflags::bitflags! { # [doc = " Which file time attributes to adjust."] # [doc = " TODO: wit appears to not have support for flags repr"] # [doc = " (@witx repr u16)"] pub struct Fstflags : u16 { # [doc = " Adjust the last data access timestamp to the value stored in `filestat::atim`."] const SET_ATIM = 1 << 0 ; # [doc = " Adjust the last data access timestamp to the time of clock `clockid::realtime`."] const SET_ATIM_NOW = 1 << 1 ; # [doc = " Adjust the last data modification timestamp to the value stored in `filestat::mtim`."] const SET_MTIM = 1 << 2 ; # [doc = " Adjust the last data modification timestamp to the time of clock `clockid::realtime`."] const SET_MTIM_NOW = 1 << 3 ; } } +wai_bindgen_rust::bitflags::bitflags! { + #[doc = " Which file time attributes to adjust."] + #[doc = " TODO: wit appears to not have support for flags repr"] + #[doc = " (@witx repr u16)"] + pub struct Fstflags : u16 { + #[doc = " Adjust the last data access timestamp to the value stored in `filestat::atim`."] + const SET_ATIM = 1 << 0; + #[doc = " Adjust the last data access timestamp to the time of clock `clockid::realtime`."] + const SET_ATIM_NOW = 1 << 1; + #[doc = " Adjust the last data modification timestamp to the value stored in `filestat::mtim`."] + const SET_MTIM = 1 << 2; + #[doc = " Adjust the last data modification timestamp to the time of clock `clockid::realtime`."] + const SET_MTIM_NOW = 1 << 3; + } +} impl Fstflags { #[doc = " Convert from a raw integer, preserving any unknown bits. See"] #[doc = " "] @@ -694,7 +831,15 @@ impl Fstflags { Self { bits } } } -wai_bindgen_rust::bitflags::bitflags! { # [doc = " Flags determining the method of how paths are resolved."] # [doc = " TODO: wit appears to not have support for flags repr"] # [doc = " (@witx repr u32)"] pub struct Lookup : u32 { # [doc = " As long as the resolved path corresponds to a symbolic link, it is expanded."] const SYMLINK_FOLLOW = 1 << 0 ; } } +wai_bindgen_rust::bitflags::bitflags! { + #[doc = " Flags determining the method of how paths are resolved."] + #[doc = " TODO: wit appears to not have support for flags repr"] + #[doc = " (@witx repr u32)"] + pub struct Lookup : u32 { + #[doc = " As long as the resolved path corresponds to a symbolic link, it is expanded."] + const SYMLINK_FOLLOW = 1 << 0; + } +} impl Lookup { #[doc = " Convert from a raw integer, preserving any unknown bits. See"] #[doc = " "] @@ -702,7 +847,21 @@ impl Lookup { Self { bits } } } -wai_bindgen_rust::bitflags::bitflags! { # [doc = " Open flags used by `path_open`."] # [doc = " TODO: wit appears to not have support for flags repr"] # [doc = " (@witx repr u16)"] pub struct Oflags : u16 { # [doc = " Create file if it does not exist."] const CREATE = 1 << 0 ; # [doc = " Fail if not a directory."] const DIRECTORY = 1 << 1 ; # [doc = " Fail if file already exists."] const EXCL = 1 << 2 ; # [doc = " Truncate file to size 0."] const TRUNC = 1 << 3 ; } } +wai_bindgen_rust::bitflags::bitflags! { + #[doc = " Open flags used by `path_open`."] + #[doc = " TODO: wit appears to not have support for flags repr"] + #[doc = " (@witx repr u16)"] + pub struct Oflags : u16 { + #[doc = " Create file if it does not exist."] + const CREATE = 1 << 0; + #[doc = " Fail if not a directory."] + const DIRECTORY = 1 << 1; + #[doc = " Fail if file already exists."] + const EXCL = 1 << 2; + #[doc = " Truncate file to size 0."] + const TRUNC = 1 << 3; + } +} impl Oflags { #[doc = " Convert from a raw integer, preserving any unknown bits. See"] #[doc = " "] @@ -736,7 +895,18 @@ impl core::fmt::Debug for Eventtype { } } } -wai_bindgen_rust::bitflags::bitflags! { # [doc = " Flags determining how to interpret the timestamp provided in"] # [doc = " `subscription-clock::timeout`."] pub struct Subclockflags : u16 { # [doc = " If set, treat the timestamp provided in"] # [doc = " `subscription-clock::timeout` as an absolute timestamp of clock"] # [doc = " `subscription-clock::id`. If clear, treat the timestamp"] # [doc = " provided in `subscription-clock::timeout` relative to the"] # [doc = " current time value of clock `subscription-clock::id`."] const SUBSCRIPTION_CLOCK_ABSTIME = 1 << 0 ; } } +wai_bindgen_rust::bitflags::bitflags! { + #[doc = " Flags determining how to interpret the timestamp provided in"] + #[doc = " `subscription-clock::timeout`."] + pub struct Subclockflags : u16 { + #[doc = " If set, treat the timestamp provided in"] + #[doc = " `subscription-clock::timeout` as an absolute timestamp of clock"] + #[doc = " `subscription-clock::id`. If clear, treat the timestamp"] + #[doc = " provided in `subscription-clock::timeout` relative to the"] + #[doc = " current time value of clock `subscription-clock::id`."] + const SUBSCRIPTION_CLOCK_ABSTIME = 1 << 0; + } +} impl Subclockflags { #[doc = " Convert from a raw integer, preserving any unknown bits. See"] #[doc = " "] @@ -809,7 +979,14 @@ impl core::fmt::Debug for Preopentype { } } } -wai_bindgen_rust::bitflags::bitflags! { # [doc = " The state of the file descriptor subscribed to with"] # [doc = " `eventtype::fd_read` or `eventtype::fd_write`."] pub struct Eventrwflags : u16 { # [doc = " The peer of this socket has closed or disconnected."] const FD_READWRITE_HANGUP = 1 << 0 ; } } +wai_bindgen_rust::bitflags::bitflags! { + #[doc = " The state of the file descriptor subscribed to with"] + #[doc = " `eventtype::fd_read` or `eventtype::fd_write`."] + pub struct Eventrwflags : u16 { + #[doc = " The peer of this socket has closed or disconnected."] + const FD_READWRITE_HANGUP = 1 << 0; + } +} impl Eventrwflags { #[doc = " Convert from a raw integer, preserving any unknown bits. See"] #[doc = " "] @@ -1157,6 +1334,20 @@ impl core::fmt::Debug for OptionCid { } #[repr(C)] #[derive(Copy, Clone)] +pub struct OptionPid { + pub tag: OptionTag, + pub pid: Pid, +} +impl core::fmt::Debug for OptionPid { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("OptionPid") + .field("tag", &self.tag) + .field("pid", &self.pid) + .finish() + } +} +#[repr(C)] +#[derive(Copy, Clone)] pub struct OptionFd { pub tag: OptionTag, pub fd: Fd, @@ -1187,7 +1378,6 @@ impl core::fmt::Debug for BusHandles { .finish() } } -pub type ExitCode = u32; #[repr(C)] #[derive(Copy, Clone)] pub struct BusEventExit { @@ -2183,6 +2373,7 @@ impl core::fmt::Debug for OptionTimestamp { #[repr(u8)] #[derive(Clone, Copy, PartialEq, Eq, num_enum :: TryFromPrimitive, Hash)] pub enum Signal { + Sigunknown = 0, Sighup, Sigint, Sigquit, @@ -2198,6 +2389,7 @@ pub enum Signal { Sigpipe, Sigalrm, Sigterm, + Sigstkflt, Sigchld, Sigcont, Sigstop, @@ -2217,6 +2409,7 @@ pub enum Signal { impl core::fmt::Debug for Signal { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { + Signal::Sigunknown => f.debug_tuple("Signal::Sigunknown").finish(), Signal::Sighup => f.debug_tuple("Signal::Sighup").finish(), Signal::Sigint => f.debug_tuple("Signal::Sigint").finish(), Signal::Sigquit => f.debug_tuple("Signal::Sigquit").finish(), @@ -2232,6 +2425,7 @@ impl core::fmt::Debug for Signal { Signal::Sigpipe => f.debug_tuple("Signal::Sigpipe").finish(), Signal::Sigalrm => f.debug_tuple("Signal::Sigalrm").finish(), Signal::Sigterm => f.debug_tuple("Signal::Sigterm").finish(), + Signal::Sigstkflt => f.debug_tuple("Signal::Sigstkflt").finish(), Signal::Sigchld => f.debug_tuple("Signal::Sigchld").finish(), Signal::Sigcont => f.debug_tuple("Signal::Sigcont").finish(), Signal::Sigstop => f.debug_tuple("Signal::Sigstop").finish(), @@ -2344,6 +2538,77 @@ impl core::fmt::Debug for Timeout { } } } +wai_bindgen_rust::bitflags::bitflags! { + #[doc = " join flags."] + pub struct JoinFlags : u32 { + #[doc = " Non-blocking join on the process"] + const NON_BLOCKING = 1 << 0 ; + #[doc = " Return if a process is stopped"] + const WAKE_STOPPED = 1 << 1 ; + } +} +impl JoinFlags { + #[doc = " Convert from a raw integer, preserving any unknown bits. See"] + #[doc = " "] + pub fn from_bits_preserve(bits: u32) -> Self { + Self { bits } + } +} +#[doc = " What has happened with the proccess when we joined on it"] +#[repr(u8)] +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum JoinStatusType { + #[doc = " Nothing has happened"] + Nothing, + #[doc = " The process has exited by a normal exit code"] + ExitNormal, + #[doc = " The process was terminated by a signal"] + ExitSignal, + #[doc = " The process was stopped by a signal and can be resumed with SIGCONT"] + Stopped, +} +impl core::fmt::Debug for JoinStatusType { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + JoinStatusType::Nothing => f.debug_tuple("JoinStatusType::Nothing").finish(), + JoinStatusType::ExitNormal => f.debug_tuple("JoinStatusType::ExitNormal").finish(), + JoinStatusType::ExitSignal => f.debug_tuple("JoinStatusType::ExitSignal").finish(), + JoinStatusType::Stopped => f.debug_tuple("JoinStatusType::Stopped").finish(), + } + } +} +#[doc = " Represents an errno and a signal"] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct ErrnoSignal { + #[doc = " The exit code that was returned"] + pub exit_code: Errno, + #[doc = " The signal that was returned"] + pub signal: Signal, +} +impl core::fmt::Debug for ErrnoSignal { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("ErrnoSignal") + .field("exit-code", &self.exit_code) + .field("signal", &self.signal) + .finish() + } +} + +wai_bindgen_rust::bitflags::bitflags! { + #[doc = " thread state flags"] + pub struct ThreadStateFlags : u16 { + const TSD_USED = 1 << 0 ; + const DLERROR_FLAG = 1 << 1 ; + } +} +impl ThreadStateFlags { + #[doc = " Convert from a raw integer, preserving any unknown bits. See"] + #[doc = " "] + pub fn from_bits_preserve(bits: u16) -> Self { + Self { bits } + } +} // TODO: if necessary, must be implemented in wit-bindgen unsafe impl ValueType for Snapshot0Clockid { @@ -2495,8 +2760,11 @@ unsafe impl wasmer::FromToNativeWasmType for Errno { 74 => Self::Txtbsy, 75 => Self::Xdev, 76 => Self::Notcapable, + 77 => Self::Shutdown, + 78 => Self::Memviolation, + 79 => Self::Unknown, - q => todo!("could not serialize number {q} to enum Errno"), + _ => Self::Unknown, } } @@ -3035,7 +3303,7 @@ unsafe impl ValueType for OptionCid { } // TODO: if necessary, must be implemented in wit-bindgen -unsafe impl ValueType for OptionFd { +unsafe impl ValueType for OptionPid { #[inline] fn zero_padding_bytes(&self, _bytes: &mut [MaybeUninit]) {} } @@ -3064,6 +3332,12 @@ unsafe impl ValueType for BusEventClose { fn zero_padding_bytes(&self, _bytes: &mut [MaybeUninit]) {} } +// TODO: if necessary, must be implemented in wit-bindgen +unsafe impl ValueType for OptionFd { + #[inline] + fn zero_padding_bytes(&self, _bytes: &mut [MaybeUninit]) {} +} + // TODO: if necessary, must be implemented in wit-bindgen unsafe impl ValueType for PrestatUDir { #[inline] @@ -3449,38 +3723,40 @@ unsafe impl wasmer::FromToNativeWasmType for Signal { fn from_native(n: Self::Native) -> Self { match n { - 0 => Self::Sighup, - 1 => Self::Sigint, - 2 => Self::Sigquit, - 3 => Self::Sigill, - 4 => Self::Sigtrap, - 5 => Self::Sigabrt, - 6 => Self::Sigbus, - 7 => Self::Sigfpe, - 8 => Self::Sigkill, - 9 => Self::Sigusr1, - 10 => Self::Sigsegv, - 11 => Self::Sigusr2, - 12 => Self::Sigpipe, - 13 => Self::Sigalrm, - 14 => Self::Sigterm, - 15 => Self::Sigchld, - 16 => Self::Sigcont, - 17 => Self::Sigstop, - 18 => Self::Sigtstp, - 19 => Self::Sigttin, - 20 => Self::Sigttou, - 21 => Self::Sigurg, - 22 => Self::Sigxcpu, - 23 => Self::Sigxfsz, - 24 => Self::Sigvtalrm, - 25 => Self::Sigprof, - 26 => Self::Sigwinch, - 27 => Self::Sigpoll, - 28 => Self::Sigpwr, - 29 => Self::Sigsys, - - q => todo!("could not serialize number {q} to enum Signal"), + 0 => Self::Sigunknown, + 1 => Self::Sighup, + 2 => Self::Sigint, + 3 => Self::Sigquit, + 4 => Self::Sigill, + 5 => Self::Sigtrap, + 6 => Self::Sigabrt, + 7 => Self::Sigbus, + 8 => Self::Sigfpe, + 9 => Self::Sigkill, + 10 => Self::Sigusr1, + 11 => Self::Sigsegv, + 12 => Self::Sigusr2, + 13 => Self::Sigpipe, + 14 => Self::Sigalrm, + 15 => Self::Sigterm, + 16 => Self::Sigstkflt, + 17 => Self::Sigchld, + 18 => Self::Sigcont, + 19 => Self::Sigstop, + 20 => Self::Sigtstp, + 21 => Self::Sigttin, + 22 => Self::Sigttou, + 23 => Self::Sigurg, + 24 => Self::Sigxcpu, + 25 => Self::Sigxfsz, + 26 => Self::Sigvtalrm, + 27 => Self::Sigprof, + 28 => Self::Sigwinch, + 29 => Self::Sigpoll, + 30 => Self::Sigpwr, + 31 => Self::Sigsys, + + _ => Self::Sigunknown, } } @@ -3547,3 +3823,44 @@ unsafe impl wasmer::FromToNativeWasmType for Timeout { false } } + +// TODO: if necessary, must be implemented in wit-bindgen +unsafe impl ValueType for JoinFlags { + #[inline] + fn zero_padding_bytes(&self, _bytes: &mut [MaybeUninit]) {} +} + +// TODO: if necessary, must be implemented in wit-bindgen +unsafe impl ValueType for JoinStatusType { + #[inline] + fn zero_padding_bytes(&self, _bytes: &mut [MaybeUninit]) {} +} + +unsafe impl wasmer::FromToNativeWasmType for JoinStatusType { + type Native = i32; + + fn to_native(self) -> Self::Native { + self as i32 + } + + fn from_native(n: Self::Native) -> Self { + match n { + 0 => Self::Nothing, + 1 => Self::ExitNormal, + 2 => Self::ExitSignal, + 3 => Self::Stopped, + + q => todo!("could not serialize number {q} to enum JoinStatusType"), + } + } + + fn is_from_store(&self, _store: &impl wasmer::AsStoreRef) -> bool { + false + } +} + +// TODO: if necessary, must be implemented in wit-bindgen +unsafe impl ValueType for ErrnoSignal { + #[inline] + fn zero_padding_bytes(&self, _bytes: &mut [MaybeUninit]) {} +} diff --git a/lib/wasi-types/src/wasi/bindings_manual.rs b/lib/wasi-types/src/wasi/bindings_manual.rs index 387471f1106..31ca883f748 100644 --- a/lib/wasi-types/src/wasi/bindings_manual.rs +++ b/lib/wasi-types/src/wasi/bindings_manual.rs @@ -359,3 +359,20 @@ impl From for Errno { } } } + +// TODO: if necessary, must be implemented in wit-bindgen +unsafe impl wasmer::FromToNativeWasmType for JoinFlags { + type Native = i32; + + fn to_native(self) -> Self::Native { + self.bits() as i32 + } + fn from_native(n: Self::Native) -> Self { + Self::from_bits_truncate(n as u32) + } + + fn is_from_store(&self, _store: &impl wasmer::AsStoreRef) -> bool { + // TODO: find correct implementation + false + } +} diff --git a/lib/wasi-types/src/wasi/wasix_manual.rs b/lib/wasi-types/src/wasi/wasix_manual.rs index cadf943b6df..bd96ac690a4 100644 --- a/lib/wasi-types/src/wasi/wasix_manual.rs +++ b/lib/wasi-types/src/wasi/wasix_manual.rs @@ -1,10 +1,10 @@ use std::mem::MaybeUninit; -use wasmer::ValueType; +use wasmer::{FromToNativeWasmType, MemorySize, ValueType}; use super::{ - Errno, EventFdReadwrite, Eventtype, Snapshot0SubscriptionClock, SubscriptionClock, - SubscriptionFsReadwrite, Userdata, + Errno, ErrnoSignal, EventFdReadwrite, Eventtype, JoinStatusType, Signal, + Snapshot0SubscriptionClock, SubscriptionClock, SubscriptionFsReadwrite, Userdata, }; /// Thread local key @@ -175,3 +175,149 @@ unsafe impl ValueType for StackSnapshot { #[inline] fn zero_padding_bytes(&self, _bytes: &mut [MaybeUninit]) {} } + +#[repr(C)] +#[derive(Clone, Copy)] +pub union JoinStatusUnion { + pub nothing: u8, + pub exit_normal: Errno, + pub exit_signal: ErrnoSignal, + pub stopped: Signal, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct JoinStatus { + pub tag: JoinStatusType, + pub u: JoinStatusUnion, +} +impl core::fmt::Debug for JoinStatus { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut binding = f.debug_struct("JoinStatus"); + let mut f = binding.field("tag", &self.tag); + f = unsafe { + match self.tag { + JoinStatusType::Nothing => f.field("pid", &self.u.nothing), + JoinStatusType::ExitNormal => f.field("exit_normal", &self.u.exit_normal), + JoinStatusType::ExitSignal => f.field("exit_signal", &self.u.exit_signal), + JoinStatusType::Stopped => f.field("stopped", &self.u.stopped), + } + }; + f.finish() + } +} +unsafe impl ValueType for JoinStatus { + #[inline] + fn zero_padding_bytes(&self, _bytes: &mut [MaybeUninit]) {} +} + +#[doc = " Represents the thread start object"] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct ThreadStart { + pub stack_start: M::Offset, + pub tls_base: M::Offset, + pub start_funct: M::Offset, + pub start_args: M::Offset, + pub reserved: [M::Offset; 10], + pub stack_size: M::Offset, + pub guard_size: M::Offset, +} +impl core::fmt::Debug for ThreadStart { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("ThreadStart") + .field("stack_start", &self.stack_start) + .field("tls-base", &self.tls_base) + .field("start-funct", &self.start_funct) + .field("start-args", &self.start_args) + .field("stack_size", &self.stack_size) + .field("guard_size", &self.guard_size) + .finish() + } +} + +// TODO: if necessary, must be implemented in wit-bindgen +unsafe impl ValueType for ThreadStart { + #[inline] + fn zero_padding_bytes(&self, _bytes: &mut [MaybeUninit]) {} +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum ExitCode { + Errno(Errno), + Other(i32), +} +impl ExitCode { + pub fn raw(&self) -> i32 { + match self { + ExitCode::Errno(err) => err.to_native(), + ExitCode::Other(code) => *code, + } + } + + pub fn is_success(&self) -> bool { + self.raw() == 0 + } +} +impl core::fmt::Debug for ExitCode { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + ExitCode::Errno(a) => write!(f, "ExitCode::{}", a), + ExitCode::Other(a) => write!(f, "ExitCode::{}", a), + } + } +} +impl core::fmt::Display for ExitCode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + core::fmt::Debug::fmt(&self, f) + } +} + +unsafe impl wasmer::FromToNativeWasmType for ExitCode { + type Native = i32; + + fn to_native(self) -> Self::Native { + self.into() + } + + fn from_native(n: Self::Native) -> Self { + n.into() + } + + fn is_from_store(&self, _store: &impl wasmer::AsStoreRef) -> bool { + false + } +} + +impl From for ExitCode { + fn from(val: Errno) -> Self { + Self::Errno(val) + } +} + +impl From for ExitCode { + fn from(val: i32) -> Self { + let err = Errno::from_native(val); + match err { + Errno::Unknown => Self::Other(val), + err => Self::Errno(err), + } + } +} + +impl From for Errno { + fn from(code: ExitCode) -> Self { + match code { + ExitCode::Errno(err) => err, + ExitCode::Other(code) => Errno::from_native(code), + } + } +} + +impl From for i32 { + fn from(val: ExitCode) -> Self { + match val { + ExitCode::Errno(err) => err.to_native(), + ExitCode::Other(code) => code, + } + } +} diff --git a/lib/wasi/Cargo.toml b/lib/wasi/Cargo.toml index f791f24481c..4c7719b6ab7 100644 --- a/lib/wasi/Cargo.toml +++ b/lib/wasi/Cargo.toml @@ -47,10 +47,8 @@ serde_yaml = { version = "^0.8" } shellexpand = { version = "^2" } weezl = { version = "^0.1" } hex = { version = "^0.4" } -term_size = { version = "0.3", optional = true } +term_size = { version = "0.3" } linked_hash_set = { version = "0.1" } -# used by feature='host-termios' -termios = { version = "0.3", optional = true } # the various compilers wasmer-compiler = { version = "=3.2.0-alpha.1", path = "../compiler", features = [ "translator" ], optional = true } http = "0.2.8" @@ -71,6 +69,7 @@ optional = true [target.'cfg(unix)'.dependencies] libc = { version = "^0.2", default-features = false } +termios = { version = "0.3" } [target.'cfg(windows)'.dependencies] winapi = "0.3" @@ -115,7 +114,6 @@ host-vnet = [ "virtual-net/host-net" ] host-threads = [] host-reqwest = ["reqwest"] host-fs = ["virtual-fs/host-fs"] -host-termios = ["termios", "term_size"] logging = ["tracing/log"] disable-all-logging = [ diff --git a/lib/wasi/src/bin_factory/exec.rs b/lib/wasi/src/bin_factory/exec.rs index 92a115ebddc..9b3020f7266 100644 --- a/lib/wasi/src/bin_factory/exec.rs +++ b/lib/wasi/src/bin_factory/exec.rs @@ -9,7 +9,7 @@ use tracing::*; #[cfg(feature = "sys")] use wasmer::NativeEngineExt; use wasmer::{FunctionEnvMut, Instance, Memory, Module, Store}; -use wasmer_wasix_types::wasi::{Errno, ExitCode}; +use wasmer_wasix_types::wasi::Errno; use super::{BinFactory, BinaryPackage, ModuleCache}; use crate::{ @@ -42,7 +42,7 @@ pub fn spawn_exec( VirtualBusError::CompileError }); if module.is_err() { - env.cleanup(Some(Errno::Noexec as ExitCode)); + env.blocking_cleanup(Some(Errno::Noexec.into())); } let module = module?; compiled_modules.set_compiled_module(binary.hash().as_str(), compiler, &module); @@ -50,7 +50,7 @@ pub fn spawn_exec( } (None, None) => { error!("package has no entry [{}]", name,); - env.cleanup(Some(Errno::Noexec as ExitCode)); + env.blocking_cleanup(Some(Errno::Noexec.into())); return Err(VirtualBusError::CompileError); } }; @@ -124,7 +124,7 @@ pub fn spawn_exec_module( error!("wasi[{}]::wasm instantiate error ({})", pid, err); wasi_env .data(&store) - .cleanup(Some(Errno::Noexec as ExitCode)); + .blocking_cleanup(Some(Errno::Noexec.into())); return; } }; @@ -138,7 +138,7 @@ pub fn spawn_exec_module( error!("wasi[{}]::wasi initialize error ({})", pid, err); wasi_env .data(&store) - .cleanup(Some(Errno::Noexec as ExitCode)); + .blocking_cleanup(Some(Errno::Noexec.into())); return; } @@ -148,7 +148,7 @@ pub fn spawn_exec_module( thread.thread.set_status_finished(Err(err.into())); wasi_env .data(&store) - .cleanup(Some(Errno::Noexec as ExitCode)); + .blocking_cleanup(Some(Errno::Noexec.into())); return; } } @@ -166,22 +166,23 @@ pub fn spawn_exec_module( start .call(&mut store, &[]) .map_err(WasiRuntimeError::from) - .map(|_| 0) + .map(|_| Errno::Success) } else { debug!("wasi[{}]::exec-failed: missing _start function", pid); - Ok(Errno::Noexec as u32) + Ok(Errno::Noexec) }; - debug!("wasi[{pid}]::main() has exited with {ret:?}"); let code = if let Err(err) = &ret { - err.as_exit_code().unwrap_or(Errno::Child as u32) + err.as_exit_code().unwrap_or_else(|| Errno::Noexec.into()) } else { - 0 + Errno::Success.into() }; - thread.thread.set_status_finished(ret); // Cleanup the environment - wasi_env.data(&store).cleanup(Some(code)); + wasi_env.data(&store).blocking_cleanup(Some(code)); + + debug!("wasi[{pid}]::main() has exited with {code}"); + thread.thread.set_status_finished(ret.map(|a| a.into())); } }; @@ -210,7 +211,7 @@ impl BinFactory { .await .ok_or(VirtualBusError::NotFound); if binary.is_err() { - env.cleanup(Some(Errno::Noent as ExitCode)); + env.cleanup(Some(Errno::Noent.into())).await; } let binary = binary?; diff --git a/lib/wasi/src/fs/mod.rs b/lib/wasi/src/fs/mod.rs index 5c081ce93c4..f2d4fa93d85 100644 --- a/lib/wasi/src/fs/mod.rs +++ b/lib/wasi/src/fs/mod.rs @@ -93,6 +93,9 @@ impl InodeGuard { inner: Arc::downgrade(&self.inner), } } + pub fn ref_cnt(&self) -> usize { + Arc::strong_count(&self.inner) + } } impl std::ops::Deref for InodeGuard { type Target = InodeVal; @@ -370,6 +373,10 @@ impl WasiFs { pub async fn close_all(&self) { // TODO: this should close all uniquely owned files instead of just flushing. + // Make sure the STDOUT and STDERR are explicitely flushed + self.flush(__WASI_STDOUT_FILENO).await.ok(); + self.flush(__WASI_STDERR_FILENO).await.ok(); + let to_close = { if let Ok(map) = self.fd_map.read() { map.keys().copied().collect::>() @@ -1740,7 +1747,8 @@ impl WasiFs { match pfd { Ok(fd_ref) => { let inode = fd_ref.inode.ino().as_u64(); - trace!(%fd, %inode, "closing file descriptor"); + let ref_cnt = fd_ref.inode.ref_cnt(); + trace!(%fd, %inode, %ref_cnt, "closing file descriptor"); } Err(err) => { trace!(%fd, "closing file descriptor failed - {}", err); diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index 2034e0eb935..f7d9409bbe0 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/src/lib.rs @@ -246,6 +246,12 @@ impl WasiRuntimeError { pub fn as_exit_code(&self) -> Option { if let WasiRuntimeError::Wasi(WasiError::Exit(code)) = self { Some(*code) + } else if let WasiRuntimeError::Runtime(err) = self { + if let Some(WasiError::Exit(code)) = err.downcast_ref() { + Some(*code) + } else { + None + } } else { None } @@ -823,10 +829,10 @@ fn generate_import_object_wasix64_v1( fn mem_error_to_wasi(err: MemoryAccessError) -> Errno { match err { - MemoryAccessError::HeapOutOfBounds => Errno::Fault, + MemoryAccessError::HeapOutOfBounds => Errno::Memviolation, MemoryAccessError::Overflow => Errno::Overflow, MemoryAccessError::NonUtf8String => Errno::Inval, - _ => Errno::Inval, + _ => Errno::Unknown, } } diff --git a/lib/wasi/src/os/command/builtins/cmd_wasmer.rs b/lib/wasi/src/os/command/builtins/cmd_wasmer.rs index 1bde0402d4b..2a9c6d7362f 100644 --- a/lib/wasi/src/os/command/builtins/cmd_wasmer.rs +++ b/lib/wasi/src/os/command/builtins/cmd_wasmer.rs @@ -63,6 +63,11 @@ impl CmdWasmer { what: Option, mut args: Vec, ) -> Result { + // If the first argument is a '--' then skip it + if args.first().map(|a| a.as_str()) == Some("--") { + args = args.into_iter().skip(1).collect(); + } + if let Some(what) = what { let store = store.take().ok_or(VirtualBusError::UnknownError)?; let mut env = config.take().ok_or(VirtualBusError::UnknownError)?; @@ -85,14 +90,14 @@ impl CmdWasmer { ) .await; }); - let handle = OwnedTaskStatus::new_finished_with_code(Errno::Noent as u32).handle(); + let handle = OwnedTaskStatus::new_finished_with_code(Errno::Noent.into()).handle(); Ok(handle) } } else { parent_ctx.data().tasks().block_on(async move { let _ = stderr_write(parent_ctx, HELP_RUN.as_bytes()).await; }); - let handle = OwnedTaskStatus::new_finished_with_code(0).handle(); + let handle = OwnedTaskStatus::new_finished_with_code(Errno::Success.into()).handle(); Ok(handle) } } @@ -135,7 +140,8 @@ impl VirtualCommand for CmdWasmer { parent_ctx.data().tasks().block_on(async move { let _ = stderr_write(parent_ctx, HELP.as_bytes()).await; }); - let handle = OwnedTaskStatus::new_finished_with_code(0).handle(); + let handle = + OwnedTaskStatus::new_finished_with_code(Errno::Success.into()).handle(); Ok(handle) } Some(what) => { diff --git a/lib/wasi/src/os/command/mod.rs b/lib/wasi/src/os/command/mod.rs index cb0093db7a4..b2723dbb1eb 100644 --- a/lib/wasi/src/os/command/mod.rs +++ b/lib/wasi/src/os/command/mod.rs @@ -101,7 +101,7 @@ impl Commands { format!("wasm command unknown - {}\r\n", path).as_bytes(), ); - let res = OwnedTaskStatus::new(TaskStatus::Finished(Ok(Errno::Noent as u32))); + let res = OwnedTaskStatus::new(TaskStatus::Finished(Ok(Errno::Noent.into()))); Ok(res.handle()) } } diff --git a/lib/wasi/src/os/task/control_plane.rs b/lib/wasi/src/os/task/control_plane.rs index 47b93252bed..19fa8e87fbf 100644 --- a/lib/wasi/src/os/task/control_plane.rs +++ b/lib/wasi/src/os/task/control_plane.rs @@ -135,6 +135,12 @@ impl WasiControlPlane { Ok(proc) } + /// Generates a new process ID + pub fn generate_id(&self) -> Result { + let mut mutable = self.state.mutable.write().unwrap(); + mutable.next_process_id() + } + /// Gets a reference to a running process pub fn get_process(&self, pid: WasiProcessId) -> Option { self.state diff --git a/lib/wasi/src/os/task/process.rs b/lib/wasi/src/os/task/process.rs index 0c80b3f2c8a..69972cffbaf 100644 --- a/lib/wasi/src/os/task/process.rs +++ b/lib/wasi/src/os/task/process.rs @@ -1,10 +1,9 @@ use std::{ - borrow::Cow, collections::HashMap, convert::TryInto, sync::{ atomic::{AtomicU32, Ordering}, - Arc, RwLock, RwLockReadGuard, RwLockWriteGuard, + Arc, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak, }, time::Duration, }; @@ -24,7 +23,7 @@ use crate::{ use super::{ control_plane::{ControlPlaneError, WasiControlPlaneHandle}, signal::{SignalDeliveryError, SignalHandlerAbi}, - task_join_handle::{OwnedTaskStatus, TaskJoinHandle}, + task_join_handle::OwnedTaskStatus, }; /// Represents the ID of a sub-process @@ -79,8 +78,8 @@ impl std::fmt::Debug for WasiProcessId { pub struct WasiProcess { /// Unique ID of this process pub(crate) pid: WasiProcessId, - /// ID of the parent process - pub(crate) ppid: WasiProcessId, + /// List of all the children spawned from this thread + pub(crate) parent: Option>>, /// The inner protected region of the process pub(crate) inner: Arc>, /// Reference back to the compute engine @@ -89,8 +88,6 @@ pub struct WasiProcess { pub(crate) compute: WasiControlPlaneHandle, /// Reference to the exit code for the main thread pub(crate) finished: Arc, - /// List of all the children spawned from this thread - pub(crate) children: Arc>>, /// Number of threads waiting for children to exit pub(crate) waiting: Arc, } @@ -98,12 +95,12 @@ pub struct WasiProcess { // TODO: fields should be private and only accessed via methods. #[derive(Debug)] pub struct WasiProcessInner { + /// Unique ID of this process + pub pid: WasiProcessId, /// The threads that make up this process pub threads: HashMap, /// Number of threads running for this process pub thread_count: u32, - /// Seed used to generate thread ID's - pub thread_seed: WasiThreadId, /// All the thread local variables pub thread_local: HashMap<(WasiThreadId, TlKey), TlVal>, /// User data associated with thread local data @@ -112,10 +109,8 @@ pub struct WasiProcessInner { pub thread_local_seed: TlKey, /// Signals that will be triggered at specific intervals pub signal_intervals: HashMap, - /// Represents all the process spun up as a bus process - pub bus_processes: HashMap, - /// Indicates if the bus process can be reused - pub bus_process_reuse: HashMap, WasiProcessId>, + /// List of all the children spawned from this thread + pub children: Vec, } // TODO: why do we need this, how is it used? @@ -142,20 +137,18 @@ impl WasiProcess { pub fn new(pid: WasiProcessId, plane: WasiControlPlaneHandle) -> Self { WasiProcess { pid, - ppid: 0u32.into(), + parent: None, compute: plane, inner: Arc::new(RwLock::new(WasiProcessInner { + pid, threads: Default::default(), thread_count: Default::default(), - thread_seed: Default::default(), thread_local: Default::default(), thread_local_user_data: Default::default(), thread_local_seed: Default::default(), signal_intervals: Default::default(), - bus_processes: Default::default(), - bus_process_reuse: Default::default(), + children: Default::default(), })), - children: Arc::new(RwLock::new(Default::default())), finished: Arc::new(OwnedTaskStatus::default()), waiting: Arc::new(AtomicU32::new(0)), } @@ -172,7 +165,12 @@ impl WasiProcess { /// Gets the process ID of the parent process pub fn ppid(&self) -> WasiProcessId { - self.ppid + self.parent + .iter() + .filter_map(|parent| parent.upgrade()) + .map(|parent| parent.read().unwrap().pid) + .next() + .unwrap_or(WasiProcessId(0)) } /// Gains write access to the process internals @@ -189,21 +187,35 @@ impl WasiProcess { /// Creates a a thread and returns it pub fn new_thread(&self) -> Result { - let task_count_guard = self.compute.must_upgrade().register_task()?; + let control_plane = self.compute.must_upgrade(); + let task_count_guard = control_plane.register_task()?; - let mut inner = self.inner.write().unwrap(); - let id = inner.thread_seed.inc(); + // Determine if its the main thread or not + let is_main = { + let inner = self.inner.read().unwrap(); + inner.thread_count == 0 + }; - let mut is_main = false; - let finished = if inner.thread_count < 1 { - is_main = true; + // Generate a new process ID (this is because the process ID and thread ID + // address space must not overlap in libc). For the main proecess the TID=PID + let tid: WasiThreadId = if is_main { + self.pid().raw().into() + } else { + let tid: u32 = control_plane.generate_id()?.into(); + tid.into() + }; + + // The wait finished should be the process version if its the main thread + let mut inner = self.inner.write().unwrap(); + let finished = if is_main { self.finished.clone() } else { Arc::new(OwnedTaskStatus::default()) }; - let ctrl = WasiThread::new(self.pid(), id, is_main, finished, task_count_guard); - inner.threads.insert(id, ctrl.clone()); + // Insert the thread into the pool + let ctrl = WasiThread::new(self.pid(), tid, is_main, finished, task_count_guard); + inner.threads.insert(tid, ctrl.clone()); inner.thread_count += 1; Ok(WasiThreadHandle::new(ctrl, &self.inner)) @@ -217,8 +229,15 @@ impl WasiProcess { /// Signals a particular thread in the process pub fn signal_thread(&self, tid: &WasiThreadId, signal: Signal) { + // Sometimes we will signal the process rather than the thread hence this libc hardcoded value + let mut tid = tid.raw(); + if tid == 1073741823 { + tid = self.pid().raw(); + } + let tid: WasiThreadId = tid.into(); + let inner = self.inner.read().unwrap(); - if let Some(thread) = inner.threads.get(tid) { + if let Some(thread) = inner.threads.get(&tid) { thread.signal(signal); } else { trace!( @@ -233,14 +252,12 @@ impl WasiProcess { /// Signals all the threads in this process pub fn signal_process(&self, signal: Signal) { { - let children = self.children.read().unwrap(); + let inner = self.inner.read().unwrap(); if self.waiting.load(Ordering::Acquire) > 0 { let mut triggered = false; - for pid in children.iter() { - if let Some(process) = self.compute.must_upgrade().get_process(*pid) { - process.signal_process(signal); - triggered = true; - } + for child in inner.children.iter() { + child.signal_process(signal); + triggered = true; } if triggered { return; @@ -298,20 +315,20 @@ impl WasiProcess { pub async fn join_children(&mut self) -> Option>> { let _guard = WasiProcessWait::new(self); let children: Vec<_> = { - let children = self.children.read().unwrap(); - children.clone() + let inner = self.inner.read().unwrap(); + inner.children.clone() }; if children.is_empty() { return None; } let mut waits = Vec::new(); - for pid in children { - if let Some(process) = self.compute.must_upgrade().get_process(pid) { - let children = self.children.clone(); + for child in children { + if let Some(process) = self.compute.must_upgrade().get_process(child.pid) { + let inner = self.inner.clone(); waits.push(async move { let join = process.join().await; - let mut children = children.write().unwrap(); - children.retain(|a| *a != pid); + let mut inner = inner.write().unwrap(); + inner.children.retain(|a| a.pid != child.pid); join }) } @@ -326,32 +343,33 @@ impl WasiProcess { pub async fn join_any_child(&mut self) -> Result, Errno> { let _guard = WasiProcessWait::new(self); let children: Vec<_> = { - let children = self.children.read().unwrap(); - children.clone() + let inner = self.inner.read().unwrap(); + inner.children.clone() }; if children.is_empty() { - return Ok(None); + return Err(Errno::Child); } let mut waits = Vec::new(); - for pid in children { - if let Some(process) = self.compute.must_upgrade().get_process(pid) { - let children = self.children.clone(); + for child in children { + if let Some(process) = self.compute.must_upgrade().get_process(child.pid) { + let inner = self.inner.clone(); waits.push(async move { let join = process.join().await; - let mut children = children.write().unwrap(); - children.retain(|a| *a != pid); - (pid, join) + let mut inner = inner.write().unwrap(); + inner.children.retain(|a| a.pid != child.pid); + (child, join) }) } } - let (pid, res) = futures::future::select_all(waits.into_iter().map(|a| Box::pin(a))) + let (child, res) = futures::future::select_all(waits.into_iter().map(|a| Box::pin(a))) .await .0; - let code = res.unwrap_or_else(|e| e.as_exit_code().unwrap_or(Errno::Canceled as u32)); + let code = + res.unwrap_or_else(|e| e.as_exit_code().unwrap_or_else(|| Errno::Canceled.into())); - Ok(Some((pid, code))) + Ok(Some((child.pid, code))) } /// Terminate the process and all its threads diff --git a/lib/wasi/src/os/task/task_join_handle.rs b/lib/wasi/src/os/task/task_join_handle.rs index c4fa6b14293..c374305fd96 100644 --- a/lib/wasi/src/os/task/task_join_handle.rs +++ b/lib/wasi/src/os/task/task_join_handle.rs @@ -70,13 +70,19 @@ pub trait VirtualTaskHandle: std::fmt::Debug + Send + Sync + 'static { /// A handle that allows awaiting the termination of a task, and retrieving its exit code. #[derive(Debug)] pub struct OwnedTaskStatus { - watch: tokio::sync::watch::Sender, + watch_tx: tokio::sync::watch::Sender, + // Even through unused, without this receive there is a race condition + // where the previously sent values are lost. + #[allow(dead_code)] + watch_rx: tokio::sync::watch::Receiver, } impl OwnedTaskStatus { pub fn new(status: TaskStatus) -> Self { + let (tx, rx) = tokio::sync::watch::channel(status); Self { - watch: tokio::sync::watch::channel(status).0, + watch_tx: tx, + watch_rx: rx, } } @@ -86,7 +92,7 @@ impl OwnedTaskStatus { /// Marks the task as finished. pub fn set_running(&self) { - self.watch.send_modify(|value| { + self.watch_tx.send_modify(|value| { // Only set to running if task was pending, otherwise the transition would be invalid. if value.is_pending() { *value = TaskStatus::Running; @@ -95,12 +101,7 @@ impl OwnedTaskStatus { } /// Marks the task as finished. - pub(super) fn set_finished(&self, res: Result>) { - // Don't overwrite a previous finished state. - if self.status().is_finished() { - return; - } - + pub(crate) fn set_finished(&self, res: Result>) { let inner = match res { Ok(code) => Ok(code), Err(err) => { @@ -111,36 +112,35 @@ impl OwnedTaskStatus { } } }; - self.watch.send(TaskStatus::Finished(inner)).ok(); + self.watch_tx.send_modify(move |old| { + if !old.is_finished() { + *old = TaskStatus::Finished(inner); + } + }); } pub fn status(&self) -> TaskStatus { - self.watch.borrow().clone() + self.watch_tx.borrow().clone() } pub async fn await_termination(&self) -> Result> { - let mut receiver = self.watch.subscribe(); - match &*receiver.borrow_and_update() { - TaskStatus::Pending | TaskStatus::Running => {} - TaskStatus::Finished(res) => { - return res.clone(); - } - } + let mut receiver = self.watch_tx.subscribe(); loop { - // NOTE: unwrap() is fine, because &self always holds on to the sender. - receiver.changed().await.unwrap(); - match &*receiver.borrow_and_update() { + let status = receiver.borrow_and_update().clone(); + match status { TaskStatus::Pending | TaskStatus::Running => {} TaskStatus::Finished(res) => { - return res.clone(); + return res; } } + // NOTE: unwrap() is fine, because &self always holds on to the sender. + receiver.changed().await.unwrap(); } } pub fn handle(&self) -> TaskJoinHandle { TaskJoinHandle { - watch: self.watch.subscribe(), + watch: self.watch_tx.subscribe(), } } } @@ -165,22 +165,17 @@ impl TaskJoinHandle { /// Wait until the task finishes. pub async fn wait_finished(&mut self) -> Result> { - match &*self.watch.borrow_and_update() { - TaskStatus::Pending | TaskStatus::Running => {} - TaskStatus::Finished(res) => { - return res.clone(); - } - } loop { - if self.watch.changed().await.is_err() { - return Ok(Errno::Noent as u32); - } - match &*self.watch.borrow_and_update() { + let status = self.watch.borrow_and_update().clone(); + match status { TaskStatus::Pending | TaskStatus::Running => {} TaskStatus::Finished(res) => { - return res.clone(); + return res; } } + if self.watch.changed().await.is_err() { + return Ok(Errno::Noent.into()); + } } } } diff --git a/lib/wasi/src/os/task/thread.rs b/lib/wasi/src/os/task/thread.rs index 38098ee20de..4a0d265d659 100644 --- a/lib/wasi/src/os/task/thread.rs +++ b/lib/wasi/src/os/task/thread.rs @@ -424,7 +424,7 @@ impl Drop for WasiThreadHandleProtected { if let Some(inner) = Weak::upgrade(&self.inner) { let mut inner = inner.write().unwrap(); if let Some(ctrl) = inner.threads.remove(&id) { - ctrl.set_status_finished(Ok(0)); + ctrl.set_status_finished(Ok(Errno::Success.into())); } inner.thread_count -= 1; } @@ -457,7 +457,7 @@ impl From for Errno { match a { WasiThreadError::Unsupported => Errno::Notsup, WasiThreadError::MethodNotFound => Errno::Inval, - WasiThreadError::MemoryCreateFailed => Errno::Fault, + WasiThreadError::MemoryCreateFailed => Errno::Nomem, WasiThreadError::InvalidWasmContext => Errno::Noexec, } } diff --git a/lib/wasi/src/os/tty/mod.rs b/lib/wasi/src/os/tty/mod.rs index f54bc5176b0..e81ea873dc5 100644 --- a/lib/wasi/src/os/tty/mod.rs +++ b/lib/wasi/src/os/tty/mod.rs @@ -14,7 +14,6 @@ use super::task::signal::SignalHandlerAbi; const TTY_MOBILE_PAUSE: u128 = std::time::Duration::from_millis(200).as_nanos(); -#[cfg(feature = "host-termios")] pub mod tty_sys; #[derive(Debug)] @@ -446,6 +445,9 @@ impl Default for WasiTtyState { /// Provides access to a TTY. pub trait TtyBridge { + /// Resets the values + fn reset(&self); + /// Retrieve the current TTY state. fn tty_get(&self) -> WasiTtyState; diff --git a/lib/wasi/src/os/tty/tty_sys.rs b/lib/wasi/src/os/tty/tty_sys.rs index 36bbad91392..6dae0df0423 100644 --- a/lib/wasi/src/os/tty/tty_sys.rs +++ b/lib/wasi/src/os/tty/tty_sys.rs @@ -2,19 +2,21 @@ use super::TtyBridge; use crate::WasiTtyState; /// [`TtyBridge`] implementation for Unix systems. -pub struct SysTyy; +#[derive(Debug, Default, Clone)] +pub struct SysTty; + +impl TtyBridge for SysTty { + fn reset(&self) { + sys::reset().ok(); + } -impl TtyBridge for SysTyy { fn tty_get(&self) -> WasiTtyState { - let mut echo = false; - let mut line_buffered = false; - let mut line_feeds = false; - - if let Ok(termios) = termios::Termios::from_fd(0) { - echo = (termios.c_lflag & termios::ECHO) != 0; - line_buffered = (termios.c_lflag & termios::ICANON) != 0; - line_feeds = (termios.c_lflag & termios::ONLCR) != 0; - } + let echo = sys::is_mode_echo(); + let line_buffered = sys::is_mode_line_buffering(); + let line_feeds = sys::is_mode_line_feeds(); + let stdin_tty = sys::is_stdin_tty(); + let stdout_tty = sys::is_stdout_tty(); + let stderr_tty = sys::is_stderr_tty(); if let Some((w, h)) = term_size::dimensions() { WasiTtyState { @@ -22,9 +24,9 @@ impl TtyBridge for SysTyy { rows: h as u32, width: 800, height: 600, - stdin_tty: true, - stdout_tty: true, - stderr_tty: true, + stdin_tty, + stdout_tty, + stderr_tty, echo, line_buffered, line_feeds, @@ -35,9 +37,9 @@ impl TtyBridge for SysTyy { cols: 25, width: 800, height: 600, - stdin_tty: true, - stdout_tty: true, - stderr_tty: true, + stdin_tty, + stdout_tty, + stderr_tty, echo, line_buffered, line_feeds, @@ -46,146 +48,229 @@ impl TtyBridge for SysTyy { } fn tty_set(&self, tty_state: WasiTtyState) { - #[cfg(unix)] - { - if tty_state.echo { - unix::set_mode_echo(); - } else { - unix::set_mode_no_echo(); - } - if tty_state.line_buffered { - unix::set_mode_line_buffered(); - } else { - unix::set_mode_no_line_buffered(); - } - if tty_state.line_feeds { - unix::set_mode_line_feeds(); - } else { - unix::set_mode_no_line_feeds(); - } + if tty_state.echo { + sys::set_mode_echo().ok(); + } else { + sys::set_mode_no_echo().ok(); + } + if tty_state.line_buffered { + sys::set_mode_line_buffered().ok(); + } else { + sys::set_mode_no_line_buffered().ok(); + } + if tty_state.line_feeds { + sys::set_mode_line_feeds().ok(); + } else { + sys::set_mode_no_line_feeds().ok(); } } } +#[allow(unused_mut)] #[cfg(unix)] -mod unix { +mod sys { #![allow(unused_imports)] use { libc::{ - c_int, tcsetattr, termios, ECHO, ECHOE, ECHONL, ICANON, ICRNL, IEXTEN, ISIG, IXON, - OPOST, TCSANOW, + c_int, tcsetattr, termios, ECHO, ECHOCTL, ECHOE, ECHOK, ECHONL, ICANON, ICRNL, IEXTEN, + IGNCR, ISIG, IXON, ONLCR, OPOST, TCSANOW, }, std::mem, std::os::unix::io::AsRawFd, }; - #[cfg(unix)] - pub fn io_result(ret: libc::c_int) -> std::io::Result<()> { + fn io_result(ret: libc::c_int) -> std::io::Result<()> { match ret { 0 => Ok(()), _ => Err(std::io::Error::last_os_error()), } } - #[cfg(unix)] - pub fn set_mode_no_echo() -> std::fs::File { - let tty = std::fs::File::open("/dev/tty").unwrap(); - let fd = tty.as_raw_fd(); + pub fn reset() -> Result<(), anyhow::Error> { + let mut termios = mem::MaybeUninit::::uninit(); + io_result(unsafe { ::libc::tcgetattr(0, termios.as_mut_ptr()) })?; + let mut termios = unsafe { termios.assume_init() }; + + termios.c_lflag |= ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL; + + unsafe { tcsetattr(0, TCSANOW, &termios) }; + Ok(()) + } + + pub fn is_stdin_tty() -> bool { + ::termios::Termios::from_fd(0).is_ok() + } + + pub fn is_stdout_tty() -> bool { + ::termios::Termios::from_fd(1).is_ok() + } + + pub fn is_stderr_tty() -> bool { + ::termios::Termios::from_fd(2).is_ok() + } + + pub fn is_mode_echo() -> bool { + if let Ok(termios) = ::termios::Termios::from_fd(0) { + (termios.c_lflag & ::termios::ECHO) != 0 + } else { + false + } + } + + pub fn is_mode_line_buffering() -> bool { + if let Ok(termios) = ::termios::Termios::from_fd(0) { + (termios.c_lflag & ::termios::ICANON) != 0 + } else { + false + } + } + pub fn is_mode_line_feeds() -> bool { + if let Ok(termios) = ::termios::Termios::from_fd(0) { + (termios.c_lflag & ::termios::ONLCR) != 0 + } else { + false + } + } + + pub fn set_mode_no_echo() -> Result<(), anyhow::Error> { let mut termios = mem::MaybeUninit::::uninit(); - io_result(unsafe { ::libc::tcgetattr(fd, termios.as_mut_ptr()) }).unwrap(); + io_result(unsafe { ::libc::tcgetattr(0, termios.as_mut_ptr()) })?; let mut termios = unsafe { termios.assume_init() }; termios.c_lflag &= !ECHO; termios.c_lflag &= !ECHOE; + termios.c_lflag &= !ECHOK; + termios.c_lflag &= !ECHOCTL; + termios.c_lflag &= !IEXTEN; + /* termios.c_lflag &= !ISIG; termios.c_lflag &= !IXON; - termios.c_lflag &= !IEXTEN; termios.c_lflag &= !ICRNL; termios.c_lflag &= !OPOST; + */ - unsafe { tcsetattr(fd, TCSANOW, &termios) }; - tty + unsafe { tcsetattr(0, TCSANOW, &termios) }; + Ok(()) } - #[cfg(unix)] - pub fn set_mode_echo() -> std::fs::File { - let tty = std::fs::File::open("/dev/tty").unwrap(); - let fd = tty.as_raw_fd(); - + pub fn set_mode_echo() -> Result<(), anyhow::Error> { let mut termios = mem::MaybeUninit::::uninit(); - io_result(unsafe { ::libc::tcgetattr(fd, termios.as_mut_ptr()) }).unwrap(); + io_result(unsafe { ::libc::tcgetattr(0, termios.as_mut_ptr()) })?; let mut termios = unsafe { termios.assume_init() }; termios.c_lflag |= ECHO; termios.c_lflag |= ECHOE; + termios.c_lflag |= ECHOK; + termios.c_lflag |= ECHOCTL; + termios.c_lflag |= IEXTEN; + /* termios.c_lflag |= ISIG; termios.c_lflag |= IXON; - termios.c_lflag |= IEXTEN; termios.c_lflag |= ICRNL; termios.c_lflag |= OPOST; + */ - unsafe { tcsetattr(fd, TCSANOW, &termios) }; - tty + unsafe { tcsetattr(0, TCSANOW, &termios) }; + Ok(()) } - #[cfg(unix)] - pub fn set_mode_no_line_feeds() -> std::fs::File { - let tty = std::fs::File::open("/dev/tty").unwrap(); - let fd = tty.as_raw_fd(); - + pub fn set_mode_no_line_buffered() -> Result<(), anyhow::Error> { let mut termios = mem::MaybeUninit::::uninit(); - io_result(unsafe { ::libc::tcgetattr(fd, termios.as_mut_ptr()) }).unwrap(); + io_result(unsafe { ::libc::tcgetattr(0, termios.as_mut_ptr()) })?; let mut termios = unsafe { termios.assume_init() }; termios.c_lflag &= !ICANON; - unsafe { tcsetattr(fd, TCSANOW, &termios) }; - tty + unsafe { tcsetattr(0, TCSANOW, &termios) }; + Ok(()) } - #[cfg(unix)] - pub fn set_mode_line_feeds() -> std::fs::File { - let tty = std::fs::File::open("/dev/tty").unwrap(); - let fd = tty.as_raw_fd(); - + pub fn set_mode_line_buffered() -> Result<(), anyhow::Error> { let mut termios = mem::MaybeUninit::::uninit(); - io_result(unsafe { ::libc::tcgetattr(fd, termios.as_mut_ptr()) }).unwrap(); + io_result(unsafe { ::libc::tcgetattr(0, termios.as_mut_ptr()) })?; let mut termios = unsafe { termios.assume_init() }; termios.c_lflag |= ICANON; - unsafe { tcsetattr(fd, TCSANOW, &termios) }; - tty + unsafe { tcsetattr(0, TCSANOW, &termios) }; + Ok(()) + } + + pub fn set_mode_no_line_feeds() -> Result<(), anyhow::Error> { + let mut termios = mem::MaybeUninit::::uninit(); + io_result(unsafe { ::libc::tcgetattr(0, termios.as_mut_ptr()) })?; + let mut termios = unsafe { termios.assume_init() }; + + termios.c_lflag &= !ONLCR; + + unsafe { tcsetattr(0, TCSANOW, &termios) }; + Ok(()) + } + + pub fn set_mode_line_feeds() -> Result<(), anyhow::Error> { + let mut termios = mem::MaybeUninit::::uninit(); + io_result(unsafe { ::libc::tcgetattr(0, termios.as_mut_ptr()) })?; + let mut termios = unsafe { termios.assume_init() }; + + termios.c_lflag |= ONLCR; + + unsafe { tcsetattr(0, TCSANOW, &termios) }; + Ok(()) + } +} + +#[cfg(not(unix))] +mod sys { + pub fn reset() -> Result<(), anyhow::Error> { + Ok(()) + } + + pub fn is_stdin_tty() -> bool { + false + } + + pub fn is_stdout_tty() -> bool { + false + } + + pub fn is_stderr_tty() -> bool { + false + } + + pub fn is_mode_echo() -> bool { + true } - // #[cfg(unix)] - // pub fn set_mode_no_line_feeds() -> std::fs::File { - // let tty = std::fs::File::open("/dev/tty").unwrap(); - // let fd = tty.as_raw_fd(); + pub fn is_mode_line_buffering() -> bool { + true + } - // let mut termios = mem::MaybeUninit::::uninit(); - // io_result(unsafe { ::libc::tcgetattr(fd, termios.as_mut_ptr()) }).unwrap(); - // let mut termios = unsafe { termios.assume_init() }; + pub fn is_mode_line_feeds() -> bool { + true + } - // termios.c_lflag &= !::termios::ONLCR; + pub fn set_mode_no_echo() -> Result<(), anyhow::Error> { + Ok(()) + } - // unsafe { tcsetattr(fd, TCSANOW, &termios) }; - // tty - // } + pub fn set_mode_echo() -> Result<(), anyhow::Error> { + Ok(()) + } - // #[cfg(unix)] - // pub fn set_mode_line_feeds() -> std::fs::File { - // let tty = std::fs::File::open("/dev/tty").unwrap(); - // let fd = tty.as_raw_fd(); + pub fn set_mode_no_line_buffered() -> Result<(), anyhow::Error> { + Ok(()) + } - // let mut termios = mem::MaybeUninit::::uninit(); - // io_result(unsafe { ::libc::tcgetattr(fd, termios.as_mut_ptr()) }).unwrap(); - // let mut termios = unsafe { termios.assume_init() }; + pub fn set_mode_line_buffered() -> Result<(), anyhow::Error> { + Ok(()) + } - // termios.c_lflag |= ONLCR; + pub fn set_mode_no_line_feeds() -> Result<(), anyhow::Error> { + Ok(()) + } - // unsafe { tcsetattr(fd, TCSANOW, &termios) }; - // tty - // } + pub fn set_mode_line_feeds() -> Result<(), anyhow::Error> { + Ok(()) + } } diff --git a/lib/wasi/src/runtime/mod.rs b/lib/wasi/src/runtime/mod.rs index c84e236aa6d..3dfe9e0e1cd 100644 --- a/lib/wasi/src/runtime/mod.rs +++ b/lib/wasi/src/runtime/mod.rs @@ -1,13 +1,17 @@ pub mod task_manager; +use crate::{http::DynHttpClient, os::TtyBridge, WasiTtyState}; + pub use self::task_manager::{SpawnType, SpawnedMemory, VirtualTaskManager}; -use std::{fmt, sync::Arc}; +use std::{ + fmt, + sync::{Arc, Mutex}, +}; +use derivative::Derivative; use virtual_net::{DynVirtualNetworking, VirtualNetworking}; -use crate::{http::DynHttpClient, os::TtyBridge}; - #[cfg(feature = "sys")] pub type ArcTunables = std::sync::Arc; @@ -52,18 +56,45 @@ where } /// Get access to the TTY used by the environment. - fn tty(&self) -> Option<&dyn TtyBridge> { + fn tty(&self) -> Option<&(dyn TtyBridge + Send + Sync)> { None } } -#[derive(Clone, Debug)] +#[derive(Debug, Default)] +pub struct DefaultTty { + state: Mutex, +} + +impl TtyBridge for DefaultTty { + fn reset(&self) { + let mut state = self.state.lock().unwrap(); + state.echo = false; + state.line_buffered = false; + state.line_feeds = false + } + + fn tty_get(&self) -> WasiTtyState { + let state = self.state.lock().unwrap(); + state.clone() + } + + fn tty_set(&self, tty_state: WasiTtyState) { + let mut state = self.state.lock().unwrap(); + *state = tty_state; + } +} + +#[derive(Clone, Derivative)] +#[derivative(Debug)] pub struct PluggableRuntimeImplementation { pub rt: Arc, pub networking: DynVirtualNetworking, pub http_client: Option, #[cfg(feature = "sys")] pub engine: Option, + #[derivative(Debug = "ignore")] + pub tty: Option>, } impl PluggableRuntimeImplementation { @@ -79,6 +110,10 @@ impl PluggableRuntimeImplementation { self.engine = engine; } + pub fn set_tty(&mut self, tty: Arc) { + self.tty = Some(tty); + } + pub fn new(rt: Arc) -> Self { // TODO: the cfg flags below should instead be handled by separate implementations. cfg_if::cfg_if! { @@ -104,6 +139,7 @@ impl PluggableRuntimeImplementation { http_client, #[cfg(feature = "sys")] engine: None, + tty: None, } } } @@ -141,4 +177,8 @@ impl WasiRuntime for PluggableRuntimeImplementation { fn task_manager(&self) -> &Arc { &self.rt } + + fn tty(&self) -> Option<&(dyn TtyBridge + Send + Sync)> { + self.tty.as_deref() + } } diff --git a/lib/wasi/src/runtime/task_manager/mod.rs b/lib/wasi/src/runtime/task_manager/mod.rs index d11d7efae12..bb848e42227 100644 --- a/lib/wasi/src/runtime/task_manager/mod.rs +++ b/lib/wasi/src/runtime/task_manager/mod.rs @@ -26,7 +26,7 @@ pub struct SpawnedMemory { pub enum SpawnType { Create, CreateWithType(SpawnedMemory), - NewThread(VMMemory), + NewThread(VMMemory, MemoryType), } /// An implementation of task management diff --git a/lib/wasi/src/runtime/task_manager/tokio.rs b/lib/wasi/src/runtime/task_manager/tokio.rs index 48a35887cd8..f50e7fcf35a 100644 --- a/lib/wasi/src/runtime/task_manager/tokio.rs +++ b/lib/wasi/src/runtime/task_manager/tokio.rs @@ -81,7 +81,7 @@ impl VirtualTaskManager for TokioTaskManager { WasiThreadError::MemoryCreateFailed }) .map(|m| Some(m.into())), - SpawnType::NewThread(mem) => Ok(Some(mem)), + SpawnType::NewThread(mem, _) => Ok(Some(mem)), SpawnType::Create => Ok(None), } } diff --git a/lib/wasi/src/state/builder.rs b/lib/wasi/src/state/builder.rs index c761d9cb323..54e099ba65d 100644 --- a/lib/wasi/src/state/builder.rs +++ b/lib/wasi/src/state/builder.rs @@ -10,12 +10,14 @@ use rand::Rng; use thiserror::Error; use virtual_fs::{ArcFile, FsError, TmpFileSystem, VirtualFile}; use wasmer::{AsStoreMut, Instance, Module}; +use wasmer_wasix_types::wasi::Errno; use crate::{ bin_factory::{BinFactory, ModuleCache}, capabilities::Capabilities, fs::{WasiFs, WasiFsRoot, WasiInodes}, os::task::control_plane::{ControlPlaneConfig, ControlPlaneError, WasiControlPlane}, + parse_static_webc, state::WasiState, syscalls::types::{__WASI_STDERR_FILENO, __WASI_STDIN_FILENO, __WASI_STDOUT_FILENO}, PluggableRuntimeImplementation, WasiEnv, WasiFunctionEnv, WasiRuntime, WasiRuntimeError, @@ -62,6 +64,9 @@ pub struct WasiEnvBuilder { /// List of webc dependencies to be injected. pub(super) uses: Vec, + /// List ofsupplied webc packages to use instead of downloading from the registry. + pub(super) include_webcs: Vec, + /// List of host commands to map into the WASI instance. pub(super) map_commands: HashMap, @@ -76,6 +81,7 @@ impl std::fmt::Debug for WasiEnvBuilder { .field("envs", &self.envs) .field("preopens", &self.preopens) .field("uses", &self.uses) + .field("include_webcs", &self.include_webcs) .field("setup_fs_fn exists", &self.setup_fs_fn.is_some()) .field("stdout_override exists", &self.stdout.is_some()) .field("stderr_override exists", &self.stderr.is_some()) @@ -106,6 +112,8 @@ pub enum WasiStateCreationError { FileSystemError(FsError), #[error("wasi inherit error: `{0}`")] WasiInheritError(String), + #[error("wasi include package: `{0}`")] + WasiIncludePackageError(String), #[error("control plane error")] ControlPlane(#[from] ControlPlaneError), } @@ -260,6 +268,26 @@ impl WasiEnvBuilder { self } + /// Includes a webc package to use instead of downloading them from the registry + pub fn include_webc(mut self, webc: Name) -> Self + where + Name: AsRef, + { + self.include_webcs.push(webc.as_ref().to_string()); + self + } + + /// Adds a list of webc packages to use instead of downloading them from the registry + pub fn include_webcs(mut self, webcs: I) -> Self + where + I: IntoIterator, + { + webcs.into_iter().for_each(|webc| { + self.include_webcs.push(webc); + }); + self + } + /// Map an atom to a local binary #[cfg(feature = "sys")] pub fn map_command(mut self, name: Name, target: Target) -> Self @@ -673,6 +701,27 @@ impl WasiEnvBuilder { // TODO: this method should not exist - must have unified construction flow! let module_cache = self.compiled_modules.unwrap_or_default(); + + // Add the supplied webc packages to the module cache + for include_webc in self.include_webcs.iter() { + let data = std::fs::read(include_webc.as_str()) + .map_err(|err| WasiStateCreationError::WasiIncludePackageError(err.to_string()))?; + let package = parse_static_webc(data) + .map_err(|err| WasiStateCreationError::WasiIncludePackageError(err.to_string()))?; + + let mut package_name = package.package_name.to_string(); + module_cache.add_webc(package_name.as_ref(), package.clone()); + for version_part in package.version.split('.') { + if !package_name.contains('@') { + package_name.push('@'); + } else { + package_name.push('.'); + } + package_name.push_str(version_part); + module_cache.add_webc(package_name.as_ref(), package.clone()); + } + } + let runtime = self .runtime .unwrap_or_else(|| Arc::new(PluggableRuntimeImplementation::default())); @@ -766,8 +815,8 @@ impl WasiEnvBuilder { ); let exit_code = match &res { - Ok(_) => 0, - Err(err) => err.as_exit_code().unwrap_or(1), + Ok(_) => Errno::Success.into(), + Err(err) => err.as_exit_code().unwrap_or_else(|| Errno::Noexec.into()), }; env.cleanup(store, Some(exit_code)); diff --git a/lib/wasi/src/state/env.rs b/lib/wasi/src/state/env.rs index ce108df9f86..f0911bea6fb 100644 --- a/lib/wasi/src/state/env.rs +++ b/lib/wasi/src/state/env.rs @@ -30,7 +30,7 @@ use crate::{ }, }, runtime::SpawnType, - syscalls::platform_clock_time_get, + syscalls::{__asyncify_light, platform_clock_time_get}, SpawnedMemory, VirtualTaskManager, WasiControlPlane, WasiEnvBuilder, WasiError, WasiFunctionEnv, WasiRuntime, WasiRuntimeError, WasiStateCreationError, WasiVFork, DEFAULT_STACK_SIZE, @@ -60,13 +60,13 @@ pub struct WasiInstanceHandles { #[allow(dead_code)] pub(crate) start: Option>, - /// Function thats invoked to initialize the WASM module (nane = "_initialize") + /// Function thats invoked to initialize the WASM module (name = "_initialize") #[derivative(Debug = "ignore")] // TODO: review allow... #[allow(dead_code)] pub(crate) initialize: Option>, - /// Represents the callback for spawning a thread (name = "_start_thread") + /// Represents the callback for spawning a thread (name = "wasi_thread_start") /// (due to limitations with i64 in browsers the parameters are broken into i32 pairs) /// [this takes a user_data field] #[derivative(Debug = "ignore")] @@ -155,7 +155,7 @@ impl WasiInstanceHandles { .ok(), thread_spawn: instance .exports - .get_typed_function(store, "_start_thread") + .get_typed_function(store, "wasi_thread_start") .ok(), react: instance.exports.get_typed_function(store, "_react").ok(), signal: instance @@ -261,7 +261,6 @@ impl WasiEnvInit { } /// The environment provided to the WASI imports. -#[derive(Debug)] pub struct WasiEnv { pub control_plane: WasiControlPlane, /// Represents the process this environment is attached to @@ -270,8 +269,8 @@ pub struct WasiEnv { pub thread: WasiThread, /// Represents a fork of the process that is currently in play pub vfork: Option, - /// Base stack pointer for the memory stack - pub stack_base: u64, + /// End of the stack memory that is allocated for this thread + pub stack_end: u64, /// Start of the stack memory that is allocated for this thread pub stack_start: u64, /// Seed used to rotate around the events returned by `poll_oneoff` @@ -293,6 +292,12 @@ pub struct WasiEnv { pub capabilities: Capabilities, } +impl std::fmt::Debug for WasiEnv { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "env(pid={}, tid={})", self.pid().raw(), self.tid().raw()) + } +} + // FIXME: remove unsafe impls! // Added because currently WasiEnv can hold a wasm_bindgen::JsValue via wasmer::Module. #[cfg(feature = "js")] @@ -321,7 +326,7 @@ impl WasiEnv { poll_seed: self.poll_seed, thread: self.thread.clone(), vfork: self.vfork.as_ref().map(|v| v.duplicate()), - stack_base: self.stack_base, + stack_end: self.stack_end, stack_start: self.stack_start, state: self.state.clone(), bin_factory: self.bin_factory.clone(), @@ -351,7 +356,7 @@ impl WasiEnv { thread, vfork: None, poll_seed: 0, - stack_base: self.stack_base, + stack_end: self.stack_end, stack_start: self.stack_start, bin_factory, state, @@ -390,7 +395,7 @@ impl WasiEnv { thread: thread.as_thread(), vfork: None, poll_seed: 0, - stack_base: DEFAULT_STACK_SIZE, + stack_end: DEFAULT_STACK_SIZE, stack_start: 0, state: Arc::new(init.state), inner: None, @@ -470,7 +475,7 @@ impl WasiEnv { tracing::error!("wasi[{}]::wasm instantiate error ({})", pid, err); func_env .data(&store) - .cleanup(Some(Errno::Noexec as ExitCode)); + .blocking_cleanup(Some(Errno::Noexec.into())); return Err(err.into()); } }; @@ -485,7 +490,7 @@ impl WasiEnv { tracing::error!("wasi[{}]::wasi initialize error ({})", pid, err); func_env .data(&store) - .cleanup(Some(Errno::Noexec as ExitCode)); + .blocking_cleanup(Some(Errno::Noexec.into())); return Err(err.into()); } @@ -495,7 +500,7 @@ impl WasiEnv { if let Err(err) = crate::run_wasi_func_start(initialize, &mut store) { func_env .data(&store) - .cleanup(Some(Errno::Noexec as ExitCode)); + .blocking_cleanup(Some(Errno::Noexec.into())); return Err(err); } } @@ -543,8 +548,8 @@ impl WasiEnv { let signal_cnt = signals.len(); for sig in signals { if sig == Signal::Sigint || sig == Signal::Sigquit || sig == Signal::Sigkill { - env.thread.set_status_finished(Ok(Errno::Intr as u32)); - return Err(WasiError::Exit(Errno::Intr as u32)); + env.thread.set_status_finished(Ok(Errno::Intr.into())); + return Err(WasiError::Exit(Errno::Intr.into())); } else { trace!("wasi[{}]::signal-ignored: {:?}", env.pid(), sig); } @@ -572,7 +577,7 @@ impl WasiEnv { .thread .has_signal(&[Signal::Sigint, Signal::Sigquit, Signal::Sigkill]) { - env.thread.set_status_finished(Ok(Errno::Intr as u32)); + env.thread.set_status_finished(Ok(Errno::Intr.into())); } return Ok(Ok(false)); } @@ -644,7 +649,7 @@ impl WasiEnv { ctx.data().pid(), runtime_err ); - return Err(WasiError::Exit(Errno::Intr as ExitCode)); + return Err(WasiError::Exit(Errno::Intr.into())); } } } @@ -656,13 +661,13 @@ impl WasiEnv { } /// Returns an exit code if the thread or process has been forced to exit - pub fn should_exit(&self) -> Option { + pub fn should_exit(&self) -> Option { // Check for forced exit if let Some(forced_exit) = self.thread.try_join() { - return Some(forced_exit.unwrap_or(Errno::Child as u32)); + return Some(forced_exit.unwrap_or_else(|_| Errno::Child.into())); } if let Some(forced_exit) = self.process.try_join() { - return Some(forced_exit.unwrap_or(Errno::Child as u32)); + return Some(forced_exit.unwrap_or_else(|_| Errno::Child.into())); } None } @@ -892,41 +897,39 @@ impl WasiEnv { /// Cleans up all the open files (if this is the main thread) #[allow(clippy::await_holding_lock)] - pub fn cleanup(&self, exit_code: Option) { + pub fn blocking_cleanup(&self, exit_code: Option) { + __asyncify_light(self, None, async { + self.cleanup(exit_code).await; + Ok(()) + }) + .ok(); + } + + /// Cleans up all the open files (if this is the main thread) + #[allow(clippy::await_holding_lock)] + pub async fn cleanup(&self, exit_code: Option) { const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10); // If this is the main thread then also close all the files if self.thread.is_main() { trace!("wasi[{}]:: cleaning up open file handles", self.pid()); - let state = self.state.clone(); - let tasks = self.tasks().clone(); - - let (tx, rx) = std::sync::mpsc::channel(); - self.tasks() - .task_dedicated(Box::new(move || { - let tasks_inner = tasks.clone(); - tasks.runtime().block_on(async { - let timeout = tasks_inner.sleep_now(CLEANUP_TIMEOUT); - tokio::select! { - _ = timeout => { - tracing::warn!( - "WasiEnv::cleanup has timed out after {CLEANUP_TIMEOUT:?}" - ); - }, - _ = state.fs.close_all() => { } - } - }); - tx.send(()).ok(); - })) - .ok(); - rx.recv().ok(); + // Perform the clean operation using the asynchronous runtime + let timeout = self.tasks().sleep_now(CLEANUP_TIMEOUT); + tokio::select! { + _ = timeout => { + tracing::warn!( + "WasiEnv::cleanup has timed out after {CLEANUP_TIMEOUT:?}" + ); + }, + _ = self.state.fs.close_all() => { } + } // Now send a signal that the thread is terminated self.process.signal_process(Signal::Sigquit); // Terminate the process - let exit_code = exit_code.unwrap_or(Errno::Canceled as ExitCode); + let exit_code = exit_code.unwrap_or_else(|| Errno::Canceled.into()); self.process.terminate(exit_code); } } diff --git a/lib/wasi/src/state/func_env.rs b/lib/wasi/src/state/func_env.rs index 2649ca3927c..9c784a8c53a 100644 --- a/lib/wasi/src/state/func_env.rs +++ b/lib/wasi/src/state/func_env.rs @@ -106,7 +106,7 @@ impl WasiFunctionEnv { } else { DEFAULT_STACK_SIZE }; - self.data_mut(store).stack_base = stack_base; + self.data_mut(store).stack_end = stack_base; Ok(()) } @@ -182,6 +182,6 @@ impl WasiFunctionEnv { } // Cleans up all the open files (if this is the main thread) - self.data(store).cleanup(exit_code); + self.data(store).blocking_cleanup(exit_code); } } diff --git a/lib/wasi/src/syscalls/mod.rs b/lib/wasi/src/syscalls/mod.rs index 08622ff8449..f76e6b5370f 100644 --- a/lib/wasi/src/syscalls/mod.rs +++ b/lib/wasi/src/syscalls/mod.rs @@ -690,7 +690,7 @@ pub(crate) fn get_current_time_in_nanos() -> Result { } pub(crate) fn get_stack_base(mut ctx: &mut FunctionEnvMut<'_, WasiEnv>) -> u64 { - ctx.data().stack_base + ctx.data().stack_end } pub(crate) fn get_stack_start(mut ctx: &mut FunctionEnvMut<'_, WasiEnv>) -> u64 { @@ -769,7 +769,7 @@ pub(crate) fn get_memory_stack( }; let env = ctx.data(); let memory = env.memory_view(&ctx); - let stack_offset = env.stack_base - stack_pointer; + let stack_offset = env.stack_end - stack_pointer; // Read the memory stack into a vector let memory_stack_ptr = WasmPtr::::new( @@ -838,7 +838,7 @@ where Ok(a) => a, Err(err) => { warn!("unable to get the memory stack - {}", err); - return Err(WasiError::Exit(Errno::Fault as ExitCode)); + return Err(WasiError::Exit(Errno::Unknown.into())); } }; @@ -852,7 +852,7 @@ where unwind_pointer + (std::mem::size_of::<__wasi_asyncify_t>() as u64); let unwind_data = __wasi_asyncify_t:: { start: wasi_try_ok!(unwind_data_start.try_into().map_err(|_| Errno::Overflow)), - end: wasi_try_ok!(env.stack_base.try_into().map_err(|_| Errno::Overflow)), + end: wasi_try_ok!(env.stack_end.try_into().map_err(|_| Errno::Overflow)), }; let unwind_data_ptr: WasmPtr<__wasi_asyncify_t, M> = WasmPtr::new(wasi_try_ok!(unwind_pointer @@ -867,20 +867,22 @@ where asyncify_start_unwind.call(&mut ctx, asyncify_data); } else { warn!("failed to unwind the stack because the asyncify_start_rewind export is missing"); - return Err(WasiError::Exit(128)); + return Err(WasiError::Exit(Errno::Noexec.into())); } // Set callback that will be invoked when this process finishes let env = ctx.data(); let unwind_stack_begin: u64 = unwind_data.start.into(); - let unwind_space = env.stack_base - env.stack_start; + let total_stack_space = env.stack_end - env.stack_start; let func = ctx.as_ref(); trace!( - "wasi[{}:{}]::unwinding (memory_stack_size={} unwind_space={})", + stack_end = env.stack_end, + stack_start = env.stack_start, + "wasi[{}:{}]::unwinding (used_stack_space={} total_stack_space={})", ctx.data().pid(), ctx.data().tid(), memory_stack.len(), - unwind_space + total_stack_space ); ctx.as_store_mut().on_called(move |mut store| { let mut ctx = func.into_mut(&mut store); @@ -951,7 +953,7 @@ pub(crate) fn rewind( Ok(a) => a, Err(err) => { warn!("snapshot restore failed - the store snapshot could not be deserialized"); - return Errno::Fault; + return Errno::Unknown; } }; crate::utils::store::restore_snapshot(&mut ctx.as_store_mut(), &store_snapshot); @@ -963,16 +965,16 @@ pub(crate) fn rewind( let rewind_data_start = rewind_pointer + (std::mem::size_of::<__wasi_asyncify_t>() as u64); let rewind_data_end = rewind_data_start + (rewind_stack.len() as u64); - if rewind_data_end > env.stack_base { + if rewind_data_end > env.stack_end { warn!( "attempting to rewind a stack bigger than the allocated stack space ({} > {})", - rewind_data_end, env.stack_base + rewind_data_end, env.stack_end ); return Errno::Overflow; } let rewind_data = __wasi_asyncify_t:: { start: wasi_try!(rewind_data_end.try_into().map_err(|_| Errno::Overflow)), - end: wasi_try!(env.stack_base.try_into().map_err(|_| Errno::Overflow)), + end: wasi_try!(env.stack_end.try_into().map_err(|_| Errno::Overflow)), }; let rewind_data_ptr: WasmPtr<__wasi_asyncify_t, M> = WasmPtr::new(wasi_try!(rewind_pointer @@ -997,7 +999,7 @@ pub(crate) fn rewind( asyncify_start_rewind.call(&mut ctx, asyncify_data); } else { warn!("failed to rewind the stack because the asyncify_start_rewind export is missing"); - return Errno::Fault; + return Errno::Noexec; } Errno::Success @@ -1054,9 +1056,9 @@ pub(crate) fn _prepare_wasi(wasi_env: &mut WasiEnv, args: Option>) { pub(crate) fn conv_bus_err_to_exit_code(err: VirtualBusError) -> ExitCode { match err { - VirtualBusError::AccessDenied => Errno::Access as ExitCode, - VirtualBusError::NotFound => Errno::Noent as ExitCode, - VirtualBusError::Unsupported => Errno::Noexec as ExitCode, - _ => Errno::Inval as ExitCode, + VirtualBusError::AccessDenied => Errno::Access.into(), + VirtualBusError::NotFound => Errno::Noent.into(), + VirtualBusError::Unsupported => Errno::Noexec.into(), + _ => Errno::Inval.into(), } } diff --git a/lib/wasi/src/syscalls/wasi/fd_close.rs b/lib/wasi/src/syscalls/wasi/fd_close.rs index d5fcaec6e94..cd270b4f52a 100644 --- a/lib/wasi/src/syscalls/wasi/fd_close.rs +++ b/lib/wasi/src/syscalls/wasi/fd_close.rs @@ -12,7 +12,7 @@ use crate::syscalls::*; /// If `fd` is a directory /// - `Errno::Badf` /// If `fd` is invalid or not open -#[instrument(level = "debug", skip_all, fields(fd), ret, err)] +#[instrument(level = "debug", skip_all, fields(pid = ctx.data().process.pid().raw(), fd), ret, err)] pub fn fd_close(mut ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd) -> Result { let env = ctx.data(); let (_, mut state) = env.get_memory_and_wasi_state(&ctx, 0); diff --git a/lib/wasi/src/syscalls/wasi/path_filestat_get.rs b/lib/wasi/src/syscalls/wasi/path_filestat_get.rs index ea1fa40361c..c722ff7d095 100644 --- a/lib/wasi/src/syscalls/wasi/path_filestat_get.rs +++ b/lib/wasi/src/syscalls/wasi/path_filestat_get.rs @@ -15,7 +15,7 @@ use crate::syscalls::*; /// Output: /// - `__wasi_file_stat_t *buf` /// The location where the metadata will be stored -#[instrument(level = "trace", skip_all, fields(fd, path = field::Empty))] +#[instrument(level = "trace", skip_all, fields(fd, path = field::Empty), ret)] pub fn path_filestat_get( ctx: FunctionEnvMut<'_, WasiEnv>, fd: WasiFd, @@ -33,7 +33,7 @@ pub fn path_filestat_get( if path_string.starts_with("./") { path_string = ctx.data().state.fs.relative_path_to_absolute(path_string); } - Span::current().record("path", path_string.as_str()); + tracing::trace!(path = path_string.as_str()); let stat = wasi_try!(path_filestat_get_internal( &memory, diff --git a/lib/wasi/src/syscalls/wasi/proc_exit.rs b/lib/wasi/src/syscalls/wasi/proc_exit.rs index ac1d5c95c15..e0a2d885f21 100644 --- a/lib/wasi/src/syscalls/wasi/proc_exit.rs +++ b/lib/wasi/src/syscalls/wasi/proc_exit.rs @@ -8,13 +8,12 @@ use crate::syscalls::*; /// Inputs: /// - `ExitCode` /// Exit code to return to the operating system -#[instrument(level = "debug", skip_all, fields(code))] +#[instrument(level = "debug", skip_all)] pub fn proc_exit( mut ctx: FunctionEnvMut<'_, WasiEnv>, code: ExitCode, ) -> Result<(), WasiError> { - // Set the exit code for this process - ctx.data().thread.set_status_finished(Ok(code as u32)); + debug!(%code); // If we are in a vfork we need to return to the point we left off if let Some(mut vfork) = ctx.data_mut().vfork.take() { @@ -34,16 +33,16 @@ pub fn proc_exit( // If the return value offset is within the memory stack then we need // to update it here rather than in the real memory let pid_offset: u64 = vfork.pid_offset; - if pid_offset >= wasi_env.stack_start && pid_offset < wasi_env.stack_base { + if pid_offset >= wasi_env.stack_start && pid_offset < wasi_env.stack_end { // Make sure its within the "active" part of the memory stack - let offset = wasi_env.stack_base - pid_offset; + let offset = wasi_env.stack_end - pid_offset; if offset as usize > memory_stack.len() { warn!( "fork failed - the return value (pid) is outside of the active part of the memory stack ({} vs {})", offset, memory_stack.len() ); - return Err(WasiError::Exit(Errno::Fault as u32)); + return Err(WasiError::Exit(Errno::Memviolation.into())); } // Update the memory stack with the new PID @@ -56,7 +55,7 @@ pub fn proc_exit( warn!( "fork failed - the return value (pid) is not being returned on the stack - which is not supported" ); - return Err(WasiError::Exit(Errno::Fault as u32)); + return Err(WasiError::Exit(Errno::Memviolation.into())); } // Jump back to the vfork point and current on execution @@ -71,7 +70,7 @@ pub fn proc_exit( Errno::Success => OnCalledAction::InvokeAgain, err => { warn!("fork failed - could not rewind the stack - errno={}", err); - OnCalledAction::Trap(Box::new(WasiError::Exit(Errno::Fault as u32))) + OnCalledAction::Trap(Box::new(WasiError::Exit(err.into()))) } } })?; diff --git a/lib/wasi/src/syscalls/wasix/getcwd.rs b/lib/wasi/src/syscalls/wasix/getcwd.rs index 59afb402a41..5df0f8bf825 100644 --- a/lib/wasi/src/syscalls/wasix/getcwd.rs +++ b/lib/wasi/src/syscalls/wasix/getcwd.rs @@ -4,7 +4,7 @@ use crate::syscalls::*; /// ### `getcwd()` /// Returns the current working directory /// If the path exceeds the size of the buffer then this function -/// will fill the path_len with the needed size and return EOVERFLOW +/// will return ERANGE #[instrument(level = "debug", skip_all, fields(path = field::Empty, max_path_len = field::Empty), ret)] pub fn getcwd( ctx: FunctionEnvMut<'_, WasiEnv>, @@ -18,28 +18,35 @@ pub fn getcwd( Span::current().record("path", cur_dir.as_str()); let max_path_len = wasi_try_mem!(path_len.read(&memory)); - let path_slice = wasi_try_mem!(path.slice(&memory, max_path_len)); - let max_path_len: u64 = max_path_len.into(); - Span::current().record("max_path_len", max_path_len); + let max_path_len64: u64 = max_path_len.into(); + let path_slice = match (path.is_null(), max_path_len64) { + (true, _) => None, + (_, 0) => None, + (_, _) => Some(wasi_try_mem!(path.slice(&memory, max_path_len))), + }; + Span::current().record("max_path_len", max_path_len64); let cur_dir = cur_dir.as_bytes(); wasi_try_mem!(path_len.write(&memory, wasi_try!(to_offset::(cur_dir.len())))); - if cur_dir.len() as u64 >= max_path_len { - return Errno::Overflow; + if cur_dir.len() as u64 > max_path_len64 { + return Errno::Range; } - let cur_dir = { - let mut u8_buffer = vec![0; max_path_len as usize]; - let cur_dir_len = cur_dir.len(); - if (cur_dir_len as u64) < max_path_len { - u8_buffer[..cur_dir_len].clone_from_slice(cur_dir); - u8_buffer[cur_dir_len] = 0; - } else { - return Errno::Overflow; - } - u8_buffer - }; + if let Some(path_slice) = path_slice { + let cur_dir = { + let mut u8_buffer = vec![0; max_path_len64 as usize]; + let cur_dir_len = cur_dir.len(); + if (cur_dir_len as u64) <= max_path_len64 { + u8_buffer[..cur_dir_len].clone_from_slice(cur_dir); + } else { + return Errno::Range; + } + u8_buffer + }; - wasi_try_mem!(path_slice.write_slice(&cur_dir[..])); - Errno::Success + wasi_try_mem!(path_slice.write_slice(cur_dir.as_ref())); + Errno::Success + } else { + Errno::Inval + } } diff --git a/lib/wasi/src/syscalls/wasix/proc_exec.rs b/lib/wasi/src/syscalls/wasix/proc_exec.rs index e8a16f2a6d4..40371d0c220 100644 --- a/lib/wasi/src/syscalls/wasix/proc_exec.rs +++ b/lib/wasi/src/syscalls/wasix/proc_exec.rs @@ -26,12 +26,12 @@ pub fn proc_exec( let memory = ctx.data().memory_view(&ctx); let mut name = name.read_utf8_string(&memory, name_len).map_err(|err| { warn!("failed to execve as the name could not be read - {}", err); - WasiError::Exit(Errno::Fault as ExitCode) + WasiError::Exit(Errno::Inval.into()) })?; Span::current().record("name", name.as_str()); let args = args.read_utf8_string(&memory, args_len).map_err(|err| { warn!("failed to execve as the args could not be read - {}", err); - WasiError::Exit(Errno::Fault as ExitCode) + WasiError::Exit(Errno::Inval.into()) })?; let args: Vec<_> = args .split(&['\n', '\r']) @@ -55,7 +55,7 @@ pub fn proc_exec( Ok(a) => a, Err(err) => { warn!("failed to create subprocess for fork - {}", err); - return Err(WasiError::Exit(Errno::Fault as ExitCode)); + return Err(WasiError::Exit(err.into())); } } }; @@ -66,7 +66,9 @@ pub fn proc_exec( // with the forked WasiEnv, then do a longjmp back to the vfork point. if let Some(mut vfork) = ctx.data_mut().vfork.take() { // We will need the child pid later - let child_pid = ctx.data().process.pid(); + let child_process = ctx.data().process.clone(); + let child_pid = child_process.pid(); + let child_finished = child_process.finished; // Restore the WasiEnv to the point when we vforked std::mem::swap(&mut vfork.env.inner, &mut ctx.data_mut().inner); @@ -76,13 +78,13 @@ pub fn proc_exec( _prepare_wasi(&mut wasi_env, Some(args)); // Recrod the stack offsets before we give up ownership of the wasi_env - let stack_base = wasi_env.stack_base; + let stack_base = wasi_env.stack_end; let stack_start = wasi_env.stack_start; // Spawn a new process with this current execution environment - let mut err_exit_code = -2i32 as u32; + let mut err_exit_code: ExitCode = Errno::Success.into(); - let mut process = { + { let bin_factory = Box::new(ctx.data().bin_factory.clone()); let tasks = wasi_env.tasks().clone(); @@ -90,7 +92,7 @@ pub fn proc_exec( let mut config = Some(wasi_env); match bin_factory.try_built_in(name.clone(), Some(&ctx), &mut new_store, &mut config) { - Ok(a) => Some(a), + Ok(a) => {} Err(err) => { if err != VirtualBusError::NotFound { error!("builtin failed - {}", err); @@ -99,7 +101,7 @@ pub fn proc_exec( let new_store = new_store.take().unwrap(); let env = config.take().unwrap(); - let (process, c) = tasks.block_on(async move { + tasks.block_on(async { let name_inner = name.clone(); let ret = bin_factory.spawn( name_inner, @@ -108,9 +110,15 @@ pub fn proc_exec( ) .await; match ret { - Ok(ret) => (Some(ret), ctx), + Ok(ret) => { + trace!(%child_pid, "spawned sub-process"); + }, Err(err) => { err_exit_code = conv_bus_err_to_exit_code(err); + + debug!(%child_pid, "process failed with (err={})", err_exit_code); + child_finished.set_finished(Ok(err_exit_code)); + warn!( "failed to execve as the process could not be spawned (vfork) - {}", err @@ -120,35 +128,13 @@ pub fn proc_exec( format!("wasm execute failed [{}] - {}\n", name.as_str(), err) .as_bytes(), ).await; - (None, ctx) } } - }); - ctx = c; - process + }) } } }; - // If no process was created then we create a dummy one so that an - // exit code can be processed - let process = match process { - Some(a) => { - trace!("spawned sub-process (pid={})", child_pid.raw()); - a - } - None => { - debug!("process failed with (err={})", err_exit_code); - OwnedTaskStatus::new(TaskStatus::Finished(Ok(err_exit_code))).handle() - } - }; - - // Add the process to the environment state - { - let mut inner = ctx.data().process.write(); - inner.bus_processes.insert(child_pid, process); - } - let mut memory_stack = vfork.memory_stack; let rewind_stack = vfork.rewind_stack; let store_data = vfork.store_data; @@ -191,7 +177,7 @@ pub fn proc_exec( Errno::Success => OnCalledAction::InvokeAgain, err => { warn!("fork failed - could not rewind the stack - errno={}", err); - OnCalledAction::Trap(Box::new(WasiError::Exit(Errno::Fault as u32))) + OnCalledAction::Trap(Box::new(WasiError::Exit(err.into()))) } } })?; @@ -249,25 +235,28 @@ pub fn proc_exec( let (tx, rx) = std::sync::mpsc::channel(); let tasks_inner = tasks.clone(); tasks.block_on(Box::pin(async move { - let code = process.wait_finished().await.unwrap_or(Errno::Child as u32); + let code = process + .wait_finished() + .await + .unwrap_or_else(|_| Errno::Child.into()); tx.send(code); })); let exit_code = rx.recv().unwrap(); - OnCalledAction::Trap(Box::new(WasiError::Exit(exit_code as ExitCode))) + OnCalledAction::Trap(Box::new(WasiError::Exit(exit_code))) } Ok(Err(err)) => { warn!( "failed to execve as the process could not be spawned (fork)[0] - {}", err ); - OnCalledAction::Trap(Box::new(WasiError::Exit(Errno::Noexec as ExitCode))) + OnCalledAction::Trap(Box::new(WasiError::Exit(Errno::Noexec.into()))) } Err(err) => { warn!( "failed to execve as the process could not be spawned (fork)[1] - {}", err ); - OnCalledAction::Trap(Box::new(WasiError::Exit(Errno::Noexec as ExitCode))) + OnCalledAction::Trap(Box::new(WasiError::Exit(Errno::Noexec.into()))) } } })?; diff --git a/lib/wasi/src/syscalls/wasix/proc_fork.rs b/lib/wasi/src/syscalls/wasix/proc_fork.rs index 3078674fd05..aafa0bd41aa 100644 --- a/lib/wasi/src/syscalls/wasix/proc_fork.rs +++ b/lib/wasi/src/syscalls/wasix/proc_fork.rs @@ -7,7 +7,7 @@ use wasmer::vm::VMMemory; /// Forks the current process into a new subprocess. If the function /// returns a zero then its the new subprocess. If it returns a positive /// number then its the current process and the $pid represents the child. -#[instrument(level = "debug", skip_all, fields(copy_memory, pid = field::Empty), ret, err)] +#[instrument(level = "debug", skip_all, fields(pid = ctx.data().process.pid().raw()), ret, err)] pub fn proc_fork( mut ctx: FunctionEnvMut<'_, WasiEnv>, mut copy_memory: Bool, @@ -17,9 +17,16 @@ pub fn proc_fork( // If we were just restored then we need to return the value instead if handle_rewind::(&mut ctx) { + let view = ctx.data().memory_view(&ctx); + let ret_pid = pid_ptr.read(&view).unwrap_or(u32::MAX); + if ret_pid == 0 { + trace!("handle_rewind - i am child"); + } else { + trace!("handle_rewind - i am parent (child={})", ret_pid); + } return Ok(Errno::Success); } - trace!("capturing",); + trace!(%copy_memory, "capturing"); // Fork the environment which will copy all the open file handlers // and associate a new context but otherwise shares things like the @@ -34,12 +41,13 @@ pub fn proc_fork( } }; let child_pid = child_env.process.pid(); + let child_finished = child_env.process.finished.clone(); // We write a zero to the PID before we capture the stack // so that this is what will be returned to the child { - let mut children = ctx.data().process.children.write().unwrap(); - children.push(child_pid); + let mut inner = ctx.data().process.inner.write().unwrap(); + inner.children.push(child_env.process.clone()); } let env = ctx.data(); let memory = env.memory_view(&ctx); @@ -88,7 +96,7 @@ pub fn proc_fork( Errno::Success => OnCalledAction::InvokeAgain, err => { warn!("failed - could not rewind the stack - errno={}", err); - OnCalledAction::Trap(Box::new(WasiError::Exit(Errno::Fault as u32))) + OnCalledAction::Trap(Box::new(WasiError::Exit(err.into()))) } } }); @@ -115,6 +123,7 @@ pub fn proc_fork( // Fork the memory and copy the module (compiled code) let env = ctx.data(); + let fork_memory_ty = env.memory().ty(&ctx); let fork_memory: VMMemory = match env .memory() .try_clone(&ctx) @@ -126,7 +135,7 @@ pub fn proc_fork( warn!( %err ); - return OnCalledAction::Trap(Box::new(WasiError::Exit(Errno::Fault as u32))); + return OnCalledAction::Trap(Box::new(WasiError::Exit(Errno::Memviolation.into()))); } }; let fork_module = env.inner().instance.module().clone(); @@ -141,7 +150,6 @@ pub fn proc_fork( // Spawn a new process with this current execution environment let signaler = Box::new(child_env.process.clone()); - let (exit_code_tx, exit_code_rx) = tokio::sync::mpsc::unbounded_channel(); { let store_data = store_data.clone(); let runtime = runtime.clone(); @@ -151,7 +159,7 @@ pub fn proc_fork( let store = fork_store; let module = fork_module; - let spawn_type = SpawnType::NewThread(fork_memory); + let spawn_type = SpawnType::NewThread(fork_memory, fork_memory_ty); let task = move |mut store, module, memory| { // Create the WasiFunctionEnv let pid = child_env.pid(); @@ -207,24 +215,27 @@ pub fn proc_fork( } // Invoke the start function - let mut ret = Errno::Success; - if ctx.data(&store).thread.is_main() { + let mut ret: ExitCode = Errno::Success.into(); + let err = if ctx.data(&store).thread.is_main() { trace!("re-invoking main"); let start = ctx.data(&store).inner().start.clone().unwrap(); - start.call(&mut store); + start.call(&mut store) } else { trace!("re-invoking thread_spawn"); let start = ctx.data(&store).inner().thread_spawn.clone().unwrap(); - start.call(&mut store, 0, 0); + start.call(&mut store, 0, 0) + }; + if let Err(err) = err { + if let Ok(WasiError::Exit(exit_code)) = err.downcast::() { + ret = exit_code; + } } - trace!("child exited (code = {})", ret); + trace!(%pid, %tid, "child exited (code = {})", ret); // Clean up the environment - ctx.cleanup((&mut store), Some(ret as ExitCode)); + ctx.cleanup((&mut store), Some(ret)); // Send the result - let _ = exit_code_tx.send(ret as u32); - drop(exit_code_tx); drop(child_handle); }; @@ -240,29 +251,19 @@ pub fn proc_fork( .ok() }; - // Add the process to the environment state - - let process = OwnedTaskStatus::default(); - - { - trace!("spawned sub-process (pid={})", child_pid.raw()); - let mut inner = ctx.data().process.write(); - inner.bus_processes.insert(child_pid, process.handle()); - } - // If the return value offset is within the memory stack then we need // to update it here rather than in the real memory let pid_offset: u64 = pid_offset.into(); - if pid_offset >= env.stack_start && pid_offset < env.stack_base { + if pid_offset >= env.stack_start && pid_offset < env.stack_end { // Make sure its within the "active" part of the memory stack - let offset = env.stack_base - pid_offset; + let offset = env.stack_end - pid_offset; if offset as usize > memory_stack.len() { warn!( "failed - the return value (pid) is outside of the active part of the memory stack ({} vs {})", offset, memory_stack.len() ); - return OnCalledAction::Trap(Box::new(WasiError::Exit(Errno::Fault as u32))); + return OnCalledAction::Trap(Box::new(WasiError::Exit(Errno::Memviolation.into()))); } // Update the memory stack with the new PID @@ -275,7 +276,7 @@ pub fn proc_fork( warn!( "failed - the return value (pid) is not being returned on the stack - which is not supported" ); - return OnCalledAction::Trap(Box::new(WasiError::Exit(Errno::Fault as u32))); + return OnCalledAction::Trap(Box::new(WasiError::Exit(Errno::Memviolation.into()))); } // Rewind the stack and carry on @@ -288,7 +289,7 @@ pub fn proc_fork( Errno::Success => OnCalledAction::InvokeAgain, err => { warn!("failed - could not rewind the stack - errno={}", err); - OnCalledAction::Trap(Box::new(WasiError::Exit(Errno::Fault as u32))) + OnCalledAction::Trap(Box::new(WasiError::Exit(err.into()))) } } }) diff --git a/lib/wasi/src/syscalls/wasix/proc_join.rs b/lib/wasi/src/syscalls/wasix/proc_join.rs index 36bac17162d..7e551cc00c7 100644 --- a/lib/wasi/src/syscalls/wasix/proc_join.rs +++ b/lib/wasi/src/syscalls/wasix/proc_join.rs @@ -1,3 +1,5 @@ +use wasmer_wasix_types::wasi::{JoinFlags, JoinStatus, JoinStatusType, JoinStatusUnion, OptionPid}; + use super::*; use crate::syscalls::*; @@ -7,63 +9,95 @@ use crate::syscalls::*; /// ## Parameters /// /// * `pid` - Handle of the child process to wait on -#[instrument(level = "trace", skip_all, fields(filter_pid = field::Empty, ret_pid = field::Empty, exit_code = field::Empty), ret, err)] +#[instrument(level = "trace", skip_all, fields(pid = ctx.data().process.pid().raw()), ret, err)] pub fn proc_join( mut ctx: FunctionEnvMut<'_, WasiEnv>, - pid_ptr: WasmPtr, - exit_code_ptr: WasmPtr, + pid_ptr: WasmPtr, + _flags: JoinFlags, + status_ptr: WasmPtr, ) -> Result { wasi_try_ok!(WasiEnv::process_signals_and_exit(&mut ctx)?); let env = ctx.data(); let memory = env.memory_view(&ctx); - let pid = wasi_try_mem_ok!(pid_ptr.read(&memory)); - Span::current().record("filter_pid", pid); + let option_pid = wasi_try_mem_ok!(pid_ptr.read(&memory)); + let option_pid = match option_pid.tag { + OptionTag::None => None, + OptionTag::Some => Some(option_pid.pid), + }; + tracing::trace!("filter_pid = {:?}", option_pid); // If the ID is maximum then it means wait for any of the children - if pid == u32::MAX { - let mut process = ctx.data_mut().process.clone(); - let child_exit = - wasi_try_ok!(__asyncify( - &mut ctx, - None, - async move { process.join_any_child().await } - ) - .map_err(|err| { - trace!( - %pid, - %err - ); - err + let pid = match option_pid { + None => { + let mut process = ctx.data_mut().process.clone(); + let child_exit = wasi_try_ok!(__asyncify(&mut ctx, None, async move { + process.join_any_child().await })?); - return match child_exit { - Some((pid, exit_code)) => { - Span::current() - .record("ret_pid", pid.raw()) - .record("exit_code", exit_code); - let env = ctx.data(); - let memory = env.memory_view(&ctx); - wasi_try_mem_ok!(pid_ptr.write(&memory, pid.raw() as Pid)); - wasi_try_mem_ok!(exit_code_ptr.write(&memory, exit_code)); - Ok(Errno::Success) - } - None => { - let env = ctx.data(); - let memory = env.memory_view(&ctx); - wasi_try_mem_ok!(pid_ptr.write(&memory, -1i32 as Pid)); - wasi_try_mem_ok!(exit_code_ptr.write(&memory, Errno::Child as u32)); - Ok(Errno::Child) - } - }; - } + return match child_exit { + Some((pid, exit_code)) => { + trace!(ret_id = pid.raw(), exit_code = exit_code.raw()); + let env = ctx.data(); + let memory = env.memory_view(&ctx); + + let option_pid = OptionPid { + tag: OptionTag::Some, + pid: pid.raw() as Pid, + }; + wasi_try_mem_ok!(pid_ptr.write(&memory, option_pid)); + + let status = JoinStatus { + tag: JoinStatusType::ExitNormal, + u: JoinStatusUnion { + exit_normal: exit_code.into(), + }, + }; + wasi_try_mem_ok!(status_ptr.write(&memory, status)); + Ok(Errno::Success) + } + None => { + let env = ctx.data(); + let memory = env.memory_view(&ctx); + + let status = JoinStatus { + tag: JoinStatusType::Nothing, + u: JoinStatusUnion { nothing: 0 }, + }; + wasi_try_mem_ok!(status_ptr.write(&memory, status)); + Ok(Errno::Child) + } + }; + } + Some(pid) => pid, + }; // Otherwise we wait for the specific PID let env = ctx.data(); let pid: WasiProcessId = pid.into(); - let process = env.control_plane.get_process(pid); + + // Waiting for a process that is an explicit child will join it + // meaning it will no longer be a sub-process of the main process + let mut process = { + let mut inner = env.process.inner.write().unwrap(); + let process = inner + .children + .iter() + .filter(|c| c.pid == pid) + .map(Clone::clone) + .next(); + inner.children.retain(|c| c.pid != pid); + process + }; + + // Otherwise it could be the case that we are waiting for a process + // that is not a child of this process but may still be running + if process.is_none() { + process = env.control_plane.get_process(pid); + } + if let Some(process) = process { let exit_code = wasi_try_ok!(__asyncify(&mut ctx, None, async move { - let code = process.join().await.unwrap_or(Errno::Child as u32); + let code = process.join().await.unwrap_or_else(|_| Errno::Child.into()); Ok(code) }) .map_err(|err| { @@ -74,22 +108,39 @@ pub fn proc_join( err })?); - Span::current() - .record("ret_pid", pid.raw()) - .record("exit_code", exit_code); + trace!(ret_id = pid.raw(), exit_code = exit_code.raw()); let env = ctx.data(); - let mut children = env.process.children.write().unwrap(); - children.retain(|a| *a != pid); + { + let mut inner = env.process.inner.write().unwrap(); + inner.children.retain(|a| a.pid != pid); + } let memory = env.memory_view(&ctx); - wasi_try_mem_ok!(exit_code_ptr.write(&memory, exit_code)); + + let option_pid = OptionPid { + tag: OptionTag::Some, + pid: pid.raw(), + }; + let status = JoinStatus { + tag: JoinStatusType::ExitNormal, + u: JoinStatusUnion { + exit_normal: exit_code.into(), + }, + }; + wasi_try_mem_ok!(pid_ptr.write(&memory, option_pid)); + wasi_try_mem_ok!(status_ptr.write(&memory, status)); return Ok(Errno::Success); } - Span::current().record("ret_pid", pid.raw()); + trace!(ret_id = pid.raw(), "status=nothing"); let env = ctx.data(); let memory = env.memory_view(&ctx); - wasi_try_mem_ok!(exit_code_ptr.write(&memory, Errno::Child as ExitCode)); + + let status = JoinStatus { + tag: JoinStatusType::Nothing, + u: JoinStatusUnion { nothing: 0 }, + }; + wasi_try_mem_ok!(status_ptr.write(&memory, status)); Ok(Errno::Child) } diff --git a/lib/wasi/src/syscalls/wasix/proc_spawn.rs b/lib/wasi/src/syscalls/wasix/proc_spawn.rs index 9fb4de0700f..38fd653ccac 100644 --- a/lib/wasi/src/syscalls/wasix/proc_spawn.rs +++ b/lib/wasi/src/syscalls/wasix/proc_spawn.rs @@ -113,6 +113,7 @@ pub fn proc_spawn_internal( return Ok(Err(BusErrno::Denied)); } }; + let child_process = child_env.process.clone(); if let Some(args) = args { let mut child_state = env.state.fork(); child_state.args = args; @@ -241,17 +242,12 @@ pub fn proc_spawn_internal( // Add the process to the environment state { - let mut children = ctx.data().process.children.write().unwrap(); - children.push(child_pid); + let mut inner = ctx.data().process.inner.write().unwrap(); + inner.children.push(child_process); } let env = ctx.data(); let memory = env.memory_view(&ctx); - { - let mut guard = env.process.write(); - guard.bus_processes.insert(child_pid, process); - }; - let handles = BusHandles { bid: child_pid.raw(), stdin, diff --git a/lib/wasi/src/syscalls/wasix/stack_checkpoint.rs b/lib/wasi/src/syscalls/wasix/stack_checkpoint.rs index f9132222da2..7de1e9afc77 100644 --- a/lib/wasi/src/syscalls/wasix/stack_checkpoint.rs +++ b/lib/wasi/src/syscalls/wasix/stack_checkpoint.rs @@ -93,10 +93,10 @@ pub fn stack_checkpoint( let mut memory_stack_corrected = memory_stack.clone(); { let snapshot_offset: u64 = snapshot_offset.into(); - if snapshot_offset >= env.stack_start && snapshot_offset < env.stack_base { + if snapshot_offset >= env.stack_start && snapshot_offset < env.stack_end { // Make sure its within the "active" part of the memory stack // (note - the area being written to might not go past the memory pointer) - let offset = env.stack_base - snapshot_offset; + let offset = env.stack_end - snapshot_offset; if (offset as usize) < memory_stack_corrected.len() { let left = memory_stack_corrected.len() - (offset as usize); let end = offset + (val_bytes.len().min(left) as u64); @@ -126,7 +126,7 @@ pub fn stack_checkpoint( let snapshot_ptr: WasmPtr = WasmPtr::new(snapshot_offset); if let Err(err) = snapshot_ptr.write(&memory, snapshot) { warn!("could not save stack snapshot - {}", err); - return OnCalledAction::Trap(Box::new(WasiError::Exit(Errno::Fault as u32))); + return OnCalledAction::Trap(Box::new(WasiError::Exit(mem_error_to_wasi(err).into()))); } // Rewind the stack and carry on @@ -144,7 +144,7 @@ pub fn stack_checkpoint( "failed checkpoint - could not rewind the stack - errno={}", err ); - OnCalledAction::Trap(Box::new(WasiError::Exit(err as u32))) + OnCalledAction::Trap(Box::new(WasiError::Exit(err.into()))) } } }) diff --git a/lib/wasi/src/syscalls/wasix/stack_restore.rs b/lib/wasi/src/syscalls/wasix/stack_restore.rs index 4c218545e55..ec978c57492 100644 --- a/lib/wasi/src/syscalls/wasix/stack_restore.rs +++ b/lib/wasi/src/syscalls/wasix/stack_restore.rs @@ -24,7 +24,7 @@ pub fn stack_restore( } Err(err) => { warn!("failed to read stack snapshot - {}", err); - return Err(WasiError::Exit(128)); + return Err(WasiError::Exit(mem_error_to_wasi(err).into())); } }; @@ -41,10 +41,10 @@ pub fn stack_restore( // If the return value offset is within the memory stack then we need // to update it here rather than in the real memory let ret_val_offset = snapshot.user; - if ret_val_offset >= env.stack_start && ret_val_offset < env.stack_base { + if ret_val_offset >= env.stack_start && ret_val_offset < env.stack_end { // Make sure its within the "active" part of the memory stack let val_bytes = val.to_ne_bytes(); - let offset = env.stack_base - ret_val_offset; + let offset = env.stack_end - ret_val_offset; let end = offset + (val_bytes.len() as u64); if end as usize > memory_stack.len() { warn!( @@ -54,7 +54,9 @@ pub fn stack_restore( ret_val_offset, end ); - return OnCalledAction::Trap(Box::new(WasiError::Exit(Errno::Fault as u32))); + return OnCalledAction::Trap(Box::new(WasiError::Exit( + Errno::Memviolation.into(), + ))); } else { // Update the memory stack with the new return value let pstart = memory_stack.len() - offset as usize; @@ -71,7 +73,8 @@ pub fn stack_restore( .map(|a| { a.write(&memory, val) .map(|_| Errno::Success) - .unwrap_or(Errno::Fault) + .map_err(mem_error_to_wasi) + .unwrap_or_else(|e| e) }) .unwrap_or_else(|a| a); if err != Errno::Success { @@ -79,7 +82,7 @@ pub fn stack_restore( "snapshot stack restore failed - the return value can not be written too - {}", err ); - return OnCalledAction::Trap(Box::new(WasiError::Exit(Errno::Fault as u32))); + return OnCalledAction::Trap(Box::new(WasiError::Exit(err.into()))); } } @@ -91,7 +94,7 @@ pub fn stack_restore( Errno::Success => OnCalledAction::InvokeAgain, err => { warn!("failed to rewind the stack - errno={}", err); - OnCalledAction::Trap(Box::new(WasiError::Exit(Errno::Fault as u32))) + OnCalledAction::Trap(Box::new(WasiError::Exit(err.into()))) } } } else { @@ -99,7 +102,7 @@ pub fn stack_restore( "snapshot stack restore failed - the snapshot can not be found and hence restored (hash={})", snapshot.hash ); - OnCalledAction::Trap(Box::new(WasiError::Exit(Errno::Fault as u32))) + OnCalledAction::Trap(Box::new(WasiError::Exit(Errno::Unknown.into()))) } }); diff --git a/lib/wasi/src/syscalls/wasix/thread_exit.rs b/lib/wasi/src/syscalls/wasix/thread_exit.rs index 0b5b4da36a4..aab730de729 100644 --- a/lib/wasi/src/syscalls/wasix/thread_exit.rs +++ b/lib/wasi/src/syscalls/wasix/thread_exit.rs @@ -10,10 +10,8 @@ use crate::syscalls::*; /// ## Parameters /// /// * `rval` - The exit code returned by the process. -#[instrument(level = "debug", skip_all, fields(exitcode), ret, err)] -pub fn thread_exit( - ctx: FunctionEnvMut<'_, WasiEnv>, - exitcode: ExitCode, -) -> Result { +#[instrument(level = "debug", skip_all, fields(exitcode), ret)] +pub fn thread_exit(ctx: FunctionEnvMut<'_, WasiEnv>, exitcode: ExitCode) -> Result<(), WasiError> { + tracing::debug!(%exitcode); Err(WasiError::Exit(exitcode)) } diff --git a/lib/wasi/src/syscalls/wasix/thread_spawn.rs b/lib/wasi/src/syscalls/wasix/thread_spawn.rs index 945bcaf5dd2..144dc9013c7 100644 --- a/lib/wasi/src/syscalls/wasix/thread_spawn.rs +++ b/lib/wasi/src/syscalls/wasix/thread_spawn.rs @@ -2,6 +2,7 @@ use super::*; use crate::syscalls::*; use wasmer::vm::VMMemory; +use wasmer_wasix_types::wasi::ThreadStart; /// ### `thread_spawn()` /// Creates a new thread by spawning that shares the same @@ -24,10 +25,7 @@ use wasmer::vm::VMMemory; #[instrument(level = "debug", skip_all, fields(user_data, stack_base, stack_start, reactor, tid = field::Empty), ret)] pub fn thread_spawn( mut ctx: FunctionEnvMut<'_, WasiEnv>, - user_data: u64, - stack_base: u64, - stack_start: u64, - reactor: Bool, + start_ptr: WasmPtr, M>, ret_tid: WasmPtr, ) -> Errno { // Now we use the environment and memory references @@ -36,12 +34,17 @@ pub fn thread_spawn( let runtime = env.runtime.clone(); let tasks = env.tasks().clone(); + // Read the properties about the stack which we will use for asyncify + let start = wasi_try_mem!(start_ptr.read(&memory)); + let stack_start: u64 = wasi_try!(start.stack_start.try_into().map_err(|_| Errno::Overflow)); + let stack_size: u64 = wasi_try!(start.stack_size.try_into().map_err(|_| Errno::Overflow)); + let stack_base = stack_start - stack_size; + // Create the handle that represents this thread let mut thread_handle = match env.process.new_thread() { Ok(h) => h, Err(err) => { error!( - %reactor, %stack_base, caller_id = current_caller_id().raw(), "failed to create thread handle", @@ -55,6 +58,7 @@ pub fn thread_spawn( // We need a copy of the process memory and a packaged store in order to // launch threads and reactors + let thread_memory_ty = ctx.data().memory().ty(&ctx); let thread_memory = wasi_try!(ctx.data().memory().try_clone(&ctx).ok_or_else(|| { error!("failed - the memory could not be cloned"); Errno::Notcapable @@ -78,7 +82,7 @@ pub fn thread_spawn( { let env = ctx.data_mut(&mut store); env.thread = thread.clone(); - env.stack_base = stack_base; + env.stack_end = stack_base; env.stack_start = stack_start; } @@ -107,26 +111,25 @@ pub fn thread_spawn( }; // This function calls into the module + let start_ptr_offset = start_ptr.offset(); let call_module = move |ctx: &WasiFunctionEnv, store: &mut Store| { // We either call the reactor callback or the thread spawn callback //trace!("threading: invoking thread callback (reactor={})", reactor); - let spawn = match reactor { - Bool::False => ctx.data(&store).inner().thread_spawn.clone().unwrap(), - Bool::True => ctx.data(&store).inner().react.clone().unwrap(), - _ => { - debug!("failed as the reactor type is not value",); - return Errno::Noexec as u32; - } - }; - - let user_data_low: u32 = (user_data & 0xFFFFFFFF) as u32; - let user_data_high: u32 = (user_data >> 32) as u32; - + let spawn = ctx.data(&store).inner().thread_spawn.clone().unwrap(); + let tid = ctx.data(&store).tid(); + let call_ret = spawn.call( + store, + tid.raw().try_into().map_err(|_| Errno::Overflow).unwrap(), + start_ptr_offset + .try_into() + .map_err(|_| Errno::Overflow) + .unwrap(), + ); let mut ret = Errno::Success; - if let Err(err) = spawn.call(store, user_data_low as i32, user_data_high as i32) { + if let Err(err) = call_ret { match err.downcast::() { Ok(WasiError::Exit(code)) => { - ret = if code == 0 { + ret = if code.is_success() { Errno::Success } else { Errno::Noexec @@ -142,13 +145,10 @@ pub fn thread_spawn( } } } - trace!("callback finished (reactor={:?}, ret={})", reactor, ret); + trace!("callback finished (ret={})", ret); - // If we are NOT a reactor then we will only run once and need to clean up - if reactor == Bool::False { - // Clean up the environment - ctx.cleanup(store, Some(ret as ExitCode)); - } + // Clean up the environment + ctx.cleanup(store, Some(ret.into())); // Return the result ret as u32 @@ -222,44 +222,29 @@ pub fn thread_spawn( } }; - // If we are a reactor then instead of launching the thread now - // we store it in the state machine and only launch it whenever - // work arrives that needs to be processed - match reactor { - Bool::True => { - warn!("thread failed - reactors are not currently supported"); - return Errno::Notcapable; - } - Bool::False => { - // If the process does not export a thread spawn function then obviously - // we can't spawn a background thread - if env.inner().thread_spawn.is_none() { - warn!("failed - the program does not export a _start_thread function"); - return Errno::Notcapable; - } + // If the process does not export a thread spawn function then obviously + // we can't spawn a background thread + if env.inner().thread_spawn.is_none() { + warn!("thread failed - the program does not export a `wasi_thread_start` function"); + return Errno::Notcapable; + } - let spawn_type = crate::runtime::SpawnType::NewThread(thread_memory); + let spawn_type = crate::runtime::SpawnType::NewThread(thread_memory, thread_memory_ty); - // Now spawn a thread - trace!("spawning background thread"); - let thread_module = env.inner().instance.module().clone(); - let tasks2 = tasks.clone(); + // Now spawn a thread + trace!("threading: spawning background thread"); + let thread_module = env.inner().instance.module().clone(); + let tasks2 = tasks.clone(); - let task = move |store, thread_module, mut thread_memory| { - // FIXME: should not use unwrap() here! (initializiation refactor) - let mut store = Some(store); - execute_module(&mut store, thread_module, &mut thread_memory); - }; + let task = move |store, thread_module, mut thread_memory| { + // FIXME: should not use unwrap() here! (initializiation refactor) + let mut store = Some(store); + execute_module(&mut store, thread_module, &mut thread_memory); + }; - wasi_try!(tasks - .task_wasm(Box::new(task), store, thread_module, spawn_type) - .map_err(|err| { Into::::into(err) })); - } - _ => { - warn!("failed - invalid reactor parameter value"); - return Errno::Notcapable; - } - } + wasi_try!(tasks + .task_wasm(Box::new(task), store, thread_module, spawn_type) + .map_err(|err| { Into::::into(err) })); // Success let memory = ctx.data().memory_view(&ctx); diff --git a/lib/wasi/tests/runners.rs b/lib/wasi/tests/runners.rs index 6b37c4a8b2a..3bf033442a8 100644 --- a/lib/wasi/tests/runners.rs +++ b/lib/wasi/tests/runners.rs @@ -51,7 +51,7 @@ mod wasi { WasiError::Exit(code) => *code, other => unreachable!("Something else went wrong: {:?}", other), }; - assert_eq!(exit_code, 1); + assert_eq!(exit_code.is_success(), true); } #[tokio::test] @@ -77,7 +77,7 @@ mod wasi { WasiError::Exit(code) => *code, other => unreachable!("Something else went wrong: {:?}", other), }; - assert_eq!(exit_code, 42); + assert_eq!(exit_code.raw(), 42); } } diff --git a/tests/integration/cli/Cargo.toml b/tests/integration/cli/Cargo.toml index 4472c06760c..cb76f0293fc 100644 --- a/tests/integration/cli/Cargo.toml +++ b/tests/integration/cli/Cargo.toml @@ -16,6 +16,8 @@ md5 = "0.7.0" hex = "0.4.3" pretty_assertions = "1.3.0" object = "0.30.0" +reqwest = { version = "0.11.14", features = ["json"] } +tokio = { version = "1", features = [ "rt", "rt-multi-thread", "macros" ] } [dependencies] anyhow = "1" @@ -24,6 +26,7 @@ target-lexicon = "0.12.5" tar = "0.4.38" flate2 = "1.0.24" dirs = "4.0.0" +derivative = { version = "^2" } [features] default = ["webc_runner"] diff --git a/tests/integration/cli/tests/snapshot.rs b/tests/integration/cli/tests/snapshot.rs index e1dfa2d9faf..13606307f11 100644 --- a/tests/integration/cli/tests/snapshot.rs +++ b/tests/integration/cli/tests/snapshot.rs @@ -1,27 +1,61 @@ use std::{ io::{Read, Write}, path::{Path, PathBuf}, - process::Stdio, + process::{Child, Stdio}, + sync::Arc, }; +use derivative::Derivative; #[cfg(test)] use insta::assert_json_snapshot; +use tempfile::NamedTempFile; use wasmer_integration_tests_cli::get_wasmer_path; +#[derive(Derivative, serde::Serialize, serde::Deserialize, Clone)] +#[derivative(Debug, PartialEq)] +pub struct TestIncludeWeb { + pub name: String, + #[derivative(Debug = "ignore", PartialEq = "ignore")] + #[serde(skip, default = "default_include_webc")] + pub webc: Arc, +} + +fn default_include_webc() -> Arc { + Arc::new(NamedTempFile::new().unwrap()) +} +impl Eq for TestIncludeWeb {} + #[derive(serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq)] pub struct TestSpec { pub name: Option, // Uses a hex-encoded String for better review output. + #[serde(skip)] pub wasm_hash: String, /// Name of webc dependencies to inject. pub use_packages: Vec, + pub include_webcs: Vec, pub cli_args: Vec, pub stdin: Option>, pub debug_output: bool, pub enable_threads: bool, + pub enable_network: bool, } +static WEBC_BASH: &'static [u8] = + include_bytes!("./webc/bash-1.0.12-0103d733-1afb-4a56-b0ef-0e124139e996.webc"); +static WEBC_COREUTILS_14: &'static [u8] = + include_bytes!("./webc/coreutils-1.0.14-076508e5-e704-463f-b467-f3d9658fc907.webc"); +static WEBC_COREUTILS_11: &'static [u8] = + include_bytes!("./webc/coreutils-1.0.11-9d7746ca-694f-11ed-b932-dead3543c068.webc"); +static WEBC_DASH: &'static [u8] = + include_bytes!("./webc/dash-1.0.16-bd931010-c134-4785-9423-13c0a0d49d90.webc"); +static WEBC_PYTHON: &'static [u8] = include_bytes!("./webc/python-0.1.0.webc"); +static WEBC_WEB_SERVER: &'static [u8] = + include_bytes!("./webc/static-web-server-1.0.8-a241658c-e409-4749-872c-ae8eab142ef0.webc"); +static WEBC_WASMER_SH: &'static [u8] = + include_bytes!("./webc/wasmer-sh-1.0.63-dd3d67d1-de94-458c-a9ee-caea3b230ccf.webc"); + impl std::fmt::Debug for TestSpec { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("TestSpec") @@ -29,6 +63,7 @@ impl std::fmt::Debug for TestSpec { // TODO: show hash of code? // .field("wasm_code", &self.wasm_code) .field("use_packages", &self.use_packages) + .field("include_webcs", &self.include_webcs) .field("cli_args", &self.cli_args) .field("stdin", &self.stdin) .finish() @@ -60,6 +95,8 @@ pub struct TestBuilder { spec: TestSpec, } +type RunWith = Box Result + 'static>; + impl TestBuilder { pub fn new() -> Self { Self { @@ -67,10 +104,12 @@ impl TestBuilder { name: None, wasm_hash: String::new(), use_packages: Vec::new(), + include_webcs: Vec::new(), cli_args: Vec::new(), stdin: None, debug_output: false, enable_threads: true, + enable_network: false, }, } } @@ -96,9 +135,33 @@ impl TestBuilder { self } + pub fn include_static_package(mut self, name: &str, data: &[u8]) -> Self { + let package = build_test_file(data); + self.spec.include_webcs.push(TestIncludeWeb { + name: name.to_string(), + webc: Arc::new(package), + }); + self + } + pub fn use_coreutils(self) -> Self { // TODO: use custom compiled coreutils self.use_pkg("sharrattj/coreutils") + .include_static_package("sharrattj/coreutils@1.0.16", WEBC_COREUTILS_14) + } + + pub fn use_dash(self) -> Self { + // TODO: use custom compiled dash + self.use_pkg("sharrattj/dash") + .include_static_package("sharrattj/dash@1.0.16", WEBC_DASH) + .include_static_package("sharrattj/coreutils@1.0.11", WEBC_COREUTILS_11) + } + + pub fn use_bash(self) -> Self { + // TODO: use custom compiled bash + self.use_pkg("sharrattj/bash") + .include_static_package("sharrattj/bash@1.0.12", WEBC_BASH) + .include_static_package("sharrattj/coreutils@1.0.11", WEBC_COREUTILS_11) } pub fn debug_output(mut self, show_debug: bool) -> Self { @@ -113,13 +176,27 @@ impl TestBuilder { self } + pub fn enable_network(mut self, enabled: bool) -> Self { + self.spec.enable_network = enabled; + self + } + pub fn run_file(self, path: impl AsRef) -> TestSnapshot { snapshot_file(path.as_ref(), self.spec) } + pub fn with_name(mut self, name: &str) -> Self { + self.spec.name = Some(name.to_string()); + self + } + pub fn run_wasm(self, code: &[u8]) -> TestSnapshot { build_snapshot(self.spec, code) } + + pub fn run_wasm_with(self, code: &[u8], with: RunWith) -> TestSnapshot { + build_snapshot_with(self.spec, code, with) + } } pub fn wasm_dir() -> PathBuf { @@ -140,14 +217,12 @@ fn wasmer_path() -> PathBuf { path } -fn build_test_file(contents: &[u8]) -> PathBuf { - // TODO: use TmpFile crate that auto-deletes files. - let dir = std::env::temp_dir().join("wasmer-snapshot-tests"); - std::fs::create_dir_all(&dir).unwrap(); - let hash = format!("{:x}.wasm", md5::compute(contents)); - let path = dir.join(hash); - std::fs::write(&path, contents).unwrap(); - path +fn build_test_file(contents: &[u8]) -> NamedTempFile { + let mut named_file = NamedTempFile::new().unwrap(); + let file = named_file.as_file_mut(); + file.write_all(contents).unwrap(); + file.flush().unwrap(); + named_file } fn bytes_to_hex_string(bytes: Vec) -> String { @@ -158,7 +233,7 @@ fn bytes_to_hex_string(bytes: Vec) -> String { } } -pub fn run_test(spec: TestSpec, code: &[u8]) -> TestResult { +pub fn run_test_with(spec: TestSpec, code: &[u8], with: RunWith) -> TestResult { let wasm_path = build_test_file(code); let mut cmd = std::process::Command::new(wasmer_path()); @@ -170,21 +245,31 @@ pub fn run_test(spec: TestSpec, code: &[u8]) -> TestResult { if spec.enable_threads { cmd.arg("--enable-threads"); } + if spec.enable_network { + cmd.arg("--net"); + } cmd.arg("--allow-multiple-wasi-versions"); - cmd.arg("--net"); for pkg in &spec.use_packages { cmd.args(&["--use", &pkg]); } + for pkg in &spec.include_webcs { + cmd.args(&[ + "--include-webc", + pkg.webc.path().as_os_str().to_string_lossy().as_ref(), + ]); + } + let log_level = if spec.debug_output { "debug" } else { "never=error" }; cmd.env("RUST_LOG", log_level); + cmd.env("RUST_BACKTRACE", "1"); - cmd.arg(wasm_path); + cmd.arg(wasm_path.path()); if !spec.cli_args.is_empty() { cmd.arg("--").args(&spec.cli_args); } @@ -222,7 +307,9 @@ pub fn run_test(spec: TestSpec, code: &[u8]) -> TestResult { proc.stdin.take().unwrap().write_all(stdin).unwrap(); } - let status = match proc.wait() { + let status = with(proc); + + let status = match status { Ok(status) => status, Err(err) => { let stdout = stdout_thread.join().unwrap().unwrap(); @@ -237,17 +324,61 @@ pub fn run_test(spec: TestSpec, code: &[u8]) -> TestResult { let stdout = bytes_to_hex_string(stdout_thread.join().unwrap().unwrap()); let stderr = bytes_to_hex_string(stderr_thread.join().unwrap().unwrap()); + + // we do some post processing to replace the temporary random name of the binary + // with a fixed name as otherwise the results are not comparable. this occurs + // because bash (and others) use the process name in the printf on stdout + let stdout = stdout + .replace( + wasm_path + .path() + .file_name() + .unwrap() + .to_string_lossy() + .as_ref(), + "test.wasm", + ) + .to_string(); + let stderr = stderr + .replace( + wasm_path + .path() + .file_name() + .unwrap() + .to_string_lossy() + .as_ref(), + "test.wasm", + ) + .to_string(); + TestResult::Success(TestOutput { stdout, stderr, - exit_code: status.code().unwrap_or_default(), + exit_code: status, }) } pub fn build_snapshot(mut spec: TestSpec, code: &[u8]) -> TestSnapshot { spec.wasm_hash = format!("{:x}", md5::compute(code)); - let result = run_test(spec.clone(), code); + let result = run_test_with( + spec.clone(), + code, + Box::new(|mut child| { + child + .wait() + .map_err(|err| err.into()) + .map(|status| status.code().unwrap_or_default()) + }), + ); + let snapshot = TestSnapshot { spec, result }; + snapshot +} + +pub fn build_snapshot_with(mut spec: TestSpec, code: &[u8], with: RunWith) -> TestSnapshot { + spec.wasm_hash = format!("{:x}", md5::compute(code)); + + let result = run_test_with(spec.clone(), code, with); let snapshot = TestSnapshot { spec, result }; snapshot } @@ -259,18 +390,33 @@ pub fn snapshot_file(path: &Path, spec: TestSpec) -> TestSnapshot { build_snapshot(spec, &code) } +macro_rules! function { + () => {{ + fn f() {} + fn type_name_of(_: T) -> &'static str { + std::any::type_name::() + } + let name = type_name_of(f); + &name[..name.len() - 3] + }}; +} + +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] #[test] fn test_snapshot_condvar() { let snapshot = TestBuilder::new() + .with_name(function!()) .debug_output(true) .run_wasm(include_bytes!("./wasm/example-condvar.wasm")); assert_json_snapshot!(snapshot); } // Test that the expected default directories are present. +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] #[test] fn test_snapshot_default_file_system_tree() { let snapshot = TestBuilder::new() + .with_name(function!()) .arg("ls") .run_wasm(include_bytes!("./wasm/coreutils.wasm")); assert_json_snapshot!(snapshot); @@ -278,9 +424,11 @@ fn test_snapshot_default_file_system_tree() { // TODO: figure out why this hangs on Windows and Mac OS #[cfg(target_os = "linux")] +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] #[test] fn test_snapshot_stdin_stdout_stderr() { let snapshot = TestBuilder::new() + .with_name(function!()) .stdin_str("blah") .args(&["tee", "/dev/stderr"]) .run_wasm(include_bytes!("./wasm/coreutils.wasm")); @@ -288,38 +436,162 @@ fn test_snapshot_stdin_stdout_stderr() { } // Piping to cowsay should, well.... display a cow that says something +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] #[test] fn test_snapshot_cowsay() { let snapshot = TestBuilder::new() + .with_name(function!()) .stdin_str("blah\n") .run_wasm(include_bytes!("./wasm/cowsay.wasm")); assert_json_snapshot!(snapshot); } -// FIXME: output contains timestamps - cant create snapshot -// #[test] -// fn test_snapshot_epoll() { -// let snapshot = TestBuilder::new().run_file(wasm_dir().join("example-epoll.wasm")); -// assert_json_snapshot!(snapshot); -// } +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_epoll() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .run_wasm(include_bytes!("./wasm/example-epoll.wasm")); + assert_json_snapshot!(snapshot); +} + +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_file_copy() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .use_coreutils() + .stdin_str("hi") + .arg("/dev/stdin") + .arg("/dev/stdout") + .run_wasm(include_bytes!("./wasm/example-file-copy.wasm")); + assert_json_snapshot!(snapshot); +} + +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_execve() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .use_coreutils() + .run_wasm(include_bytes!("./wasm/example-execve.wasm")); + assert_json_snapshot!(snapshot); +} + +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_web_server() { + let with_inner = || { + let rt = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build()?; + + let http_get = |url, max_retries| { + rt.block_on(async move { + for n in 0..(max_retries+1) { + println!("http request: {}", url); + tokio::select! { + resp = reqwest::get(url) => { + let resp = match resp { + Ok(a) => a, + Err(_) if n < max_retries => { + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + continue; + } + Err(err) => return Err(err.into()) + }; + if resp.status().is_success() == false { + return Err(anyhow::format_err!("incorrect status code: {}", resp.status())); + } + return Ok(resp.bytes().await?); + } + _ = tokio::time::sleep(std::time::Duration::from_secs(1)) => { + eprintln!("retrying request... ({} attempts)", n); + continue; + } + } + } + Err(anyhow::format_err!("timeout while performing HTTP request")) + }) + }; + + let expected_size = usize::from_str_radix( + String::from_utf8_lossy(http_get("http://localhost:7777/main.js.size", 50)?.as_ref()) + .trim(), + 10, + )?; + if expected_size == 0 { + return Err(anyhow::format_err!("There was no data returned")); + } + println!("expected_size: {}", expected_size); + + let reference_data = http_get("http://localhost:7777/main.js", 0)?; + for _ in 0..20 { + let test_data = http_get("http://localhost:7777/main.js", 0)?; + println!("actual_size: {}", test_data.len()); + + if expected_size != test_data.len() { + return Err(anyhow::format_err!( + "The actual size and expected size does not match {} vs {}", + test_data.len(), + expected_size + )); + } + if test_data + .iter() + .zip(reference_data.iter()) + .any(|(a, b)| a != b) + { + return Err(anyhow::format_err!("The returned data is inconsistent")); + } + } + + Ok(0) + }; + + let with = move |mut child: Child| { + let ret = with_inner(); + child.kill().ok(); + ret + }; + + let snapshot = TestBuilder::new() + .with_name(function!()) + .enable_network(true) + .include_static_package("sharrattj/static-web-server@1.0.8", WEBC_WEB_SERVER) + .include_static_package("sharrattj/wasmer-sh@1.0.63", WEBC_WASMER_SH) + .use_coreutils() + .use_pkg("sharrattj/wasmer-sh") + .stdin_str( + r#" +cat /public/main.js | wc -c > /public/main.js.size +rm -f /cfg/config.toml +/bin/webserver --log-level warn --root /public --port 7777"#, + ) + .run_wasm_with(include_bytes!("./wasm/dash.wasm"), Box::new(with)); + assert_json_snapshot!(snapshot); +} -// FIXME: re-enable, disabled due to flakyness // The ability to fork the current process and run a different image but retain // the existing open file handles (which is needed for stdin and stdout redirection) -// #[cfg(not(target_os = "windows"))] -// #[test] -// fn test_snapshot_fork_and_exec() { -// let snapshot = TestBuilder::new() -// .use_coreutils() -// .run_wasm(include_bytes!("./wasm/example-execve.wasm")); -// assert_json_snapshot!(snapshot); -// } +#[cfg(not(target_os = "windows"))] +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_fork_and_exec() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .use_coreutils() + .run_wasm(include_bytes!("./wasm/example-execve.wasm")); + assert_json_snapshot!(snapshot); +} // longjmp is used by C programs that save and restore the stack at specific // points - this functionality is often used for exception handling +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] #[test] fn test_snapshot_longjump() { let snapshot = TestBuilder::new() + .with_name(function!()) .use_coreutils() .run_wasm(include_bytes!("./wasm/example-longjmp.wasm")); assert_json_snapshot!(snapshot); @@ -327,18 +599,22 @@ fn test_snapshot_longjump() { // Another longjump test. // This one is initiated from `rust` code and thus has the risk of leaking memory but uses different interfaces +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] #[test] fn test_snapshot_longjump2() { let snapshot = TestBuilder::new() + .with_name(function!()) .use_coreutils() .run_wasm(include_bytes!("./wasm/example-stack.wasm")); assert_json_snapshot!(snapshot); } // Simple fork example that is a crude multi-threading implementation - used by `dash` +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] #[test] fn test_snapshot_fork() { let snapshot = TestBuilder::new() + .with_name(function!()) .use_coreutils() .run_wasm(include_bytes!("./wasm/example-fork.wasm")); assert_json_snapshot!(snapshot); @@ -346,9 +622,11 @@ fn test_snapshot_fork() { // Uses the `fd_pipe` syscall to create a bidirection pipe with two file // descriptors then forks the process to write and read to this pipe. +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] #[test] fn test_snapshot_pipes() { let snapshot = TestBuilder::new() + .with_name(function!()) .use_coreutils() .run_wasm(include_bytes!("./wasm/example-pipe.wasm")); assert_json_snapshot!(snapshot); @@ -358,44 +636,55 @@ fn test_snapshot_pipes() { // This test ensures that the stacks that have been recorded are preserved // after a fork. // The behavior is needed for `dash` +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] #[test] fn test_snapshot_longjump_fork() { - let snapshot = TestBuilder::new().run_wasm(include_bytes!("./wasm/example-fork-longjmp.wasm")); + let snapshot = TestBuilder::new() + .with_name(function!()) + .run_wasm(include_bytes!("./wasm/example-fork-longjmp.wasm")); assert_json_snapshot!(snapshot); } // full multi-threading with shared memory and shared compiled modules +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] #[test] fn test_snapshot_multithreading() { - let snapshot = - TestBuilder::new().run_wasm(include_bytes!("./wasm/example-multi-threading.wasm")); + let snapshot = TestBuilder::new() + .with_name(function!()) + .debug_output(true) + .run_wasm(include_bytes!("./wasm/example-multi-threading.wasm")); assert_json_snapshot!(snapshot); } // full multi-threading with shared memory and shared compiled modules #[cfg(target_os = "linux")] +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] #[test] fn test_snapshot_sleep() { - let snapshot = TestBuilder::new().run_wasm(include_bytes!("./wasm/example-sleep.wasm")); + let snapshot = TestBuilder::new() + .with_name(function!()) + .run_wasm(include_bytes!("./wasm/example-sleep.wasm")); assert_json_snapshot!(snapshot); } -// FIXME: re-enable, disabled due to flakyness // Uses `posix_spawn` to launch a sub-process and wait on it to exit -// #[cfg(not(target_os = "windows"))] -// #[test] -// fn test_snapshot_process_spawn() { -// let snapshot = TestBuilder::new() -// .use_coreutils() -// .run_wasm(include_bytes!("./wasm/example-spawn.wasm")); -// assert_json_snapshot!(snapshot); -// } +#[cfg(not(target_os = "windows"))] +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_process_spawn() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .use_coreutils() + .run_wasm(include_bytes!("./wasm/example-spawn.wasm")); + assert_json_snapshot!(snapshot); +} // FIXME: re-enable - hangs on windows and macos // Connects to 8.8.8.8:53 over TCP to verify TCP clients work // #[test] // fn test_snapshot_tcp_client() { // let snapshot = TestBuilder::new() +// .with_name(function!()) // .use_coreutils() // .run_wasm(include_bytes!("./wasm/example-tcp-client.wasm")); // assert_json_snapshot!(snapshot); @@ -403,10 +692,11 @@ fn test_snapshot_sleep() { // Tests that thread local variables work correctly #[cfg(target_os = "linux")] +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] #[test] fn test_snapshot_thread_locals() { let mut snapshot = TestBuilder::new() - .use_coreutils() + .with_name(function!()) .run_wasm(include_bytes!("./wasm/example-thread-local.wasm")); match &mut snapshot.result { @@ -424,52 +714,184 @@ fn test_snapshot_thread_locals() { // Tests that lightweight forking that does not copy the memory but retains the // open file descriptors works correctly. -// #[test] -// fn test_snapshot_vfork() { -// let snapshot = TestBuilder::new() -// .use_coreutils() -// .run_wasm(include_bytes!("./wasm/example-vfork.wasm")); -// assert_json_snapshot!(snapshot); -// } +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_vfork() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .use_coreutils() + .run_wasm(include_bytes!("./wasm/example-vfork.wasm")); + assert_json_snapshot!(snapshot); +} -// Tests that signals can be received and processed by WASM applications -// Note: This test requires that a signal is sent to the process asynchronously -// #[test] -// fn test_snapshot_signals() { -// let snapshot = TestBuilder::new().run_file(wasm_dir().join("example-signal.wasm")); -// assert_json_snapshot!(snapshot); -// } +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_signals() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .run_wasm(include_bytes!("./wasm/example-signal.wasm")); + assert_json_snapshot!(snapshot); +} #[cfg(target_os = "linux")] +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] #[test] -fn test_snapshot_dash() { +fn test_snapshot_dash_echo() { let snapshot = TestBuilder::new() + .with_name(function!()) .stdin_str("echo 2") .run_wasm(include_bytes!("./wasm/dash.wasm")); - // TODO: more tests! assert_json_snapshot!(snapshot); } +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_dash_echo_to_cat() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .use_coreutils() + .stdin_str("echo hello | cat") + .run_wasm(include_bytes!("./wasm/dash.wasm")); + assert_json_snapshot!(snapshot); +} + +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_dash_python() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .use_coreutils() + .include_static_package("syrusakbary/python@0.1.0", WEBC_PYTHON) + .stdin_str("wasmer run syrusakbary/python -- -c 'print(10)'") + .run_wasm(include_bytes!("./wasm/dash.wasm")); + assert_json_snapshot!(snapshot); +} + +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_dash_dev_zero() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .use_coreutils() + .stdin_str("head -c 10 /dev/zero") + .run_wasm(include_bytes!("./wasm/dash.wasm")); + assert_json_snapshot!(snapshot); +} + +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_dash_dev_urandom() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .use_coreutils() + .stdin_str("head -c 10 /dev/urandom | wc -c") + .run_wasm(include_bytes!("./wasm/dash.wasm")); + assert_json_snapshot!(snapshot); +} + +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_dash_dash() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .use_dash() + .stdin_str("/bin/dash\necho hi\nexit\nexit\n") + .run_wasm(include_bytes!("./wasm/dash.wasm")); + assert_json_snapshot!(snapshot); +} + +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_dash_bash() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .use_bash() + .stdin_str("/bin/bash\necho hi\nexit\nexit\n") + .run_wasm(include_bytes!("./wasm/dash.wasm")); + assert_json_snapshot!(snapshot); +} + +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_bash_echo() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .stdin_str("echo hello\n") + .run_wasm(include_bytes!("./wasm/bash.wasm")); + assert_json_snapshot!(snapshot); +} + +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_bash_ls() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .stdin_str("ls\nexit\n") + .use_coreutils() + .run_wasm(include_bytes!("./wasm/bash.wasm")); + assert_json_snapshot!(snapshot); +} + +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_bash_pipe() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .stdin_str("echo hello | cat\nexit\n") + .use_coreutils() + .run_wasm(include_bytes!("./wasm/bash.wasm")); + assert_json_snapshot!(snapshot); +} + +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_bash_python() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .use_coreutils() + .include_static_package("syrusakbary/python@0.1.0", WEBC_PYTHON) + .stdin_str("wasmer run syrusakbary/python -- -c 'print(10)'\n") + .run_wasm(include_bytes!("./wasm/bash.wasm")); + assert_json_snapshot!(snapshot); +} + +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_bash_bash() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .stdin_str("/bin/bash\necho hi\nexit\necho hi2\n") + .use_bash() + .run_wasm(include_bytes!("./wasm/bash.wasm")); + assert_json_snapshot!(snapshot); +} + +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] #[test] -fn test_snapshot_bash() { +fn test_snapshot_bash_dash() { let snapshot = TestBuilder::new() - .stdin_str("echo hello") + .with_name(function!()) + .use_dash() + .stdin_str("/bin/dash\necho hi\nexit\nexit\n") .run_wasm(include_bytes!("./wasm/bash.wasm")); - // TODO: more tests! assert_json_snapshot!(snapshot); } +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] #[test] fn test_snapshot_catsay() { let snapshot = TestBuilder::new() + .with_name(function!()) .stdin_str("meoooww") .run_wasm(include_bytes!("./wasm/catsay.wasm")); assert_json_snapshot!(snapshot); } +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] #[test] fn test_snapshot_quickjs() { let snapshot = TestBuilder::new() + .with_name(function!()) .stdin_str("print(2+2);\n\\q\n") .run_wasm(include_bytes!("./wasm/qjs.wasm")); assert_json_snapshot!(snapshot); diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_bash.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_bash.snap new file mode 100644 index 00000000000..d29d4a46db7 --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_bash.snap @@ -0,0 +1,66 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +assertion_line: 594 +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_bash_bash", + "use_packages": [ + "sharrattj/bash" + ], + "include_webcs": [ + { + "name": "sharrattj/bash@1.0.12" + }, + { + "name": "sharrattj/coreutils@1.0.11" + } + ], + "cli_args": [], + "stdin": [ + 47, + 98, + 105, + 110, + 47, + 98, + 97, + 115, + 104, + 10, + 101, + 99, + 104, + 111, + 32, + 104, + 105, + 10, + 101, + 120, + 105, + 116, + 10, + 101, + 99, + 104, + 111, + 32, + 104, + 105, + 50, + 10 + ], + "debug_output": false, + "enable_threads": true, + "enable_network": false + }, + "result": { + "Success": { + "stdout": "hi\nhi2\n", + "stderr": "test.wasm-5.1# bash-5.1# bash-5.1# exit\ntest.wasm-5.1# test.wasm-5.1# exit\n", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_dash.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_dash.snap new file mode 100644 index 00000000000..48f086210f0 --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_dash.snap @@ -0,0 +1,62 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +assertion_line: 622 +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_bash_dash", + "use_packages": [ + "sharrattj/dash" + ], + "include_webcs": [ + { + "name": "sharrattj/dash@1.0.16" + }, + { + "name": "sharrattj/coreutils@1.0.11" + } + ], + "cli_args": [], + "stdin": [ + 47, + 98, + 105, + 110, + 47, + 100, + 97, + 115, + 104, + 10, + 101, + 99, + 104, + 111, + 32, + 104, + 105, + 10, + 101, + 120, + 105, + 116, + 10, + 101, + 120, + 105, + 116, + 10 + ], + "debug_output": false, + "enable_threads": true, + "enable_network": false + }, + "result": { + "Success": { + "stdout": "hi\n", + "stderr": "test.wasm-5.1# # # test.wasm-5.1# exit\n", + "exit_code": 78 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_echo.snap similarity index 61% rename from tests/integration/cli/tests/snapshots/snapshot__snapshot_bash.snap rename to tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_echo.snap index f51d0f9cdc3..892da5b1649 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_echo.snap @@ -4,9 +4,9 @@ expression: snapshot --- { "spec": { - "name": null, - "wasm_hash": "34124ab56a8a69bc5964b05748e052d8", + "name": "snapshot::test_snapshot_bash_echo", "use_packages": [], + "include_webcs": [], "cli_args": [], "stdin": [ 101, @@ -18,15 +18,17 @@ expression: snapshot 101, 108, 108, - 111 + 111, + 10 ], "debug_output": false, - "enable_threads": true + "enable_threads": true, + "enable_network": false }, "result": { "Success": { "stdout": "hello\n", - "stderr": "34124ab56a8a69bc5964b05748e052d8.wasm-5.1# echo hello\n34124ab56a8a69bc5964b05748e052d8.wasm-5.1# exit\n", + "stderr": "test.wasm-5.1# test.wasm-5.1# exit\n", "exit_code": 0 } } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_ls.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_ls.snap new file mode 100644 index 00000000000..1d688eb6419 --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_ls.snap @@ -0,0 +1,39 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +assertion_line: 477 +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_bash_ls", + "use_packages": [ + "sharrattj/coreutils" + ], + "include_webcs": [ + { + "name": "sharrattj/coreutils@1.0.16" + } + ], + "cli_args": [], + "stdin": [ + 108, + 115, + 10, + 101, + 120, + 105, + 116, + 10 + ], + "debug_output": false, + "enable_threads": true, + "enable_network": false + }, + "result": { + "Success": { + "stdout": "bin\ndev\netc\ntmp\nusr\n", + "stderr": "test.wasm-5.1# test.wasm-5.1# exit\n", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_pipe.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_pipe.snap new file mode 100644 index 00000000000..2f8dd8607ed --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_pipe.snap @@ -0,0 +1,53 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +assertion_line: 487 +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_bash_pipe", + "use_packages": [ + "sharrattj/coreutils" + ], + "include_webcs": [ + { + "name": "sharrattj/coreutils@1.0.16" + } + ], + "cli_args": [], + "stdin": [ + 101, + 99, + 104, + 111, + 32, + 104, + 101, + 108, + 108, + 111, + 32, + 124, + 32, + 99, + 97, + 116, + 10, + 101, + 120, + 105, + 116, + 10 + ], + "debug_output": false, + "enable_threads": true, + "enable_network": false + }, + "result": { + "Success": { + "stdout": "hello\n", + "stderr": "test.wasm-5.1# test.wasm-5.1# exit\n", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_python.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_python.snap new file mode 100644 index 00000000000..8ecc184c455 --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_python.snap @@ -0,0 +1,82 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +assertion_line: 554 +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_bash_python", + "use_packages": [ + "sharrattj/coreutils" + ], + "include_webcs": [ + { + "name": "sharrattj/coreutils@1.0.16" + }, + { + "name": "syrusakbary/python@0.1.0" + } + ], + "cli_args": [], + "stdin": [ + 119, + 97, + 115, + 109, + 101, + 114, + 32, + 114, + 117, + 110, + 32, + 115, + 121, + 114, + 117, + 115, + 97, + 107, + 98, + 97, + 114, + 121, + 47, + 112, + 121, + 116, + 104, + 111, + 110, + 32, + 45, + 45, + 32, + 45, + 99, + 32, + 39, + 112, + 114, + 105, + 110, + 116, + 40, + 49, + 48, + 41, + 39, + 10 + ], + "debug_output": false, + "enable_threads": true, + "enable_network": false + }, + "result": { + "Success": { + "stdout": "10\n", + "stderr": "test.wasm-5.1# test.wasm-5.1# exit\n", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_catsay.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_catsay.snap index 3fe8545554e..b37f4e4447e 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_catsay.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_catsay.snap @@ -4,9 +4,9 @@ expression: snapshot --- { "spec": { - "name": null, - "wasm_hash": "6ee0e633eeb4a5caeef73493712870f6", + "name": "snapshot::test_snapshot_catsay", "use_packages": [], + "include_webcs": [], "cli_args": [], "stdin": [ 109, @@ -18,7 +18,8 @@ expression: snapshot 119 ], "debug_output": false, - "enable_threads": true + "enable_threads": true, + "enable_network": false }, "result": { "Success": { diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_condvar.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_condvar.snap index b9d876a288c..f58d950e827 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_condvar.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_condvar.snap @@ -4,13 +4,14 @@ expression: snapshot --- { "spec": { - "name": null, - "wasm_hash": "33552ff4ca03a898f0e2e671cfc56ed8", + "name": "snapshot::test_snapshot_condvar", "use_packages": [], + "include_webcs": [], "cli_args": [], "stdin": null, "debug_output": true, - "enable_threads": true + "enable_threads": true, + "enable_network": false }, "result": { "Success": { diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_cowsay.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_cowsay.snap index 597adebe217..cec88387447 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_cowsay.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_cowsay.snap @@ -4,9 +4,9 @@ expression: snapshot --- { "spec": { - "name": null, - "wasm_hash": "e124abdb35b6021a00ae6bf69dfba190", + "name": "snapshot::test_snapshot_cowsay", "use_packages": [], + "include_webcs": [], "cli_args": [], "stdin": [ 98, @@ -16,7 +16,8 @@ expression: snapshot 10 ], "debug_output": false, - "enable_threads": true + "enable_threads": true, + "enable_network": false }, "result": { "Success": { diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_bash.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_bash.snap new file mode 100644 index 00000000000..403bda5a745 --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_bash.snap @@ -0,0 +1,62 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +assertion_line: 561 +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_dash_bash", + "use_packages": [ + "sharrattj/bash" + ], + "include_webcs": [ + { + "name": "sharrattj/bash@1.0.12" + }, + { + "name": "sharrattj/coreutils@1.0.11" + } + ], + "cli_args": [], + "stdin": [ + 47, + 98, + 105, + 110, + 47, + 98, + 97, + 115, + 104, + 10, + 101, + 99, + 104, + 111, + 32, + 104, + 105, + 10, + 101, + 120, + 105, + 116, + 10, + 101, + 120, + 105, + 116, + 10 + ], + "debug_output": false, + "enable_threads": true, + "enable_network": false + }, + "result": { + "Success": { + "stdout": "hi\n", + "stderr": "# bash-5.1# exit\n# # ", + "exit_code": 78 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_dash.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_dash.snap new file mode 100644 index 00000000000..7e46eb51574 --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_dash.snap @@ -0,0 +1,62 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +assertion_line: 551 +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_dash_dash", + "use_packages": [ + "sharrattj/dash" + ], + "include_webcs": [ + { + "name": "sharrattj/dash@1.0.16" + }, + { + "name": "sharrattj/coreutils@1.0.11" + } + ], + "cli_args": [], + "stdin": [ + 47, + 98, + 105, + 110, + 47, + 100, + 97, + 115, + 104, + 10, + 101, + 99, + 104, + 111, + 32, + 104, + 105, + 10, + 101, + 120, + 105, + 116, + 10, + 101, + 120, + 105, + 116, + 10 + ], + "debug_output": false, + "enable_threads": true, + "enable_network": false + }, + "result": { + "Success": { + "stdout": "hi\n", + "stderr": "# # \n# # ", + "exit_code": 78 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_dev_urandom.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_dev_urandom.snap new file mode 100644 index 00000000000..3f97603037d --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_dev_urandom.snap @@ -0,0 +1,62 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +assertion_line: 642 +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_dash_dev_urandom", + "use_packages": [ + "sharrattj/coreutils" + ], + "include_webcs": [ + { + "name": "sharrattj/coreutils@1.0.16" + } + ], + "cli_args": [], + "stdin": [ + 104, + 101, + 97, + 100, + 32, + 45, + 99, + 32, + 49, + 48, + 32, + 47, + 100, + 101, + 118, + 47, + 117, + 114, + 97, + 110, + 100, + 111, + 109, + 32, + 124, + 32, + 119, + 99, + 32, + 45, + 99 + ], + "debug_output": false, + "enable_threads": true, + "enable_network": false + }, + "result": { + "Success": { + "stdout": "10\n", + "stderr": "# # \n", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_dev_zero.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_dev_zero.snap new file mode 100644 index 00000000000..9aae839fe1b --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_dev_zero.snap @@ -0,0 +1,51 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +assertion_line: 622 +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_dash_dev_zero", + "use_packages": [ + "sharrattj/coreutils" + ], + "include_webcs": [ + { + "name": "sharrattj/coreutils@1.0.16" + } + ], + "cli_args": [], + "stdin": [ + 104, + 101, + 97, + 100, + 32, + 45, + 99, + 32, + 49, + 48, + 32, + 47, + 100, + 101, + 118, + 47, + 122, + 101, + 114, + 111 + ], + "debug_output": false, + "enable_threads": true, + "enable_network": false + }, + "result": { + "Success": { + "stdout": "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000", + "stderr": "# # \n", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_echo.snap similarity index 73% rename from tests/integration/cli/tests/snapshots/snapshot__snapshot_dash.snap rename to tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_echo.snap index aeb53d46835..aef5c929c58 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_echo.snap @@ -4,9 +4,9 @@ expression: snapshot --- { "spec": { - "name": null, - "wasm_hash": "516e4c8a7100dbf1132826a2eb10ed94", + "name": "snapshot::test_snapshot_dash_echo", "use_packages": [], + "include_webcs": [], "cli_args": [], "stdin": [ 101, @@ -17,7 +17,8 @@ expression: snapshot 50 ], "debug_output": false, - "enable_threads": true + "enable_threads": true, + "enable_network": false }, "result": { "Success": { diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_echo_to_cat.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_echo_to_cat.snap new file mode 100644 index 00000000000..53782128179 --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_echo_to_cat.snap @@ -0,0 +1,47 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +assertion_line: 456 +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_dash_echo_to_cat", + "use_packages": [ + "sharrattj/coreutils" + ], + "include_webcs": [ + { + "name": "sharrattj/coreutils@1.0.16" + } + ], + "cli_args": [], + "stdin": [ + 101, + 99, + 104, + 111, + 32, + 104, + 101, + 108, + 108, + 111, + 32, + 124, + 32, + 99, + 97, + 116 + ], + "debug_output": false, + "enable_threads": true, + "enable_network": false + }, + "result": { + "Success": { + "stdout": "hello\n", + "stderr": "# # \n", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_python.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_python.snap new file mode 100644 index 00000000000..a090e6c8c17 --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_python.snap @@ -0,0 +1,81 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +assertion_line: 512 +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_dash_python", + "use_packages": [ + "sharrattj/coreutils" + ], + "include_webcs": [ + { + "name": "sharrattj/coreutils@1.0.16" + }, + { + "name": "syrusakbary/python@0.1.0" + } + ], + "cli_args": [], + "stdin": [ + 119, + 97, + 115, + 109, + 101, + 114, + 32, + 114, + 117, + 110, + 32, + 115, + 121, + 114, + 117, + 115, + 97, + 107, + 98, + 97, + 114, + 121, + 47, + 112, + 121, + 116, + 104, + 111, + 110, + 32, + 45, + 45, + 32, + 45, + 99, + 32, + 39, + 112, + 114, + 105, + 110, + 116, + 40, + 49, + 48, + 41, + 39 + ], + "debug_output": false, + "enable_threads": true, + "enable_network": false + }, + "result": { + "Success": { + "stdout": "10\n", + "stderr": "# # \n", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_default_file_system_tree.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_default_file_system_tree.snap index 6777d1e0a3f..52eddd561d5 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_default_file_system_tree.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_default_file_system_tree.snap @@ -4,15 +4,16 @@ expression: snapshot --- { "spec": { - "name": null, - "wasm_hash": "a14ec977d28125d9a8a24e5597553a19", + "name": "snapshot::test_snapshot_default_file_system_tree", "use_packages": [], + "include_webcs": [], "cli_args": [ "ls" ], "stdin": null, "debug_output": false, - "enable_threads": true + "enable_threads": true, + "enable_network": false }, "result": { "Success": { diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_epoll.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_epoll.snap new file mode 100644 index 00000000000..076d584e5f9 --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_epoll.snap @@ -0,0 +1,24 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +assertion_line: 363 +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_epoll", + "use_packages": [], + "include_webcs": [], + "cli_args": [], + "stdin": null, + "debug_output": false, + "enable_threads": true, + "enable_network": false + }, + "result": { + "Success": { + "stdout": "EFD_NONBLOCK:4\nsuccess write to efd, write 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\n", + "stderr": "", + "exit_code": 8 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_execve.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_execve.snap new file mode 100644 index 00000000000..00deec631dc --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_execve.snap @@ -0,0 +1,30 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +assertion_line: 381 +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_execve", + "use_packages": [ + "sharrattj/coreutils" + ], + "include_webcs": [ + { + "name": "sharrattj/coreutils@1.0.16" + } + ], + "cli_args": [], + "stdin": null, + "debug_output": false, + "enable_threads": true, + "enable_network": false + }, + "result": { + "Success": { + "stdout": "Main program started\nexecve: echo hi-from-child\nhi-from-child\nChild(2) exited with 0\nexecve: echo hi-from-parent\nhi-from-parent\n", + "stderr": "", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_file_copy.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_file_copy.snap new file mode 100644 index 00000000000..6d2791c7f8a --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_file_copy.snap @@ -0,0 +1,36 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +assertion_line: 374 +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_file_copy", + "use_packages": [ + "sharrattj/coreutils" + ], + "include_webcs": [ + { + "name": "sharrattj/coreutils@1.0.16" + } + ], + "cli_args": [ + "/dev/stdin", + "/dev/stdout" + ], + "stdin": [ + 104, + 105 + ], + "debug_output": false, + "enable_threads": true, + "enable_network": false + }, + "result": { + "Success": { + "stdout": "hi", + "stderr": "", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork.snap index 4185ac6ddba..61b794ff7a6 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork.snap @@ -4,19 +4,24 @@ expression: snapshot --- { "spec": { - "name": null, - "wasm_hash": "b1a423809f6a0ff526c3daf76024f39f", + "name": "snapshot::test_snapshot_fork", "use_packages": [ "sharrattj/coreutils" ], + "include_webcs": [ + { + "name": "sharrattj/coreutils@1.0.16" + } + ], "cli_args": [], "stdin": null, "debug_output": false, - "enable_threads": true + "enable_threads": true, + "enable_network": false }, "result": { "Success": { - "stdout": "Parent has x = 0\nChild has x = 2\nChild(2) exited with 0\n", + "stdout": "Parent has x = 0\nParent memory is good\nChild has x = 2\nChild memory is good\nChild(2) exited with 3\n", "stderr": "", "exit_code": 0 } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork_and_exec.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork_and_exec.snap index 00e3494301c..6c69a9f3ddd 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork_and_exec.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork_and_exec.snap @@ -4,15 +4,20 @@ expression: snapshot --- { "spec": { - "name": null, - "wasm_hash": "269f60f8ea24ed3fbbddd930abecaf6c", + "name": "snapshot::test_snapshot_fork_and_exec", "use_packages": [ "sharrattj/coreutils" ], + "include_webcs": [ + { + "name": "sharrattj/coreutils@1.0.16" + } + ], "cli_args": [], "stdin": null, "debug_output": false, - "enable_threads": true + "enable_threads": true, + "enable_network": false }, "result": { "Success": { diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_longjump.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_longjump.snap index 38ee2d14780..09ce0a8f0e2 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_longjump.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_longjump.snap @@ -4,15 +4,20 @@ expression: snapshot --- { "spec": { - "name": null, - "wasm_hash": "31459fbda894b527295e874cdec44514", + "name": "snapshot::test_snapshot_longjump", "use_packages": [ "sharrattj/coreutils" ], + "include_webcs": [ + { + "name": "sharrattj/coreutils@1.0.16" + } + ], "cli_args": [], "stdin": null, "debug_output": false, - "enable_threads": true + "enable_threads": true, + "enable_network": false }, "result": { "Success": { diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_longjump2.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_longjump2.snap index 83b0d9750f4..761a1e59938 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_longjump2.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_longjump2.snap @@ -4,15 +4,20 @@ expression: snapshot --- { "spec": { - "name": null, - "wasm_hash": "999c6459bbc565b8f2c483c7baec2208", + "name": "snapshot::test_snapshot_longjump2", "use_packages": [ "sharrattj/coreutils" ], + "include_webcs": [ + { + "name": "sharrattj/coreutils@1.0.16" + } + ], "cli_args": [], "stdin": null, "debug_output": false, - "enable_threads": true + "enable_threads": true, + "enable_network": false }, "result": { "Success": { diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_longjump_fork.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_longjump_fork.snap index 96cd53e8c3c..4eaa2ea17dc 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_longjump_fork.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_longjump_fork.snap @@ -4,17 +4,18 @@ expression: snapshot --- { "spec": { - "name": null, - "wasm_hash": "46528fe586c0a942d8504354b6a2ca48", + "name": "snapshot::test_snapshot_longjump_fork", "use_packages": [], + "include_webcs": [], "cli_args": [], "stdin": null, "debug_output": false, - "enable_threads": true + "enable_threads": true, + "enable_network": false }, "result": { "Success": { - "stdout": "Parent has x = 0\nChild has x = 2\nChild(2) exited with 5\n", + "stdout": "Parent memory is good\nParent memory after longjmp is good\nParent has x = 0\nChild memory is good\nChild memory after longjmp is good\nChild has x = 2\nChild(2) exited with 5\n", "stderr": "", "exit_code": 0 } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_multithreading.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_multithreading.snap index 19c30331611..da0751a6add 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_multithreading.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_multithreading.snap @@ -4,13 +4,14 @@ expression: snapshot --- { "spec": { - "name": null, - "wasm_hash": "7ed0538ebf51b6afd5fe203200365271", + "name": "snapshot::test_snapshot_multithreading", "use_packages": [], + "include_webcs": [], "cli_args": [], "stdin": null, - "debug_output": false, - "enable_threads": true + "debug_output": true, + "enable_threads": true, + "enable_network": false }, "result": { "Success": { diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_pipes.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_pipes.snap index 2821176ad78..ae9f3e6e3c1 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_pipes.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_pipes.snap @@ -4,15 +4,20 @@ expression: snapshot --- { "spec": { - "name": null, - "wasm_hash": "0dbff3332afdb3a5cec08478d85b45e2", + "name": "snapshot::test_snapshot_pipes", "use_packages": [ "sharrattj/coreutils" ], + "include_webcs": [ + { + "name": "sharrattj/coreutils@1.0.16" + } + ], "cli_args": [], "stdin": null, "debug_output": false, - "enable_threads": true + "enable_threads": true, + "enable_network": false }, "result": { "Success": { diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_process_spawn.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_process_spawn.snap index 860c7719a2b..a7eab67a4d9 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_process_spawn.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_process_spawn.snap @@ -4,15 +4,20 @@ expression: snapshot --- { "spec": { - "name": null, - "wasm_hash": "236b09a27be50336a6abf87e53ca754d", + "name": "snapshot::test_snapshot_process_spawn", "use_packages": [ "sharrattj/coreutils" ], + "include_webcs": [ + { + "name": "sharrattj/coreutils@1.0.16" + } + ], "cli_args": [], "stdin": null, "debug_output": false, - "enable_threads": true + "enable_threads": true, + "enable_network": false }, "result": { "Success": { diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_quickjs.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_quickjs.snap index 20a810a2ca2..4d7cb7a4f4c 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_quickjs.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_quickjs.snap @@ -4,9 +4,9 @@ expression: snapshot --- { "spec": { - "name": null, - "wasm_hash": "44ceba0b68971110a9bc8af3349e7663", + "name": "snapshot::test_snapshot_quickjs", "use_packages": [], + "include_webcs": [], "cli_args": [], "stdin": [ 112, @@ -26,7 +26,8 @@ expression: snapshot 10 ], "debug_output": false, - "enable_threads": true + "enable_threads": true, + "enable_network": false }, "result": { "Success": { diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_signals.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_signals.snap new file mode 100644 index 00000000000..7810ad28db4 --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_signals.snap @@ -0,0 +1,24 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +assertion_line: 537 +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_signals", + "use_packages": [], + "include_webcs": [], + "cli_args": [], + "stdin": null, + "debug_output": false, + "enable_threads": true, + "enable_network": false + }, + "result": { + "Success": { + "stdout": "received SIGHUP\n", + "stderr": "", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_sleep.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_sleep.snap index 3d6c2c2b9c6..8b7eaff0ea1 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_sleep.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_sleep.snap @@ -4,13 +4,14 @@ expression: snapshot --- { "spec": { - "name": null, - "wasm_hash": "dd97d3f821b4239d3cf905958b613f77", + "name": "snapshot::test_snapshot_sleep", "use_packages": [], + "include_webcs": [], "cli_args": [], "stdin": null, "debug_output": false, - "enable_threads": true + "enable_threads": true, + "enable_network": false }, "result": { "Success": { diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_stdin_stdout_stderr.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_stdin_stdout_stderr.snap index 613893f6a47..14e0b0a4e41 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_stdin_stdout_stderr.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_stdin_stdout_stderr.snap @@ -4,9 +4,9 @@ expression: snapshot --- { "spec": { - "name": null, - "wasm_hash": "a14ec977d28125d9a8a24e5597553a19", + "name": "snapshot::test_snapshot_stdin_stdout_stderr", "use_packages": [], + "include_webcs": [], "cli_args": [ "tee", "/dev/stderr" @@ -18,7 +18,8 @@ expression: snapshot 104 ], "debug_output": false, - "enable_threads": true + "enable_threads": true, + "enable_network": false }, "result": { "Success": { diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_thread_locals.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_thread_locals.snap index 411e7699ed5..17041482d40 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_thread_locals.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_thread_locals.snap @@ -4,15 +4,14 @@ expression: snapshot --- { "spec": { - "name": null, - "wasm_hash": "7f838e4d318a694677b235d35418452a", - "use_packages": [ - "sharrattj/coreutils" - ], + "name": "snapshot::test_snapshot_thread_locals", + "use_packages": [], + "include_webcs": [], "cli_args": [], "stdin": null, "debug_output": false, - "enable_threads": true + "enable_threads": true, + "enable_network": false }, "result": { "Success": { diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_vfork.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_vfork.snap index 6149ad4b5db..4a0d8de7685 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_vfork.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_vfork.snap @@ -4,19 +4,24 @@ expression: snapshot --- { "spec": { - "name": null, - "wasm_hash": "a20a74e0008e21437221d6b2a8bc8d4d", + "name": "snapshot::test_snapshot_vfork", "use_packages": [ "sharrattj/coreutils" ], + "include_webcs": [ + { + "name": "sharrattj/coreutils@1.0.16" + } + ], "cli_args": [], "stdin": null, "debug_output": false, - "enable_threads": true + "enable_threads": true, + "enable_network": false }, "result": { "Success": { - "stdout": "Parent waiting on Child(2)\nChild(2) exited with 10\n", + "stdout": "Parent waiting on Child(2)\nChild(2) exited with 0\n", "stderr": "", "exit_code": 0 } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_web_server.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_web_server.snap new file mode 100644 index 00000000000..7e2eff9f72c --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_web_server.snap @@ -0,0 +1,171 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +assertion_line: 492 +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_web_server", + "use_packages": [ + "sharrattj/coreutils", + "sharrattj/wasmer-sh" + ], + "include_webcs": [ + { + "name": "sharrattj/static-web-server@1.0.8" + }, + { + "name": "sharrattj/wasmer-sh@1.0.63" + }, + { + "name": "sharrattj/coreutils@1.0.16" + } + ], + "cli_args": [], + "stdin": [ + 10, + 99, + 97, + 116, + 32, + 47, + 112, + 117, + 98, + 108, + 105, + 99, + 47, + 109, + 97, + 105, + 110, + 46, + 106, + 115, + 32, + 124, + 32, + 119, + 99, + 32, + 45, + 99, + 32, + 62, + 32, + 47, + 112, + 117, + 98, + 108, + 105, + 99, + 47, + 109, + 97, + 105, + 110, + 46, + 106, + 115, + 46, + 115, + 105, + 122, + 101, + 10, + 114, + 109, + 32, + 45, + 102, + 32, + 47, + 99, + 102, + 103, + 47, + 99, + 111, + 110, + 102, + 105, + 103, + 46, + 116, + 111, + 109, + 108, + 10, + 47, + 98, + 105, + 110, + 47, + 119, + 101, + 98, + 115, + 101, + 114, + 118, + 101, + 114, + 32, + 45, + 45, + 108, + 111, + 103, + 45, + 108, + 101, + 118, + 101, + 108, + 32, + 119, + 97, + 114, + 110, + 32, + 45, + 45, + 114, + 111, + 111, + 116, + 32, + 47, + 112, + 117, + 98, + 108, + 105, + 99, + 32, + 45, + 45, + 112, + 111, + 114, + 116, + 32, + 55, + 55, + 55, + 55 + ], + "debug_output": false, + "enable_threads": true, + "enable_network": true + }, + "result": { + "Success": { + "stdout": "", + "stderr": "# # # # ", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/wasm/bash.wasm b/tests/integration/cli/tests/wasm/bash.wasm index 42eb17790c6..2b3e1c0c1a5 100644 Binary files a/tests/integration/cli/tests/wasm/bash.wasm and b/tests/integration/cli/tests/wasm/bash.wasm differ diff --git a/tests/integration/cli/tests/wasm/coreutils.wasm b/tests/integration/cli/tests/wasm/coreutils.wasm index 428219edb6f..9a5cebe200e 100755 Binary files a/tests/integration/cli/tests/wasm/coreutils.wasm and b/tests/integration/cli/tests/wasm/coreutils.wasm differ diff --git a/tests/integration/cli/tests/wasm/dash.wasm b/tests/integration/cli/tests/wasm/dash.wasm index 5ea39e9d342..268d91a5efd 100755 Binary files a/tests/integration/cli/tests/wasm/dash.wasm and b/tests/integration/cli/tests/wasm/dash.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-condvar.wasm b/tests/integration/cli/tests/wasm/example-condvar.wasm index 4aad0385307..89a8da08e9f 100755 Binary files a/tests/integration/cli/tests/wasm/example-condvar.wasm and b/tests/integration/cli/tests/wasm/example-condvar.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-epoll.wasm b/tests/integration/cli/tests/wasm/example-epoll.wasm index 990c9ade64f..c04cad0205a 100644 Binary files a/tests/integration/cli/tests/wasm/example-epoll.wasm and b/tests/integration/cli/tests/wasm/example-epoll.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-execve.wasm b/tests/integration/cli/tests/wasm/example-execve.wasm index a7391e00065..5caeb2fde26 100644 Binary files a/tests/integration/cli/tests/wasm/example-execve.wasm and b/tests/integration/cli/tests/wasm/example-execve.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-file-copy.wasm b/tests/integration/cli/tests/wasm/example-file-copy.wasm new file mode 100644 index 00000000000..a1577cee14f Binary files /dev/null and b/tests/integration/cli/tests/wasm/example-file-copy.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-fork-longjmp.wasm b/tests/integration/cli/tests/wasm/example-fork-longjmp.wasm index c1286dd46ed..e2e847c693f 100644 Binary files a/tests/integration/cli/tests/wasm/example-fork-longjmp.wasm and b/tests/integration/cli/tests/wasm/example-fork-longjmp.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-fork.wasm b/tests/integration/cli/tests/wasm/example-fork.wasm index 355379ad97d..d805b180fac 100755 Binary files a/tests/integration/cli/tests/wasm/example-fork.wasm and b/tests/integration/cli/tests/wasm/example-fork.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-http-ok.wasm b/tests/integration/cli/tests/wasm/example-http-ok.wasm new file mode 100644 index 00000000000..3ddc0ba9f70 Binary files /dev/null and b/tests/integration/cli/tests/wasm/example-http-ok.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-longjmp.wasm b/tests/integration/cli/tests/wasm/example-longjmp.wasm index c3033b1dccf..6d7d8b48f64 100755 Binary files a/tests/integration/cli/tests/wasm/example-longjmp.wasm and b/tests/integration/cli/tests/wasm/example-longjmp.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-multi-threading.wasm b/tests/integration/cli/tests/wasm/example-multi-threading.wasm index 4967a3dc781..7df2b6119f0 100644 Binary files a/tests/integration/cli/tests/wasm/example-multi-threading.wasm and b/tests/integration/cli/tests/wasm/example-multi-threading.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-pipe.wasm b/tests/integration/cli/tests/wasm/example-pipe.wasm index 08527160712..14af9d99966 100644 Binary files a/tests/integration/cli/tests/wasm/example-pipe.wasm and b/tests/integration/cli/tests/wasm/example-pipe.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-signal.wasm b/tests/integration/cli/tests/wasm/example-signal.wasm index f8a7fd536a9..52dcbdaef67 100755 Binary files a/tests/integration/cli/tests/wasm/example-signal.wasm and b/tests/integration/cli/tests/wasm/example-signal.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-spawn.wasm b/tests/integration/cli/tests/wasm/example-spawn.wasm index 30705e7bb80..16b344b19d1 100644 Binary files a/tests/integration/cli/tests/wasm/example-spawn.wasm and b/tests/integration/cli/tests/wasm/example-spawn.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-tcp-listener.wasm b/tests/integration/cli/tests/wasm/example-tcp-listener.wasm index dcbf767b515..c383d200254 100644 Binary files a/tests/integration/cli/tests/wasm/example-tcp-listener.wasm and b/tests/integration/cli/tests/wasm/example-tcp-listener.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-thread-local.wasm b/tests/integration/cli/tests/wasm/example-thread-local.wasm index cbb99c44596..221b1c43e63 100644 Binary files a/tests/integration/cli/tests/wasm/example-thread-local.wasm and b/tests/integration/cli/tests/wasm/example-thread-local.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-vfork.wasm b/tests/integration/cli/tests/wasm/example-vfork.wasm index b6a1a170b5d..476ad33f57e 100644 Binary files a/tests/integration/cli/tests/wasm/example-vfork.wasm and b/tests/integration/cli/tests/wasm/example-vfork.wasm differ diff --git a/tests/integration/cli/tests/wasm/multi-threading.wasm b/tests/integration/cli/tests/wasm/multi-threading.wasm index 4967a3dc781..64a4ebec2b6 100644 Binary files a/tests/integration/cli/tests/wasm/multi-threading.wasm and b/tests/integration/cli/tests/wasm/multi-threading.wasm differ diff --git a/tests/integration/cli/tests/wasm/web-server.wasm b/tests/integration/cli/tests/wasm/web-server.wasm index ae35be7376b..640a3f48c9e 100755 Binary files a/tests/integration/cli/tests/wasm/web-server.wasm and b/tests/integration/cli/tests/wasm/web-server.wasm differ diff --git a/tests/integration/cli/tests/webc/bash-1.0.12-0103d733-1afb-4a56-b0ef-0e124139e996.webc b/tests/integration/cli/tests/webc/bash-1.0.12-0103d733-1afb-4a56-b0ef-0e124139e996.webc new file mode 100644 index 00000000000..69aec17cbbf Binary files /dev/null and b/tests/integration/cli/tests/webc/bash-1.0.12-0103d733-1afb-4a56-b0ef-0e124139e996.webc differ diff --git a/tests/integration/cli/tests/webc/coreutils-1.0.11-9d7746ca-694f-11ed-b932-dead3543c068.webc b/tests/integration/cli/tests/webc/coreutils-1.0.11-9d7746ca-694f-11ed-b932-dead3543c068.webc new file mode 100644 index 00000000000..68f1a5f0edd Binary files /dev/null and b/tests/integration/cli/tests/webc/coreutils-1.0.11-9d7746ca-694f-11ed-b932-dead3543c068.webc differ diff --git a/tests/integration/cli/tests/webc/coreutils-1.0.14-076508e5-e704-463f-b467-f3d9658fc907.webc b/tests/integration/cli/tests/webc/coreutils-1.0.14-076508e5-e704-463f-b467-f3d9658fc907.webc new file mode 100644 index 00000000000..54477471634 Binary files /dev/null and b/tests/integration/cli/tests/webc/coreutils-1.0.14-076508e5-e704-463f-b467-f3d9658fc907.webc differ diff --git a/tests/integration/cli/tests/webc/dash-1.0.16-bd931010-c134-4785-9423-13c0a0d49d90.webc b/tests/integration/cli/tests/webc/dash-1.0.16-bd931010-c134-4785-9423-13c0a0d49d90.webc new file mode 100644 index 00000000000..bde1a38a85a Binary files /dev/null and b/tests/integration/cli/tests/webc/dash-1.0.16-bd931010-c134-4785-9423-13c0a0d49d90.webc differ diff --git a/tests/integration/cli/tests/webc/python-0.1.0.webc b/tests/integration/cli/tests/webc/python-0.1.0.webc new file mode 100644 index 00000000000..43eeccc2c67 Binary files /dev/null and b/tests/integration/cli/tests/webc/python-0.1.0.webc differ diff --git a/tests/integration/cli/tests/webc/static-web-server-1.0.8-a241658c-e409-4749-872c-ae8eab142ef0.webc b/tests/integration/cli/tests/webc/static-web-server-1.0.8-a241658c-e409-4749-872c-ae8eab142ef0.webc new file mode 100644 index 00000000000..5c839c1f42a Binary files /dev/null and b/tests/integration/cli/tests/webc/static-web-server-1.0.8-a241658c-e409-4749-872c-ae8eab142ef0.webc differ diff --git a/tests/integration/cli/tests/webc/wasmer-sh-1.0.63-dd3d67d1-de94-458c-a9ee-caea3b230ccf.webc b/tests/integration/cli/tests/webc/wasmer-sh-1.0.63-dd3d67d1-de94-458c-a9ee-caea3b230ccf.webc new file mode 100644 index 00000000000..7293f0c4f60 Binary files /dev/null and b/tests/integration/cli/tests/webc/wasmer-sh-1.0.63-dd3d67d1-de94-458c-a9ee-caea3b230ccf.webc differ