Repository: bytecodealliance/wasmtime
Pull request #7607 (2023-11-29 19:38:55)

Remove wasi-http "checking authority" log
The Spin framework uses `wasi-http` as a crate.  Since updating to Wasmtime 15, whenever a Spin guest makes an outbound HTTP call, it prints "checking authority (whatever)".  This PR removes that extraneous print.



Patch:
diff --git a/ci/build-wasi-preview1-component-adapter.sh b/ci/build-wasi-preview1-component-adapter.sh
index ba1380d3b..259bb6975 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/wasi-http/src/body.rs b/crates/wasi-http/src/body.rs
index 4906bcf48..92e73d420 100644
--- a/crates/wasi-http/src/body.rs
+++ b/crates/wasi-http/src/body.rs
@@ -1,4 +1,3 @@
-use crate::internal_error;
 use crate::{bindings::http::types, types::FieldMap};
 use anyhow::anyhow;
 use bytes::Bytes;
@@ -386,6 +385,11 @@ impl WrittenState {
         }
     }
 
+    /// The number of bytes that have been written so far.
+    fn written(&self) -> u64 {
+        self.written.load(std::sync::atomic::Ordering::Relaxed)
+    }
+
     /// Add `len` to the total number of bytes written. Returns `false` if the new total exceeds
     /// the number of bytes expected to be written.
     fn update(&self, len: usize) -> bool {
@@ -395,22 +399,17 @@ impl WrittenState {
             .fetch_add(len, std::sync::atomic::Ordering::Relaxed);
         old + len <= self.expected
     }
-
-    /// Return a comparison of total bytes written to the number of bytes expected to be written.
-    fn finish(self) -> std::cmp::Ordering {
-        let written = self.written.load(std::sync::atomic::Ordering::Relaxed);
-        written.cmp(&self.expected)
-    }
 }
 
 pub struct HostOutgoingBody {
     pub body_output_stream: Option<Box<dyn HostOutputStream>>,
+    context: StreamContext,
     written: Option<WrittenState>,
     finish_sender: Option<tokio::sync::oneshot::Sender<FinishMessage>>,
 }
 
 impl HostOutgoingBody {
-    pub fn new(size: Option<u64>) -> (Self, HyperOutgoingBody) {
+    pub fn new(context: StreamContext, size: Option<u64>) -> (Self, HyperOutgoingBody) {
         let written = size.map(WrittenState::new);
 
         use tokio::sync::oneshot::error::RecvError;
@@ -465,11 +464,13 @@ impl HostOutgoingBody {
         .boxed();
 
         // TODO: this capacity constant is arbitrary, and should be configurable
-        let output_stream = BodyWriteStream::new(1024 * 1024, body_sender, written.clone());
+        let output_stream =
+            BodyWriteStream::new(context, 1024 * 1024, body_sender, written.clone());
 
         (
             Self {
                 body_output_stream: Some(Box::new(output_stream)),
+                context,
                 written,
                 finish_sender: Some(finish_sender),
             },
@@ -488,17 +489,10 @@ impl HostOutgoingBody {
             .expect("outgoing-body trailer_sender consumed by a non-owning function");
 
         if let Some(w) = self.written {
-            use std::cmp::Ordering;
-            let res = w.finish();
-            if res != Ordering::Equal {
-                let msg = match res {
-                    Ordering::Less => "not enough",
-                    Ordering::Greater => "too much",
-                    Ordering::Equal => unreachable!(),
-                };
-
+            let written = w.written();
+            if written != w.expected {
                 let _ = sender.send(FinishMessage::Abort);
-                return Err(internal_error(format!("{msg} written to body stream")));
+                return Err(self.context.as_body_error(written));
             }
         }
 
@@ -528,8 +522,25 @@ impl HostOutgoingBody {
     }
 }
 
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum StreamContext {
+    Request,
+    Response,
+}
+
+impl StreamContext {
+    /// Construct an http request or response body size error.
+    pub fn as_body_error(&self, size: u64) -> types::ErrorCode {
+        match self {
+            StreamContext::Request => types::ErrorCode::HttpRequestBodySize(Some(size)),
+            StreamContext::Response => types::ErrorCode::HttpResponseBodySize(Some(size)),
+        }
+    }
+}
+
 /// Provides a [`HostOutputStream`] impl from a [`tokio::sync::mpsc::Sender`].
 struct BodyWriteStream {
+    context: StreamContext,
     writer: mpsc::Sender<Bytes>,
     write_budget: usize,
     written: Option<WrittenState>,
@@ -538,6 +549,7 @@ struct BodyWriteStream {
 impl BodyWriteStream {
     /// Create a [`BodyWriteStream`].
     fn new(
+        context: StreamContext,
         write_budget: usize,
         writer: mpsc::Sender<Bytes>,
         written: Option<WrittenState>,
@@ -545,6 +557,7 @@ impl BodyWriteStream {
         // at least one capacity is required to send a message
         assert!(writer.max_capacity() >= 1);
         BodyWriteStream {
+            context,
             writer,
             write_budget,
             written,
@@ -562,9 +575,10 @@ impl HostOutputStream for BodyWriteStream {
             Ok(()) => {
                 if let Some(written) = self.written.as_ref() {
                     if !written.update(len) {
-                        return Err(StreamError::LastOperationFailed(anyhow!(internal_error(
-                            "too much written to output stream".to_owned()
-                        ))));
+                        let total = written.written();
+                        return Err(StreamError::LastOperationFailed(anyhow!(self
+                            .context
+                            .as_body_error(total))));
                     }
                 }
 
diff --git a/crates/wasi-http/src/proxy.rs b/crates/wasi-http/src/proxy.rs
index 155a55fd1..5c6557678 100644
--- a/crates/wasi-http/src/proxy.rs
+++ b/crates/wasi-http/src/proxy.rs
@@ -25,10 +25,24 @@ pub fn add_to_linker<T>(l: &mut wasmtime::component::Linker<T>) -> 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<T>(l: &mut wasmtime::component::Linker<T>) -> 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/src/types_impl.rs b/crates/wasi-http/src/types_impl.rs
index 718a26588..4ecb27481 100644
--- a/crates/wasi-http/src/types_impl.rs
+++ b/crates/wasi-http/src/types_impl.rs
@@ -1,6 +1,6 @@
 use crate::{
     bindings::http::types::{self, Headers, Method, Scheme, StatusCode, Trailers},
-    body::{HostFutureTrailers, HostIncomingBody, HostOutgoingBody},
+    body::{HostFutureTrailers, HostIncomingBody, HostOutgoingBody, StreamContext},
     types::{
         is_forbidden_header, remove_forbidden_headers, FieldMap, HostFields,
         HostFutureIncomingResponse, HostIncomingRequest, HostIncomingResponse, HostOutgoingRequest,
@@ -384,7 +384,7 @@ impl<T: WasiHttpView> crate::bindings::http::types::HostOutgoingRequest for T {
             Err(e) => return Ok(Err(e)),
         };
 
-        let (host_body, hyper_body) = HostOutgoingBody::new(size);
+        let (host_body, hyper_body) = HostOutgoingBody::new(StreamContext::Request, size);
 
         req.body = Some(hyper_body);
 
@@ -490,7 +490,6 @@ impl<T: WasiHttpView> crate::bindings::http::types::HostOutgoingRequest for T {
         let req = self.table().get_mut(&request)?;
 
         if let Some(s) = authority.as_ref() {
-            println!("checking authority {s}");
             let auth = match http::uri::Authority::from_str(s.as_str()) {
                 Ok(auth) => auth,
                 Err(_) => return Ok(Err(())),
@@ -727,7 +726,7 @@ impl<T: WasiHttpView> crate::bindings::http::types::HostOutgoingResponse for T {
             Err(e) => return Ok(Err(e)),
         };
 
-        let (host, body) = HostOutgoingBody::new(size);
+        let (host, body) = HostOutgoingBody::new(StreamContext::Response, size);
 
         resp.body.replace(body);
 
diff --git a/crates/wasi-preview1-component-adapter/Cargo.toml b/crates/wasi-preview1-component-adapter/Cargo.toml
index a43df3097..cae715a7d 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 c3c01c7a7..0c9cab019 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<Option<&'static [Preopen]>>,
 }
 
@@ -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<Fd, Errno> {
@@ -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 09a56510a..22fcb171c 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<filesystem::NewTimestamp, Errno> {
     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<filesystem::Datetime>) -> Timestamp {
     match datetime {
         Some(datetime) => u64::from(datetime.nanoseconds)
@@ -1977,6 +2142,7 @@ fn datetime_to_timestamp(datetime: Option<filesystem::Datetime>) -> 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<filesystem::ErrorCode> 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<filesystem::ErrorCode> for Errno {
     }
 }
 
+#[cfg(not(feature = "proxy"))]
 impl From<filesystem::DescriptorType> 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<Option<Descriptors>>,
 
     /// Auxiliary storage to handle the `path_readlink` function.
+    #[cfg(not(feature = "proxy"))]
     path_buf: UnsafeCell<MaybeUninit<[u8; PATH_MAX]>>,
 
     /// 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<Option<&'static [WasmStr]>>,
 
     /// Environment variables. Initialized lazily. Access with `State::get_environment`
     /// to take care of initialization.
+    #[cfg(not(feature = "proxy"))]
     env_vars: Cell<Option<&'static [StrTuple]>>,
 
     /// 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<u8>; 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<Option<DirectoryEntryStream>>,
     for_fd: Cell<wasi::Fd>,
@@ -2263,6 +2441,7 @@ struct DirentCache {
     path_data: UnsafeCell<MaybeUninit<[u8; DIRENT_CACHE]>>,
 }
 
+#[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::<Descriptors>();
-    start -= size_of::<DirentCache>();
+    #[cfg(not(feature = "proxy"))]
+    {
+        start -= PATH_MAX;
+        start -= size_of::<DirentCache>();
+    }
 
     // Remove miscellaneous metadata also stored in state.
-    start -= 14 * size_of::<usize>();
+    let misc = if cfg!(feature = "proxy") { 7 } else { 14 };
+    start -= misc * size_of::<usize>();
 
     // 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/crates/wasi/src/preview2/host/network.rs b/crates/wasi/src/preview2/host/network.rs
index ad8d7ed7b..ddb4b7d63 100644
--- a/crates/wasi/src/preview2/host/network.rs
+++ b/crates/wasi/src/preview2/host/network.rs
@@ -306,8 +306,10 @@ pub(crate) mod util {
         binder
             .bind_existing_tcp_listener(listener)
             .map_err(|error| match Errno::from_io_error(&error) {
+                // See: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-bind#:~:text=WSAENOBUFS
+                // Windows returns WSAENOBUFS when the ephemeral ports have been exhausted.
                 #[cfg(windows)]
-                Some(Errno::NOBUFS) => Errno::ADDRINUSE.into(), // Windows returns WSAENOBUFS when the ephemeral ports have been exhausted.
+                Some(Errno::NOBUFS) => Errno::ADDRINUSE.into(),
                 _ => error,
             })
     }
@@ -315,8 +317,10 @@ pub(crate) mod util {
     pub fn udp_bind(socket: &UdpSocket, binder: &UdpBinder) -> std::io::Result<()> {
         binder.bind_existing_udp_socket(socket).map_err(|error| {
             match Errno::from_io_error(&error) {
+                // See: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-bind#:~:text=WSAENOBUFS
+                // Windows returns WSAENOBUFS when the ephemeral ports have been exhausted.
                 #[cfg(windows)]
-                Some(Errno::NOBUFS) => Errno::ADDRINUSE.into(), // Windows returns WSAENOBUFS when the ephemeral ports have been exhausted.
+                Some(Errno::NOBUFS) => Errno::ADDRINUSE.into(),
                 _ => error,
             }
         })
@@ -325,7 +329,10 @@ pub(crate) mod util {
     pub fn tcp_connect(listener: &TcpListener, connecter: &TcpConnecter) -> std::io::Result<()> {
         connecter.connect_existing_tcp_listener(listener).map_err(
             |error| match Errno::from_io_error(&error) {
-                // On POSIX, non-blocking `connect` returns `EINPROGRESS`. Windows returns `WSAEWOULDBLOCK`.
+                // On POSIX, non-blocking `connect` returns `EINPROGRESS`.
+                // Windows returns `WSAEWOULDBLOCK`.
+                //
+                // This normalized error code is depended upon by: tcp.rs
                 #[cfg(windows)]
                 Some(Errno::WOULDBLOCK) => Errno::INPROGRESS.into(),
                 _ => error,
@@ -333,6 +340,25 @@ pub(crate) mod util {
         )
     }
 
+    pub fn tcp_listen(listener: &TcpListener, backlog: Option<i32>) -> std::io::Result<()> {
+        listener
+            .listen(backlog)
+            .map_err(|error| match Errno::from_io_error(&error) {
+                // See: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-listen#:~:text=WSAEMFILE
+                // According to the docs, `listen` can return EMFILE on Windows.
+                // This is odd, because we're not trying to create a new socket
+                // or file descriptor of any kind. So we rewrite it to less
+                // surprising error code.
+                //
+                // At the time of writing, this behavior has never been experimentally
+                // observed by any of the wasmtime authors, so we're relying fully
+                // on Microsoft's documentation here.
+                #[cfg(windows)]
+                Some(Errno::MFILE) => Errno::NOBUFS.into(),
+                _ => error,
+            })
+    }
+
     pub fn tcp_accept(
         listener: &TcpListener,
         blocking: Blocking,
@@ -340,11 +366,22 @@ pub(crate) mod util {
         listener
             .accept_with(blocking)
             .map_err(|error| match Errno::from_io_error(&error) {
+                // From: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-accept#:~:text=WSAEINPROGRESS
+                // > WSAEINPROGRESS: A blocking Windows Sockets 1.1 call is in progress,
+                // > or the service provider is still processing a callback function.
+                //
+                // wasi-sockets doesn't have an equivalent to the EINPROGRESS error,
+                // because in POSIX this error is only returned by a non-blocking
+                // `connect` and wasi-sockets has a different solution for that.
                 #[cfg(windows)]
-                Some(Errno::INPROGRESS) => Errno::INTR.into(), // "A blocking Windows Sockets 1.1 call is in progress, or the service provider is still processing a callback function."
+                Some(Errno::INPROGRESS) => Errno::INTR.into(),
 
                 // Normalize Linux' non-standard behavior.
-                // "Linux accept() passes already-pending network errors on the new socket as an error code from accept(). This behavior differs from other BSD socket implementations."
+                //
+                // From https://man7.org/linux/man-pages/man2/accept.2.html:
+                // > Linux accept() passes already-pending network errors on the
+                // > new socket as an error code from accept(). This behavior
+                // > differs from other BSD socket implementations. (...)
                 #[cfg(target_os = "linux")]
                 Some(
                     Errno::CONNRESET
@@ -366,6 +403,16 @@ pub(crate) mod util {
     pub fn udp_disconnect<Fd: AsFd>(sockfd: Fd) -> rustix::io::Result<()> {
         match rustix::net::connect_unspec(sockfd) {
             // BSD platforms return an error even if the UDP socket was disconnected successfully.
+            //
+            // MacOS was kind enough to document this: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/connect.2.html
+            // > Datagram sockets may dissolve the association by connecting to an
+            // > invalid address, such as a null address or an address with the address
+            // > family set to AF_UNSPEC (the error EAFNOSUPPORT will be harmlessly
+            // > returned).
+            //
+            // ... except that this appears to be incomplete, because experiments
+            // have shown that MacOS actually returns EINVAL, depending on the
+            // address family of the socket.
             #[cfg(target_os = "macos")]
             Err(Errno::INVAL | Errno::AFNOSUPPORT) => Ok(()),
             r => r,
@@ -374,6 +421,7 @@ pub(crate) mod util {
 
     pub fn set_tcp_keepidle<Fd: AsFd>(sockfd: Fd, value: Duration) -> rustix::io::Result<()> {
         if value <= Duration::ZERO {
+            // WIT: "If the provided value is 0, an `invalid-argument` error is returned."
             return Err(Errno::INVAL);
         }
 
@@ -391,6 +439,7 @@ pub(crate) mod util {
 
     pub fn set_tcp_keepintvl<Fd: AsFd>(sockfd: Fd, value: Duration) -> rustix::io::Result<()> {
         if value <= Duration::ZERO {
+            // WIT: "If the provided value is 0, an `invalid-argument` error is returned."
             return Err(Errno::INVAL);
         }
 
@@ -408,6 +457,7 @@ pub(crate) mod util {
 
     pub fn set_tcp_keepcnt<Fd: AsFd>(sockfd: Fd, value: u32) -> rustix::io::Result<()> {
         if value == 0 {
+            // WIT: "If the provided value is 0, an `invalid-argument` error is returned."
             return Err(Errno::INVAL);
         }
 
@@ -430,6 +480,8 @@ pub(crate) mod util {
 
     pub fn set_ip_ttl<Fd: AsFd>(sockfd: Fd, value: u8) -> rustix::io::Result<()> {
         match value {
+            // WIT: "If the provided value is 0, an `invalid-argument` error is returned."
+            //
             // A well-behaved IP application should never send out new packets with TTL 0.
             // We validate the value ourselves because OS'es are not consistent in this.
             // On Linux the validation is even inconsistent between their IPv4 and IPv6 implementation.
@@ -440,10 +492,7 @@ pub(crate) mod util {
 
     pub fn set_ipv6_unicast_hops<Fd: AsFd>(sockfd: Fd, value: u8) -> rustix::io::Result<()> {
         match value {
-            // A well-behaved IP application should never send out new packets with TTL 0.
-            // We validate the value ourselves because OS'es are not consistent in this.
-            // On Linux the validation is even inconsistent between their IPv4 and IPv6 implementation.
-            0 => Err(Errno::INVAL),
+            0 => Err(Errno::INVAL), // See `set_ip_ttl`
             _ => sockopt::set_ipv6_unicast_hops(sockfd, Some(value)),
         }
     }
@@ -453,6 +502,8 @@ pub(crate) mod util {
             // Linux doubles the value passed to setsockopt to allow space for bookkeeping overhead.
             // getsockopt returns this internally doubled value.
             // We'll half the value to at least get it back into the same ballpark that the application requested it in.
+            //
+            // This normalized behavior is tested for in: test-programs/src/bin/preview2_tcp_sockopts.rs
             value / 2
         } else {
             value
@@ -478,6 +529,7 @@ pub(crate) mod util {
         value: usize,
     ) -> rustix::io::Result<()> {
         if value == 0 {
+            // WIT: "If the provided value is 0, an `invalid-argument` error is returned."
             return Err(Errno::INVAL);
         }
 
@@ -492,6 +544,8 @@ pub(crate) mod util {
             // "performance hint" semantics. In other words; even ENOBUFS is "Ok".
             // A future improvement could be to query the corresponding sysctl on *BSD platforms and clamp the input
             // `size` ourselves, to completely close the gap with other platforms.
+            //
+            // This normalized behavior is tested for in: test-programs/src/bin/preview2_tcp_sockopts.rs
             Err(Errno::NOBUFS) => Ok(()),
             r => r,
         }
@@ -502,6 +556,7 @@ pub(crate) mod util {
         value: usize,
     ) -> rustix::io::Result<()> {
         if value == 0 {
+            // WIT: "If the provided value is 0, an `invalid-argument` error is returned."
             return Err(Errno::INVAL);
         }
 
diff --git a/crates/wasi/src/preview2/host/tcp.rs b/crates/wasi/src/preview2/host/tcp.rs
index 1c805467b..0f977133f 100644
--- a/crates/wasi/src/preview2/host/tcp.rs
+++ b/crates/wasi/src/preview2/host/tcp.rs
@@ -9,7 +9,7 @@ use crate::preview2::{
     network::SocketAddressFamily,
 };
 use crate::preview2::{Pollable, SocketResult, WasiView};
-use cap_net_ext::{Blocking, PoolExt, TcpListenerExt};
+use cap_net_ext::{Blocking, PoolExt};
 use cap_std::net::TcpListener;
 use io_lifetimes::AsSocketlike;
 use rustix::io::Errno;
@@ -49,7 +49,14 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
             // Perform the OS bind call.
             util::tcp_bind(listener, &binder).map_err(|error| {
                 match Errno::from_io_error(&error) {
-                    Some(Errno::AFNOSUPPORT) => ErrorCode::InvalidArgument, // Just in case our own validations weren't sufficient.
+                    // From https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html:
+                    // > [EAFNOSUPPORT] The specified address is not a valid address for the address family of the specified socket
+                    //
+                    // The most common reasons for this error should have already
+                    // been handled by our own validation slightly higher up in this
+                    // function. This error mapping is here just in case there is
+                    // an edge case we didn't catch.
+                    Some(Errno::AFNOSUPPORT) => ErrorCode::InvalidArgument,
                     _ => ErrorCode::from(error),
                 }
             })?;
@@ -122,7 +129,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
             // or fail immediately.
             Err(err) => {
                 return Err(match Errno::from_io_error(&err) {
-                    Some(Errno::AFNOSUPPORT) => ErrorCode::InvalidArgument.into(), // Just in case our own validations weren't sufficient.
+                    Some(Errno::AFNOSUPPORT) => ErrorCode::InvalidArgument.into(), // See `bind` implementation.
                     _ => err.into(),
                 });
             }
@@ -194,15 +201,10 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
             | TcpState::BindStarted => return Err(ErrorCode::ConcurrencyConflict.into()),
         }
 
-        socket
-            .tcp_socket()
-            .as_socketlike_view::<TcpListener>()
-            .listen(socket.listen_backlog_size)
-            .map_err(|error| match Errno::from_io_error(&error) {
-                #[cfg(windows)]
-                Some(Errno::MFILE) => ErrorCode::OutOfMemory, // We're not trying to create a new socket. Rewrite it to less surprising error code.
-                _ => ErrorCode::from(error),
-            })?;
+        {
+            let listener = &*socket.tcp_socket().as_socketlike_view::<TcpListener>();
+            util::tcp_listen(listener, socket.listen_backlog_size)?;
+        }
 
         socket.tcp_state = TcpState::ListenStarted;
 
diff --git a/crates/wasi/src/preview2/host/udp.rs b/crates/wasi/src/preview2/host/udp.rs
index 52a796f33..9c6d20890 100644
--- a/crates/wasi/src/preview2/host/udp.rs
+++ b/crates/wasi/src/preview2/host/udp.rs
@@ -55,7 +55,14 @@ impl<T: WasiView> udp::HostUdpSocket for T {
             // Perform the OS bind call.
             util::udp_bind(udp_socket, &binder).map_err(|error| {
                 match Errno::from_io_error(&error) {
-                    Some(Errno::AFNOSUPPORT) => ErrorCode::InvalidArgument, // Just in case our own validations weren't sufficient.
+                    // From https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html:
+                    // > [EAFNOSUPPORT] The specified address is not a valid address for the address family of the specified socket
+                    //
+                    // The most common reasons for this error should have already
+                    // been handled by our own validation slightly higher up in this
+                    // function. This error mapping is here just in case there is
+                    // an edge case we didn't catch.
+                    Some(Errno::AFNOSUPPORT) => ErrorCode::InvalidArgument,
                     _ => ErrorCode::from(error),
                 }
             })?;
@@ -125,7 +132,7 @@ impl<T: WasiView> udp::HostUdpSocket for T {
 
             rustix::net::connect(socket.udp_socket(), &connect_addr).map_err(
                 |error| match error {
-                    Errno::AFNOSUPPORT => ErrorCode::InvalidArgument, // Just in case our own validations weren't sufficient.
+                    Errno::AFNOSUPPORT => ErrorCode::InvalidArgument, // See `bind` implementation.
                     Errno::INPROGRESS => {
                         log::debug!("UDP connect returned EINPROGRESS, which should never happen");
                         ErrorCode::Unknown
diff --git a/src/commands/serve.rs b/src/commands/serve.rs
index d3b8c9e19..629ec8b57 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<Host>) -> 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(())
     }
 


Test patch:
diff --git a/crates/test-programs/artifacts/build.rs b/crates/test-programs/artifacts/build.rs
index eb446fd0b..b97422d3b 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/test-programs/src/bin/http_outbound_request_content_length.rs b/crates/test-programs/src/bin/http_outbound_request_content_length.rs
index ea7d69c58..59b625431 100644
--- a/crates/test-programs/src/bin/http_outbound_request_content_length.rs
+++ b/crates/test-programs/src/bin/http_outbound_request_content_length.rs
@@ -54,11 +54,7 @@ fn main() {
             http_types::OutgoingBody::finish(outgoing_body, None).expect_err("finish should fail");
 
         assert!(
-            matches!(
-                &e,
-                http_types::ErrorCode::InternalError(Some(s))
-                  if s == "not enough written to body stream",
-            ),
+            matches!(&e, http_types::ErrorCode::HttpRequestBodySize(Some(3))),
             "unexpected error: {e:#?}"
         );
     }
@@ -75,25 +71,26 @@ fn main() {
                 .expect_err("write should fail");
 
             let e = match e {
-                test_programs::wasi::io::streams::StreamError::LastOperationFailed(e) => e,
+                test_programs::wasi::io::streams::StreamError::LastOperationFailed(e) => {
+                    http_types::http_error_code(&e)
+                }
                 test_programs::wasi::io::streams::StreamError::Closed => panic!("request closed"),
             };
 
-            assert!(matches!(
-                http_types::http_error_code(&e),
-                Some(http_types::ErrorCode::InternalError(Some(msg)))
-                  if msg == "too much written to output stream"));
+            assert!(
+                matches!(
+                    e,
+                    Some(http_types::ErrorCode::HttpRequestBodySize(Some(18)))
+                ),
+                "unexpected error {e:?}"
+            );
         }
 
         let e =
             http_types::OutgoingBody::finish(outgoing_body, None).expect_err("finish should fail");
 
         assert!(
-            matches!(
-                &e,
-                http_types::ErrorCode::InternalError(Some(s))
-                  if s == "too much written to body stream",
-            ),
+            matches!(&e, http_types::ErrorCode::HttpRequestBodySize(Some(18))),
             "unexpected error: {e:#?}"
         );
     }
diff --git a/crates/test-programs/src/sockets.rs b/crates/test-programs/src/sockets.rs
index 1d0e7f089..cea4f50ba 100644
--- a/crates/test-programs/src/sockets.rs
+++ b/crates/test-programs/src/sockets.rs
@@ -17,7 +17,7 @@ use std::ops::Range;
 const TIMEOUT_NS: u64 = 1_000_000_000;
 
 impl Pollable {
-    pub fn wait_until(&self, timeout: &Pollable) -> Result<(), ErrorCode> {
+    pub fn block_until(&self, timeout: &Pollable) -> Result<(), ErrorCode> {
         let ready = poll::poll(&[self, timeout]);
         assert!(ready.len() > 0);
         match ready[0] {
@@ -30,10 +30,11 @@ impl Pollable {
 
 impl OutputStream {
     pub fn blocking_write_util(&self, mut bytes: &[u8]) -> Result<(), StreamError> {
+        let timeout = monotonic_clock::subscribe_duration(TIMEOUT_NS);
         let pollable = self.subscribe();
 
         while !bytes.is_empty() {
-            pollable.block();
+            pollable.block_until(&timeout).expect("write timed out");
 
             let permit = self.check_write()?;
 
@@ -73,7 +74,7 @@ impl Network {
                     _ => return Ok(addresses),
                 },
                 Err(ErrorCode::WouldBlock) => {
-                    pollable.wait_until(&timeout)?;
+                    pollable.block_until(&timeout)?;
                 }
                 Err(err) => return Err(err),
             }
@@ -91,26 +92,28 @@ impl TcpSocket {
         network: &Network,
         local_address: IpSocketAddress,
     ) -> Result<(), ErrorCode> {
+        let timeout = monotonic_clock::subscribe_duration(TIMEOUT_NS);
         let sub = self.subscribe();
 
         self.start_bind(&network, local_address)?;
 
         loop {
             match self.finish_bind() {
-                Err(ErrorCode::WouldBlock) => sub.block(),
+                Err(ErrorCode::WouldBlock) => sub.block_until(&timeout)?,
                 result => return result,
             }
         }
     }
 
     pub fn blocking_listen(&self) -> Result<(), ErrorCode> {
+        let timeout = monotonic_clock::subscribe_duration(TIMEOUT_NS);
         let sub = self.subscribe();
 
         self.start_listen()?;
 
         loop {
             match self.finish_listen() {
-                Err(ErrorCode::WouldBlock) => sub.block(),
+                Err(ErrorCode::WouldBlock) => sub.block_until(&timeout)?,
                 result => return result,
             }
         }
@@ -121,24 +124,26 @@ impl TcpSocket {
         network: &Network,
         remote_address: IpSocketAddress,
     ) -> Result<(InputStream, OutputStream), ErrorCode> {
+        let timeout = monotonic_clock::subscribe_duration(TIMEOUT_NS);
         let sub = self.subscribe();
 
         self.start_connect(&network, remote_address)?;
 
         loop {
             match self.finish_connect() {
-                Err(ErrorCode::WouldBlock) => sub.block(),
+                Err(ErrorCode::WouldBlock) => sub.block_until(&timeout)?,
                 result => return result,
             }
         }
     }
 
     pub fn blocking_accept(&self) -> Result<(TcpSocket, InputStream, OutputStream), ErrorCode> {
+        let timeout = monotonic_clock::subscribe_duration(TIMEOUT_NS);
         let sub = self.subscribe();
 
         loop {
             match self.accept() {
-                Err(ErrorCode::WouldBlock) => sub.block(),
+                Err(ErrorCode::WouldBlock) => sub.block_until(&timeout)?,
                 result => return result,
             }
         }
@@ -155,13 +160,14 @@ impl UdpSocket {
         network: &Network,
         local_address: IpSocketAddress,
     ) -> Result<(), ErrorCode> {
+        let timeout = monotonic_clock::subscribe_duration(TIMEOUT_NS);
         let sub = self.subscribe();
 
         self.start_bind(&network, local_address)?;
 
         loop {
             match self.finish_bind() {
-                Err(ErrorCode::WouldBlock) => sub.block(),
+                Err(ErrorCode::WouldBlock) => sub.block_until(&timeout)?,
                 result => return result,
             }
         }
@@ -181,7 +187,7 @@ impl OutgoingDatagramStream {
 
         loop {
             match self.check_send() {
-                Ok(0) => sub.wait_until(timeout)?,
+                Ok(0) => sub.block_until(timeout)?,
                 result => return result,
             }
         }
@@ -221,7 +227,7 @@ impl IncomingDatagramStream {
                     if datagrams.len() >= count.start as usize {
                         return Ok(datagrams);
                     } else {
-                        pollable.wait_until(&timeout)?;
+                        pollable.block_until(&timeout)?;
                     }
                 }
                 Err(err) => return Err(err),
diff --git a/crates/wasi-http/tests/all/async_.rs b/crates/wasi-http/tests/all/async_.rs
index e9d735279..19ce8c1ae 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"))