diff --git a/ci/build-wasi-preview1-component-adapter.sh b/ci/build-wasi-preview1-component-adapter.sh index ba1380d3bcd8..259bb697589c 100755 --- a/ci/build-wasi-preview1-component-adapter.sh +++ b/ci/build-wasi-preview1-component-adapter.sh @@ -26,3 +26,9 @@ $build_adapter --release $verify $release wasm-tools metadata add --name "wasi_preview1_component_adapter.reactor.adapter:${VERSION}" $release \ -o target/wasm32-unknown-unknown/release/wasi_snapshot_preview1.reactor.wasm + +# Release build, proxy +$build_adapter --release --no-default-features --features proxy +$verify $release +wasm-tools metadata add --name "wasi_preview1_component_adapter.proxy.adapter:${VERSION}" $release \ + -o target/wasm32-unknown-unknown/release/wasi_snapshot_preview1.proxy.wasm diff --git a/crates/test-programs/artifacts/build.rs b/crates/test-programs/artifacts/build.rs index eb446fd0ba0d..b97422d3b953 100644 --- a/crates/test-programs/artifacts/build.rs +++ b/crates/test-programs/artifacts/build.rs @@ -19,6 +19,11 @@ fn build_and_generate_tests() { "command", &["--no-default-features", "--features=command"], ); + let proxy_adapter = build_adapter( + &out_dir, + "proxy", + &["--no-default-features", "--features=proxy"], + ); println!("cargo:rerun-if-changed=../src"); @@ -62,6 +67,7 @@ fn build_and_generate_tests() { let adapter = match target.as_str() { "reactor" => &reactor_adapter, + s if s.starts_with("api_proxy") => &proxy_adapter, _ => &command_adapter, }; let path = compile_component(&wasm, adapter); diff --git a/crates/wasi-http/src/proxy.rs b/crates/wasi-http/src/proxy.rs index 155a55fd11fd..5c6557678864 100644 --- a/crates/wasi-http/src/proxy.rs +++ b/crates/wasi-http/src/proxy.rs @@ -25,10 +25,24 @@ pub fn add_to_linker(l: &mut wasmtime::component::Linker) -> anyhow::Resul where T: WasiHttpView + preview2::WasiView + bindings::http::types::Host, { - // TODO: this shouldn't be required, but the adapter unconditionally pulls in all of these - // dependencies. - preview2::command::add_to_linker(l)?; + preview2::bindings::clocks::wall_clock::add_to_linker(l, |t| t)?; + preview2::bindings::clocks::monotonic_clock::add_to_linker(l, |t| t)?; + preview2::bindings::io::poll::add_to_linker(l, |t| t)?; + preview2::bindings::io::error::add_to_linker(l, |t| t)?; + preview2::bindings::io::streams::add_to_linker(l, |t| t)?; + preview2::bindings::cli::stdin::add_to_linker(l, |t| t)?; + preview2::bindings::cli::stdout::add_to_linker(l, |t| t)?; + preview2::bindings::cli::stderr::add_to_linker(l, |t| t)?; + preview2::bindings::random::random::add_to_linker(l, |t| t)?; + add_only_http_to_linker(l) +} + +#[doc(hidden)] +pub fn add_only_http_to_linker(l: &mut wasmtime::component::Linker) -> anyhow::Result<()> +where + T: WasiHttpView + preview2::WasiView + bindings::http::types::Host, +{ bindings::http::outgoing_handler::add_to_linker(l, |t| t)?; bindings::http::types::add_to_linker(l, |t| t)?; diff --git a/crates/wasi-http/tests/all/async_.rs b/crates/wasi-http/tests/all/async_.rs index e9d735279290..19ce8c1ae514 100644 --- a/crates/wasi-http/tests/all/async_.rs +++ b/crates/wasi-http/tests/all/async_.rs @@ -13,7 +13,8 @@ async fn run(path: &str, server: &Server) -> Result<()> { let component = Component::from_file(&engine, path)?; let mut store = store(&engine, server); let mut linker = Linker::new(&engine); - wasmtime_wasi_http::proxy::add_to_linker(&mut linker)?; + wasmtime_wasi::preview2::command::add_to_linker(&mut linker)?; + wasmtime_wasi_http::proxy::add_only_http_to_linker(&mut linker)?; let (command, _instance) = Command::instantiate_async(&mut store, &component, &linker).await?; let result = command.wasi_cli_run().call_run(&mut store).await?; result.map_err(|()| anyhow::anyhow!("run returned an error")) diff --git a/crates/wasi-preview1-component-adapter/Cargo.toml b/crates/wasi-preview1-component-adapter/Cargo.toml index a43df3097721..cae715a7d074 100644 --- a/crates/wasi-preview1-component-adapter/Cargo.toml +++ b/crates/wasi-preview1-component-adapter/Cargo.toml @@ -26,3 +26,4 @@ name = "wasi_snapshot_preview1" default = ["reactor"] reactor = [] command = [] +proxy = [] diff --git a/crates/wasi-preview1-component-adapter/src/descriptors.rs b/crates/wasi-preview1-component-adapter/src/descriptors.rs index c3c01c7a766b..0c9cab019f18 100644 --- a/crates/wasi-preview1-component-adapter/src/descriptors.rs +++ b/crates/wasi-preview1-component-adapter/src/descriptors.rs @@ -1,13 +1,15 @@ -use crate::bindings::wasi::cli::{ - stderr, stdin, stdout, terminal_stderr, terminal_stdin, terminal_stdout, -}; -use crate::bindings::wasi::filesystem::types as filesystem; +use crate::bindings::wasi::cli::{stderr, stdin, stdout}; use crate::bindings::wasi::io::streams::{InputStream, OutputStream}; -use crate::{BlockingMode, BumpArena, File, ImportAlloc, TrappingUnwrap, WasmStr}; +use crate::{BlockingMode, BumpArena, ImportAlloc, TrappingUnwrap, WasmStr}; use core::cell::{Cell, OnceCell, UnsafeCell}; use core::mem::MaybeUninit; use wasi::{Errno, Fd}; +#[cfg(not(feature = "proxy"))] +use crate::bindings::wasi::filesystem::types as filesystem; +#[cfg(not(feature = "proxy"))] +use crate::File; + pub const MAX_DESCRIPTORS: usize = 128; #[repr(C)] @@ -43,12 +45,14 @@ impl Streams { let input = match &self.type_ { // For directories, preview 1 behavior was to return ERRNO_BADF on attempts to read // or write. + #[cfg(not(feature = "proxy"))] StreamType::File(File { descriptor_type: filesystem::DescriptorType::Directory, .. }) => return Err(wasi::ERRNO_BADF), // For files, we may have adjusted the position for seeking, so // create a new stream. + #[cfg(not(feature = "proxy"))] StreamType::File(file) => { let input = file.fd.read_via_stream(file.position.get())?; input @@ -69,12 +73,14 @@ impl Streams { let output = match &self.type_ { // For directories, preview 1 behavior was to return ERRNO_BADF on attempts to read // or write. + #[cfg(not(feature = "proxy"))] StreamType::File(File { descriptor_type: filesystem::DescriptorType::Directory, .. }) => return Err(wasi::ERRNO_BADF), // For files, we may have adjusted the position for seeking, so // create a new stream. + #[cfg(not(feature = "proxy"))] StreamType::File(file) => { let output = if file.append { file.fd.append_via_stream()? @@ -97,6 +103,7 @@ pub enum StreamType { Stdio(Stdio), /// Streaming data with a file. + #[cfg(not(feature = "proxy"))] File(File), } @@ -108,11 +115,17 @@ pub enum Stdio { impl Stdio { pub fn filetype(&self) -> wasi::Filetype { - let is_terminal = match self { - Stdio::Stdin => terminal_stdin::get_terminal_stdin().is_some(), - Stdio::Stdout => terminal_stdout::get_terminal_stdout().is_some(), - Stdio::Stderr => terminal_stderr::get_terminal_stderr().is_some(), + #[cfg(not(feature = "proxy"))] + let is_terminal = { + use crate::bindings::wasi::cli; + match self { + Stdio::Stdin => cli::terminal_stdin::get_terminal_stdin().is_some(), + Stdio::Stdout => cli::terminal_stdout::get_terminal_stdout().is_some(), + Stdio::Stderr => cli::terminal_stderr::get_terminal_stderr().is_some(), + } }; + #[cfg(feature = "proxy")] + let is_terminal = false; if is_terminal { wasi::FILETYPE_CHARACTER_DEVICE } else { @@ -133,6 +146,7 @@ pub struct Descriptors { /// Preopened directories. Initialized lazily. Access with `State::get_preopens` /// to take care of initialization. + #[cfg(not(feature = "proxy"))] preopens: Cell>, } @@ -142,6 +156,7 @@ impl Descriptors { table: UnsafeCell::new(MaybeUninit::uninit()), table_len: Cell::new(0), closed: None, + #[cfg(not(feature = "proxy"))] preopens: Cell::new(None), }; @@ -170,6 +185,13 @@ impl Descriptors { })) .trapping_unwrap(); + #[cfg(not(feature = "proxy"))] + d.open_preopens(import_alloc, arena); + d + } + + #[cfg(not(feature = "proxy"))] + fn open_preopens(&self, import_alloc: &ImportAlloc, arena: &BumpArena) { #[link(wasm_import_module = "wasi:filesystem/preopens@0.2.0-rc-2023-11-10")] #[allow(improper_ctypes)] // FIXME(bytecodealliance/wit-bindgen#684) extern "C" { @@ -195,7 +217,7 @@ impl Descriptors { // Expectation is that the descriptor index is initialized with // stdio (0,1,2) and no others, so that preopens are 3.. let descriptor_type = descriptor.get_type().trapping_unwrap(); - d.push(Descriptor::Streams(Streams { + self.push(Descriptor::Streams(Streams { input: OnceCell::new(), output: OnceCell::new(), type_: StreamType::File(File { @@ -209,8 +231,7 @@ impl Descriptors { .trapping_unwrap(); } - d.preopens.set(Some(preopens)); - d + self.preopens.set(Some(preopens)); } fn push(&self, desc: Descriptor) -> Result { @@ -276,6 +297,7 @@ impl Descriptors { .ok_or(wasi::ERRNO_BADF) } + #[cfg(not(feature = "proxy"))] pub fn get_preopen(&self, fd: Fd) -> Option<&Preopen> { let preopens = self.preopens.get().trapping_unwrap(); // Subtract 3 for the stdio indices to compute the preopen index. @@ -340,6 +362,7 @@ impl Descriptors { } } + #[cfg(not(feature = "proxy"))] pub fn get_file_with_error(&self, fd: Fd, error: Errno) -> Result<&File, Errno> { match self.get(fd)? { Descriptor::Streams(Streams { @@ -359,10 +382,12 @@ impl Descriptors { } } + #[cfg(not(feature = "proxy"))] pub fn get_file(&self, fd: Fd) -> Result<&File, Errno> { self.get_file_with_error(fd, wasi::ERRNO_INVAL) } + #[cfg(not(feature = "proxy"))] pub fn get_dir(&self, fd: Fd) -> Result<&File, Errno> { match self.get(fd)? { Descriptor::Streams(Streams { @@ -383,6 +408,7 @@ impl Descriptors { } } + #[cfg(not(feature = "proxy"))] pub fn get_seekable_file(&self, fd: Fd) -> Result<&File, Errno> { self.get_file_with_error(fd, wasi::ERRNO_SPIPE) } @@ -406,6 +432,7 @@ impl Descriptors { } } +#[cfg(not(feature = "proxy"))] #[repr(C)] pub struct Preopen { /// This is `MaybeUninit` because we take ownership of the `Descriptor` to @@ -414,6 +441,7 @@ pub struct Preopen { pub path: WasmStr, } +#[cfg(not(feature = "proxy"))] #[repr(C)] pub struct PreopenList { pub base: *const Preopen, diff --git a/crates/wasi-preview1-component-adapter/src/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs index 09a56510a607..22fcb171cc23 100644 --- a/crates/wasi-preview1-component-adapter/src/lib.rs +++ b/crates/wasi-preview1-component-adapter/src/lib.rs @@ -1,6 +1,18 @@ -use crate::bindings::wasi::cli::exit; +// The proxy world has no filesystem which most of this file is concerned with, +// so disable many warnings to avoid having to contort code too much for the +// proxy world. +#![cfg_attr( + feature = "proxy", + allow( + unused_mut, + unused_variables, + dead_code, + unused_imports, + unreachable_code + ) +)] + use crate::bindings::wasi::clocks::{monotonic_clock, wall_clock}; -use crate::bindings::wasi::filesystem::types as filesystem; use crate::bindings::wasi::io::poll; use crate::bindings::wasi::io::streams; use crate::bindings::wasi::random::random; @@ -16,8 +28,17 @@ use core::slice; use poll::Pollable; use wasi::*; -#[cfg(all(feature = "command", feature = "reactor"))] -compile_error!("only one of the `command` and `reactor` features may be selected at a time"); +#[cfg(not(feature = "proxy"))] +use crate::bindings::wasi::filesystem::types as filesystem; + +#[cfg(any( + all(feature = "command", feature = "reactor"), + all(feature = "reactor", feature = "proxy"), + all(feature = "command", feature = "proxy"), +))] +compile_error!( + "only one of the `command`, `reactor` or `proxy` features may be selected at a time" +); #[macro_use] mod macros; @@ -55,6 +76,26 @@ pub mod bindings { // terms of raw pointers. skip: ["get-environment", "poll"], }); + + #[cfg(feature = "proxy")] + wit_bindgen::generate!({ + path: "./crates/wasi/wit", + inline: r#" + package wasmtime:adapter; + + world adapter { + import wasi:clocks/wall-clock@0.2.0-rc-2023-11-10; + import wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-10; + import wasi:random/random@0.2.0-rc-2023-11-10; + import wasi:cli/stdout@0.2.0-rc-2023-11-10; + import wasi:cli/stderr@0.2.0-rc-2023-11-10; + import wasi:cli/stdin@0.2.0-rc-2023-11-10; + } + "#, + std_feature, + raw_strings, + skip: ["poll"], + }); } #[export_name = "wasi:cli/run@0.2.0-rc-2023-11-10#run"] @@ -68,6 +109,17 @@ pub unsafe extern "C" fn run() -> u32 { 0 } +#[cfg(feature = "proxy")] +macro_rules! cfg_filesystem_available { + ($($t:tt)*) => { + wasi::ERRNO_NOTSUP + }; +} +#[cfg(not(feature = "proxy"))] +macro_rules! cfg_filesystem_available { + ($($t:tt)*) => ($($t)*); +} + // The unwrap/expect methods in std pull panic when they fail, which pulls // in unwinding machinery that we can't use in the adapter. Instead, use this // extension trait to get postfixed upwrap on Option and Result. @@ -260,19 +312,22 @@ pub unsafe extern "C" fn cabi_export_realloc( #[no_mangle] pub unsafe extern "C" fn args_get(mut argv: *mut *mut u8, mut argv_buf: *mut u8) -> Errno { State::with(|state| { - for arg in state.get_args() { - // Copy the argument into `argv_buf` which must be sized - // appropriately by the caller. - ptr::copy_nonoverlapping(arg.ptr, argv_buf, arg.len); - *argv_buf.add(arg.len) = 0; - - // Copy the argument pointer into the `argv` buf - *argv = argv_buf; - - // Update our pointers past what's written to prepare for the - // next argument. - argv = argv.add(1); - argv_buf = argv_buf.add(arg.len + 1); + #[cfg(not(feature = "proxy"))] + { + for arg in state.get_args() { + // Copy the argument into `argv_buf` which must be sized + // appropriately by the caller. + ptr::copy_nonoverlapping(arg.ptr, argv_buf, arg.len); + *argv_buf.add(arg.len) = 0; + + // Copy the argument pointer into the `argv` buf + *argv = argv_buf; + + // Update our pointers past what's written to prepare for the + // next argument. + argv = argv.add(1); + argv_buf = argv_buf.add(arg.len + 1); + } } Ok(()) }) @@ -282,11 +337,19 @@ pub unsafe extern "C" fn args_get(mut argv: *mut *mut u8, mut argv_buf: *mut u8) #[no_mangle] pub unsafe extern "C" fn args_sizes_get(argc: *mut Size, argv_buf_size: *mut Size) -> Errno { State::with(|state| { - let args = state.get_args(); - *argc = args.len(); - // Add one to each length for the terminating nul byte added by - // the `args_get` function. - *argv_buf_size = args.iter().map(|s| s.len + 1).sum(); + #[cfg(feature = "proxy")] + { + *argc = 0; + *argv_buf_size = 0; + } + #[cfg(not(feature = "proxy"))] + { + let args = state.get_args(); + *argc = args.len(); + // Add one to each length for the terminating nul byte added by + // the `args_get` function. + *argv_buf_size = args.iter().map(|s| s.len + 1).sum(); + } Ok(()) }) } @@ -296,23 +359,26 @@ pub unsafe extern "C" fn args_sizes_get(argc: *mut Size, argv_buf_size: *mut Siz #[no_mangle] pub unsafe extern "C" fn environ_get(environ: *mut *mut u8, environ_buf: *mut u8) -> Errno { State::with(|state| { - let mut offsets = environ; - let mut buffer = environ_buf; - for var in state.get_environment() { - ptr::write(offsets, buffer); - offsets = offsets.add(1); + #[cfg(not(feature = "proxy"))] + { + let mut offsets = environ; + let mut buffer = environ_buf; + for var in state.get_environment() { + ptr::write(offsets, buffer); + offsets = offsets.add(1); - ptr::copy_nonoverlapping(var.key.ptr, buffer, var.key.len); - buffer = buffer.add(var.key.len); + ptr::copy_nonoverlapping(var.key.ptr, buffer, var.key.len); + buffer = buffer.add(var.key.len); - ptr::write(buffer, b'='); - buffer = buffer.add(1); + ptr::write(buffer, b'='); + buffer = buffer.add(1); - ptr::copy_nonoverlapping(var.value.ptr, buffer, var.value.len); - buffer = buffer.add(var.value.len); + ptr::copy_nonoverlapping(var.value.ptr, buffer, var.value.len); + buffer = buffer.add(var.value.len); - ptr::write(buffer, 0); - buffer = buffer.add(1); + ptr::write(buffer, 0); + buffer = buffer.add(1); + } } Ok(()) @@ -325,11 +391,23 @@ pub unsafe extern "C" fn environ_sizes_get( environc: *mut Size, environ_buf_size: *mut Size, ) -> Errno { - if matches!( + if !matches!( get_allocation_state(), AllocationState::StackAllocated | AllocationState::StateAllocated ) { - State::with(|state| { + *environc = 0; + *environ_buf_size = 0; + return ERRNO_SUCCESS; + } + + State::with(|state| { + #[cfg(feature = "proxy")] + { + *environc = 0; + *environ_buf_size = 0; + } + #[cfg(not(feature = "proxy"))] + { let vars = state.get_environment(); *environc = vars.len(); *environ_buf_size = { @@ -339,14 +417,10 @@ pub unsafe extern "C" fn environ_sizes_get( } sum }; + } - Ok(()) - }) - } else { - *environc = 0; - *environ_buf_size = 0; - ERRNO_SUCCESS - } + Ok(()) + }) } /// Return the resolution of a clock. @@ -412,34 +486,38 @@ pub unsafe extern "C" fn fd_advise( len: Filesize, advice: Advice, ) -> Errno { - let advice = match advice { - ADVICE_NORMAL => filesystem::Advice::Normal, - ADVICE_SEQUENTIAL => filesystem::Advice::Sequential, - ADVICE_RANDOM => filesystem::Advice::Random, - ADVICE_WILLNEED => filesystem::Advice::WillNeed, - ADVICE_DONTNEED => filesystem::Advice::DontNeed, - ADVICE_NOREUSE => filesystem::Advice::NoReuse, - _ => return ERRNO_INVAL, - }; - State::with(|state| { - let ds = state.descriptors(); - let file = ds.get_seekable_file(fd)?; - file.fd.advise(offset, len, advice)?; - Ok(()) - }) + cfg_filesystem_available! { + let advice = match advice { + ADVICE_NORMAL => filesystem::Advice::Normal, + ADVICE_SEQUENTIAL => filesystem::Advice::Sequential, + ADVICE_RANDOM => filesystem::Advice::Random, + ADVICE_WILLNEED => filesystem::Advice::WillNeed, + ADVICE_DONTNEED => filesystem::Advice::DontNeed, + ADVICE_NOREUSE => filesystem::Advice::NoReuse, + _ => return ERRNO_INVAL, + }; + State::with(|state| { + let ds = state.descriptors(); + let file = ds.get_seekable_file(fd)?; + file.fd.advise(offset, len, advice)?; + Ok(()) + }) + } } /// Force the allocation of space in a file. /// Note: This is similar to `posix_fallocate` in POSIX. #[no_mangle] pub unsafe extern "C" fn fd_allocate(fd: Fd, _offset: Filesize, _len: Filesize) -> Errno { - State::with(|state| { - let ds = state.descriptors(); - // For not-files, fail with BADF - ds.get_file(fd)?; - // For all files, fail with NOTSUP, because this call does not exist in preview 2. - Err(wasi::ERRNO_NOTSUP) - }) + cfg_filesystem_available! { + State::with(|state| { + let ds = state.descriptors(); + // For not-files, fail with BADF + ds.get_file(fd)?; + // For all files, fail with NOTSUP, because this call does not exist in preview 2. + Err(wasi::ERRNO_NOTSUP) + }) + } } /// Close a file descriptor. @@ -450,6 +528,7 @@ pub unsafe extern "C" fn fd_close(fd: Fd) -> Errno { // If there's a dirent cache entry for this file descriptor then drop // it since the descriptor is being closed and future calls to // `fd_readdir` should return an error. + #[cfg(not(feature = "proxy"))] if fd == state.dirent_cache.for_fd.get() { drop(state.dirent_cache.stream.replace(None)); } @@ -463,133 +542,137 @@ pub unsafe extern "C" fn fd_close(fd: Fd) -> Errno { /// Note: This is similar to `fdatasync` in POSIX. #[no_mangle] pub unsafe extern "C" fn fd_datasync(fd: Fd) -> Errno { - State::with(|state| { - let ds = state.descriptors(); - let file = ds.get_file(fd)?; - file.fd.sync_data()?; - Ok(()) - }) + cfg_filesystem_available! { + State::with(|state| { + let ds = state.descriptors(); + let file = ds.get_file(fd)?; + file.fd.sync_data()?; + Ok(()) + }) + } } /// Get the attributes of a file descriptor. /// Note: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well as additional fields. #[no_mangle] pub unsafe extern "C" fn fd_fdstat_get(fd: Fd, stat: *mut Fdstat) -> Errno { - State::with(|state| { - let ds = state.descriptors(); - match ds.get(fd)? { - Descriptor::Streams(Streams { - type_: StreamType::File(file), - .. - }) => { - let flags = file.fd.get_flags()?; - let type_ = file.fd.get_type()?; - match type_ { - filesystem::DescriptorType::Directory => { - // Hard-coded set of rights expected by many userlands: - let fs_rights_base = wasi::RIGHTS_PATH_CREATE_DIRECTORY - | wasi::RIGHTS_PATH_CREATE_FILE - | wasi::RIGHTS_PATH_LINK_SOURCE - | wasi::RIGHTS_PATH_LINK_TARGET - | wasi::RIGHTS_PATH_OPEN - | wasi::RIGHTS_FD_READDIR - | wasi::RIGHTS_PATH_READLINK - | wasi::RIGHTS_PATH_RENAME_SOURCE - | wasi::RIGHTS_PATH_RENAME_TARGET - | wasi::RIGHTS_PATH_SYMLINK - | wasi::RIGHTS_PATH_REMOVE_DIRECTORY - | wasi::RIGHTS_PATH_UNLINK_FILE - | wasi::RIGHTS_PATH_FILESTAT_GET - | wasi::RIGHTS_PATH_FILESTAT_SET_TIMES - | wasi::RIGHTS_FD_FILESTAT_GET - | wasi::RIGHTS_FD_FILESTAT_SET_TIMES; - - let fs_rights_inheriting = fs_rights_base - | wasi::RIGHTS_FD_DATASYNC - | wasi::RIGHTS_FD_READ - | wasi::RIGHTS_FD_SEEK - | wasi::RIGHTS_FD_FDSTAT_SET_FLAGS - | wasi::RIGHTS_FD_SYNC - | wasi::RIGHTS_FD_TELL - | wasi::RIGHTS_FD_WRITE - | wasi::RIGHTS_FD_ADVISE - | wasi::RIGHTS_FD_ALLOCATE - | wasi::RIGHTS_FD_FILESTAT_GET - | wasi::RIGHTS_FD_FILESTAT_SET_SIZE - | wasi::RIGHTS_FD_FILESTAT_SET_TIMES - | wasi::RIGHTS_POLL_FD_READWRITE; - - stat.write(Fdstat { - fs_filetype: wasi::FILETYPE_DIRECTORY, - fs_flags: 0, - fs_rights_base, - fs_rights_inheriting, - }); - Ok(()) - } - _ => { - let fs_filetype = type_.into(); - - let mut fs_flags = 0; - let mut fs_rights_base = !0; - if !flags.contains(filesystem::DescriptorFlags::READ) { - fs_rights_base &= !RIGHTS_FD_READ; - } - if !flags.contains(filesystem::DescriptorFlags::WRITE) { - fs_rights_base &= !RIGHTS_FD_WRITE; - } - if flags.contains(filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC) { - fs_flags |= FDFLAGS_DSYNC; - } - if flags.contains(filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC) { - fs_flags |= FDFLAGS_RSYNC; - } - if flags.contains(filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC) { - fs_flags |= FDFLAGS_SYNC; - } - if file.append { - fs_flags |= FDFLAGS_APPEND; + cfg_filesystem_available! { + State::with(|state| { + let ds = state.descriptors(); + match ds.get(fd)? { + Descriptor::Streams(Streams { + type_: StreamType::File(file), + .. + }) => { + let flags = file.fd.get_flags()?; + let type_ = file.fd.get_type()?; + match type_ { + filesystem::DescriptorType::Directory => { + // Hard-coded set of rights expected by many userlands: + let fs_rights_base = wasi::RIGHTS_PATH_CREATE_DIRECTORY + | wasi::RIGHTS_PATH_CREATE_FILE + | wasi::RIGHTS_PATH_LINK_SOURCE + | wasi::RIGHTS_PATH_LINK_TARGET + | wasi::RIGHTS_PATH_OPEN + | wasi::RIGHTS_FD_READDIR + | wasi::RIGHTS_PATH_READLINK + | wasi::RIGHTS_PATH_RENAME_SOURCE + | wasi::RIGHTS_PATH_RENAME_TARGET + | wasi::RIGHTS_PATH_SYMLINK + | wasi::RIGHTS_PATH_REMOVE_DIRECTORY + | wasi::RIGHTS_PATH_UNLINK_FILE + | wasi::RIGHTS_PATH_FILESTAT_GET + | wasi::RIGHTS_PATH_FILESTAT_SET_TIMES + | wasi::RIGHTS_FD_FILESTAT_GET + | wasi::RIGHTS_FD_FILESTAT_SET_TIMES; + + let fs_rights_inheriting = fs_rights_base + | wasi::RIGHTS_FD_DATASYNC + | wasi::RIGHTS_FD_READ + | wasi::RIGHTS_FD_SEEK + | wasi::RIGHTS_FD_FDSTAT_SET_FLAGS + | wasi::RIGHTS_FD_SYNC + | wasi::RIGHTS_FD_TELL + | wasi::RIGHTS_FD_WRITE + | wasi::RIGHTS_FD_ADVISE + | wasi::RIGHTS_FD_ALLOCATE + | wasi::RIGHTS_FD_FILESTAT_GET + | wasi::RIGHTS_FD_FILESTAT_SET_SIZE + | wasi::RIGHTS_FD_FILESTAT_SET_TIMES + | wasi::RIGHTS_POLL_FD_READWRITE; + + stat.write(Fdstat { + fs_filetype: wasi::FILETYPE_DIRECTORY, + fs_flags: 0, + fs_rights_base, + fs_rights_inheriting, + }); + Ok(()) } - if matches!(file.blocking_mode, BlockingMode::NonBlocking) { - fs_flags |= FDFLAGS_NONBLOCK; + _ => { + let fs_filetype = type_.into(); + + let mut fs_flags = 0; + let mut fs_rights_base = !0; + if !flags.contains(filesystem::DescriptorFlags::READ) { + fs_rights_base &= !RIGHTS_FD_READ; + } + if !flags.contains(filesystem::DescriptorFlags::WRITE) { + fs_rights_base &= !RIGHTS_FD_WRITE; + } + if flags.contains(filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC) { + fs_flags |= FDFLAGS_DSYNC; + } + if flags.contains(filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC) { + fs_flags |= FDFLAGS_RSYNC; + } + if flags.contains(filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC) { + fs_flags |= FDFLAGS_SYNC; + } + if file.append { + fs_flags |= FDFLAGS_APPEND; + } + if matches!(file.blocking_mode, BlockingMode::NonBlocking) { + fs_flags |= FDFLAGS_NONBLOCK; + } + let fs_rights_inheriting = fs_rights_base; + + stat.write(Fdstat { + fs_filetype, + fs_flags, + fs_rights_base, + fs_rights_inheriting, + }); + Ok(()) } - let fs_rights_inheriting = fs_rights_base; - - stat.write(Fdstat { - fs_filetype, - fs_flags, - fs_rights_base, - fs_rights_inheriting, - }); - Ok(()) } } - } - Descriptor::Streams(Streams { - input, - output, - type_: StreamType::Stdio(stdio), - }) => { - let fs_flags = 0; - let mut fs_rights_base = 0; - if input.get().is_some() { - fs_rights_base |= RIGHTS_FD_READ; - } - if output.get().is_some() { - fs_rights_base |= RIGHTS_FD_WRITE; + Descriptor::Streams(Streams { + input, + output, + type_: StreamType::Stdio(stdio), + }) => { + let fs_flags = 0; + let mut fs_rights_base = 0; + if input.get().is_some() { + fs_rights_base |= RIGHTS_FD_READ; + } + if output.get().is_some() { + fs_rights_base |= RIGHTS_FD_WRITE; + } + let fs_rights_inheriting = fs_rights_base; + stat.write(Fdstat { + fs_filetype: stdio.filetype(), + fs_flags, + fs_rights_base, + fs_rights_inheriting, + }); + Ok(()) } - let fs_rights_inheriting = fs_rights_base; - stat.write(Fdstat { - fs_filetype: stdio.filetype(), - fs_flags, - fs_rights_base, - fs_rights_inheriting, - }); - Ok(()) + Descriptor::Closed(_) => Err(ERRNO_BADF), } - Descriptor::Closed(_) => Err(ERRNO_BADF), - } - }) + }) + } } /// Adjust the flags associated with a file descriptor. @@ -601,23 +684,25 @@ pub unsafe extern "C" fn fd_fdstat_set_flags(fd: Fd, flags: Fdflags) -> Errno { return wasi::ERRNO_INVAL; } - State::with(|state| { - let mut ds = state.descriptors_mut(); - let file = match ds.get_mut(fd)? { - Descriptor::Streams(Streams { - type_: StreamType::File(file), - .. - }) if !file.is_dir() => file, - _ => Err(wasi::ERRNO_BADF)?, - }; - file.append = flags & FDFLAGS_APPEND == FDFLAGS_APPEND; - file.blocking_mode = if flags & FDFLAGS_NONBLOCK == FDFLAGS_NONBLOCK { - BlockingMode::NonBlocking - } else { - BlockingMode::Blocking - }; - Ok(()) - }) + cfg_filesystem_available! { + State::with(|state| { + let mut ds = state.descriptors_mut(); + let file = match ds.get_mut(fd)? { + Descriptor::Streams(Streams { + type_: StreamType::File(file), + .. + }) if !file.is_dir() => file, + _ => Err(wasi::ERRNO_BADF)?, + }; + file.append = flags & FDFLAGS_APPEND == FDFLAGS_APPEND; + file.blocking_mode = if flags & FDFLAGS_NONBLOCK == FDFLAGS_NONBLOCK { + BlockingMode::NonBlocking + } else { + BlockingMode::Blocking + }; + Ok(()) + }) + } } /// Does not do anything if `fd` corresponds to a valid descriptor and returns [`wasi::ERRNO_BADF`] otherwise. @@ -639,62 +724,67 @@ pub unsafe extern "C" fn fd_fdstat_set_rights( /// Return the attributes of an open file. #[no_mangle] pub unsafe extern "C" fn fd_filestat_get(fd: Fd, buf: *mut Filestat) -> Errno { - State::with(|state| { - let ds = state.descriptors(); - match ds.get(fd)? { - Descriptor::Streams(Streams { - type_: StreamType::File(file), - .. - }) => { - let stat = file.fd.stat()?; - let metadata_hash = file.fd.metadata_hash()?; - let filetype = stat.type_.into(); - *buf = Filestat { - dev: 1, - ino: metadata_hash.lower, - filetype, - nlink: stat.link_count, - size: stat.size, - atim: datetime_to_timestamp(stat.data_access_timestamp), - mtim: datetime_to_timestamp(stat.data_modification_timestamp), - ctim: datetime_to_timestamp(stat.status_change_timestamp), - }; - Ok(()) - } - // Stdio is all zero fields, except for filetype character device - Descriptor::Streams(Streams { - type_: StreamType::Stdio(stdio), - .. - }) => { - *buf = Filestat { - dev: 0, - ino: 0, - filetype: stdio.filetype(), - nlink: 0, - size: 0, - atim: 0, - mtim: 0, - ctim: 0, - }; - Ok(()) + cfg_filesystem_available! { + State::with(|state| { + let ds = state.descriptors(); + match ds.get(fd)? { + Descriptor::Streams(Streams { + type_: StreamType::File(file), + .. + }) => { + let stat = file.fd.stat()?; + let metadata_hash = file.fd.metadata_hash()?; + let filetype = stat.type_.into(); + *buf = Filestat { + dev: 1, + ino: metadata_hash.lower, + filetype, + nlink: stat.link_count, + size: stat.size, + atim: datetime_to_timestamp(stat.data_access_timestamp), + mtim: datetime_to_timestamp(stat.data_modification_timestamp), + ctim: datetime_to_timestamp(stat.status_change_timestamp), + }; + Ok(()) + } + // Stdio is all zero fields, except for filetype character device + Descriptor::Streams(Streams { + type_: StreamType::Stdio(stdio), + .. + }) => { + *buf = Filestat { + dev: 0, + ino: 0, + filetype: stdio.filetype(), + nlink: 0, + size: 0, + atim: 0, + mtim: 0, + ctim: 0, + }; + Ok(()) + } + _ => Err(wasi::ERRNO_BADF), } - _ => Err(wasi::ERRNO_BADF), - } - }) + }) + } } /// Adjust the size of an open file. If this increases the file's size, the extra bytes are filled with zeros. /// Note: This is similar to `ftruncate` in POSIX. #[no_mangle] pub unsafe extern "C" fn fd_filestat_set_size(fd: Fd, size: Filesize) -> Errno { - State::with(|state| { - let ds = state.descriptors(); - let file = ds.get_file(fd)?; - file.fd.set_size(size)?; - Ok(()) - }) + cfg_filesystem_available! { + State::with(|state| { + let ds = state.descriptors(); + let file = ds.get_file(fd)?; + file.fd.set_size(size)?; + Ok(()) + }) + } } +#[cfg(not(feature = "proxy"))] fn systimespec(set: bool, ts: Timestamp, now: bool) -> Result { if set && now { Err(wasi::ERRNO_INVAL) @@ -719,22 +809,24 @@ pub unsafe extern "C" fn fd_filestat_set_times( mtim: Timestamp, fst_flags: Fstflags, ) -> Errno { - State::with(|state| { - let atim = systimespec( - fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM, - atim, - fst_flags & FSTFLAGS_ATIM_NOW == FSTFLAGS_ATIM_NOW, - )?; - let mtim = systimespec( - fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM, - mtim, - fst_flags & FSTFLAGS_MTIM_NOW == FSTFLAGS_MTIM_NOW, - )?; - let ds = state.descriptors(); - let file = ds.get_file(fd)?; - file.fd.set_times(atim, mtim)?; - Ok(()) - }) + cfg_filesystem_available! { + State::with(|state| { + let atim = systimespec( + fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM, + atim, + fst_flags & FSTFLAGS_ATIM_NOW == FSTFLAGS_ATIM_NOW, + )?; + let mtim = systimespec( + fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM, + mtim, + fst_flags & FSTFLAGS_MTIM_NOW == FSTFLAGS_MTIM_NOW, + )?; + let ds = state.descriptors(); + let file = ds.get_file(fd)?; + file.fd.set_times(atim, mtim)?; + Ok(()) + }) + } } /// Read from a file descriptor, without using and updating the file descriptor's offset. @@ -747,46 +839,52 @@ pub unsafe extern "C" fn fd_pread( offset: Filesize, nread: *mut Size, ) -> Errno { - // Advance to the first non-empty buffer. - while iovs_len != 0 && (*iovs_ptr).buf_len == 0 { - iovs_ptr = iovs_ptr.add(1); - iovs_len -= 1; - } - if iovs_len == 0 { - *nread = 0; - return ERRNO_SUCCESS; - } + cfg_filesystem_available! { + // Advance to the first non-empty buffer. + while iovs_len != 0 && (*iovs_ptr).buf_len == 0 { + iovs_ptr = iovs_ptr.add(1); + iovs_len -= 1; + } + if iovs_len == 0 { + *nread = 0; + return ERRNO_SUCCESS; + } - State::with(|state| { - let ptr = (*iovs_ptr).buf; - let len = (*iovs_ptr).buf_len; + State::with(|state| { + let ptr = (*iovs_ptr).buf; + let len = (*iovs_ptr).buf_len; - let ds = state.descriptors(); - let file = ds.get_file(fd)?; - let (data, end) = state - .import_alloc - .with_buffer(ptr, len, || file.fd.read(len as u64, offset))?; - assert_eq!(data.as_ptr(), ptr); - assert!(data.len() <= len); - - let len = data.len(); - forget(data); - if !end && len == 0 { - Err(ERRNO_INTR) - } else { - *nread = len; - Ok(()) - } - }) + let ds = state.descriptors(); + let file = ds.get_file(fd)?; + let (data, end) = state + .import_alloc + .with_buffer(ptr, len, || file.fd.read(len as u64, offset))?; + assert_eq!(data.as_ptr(), ptr); + assert!(data.len() <= len); + + let len = data.len(); + forget(data); + if !end && len == 0 { + Err(ERRNO_INTR) + } else { + *nread = len; + Ok(()) + } + }) + } } /// Return a description of the given preopened file descriptor. #[no_mangle] pub unsafe extern "C" fn fd_prestat_get(fd: Fd, buf: *mut Prestat) -> Errno { - if matches!( + if !matches!( get_allocation_state(), AllocationState::StackAllocated | AllocationState::StateAllocated ) { + return ERRNO_BADF; + } + + cfg_filesystem_available! { State::with(|state| { let ds = state.descriptors(); if let Some(preopen) = ds.get_preopen(fd) { @@ -804,27 +902,27 @@ pub unsafe extern "C" fn fd_prestat_get(fd: Fd, buf: *mut Prestat) -> Errno { Err(ERRNO_BADF) } }) - } else { - ERRNO_BADF } } /// Return a description of the given preopened file descriptor. #[no_mangle] pub unsafe extern "C" fn fd_prestat_dir_name(fd: Fd, path: *mut u8, path_max_len: Size) -> Errno { - State::with(|state| { - let ds = state.descriptors(); - if let Some(preopen) = ds.get_preopen(fd) { - if preopen.path.len > path_max_len { - Err(ERRNO_NAMETOOLONG) + cfg_filesystem_available! { + State::with(|state| { + let ds = state.descriptors(); + if let Some(preopen) = ds.get_preopen(fd) { + if preopen.path.len > path_max_len { + Err(ERRNO_NAMETOOLONG) + } else { + ptr::copy_nonoverlapping(preopen.path.ptr, path, preopen.path.len); + Ok(()) + } } else { - ptr::copy_nonoverlapping(preopen.path.ptr, path, preopen.path.len); - Ok(()) + Err(ERRNO_NOTDIR) } - } else { - Err(ERRNO_NOTDIR) - } - }) + }) + } } /// Write to a file descriptor, without using and updating the file descriptor's offset. @@ -837,26 +935,28 @@ pub unsafe extern "C" fn fd_pwrite( offset: Filesize, nwritten: *mut Size, ) -> Errno { - // Advance to the first non-empty buffer. - while iovs_len != 0 && (*iovs_ptr).buf_len == 0 { - iovs_ptr = iovs_ptr.add(1); - iovs_len -= 1; - } - if iovs_len == 0 { - *nwritten = 0; - return ERRNO_SUCCESS; - } + cfg_filesystem_available! { + // Advance to the first non-empty buffer. + while iovs_len != 0 && (*iovs_ptr).buf_len == 0 { + iovs_ptr = iovs_ptr.add(1); + iovs_len -= 1; + } + if iovs_len == 0 { + *nwritten = 0; + return ERRNO_SUCCESS; + } - let ptr = (*iovs_ptr).buf; - let len = (*iovs_ptr).buf_len; + let ptr = (*iovs_ptr).buf; + let len = (*iovs_ptr).buf_len; - State::with(|state| { - let ds = state.descriptors(); - let file = ds.get_seekable_file(fd)?; - let bytes = file.fd.write(slice::from_raw_parts(ptr, len), offset)?; - *nwritten = bytes as usize; - Ok(()) - }) + State::with(|state| { + let ds = state.descriptors(); + let file = ds.get_seekable_file(fd)?; + let bytes = file.fd.write(slice::from_raw_parts(ptr, len), offset)?; + *nwritten = bytes as usize; + Ok(()) + }) + } } /// Read from a file descriptor. @@ -885,11 +985,14 @@ pub unsafe extern "C" fn fd_read( let ds = state.descriptors(); match ds.get(fd)? { Descriptor::Streams(streams) => { + #[cfg(not(feature = "proxy"))] let blocking_mode = if let StreamType::File(file) = &streams.type_ { file.blocking_mode } else { BlockingMode::Blocking }; + #[cfg(feature = "proxy")] + let blocking_mode = BlockingMode::Blocking; let read_len = u64::try_from(len).trapping_unwrap(); let wasi_stream = streams.get_read_stream()?; @@ -911,6 +1014,7 @@ pub unsafe extern "C" fn fd_read( assert!(data.len() <= len); // If this is a file, keep the current-position pointer up to date. + #[cfg(not(feature = "proxy"))] if let StreamType::File(file) = &streams.type_ { file.position .set(file.position.get() + data.len() as filesystem::Filesize); @@ -930,6 +1034,9 @@ pub unsafe extern "C" fn fd_read( } fn stream_error_to_errno(err: streams::Error) -> Errno { + #[cfg(feature = "proxy")] + return ERRNO_IO; + #[cfg(not(feature = "proxy"))] match filesystem::filesystem_error_code(&err) { Some(code) => code.into(), None => ERRNO_IO, @@ -946,6 +1053,19 @@ fn stream_error_to_errno(err: streams::Error) -> Errno { /// read buffer size in case it's too small to fit a single large directory /// entry, or skip the oversized directory entry. #[no_mangle] +#[cfg(feature = "proxy")] +pub unsafe extern "C" fn fd_readdir( + fd: Fd, + buf: *mut u8, + buf_len: Size, + cookie: Dircookie, + bufused: *mut Size, +) -> Errno { + wasi::ERRNO_NOTSUP +} + +#[no_mangle] +#[cfg(not(feature = "proxy"))] pub unsafe extern "C" fn fd_readdir( fd: Fd, buf: *mut u8, @@ -1194,62 +1314,68 @@ pub unsafe extern "C" fn fd_seek( whence: Whence, newoffset: *mut Filesize, ) -> Errno { - State::with(|state| { - let mut ds = state.descriptors_mut(); - let stream = ds.get_seekable_stream_mut(fd)?; - - // Seeking only works on files. - if let StreamType::File(file) = &mut stream.type_ { - if let filesystem::DescriptorType::Directory = file.descriptor_type { - // This isn't really the "right" errno, but it is consistient with wasmtime's - // preview 1 tests. - return Err(ERRNO_BADF); - } - let from = match whence { - WHENCE_SET if offset >= 0 => offset, - WHENCE_CUR => match (file.position.get() as i64).checked_add(offset) { - Some(pos) if pos >= 0 => pos, - _ => return Err(ERRNO_INVAL), - }, - WHENCE_END => match (file.fd.stat()?.size as i64).checked_add(offset) { - Some(pos) if pos >= 0 => pos, + cfg_filesystem_available! { + State::with(|state| { + let mut ds = state.descriptors_mut(); + let stream = ds.get_seekable_stream_mut(fd)?; + + // Seeking only works on files. + if let StreamType::File(file) = &mut stream.type_ { + if let filesystem::DescriptorType::Directory = file.descriptor_type { + // This isn't really the "right" errno, but it is consistient with wasmtime's + // preview 1 tests. + return Err(ERRNO_BADF); + } + let from = match whence { + WHENCE_SET if offset >= 0 => offset, + WHENCE_CUR => match (file.position.get() as i64).checked_add(offset) { + Some(pos) if pos >= 0 => pos, + _ => return Err(ERRNO_INVAL), + }, + WHENCE_END => match (file.fd.stat()?.size as i64).checked_add(offset) { + Some(pos) if pos >= 0 => pos, + _ => return Err(ERRNO_INVAL), + }, _ => return Err(ERRNO_INVAL), - }, - _ => return Err(ERRNO_INVAL), - }; - drop(stream.input.take()); - drop(stream.output.take()); - file.position.set(from as filesystem::Filesize); - *newoffset = from as filesystem::Filesize; - Ok(()) - } else { - Err(ERRNO_SPIPE) - } - }) + }; + drop(stream.input.take()); + drop(stream.output.take()); + file.position.set(from as filesystem::Filesize); + *newoffset = from as filesystem::Filesize; + Ok(()) + } else { + Err(ERRNO_SPIPE) + } + }) + } } /// Synchronize the data and metadata of a file to disk. /// Note: This is similar to `fsync` in POSIX. #[no_mangle] pub unsafe extern "C" fn fd_sync(fd: Fd) -> Errno { - State::with(|state| { - let ds = state.descriptors(); - let file = ds.get_file(fd)?; - file.fd.sync()?; - Ok(()) - }) + cfg_filesystem_available! { + State::with(|state| { + let ds = state.descriptors(); + let file = ds.get_file(fd)?; + file.fd.sync()?; + Ok(()) + }) + } } /// Return the current offset of a file descriptor. /// Note: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX. #[no_mangle] pub unsafe extern "C" fn fd_tell(fd: Fd, offset: *mut Filesize) -> Errno { - State::with(|state| { - let ds = state.descriptors(); - let file = ds.get_seekable_file(fd)?; - *offset = file.position.get(); - Ok(()) - }) + cfg_filesystem_available! { + State::with(|state| { + let ds = state.descriptors(); + let file = ds.get_seekable_file(fd)?; + *offset = file.position.get(); + Ok(()) + }) + } } /// Write to a file descriptor. @@ -1261,64 +1387,68 @@ pub unsafe extern "C" fn fd_write( mut iovs_len: usize, nwritten: *mut Size, ) -> Errno { - if matches!( + if !matches!( get_allocation_state(), AllocationState::StackAllocated | AllocationState::StateAllocated ) { - // Advance to the first non-empty buffer. - while iovs_len != 0 && (*iovs_ptr).buf_len == 0 { - iovs_ptr = iovs_ptr.add(1); - iovs_len -= 1; - } - if iovs_len == 0 { - *nwritten = 0; - return ERRNO_SUCCESS; - } + *nwritten = 0; + return ERRNO_IO; + } - let ptr = (*iovs_ptr).buf; - let len = (*iovs_ptr).buf_len; - let bytes = slice::from_raw_parts(ptr, len); + // Advance to the first non-empty buffer. + while iovs_len != 0 && (*iovs_ptr).buf_len == 0 { + iovs_ptr = iovs_ptr.add(1); + iovs_len -= 1; + } + if iovs_len == 0 { + *nwritten = 0; + return ERRNO_SUCCESS; + } - State::with(|state| { - let ds = state.descriptors(); - match ds.get(fd)? { - Descriptor::Streams(streams) => { - let wasi_stream = streams.get_write_stream()?; + let ptr = (*iovs_ptr).buf; + let len = (*iovs_ptr).buf_len; + let bytes = slice::from_raw_parts(ptr, len); - let nbytes = if let StreamType::File(file) = &streams.type_ { - file.blocking_mode.write(wasi_stream, bytes)? - } else { - // Use blocking writes on non-file streams (stdout, stderr, as sockets - // aren't currently used). - BlockingMode::Blocking.write(wasi_stream, bytes)? - }; + State::with(|state| { + let ds = state.descriptors(); + match ds.get(fd)? { + Descriptor::Streams(streams) => { + let wasi_stream = streams.get_write_stream()?; - // If this is a file, keep the current-position pointer up - // to date. Note that for files that perform appending - // writes this function will always update the current - // position to the end of the file. - // - // NB: this isn't "atomic" as it doesn't necessarily account - // for concurrent writes, but there's not much that can be - // done about that. - if let StreamType::File(file) = &streams.type_ { - if file.append { - file.position.set(file.fd.stat()?.size); - } else { - file.position.set(file.position.get() + nbytes as u64); - } + #[cfg(not(feature = "proxy"))] + let nbytes = if let StreamType::File(file) = &streams.type_ { + file.blocking_mode.write(wasi_stream, bytes)? + } else { + // Use blocking writes on non-file streams (stdout, stderr, as sockets + // aren't currently used). + BlockingMode::Blocking.write(wasi_stream, bytes)? + }; + #[cfg(feature = "proxy")] + let nbytes = BlockingMode::Blocking.write(wasi_stream, bytes)?; + + // If this is a file, keep the current-position pointer up + // to date. Note that for files that perform appending + // writes this function will always update the current + // position to the end of the file. + // + // NB: this isn't "atomic" as it doesn't necessarily account + // for concurrent writes, but there's not much that can be + // done about that. + #[cfg(not(feature = "proxy"))] + if let StreamType::File(file) = &streams.type_ { + if file.append { + file.position.set(file.fd.stat()?.size); + } else { + file.position.set(file.position.get() + nbytes as u64); } - - *nwritten = nbytes; - Ok(()) } - Descriptor::Closed(_) => Err(ERRNO_BADF), + + *nwritten = nbytes; + Ok(()) } - }) - } else { - *nwritten = 0; - ERRNO_IO - } + Descriptor::Closed(_) => Err(ERRNO_BADF), + } + }) } /// Create a directory. @@ -1329,14 +1459,16 @@ pub unsafe extern "C" fn path_create_directory( path_ptr: *const u8, path_len: usize, ) -> Errno { - let path = slice::from_raw_parts(path_ptr, path_len); + cfg_filesystem_available! { + let path = slice::from_raw_parts(path_ptr, path_len); - State::with(|state| { - let ds = state.descriptors(); - let file = ds.get_dir(fd)?; - file.fd.create_directory_at(path)?; - Ok(()) - }) + State::with(|state| { + let ds = state.descriptors(); + let file = ds.get_dir(fd)?; + file.fd.create_directory_at(path)?; + Ok(()) + }) + } } /// Return the attributes of a file or directory. @@ -1349,27 +1481,29 @@ pub unsafe extern "C" fn path_filestat_get( path_len: usize, buf: *mut Filestat, ) -> Errno { - let path = slice::from_raw_parts(path_ptr, path_len); - let at_flags = at_flags_from_lookupflags(flags); + cfg_filesystem_available! { + let path = slice::from_raw_parts(path_ptr, path_len); + let at_flags = at_flags_from_lookupflags(flags); - State::with(|state| { - let ds = state.descriptors(); - let file = ds.get_dir(fd)?; - let stat = file.fd.stat_at(at_flags, path)?; - let metadata_hash = file.fd.metadata_hash_at(at_flags, path)?; - let filetype = stat.type_.into(); - *buf = Filestat { - dev: 1, - ino: metadata_hash.lower, - filetype, - nlink: stat.link_count, - size: stat.size, - atim: datetime_to_timestamp(stat.data_access_timestamp), - mtim: datetime_to_timestamp(stat.data_modification_timestamp), - ctim: datetime_to_timestamp(stat.status_change_timestamp), - }; - Ok(()) - }) + State::with(|state| { + let ds = state.descriptors(); + let file = ds.get_dir(fd)?; + let stat = file.fd.stat_at(at_flags, path)?; + let metadata_hash = file.fd.metadata_hash_at(at_flags, path)?; + let filetype = stat.type_.into(); + *buf = Filestat { + dev: 1, + ino: metadata_hash.lower, + filetype, + nlink: stat.link_count, + size: stat.size, + atim: datetime_to_timestamp(stat.data_access_timestamp), + mtim: datetime_to_timestamp(stat.data_modification_timestamp), + ctim: datetime_to_timestamp(stat.status_change_timestamp), + }; + Ok(()) + }) + } } /// Adjust the timestamps of a file or directory. @@ -1384,26 +1518,28 @@ pub unsafe extern "C" fn path_filestat_set_times( mtim: Timestamp, fst_flags: Fstflags, ) -> Errno { - let path = slice::from_raw_parts(path_ptr, path_len); - let at_flags = at_flags_from_lookupflags(flags); + cfg_filesystem_available! { + let path = slice::from_raw_parts(path_ptr, path_len); + let at_flags = at_flags_from_lookupflags(flags); - State::with(|state| { - let atim = systimespec( - fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM, - atim, - fst_flags & FSTFLAGS_ATIM_NOW == FSTFLAGS_ATIM_NOW, - )?; - let mtim = systimespec( - fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM, - mtim, - fst_flags & FSTFLAGS_MTIM_NOW == FSTFLAGS_MTIM_NOW, - )?; + State::with(|state| { + let atim = systimespec( + fst_flags & FSTFLAGS_ATIM == FSTFLAGS_ATIM, + atim, + fst_flags & FSTFLAGS_ATIM_NOW == FSTFLAGS_ATIM_NOW, + )?; + let mtim = systimespec( + fst_flags & FSTFLAGS_MTIM == FSTFLAGS_MTIM, + mtim, + fst_flags & FSTFLAGS_MTIM_NOW == FSTFLAGS_MTIM_NOW, + )?; - let ds = state.descriptors(); - let file = ds.get_dir(fd)?; - file.fd.set_times_at(at_flags, path, atim, mtim)?; - Ok(()) - }) + let ds = state.descriptors(); + let file = ds.get_dir(fd)?; + file.fd.set_times_at(at_flags, path, atim, mtim)?; + Ok(()) + }) + } } /// Create a hard link. @@ -1418,17 +1554,19 @@ pub unsafe extern "C" fn path_link( new_path_ptr: *const u8, new_path_len: usize, ) -> Errno { - let old_path = slice::from_raw_parts(old_path_ptr, old_path_len); - let new_path = slice::from_raw_parts(new_path_ptr, new_path_len); - let at_flags = at_flags_from_lookupflags(old_flags); + cfg_filesystem_available! { + let old_path = slice::from_raw_parts(old_path_ptr, old_path_len); + let new_path = slice::from_raw_parts(new_path_ptr, new_path_len); + let at_flags = at_flags_from_lookupflags(old_flags); - State::with(|state| { - let ds = state.descriptors(); - let old = &ds.get_dir(old_fd)?.fd; - let new = &ds.get_dir(new_fd)?.fd; - old.link_at(at_flags, old_path, new, new_path)?; - Ok(()) - }) + State::with(|state| { + let ds = state.descriptors(); + let old = &ds.get_dir(old_fd)?.fd; + let new = &ds.get_dir(new_fd)?.fd; + old.link_at(at_flags, old_path, new, new_path)?; + Ok(()) + }) + } } /// Open a file or directory. @@ -1450,41 +1588,47 @@ pub unsafe extern "C" fn path_open( fdflags: Fdflags, opened_fd: *mut Fd, ) -> Errno { - let _ = fs_rights_inheriting; + cfg_filesystem_available! { + let _ = fs_rights_inheriting; - let path = slice::from_raw_parts(path_ptr, path_len); - let at_flags = at_flags_from_lookupflags(dirflags); - let o_flags = o_flags_from_oflags(oflags); - let flags = descriptor_flags_from_flags(fs_rights_base, fdflags); - let append = fdflags & wasi::FDFLAGS_APPEND == wasi::FDFLAGS_APPEND; + let path = slice::from_raw_parts(path_ptr, path_len); + let at_flags = at_flags_from_lookupflags(dirflags); + let o_flags = o_flags_from_oflags(oflags); + let flags = descriptor_flags_from_flags(fs_rights_base, fdflags); + let append = fdflags & wasi::FDFLAGS_APPEND == wasi::FDFLAGS_APPEND; - State::with(|state| { - let result = state - .descriptors() - .get_dir(fd)? - .fd - .open_at(at_flags, path, o_flags, flags)?; - let descriptor_type = result.get_type()?; - let desc = Descriptor::Streams(Streams { - input: OnceCell::new(), - output: OnceCell::new(), - type_: StreamType::File(File { - fd: result, - descriptor_type, - position: Cell::new(0), - append, - blocking_mode: if fdflags & wasi::FDFLAGS_NONBLOCK == 0 { - BlockingMode::Blocking - } else { - BlockingMode::NonBlocking - }, - }), - }); + #[cfg(feature = "proxy")] + return wasi::ERRNO_NOTSUP; - let fd = state.descriptors_mut().open(desc)?; - *opened_fd = fd; - Ok(()) - }) + #[cfg(not(feature = "proxy"))] + State::with(|state| { + let result = state + .descriptors() + .get_dir(fd)? + .fd + .open_at(at_flags, path, o_flags, flags)?; + let descriptor_type = result.get_type()?; + let desc = Descriptor::Streams(Streams { + input: OnceCell::new(), + output: OnceCell::new(), + type_: StreamType::File(File { + fd: result, + descriptor_type, + position: Cell::new(0), + append, + blocking_mode: if fdflags & wasi::FDFLAGS_NONBLOCK == 0 { + BlockingMode::Blocking + } else { + BlockingMode::NonBlocking + }, + }), + }); + + let fd = state.descriptors_mut().open(desc)?; + *opened_fd = fd; + Ok(()) + }) + } } /// Read the contents of a symbolic link. @@ -1498,44 +1642,46 @@ pub unsafe extern "C" fn path_readlink( buf_len: Size, bufused: *mut Size, ) -> Errno { - let path = slice::from_raw_parts(path_ptr, path_len); + cfg_filesystem_available! { + let path = slice::from_raw_parts(path_ptr, path_len); - State::with(|state| { - // If the user gave us a buffer shorter than `PATH_MAX`, it may not be - // long enough to accept the actual path. `cabi_realloc` can't fail, - // so instead we handle this case specially. - let use_state_buf = buf_len < PATH_MAX; + State::with(|state| { + // If the user gave us a buffer shorter than `PATH_MAX`, it may not be + // long enough to accept the actual path. `cabi_realloc` can't fail, + // so instead we handle this case specially. + let use_state_buf = buf_len < PATH_MAX; - let ds = state.descriptors(); - let file = ds.get_dir(fd)?; - let path = if use_state_buf { - state - .import_alloc - .with_buffer(state.path_buf.get().cast(), PATH_MAX, || { - file.fd.readlink_at(path) - })? - } else { - state - .import_alloc - .with_buffer(buf, buf_len, || file.fd.readlink_at(path))? - }; + let ds = state.descriptors(); + let file = ds.get_dir(fd)?; + let path = if use_state_buf { + state + .import_alloc + .with_buffer(state.path_buf.get().cast(), PATH_MAX, || { + file.fd.readlink_at(path) + })? + } else { + state + .import_alloc + .with_buffer(buf, buf_len, || file.fd.readlink_at(path))? + }; - if use_state_buf { - // Preview1 follows POSIX in truncating the returned path if it - // doesn't fit. - let len = min(path.len(), buf_len); - ptr::copy_nonoverlapping(path.as_ptr().cast(), buf, len); - *bufused = len; - } else { - *bufused = path.len(); - } + if use_state_buf { + // Preview1 follows POSIX in truncating the returned path if it + // doesn't fit. + let len = min(path.len(), buf_len); + ptr::copy_nonoverlapping(path.as_ptr().cast(), buf, len); + *bufused = len; + } else { + *bufused = path.len(); + } - // The returned string's memory was allocated in `buf`, so don't separately - // free it. - forget(path); + // The returned string's memory was allocated in `buf`, so don't separately + // free it. + forget(path); - Ok(()) - }) + Ok(()) + }) + } } /// Remove a directory. @@ -1547,14 +1693,16 @@ pub unsafe extern "C" fn path_remove_directory( path_ptr: *const u8, path_len: usize, ) -> Errno { - let path = slice::from_raw_parts(path_ptr, path_len); + cfg_filesystem_available! { + let path = slice::from_raw_parts(path_ptr, path_len); - State::with(|state| { - let ds = state.descriptors(); - let file = ds.get_dir(fd)?; - file.fd.remove_directory_at(path)?; - Ok(()) - }) + State::with(|state| { + let ds = state.descriptors(); + let file = ds.get_dir(fd)?; + file.fd.remove_directory_at(path)?; + Ok(()) + }) + } } /// Rename a file or directory. @@ -1568,16 +1716,18 @@ pub unsafe extern "C" fn path_rename( new_path_ptr: *const u8, new_path_len: usize, ) -> Errno { - let old_path = slice::from_raw_parts(old_path_ptr, old_path_len); - let new_path = slice::from_raw_parts(new_path_ptr, new_path_len); + cfg_filesystem_available! { + let old_path = slice::from_raw_parts(old_path_ptr, old_path_len); + let new_path = slice::from_raw_parts(new_path_ptr, new_path_len); - State::with(|state| { - let ds = state.descriptors(); - let old = &ds.get_dir(old_fd)?.fd; - let new = &ds.get_dir(new_fd)?.fd; - old.rename_at(old_path, new, new_path)?; - Ok(()) - }) + State::with(|state| { + let ds = state.descriptors(); + let old = &ds.get_dir(old_fd)?.fd; + let new = &ds.get_dir(new_fd)?.fd; + old.rename_at(old_path, new, new_path)?; + Ok(()) + }) + } } /// Create a symbolic link. @@ -1590,15 +1740,17 @@ pub unsafe extern "C" fn path_symlink( new_path_ptr: *const u8, new_path_len: usize, ) -> Errno { - let old_path = slice::from_raw_parts(old_path_ptr, old_path_len); - let new_path = slice::from_raw_parts(new_path_ptr, new_path_len); + cfg_filesystem_available! { + let old_path = slice::from_raw_parts(old_path_ptr, old_path_len); + let new_path = slice::from_raw_parts(new_path_ptr, new_path_len); - State::with(|state| { - let ds = state.descriptors(); - let file = ds.get_dir(fd)?; - file.fd.symlink_at(old_path, new_path)?; - Ok(()) - }) + State::with(|state| { + let ds = state.descriptors(); + let file = ds.get_dir(fd)?; + file.fd.symlink_at(old_path, new_path)?; + Ok(()) + }) + } } /// Unlink a file. @@ -1606,14 +1758,16 @@ pub unsafe extern "C" fn path_symlink( /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. #[no_mangle] pub unsafe extern "C" fn path_unlink_file(fd: Fd, path_ptr: *const u8, path_len: usize) -> Errno { - let path = slice::from_raw_parts(path_ptr, path_len); + cfg_filesystem_available! { + let path = slice::from_raw_parts(path_ptr, path_len); - State::with(|state| { - let ds = state.descriptors(); - let file = ds.get_dir(fd)?; - file.fd.unlink_file_at(path)?; - Ok(()) - }) + State::with(|state| { + let ds = state.descriptors(); + let file = ds.get_dir(fd)?; + file.fd.unlink_file_at(path)?; + Ok(()) + }) + } } struct Pollables { @@ -1817,6 +1971,7 @@ pub unsafe extern "C" fn poll_oneoff( .trapping_unwrap(); match desc { Descriptor::Streams(streams) => match &streams.type_ { + #[cfg(not(feature = "proxy"))] StreamType::File(file) => match file.fd.stat() { Ok(stat) => { let nbytes = stat.size.saturating_sub(file.position.get()); @@ -1845,7 +2000,9 @@ pub unsafe extern "C" fn poll_oneoff( .trapping_unwrap(); match desc { Descriptor::Streams(streams) => match &streams.type_ { - StreamType::File(_) | StreamType::Stdio(_) => (ERRNO_SUCCESS, 1, 0), + #[cfg(not(feature = "proxy"))] + StreamType::File(_) => (ERRNO_SUCCESS, 1, 0), + StreamType::Stdio(_) => (ERRNO_SUCCESS, 1, 0), }, _ => unreachable!(), } @@ -1875,9 +2032,16 @@ pub unsafe extern "C" fn poll_oneoff( /// the environment. #[no_mangle] pub unsafe extern "C" fn proc_exit(rval: Exitcode) -> ! { - let status = if rval == 0 { Ok(()) } else { Err(()) }; - exit::exit(status); // does not return - unreachable!("host exit implementation didn't exit!") // actually unreachable + #[cfg(feature = "proxy")] + { + unreachable!("no other implementation available in proxy world"); + } + #[cfg(not(feature = "proxy"))] + { + let status = if rval == 0 { Ok(()) } else { Err(()) }; + crate::bindings::wasi::cli::exit::exit(status); // does not return + unreachable!("host exit implementation didn't exit!") // actually unreachable + } } /// Send a signal to the process of the calling thread. @@ -1969,6 +2133,7 @@ pub unsafe extern "C" fn sock_shutdown(_fd: Fd, _how: Sdflags) -> Errno { unreachable!() } +#[cfg(not(feature = "proxy"))] fn datetime_to_timestamp(datetime: Option) -> Timestamp { match datetime { Some(datetime) => u64::from(datetime.nanoseconds) @@ -1977,6 +2142,7 @@ fn datetime_to_timestamp(datetime: Option) -> Timestamp { } } +#[cfg(not(feature = "proxy"))] fn at_flags_from_lookupflags(flags: Lookupflags) -> filesystem::PathFlags { if flags & LOOKUPFLAGS_SYMLINK_FOLLOW == LOOKUPFLAGS_SYMLINK_FOLLOW { filesystem::PathFlags::SYMLINK_FOLLOW @@ -1985,6 +2151,7 @@ fn at_flags_from_lookupflags(flags: Lookupflags) -> filesystem::PathFlags { } } +#[cfg(not(feature = "proxy"))] fn o_flags_from_oflags(flags: Oflags) -> filesystem::OpenFlags { let mut o_flags = filesystem::OpenFlags::empty(); if flags & OFLAGS_CREAT == OFLAGS_CREAT { @@ -2002,6 +2169,7 @@ fn o_flags_from_oflags(flags: Oflags) -> filesystem::OpenFlags { o_flags } +#[cfg(not(feature = "proxy"))] fn descriptor_flags_from_flags(rights: Rights, fdflags: Fdflags) -> filesystem::DescriptorFlags { let mut flags = filesystem::DescriptorFlags::empty(); if rights & wasi::RIGHTS_FD_READ == wasi::RIGHTS_FD_READ { @@ -2022,6 +2190,7 @@ fn descriptor_flags_from_flags(rights: Rights, fdflags: Fdflags) -> filesystem:: flags } +#[cfg(not(feature = "proxy"))] impl From for Errno { #[inline(never)] // Disable inlining as this is bulky and relatively cold. fn from(err: filesystem::ErrorCode) -> Errno { @@ -2069,6 +2238,7 @@ impl From for Errno { } } +#[cfg(not(feature = "proxy"))] impl From for wasi::Filetype { fn from(ty: filesystem::DescriptorType) -> wasi::Filetype { match ty { @@ -2167,6 +2337,7 @@ impl BlockingMode { } #[repr(C)] +#[cfg(not(feature = "proxy"))] pub struct File { /// The handle to the preview2 descriptor that this file is referencing. fd: filesystem::Descriptor, @@ -2186,6 +2357,7 @@ pub struct File { blocking_mode: BlockingMode, } +#[cfg(not(feature = "proxy"))] impl File { fn is_dir(&self) -> bool { match self.descriptor_type { @@ -2225,6 +2397,7 @@ struct State { descriptors: RefCell>, /// Auxiliary storage to handle the `path_readlink` function. + #[cfg(not(feature = "proxy"))] path_buf: UnsafeCell>, /// Long-lived bump allocated memory arena. @@ -2237,17 +2410,21 @@ struct State { /// Arguments. Initialized lazily. Access with `State::get_args` to take care of /// initialization. + #[cfg(not(feature = "proxy"))] args: Cell>, /// Environment variables. Initialized lazily. Access with `State::get_environment` /// to take care of initialization. + #[cfg(not(feature = "proxy"))] env_vars: Cell>, /// Cache for the `fd_readdir` call for a final `wasi::Dirent` plus path /// name that didn't fit into the caller's buffer. + #[cfg(not(feature = "proxy"))] dirent_cache: DirentCache, /// The string `..` for use by the directory iterator. + #[cfg(not(feature = "proxy"))] dotdot: [UnsafeCell; 2], /// Another canary constant located at the end of the structure to catch @@ -2255,6 +2432,7 @@ struct State { magic2: u32, } +#[cfg(not(feature = "proxy"))] struct DirentCache { stream: Cell>, for_fd: Cell, @@ -2263,6 +2441,7 @@ struct DirentCache { path_data: UnsafeCell>, } +#[cfg(not(feature = "proxy"))] struct DirectoryEntryStream(filesystem::DirectoryEntryStream); #[repr(C)] @@ -2301,14 +2480,17 @@ const fn bump_arena_size() -> usize { // The total size of the struct should be a page, so start there let mut start = PAGE_SIZE; - // Remove the big chunks of the struct, the `path_buf` and `descriptors` - // fields. - start -= PATH_MAX; + // Remove big chunks of the struct for its various fields. start -= size_of::(); - start -= size_of::(); + #[cfg(not(feature = "proxy"))] + { + start -= PATH_MAX; + start -= size_of::(); + } // Remove miscellaneous metadata also stored in state. - start -= 14 * size_of::(); + let misc = if cfg!(feature = "proxy") { 7 } else { 14 }; + start -= misc * size_of::(); // Everything else is the `command_data` allocation. start @@ -2407,10 +2589,14 @@ impl State { magic2: MAGIC, import_alloc: ImportAlloc::new(), descriptors: RefCell::new(None), + #[cfg(not(feature = "proxy"))] path_buf: UnsafeCell::new(MaybeUninit::uninit()), long_lived_arena: BumpArena::new(), + #[cfg(not(feature = "proxy"))] args: Cell::new(None), + #[cfg(not(feature = "proxy"))] env_vars: Cell::new(None), + #[cfg(not(feature = "proxy"))] dirent_cache: DirentCache { stream: Cell::new(None), for_fd: Cell::new(0), @@ -2423,6 +2609,7 @@ impl State { }), path_data: UnsafeCell::new(MaybeUninit::uninit()), }, + #[cfg(not(feature = "proxy"))] dotdot: [UnsafeCell::new(b'.'), UnsafeCell::new(b'.')], }); } @@ -2451,6 +2638,7 @@ impl State { RefMut::map(d, |d| d.as_mut().unwrap_or_else(|| unreachable!())) } + #[cfg(not(feature = "proxy"))] fn get_environment(&self) -> &[StrTuple] { if self.env_vars.get().is_none() { #[link(wasm_import_module = "wasi:cli/environment@0.2.0-rc-2023-11-10")] @@ -2475,6 +2663,7 @@ impl State { self.env_vars.get().trapping_unwrap() } + #[cfg(not(feature = "proxy"))] fn get_args(&self) -> &[WasmStr] { if self.args.get().is_none() { #[link(wasm_import_module = "wasi:cli/environment@0.2.0-rc-2023-11-10")] diff --git a/src/commands/serve.rs b/src/commands/serve.rs index d3b8c9e19f01..629ec8b57d6d 100644 --- a/src/commands/serve.rs +++ b/src/commands/serve.rs @@ -202,19 +202,41 @@ impl ServeCommand { } fn add_to_linker(&self, linker: &mut Linker) -> Result<()> { - // wasi-http and the component model are implicitly enabled for `wasmtime serve`, so we - // don't test for `self.run.common.wasi.common` or `self.run.common.wasi.http` in this - // function. - - wasmtime_wasi_http::proxy::add_to_linker(linker)?; + // Repurpose the `-Scommon` flag of `wasmtime run` for `wasmtime serve` + // to serve as a signal to enable all WASI interfaces instead of just + // those in the `proxy` world. If `-Scommon` is present then add all + // `command` APIs which includes all "common" or base WASI APIs and then + // additionally add in the required HTTP APIs. + // + // If `-Scommon` isn't passed then use the `proxy::add_to_linker` + // bindings which adds just those interfaces that the proxy interface + // uses. + if self.run.common.wasi.common == Some(true) { + preview2::command::add_to_linker(linker)?; + wasmtime_wasi_http::proxy::add_only_http_to_linker(linker)?; + } else { + wasmtime_wasi_http::proxy::add_to_linker(linker)?; + } if self.run.common.wasi.nn == Some(true) { + #[cfg(not(feature = "wasi-nn"))] + { + bail!("support for wasi-nn was disabled at compile time"); + } #[cfg(feature = "wasi-nn")] { wasmtime_wasi_nn::wit::ML::add_to_linker(linker, |host| host.nn.as_mut().unwrap())?; } } + if self.run.common.wasi.threads == Some(true) { + bail!("support for wasi-threads is not available with components"); + } + + if self.run.common.wasi.http == Some(false) { + bail!("support for wasi-http must be enabled for `serve` subcommand"); + } + Ok(()) }