//! Sealed WASI: Smith's own synchronous implementations of the WASI imports //! that guest std runtimes pull in (`SMH-SPEC-WASM0001`, no ambient WASI). //! //! Sealed by default: empty environment and arguments, empty stdin, stdout //! and stderr writes discarded, no terminals, `exit` traps, the monotonic //! clock reads a host-injected value, and every pollable is ready. //! //! No host state backs a stream or pollable, so resources are bare handles //! with no table entry; dropping one is a no-op. use super::PluginHostState; use super::bindings::wasi::cli::{ environment, exit, stderr, stdin, stdout, terminal_input, terminal_output, terminal_stderr, terminal_stdin, terminal_stdout, }; use super::bindings::wasi::clocks::monotonic_clock; use super::bindings::wasi::io::error::{self, Error}; use super::bindings::wasi::io::poll::{self, Pollable}; use super::bindings::wasi::io::streams::{self, InputStream, OutputStream, StreamError}; use wasmtime::component::Resource; /// The only handle value ever minted; no handle carries host state. const SEALED: u32 = 0; /// Bytes a sealed output stream accepts per write; writes are discarded. const WRITE_BUDGET: u64 = 4096; fn sealed() -> Resource { Resource::new_own(SEALED) } impl poll::HostPollable for PluginHostState { fn ready(&mut self, _pollable: Resource) -> bool { true } fn block(&mut self, _pollable: Resource) {} fn drop(&mut self, _pollable: Resource) -> wasmtime::Result<()> { Ok(()) } } impl poll::Host for PluginHostState { fn poll(&mut self, pollables: Vec>) -> Vec { (0..u32::try_from(pollables.len()).unwrap_or(u32::MAX)).collect() } } impl error::HostError for PluginHostState { fn to_debug_string(&mut self, _error: Resource) -> String { String::new() } fn drop(&mut self, _error: Resource) -> wasmtime::Result<()> { Ok(()) } } impl error::Host for PluginHostState {} impl streams::HostInputStream for PluginHostState { fn read(&mut self, _stream: Resource, _len: u64) -> Result, StreamError> { Err(StreamError::Closed) } fn blocking_read( &mut self, _stream: Resource, _len: u64, ) -> Result, StreamError> { Err(StreamError::Closed) } fn skip(&mut self, _stream: Resource, _len: u64) -> Result { Err(StreamError::Closed) } fn blocking_skip( &mut self, _stream: Resource, _len: u64, ) -> Result { Err(StreamError::Closed) } fn subscribe(&mut self, _stream: Resource) -> Resource { sealed() } fn drop(&mut self, _stream: Resource) -> wasmtime::Result<()> { Ok(()) } } impl streams::HostOutputStream for PluginHostState { fn check_write(&mut self, _stream: Resource) -> Result { Ok(WRITE_BUDGET) } fn write( &mut self, _stream: Resource, _contents: Vec, ) -> Result<(), StreamError> { Ok(()) } fn blocking_write_and_flush( &mut self, _stream: Resource, _contents: Vec, ) -> Result<(), StreamError> { Ok(()) } fn flush(&mut self, _stream: Resource) -> Result<(), StreamError> { Ok(()) } fn blocking_flush(&mut self, _stream: Resource) -> Result<(), StreamError> { Ok(()) } fn subscribe(&mut self, _stream: Resource) -> Resource { sealed() } fn write_zeroes( &mut self, _stream: Resource, _len: u64, ) -> Result<(), StreamError> { Ok(()) } fn blocking_write_zeroes_and_flush( &mut self, _stream: Resource, _len: u64, ) -> Result<(), StreamError> { Ok(()) } fn splice( &mut self, _stream: Resource, _source: Resource, _len: u64, ) -> Result { Err(StreamError::Closed) } fn blocking_splice( &mut self, _stream: Resource, _source: Resource, _len: u64, ) -> Result { Err(StreamError::Closed) } fn drop(&mut self, _stream: Resource) -> wasmtime::Result<()> { Ok(()) } } impl streams::Host for PluginHostState {} impl monotonic_clock::Host for PluginHostState { fn now(&mut self) -> monotonic_clock::Instant { self.monotonic_now } fn resolution(&mut self) -> monotonic_clock::Duration { 1 } fn subscribe_instant(&mut self, _when: monotonic_clock::Instant) -> Resource { sealed() } fn subscribe_duration(&mut self, _when: monotonic_clock::Duration) -> Resource { sealed() } } impl environment::Host for PluginHostState { fn get_environment(&mut self) -> Vec<(String, String)> { Vec::new() } fn get_arguments(&mut self) -> Vec { Vec::new() } fn initial_cwd(&mut self) -> Option { None } } impl exit::Host for PluginHostState { fn exit(&mut self, status: Result<(), ()>) -> wasmtime::Result<()> { Err(wasmtime::format_err!("guest exited: {status:?}")) } fn exit_with_code(&mut self, status_code: u8) -> wasmtime::Result<()> { Err(wasmtime::format_err!( "guest exited with code {status_code}" )) } } impl stdin::Host for PluginHostState { fn get_stdin(&mut self) -> Resource { sealed() } } impl stdout::Host for PluginHostState { fn get_stdout(&mut self) -> Resource { sealed() } } impl stderr::Host for PluginHostState { fn get_stderr(&mut self) -> Resource { sealed() } } impl terminal_input::HostTerminalInput for PluginHostState { fn drop(&mut self, _terminal: Resource) -> wasmtime::Result<()> { Ok(()) } } impl terminal_input::Host for PluginHostState {} impl terminal_output::HostTerminalOutput for PluginHostState { fn drop( &mut self, _terminal: Resource, ) -> wasmtime::Result<()> { Ok(()) } } impl terminal_output::Host for PluginHostState {} impl terminal_stdin::Host for PluginHostState { fn get_terminal_stdin(&mut self) -> Option> { None } } impl terminal_stdout::Host for PluginHostState { fn get_terminal_stdout(&mut self) -> Option> { None } } impl terminal_stderr::Host for PluginHostState { fn get_terminal_stderr(&mut self) -> Option> { None } } #[cfg(test)] mod tests { use super::*; use crate::wasm::PluginCeilings; use poll::Host as _; use streams::{HostInputStream as _, HostOutputStream as _}; fn state() -> PluginHostState { PluginHostState { monotonic_now: 42, ceilings: PluginCeilings::default(), } } #[test] fn poll_reports_every_pollable_ready() { let mut host = state(); assert_eq!(host.poll(vec![sealed(), sealed(), sealed()]), [0, 1, 2]); assert!(host.poll(Vec::new()).is_empty()); } #[test] fn stdin_is_closed_and_stdout_discards() { let mut host = state(); assert!(matches!(host.read(sealed(), 16), Err(StreamError::Closed))); assert_eq!(host.check_write(sealed()).unwrap(), WRITE_BUDGET); assert!(host.write(sealed(), b"dropped".to_vec()).is_ok()); } #[test] fn clock_is_host_injected_and_exit_traps() { let mut host = state(); assert_eq!(monotonic_clock::Host::now(&mut host), 42); assert!(exit::Host::exit(&mut host, Ok(())).is_err()); assert!(exit::Host::exit_with_code(&mut host, 0).is_err()); } #[test] fn environment_is_empty() { let mut host = state(); assert!(environment::Host::get_environment(&mut host).is_empty()); assert!(environment::Host::get_arguments(&mut host).is_empty()); assert!(environment::Host::initial_cwd(&mut host).is_none()); } }