From acf244f0be0310e4cc6aada0fcabd90f0009486b Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Fri, 21 Feb 2025 20:17:50 +0000 Subject: [PATCH] Add proper posix_spawn support without needing asyncify --- lib/wasi-types/src/wasi/bindings.rs | 87 ++++++ lib/wasi-types/src/wasi/wasix_manual.rs | 15 +- lib/wasix/Cargo.toml | 6 +- .../src/journal/effector/syscalls/chdir.rs | 2 +- .../journal/effector/syscalls/path_open.rs | 2 +- lib/wasix/src/lib.rs | 6 + lib/wasix/src/state/builder.rs | 62 +++- lib/wasix/src/state/env.rs | 1 + lib/wasix/src/state/mod.rs | 4 +- lib/wasix/src/syscalls/mod.rs | 17 +- lib/wasix/src/syscalls/wasi/path_open.rs | 2 +- lib/wasix/src/syscalls/wasix/chdir.rs | 7 +- lib/wasix/src/syscalls/wasix/mod.rs | 6 + lib/wasix/src/syscalls/wasix/path_open2.rs | 9 +- lib/wasix/src/syscalls/wasix/proc_exec2.rs | 13 +- lib/wasix/src/syscalls/wasix/proc_exec3.rs | 71 ++++- .../syscalls/wasix/proc_signals_count_get.rs | 32 ++ .../src/syscalls/wasix/proc_signals_get.rs | 30 ++ lib/wasix/src/syscalls/wasix/proc_spawn2.rs | 273 ++++++++++++++++++ tests/wasix/_posix_spawn/main.c | 263 +++++++++++++++++ tests/wasix/_posix_spawn/run.sh | 6 + tests/wasix/cloexec/main.c | 10 +- tests/wasix/closing-pre-opened-dirs/main.c | 6 +- tests/wasix/test.sh | 4 +- 24 files changed, 878 insertions(+), 56 deletions(-) create mode 100644 lib/wasix/src/syscalls/wasix/proc_signals_count_get.rs create mode 100644 lib/wasix/src/syscalls/wasix/proc_signals_get.rs create mode 100644 lib/wasix/src/syscalls/wasix/proc_spawn2.rs create mode 100644 tests/wasix/_posix_spawn/main.c create mode 100755 tests/wasix/_posix_spawn/run.sh diff --git a/lib/wasi-types/src/wasi/bindings.rs b/lib/wasi-types/src/wasi/bindings.rs index b6bd04e48e7..e11b90cec24 100644 --- a/lib/wasi-types/src/wasi/bindings.rs +++ b/lib/wasi-types/src/wasi/bindings.rs @@ -2328,6 +2328,93 @@ impl core::fmt::Debug for Signal { } } } + +#[repr(u8)] +#[derive(Clone, Copy, PartialEq, Eq, num_enum :: TryFromPrimitive, Hash)] +pub enum SigAction { + Default, + Ignore, +} +impl core::fmt::Debug for SigAction { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + SigAction::Default => f.debug_tuple("SigAction::Default").finish(), + SigAction::Ignore => f.debug_tuple("SigAction::Ignore").finish(), + } + } +} + +#[doc = " A signal and its corresponding action."] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct SignalAndAction { + pub sig: Signal, + pub act: SigAction, +} +impl core::fmt::Debug for SignalAndAction { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("SignalAndAction") + .field("sig", &self.sig) + .field("act", &self.act) + .finish() + } +} + +#[repr(u8)] +#[derive(Clone, Copy, PartialEq, Eq, num_enum :: TryFromPrimitive, Hash)] +pub enum ProcSpawnFdOpName { + Close, + Dup2, + Open, + Chdir, + Fchdir, +} +impl core::fmt::Debug for ProcSpawnFdOpName { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + ProcSpawnFdOpName::Close => f.debug_tuple("ProcSpawnFdOpName::Close").finish(), + ProcSpawnFdOpName::Dup2 => f.debug_tuple("ProcSpawnFdOpName::Dup2").finish(), + ProcSpawnFdOpName::Open => f.debug_tuple("ProcSpawnFdOpName::Open").finish(), + ProcSpawnFdOpName::Chdir => f.debug_tuple("ProcSpawnFdOpName::Chdir").finish(), + ProcSpawnFdOpName::Fchdir => f.debug_tuple("ProcSpawnFdOpName::Fchdir").finish(), + } + } +} + +#[doc = " An FD operation performed during proc_spawn2, which is the backing syscall for posix_spawn."] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct ProcSpawnFdOp { + pub cmd: ProcSpawnFdOpName, + pub fd: Fd, + pub src_fd: Fd, + pub name: M::Offset, + pub name_len: M::Offset, + pub dirflags: LookupFlags, + pub oflags: Oflags, + pub fs_rights_base: Rights, + pub fs_rights_inheriting: Rights, + pub fdflags: Fdflags, + pub fdflagsext: Fdflagsext, +} +impl core::fmt::Debug for ProcSpawnFdOp { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("ProcSpawnFdOp") + .field("cmd", &self.cmd) + .field("fd", &self.fd) + .field("src_fd", &self.src_fd) + .field("name", &self.name) + .field("name_len", &self.name_len) + .field("dirflags", &self.dirflags) + .field("oflags", &self.oflags) + .field("fs_rights_base", &self.fs_rights_base) + .field("fs_rights_inheriting", &self.fs_rights_inheriting) + .field("fdflags", &self.fdflags) + .field("fdflagsext", &self.fdflagsext) + .finish() + } +} + #[repr(C)] #[derive(Copy, Clone)] pub struct AddrUnspec { diff --git a/lib/wasi-types/src/wasi/wasix_manual.rs b/lib/wasi-types/src/wasi/wasix_manual.rs index f827ff8d32e..b689a1db796 100644 --- a/lib/wasi-types/src/wasi/wasix_manual.rs +++ b/lib/wasi-types/src/wasi/wasix_manual.rs @@ -5,8 +5,9 @@ use std::mem::MaybeUninit; use wasmer::{FromToNativeWasmType, MemorySize, ValueType}; use super::{ - Errno, ErrnoSignal, EventFdReadwrite, Eventtype, Fd, JoinStatusType, Signal, - Snapshot0SubscriptionClock, SubscriptionClock, SubscriptionFsReadwrite, Userdata, + Errno, ErrnoSignal, EventFdReadwrite, Eventtype, Fd, JoinStatusType, ProcSpawnFdOp, Signal, + SignalAndAction, Snapshot0SubscriptionClock, SubscriptionClock, SubscriptionFsReadwrite, + Userdata, }; /// Thread local key @@ -505,3 +506,13 @@ where #[inline] fn zero_padding_bytes(&self, _bytes: &mut [MaybeUninit]) {} } + +unsafe impl ValueType for SignalAndAction { + #[inline] + fn zero_padding_bytes(&self, _bytes: &mut [MaybeUninit]) {} +} + +unsafe impl ValueType for ProcSpawnFdOp { + #[inline] + fn zero_padding_bytes(&self, _bytes: &mut [MaybeUninit]) {} +} diff --git a/lib/wasix/Cargo.toml b/lib/wasix/Cargo.toml index 67257722ee7..9b3f6986812 100644 --- a/lib/wasix/Cargo.toml +++ b/lib/wasix/Cargo.toml @@ -213,9 +213,9 @@ sys-default = [ "sys-thread", "host-vnet", "host-threads", - "host-reqwest", - "wasmer/sys", - "ctrlc" + "host-reqwest", + "wasmer/sys", + "ctrlc", ] sys-poll = [] extra-logging = [] diff --git a/lib/wasix/src/journal/effector/syscalls/chdir.rs b/lib/wasix/src/journal/effector/syscalls/chdir.rs index aa3041a1562..d23dc733dc2 100644 --- a/lib/wasix/src/journal/effector/syscalls/chdir.rs +++ b/lib/wasix/src/journal/effector/syscalls/chdir.rs @@ -6,7 +6,7 @@ impl JournalEffector { } pub fn apply_chdir(ctx: &mut FunctionEnvMut<'_, WasiEnv>, path: &str) -> anyhow::Result<()> { - crate::syscalls::chdir_internal(ctx, path).map_err(|err| { + crate::syscalls::chdir_internal(ctx.data(), path).map_err(|err| { anyhow::format_err!( "snapshot restore error: failed to change directory (path={}) - {}", path, diff --git a/lib/wasix/src/journal/effector/syscalls/path_open.rs b/lib/wasix/src/journal/effector/syscalls/path_open.rs index 21a7daaeaa5..476b91ab45a 100644 --- a/lib/wasix/src/journal/effector/syscalls/path_open.rs +++ b/lib/wasix/src/journal/effector/syscalls/path_open.rs @@ -44,7 +44,7 @@ impl JournalEffector { fd_flags: Fdflagsext, ) -> anyhow::Result<()> { let res = crate::syscalls::path_open_internal( - ctx, + ctx.data(), dirfd, dirflags, path, diff --git a/lib/wasix/src/lib.rs b/lib/wasix/src/lib.rs index cf4dbf8c762..49445088782 100644 --- a/lib/wasix/src/lib.rs +++ b/lib/wasix/src/lib.rs @@ -549,6 +549,8 @@ fn wasix_exports_32(mut store: &mut impl AsStoreMut, env: &FunctionEnv) "proc_fork" => Function::new_typed_with_env(&mut store, env, proc_fork::), "proc_join" => Function::new_typed_with_env(&mut store, env, proc_join::), "proc_signal" => Function::new_typed_with_env(&mut store, env, proc_signal::), + "proc_signals_count_get" => Function::new_typed_with_env(&mut store, env, proc_signals_count_get::), + "proc_signals_get" => Function::new_typed_with_env(&mut store, env, proc_signals_get::), "proc_exec" => Function::new_typed_with_env(&mut store, env, proc_exec::), "proc_exec2" => Function::new_typed_with_env(&mut store, env, proc_exec2::), "proc_exec3" => Function::new_typed_with_env(&mut store, env, proc_exec3::), @@ -556,6 +558,7 @@ fn wasix_exports_32(mut store: &mut impl AsStoreMut, env: &FunctionEnv) "proc_raise_interval" => Function::new_typed_with_env(&mut store, env, proc_raise_interval), "proc_snapshot" => Function::new_typed_with_env(&mut store, env, proc_snapshot::), "proc_spawn" => Function::new_typed_with_env(&mut store, env, proc_spawn::), + "proc_spawn2" => Function::new_typed_with_env(&mut store, env, proc_spawn2::), "proc_id" => Function::new_typed_with_env(&mut store, env, proc_id::), "proc_parent" => Function::new_typed_with_env(&mut store, env, proc_parent::), "random_get" => Function::new_typed_with_env(&mut store, env, random_get::), @@ -678,6 +681,8 @@ fn wasix_exports_64(mut store: &mut impl AsStoreMut, env: &FunctionEnv) "proc_fork" => Function::new_typed_with_env(&mut store, env, proc_fork::), "proc_join" => Function::new_typed_with_env(&mut store, env, proc_join::), "proc_signal" => Function::new_typed_with_env(&mut store, env, proc_signal::), + "proc_signals_count_get" => Function::new_typed_with_env(&mut store, env, proc_signals_count_get::), + "proc_signals_get" => Function::new_typed_with_env(&mut store, env, proc_signals_get::), "proc_exec" => Function::new_typed_with_env(&mut store, env, proc_exec::), "proc_exec2" => Function::new_typed_with_env(&mut store, env, proc_exec2::), "proc_exec3" => Function::new_typed_with_env(&mut store, env, proc_exec3::), @@ -685,6 +690,7 @@ fn wasix_exports_64(mut store: &mut impl AsStoreMut, env: &FunctionEnv) "proc_raise_interval" => Function::new_typed_with_env(&mut store, env, proc_raise_interval), "proc_snapshot" => Function::new_typed_with_env(&mut store, env, proc_snapshot::), "proc_spawn" => Function::new_typed_with_env(&mut store, env, proc_spawn::), + "proc_spawn2" => Function::new_typed_with_env(&mut store, env, proc_spawn2::), "proc_id" => Function::new_typed_with_env(&mut store, env, proc_id::), "proc_parent" => Function::new_typed_with_env(&mut store, env, proc_parent::), "random_get" => Function::new_typed_with_env(&mut store, env, random_get::), diff --git a/lib/wasix/src/state/builder.rs b/lib/wasix/src/state/builder.rs index 340e01c3792..7aba25af5d1 100644 --- a/lib/wasix/src/state/builder.rs +++ b/lib/wasix/src/state/builder.rs @@ -28,6 +28,7 @@ use crate::{ Runtime, WasiEnv, WasiError, WasiFunctionEnv, WasiRuntimeError, }; use wasmer_types::ModuleHash; +use wasmer_wasix_types::wasi::SignalAndAction; use super::env::WasiEnvInit; @@ -55,6 +56,8 @@ pub struct WasiEnvBuilder { pub(super) args: Vec, /// Environment variables. pub(super) envs: Vec<(String, Vec)>, + /// Signals that should get their handler overridden. + pub(super) signals: Vec, /// Pre-opened directories that will be accessible from WASI. pub(super) preopens: Vec, /// Pre-opened virtual directories that will be accessible from WASI. @@ -102,6 +105,7 @@ impl std::fmt::Debug for WasiEnvBuilder { .field("entry_function", &self.entry_function) .field("args", &self.args) .field("envs", &self.envs) + .field("signals", &self.signals) .field("preopens", &self.preopens) .field("uses", &self.uses) .field("setup_fs_fn exists", &self.setup_fs_fn.is_some()) @@ -163,6 +167,14 @@ impl WasiEnvBuilder { } } + /// Attaches a ctrl-c handler which will send signals to the + /// process rather than immediately termiante it + #[cfg(feature = "ctrlc")] + pub fn attach_ctrl_c(mut self) -> Self { + self.attach_ctrl_c = true; + self + } + /// Add an environment variable pair. /// /// Both the key and value of an environment variable must not @@ -177,14 +189,6 @@ impl WasiEnvBuilder { self } - /// Attaches a ctrl-c handler which will send signals to the - /// process rather than immediately termiante it - #[cfg(feature = "ctrlc")] - pub fn attach_ctrl_c(mut self) -> Self { - self.attach_ctrl_c = true; - self - } - /// Add an environment variable pair. /// /// Both the key and value of an environment variable must not @@ -243,6 +247,47 @@ impl WasiEnvBuilder { &mut self.envs } + /// Add a signal handler override. + pub fn signal(mut self, sig_action: SignalAndAction) -> Self { + self.add_signal(sig_action); + self + } + + /// Add a signal handler override. + pub fn add_signal(&mut self, sig_action: SignalAndAction) { + self.signals.push(sig_action); + } + + /// Add multiple signal handler overrides. + pub fn signals(mut self, signal_pairs: I) -> Self + where + I: IntoIterator, + { + self.add_signals(signal_pairs); + + self + } + + /// Add multiple signal handler overrides. + pub fn add_signals(&mut self, signal_pairs: I) + where + I: IntoIterator, + { + for sig in signal_pairs { + self.add_signal(sig); + } + } + + /// Get a reference to the configured signal handler overrides. + pub fn get_signal(&self) -> &[SignalAndAction] { + &self.signals + } + + /// Get a mutable reference to the configured signalironment variables. + pub fn get_signal_mut(&mut self) -> &mut Vec { + &mut self.signals + } + pub fn entry_function(mut self, entry_function: S) -> Self where S: AsRef, @@ -891,6 +936,7 @@ impl WasiEnvBuilder { futexs: Default::default(), clock_offset: Default::default(), envs: std::sync::Mutex::new(conv_env_vars(self.envs)), + signals: std::sync::Mutex::new(self.signals.iter().map(|s| (s.sig, s.act)).collect()), }; let runtime = self.runtime.unwrap_or_else(|| { diff --git a/lib/wasix/src/state/env.rs b/lib/wasix/src/state/env.rs index 77952afb003..b555ae72e57 100644 --- a/lib/wasix/src/state/env.rs +++ b/lib/wasix/src/state/env.rs @@ -271,6 +271,7 @@ impl WasiEnvInit { ), args: std::sync::Mutex::new(self.state.args.lock().unwrap().clone()), envs: std::sync::Mutex::new(self.state.envs.lock().unwrap().deref().clone()), + signals: std::sync::Mutex::new(self.state.signals.lock().unwrap().deref().clone()), preopen: self.state.preopen.clone(), }, runtime: self.runtime.clone(), diff --git a/lib/wasix/src/state/mod.rs b/lib/wasix/src/state/mod.rs index d5bc4f17e86..3b6e52f24da 100644 --- a/lib/wasix/src/state/mod.rs +++ b/lib/wasix/src/state/mod.rs @@ -34,7 +34,7 @@ use run::*; #[cfg(feature = "enable-serde")] use serde::{Deserialize, Serialize}; use virtual_fs::{FileOpener, FileSystem, FsError, OpenOptions, VirtualFile}; -use wasmer_wasix_types::wasi::{Errno, Fd as WasiFd, Rights, Snapshot0Clockid}; +use wasmer_wasix_types::wasi::{Errno, Fd as WasiFd, Rights, SigAction, Signal, Snapshot0Clockid}; pub use self::{ builder::*, @@ -135,6 +135,7 @@ pub(crate) struct WasiState { pub clock_offset: Mutex>, pub args: Mutex>, pub envs: Mutex>>, + pub signals: Mutex>, // TODO: should not be here, since this requires active work to resolve. // State should only hold active runtime state that can be reproducibly re-created. @@ -257,6 +258,7 @@ impl WasiState { clock_offset: Mutex::new(self.clock_offset.lock().unwrap().clone()), args: Mutex::new(self.args.lock().unwrap().clone()), envs: Mutex::new(self.envs.lock().unwrap().clone()), + signals: Mutex::new(self.signals.lock().unwrap().clone()), preopen: self.preopen.clone(), } } diff --git a/lib/wasix/src/syscalls/mod.rs b/lib/wasix/src/syscalls/mod.rs index 87577dbc3e0..cc697c23805 100644 --- a/lib/wasix/src/syscalls/mod.rs +++ b/lib/wasix/src/syscalls/mod.rs @@ -87,8 +87,8 @@ pub(crate) use self::types::{ wasi::{ Addressfamily, Advice, Clockid, Dircookie, Dirent, Errno, Event, EventFdReadwrite, Eventrwflags, Eventtype, ExitCode, Fd as WasiFd, Fdflags, Fdflagsext, Fdstat, Filesize, - Filestat, Filetype, Fstflags, Linkcount, Longsize, OptionFd, Pid, Prestat, Rights, - Snapshot0Clockid, Sockoption, Sockstatus, Socktype, StackSnapshot, + Filestat, Filetype, Fstflags, Linkcount, Longsize, OptionFd, Pid, Prestat, ProcSpawnFdOp, + Rights, SignalAndAction, Snapshot0Clockid, Sockoption, Sockstatus, Socktype, StackSnapshot, StdioMode as WasiStdioMode, Streamsecurity, Subscription, SubscriptionFsReadwrite, Tid, Timestamp, TlKey, TlUser, TlVal, Tty, Whence, }, @@ -1460,12 +1460,11 @@ pub(crate) fn _prepare_wasi( wasi_env: &mut WasiEnv, args: Option>, envs: Option>, + signals: Option>, ) { // Swap out the arguments with the new ones if let Some(args) = args { - let mut wasi_state = wasi_env.state.fork(); - *wasi_state.args.lock().unwrap() = args; - wasi_env.state = Arc::new(wasi_state); + *wasi_env.state.args.lock().unwrap() = args; } // Update the env vars @@ -1499,6 +1498,14 @@ pub(crate) fn _prepare_wasi( drop(guard) } + + if let Some(signals) = signals { + let mut guard = wasi_env.state.signals.lock().unwrap(); + for signal in signals { + guard.insert(signal.sig, signal.act); + } + drop(guard); + } } pub(crate) fn conv_spawn_err_to_errno(err: &SpawnError) -> Errno { diff --git a/lib/wasix/src/syscalls/wasi/path_open.rs b/lib/wasix/src/syscalls/wasi/path_open.rs index b4b5e98c8a3..92fd1b46512 100644 --- a/lib/wasix/src/syscalls/wasi/path_open.rs +++ b/lib/wasix/src/syscalls/wasi/path_open.rs @@ -64,7 +64,7 @@ pub fn path_open( Span::current().record("path", path_string.as_str()); let out_fd = wasi_try_ok!(path_open_internal( - &mut ctx, + ctx.data(), dirfd, dirflags, &path_string, diff --git a/lib/wasix/src/syscalls/wasix/chdir.rs b/lib/wasix/src/syscalls/wasix/chdir.rs index 3ddfc2efdd4..5168d9efc02 100644 --- a/lib/wasix/src/syscalls/wasix/chdir.rs +++ b/lib/wasix/src/syscalls/wasix/chdir.rs @@ -14,7 +14,7 @@ pub fn chdir( let path = unsafe { get_input_str_ok!(&memory, path, path_len) }; Span::current().record("path", path.as_str()); - wasi_try_ok!(chdir_internal(&mut ctx, &path)); + wasi_try_ok!(chdir_internal(ctx.data(), &path)); let env = ctx.data(); #[cfg(feature = "journal")] @@ -28,9 +28,8 @@ pub fn chdir( Ok(Errno::Success) } -pub fn chdir_internal(ctx: &mut FunctionEnvMut<'_, WasiEnv>, path: &str) -> Result<(), Errno> { - let env = ctx.data(); - let (memory, mut state) = unsafe { env.get_memory_and_wasi_state(ctx, 0) }; +pub fn chdir_internal(env: &WasiEnv, path: &str) -> Result<(), Errno> { + let state = &env.state; // Check if the directory exists if state.fs.root_fs.read_dir(Path::new(path)).is_err() { diff --git a/lib/wasix/src/syscalls/wasix/mod.rs b/lib/wasix/src/syscalls/wasix/mod.rs index 8e92ecd7a1b..a3be8573a61 100644 --- a/lib/wasix/src/syscalls/wasix/mod.rs +++ b/lib/wasix/src/syscalls/wasix/mod.rs @@ -33,8 +33,11 @@ mod proc_id; mod proc_join; mod proc_parent; mod proc_signal; +mod proc_signals_count_get; +mod proc_signals_get; mod proc_snapshot; mod proc_spawn; +mod proc_spawn2; mod resolve; mod sched_yield; mod sock_accept; @@ -109,8 +112,11 @@ pub use proc_id::*; pub use proc_join::*; pub use proc_parent::*; pub use proc_signal::*; +pub use proc_signals_count_get::*; +pub use proc_signals_get::*; pub use proc_snapshot::*; pub use proc_spawn::*; +pub use proc_spawn2::*; pub use resolve::*; pub use sched_yield::*; pub use sock_accept::*; diff --git a/lib/wasix/src/syscalls/wasix/path_open2.rs b/lib/wasix/src/syscalls/wasix/path_open2.rs index f650a741b7e..6305b8bf59c 100644 --- a/lib/wasix/src/syscalls/wasix/path_open2.rs +++ b/lib/wasix/src/syscalls/wasix/path_open2.rs @@ -65,7 +65,7 @@ pub fn path_open2( Span::current().record("path", path_string.as_str()); let out_fd = wasi_try_ok!(path_open_internal( - &mut ctx, + ctx.data(), dirfd, dirflags, &path_string, @@ -111,7 +111,7 @@ pub fn path_open2( } pub(crate) fn path_open_internal( - ctx: &mut FunctionEnvMut<'_, WasiEnv>, + env: &WasiEnv, dirfd: WasiFd, dirflags: LookupFlags, path: &str, @@ -122,9 +122,8 @@ pub(crate) fn path_open_internal( fd_flags: Fdflagsext, with_fd: Option, ) -> Result, WasiError> { - let env = ctx.data(); - let (memory, mut state, mut inodes) = - unsafe { env.get_memory_and_wasi_state_and_inodes(&ctx, 0) }; + let state = env.state.deref(); + let inodes = &state.inodes; let path_arg = std::path::PathBuf::from(&path); let maybe_inode = state.fs.get_inode_at_path( diff --git a/lib/wasix/src/syscalls/wasix/proc_exec2.rs b/lib/wasix/src/syscalls/wasix/proc_exec2.rs index 2bcdc7e0a3d..c44ef1aac7d 100644 --- a/lib/wasix/src/syscalls/wasix/proc_exec2.rs +++ b/lib/wasix/src/syscalls/wasix/proc_exec2.rs @@ -28,7 +28,18 @@ pub fn proc_exec2( envs: WasmPtr, envs_len: M::Offset, ) -> Result<(), WasiError> { - match proc_exec3(ctx, name, name_len, args, args_len, envs, envs_len) { + match proc_exec3( + ctx, + name, + name_len, + args, + args_len, + envs, + envs_len, + Bool::False, + WasmPtr::null(), + 0u32.into(), + ) { Ok(e) => Err(WasiError::Exit(e.into())), Err(e) => Err(e), } diff --git a/lib/wasix/src/syscalls/wasix/proc_exec3.rs b/lib/wasix/src/syscalls/wasix/proc_exec3.rs index 5d5799b6f1a..29044ed708a 100644 --- a/lib/wasix/src/syscalls/wasix/proc_exec3.rs +++ b/lib/wasix/src/syscalls/wasix/proc_exec3.rs @@ -4,6 +4,7 @@ use super::*; use crate::{ os::task::{OwnedTaskStatus, TaskStatus}, syscalls::*, + WasiFs, VIRTUAL_ROOT_FD, }; /// Replaces the current process with a new process @@ -27,6 +28,9 @@ pub fn proc_exec3( args_len: M::Offset, envs: WasmPtr, envs_len: M::Offset, + search_path: Bool, + path: WasmPtr, + path_len: M::Offset, ) -> Result { WasiEnv::process_signals_and_exit(&mut ctx)?; @@ -68,7 +72,7 @@ pub fn proc_exec3( let mut vec = vec![]; for env in envs { - let (key, value) = env.split_once('=').unwrap(); + let (key, value) = wasi_try_ok!(env.split_once('=').ok_or(Errno::Inval)); vec.push((key.to_string(), value.to_string())); } @@ -79,9 +83,29 @@ pub fn proc_exec3( }; // Convert relative paths into absolute paths - if name.starts_with("./") { + if search_path == Bool::True && !name.contains('/') { + let path_str; + + let path = if path.is_null() { + vec!["/usr/local/bin", "/bin", "/usr/bin"] + } else { + path_str = path.read_utf8_string(&memory, path_len).map_err(|err| { + warn!("failed to execve as the path could not be read - {}", err); + WasiError::Exit(Errno::Inval.into()) + })?; + path_str.split(':').collect() + }; + let (_, state, inodes) = + unsafe { ctx.data().get_memory_and_wasi_state_and_inodes(&ctx, 0) }; + match find_executable_in_path(&state.fs, inodes, path.iter().map(AsRef::as_ref), &name) { + FindExecutableResult::Found(p) => name = p, + FindExecutableResult::AccessError => return Ok(Errno::Access), + FindExecutableResult::NotFound => return Ok(Errno::Noexec), + } + } else if name.starts_with("./") { name = ctx.data().state.fs.relative_path_to_absolute(name); } + trace!(name); // Convert the preopen directories @@ -123,7 +147,7 @@ pub fn proc_exec3( std::mem::swap(vfork_env.as_mut(), ctx.data_mut()); let mut wasi_env = *vfork_env; wasi_env.owned_handles.push(vfork.handle.clone()); - _prepare_wasi(&mut wasi_env, Some(args), envs); + _prepare_wasi(&mut wasi_env, Some(args), envs, None); // Recrod the stack offsets before we give up ownership of the wasi_env let stack_lower = wasi_env.layout.stack_lower; @@ -212,7 +236,7 @@ pub fn proc_exec3( else { // Prepare the environment let mut wasi_env = ctx.data().clone(); - _prepare_wasi(&mut wasi_env, Some(args), envs); + _prepare_wasi(&mut wasi_env, Some(args), envs, None); // Get a reference to the runtime let bin_factory = ctx.data().bin_factory.clone(); @@ -242,16 +266,6 @@ pub fn proc_exec3( // If we support deep sleeping then we switch to deep sleep mode let env = ctx.data(); - // Since we clone the env for the subprocess, we need to close - // the file handles we're holding. Otherwise, files will never - // be closed. - InlineWaker::block_on( - unsafe { env.get_memory_and_wasi_state(&ctx, 0) } - .1 - .fs - .close_all(), - ); - let thread = env.thread.clone(); // The poller will wait for the process to actually finish @@ -283,3 +297,32 @@ pub fn proc_exec3( } } } + +pub(crate) enum FindExecutableResult { + Found(String), + AccessError, + NotFound, +} + +pub(crate) fn find_executable_in_path<'a>( + fs: &WasiFs, + inodes: &WasiInodes, + path: impl IntoIterator, + file_name: &str, +) -> FindExecutableResult { + let mut encountered_eaccess = false; + for p in path { + let full_path = format!("{}/{}", p.trim_end_matches('/'), file_name); + match fs.get_inode_at_path(inodes, VIRTUAL_ROOT_FD, &full_path, true) { + Ok(_) => return FindExecutableResult::Found(full_path), + Err(Errno::Access) => encountered_eaccess = true, + Err(_) => (), + } + } + + if encountered_eaccess { + FindExecutableResult::AccessError + } else { + FindExecutableResult::NotFound + } +} diff --git a/lib/wasix/src/syscalls/wasix/proc_signals_count_get.rs b/lib/wasix/src/syscalls/wasix/proc_signals_count_get.rs new file mode 100644 index 00000000000..905dbe2a2fd --- /dev/null +++ b/lib/wasix/src/syscalls/wasix/proc_signals_count_get.rs @@ -0,0 +1,32 @@ +use super::*; +use crate::syscalls::*; + +/// ### `proc_signals_count_get()` +/// Gets the number of signals with overridden handlers. +/// +/// Outputs: +/// - `size_t *signal_count` +/// The number of signals. +#[instrument(level = "trace", skip_all, fields(signal_count = field::Empty), ret)] +pub fn proc_signals_count_get( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + signal_count: WasmPtr, +) -> Result { + let env = ctx.data(); + let (memory, mut state) = unsafe { env.get_memory_and_wasi_state(&ctx, 0) }; + + let signal_count = signal_count.deref(&memory); + + let count_val: M::Offset = wasi_try_ok!(state + .signals + .lock() + .unwrap() + .len() + .try_into() + .map_err(|_| Errno::Overflow)); + wasi_try_mem_ok!(signal_count.write(count_val)); + + Span::current().record("signal_count", u64::try_from(count_val).unwrap()); + + Ok(Errno::Success) +} diff --git a/lib/wasix/src/syscalls/wasix/proc_signals_get.rs b/lib/wasix/src/syscalls/wasix/proc_signals_get.rs new file mode 100644 index 00000000000..065947ce391 --- /dev/null +++ b/lib/wasix/src/syscalls/wasix/proc_signals_get.rs @@ -0,0 +1,30 @@ +use super::*; +use crate::syscalls::*; + +/// ### `proc_signals_get()` +/// Gets signals with overridden handlers. +/// +/// Inputs: +/// - `__wasi_signal_and_action_t *buf` +/// A pointer to a buffer to write the signal data. +#[instrument(level = "trace", skip_all, ret)] +pub fn proc_signals_get( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + buf: WasmPtr, +) -> Result { + let env = ctx.data(); + let (memory, mut state) = unsafe { env.get_memory_and_wasi_state(&ctx, 0) }; + + let signals = state.signals.lock().unwrap(); + + let buf = wasi_try_mem_ok!(buf.slice(&memory, wasi_try_ok!(to_offset::(signals.len())))); + + for (idx, (sig, act)) in signals.iter().enumerate() { + wasi_try_mem_ok!(buf.index(idx as u64).write(SignalAndAction { + sig: *sig, + act: *act + })); + } + + Ok(Errno::Success) +} diff --git a/lib/wasix/src/syscalls/wasix/proc_spawn2.rs b/lib/wasix/src/syscalls/wasix/proc_spawn2.rs new file mode 100644 index 00000000000..28299ef18a3 --- /dev/null +++ b/lib/wasix/src/syscalls/wasix/proc_spawn2.rs @@ -0,0 +1,273 @@ +use wasmer::FromToNativeWasmType; +use wasmer_wasix_types::wasi::ProcSpawnFdOpName; + +use super::*; +use crate::{ + os::task::{OwnedTaskStatus, TaskStatus}, + syscalls::*, + WasiFs, VIRTUAL_ROOT_FD, +}; + +/// Replaces the current process with a new process +/// +/// ## Parameters +/// +/// * `name` - Name of the process to be spawned +/// * `args` - List of the arguments to pass the process +/// (entries are separated by line feeds) +/// * `envs` - List of the environment variables to pass process +/// +/// ## Return +/// +/// If the execution fails, returns an error code. Does not return otherwise. +#[instrument( + level = "trace", + skip_all, + fields(name = field::Empty, full_path = field::Empty, pid = field::Empty, tid = field::Empty, %args_len), + ret)] +pub fn proc_spawn2( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + name: WasmPtr, + name_len: M::Offset, + args: WasmPtr, + args_len: M::Offset, + envs: WasmPtr, + envs_len: M::Offset, + fd_ops: WasmPtr, M>, + fd_ops_len: M::Offset, + signal_actions: WasmPtr, + signal_actions_len: M::Offset, + search_path: Bool, + path: WasmPtr, + path_len: M::Offset, + ret: WasmPtr, +) -> Result { + WasiEnv::process_signals_and_exit(&mut ctx)?; + + let env = ctx.data(); + let memory = unsafe { ctx.data().memory_view(&ctx) }; + let mut name = unsafe { get_input_str_ok!(&memory, name, name_len) }; + Span::current().record("name", name.as_str()); + let args = unsafe { get_input_str_ok!(&memory, args, args_len) }; + let args: Vec<_> = args + .split(&['\n', '\r']) + .map(|a| a.to_string()) + .filter(|a| !a.is_empty()) + .collect(); + + let envs = if !envs.is_null() { + let envs = unsafe { get_input_str_ok!(&memory, envs, envs_len) }; + + let envs = envs + .split(&['\n', '\r']) + .map(|a| a.to_string()) + .filter(|a| !a.is_empty()); + + let mut vec = vec![]; + for env in envs { + let (key, value) = wasi_try_ok!(env.split_once('=').ok_or(Errno::Inval)); + vec.push((key.to_string(), value.to_string())); + } + + Some(vec) + } else { + None + }; + + let signals = if !signal_actions.is_null() { + let signal_actions = wasi_try_mem_ok!(signal_actions.slice(&memory, signal_actions_len)); + let mut vec = Vec::with_capacity(signal_actions.len() as usize); + for s in wasi_try_mem_ok!(signal_actions.access()).iter() { + vec.push(s.clone()); + } + Some(vec) + } else { + None + }; + + let fd_ops = if !fd_ops.is_null() { + let fd_ops = wasi_try_mem_ok!(fd_ops.slice(&memory, fd_ops_len)); + let mut vec = Vec::with_capacity(fd_ops.len() as usize); + for s in wasi_try_mem_ok!(fd_ops.access()).iter() { + vec.push(s.clone()); + } + vec + } else { + vec![] + }; + + // Convert relative paths into absolute paths + if search_path == Bool::True && !name.contains('/') { + let path_str; + + let path = if path.is_null() { + vec!["/usr/local/bin", "/bin", "/usr/bin"] + } else { + path_str = unsafe { get_input_str_ok!(&memory, path, path_len) }; + path_str.split(':').collect() + }; + let (_, state, inodes) = + unsafe { ctx.data().get_memory_and_wasi_state_and_inodes(&ctx, 0) }; + match find_executable_in_path(&state.fs, inodes, path.iter().map(AsRef::as_ref), &name) { + FindExecutableResult::Found(p) => name = p, + FindExecutableResult::AccessError => return Ok(Errno::Access), + FindExecutableResult::NotFound => return Ok(Errno::Noexec), + } + } else if name.starts_with("./") { + name = ctx.data().state.fs.relative_path_to_absolute(name); + } + + Span::current().record("full_path", &name); + + // Fork the environment which will copy all the open file handlers + // and associate a new context but otherwise shares things like the + // file system interface. The handle to the forked process is stored + // in the parent process context + let (mut child_env, mut child_handle) = match ctx.data().fork() { + Ok(p) => p, + Err(err) => { + debug!("could not fork process: {err}"); + // TODO: evaluate the appropriate error code, document it in the spec. + return Ok(Errno::Perm); + } + }; + + { + let mut inner = ctx.data().process.lock(); + inner.children.push(child_env.process.clone()); + } + + // Setup some properties in the child environment + let pid = child_env.pid(); + let tid = child_env.tid(); + wasi_try_mem_ok!(ret.write(&memory, pid.raw())); + Span::current() + .record("pid", pid.raw()) + .record("tid", tid.raw()); + + _prepare_wasi(&mut child_env, Some(args), envs, signals); + + for fd_op in fd_ops { + wasi_try_ok!(apply_fd_op(&mut child_env, &memory, &fd_op)); + } + + // Create the process and drop the context + let bin_factory = Box::new(child_env.bin_factory.clone()); + + let mut builder = Some(child_env); + + let process = match bin_factory.try_built_in(name.clone(), Some(&ctx), &mut builder) { + Ok(a) => Ok(a), + Err(err) => { + if !err.is_not_found() { + error!("builtin failed - {}", err); + } + + let env = builder.take().unwrap(); + + // Spawn a new process with this current execution environment + InlineWaker::block_on(bin_factory.spawn(name, env)) + } + }; + + match process { + Ok(_) => { + ctx.data_mut().owned_handles.push(child_handle); + trace!(child_pid = %pid, "spawned sub-process"); + Ok(Errno::Success) + } + Err(err) => { + let err_exit_code = conv_spawn_err_to_exit_code(&err); + + debug!(child_pid = %pid, "process failed with (err={})", err_exit_code); + + Ok(Errno::Noexec) + } + } +} + +fn apply_fd_op( + env: &mut WasiEnv, + memory: &MemoryView, + op: &ProcSpawnFdOp, +) -> Result<(), Errno> { + match op.cmd { + ProcSpawnFdOpName::Close => env.state.fs.close_fd(op.fd), + ProcSpawnFdOpName::Dup2 => { + if env.state.fs.get_fd(op.fd).is_ok() { + env.state.fs.close_fd(op.fd)?; + } + + let mut fd_map = env.state.fs.fd_map.write().unwrap(); + let fd_entry = fd_map.get(op.src_fd).ok_or(Errno::Badf)?; + + let new_fd_entry = Fd { + // TODO: verify this is correct + inner: FdInner { + offset: fd_entry.inner.offset.clone(), + rights: fd_entry.inner.rights_inheriting, + fd_flags: { + let mut f = fd_entry.inner.fd_flags; + f.set(Fdflagsext::CLOEXEC, false); + f + }, + ..fd_entry.inner + }, + inode: fd_entry.inode.clone(), + ..*fd_entry + }; + + // Exclusive insert because we expect `to` to be empty after closing it above + fd_map.insert(true, op.fd, new_fd_entry); + Ok(()) + } + ProcSpawnFdOpName::Open => { + let mut name = unsafe { + WasmPtr::::new(op.name) + .read_utf8_string(memory, op.name_len) + .map_err(mem_error_to_wasi)? + }; + name = env.state.fs.relative_path_to_absolute(name.to_owned()); + match path_open_internal( + env, + VIRTUAL_ROOT_FD, + op.dirflags, + &name, + op.oflags, + op.fs_rights_base, + op.fs_rights_inheriting, + op.fdflags, + op.fdflagsext, + Some(op.fd), + ) { + Err(e) => { + tracing::warn!("Failed to open file for posix_spawn: {:?}", e); + Err(Errno::Io) + } + Ok(Err(e)) => Err(e), + Ok(Ok(_)) => Ok(()), + } + } + ProcSpawnFdOpName::Chdir => { + let mut path = unsafe { + WasmPtr::::new(op.name) + .read_utf8_string(memory, op.name_len) + .map_err(mem_error_to_wasi)? + }; + path = env.state.fs.relative_path_to_absolute(path.to_owned()); + chdir_internal(env, &path) + } + ProcSpawnFdOpName::Fchdir => { + let fd = env.state.fs.get_fd(op.fd)?; + let inode_kind = fd.inode.read(); + match inode_kind.deref() { + Kind::Dir { path, .. } => { + let path = path.to_str().ok_or(Errno::Notsup)?; + env.state.fs.set_current_dir(path); + Ok(()) + } + _ => Err(Errno::Notdir), + } + } + } +} diff --git a/tests/wasix/_posix_spawn/main.c b/tests/wasix/_posix_spawn/main.c new file mode 100644 index 00000000000..64928e62bb6 --- /dev/null +++ b/tests/wasix/_posix_spawn/main.c @@ -0,0 +1,263 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int run_tests() +{ + posix_spawnattr_t attr; + posix_spawn_file_actions_t fdops; + sigset_t sigdefault; + pid_t pid; + char *argv[] = {"main.out", "subprocess", NULL}; + char *envp[] = {"ABCD=1234", NULL}; + + // Ignore both SIGTERM and SIGHUP, then set SIGHUP up to be reset to default + if (signal(SIGTERM, SIG_IGN) == SIG_ERR) + { + perror("signal"); + return 1; + } + if (signal(SIGHUP, SIG_IGN) == SIG_ERR) + { + perror("signal"); + return 1; + } + + // Raise the signal once, just to make sure it's _actually_ being ignored + raise(SIGTERM); + + if (posix_spawnattr_init(&attr) != 0) + { + perror("posix_spawnattr_init"); + return 1; + } + sigemptyset(&sigdefault); + sigaddset(&sigdefault, SIGHUP); + if (posix_spawnattr_setsigdefault(&attr, &sigdefault) != 0) + { + perror("posix_spawnattr_setsigdefault"); + return 1; + } + if (posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGDEF) != 0) + { + perror("posix_spawnattr_setflags"); + return 1; + } + + // Open zzz on 11 + int fd = open("./output.zzz", O_WRONLY | O_CREAT, 0); + if (fd < 0) + { + perror("open"); + return 1; + } + if (dup2(fd, 11) != 0) + { + perror("dup2"); + return 1; + } + if (dup2(fd, 13) != 0) + { + perror("dup2"); + return 1; + } + if (fcntl(13, F_SETFD, FD_CLOEXEC) == -1) + { + perror("fcntl"); + return 1; + } + if (close(fd) != 0) + { + perror("close"); + return 1; + } + + posix_spawn_file_actions_init(&fdops); + // Open yyy on 10 + posix_spawn_file_actions_addopen(&fdops, 10, "./output.yyy", O_WRONLY | O_CREAT, 0); + // Renumber zzz to 12 + posix_spawn_file_actions_adddup2(&fdops, 11, 12); + // Close 11 + posix_spawn_file_actions_addclose(&fdops, 11); + // After all of this, the subprocess should have 10 and 12, but not 11 + + if (posix_spawn(&pid, "./main-not-asyncified.wasm", &fdops, &attr, argv, envp) != 0) + { + perror("posix_spawn"); + return 1; + } + + if (posix_spawn_file_actions_destroy(&fdops) != 0) + { + perror("posix_spawn_file_actions_destroy"); + return 1; + } + if (posix_spawnattr_destroy(&attr) != 0) + { + perror("posix_spawnattr_destroy"); + return 1; + } + + int status; + if (waitpid(pid, &status, 0) == -1) + { + perror("waitpid"); + return 1; + } + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + { + printf("Child process failed with: %d\n", WEXITSTATUS(status)); + return 1; + } + + char buffer[5] = {0}; + fd = open("./output.yyy", O_RDONLY | O_CREAT, 0); + if (fd < 0) + { + perror("open"); + return 1; + } + int r = read(fd, buffer, 5); + if (r <= 0) + { + perror("read from yyy"); + } + if (strcmp(buffer, "yyy")) + { + printf("Expected yyy, got: %s\n", buffer); + return 1; + } + if (close(fd) != 0) + { + perror("close"); + return 1; + } + + fd = open("./output.zzz", O_RDONLY | O_CREAT, 0); + if (fd < 0) + { + perror("open"); + return 1; + } + r = read(fd, buffer, 5); + if (r <= 0) + { + perror("read from yyy"); + } + if (strcmp(buffer, "zzz")) + { + printf("Expected zzz, got: %s\n", buffer); + return 1; + } + if (close(fd) != 0) + { + perror("close"); + return 1; + } + + return 0; +} + +// Since we don't pipe stderr from the child process, this function writes +// output to a file which can (hopefully!) be inspected +void write_subprocess_error(const char *msg) +{ + FILE *outf = fopen("./output.child", "w"); + if (!outf) + { + exit(EXIT_FAILURE); + } + fprintf(outf, "%s: %s\n", msg, strerror(errno)); + fclose(outf); + exit(EXIT_FAILURE); +} + +int subprocess(int argc, char **argv) +{ + if (argc != 2 || strcmp(argv[0], "main.out") || strcmp(argv[1], "subprocess")) + { + write_subprocess_error("Got bad CLI args"); + } + + const char *env = getenv("ABCD"); + if (strcmp(env, "1234")) + { + char buf[128]; + sprintf(buf, "env var not set correctly, value is: %s", env); + write_subprocess_error(buf); + } + + struct sigaction act = {0}; + if (sigaction(SIGHUP, NULL, &act) != 0) + { + write_subprocess_error("sigaction"); + } + if (act.sa_handler != SIG_DFL) + { + write_subprocess_error("expected SIGHUP to be set to SIG_DFL"); + } + + if (sigaction(SIGTERM, NULL, &act) != 0) + { + write_subprocess_error("sigaction"); + } + if (act.sa_handler != SIG_IGN) + { + write_subprocess_error("expected SIGTERM to be set to SIG_IGN"); + } + // and raise it once, just in case! + raise(SIGTERM); + + int flags = fcntl(11, F_GETFD); + if (flags != -1 || errno != EBADF) + { + write_subprocess_error("Expected EBADF for fd 11"); + } + errno = 0; + + // 13 should be closed due to FD_CLOEXEC + flags = fcntl(13, F_GETFD); + if (flags != -1 || errno != EBADF) + { + write_subprocess_error("Expected EBADF for fd 11"); + } + errno = 0; + + if (write(10, "yyy", 3) <= 0) + { + write_subprocess_error("write to yyy failed"); + } + if (close(10) < 0) + { + write_subprocess_error("close(10) failed"); + } + + if (write(12, "zzz", 3) <= 0) + { + write_subprocess_error("write to zzz failed"); + } + if (close(12) < 0) + { + write_subprocess_error("close(12) failed"); + } + + return 0; +} + +int main(int argc, char **argv) +{ + if (argc >= 2) + { + return subprocess(argc, argv); + } + + return run_tests(); +} diff --git a/tests/wasix/_posix_spawn/run.sh b/tests/wasix/_posix_spawn/run.sh new file mode 100755 index 00000000000..37fdfe41f8e --- /dev/null +++ b/tests/wasix/_posix_spawn/run.sh @@ -0,0 +1,6 @@ +set -e + +rm -f output.yyy output.zzz + +# Run the not-asyncified variant to make sure posix_spawn doesn't require asyncify +$WASMER -q run main-not-asyncified.wasm --dir . diff --git a/tests/wasix/cloexec/main.c b/tests/wasix/cloexec/main.c index adc4c41b2a0..cacaa0a3586 100644 --- a/tests/wasix/cloexec/main.c +++ b/tests/wasix/cloexec/main.c @@ -134,14 +134,14 @@ int exec_tests() // Since we don't pipe stderr from the child process, this function writes // output to a file which can (hopefully!) be inspected -void write_subprocess_output(const char *msg) +void write_subprocess_error(const char *msg) { FILE *outf = fopen("./output.child", "w"); if (!outf) { exit(EXIT_FAILURE); } - fprintf(outf, "%s: %d\n", msg, errno); + fprintf(outf, "%s: %s\n", msg, strerror(errno)); fclose(outf); } @@ -150,20 +150,20 @@ int exec_subprocess() int flags = fcntl(6, F_GETFD); if (flags != -1 || errno != EBADF) { - write_subprocess_output("Expected EBADF for fd 6"); + write_subprocess_error("Expected EBADF for fd 6"); return 2; } flags = fcntl(7, F_GETFD); if (flags == -1) { - write_subprocess_output("Error from fcntl in subprocess"); + write_subprocess_error("Error from fcntl in subprocess"); return 3; } if ((flags & FD_CLOEXEC) != 0) { - write_subprocess_output("Expected FD_CLOEXEC to be 0 for fd 7"); + write_subprocess_error("Expected FD_CLOEXEC to be 0 for fd 7"); return 4; } diff --git a/tests/wasix/closing-pre-opened-dirs/main.c b/tests/wasix/closing-pre-opened-dirs/main.c index 89f084ecd89..aa3f6b5f840 100644 --- a/tests/wasix/closing-pre-opened-dirs/main.c +++ b/tests/wasix/closing-pre-opened-dirs/main.c @@ -11,9 +11,9 @@ typedef unsigned char bool; int main() { - const size_t expected_count = 6; - const char *expected_entries[] = {".", "..", "main.c", "main.wasm", "output", "run.sh"}; - bool entries_observed[6] = {false}; + const size_t expected_count = 7; + const char *expected_entries[] = {".", "..", "main.c", "main.wasm", "main-not-asyncified.wasm", "output", "run.sh"}; + bool entries_observed[7] = {false}; for (int fd = 3; fd <= 5; fd++) { diff --git a/tests/wasix/test.sh b/tests/wasix/test.sh index 94487fbe3cb..b1bf550eaed 100755 --- a/tests/wasix/test.sh +++ b/tests/wasix/test.sh @@ -76,8 +76,8 @@ while read dir; do printf "Testing $dir..." cmd="cd $dir; \ - $CC $CFLAGS $LDFLAGS -o main.wasm main.c; \ - wasm-opt --asyncify main.wasm -o main.wasm; \ + $CC $CFLAGS $LDFLAGS -o main-not-asyncified.wasm main.c; \ + wasm-opt --asyncify main-not-asyncified.wasm -o main.wasm; \ ./run.sh" if bash -c "$cmd"; then