diff --git a/lib/wasi/src/state/builder.rs b/lib/wasi/src/state/builder.rs index 9d8ffb0ee79..a7125f28382 100644 --- a/lib/wasi/src/state/builder.rs +++ b/lib/wasi/src/state/builder.rs @@ -105,7 +105,7 @@ pub enum WasiStateCreationError { #[error("wasi filesystem setup error: `{0}`")] WasiFsSetupError(String), #[error(transparent)] - FileSystemError(FsError), + FileSystemError(#[from] FsError), #[error("wasi inherit error: `{0}`")] WasiInheritError(String), #[error("wasi include package: `{0}`")] @@ -788,7 +788,7 @@ impl WasiEnvBuilder { let start = instance.exports.get_function("_start")?; env.data(store).thread.set_status_running(); - let res = crate::run_wasi_func_start(start, store); + let mut res = crate::run_wasi_func_start(start, store); tracing::trace!( "wasi[{}:{}]::main exit (code = {:?})", @@ -800,7 +800,12 @@ impl WasiEnvBuilder { let exit_code = match &res { Ok(_) => Errno::Success.into(), Err(err) => match err.as_exit_code() { - Some(code) if code.is_success() => Errno::Success.into(), + Some(code) if code.is_success() => { + // This is actually not an error, so we need to fix up the + // result + res = Ok(()); + Errno::Success.into() + } Some(other) => other, None => Errno::Noexec.into(), }, diff --git a/lib/wasi/src/state/env.rs b/lib/wasi/src/state/env.rs index 5014e648091..b09f647fad3 100644 --- a/lib/wasi/src/state/env.rs +++ b/lib/wasi/src/state/env.rs @@ -9,7 +9,7 @@ use std::{ use derivative::Derivative; use rand::Rng; use tracing::{trace, warn}; -use virtual_fs::{FileSystem, FsError, VirtualFile}; +use virtual_fs::{AsyncWriteExt, FileSystem, FsError, VirtualFile}; use virtual_net::DynVirtualNetworking; use wasmer::{ AsStoreMut, AsStoreRef, FunctionEnvMut, Global, Instance, Memory, MemoryType, MemoryView, @@ -855,52 +855,94 @@ impl WasiEnv { /// Make all the commands in a [`BinaryPackage`] available to the WASI /// instance. + /// + /// The [`BinaryPackageCommand::atom()`] will be saved to `/bin/command`. + /// + /// This will also merge the command's filesystem + /// ([`BinaryPackage::webc_fs`]) into the current filesystem. pub fn use_package(&self, pkg: &BinaryPackage) -> Result<(), WasiStateCreationError> { - if let WasiFsRoot::Sandbox(root_fs) = &self.state.fs.root_fs { - // We first need to copy any files in the package over to the temporary file system - root_fs.union(&pkg.webc_fs); + // PERF: We should avoid all these copies in the WasiFsRoot::Backing case. + + let root_fs = &self.state.fs.root_fs; + // We first need to copy any files in the package over to the + // temporary file system + match root_fs { + WasiFsRoot::Sandbox(root_fs) => { + root_fs.union(&pkg.webc_fs); + } + WasiFsRoot::Backing(_fs) => { + // TODO: Manually copy each file across one-by-one + } + } - // Next, make sure all commands will be available + // Next, make sure all commands will be available - if !pkg.commands.is_empty() { - let _ = root_fs.create_dir(Path::new("/bin")); + if !pkg.commands.is_empty() { + let _ = root_fs.create_dir(Path::new("/bin")); - for command in &pkg.commands { - let path = format!("/bin/{}", command.name()); - let path = Path::new(path.as_str()); - - // FIXME(Michael-F-Bryan): This is pretty sketchy. - // We should be using some sort of reference-counted - // pointer to some bytes that are either on the heap - // or from a memory-mapped file. However, that's not - // possible here because things like memfs and - // WasiEnv are expecting a Cow<'static, [u8]>. It's - // too hard to refactor those at the moment, and we - // were pulling the same trick before by storing an - // "ownership" object in the BinaryPackageCommand, - // so as long as packages aren't removed from the - // module cache it should be fine. - // See https://github.com/wasmerio/wasmer/issues/3875 - let atom: &'static [u8] = unsafe { std::mem::transmute(command.atom()) }; - - if let Err(err) = root_fs - .new_open_options_ext() - .insert_ro_file(path, atom.into()) - { - tracing::debug!( - "failed to add package [{}] command [{}] - {}", - pkg.package_name, - command.name(), - err - ); - continue; - } + for command in &pkg.commands { + let path = format!("/bin/{}", command.name()); + let path = Path::new(path.as_str()); - let mut package = pkg.clone(); - package.entrypoint_cmd = Some(command.name().to_string()); - self.bin_factory - .set_binary(path.as_os_str().to_string_lossy().as_ref(), package); + match root_fs { + WasiFsRoot::Sandbox(root_fs) => { + // As a short-cut, when we are using a TmpFileSystem + // we can (unsafely) add the file to the filesystem + // without any copying. + + // FIXME(Michael-F-Bryan): This is pretty sketchy. + // We should be using some sort of reference-counted + // pointer to some bytes that are either on the heap + // or from a memory-mapped file. However, that's not + // possible here because things like memfs and + // WasiEnv are expecting a Cow<'static, [u8]>. It's + // too hard to refactor those at the moment, and we + // were pulling the same trick before by storing an + // "ownership" object in the BinaryPackageCommand, + // so as long as packages aren't removed from the + // module cache it should be fine. + // See https://github.com/wasmerio/wasmer/issues/3875 + let atom: &'static [u8] = unsafe { std::mem::transmute(command.atom()) }; + + if let Err(err) = root_fs + .new_open_options_ext() + .insert_ro_file(path, atom.into()) + { + tracing::debug!( + "failed to add package [{}] command [{}] - {}", + pkg.package_name, + command.name(), + err + ); + continue; + } + } + WasiFsRoot::Backing(fs) => { + // Looks like we need to make the copy + let mut f = fs.new_open_options().create(true).write(true).open(path)?; + self.tasks() + .block_on(f.write_all(command.atom())) + .map_err(|e| { + WasiStateCreationError::WasiIncludePackageError(format!( + "Unable to save \"{}\" to \"{}\": {e}", + command.name(), + path.display() + )) + })?; + } } + + let mut package = pkg.clone(); + package.entrypoint_cmd = Some(command.name().to_string()); + self.bin_factory + .set_binary(path.as_os_str().to_string_lossy().as_ref(), package); + + tracing::debug!( + package=%pkg.package_name, + command_name=command.name(), + path=%path.display(), + "Injected a command into the filesystem", + ); } } diff --git a/tests/integration/cli/tests/run_unstable.rs b/tests/integration/cli/tests/run_unstable.rs index d886710c8ed..a45e87bf8f9 100644 --- a/tests/integration/cli/tests/run_unstable.rs +++ b/tests/integration/cli/tests/run_unstable.rs @@ -21,8 +21,8 @@ const HTTP_GET_TIMEOUT: Duration = Duration::from_secs(5); static RUST_LOG: Lazy = Lazy::new(|| { [ "info", - "wasmer_wasi::resolve=debug", - "wasmer_wasi::runners=debug", + "wasmer_wasix::resolve=debug", + "wasmer_wasix::runners=debug", "virtual_fs::trace_fs=trace", ] .join(",") @@ -375,7 +375,11 @@ mod remote_webc { .arg("ls /bin") .assert(); - assert.success().stdout(contains("Hello, World!")); + let some_expected_binaries = [ + "arch", "base32", "base64", "baseenc", "basename", "bash", "cat", + ] + .join("\n"); + assert.success().stdout(contains(some_expected_binaries)); } }