//! Bindings for WASIp1 aka Preview 1 aka `wasi_snapshot_preview1`. //! //! This module contains runtime support for configuring and executing //! WASIp1-using core WebAssembly modules. Support for WASIp1 is built on top of //! support for WASIp2 available at [the crate root](crate), but that's just an //! internal implementation detail. //! //! Unlike the crate root, support for WASIp1 centers around two APIs: //! //! * [`WasiP1Ctx`] //! * [`add_to_linker_sync`] (or [`add_to_linker_async`]) //! //! First a [`WasiCtxBuilder`] will be used and finalized with the [`build_p1`] //! method to create a [`WasiCtx`]. Next a [`wasmtime::Linker`] is configured //! with WASI imports by using the `add_to_linker_*` desired (sync or async //! depending on [`Config::async_support`]). //! //! Note that WASIp1 is not as extensible or configurable as WASIp2 so the //! support in this module is enough to run wasm modules but any customization //! beyond that [`WasiCtxBuilder`] already supports is not possible yet. //! //! [`WasiCtxBuilder`]: crate::WasiCtxBuilder //! [`build_p1`]: crate::WasiCtxBuilder::build_p1 //! [`Config::async_support`]: wasmtime::Config::async_support //! //! # Components vs Modules //! //! Note that WASIp1 does not work for components at this time, only core wasm //! modules. That means this module is only for users of [`wasmtime::Module`] //! and [`wasmtime::Linker`], for example. If you're using //! [`wasmtime::component::Component`] or [`wasmtime::component::Linker`] you'll //! want the WASIp2 [support this crate has](crate) instead. //! //! # Examples //! //! ```no_run //! use wasmtime::{Result, Engine, Linker, Module, Store}; //! use wasmtime_wasi::preview1::{self, WasiP1Ctx}; //! use wasmtime_wasi::WasiCtxBuilder; //! //! // An example of executing a WASIp1 "command" //! fn main() -> Result<()> { //! let args = std::env::args().skip(1).collect::>(); //! let engine = Engine::default(); //! let module = Module::from_file(&engine, &args[0])?; //! //! let mut linker: Linker = Linker::new(&engine); //! preview1::add_to_linker_async(&mut linker, |t| t)?; //! let pre = linker.instantiate_pre(&module)?; //! //! let wasi_ctx = WasiCtxBuilder::new() //! .inherit_stdio() //! .inherit_env() //! .args(&args) //! .build_p1(); //! //! let mut store = Store::new(&engine, wasi_ctx); //! let instance = pre.instantiate(&mut store)?; //! let func = instance.get_typed_func::<(), ()>(&mut store, "_start")?; //! func.call(&mut store, ())?; //! //! Ok(()) //! } //! ``` use crate::bindings::{ cli::{ stderr::Host as _, stdin::Host as _, stdout::Host as _, terminal_input, terminal_output, terminal_stderr::Host as _, terminal_stdin::Host as _, terminal_stdout::Host as _, }, clocks::{monotonic_clock, wall_clock}, filesystem::{preopens::Host as _, types as filesystem}, io::streams, }; use crate::{ FsError, IsATTY, ResourceTable, StreamError, StreamResult, WasiCtx, WasiImpl, WasiView, }; use anyhow::{bail, Context}; use std::collections::{BTreeMap, HashSet}; use std::mem::{self, size_of, size_of_val}; use std::ops::{Deref, DerefMut}; use std::slice; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; use system_interface::fs::FileIoExt; use wasmtime::component::Resource; use wiggle::tracing::instrument; use wiggle::{GuestError, GuestMemory, GuestPtr, GuestType}; // Bring all WASI traits in scope that this implementation builds on. use crate::bindings::cli::environment::Host as _; use crate::bindings::filesystem::types::HostDescriptor as _; use crate::bindings::io::poll::Host as _; use crate::bindings::random::random::Host as _; /// Structure containing state for WASIp1. /// /// This structure is created through [`WasiCtxBuilder::build_p1`] and is /// configured through the various methods of [`WasiCtxBuilder`]. This structure /// itself implements generated traits for WASIp1 as well as [`WasiView`] to /// have access to WASIp2. /// /// Instances of [`WasiP1Ctx`] are typically stored within the `T` of /// [`Store`](wasmtime::Store). /// /// [`WasiCtxBuilder::build_p1`]: crate::WasiCtxBuilder::build_p1 /// [`WasiCtxBuilder`]: crate::WasiCtxBuilder /// /// # Examples /// /// ```no_run /// use wasmtime::{Result, Linker}; /// use wasmtime_wasi::preview1::{self, WasiP1Ctx}; /// use wasmtime_wasi::WasiCtxBuilder; /// /// struct MyState { /// // ... custom state as necessary ... /// /// wasi: WasiP1Ctx, /// } /// /// impl MyState { /// fn new() -> MyState { /// MyState { /// // .. initialize custom state if needed .. /// /// wasi: WasiCtxBuilder::new() /// .arg("./foo.wasm") /// // .. more customization if necesssary .. /// .build_p1(), /// } /// } /// } /// /// fn add_to_linker(linker: &mut Linker) -> Result<()> { /// preview1::add_to_linker_sync(linker, |my_state| &mut my_state.wasi)?; /// Ok(()) /// } /// ``` pub struct WasiP1Ctx { table: ResourceTable, wasi: WasiCtx, adapter: WasiPreview1Adapter, } impl WasiP1Ctx { pub(crate) fn new(wasi: WasiCtx) -> Self { Self { table: ResourceTable::new(), wasi, adapter: WasiPreview1Adapter::new(), } } fn as_wasi_impl(&mut self) -> WasiImpl<&mut Self> { WasiImpl(self) } } impl WasiView for WasiP1Ctx { fn table(&mut self) -> &mut ResourceTable { &mut self.table } fn ctx(&mut self) -> &mut WasiCtx { &mut self.wasi } } #[derive(Debug)] struct File { /// The handle to the preview2 descriptor of type [`crate::filesystem::Descriptor::File`]. fd: Resource, /// The current-position pointer. position: Arc, /// In append mode, all writes append to the file. append: bool, /// When blocking, read and write calls dispatch to blocking_read and /// blocking_check_write on the underlying streams. When false, read and write /// dispatch to stream's plain read and check_write. blocking_mode: BlockingMode, } /// NB: preview1 files always use blocking writes regardless of what /// they're configured to use since OSes don't have nonblocking /// reads/writes anyway. This behavior originated in the first /// implementation of WASIp1 where flags were propagated to the /// OS and the OS ignored the nonblocking flag for files /// generally. #[derive(Clone, Copy, Debug)] enum BlockingMode { Blocking, NonBlocking, } impl BlockingMode { fn from_fdflags(flags: &types::Fdflags) -> Self { if flags.contains(types::Fdflags::NONBLOCK) { BlockingMode::NonBlocking } else { BlockingMode::Blocking } } async fn read( &self, host: &mut impl streams::HostInputStream, input_stream: Resource, max_size: usize, ) -> Result, types::Error> { let max_size = max_size.try_into().unwrap_or(u64::MAX); match streams::HostInputStream::blocking_read(host, input_stream, max_size).await { Ok(r) if r.is_empty() => Err(types::Errno::Intr.into()), Ok(r) => Ok(r), Err(StreamError::Closed) => Ok(Vec::new()), Err(e) => Err(e.into()), } } async fn write( &self, memory: &mut GuestMemory<'_>, host: &mut impl streams::HostOutputStream, output_stream: Resource, bytes: GuestPtr<[u8]>, ) -> StreamResult { use streams::HostOutputStream as Streams; let bytes = memory .as_cow(bytes) .map_err(|e| StreamError::Trap(e.into()))?; let mut bytes = &bytes[..]; let total = bytes.len(); while !bytes.is_empty() { // NOTE: blocking_write_and_flush takes at most one 4k buffer. let len = bytes.len().min(4096); let (chunk, rest) = bytes.split_at(len); bytes = rest; Streams::blocking_write_and_flush(host, output_stream.borrowed(), Vec::from(chunk)) .await? } Ok(total) } } #[derive(Debug)] enum Descriptor { Stdin { stream: Resource, isatty: IsATTY, }, Stdout { stream: Resource, isatty: IsATTY, }, Stderr { stream: Resource, isatty: IsATTY, }, /// A fd of type [`crate::filesystem::Descriptor::Dir`] Directory { fd: Resource, /// The path this directory was preopened as. /// `None` means this directory was opened using `open-at`. preopen_path: Option, }, /// A fd of type [`crate::filesystem::Descriptor::File`] File(File), } #[derive(Debug, Default)] struct WasiPreview1Adapter { descriptors: Option, } #[derive(Debug, Default)] struct Descriptors { used: BTreeMap, free: Vec, } impl Deref for Descriptors { type Target = BTreeMap; fn deref(&self) -> &Self::Target { &self.used } } impl DerefMut for Descriptors { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.used } } impl Descriptors { /// Initializes [Self] using `preopens` fn new(mut host: WasiImpl<&mut WasiP1Ctx>) -> Result { let mut descriptors = Self::default(); descriptors.push(Descriptor::Stdin { stream: host .get_stdin() .context("failed to call `get-stdin`") .map_err(types::Error::trap)?, isatty: if let Some(term_in) = host .get_terminal_stdin() .context("failed to call `get-terminal-stdin`") .map_err(types::Error::trap)? { terminal_input::HostTerminalInput::drop(&mut host, term_in) .context("failed to call `drop-terminal-input`") .map_err(types::Error::trap)?; IsATTY::Yes } else { IsATTY::No }, })?; descriptors.push(Descriptor::Stdout { stream: host .get_stdout() .context("failed to call `get-stdout`") .map_err(types::Error::trap)?, isatty: if let Some(term_out) = host .get_terminal_stdout() .context("failed to call `get-terminal-stdout`") .map_err(types::Error::trap)? { terminal_output::HostTerminalOutput::drop(&mut host, term_out) .context("failed to call `drop-terminal-output`") .map_err(types::Error::trap)?; IsATTY::Yes } else { IsATTY::No }, })?; descriptors.push(Descriptor::Stderr { stream: host .get_stderr() .context("failed to call `get-stderr`") .map_err(types::Error::trap)?, isatty: if let Some(term_out) = host .get_terminal_stderr() .context("failed to call `get-terminal-stderr`") .map_err(types::Error::trap)? { terminal_output::HostTerminalOutput::drop(&mut host, term_out) .context("failed to call `drop-terminal-output`") .map_err(types::Error::trap)?; IsATTY::Yes } else { IsATTY::No }, })?; for dir in host .get_directories() .context("failed to call `get-directories`") .map_err(types::Error::trap)? { descriptors.push(Descriptor::Directory { fd: dir.0, preopen_path: Some(dir.1), })?; } Ok(descriptors) } /// Returns next descriptor number, which was never assigned fn unused(&self) -> Result { match self.last_key_value() { Some((fd, _)) => { if let Some(fd) = fd.checked_add(1) { return Ok(fd); } if self.len() == u32::MAX as usize { return Err(types::Errno::Loop.into()); } // TODO: Optimize Ok((0..u32::MAX) .rev() .find(|fd| !self.contains_key(fd)) .expect("failed to find an unused file descriptor")) } None => Ok(0), } } /// Removes the [Descriptor] corresponding to `fd` fn remove(&mut self, fd: types::Fd) -> Option { let fd = fd.into(); let desc = self.used.remove(&fd)?; self.free.push(fd); Some(desc) } /// Pushes the [Descriptor] returning corresponding number. /// This operation will try to reuse numbers previously removed via [`Self::remove`] /// and rely on [`Self::unused`] if no free numbers are recorded fn push(&mut self, desc: Descriptor) -> Result { let fd = if let Some(fd) = self.free.pop() { fd } else { self.unused()? }; assert!(self.insert(fd, desc).is_none()); Ok(fd) } } impl WasiPreview1Adapter { fn new() -> Self { Self::default() } } /// A mutably-borrowed [`WasiPreview1View`] implementation, which provides access to the stored /// state. It can be thought of as an in-flight [`WasiPreview1Adapter`] transaction, all /// changes will be recorded in the underlying [`WasiPreview1Adapter`] returned by /// [`WasiPreview1View::adapter_mut`] on [`Drop`] of this struct. // NOTE: This exists for the most part just due to the fact that `bindgen` generates methods with // `&mut self` receivers and so this struct lets us extend the lifetime of the `&mut self` borrow // of the [`WasiPreview1View`] to provide means to return mutably and immutably borrowed [`Descriptors`] // without having to rely on something like `Arc>`, while also being able to // call methods like [`Descriptor::is_file`] and hiding complexity from preview1 method implementations. struct Transaction<'a> { view: &'a mut WasiP1Ctx, descriptors: Descriptors, } impl Drop for Transaction<'_> { /// Record changes in the [`WasiPreview1Adapter`] returned by [`WasiPreview1View::adapter_mut`] fn drop(&mut self) { let descriptors = mem::take(&mut self.descriptors); self.view.adapter.descriptors = Some(descriptors); } } impl Transaction<'_> { /// Borrows [`Descriptor`] corresponding to `fd`. /// /// # Errors /// /// Returns [`types::Errno::Badf`] if no [`Descriptor`] is found fn get_descriptor(&self, fd: types::Fd) -> Result<&Descriptor> { let fd = fd.into(); let desc = self.descriptors.get(&fd).ok_or(types::Errno::Badf)?; Ok(desc) } /// Borrows [`File`] corresponding to `fd` /// if it describes a [`Descriptor::File`] fn get_file(&self, fd: types::Fd) -> Result<&File> { let fd = fd.into(); match self.descriptors.get(&fd) { Some(Descriptor::File(file)) => Ok(file), _ => Err(types::Errno::Badf.into()), } } /// Mutably borrows [`File`] corresponding to `fd` /// if it describes a [`Descriptor::File`] fn get_file_mut(&mut self, fd: types::Fd) -> Result<&mut File> { let fd = fd.into(); match self.descriptors.get_mut(&fd) { Some(Descriptor::File(file)) => Ok(file), _ => Err(types::Errno::Badf.into()), } } /// Borrows [`File`] corresponding to `fd` /// if it describes a [`Descriptor::File`] /// /// # Errors /// /// Returns [`types::Errno::Spipe`] if the descriptor corresponds to stdio fn get_seekable(&self, fd: types::Fd) -> Result<&File> { let fd = fd.into(); match self.descriptors.get(&fd) { Some(Descriptor::File(file)) => Ok(file), Some( Descriptor::Stdin { .. } | Descriptor::Stdout { .. } | Descriptor::Stderr { .. }, ) => { // NOTE: legacy implementation returns SPIPE here Err(types::Errno::Spipe.into()) } _ => Err(types::Errno::Badf.into()), } } /// Returns [`filesystem::Descriptor`] corresponding to `fd` fn get_fd(&self, fd: types::Fd) -> Result> { match self.get_descriptor(fd)? { Descriptor::File(File { fd, .. }) => Ok(fd.borrowed()), Descriptor::Directory { fd, .. } => Ok(fd.borrowed()), Descriptor::Stdin { .. } | Descriptor::Stdout { .. } | Descriptor::Stderr { .. } => { Err(types::Errno::Badf.into()) } } } /// Returns [`filesystem::Descriptor`] corresponding to `fd` /// if it describes a [`Descriptor::File`] fn get_file_fd(&self, fd: types::Fd) -> Result> { self.get_file(fd).map(|File { fd, .. }| fd.borrowed()) } /// Returns [`filesystem::Descriptor`] corresponding to `fd` /// if it describes a [`Descriptor::Directory`] fn get_dir_fd(&self, fd: types::Fd) -> Result> { let fd = fd.into(); match self.descriptors.get(&fd) { Some(Descriptor::Directory { fd, .. }) => Ok(fd.borrowed()), _ => Err(types::Errno::Badf.into()), } } } impl WasiP1Ctx { /// Lazily initializes [`WasiPreview1Adapter`] returned by [`WasiPreview1View::adapter_mut`] /// and returns [`Transaction`] on success fn transact(&mut self) -> Result, types::Error> { let descriptors = if let Some(descriptors) = self.adapter.descriptors.take() { descriptors } else { Descriptors::new(self.as_wasi_impl())? } .into(); Ok(Transaction { view: self, descriptors, }) } /// Lazily initializes [`WasiPreview1Adapter`] returned by [`WasiPreview1View::adapter_mut`] /// and returns [`filesystem::Descriptor`] corresponding to `fd` fn get_fd(&mut self, fd: types::Fd) -> Result, types::Error> { let st = self.transact()?; let fd = st.get_fd(fd)?; Ok(fd) } /// Lazily initializes [`WasiPreview1Adapter`] returned by [`WasiPreview1View::adapter_mut`] /// and returns [`filesystem::Descriptor`] corresponding to `fd` /// if it describes a [`Descriptor::File`] of [`crate::filesystem::File`] type fn get_file_fd( &mut self, fd: types::Fd, ) -> Result, types::Error> { let st = self.transact()?; let fd = st.get_file_fd(fd)?; Ok(fd) } /// Lazily initializes [`WasiPreview1Adapter`] returned by [`WasiPreview1View::adapter_mut`] /// and returns [`filesystem::Descriptor`] corresponding to `fd` /// if it describes a [`Descriptor::File`] or [`Descriptor::PreopenDirectory`] /// of [`crate::filesystem::Dir`] type fn get_dir_fd( &mut self, fd: types::Fd, ) -> Result, types::Error> { let st = self.transact()?; let fd = st.get_dir_fd(fd)?; Ok(fd) } /// Shared implementation of `fd_write` and `fd_pwrite`. async fn fd_write_impl( &mut self, memory: &mut GuestMemory<'_>, fd: types::Fd, ciovs: types::CiovecArray, write: FdWrite, ) -> Result { let t = self.transact()?; let desc = t.get_descriptor(fd)?; match desc { Descriptor::File(File { fd, append, position, // NB: files always use blocking writes regardless of what // they're configured to use since OSes don't have nonblocking // reads/writes anyway. This behavior originated in the first // implementation of WASIp1 where flags were propagated to the // OS and the OS ignored the nonblocking flag for files // generally. blocking_mode: _, }) => { let fd = fd.borrowed(); let position = position.clone(); let pos = position.load(Ordering::Relaxed); let append = *append; drop(t); let f = self.table().get(&fd)?.file()?; let buf = first_non_empty_ciovec(memory, ciovs)?; let do_write = move |f: &cap_std::fs::File, buf: &[u8]| match (append, write) { // Note that this is implementing Linux semantics of // `pwrite` where the offset is ignored if the file was // opened in append mode. (true, _) => f.append(&buf), (false, FdWrite::At(pos)) => f.write_at(&buf, pos), (false, FdWrite::AtCur) => f.write_at(&buf, pos), }; let nwritten = match f.as_blocking_file() { // If we can block then skip the copy out of wasm memory and // write directly to `f`. Some(f) => do_write(f, &memory.as_cow(buf)?), // ... otherwise copy out of wasm memory and use // `spawn_blocking` to do this write in a thread that can // block. None => { let buf = memory.to_vec(buf)?; f.run_blocking(move |f| do_write(f, &buf)).await } }; let nwritten = nwritten.map_err(|e| StreamError::LastOperationFailed(e.into()))?; // If this was a write at the current position then update the // current position with the result, otherwise the current // position is left unmodified. if let FdWrite::AtCur = write { if append { let len = self.as_wasi_impl().stat(fd).await?; position.store(len.size, Ordering::Relaxed); } else { let pos = pos .checked_add(nwritten as u64) .ok_or(types::Errno::Overflow)?; position.store(pos, Ordering::Relaxed); } } Ok(nwritten.try_into()?) } Descriptor::Stdout { stream, .. } | Descriptor::Stderr { stream, .. } => { match write { // Reject calls to `fd_pwrite` on stdio descriptors... FdWrite::At(_) => return Err(types::Errno::Spipe.into()), // ... but allow calls to `fd_write` FdWrite::AtCur => {} } let stream = stream.borrowed(); drop(t); let buf = first_non_empty_ciovec(memory, ciovs)?; let n = BlockingMode::Blocking .write(memory, &mut self.as_wasi_impl(), stream, buf) .await? .try_into()?; Ok(n) } _ => Err(types::Errno::Badf.into()), } } } #[derive(Copy, Clone)] enum FdWrite { At(u64), AtCur, } /// Adds asynchronous versions of all WASIp1 functions to the /// [`wasmtime::Linker`] provided. /// /// This method will add WASIp1 functions to `linker`. The `f` closure provided /// is used to project from the `T` state that `Linker` is associated with to a /// [`WasiP1Ctx`]. If `T` is `WasiP1Ctx` itself then this is the identity /// closure, but otherwise it must project out the field where `WasiP1Ctx` is /// stored within `T`. /// /// The state provided by `f` is used to implement all WASIp1 functions and /// provides configuration to know what to return. /// /// Note that this function is intended for use with /// [`Config::async_support(true)`]. If you're looking for a synchronous version /// see [`add_to_linker_sync`]. /// /// [`Config::async_support(true)`]: wasmtime::Config::async_support /// /// # Examples /// /// If the `T` in `Linker` is just `WasiP1Ctx`: /// /// ```no_run /// use wasmtime::{Result, Linker, Engine, Config}; /// use wasmtime_wasi::preview1::{self, WasiP1Ctx}; /// /// fn main() -> Result<()> { /// let mut config = Config::new(); /// config.async_support(true); /// let engine = Engine::new(&config)?; /// /// let mut linker: Linker = Linker::new(&engine); /// preview1::add_to_linker_async(&mut linker, |cx| cx)?; /// /// // ... continue to add more to `linker` as necessary and use it ... /// /// Ok(()) /// } /// ``` /// /// If the `T` in `Linker` is custom state: /// /// ```no_run /// use wasmtime::{Result, Linker, Engine, Config}; /// use wasmtime_wasi::preview1::{self, WasiP1Ctx}; /// /// struct MyState { /// // .. other custom state here .. /// /// wasi: WasiP1Ctx, /// } /// /// fn main() -> Result<()> { /// let mut config = Config::new(); /// config.async_support(true); /// let engine = Engine::new(&config)?; /// /// let mut linker: Linker = Linker::new(&engine); /// preview1::add_to_linker_async(&mut linker, |cx| &mut cx.wasi)?; /// /// // ... continue to add more to `linker` as necessary and use it ... /// /// Ok(()) /// } /// ``` pub fn add_to_linker_async( linker: &mut wasmtime::Linker, f: impl Fn(&mut T) -> &mut WasiP1Ctx + Copy + Send + Sync + 'static, ) -> anyhow::Result<()> { crate::preview1::wasi_snapshot_preview1::add_to_linker(linker, f) } /// Adds synchronous versions of all WASIp1 functions to the /// [`wasmtime::Linker`] provided. /// /// This method will add WASIp1 functions to `linker`. The `f` closure provided /// is used to project from the `T` state that `Linker` is associated with to a /// [`WasiP1Ctx`]. If `T` is `WasiP1Ctx` itself then this is the identity /// closure, but otherwise it must project out the field where `WasiP1Ctx` is /// stored within `T`. /// /// The state provided by `f` is used to implement all WASIp1 functions and /// provides configuration to know what to return. /// /// Note that this function is intended for use with /// [`Config::async_support(false)`]. If you're looking for a synchronous version /// see [`add_to_linker_async`]. /// /// [`Config::async_support(false)`]: wasmtime::Config::async_support /// /// # Examples /// /// If the `T` in `Linker` is just `WasiP1Ctx`: /// /// ```no_run /// use wasmtime::{Result, Linker, Engine, Config}; /// use wasmtime_wasi::preview1::{self, WasiP1Ctx}; /// /// fn main() -> Result<()> { /// let mut config = Config::new(); /// config.async_support(true); /// let engine = Engine::new(&config)?; /// /// let mut linker: Linker = Linker::new(&engine); /// preview1::add_to_linker_async(&mut linker, |cx| cx)?; /// /// // ... continue to add more to `linker` as necessary and use it ... /// /// Ok(()) /// } /// ``` /// /// If the `T` in `Linker` is custom state: /// /// ```no_run /// use wasmtime::{Result, Linker, Engine, Config}; /// use wasmtime_wasi::preview1::{self, WasiP1Ctx}; /// /// struct MyState { /// // .. other custom state here .. /// /// wasi: WasiP1Ctx, /// } /// /// fn main() -> Result<()> { /// let mut config = Config::new(); /// config.async_support(true); /// let engine = Engine::new(&config)?; /// /// let mut linker: Linker = Linker::new(&engine); /// preview1::add_to_linker_async(&mut linker, |cx| &mut cx.wasi)?; /// /// // ... continue to add more to `linker` as necessary and use it ... /// /// Ok(()) /// } /// ``` pub fn add_to_linker_sync( linker: &mut wasmtime::Linker, f: impl Fn(&mut T) -> &mut WasiP1Ctx + Copy + Send + Sync + 'static, ) -> anyhow::Result<()> { crate::preview1::sync::add_wasi_snapshot_preview1_to_linker(linker, f) } // Generate the wasi_snapshot_preview1::WasiSnapshotPreview1 trait, // and the module types. // None of the generated modules, traits, or types should be used externally // to this module. wiggle::from_witx!({ witx: ["$CARGO_MANIFEST_DIR/witx/preview1/wasi_snapshot_preview1.witx"], async: { wasi_snapshot_preview1::{ fd_advise, fd_close, fd_datasync, fd_fdstat_get, fd_filestat_get, fd_filestat_set_size, fd_filestat_set_times, fd_read, fd_pread, fd_seek, fd_sync, fd_readdir, fd_write, fd_pwrite, poll_oneoff, path_create_directory, path_filestat_get, path_filestat_set_times, path_link, path_open, path_readlink, path_remove_directory, path_rename, path_symlink, path_unlink_file } }, errors: { errno => trappable Error }, }); pub(crate) mod sync { use anyhow::Result; use std::future::Future; wiggle::wasmtime_integration!({ witx: ["$CARGO_MANIFEST_DIR/witx/preview1/wasi_snapshot_preview1.witx"], target: super, block_on[in_tokio]: { wasi_snapshot_preview1::{ fd_advise, fd_close, fd_datasync, fd_fdstat_get, fd_filestat_get, fd_filestat_set_size, fd_filestat_set_times, fd_read, fd_pread, fd_seek, fd_sync, fd_readdir, fd_write, fd_pwrite, poll_oneoff, path_create_directory, path_filestat_get, path_filestat_set_times, path_link, path_open, path_readlink, path_remove_directory, path_rename, path_symlink, path_unlink_file } }, errors: { errno => trappable Error }, }); // Small wrapper around `in_tokio` to add a `Result` layer which is always // `Ok` fn in_tokio(future: F) -> Result { Ok(crate::runtime::in_tokio(future)) } } impl wiggle::GuestErrorType for types::Errno { fn success() -> Self { Self::Success } } impl From for types::Error { fn from(err: StreamError) -> Self { match err { StreamError::Closed => types::Errno::Io.into(), StreamError::LastOperationFailed(e) => match e.downcast::() { Ok(err) => filesystem::ErrorCode::from(err).into(), Err(e) => { tracing::debug!("dropping error {e:?}"); types::Errno::Io.into() } }, StreamError::Trap(e) => types::Error::trap(e), } } } impl From for types::Error { fn from(err: FsError) -> Self { match err.downcast() { Ok(code) => code.into(), Err(e) => types::Error::trap(e), } } } fn systimespec(set: bool, ts: types::Timestamp, now: bool) -> Result { if set && now { Err(types::Errno::Inval.into()) } else if set { Ok(filesystem::NewTimestamp::Timestamp(filesystem::Datetime { seconds: ts / 1_000_000_000, nanoseconds: (ts % 1_000_000_000) as _, })) } else if now { Ok(filesystem::NewTimestamp::Now) } else { Ok(filesystem::NewTimestamp::NoChange) } } impl TryFrom for types::Timestamp { type Error = types::Errno; fn try_from( wall_clock::Datetime { seconds, nanoseconds, }: wall_clock::Datetime, ) -> Result { types::Timestamp::from(seconds) .checked_mul(1_000_000_000) .and_then(|ns| ns.checked_add(nanoseconds.into())) .ok_or(types::Errno::Overflow) } } impl From for filesystem::PathFlags { fn from(flags: types::Lookupflags) -> Self { if flags.contains(types::Lookupflags::SYMLINK_FOLLOW) { filesystem::PathFlags::SYMLINK_FOLLOW } else { filesystem::PathFlags::empty() } } } impl From for filesystem::OpenFlags { fn from(flags: types::Oflags) -> Self { let mut out = filesystem::OpenFlags::empty(); if flags.contains(types::Oflags::CREAT) { out |= filesystem::OpenFlags::CREATE; } if flags.contains(types::Oflags::DIRECTORY) { out |= filesystem::OpenFlags::DIRECTORY; } if flags.contains(types::Oflags::EXCL) { out |= filesystem::OpenFlags::EXCLUSIVE; } if flags.contains(types::Oflags::TRUNC) { out |= filesystem::OpenFlags::TRUNCATE; } out } } impl From for filesystem::Advice { fn from(advice: types::Advice) -> Self { match advice { types::Advice::Normal => filesystem::Advice::Normal, types::Advice::Sequential => filesystem::Advice::Sequential, types::Advice::Random => filesystem::Advice::Random, types::Advice::Willneed => filesystem::Advice::WillNeed, types::Advice::Dontneed => filesystem::Advice::DontNeed, types::Advice::Noreuse => filesystem::Advice::NoReuse, } } } impl TryFrom for types::Filetype { type Error = anyhow::Error; fn try_from(ty: filesystem::DescriptorType) -> Result { match ty { filesystem::DescriptorType::RegularFile => Ok(types::Filetype::RegularFile), filesystem::DescriptorType::Directory => Ok(types::Filetype::Directory), filesystem::DescriptorType::BlockDevice => Ok(types::Filetype::BlockDevice), filesystem::DescriptorType::CharacterDevice => Ok(types::Filetype::CharacterDevice), // preview1 never had a FIFO code. filesystem::DescriptorType::Fifo => Ok(types::Filetype::Unknown), // TODO: Add a way to disginguish between FILETYPE_SOCKET_STREAM and // FILETYPE_SOCKET_DGRAM. filesystem::DescriptorType::Socket => { bail!("sockets are not currently supported") } filesystem::DescriptorType::SymbolicLink => Ok(types::Filetype::SymbolicLink), filesystem::DescriptorType::Unknown => Ok(types::Filetype::Unknown), } } } impl From for types::Filetype { fn from(isatty: IsATTY) -> Self { match isatty { IsATTY::Yes => types::Filetype::CharacterDevice, IsATTY::No => types::Filetype::Unknown, } } } impl From for types::Errno { fn from(code: filesystem::ErrorCode) -> Self { match code { filesystem::ErrorCode::Access => types::Errno::Acces, filesystem::ErrorCode::WouldBlock => types::Errno::Again, filesystem::ErrorCode::Already => types::Errno::Already, filesystem::ErrorCode::BadDescriptor => types::Errno::Badf, filesystem::ErrorCode::Busy => types::Errno::Busy, filesystem::ErrorCode::Deadlock => types::Errno::Deadlk, filesystem::ErrorCode::Quota => types::Errno::Dquot, filesystem::ErrorCode::Exist => types::Errno::Exist, filesystem::ErrorCode::FileTooLarge => types::Errno::Fbig, filesystem::ErrorCode::IllegalByteSequence => types::Errno::Ilseq, filesystem::ErrorCode::InProgress => types::Errno::Inprogress, filesystem::ErrorCode::Interrupted => types::Errno::Intr, filesystem::ErrorCode::Invalid => types::Errno::Inval, filesystem::ErrorCode::Io => types::Errno::Io, filesystem::ErrorCode::IsDirectory => types::Errno::Isdir, filesystem::ErrorCode::Loop => types::Errno::Loop, filesystem::ErrorCode::TooManyLinks => types::Errno::Mlink, filesystem::ErrorCode::MessageSize => types::Errno::Msgsize, filesystem::ErrorCode::NameTooLong => types::Errno::Nametoolong, filesystem::ErrorCode::NoDevice => types::Errno::Nodev, filesystem::ErrorCode::NoEntry => types::Errno::Noent, filesystem::ErrorCode::NoLock => types::Errno::Nolck, filesystem::ErrorCode::InsufficientMemory => types::Errno::Nomem, filesystem::ErrorCode::InsufficientSpace => types::Errno::Nospc, filesystem::ErrorCode::Unsupported => types::Errno::Notsup, filesystem::ErrorCode::NotDirectory => types::Errno::Notdir, filesystem::ErrorCode::NotEmpty => types::Errno::Notempty, filesystem::ErrorCode::NotRecoverable => types::Errno::Notrecoverable, filesystem::ErrorCode::NoTty => types::Errno::Notty, filesystem::ErrorCode::NoSuchDevice => types::Errno::Nxio, filesystem::ErrorCode::Overflow => types::Errno::Overflow, filesystem::ErrorCode::NotPermitted => types::Errno::Perm, filesystem::ErrorCode::Pipe => types::Errno::Pipe, filesystem::ErrorCode::ReadOnly => types::Errno::Rofs, filesystem::ErrorCode::InvalidSeek => types::Errno::Spipe, filesystem::ErrorCode::TextFileBusy => types::Errno::Txtbsy, filesystem::ErrorCode::CrossDevice => types::Errno::Xdev, } } } impl From for types::Error { fn from(_: std::num::TryFromIntError) -> Self { types::Errno::Overflow.into() } } impl From for types::Error { fn from(err: GuestError) -> Self { use wiggle::GuestError::*; match err { InvalidFlagValue { .. } => types::Errno::Inval.into(), InvalidEnumValue { .. } => types::Errno::Inval.into(), // As per // https://github.com/WebAssembly/wasi/blob/main/legacy/tools/witx-docs.md#pointers // // > If a misaligned pointer is passed to a function, the function // > shall trap. // > // > If an out-of-bounds pointer is passed to a function and the // > function needs to dereference it, the function shall trap. // // so this turns OOB and misalignment errors into traps. PtrOverflow { .. } | PtrOutOfBounds { .. } | PtrNotAligned { .. } => { types::Error::trap(err.into()) } PtrBorrowed { .. } => types::Errno::Fault.into(), InvalidUtf8 { .. } => types::Errno::Ilseq.into(), TryFromIntError { .. } => types::Errno::Overflow.into(), SliceLengthsDiffer { .. } => types::Errno::Fault.into(), BorrowCheckerOutOfHandles { .. } => types::Errno::Fault.into(), InFunc { err, .. } => types::Error::from(*err), } } } impl From for types::Error { fn from(code: filesystem::ErrorCode) -> Self { types::Errno::from(code).into() } } impl From for types::Error { fn from(err: wasmtime::component::ResourceTableError) -> Self { types::Error::trap(err.into()) } } type Result = std::result::Result; fn write_bytes( memory: &mut GuestMemory<'_>, ptr: GuestPtr, buf: &[u8], ) -> Result, types::Error> { // NOTE: legacy implementation always returns Inval errno let len = u32::try_from(buf.len())?; memory.copy_from_slice(buf, ptr.as_array(len))?; let next = ptr.add(len)?; Ok(next) } fn write_byte(memory: &mut GuestMemory<'_>, ptr: GuestPtr, byte: u8) -> Result> { memory.write(ptr, byte)?; let next = ptr.add(1)?; Ok(next) } fn read_string<'a>(memory: &'a GuestMemory<'_>, ptr: GuestPtr) -> Result { Ok(memory.as_cow_str(ptr)?.into_owned()) } // Returns the first non-empty buffer in `ciovs` or a single empty buffer if // they're all empty. fn first_non_empty_ciovec( memory: &GuestMemory<'_>, ciovs: types::CiovecArray, ) -> Result> { for iov in ciovs.iter() { let iov = memory.read(iov?)?; if iov.buf_len == 0 { continue; } return Ok(iov.buf.as_array(iov.buf_len)); } Ok(GuestPtr::new((0, 0))) } // Returns the first non-empty buffer in `iovs` or a single empty buffer if // they're all empty. fn first_non_empty_iovec( memory: &GuestMemory<'_>, iovs: types::IovecArray, ) -> Result> { for iov in iovs.iter() { let iov = memory.read(iov?)?; if iov.buf_len == 0 { continue; } return Ok(iov.buf.as_array(iov.buf_len)); } Ok(GuestPtr::new((0, 0))) } #[async_trait::async_trait] // Implement the WasiSnapshotPreview1 trait using only the traits that are // required for T, i.e., in terms of the preview 2 wit interface, and state // stored in the WasiPreview1Adapter struct. impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { #[instrument(skip(self, memory))] fn args_get( &mut self, memory: &mut GuestMemory<'_>, argv: GuestPtr>, argv_buf: GuestPtr, ) -> Result<(), types::Error> { self.as_wasi_impl() .get_arguments() .context("failed to call `get-arguments`") .map_err(types::Error::trap)? .into_iter() .try_fold((argv, argv_buf), |(argv, argv_buf), arg| -> Result<_> { memory.write(argv, argv_buf)?; let argv = argv.add(1)?; let argv_buf = write_bytes(memory, argv_buf, arg.as_bytes())?; let argv_buf = write_byte(memory, argv_buf, 0)?; Ok((argv, argv_buf)) })?; Ok(()) } #[instrument(skip(self, _memory))] fn args_sizes_get( &mut self, _memory: &mut GuestMemory<'_>, ) -> Result<(types::Size, types::Size), types::Error> { let args = self .as_wasi_impl() .get_arguments() .context("failed to call `get-arguments`") .map_err(types::Error::trap)?; let num = args.len().try_into().map_err(|_| types::Errno::Overflow)?; let len = args .iter() .map(|buf| buf.len() + 1) // Each argument is expected to be `\0` terminated. .sum::() .try_into() .map_err(|_| types::Errno::Overflow)?; Ok((num, len)) } #[instrument(skip(self, memory))] fn environ_get( &mut self, memory: &mut GuestMemory<'_>, environ: GuestPtr>, environ_buf: GuestPtr, ) -> Result<(), types::Error> { self.as_wasi_impl() .get_environment() .context("failed to call `get-environment`") .map_err(types::Error::trap)? .into_iter() .try_fold( (environ, environ_buf), |(environ, environ_buf), (k, v)| -> Result<_, types::Error> { memory.write(environ, environ_buf)?; let environ = environ.add(1)?; let environ_buf = write_bytes(memory, environ_buf, k.as_bytes())?; let environ_buf = write_byte(memory, environ_buf, b'=')?; let environ_buf = write_bytes(memory, environ_buf, v.as_bytes())?; let environ_buf = write_byte(memory, environ_buf, 0)?; Ok((environ, environ_buf)) }, )?; Ok(()) } #[instrument(skip(self, _memory))] fn environ_sizes_get( &mut self, _memory: &mut GuestMemory<'_>, ) -> Result<(types::Size, types::Size), types::Error> { let environ = self .as_wasi_impl() .get_environment() .context("failed to call `get-environment`") .map_err(types::Error::trap)?; let num = environ.len().try_into()?; let len = environ .iter() .map(|(k, v)| k.len() + 1 + v.len() + 1) // Key/value pairs are expected to be joined with `=`s, and terminated with `\0`s. .sum::() .try_into()?; Ok((num, len)) } #[instrument(skip(self, _memory))] fn clock_res_get( &mut self, _memory: &mut GuestMemory<'_>, id: types::Clockid, ) -> Result { let res = match id { types::Clockid::Realtime => wall_clock::Host::resolution(&mut self.as_wasi_impl()) .context("failed to call `wall_clock::resolution`") .map_err(types::Error::trap)? .try_into()?, types::Clockid::Monotonic => { monotonic_clock::Host::resolution(&mut self.as_wasi_impl()) .context("failed to call `monotonic_clock::resolution`") .map_err(types::Error::trap)? } types::Clockid::ProcessCputimeId | types::Clockid::ThreadCputimeId => { return Err(types::Errno::Badf.into()) } }; Ok(res) } #[instrument(skip(self, _memory))] fn clock_time_get( &mut self, _memory: &mut GuestMemory<'_>, id: types::Clockid, _precision: types::Timestamp, ) -> Result { let now = match id { types::Clockid::Realtime => wall_clock::Host::now(&mut self.as_wasi_impl()) .context("failed to call `wall_clock::now`") .map_err(types::Error::trap)? .try_into()?, types::Clockid::Monotonic => monotonic_clock::Host::now(&mut self.as_wasi_impl()) .context("failed to call `monotonic_clock::now`") .map_err(types::Error::trap)?, types::Clockid::ProcessCputimeId | types::Clockid::ThreadCputimeId => { return Err(types::Errno::Badf.into()) } }; Ok(now) } #[instrument(skip(self, _memory))] async fn fd_advise( &mut self, _memory: &mut GuestMemory<'_>, fd: types::Fd, offset: types::Filesize, len: types::Filesize, advice: types::Advice, ) -> Result<(), types::Error> { let fd = self.get_file_fd(fd)?; self.as_wasi_impl() .advise(fd, offset, len, advice.into()) .await?; Ok(()) } /// Force the allocation of space in a file. /// NOTE: This is similar to `posix_fallocate` in POSIX. #[instrument(skip(self, _memory))] fn fd_allocate( &mut self, _memory: &mut GuestMemory<'_>, fd: types::Fd, _offset: types::Filesize, _len: types::Filesize, ) -> Result<(), types::Error> { self.get_file_fd(fd)?; Err(types::Errno::Notsup.into()) } /// Close a file descriptor. /// NOTE: This is similar to `close` in POSIX. #[instrument(skip(self, _memory))] async fn fd_close( &mut self, _memory: &mut GuestMemory<'_>, fd: types::Fd, ) -> Result<(), types::Error> { let desc = self .transact()? .descriptors .remove(fd) .ok_or(types::Errno::Badf)?; match desc { Descriptor::Stdin { stream, .. } => { streams::HostInputStream::drop(&mut self.as_wasi_impl(), stream) .await .context("failed to call `drop` on `input-stream`") } Descriptor::Stdout { stream, .. } | Descriptor::Stderr { stream, .. } => { streams::HostOutputStream::drop(&mut self.as_wasi_impl(), stream) .await .context("failed to call `drop` on `output-stream`") } Descriptor::File(File { fd, .. }) | Descriptor::Directory { fd, .. } => { filesystem::HostDescriptor::drop(&mut self.as_wasi_impl(), fd) .context("failed to call `drop`") } } .map_err(types::Error::trap) } /// Synchronize the data of a file to disk. /// NOTE: This is similar to `fdatasync` in POSIX. #[instrument(skip(self, _memory))] async fn fd_datasync( &mut self, _memory: &mut GuestMemory<'_>, fd: types::Fd, ) -> Result<(), types::Error> { let fd = self.get_file_fd(fd)?; self.as_wasi_impl().sync_data(fd).await?; 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. #[instrument(skip(self, _memory))] async fn fd_fdstat_get( &mut self, _memory: &mut GuestMemory<'_>, fd: types::Fd, ) -> Result { let (fd, blocking, append) = match self.transact()?.get_descriptor(fd)? { Descriptor::Stdin { isatty, .. } => { let fs_rights_base = types::Rights::FD_READ; return Ok(types::Fdstat { fs_filetype: (*isatty).into(), fs_flags: types::Fdflags::empty(), fs_rights_base, fs_rights_inheriting: fs_rights_base, }); } Descriptor::Stdout { isatty, .. } | Descriptor::Stderr { isatty, .. } => { let fs_rights_base = types::Rights::FD_WRITE; return Ok(types::Fdstat { fs_filetype: (*isatty).into(), fs_flags: types::Fdflags::empty(), fs_rights_base, fs_rights_inheriting: fs_rights_base, }); } Descriptor::Directory { preopen_path: Some(_), .. } => { // Hard-coded set or rights expected by many userlands: let fs_rights_base = types::Rights::PATH_CREATE_DIRECTORY | types::Rights::PATH_CREATE_FILE | types::Rights::PATH_LINK_SOURCE | types::Rights::PATH_LINK_TARGET | types::Rights::PATH_OPEN | types::Rights::FD_READDIR | types::Rights::PATH_READLINK | types::Rights::PATH_RENAME_SOURCE | types::Rights::PATH_RENAME_TARGET | types::Rights::PATH_SYMLINK | types::Rights::PATH_REMOVE_DIRECTORY | types::Rights::PATH_UNLINK_FILE | types::Rights::PATH_FILESTAT_GET | types::Rights::PATH_FILESTAT_SET_TIMES | types::Rights::FD_FILESTAT_GET | types::Rights::FD_FILESTAT_SET_TIMES; let fs_rights_inheriting = fs_rights_base | types::Rights::FD_DATASYNC | types::Rights::FD_READ | types::Rights::FD_SEEK | types::Rights::FD_FDSTAT_SET_FLAGS | types::Rights::FD_SYNC | types::Rights::FD_TELL | types::Rights::FD_WRITE | types::Rights::FD_ADVISE | types::Rights::FD_ALLOCATE | types::Rights::FD_FILESTAT_GET | types::Rights::FD_FILESTAT_SET_SIZE | types::Rights::FD_FILESTAT_SET_TIMES | types::Rights::POLL_FD_READWRITE; return Ok(types::Fdstat { fs_filetype: types::Filetype::Directory, fs_flags: types::Fdflags::empty(), fs_rights_base, fs_rights_inheriting, }); } Descriptor::Directory { fd, .. } => (fd.borrowed(), BlockingMode::Blocking, false), Descriptor::File(File { fd, blocking_mode, append, .. }) => (fd.borrowed(), *blocking_mode, *append), }; let flags = self.as_wasi_impl().get_flags(fd.borrowed()).await?; let fs_filetype = self .as_wasi_impl() .get_type(fd.borrowed()) .await? .try_into() .map_err(types::Error::trap)?; let mut fs_flags = types::Fdflags::empty(); let mut fs_rights_base = types::Rights::all(); if let types::Filetype::Directory = fs_filetype { fs_rights_base &= !types::Rights::FD_SEEK; fs_rights_base &= !types::Rights::FD_FILESTAT_SET_SIZE; } if !flags.contains(filesystem::DescriptorFlags::READ) { fs_rights_base &= !types::Rights::FD_READ; fs_rights_base &= !types::Rights::FD_READDIR; } if !flags.contains(filesystem::DescriptorFlags::WRITE) { fs_rights_base &= !types::Rights::FD_WRITE; } if flags.contains(filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC) { fs_flags |= types::Fdflags::DSYNC; } if flags.contains(filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC) { fs_flags |= types::Fdflags::RSYNC; } if flags.contains(filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC) { fs_flags |= types::Fdflags::SYNC; } if append { fs_flags |= types::Fdflags::APPEND; } if matches!(blocking, BlockingMode::NonBlocking) { fs_flags |= types::Fdflags::NONBLOCK; } Ok(types::Fdstat { fs_filetype, fs_flags, fs_rights_base, fs_rights_inheriting: fs_rights_base, }) } /// Adjust the flags associated with a file descriptor. /// NOTE: This is similar to `fcntl(fd, F_SETFL, flags)` in POSIX. #[instrument(skip(self, _memory))] fn fd_fdstat_set_flags( &mut self, _memory: &mut GuestMemory<'_>, fd: types::Fd, flags: types::Fdflags, ) -> Result<(), types::Error> { let mut st = self.transact()?; let File { append, blocking_mode, .. } = st.get_file_mut(fd)?; // Only support changing the NONBLOCK or APPEND flags. if flags.contains(types::Fdflags::DSYNC) || flags.contains(types::Fdflags::SYNC) || flags.contains(types::Fdflags::RSYNC) { return Err(types::Errno::Inval.into()); } *append = flags.contains(types::Fdflags::APPEND); *blocking_mode = BlockingMode::from_fdflags(&flags); Ok(()) } /// Does not do anything if `fd` corresponds to a valid descriptor and returns `[types::Errno::Badf]` error otherwise. #[instrument(skip(self, _memory))] fn fd_fdstat_set_rights( &mut self, _memory: &mut GuestMemory<'_>, fd: types::Fd, _fs_rights_base: types::Rights, _fs_rights_inheriting: types::Rights, ) -> Result<(), types::Error> { self.get_fd(fd)?; Ok(()) } /// Return the attributes of an open file. #[instrument(skip(self, _memory))] async fn fd_filestat_get( &mut self, _memory: &mut GuestMemory<'_>, fd: types::Fd, ) -> Result { let t = self.transact()?; let desc = t.get_descriptor(fd)?; match desc { Descriptor::Stdin { isatty, .. } | Descriptor::Stdout { isatty, .. } | Descriptor::Stderr { isatty, .. } => Ok(types::Filestat { dev: 0, ino: 0, filetype: (*isatty).into(), nlink: 0, size: 0, atim: 0, mtim: 0, ctim: 0, }), Descriptor::Directory { fd, .. } | Descriptor::File(File { fd, .. }) => { let fd = fd.borrowed(); drop(t); let filesystem::DescriptorStat { type_, link_count: nlink, size, data_access_timestamp, data_modification_timestamp, status_change_timestamp, } = self.as_wasi_impl().stat(fd.borrowed()).await?; let metadata_hash = self.as_wasi_impl().metadata_hash(fd).await?; let filetype = type_.try_into().map_err(types::Error::trap)?; let zero = wall_clock::Datetime { seconds: 0, nanoseconds: 0, }; let atim = data_access_timestamp.unwrap_or(zero).try_into()?; let mtim = data_modification_timestamp.unwrap_or(zero).try_into()?; let ctim = status_change_timestamp.unwrap_or(zero).try_into()?; Ok(types::Filestat { dev: 1, ino: metadata_hash.lower, filetype, nlink, size, atim, mtim, ctim, }) } } } /// 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. #[instrument(skip(self, _memory))] async fn fd_filestat_set_size( &mut self, _memory: &mut GuestMemory<'_>, fd: types::Fd, size: types::Filesize, ) -> Result<(), types::Error> { let fd = self.get_file_fd(fd)?; self.as_wasi_impl().set_size(fd, size).await?; Ok(()) } /// Adjust the timestamps of an open file or directory. /// NOTE: This is similar to `futimens` in POSIX. #[instrument(skip(self, _memory))] async fn fd_filestat_set_times( &mut self, _memory: &mut GuestMemory<'_>, fd: types::Fd, atim: types::Timestamp, mtim: types::Timestamp, fst_flags: types::Fstflags, ) -> Result<(), types::Error> { let atim = systimespec( fst_flags.contains(types::Fstflags::ATIM), atim, fst_flags.contains(types::Fstflags::ATIM_NOW), )?; let mtim = systimespec( fst_flags.contains(types::Fstflags::MTIM), mtim, fst_flags.contains(types::Fstflags::MTIM_NOW), )?; let fd = self.get_fd(fd)?; self.as_wasi_impl().set_times(fd, atim, mtim).await?; Ok(()) } /// Read from a file descriptor. /// NOTE: This is similar to `readv` in POSIX. #[instrument(skip(self, memory))] async fn fd_read( &mut self, memory: &mut GuestMemory<'_>, fd: types::Fd, iovs: types::IovecArray, ) -> Result { let t = self.transact()?; let desc = t.get_descriptor(fd)?; match desc { Descriptor::File(File { fd, position, // NB: the nonblocking flag is intentionally ignored here and // blocking reads/writes are always performed. blocking_mode: _, .. }) => { let fd = fd.borrowed(); let position = position.clone(); drop(t); let pos = position.load(Ordering::Relaxed); let file = self.table().get(&fd)?.file()?; let iov = first_non_empty_iovec(memory, iovs)?; let bytes_read = match (file.as_blocking_file(), memory.as_slice_mut(iov)?) { // Try to read directly into wasm memory where possible // when the current thread can block and additionally wasm // memory isn't shared. (Some(file), Some(mut buf)) => file .read_at(&mut buf, pos) .map_err(|e| StreamError::LastOperationFailed(e.into()))?, // ... otherwise fall back to performing the read on a // blocking thread and which copies the data back into wasm // memory. (_, buf) => { drop(buf); let mut buf = vec![0; iov.len() as usize]; let buf = file .run_blocking(move |file| -> Result<_, types::Error> { let bytes_read = file .read_at(&mut buf, pos) .map_err(|e| StreamError::LastOperationFailed(e.into()))?; buf.truncate(bytes_read); Ok(buf) }) .await?; let iov = iov.get_range(0..u32::try_from(buf.len())?).unwrap(); memory.copy_from_slice(&buf, iov)?; buf.len() } }; let pos = pos .checked_add(bytes_read.try_into()?) .ok_or(types::Errno::Overflow)?; position.store(pos, Ordering::Relaxed); Ok(bytes_read.try_into()?) } Descriptor::Stdin { stream, .. } => { let stream = stream.borrowed(); drop(t); let buf = first_non_empty_iovec(memory, iovs)?; let read = BlockingMode::Blocking .read(&mut self.as_wasi_impl(), stream, buf.len().try_into()?) .await?; if read.len() > buf.len().try_into()? { return Err(types::Errno::Range.into()); } let buf = buf.get_range(0..u32::try_from(read.len())?).unwrap(); memory.copy_from_slice(&read, buf)?; let n = read.len().try_into()?; Ok(n) } _ => return Err(types::Errno::Badf.into()), } } /// Read from a file descriptor, without using and updating the file descriptor's offset. /// NOTE: This is similar to `preadv` in POSIX. #[instrument(skip(self, memory))] async fn fd_pread( &mut self, memory: &mut GuestMemory<'_>, fd: types::Fd, iovs: types::IovecArray, offset: types::Filesize, ) -> Result { let t = self.transact()?; let desc = t.get_descriptor(fd)?; let (buf, read) = match desc { Descriptor::File(File { fd, blocking_mode, .. }) => { let fd = fd.borrowed(); let blocking_mode = *blocking_mode; drop(t); let buf = first_non_empty_iovec(memory, iovs)?; let stream = self.as_wasi_impl().read_via_stream(fd, offset)?; let read = blocking_mode .read( &mut self.as_wasi_impl(), stream.borrowed(), buf.len().try_into()?, ) .await; streams::HostInputStream::drop(&mut self.as_wasi_impl(), stream) .await .map_err(|e| types::Error::trap(e))?; (buf, read?) } Descriptor::Stdin { .. } => { // NOTE: legacy implementation returns SPIPE here return Err(types::Errno::Spipe.into()); } _ => return Err(types::Errno::Badf.into()), }; if read.len() > buf.len().try_into()? { return Err(types::Errno::Range.into()); } let buf = buf.get_range(0..u32::try_from(read.len())?).unwrap(); memory.copy_from_slice(&read, buf)?; let n = read.len().try_into()?; Ok(n) } /// Write to a file descriptor. /// NOTE: This is similar to `writev` in POSIX. #[instrument(skip(self, memory))] async fn fd_write( &mut self, memory: &mut GuestMemory<'_>, fd: types::Fd, ciovs: types::CiovecArray, ) -> Result { self.fd_write_impl(memory, fd, ciovs, FdWrite::AtCur).await } /// Write to a file descriptor, without using and updating the file descriptor's offset. /// NOTE: This is similar to `pwritev` in POSIX. #[instrument(skip(self, memory))] async fn fd_pwrite( &mut self, memory: &mut GuestMemory<'_>, fd: types::Fd, ciovs: types::CiovecArray, offset: types::Filesize, ) -> Result { self.fd_write_impl(memory, fd, ciovs, FdWrite::At(offset)) .await } /// Return a description of the given preopened file descriptor. #[instrument(skip(self, _memory))] fn fd_prestat_get( &mut self, _memory: &mut GuestMemory<'_>, fd: types::Fd, ) -> Result { if let Descriptor::Directory { preopen_path: Some(p), .. } = self.transact()?.get_descriptor(fd)? { let pr_name_len = p.len().try_into()?; return Ok(types::Prestat::Dir(types::PrestatDir { pr_name_len })); } Err(types::Errno::Badf.into()) // NOTE: legacy implementation returns BADF here } /// Return a description of the given preopened file descriptor. #[instrument(skip(self, memory))] fn fd_prestat_dir_name( &mut self, memory: &mut GuestMemory<'_>, fd: types::Fd, path: GuestPtr, path_max_len: types::Size, ) -> Result<(), types::Error> { let path_max_len = path_max_len.try_into()?; if let Descriptor::Directory { preopen_path: Some(p), .. } = self.transact()?.get_descriptor(fd)? { if p.len() > path_max_len { return Err(types::Errno::Nametoolong.into()); } write_bytes(memory, path, p.as_bytes())?; return Ok(()); } Err(types::Errno::Notdir.into()) // NOTE: legacy implementation returns NOTDIR here } /// Atomically replace a file descriptor by renumbering another file descriptor. #[instrument(skip(self, _memory))] fn fd_renumber( &mut self, _memory: &mut GuestMemory<'_>, from: types::Fd, to: types::Fd, ) -> Result<(), types::Error> { let mut st = self.transact()?; let desc = st.descriptors.remove(from).ok_or(types::Errno::Badf)?; st.descriptors.insert(to.into(), desc); Ok(()) } /// Move the offset of a file descriptor. /// NOTE: This is similar to `lseek` in POSIX. #[instrument(skip(self, _memory))] async fn fd_seek( &mut self, _memory: &mut GuestMemory<'_>, fd: types::Fd, offset: types::Filedelta, whence: types::Whence, ) -> Result { let t = self.transact()?; let File { fd, position, .. } = t.get_seekable(fd)?; let fd = fd.borrowed(); let position = position.clone(); drop(t); let pos = match whence { types::Whence::Set if offset >= 0 => { offset.try_into().map_err(|_| types::Errno::Inval)? } types::Whence::Cur => position .load(Ordering::Relaxed) .checked_add_signed(offset) .ok_or(types::Errno::Inval)?, types::Whence::End => { let filesystem::DescriptorStat { size, .. } = self.as_wasi_impl().stat(fd).await?; size.checked_add_signed(offset).ok_or(types::Errno::Inval)? } _ => return Err(types::Errno::Inval.into()), }; position.store(pos, Ordering::Relaxed); Ok(pos) } /// Synchronize the data and metadata of a file to disk. /// NOTE: This is similar to `fsync` in POSIX. #[instrument(skip(self, _memory))] async fn fd_sync( &mut self, _memory: &mut GuestMemory<'_>, fd: types::Fd, ) -> Result<(), types::Error> { let fd = self.get_file_fd(fd)?; self.as_wasi_impl().sync(fd).await?; Ok(()) } /// Return the current offset of a file descriptor. /// NOTE: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX. #[instrument(skip(self, _memory))] fn fd_tell( &mut self, _memory: &mut GuestMemory<'_>, fd: types::Fd, ) -> Result { let pos = self .transact()? .get_seekable(fd) .map(|File { position, .. }| position.load(Ordering::Relaxed))?; Ok(pos) } #[instrument(skip(self, memory))] async fn fd_readdir( &mut self, memory: &mut GuestMemory<'_>, fd: types::Fd, buf: GuestPtr, buf_len: types::Size, cookie: types::Dircookie, ) -> Result { let fd = self.get_dir_fd(fd)?; let stream = self.as_wasi_impl().read_directory(fd.borrowed()).await?; let dir_metadata_hash = self.as_wasi_impl().metadata_hash(fd.borrowed()).await?; let cookie = cookie.try_into().map_err(|_| types::Errno::Overflow)?; let head = [ ( types::Dirent { d_next: 1u64.to_le(), d_ino: dir_metadata_hash.lower.to_le(), d_type: types::Filetype::Directory, d_namlen: 1u32.to_le(), }, ".".into(), ), ( types::Dirent { d_next: 2u64.to_le(), d_ino: dir_metadata_hash.lower.to_le(), // NOTE: incorrect, but legacy implementation returns `fd` inode here d_type: types::Filetype::Directory, d_namlen: 2u32.to_le(), }, "..".into(), ), ]; let mut dir = Vec::new(); for (entry, d_next) in self .table() // remove iterator from table and use it directly: .delete(stream)? .into_iter() .zip(3u64..) { let filesystem::DirectoryEntry { type_, name } = entry?; let metadata_hash = self .as_wasi_impl() .metadata_hash_at(fd.borrowed(), filesystem::PathFlags::empty(), name.clone()) .await?; let d_type = type_.try_into().map_err(types::Error::trap)?; let d_namlen: u32 = name.len().try_into().map_err(|_| types::Errno::Overflow)?; dir.push(( types::Dirent { d_next: d_next.to_le(), d_ino: metadata_hash.lower.to_le(), d_type, // endian-invariant d_namlen: d_namlen.to_le(), }, name, )) } // assume that `types::Dirent` size always fits in `u32` const DIRENT_SIZE: u32 = size_of::() as _; assert_eq!( types::Dirent::guest_size(), DIRENT_SIZE, "Dirent guest repr and host repr should match" ); let mut buf = buf; let mut cap = buf_len; for (ref entry, path) in head.into_iter().chain(dir.into_iter()).skip(cookie) { let mut path = path.into_bytes(); assert_eq!( 1, size_of_val(&entry.d_type), "Dirent member d_type should be endian-invariant" ); let entry_len = cap.min(DIRENT_SIZE); let entry = entry as *const _ as _; let entry = unsafe { slice::from_raw_parts(entry, entry_len as _) }; cap = cap.checked_sub(entry_len).unwrap(); buf = write_bytes(memory, buf, entry)?; if cap == 0 { return Ok(buf_len); } if let Ok(cap) = cap.try_into() { // `path` cannot be longer than `usize`, only truncate if `cap` fits in `usize` path.truncate(cap); } cap = cap.checked_sub(path.len() as _).unwrap(); buf = write_bytes(memory, buf, &path)?; if cap == 0 { return Ok(buf_len); } } Ok(buf_len.checked_sub(cap).unwrap()) } #[instrument(skip(self, memory))] async fn path_create_directory( &mut self, memory: &mut GuestMemory<'_>, dirfd: types::Fd, path: GuestPtr, ) -> Result<(), types::Error> { let dirfd = self.get_dir_fd(dirfd)?; let path = read_string(memory, path)?; self.as_wasi_impl() .create_directory_at(dirfd.borrowed(), path) .await?; Ok(()) } /// Return the attributes of a file or directory. /// NOTE: This is similar to `stat` in POSIX. #[instrument(skip(self, memory))] async fn path_filestat_get( &mut self, memory: &mut GuestMemory<'_>, dirfd: types::Fd, flags: types::Lookupflags, path: GuestPtr, ) -> Result { let dirfd = self.get_dir_fd(dirfd)?; let path = read_string(memory, path)?; let filesystem::DescriptorStat { type_, link_count: nlink, size, data_access_timestamp, data_modification_timestamp, status_change_timestamp, } = self .as_wasi_impl() .stat_at(dirfd.borrowed(), flags.into(), path.clone()) .await?; let metadata_hash = self .as_wasi_impl() .metadata_hash_at(dirfd, flags.into(), path) .await?; let filetype = type_.try_into().map_err(types::Error::trap)?; let zero = wall_clock::Datetime { seconds: 0, nanoseconds: 0, }; let atim = data_access_timestamp.unwrap_or(zero).try_into()?; let mtim = data_modification_timestamp.unwrap_or(zero).try_into()?; let ctim = status_change_timestamp.unwrap_or(zero).try_into()?; Ok(types::Filestat { dev: 1, ino: metadata_hash.lower, filetype, nlink, size, atim, mtim, ctim, }) } /// Adjust the timestamps of a file or directory. /// NOTE: This is similar to `utimensat` in POSIX. #[instrument(skip(self, memory))] async fn path_filestat_set_times( &mut self, memory: &mut GuestMemory<'_>, dirfd: types::Fd, flags: types::Lookupflags, path: GuestPtr, atim: types::Timestamp, mtim: types::Timestamp, fst_flags: types::Fstflags, ) -> Result<(), types::Error> { let atim = systimespec( fst_flags.contains(types::Fstflags::ATIM), atim, fst_flags.contains(types::Fstflags::ATIM_NOW), )?; let mtim = systimespec( fst_flags.contains(types::Fstflags::MTIM), mtim, fst_flags.contains(types::Fstflags::MTIM_NOW), )?; let dirfd = self.get_dir_fd(dirfd)?; let path = read_string(memory, path)?; self.as_wasi_impl() .set_times_at(dirfd, flags.into(), path, atim, mtim) .await?; Ok(()) } /// Create a hard link. /// NOTE: This is similar to `linkat` in POSIX. #[instrument(skip(self, memory))] async fn path_link( &mut self, memory: &mut GuestMemory<'_>, src_fd: types::Fd, src_flags: types::Lookupflags, src_path: GuestPtr, target_fd: types::Fd, target_path: GuestPtr, ) -> Result<(), types::Error> { let src_fd = self.get_dir_fd(src_fd)?; let target_fd = self.get_dir_fd(target_fd)?; let src_path = read_string(memory, src_path)?; let target_path = read_string(memory, target_path)?; self.as_wasi_impl() .link_at(src_fd, src_flags.into(), src_path, target_fd, target_path) .await?; Ok(()) } /// Open a file or directory. /// NOTE: This is similar to `openat` in POSIX. #[instrument(skip(self, memory))] async fn path_open( &mut self, memory: &mut GuestMemory<'_>, dirfd: types::Fd, dirflags: types::Lookupflags, path: GuestPtr, oflags: types::Oflags, fs_rights_base: types::Rights, _fs_rights_inheriting: types::Rights, fdflags: types::Fdflags, ) -> Result { let path = read_string(memory, path)?; let mut flags = filesystem::DescriptorFlags::empty(); if fs_rights_base.contains(types::Rights::FD_READ) { flags |= filesystem::DescriptorFlags::READ; } if fs_rights_base.contains(types::Rights::FD_WRITE) { flags |= filesystem::DescriptorFlags::WRITE; } if fdflags.contains(types::Fdflags::SYNC) { flags |= filesystem::DescriptorFlags::FILE_INTEGRITY_SYNC; } if fdflags.contains(types::Fdflags::DSYNC) { flags |= filesystem::DescriptorFlags::DATA_INTEGRITY_SYNC; } if fdflags.contains(types::Fdflags::RSYNC) { flags |= filesystem::DescriptorFlags::REQUESTED_WRITE_SYNC; } let t = self.transact()?; let dirfd = match t.get_descriptor(dirfd)? { Descriptor::Directory { fd, .. } => fd.borrowed(), Descriptor::File(_) => return Err(types::Errno::Notdir.into()), _ => return Err(types::Errno::Badf.into()), }; drop(t); let fd = self .as_wasi_impl() .open_at(dirfd, dirflags.into(), path, oflags.into(), flags) .await?; let mut t = self.transact()?; let desc = match t.view.table().get(&fd)? { crate::filesystem::Descriptor::Dir(_) => Descriptor::Directory { fd, preopen_path: None, }, crate::filesystem::Descriptor::File(_) => Descriptor::File(File { fd, position: Default::default(), append: fdflags.contains(types::Fdflags::APPEND), blocking_mode: BlockingMode::from_fdflags(&fdflags), }), }; let fd = t.descriptors.push(desc)?; Ok(fd.into()) } /// Read the contents of a symbolic link. /// NOTE: This is similar to `readlinkat` in POSIX. #[instrument(skip(self, memory))] async fn path_readlink( &mut self, memory: &mut GuestMemory<'_>, dirfd: types::Fd, path: GuestPtr, buf: GuestPtr, buf_len: types::Size, ) -> Result { let dirfd = self.get_dir_fd(dirfd)?; let path = read_string(memory, path)?; let mut path = self .as_wasi_impl() .readlink_at(dirfd, path) .await? .into_bytes(); if let Ok(buf_len) = buf_len.try_into() { // `path` cannot be longer than `usize`, only truncate if `buf_len` fits in `usize` path.truncate(buf_len); } let n = path.len().try_into().map_err(|_| types::Errno::Overflow)?; write_bytes(memory, buf, &path)?; Ok(n) } #[instrument(skip(self, memory))] async fn path_remove_directory( &mut self, memory: &mut GuestMemory<'_>, dirfd: types::Fd, path: GuestPtr, ) -> Result<(), types::Error> { let dirfd = self.get_dir_fd(dirfd)?; let path = read_string(memory, path)?; self.as_wasi_impl().remove_directory_at(dirfd, path).await?; Ok(()) } /// Rename a file or directory. /// NOTE: This is similar to `renameat` in POSIX. #[instrument(skip(self, memory))] async fn path_rename( &mut self, memory: &mut GuestMemory<'_>, src_fd: types::Fd, src_path: GuestPtr, dest_fd: types::Fd, dest_path: GuestPtr, ) -> Result<(), types::Error> { let src_fd = self.get_dir_fd(src_fd)?; let dest_fd = self.get_dir_fd(dest_fd)?; let src_path = read_string(memory, src_path)?; let dest_path = read_string(memory, dest_path)?; self.as_wasi_impl() .rename_at(src_fd, src_path, dest_fd, dest_path) .await?; Ok(()) } #[instrument(skip(self, memory))] async fn path_symlink( &mut self, memory: &mut GuestMemory<'_>, src_path: GuestPtr, dirfd: types::Fd, dest_path: GuestPtr, ) -> Result<(), types::Error> { let dirfd = self.get_dir_fd(dirfd)?; let src_path = read_string(memory, src_path)?; let dest_path = read_string(memory, dest_path)?; self.as_wasi_impl() .symlink_at(dirfd.borrowed(), src_path, dest_path) .await?; Ok(()) } #[instrument(skip(self, memory))] async fn path_unlink_file( &mut self, memory: &mut GuestMemory<'_>, dirfd: types::Fd, path: GuestPtr, ) -> Result<(), types::Error> { let dirfd = self.get_dir_fd(dirfd)?; let path = memory.as_cow_str(path)?.into_owned(); self.as_wasi_impl() .unlink_file_at(dirfd.borrowed(), path) .await?; Ok(()) } #[instrument(skip(self, memory))] async fn poll_oneoff( &mut self, memory: &mut GuestMemory<'_>, subs: GuestPtr, events: GuestPtr, nsubscriptions: types::Size, ) -> Result { if nsubscriptions == 0 { // Indefinite sleeping is not supported in preview1. return Err(types::Errno::Inval.into()); } // This is a special case where `poll_oneoff` is just sleeping // on a single relative timer event. This special case was added // after experimental observations showed that std::thread::sleep // results in more consistent sleep times. This design ensures that // wasmtime can handle real-time requirements more accurately. if nsubscriptions == 1 { let sub = memory.read(subs)?; if let types::SubscriptionU::Clock(clocksub) = sub.u { if !clocksub .flags .contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME) && self.ctx().allow_blocking_current_thread { std::thread::sleep(std::time::Duration::from_nanos(clocksub.timeout)); memory.write( events, types::Event { userdata: sub.userdata, error: types::Errno::Success, type_: types::Eventtype::Clock, fd_readwrite: types::EventFdReadwrite { flags: types::Eventrwflags::empty(), nbytes: 1, }, }, )?; return Ok(1); } } } let subs = subs.as_array(nsubscriptions); let events = events.as_array(nsubscriptions); let n = usize::try_from(nsubscriptions).unwrap_or(usize::MAX); let mut pollables = Vec::with_capacity(n); for sub in subs.iter() { let sub = memory.read(sub?)?; let p = match sub.u { types::SubscriptionU::Clock(types::SubscriptionClock { id, timeout, flags, .. }) => { let absolute = flags.contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME); let (timeout, absolute) = match id { types::Clockid::Monotonic => (timeout, absolute), types::Clockid::Realtime if !absolute => (timeout, false), types::Clockid::Realtime => { let now = wall_clock::Host::now(&mut self.as_wasi_impl()) .context("failed to call `wall_clock::now`") .map_err(types::Error::trap)?; // Convert `timeout` to `Datetime` format. let seconds = timeout / 1_000_000_000; let nanoseconds = timeout % 1_000_000_000; let timeout = if now.seconds < seconds || now.seconds == seconds && u64::from(now.nanoseconds) < nanoseconds { // `now` is less than `timeout`, which is expressible as u64, // subtract the nanosecond counts directly now.seconds * 1_000_000_000 + u64::from(now.nanoseconds) - timeout } else { 0 }; (timeout, false) } _ => return Err(types::Errno::Inval.into()), }; if absolute { monotonic_clock::Host::subscribe_instant(&mut self.as_wasi_impl(), timeout) .context("failed to call `monotonic_clock::subscribe_instant`") .map_err(types::Error::trap)? } else { monotonic_clock::Host::subscribe_duration(&mut self.as_wasi_impl(), timeout) .context("failed to call `monotonic_clock::subscribe_duration`") .map_err(types::Error::trap)? } } types::SubscriptionU::FdRead(types::SubscriptionFdReadwrite { file_descriptor, }) => { let stream = { let t = self.transact()?; let desc = t.get_descriptor(file_descriptor)?; match desc { Descriptor::Stdin { stream, .. } => stream.borrowed(), Descriptor::File(File { fd, position, .. }) => { let pos = position.load(Ordering::Relaxed); let fd = fd.borrowed(); drop(t); self.as_wasi_impl().read_via_stream(fd, pos)? } // TODO: Support sockets _ => return Err(types::Errno::Badf.into()), } }; streams::HostInputStream::subscribe(&mut self.as_wasi_impl(), stream) .context("failed to call `subscribe` on `input-stream`") .map_err(types::Error::trap)? } types::SubscriptionU::FdWrite(types::SubscriptionFdReadwrite { file_descriptor, }) => { let stream = { let t = self.transact()?; let desc = t.get_descriptor(file_descriptor)?; match desc { Descriptor::Stdout { stream, .. } | Descriptor::Stderr { stream, .. } => stream.borrowed(), Descriptor::File(File { fd, position, append, .. }) => { let fd = fd.borrowed(); let position = position.clone(); let append = *append; drop(t); if append { self.as_wasi_impl().append_via_stream(fd)? } else { let pos = position.load(Ordering::Relaxed); self.as_wasi_impl().write_via_stream(fd, pos)? } } // TODO: Support sockets _ => return Err(types::Errno::Badf.into()), } }; streams::HostOutputStream::subscribe(&mut self.as_wasi_impl(), stream) .context("failed to call `subscribe` on `output-stream`") .map_err(types::Error::trap)? } }; pollables.push(p); } let ready: HashSet<_> = self .as_wasi_impl() .poll(pollables) .await .context("failed to call `poll-oneoff`") .map_err(types::Error::trap)? .into_iter() .collect(); let mut count: types::Size = 0; for (sub, event) in (0..) .zip(subs.iter()) .filter_map(|(idx, sub)| ready.contains(&idx).then_some(sub)) .zip(events.iter()) { let sub = memory.read(sub?)?; let event = event?; let e = match sub.u { types::SubscriptionU::Clock(..) => types::Event { userdata: sub.userdata, error: types::Errno::Success, type_: types::Eventtype::Clock, fd_readwrite: types::EventFdReadwrite { flags: types::Eventrwflags::empty(), nbytes: 0, }, }, types::SubscriptionU::FdRead(types::SubscriptionFdReadwrite { file_descriptor, }) => { let t = self.transact()?; let desc = t.get_descriptor(file_descriptor)?; match desc { Descriptor::Stdin { .. } => types::Event { userdata: sub.userdata, error: types::Errno::Success, type_: types::Eventtype::FdRead, fd_readwrite: types::EventFdReadwrite { flags: types::Eventrwflags::empty(), nbytes: 1, }, }, Descriptor::File(File { fd, position, .. }) => { let fd = fd.borrowed(); let position = position.clone(); drop(t); match self.as_wasi_impl().stat(fd).await? { filesystem::DescriptorStat { size, .. } => { let pos = position.load(Ordering::Relaxed); let nbytes = size.saturating_sub(pos); types::Event { userdata: sub.userdata, error: types::Errno::Success, type_: types::Eventtype::FdRead, fd_readwrite: types::EventFdReadwrite { flags: if nbytes == 0 { types::Eventrwflags::FD_READWRITE_HANGUP } else { types::Eventrwflags::empty() }, nbytes: 1, }, } } } } // TODO: Support sockets _ => return Err(types::Errno::Badf.into()), } } types::SubscriptionU::FdWrite(types::SubscriptionFdReadwrite { file_descriptor, }) => { let t = self.transact()?; let desc = t.get_descriptor(file_descriptor)?; match desc { Descriptor::Stdout { .. } | Descriptor::Stderr { .. } => types::Event { userdata: sub.userdata, error: types::Errno::Success, type_: types::Eventtype::FdWrite, fd_readwrite: types::EventFdReadwrite { flags: types::Eventrwflags::empty(), nbytes: 1, }, }, Descriptor::File(_) => types::Event { userdata: sub.userdata, error: types::Errno::Success, type_: types::Eventtype::FdWrite, fd_readwrite: types::EventFdReadwrite { flags: types::Eventrwflags::empty(), nbytes: 1, }, }, // TODO: Support sockets _ => return Err(types::Errno::Badf.into()), } } }; memory.write(event, e)?; count = count .checked_add(1) .ok_or_else(|| types::Error::from(types::Errno::Overflow))? } Ok(count) } #[instrument(skip(self, _memory))] fn proc_exit( &mut self, _memory: &mut GuestMemory<'_>, status: types::Exitcode, ) -> anyhow::Error { // Check that the status is within WASI's range. if status >= 126 { return anyhow::Error::msg("exit with invalid exit status outside of [0..126)"); } crate::I32Exit(status as i32).into() } #[instrument(skip(self, _memory))] fn proc_raise( &mut self, _memory: &mut GuestMemory<'_>, _sig: types::Signal, ) -> Result<(), types::Error> { Err(types::Errno::Notsup.into()) } #[instrument(skip(self, _memory))] fn sched_yield(&mut self, _memory: &mut GuestMemory<'_>) -> Result<(), types::Error> { // No such thing in preview 2. Intentionally left empty. Ok(()) } #[instrument(skip(self, memory))] fn random_get( &mut self, memory: &mut GuestMemory<'_>, buf: GuestPtr, buf_len: types::Size, ) -> Result<(), types::Error> { let rand = self .as_wasi_impl() .get_random_bytes(buf_len.into()) .context("failed to call `get-random-bytes`") .map_err(types::Error::trap)?; write_bytes(memory, buf, &rand)?; Ok(()) } #[allow(unused_variables)] #[instrument(skip(self, _memory))] fn sock_accept( &mut self, _memory: &mut GuestMemory<'_>, fd: types::Fd, flags: types::Fdflags, ) -> Result { tracing::warn!("preview1 sock_accept is not implemented"); self.transact()?.get_descriptor(fd)?; Err(types::Errno::Notsock.into()) } #[allow(unused_variables)] #[instrument(skip(self, _memory))] fn sock_recv( &mut self, _memory: &mut GuestMemory<'_>, fd: types::Fd, ri_data: types::IovecArray, ri_flags: types::Riflags, ) -> Result<(types::Size, types::Roflags), types::Error> { tracing::warn!("preview1 sock_recv is not implemented"); self.transact()?.get_descriptor(fd)?; Err(types::Errno::Notsock.into()) } #[allow(unused_variables)] #[instrument(skip(self, _memory))] fn sock_send( &mut self, _memory: &mut GuestMemory<'_>, fd: types::Fd, si_data: types::CiovecArray, _si_flags: types::Siflags, ) -> Result { tracing::warn!("preview1 sock_send is not implemented"); self.transact()?.get_descriptor(fd)?; Err(types::Errno::Notsock.into()) } #[allow(unused_variables)] #[instrument(skip(self, _memory))] fn sock_shutdown( &mut self, _memory: &mut GuestMemory<'_>, fd: types::Fd, how: types::Sdflags, ) -> Result<(), types::Error> { tracing::warn!("preview1 sock_shutdown is not implemented"); self.transact()?.get_descriptor(fd)?; Err(types::Errno::Notsock.into()) } } trait ResourceExt { fn borrowed(&self) -> Resource; } impl ResourceExt for Resource { fn borrowed(&self) -> Resource { Resource::new_borrow(self.rep()) } }