diff --git a/crates/component-macro/src/bindgen.rs b/crates/component-macro/src/bindgen.rs index a87616e55d82..c5a9d2920ce8 100644 --- a/crates/component-macro/src/bindgen.rs +++ b/crates/component-macro/src/bindgen.rs @@ -1,10 +1,11 @@ use proc_macro2::{Span, TokenStream}; +use quote::ToTokens; use std::collections::HashMap; use std::collections::HashSet; use std::path::{Path, PathBuf}; use syn::parse::{Error, Parse, ParseStream, Result}; use syn::punctuated::Punctuated; -use syn::{braced, token, Ident, Token}; +use syn::{braced, token, Token}; use wasmtime_wit_bindgen::{AsyncConfig, Opts, Ownership, TrappableError}; use wit_parser::{PackageId, Resolve, UnresolvedPackage, WorldId}; @@ -335,7 +336,7 @@ fn trappable_error_field_parse(input: ParseStream<'_>) -> Result input.parse::()?; let wit_type_name = ident_or_str(input)?; input.parse::()?; - let rust_type_name = input.parse::()?.to_string(); + let rust_type_name = input.parse::()?.to_token_stream().to_string(); Ok(TrappableError { wit_package_path, wit_type_name, diff --git a/crates/component-macro/tests/codegen.rs b/crates/component-macro/tests/codegen.rs index 90d58bd890f2..49bb8c6d5726 100644 --- a/crates/component-macro/tests/codegen.rs +++ b/crates/component-macro/tests/codegen.rs @@ -106,3 +106,45 @@ mod with_key_and_resources { } } } + +mod trappable_errors { + wasmtime::component::bindgen!({ + inline: " + package demo:pkg; + + interface a { + type b = u64; + + z1: func() -> result<_, b>; + z2: func() -> result<_, b>; + } + + interface b { + use a.{b}; + z: func() -> result<_, b>; + } + + interface c { + type b = u64; + } + + interface d { + use c.{b}; + z: func() -> result<_, b>; + } + + world foo { + import a; + import b; + import d; + } + ", + trappable_error_type: { + "demo:pkg/a"::"b": MyX, + "demo:pkg/c"::"b": MyX, + }, + }); + + #[allow(dead_code)] + type MyX = u32; +} diff --git a/crates/wasi-http/wit/deps/filesystem/types.wit b/crates/wasi-http/wit/deps/filesystem/types.wit index 3f69bf997a29..aecdd0ef354b 100644 --- a/crates/wasi-http/wit/deps/filesystem/types.wit +++ b/crates/wasi-http/wit/deps/filesystem/types.wit @@ -23,7 +23,7 @@ /// /// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md interface types { - use wasi:io/streams.{input-stream, output-stream} + use wasi:io/streams.{input-stream, output-stream, error} use wasi:clocks/wall-clock.{datetime} /// File size or length of a region within a file. @@ -795,4 +795,16 @@ interface types { /// Read a single directory entry from a `directory-entry-stream`. read-directory-entry: func() -> result, error-code> } + + /// Attempts to extract a filesystem-related `error-code` from the stream + /// `error` provided. + /// + /// Stream operations which return `stream-error::last-operation-failed` + /// have a payload with more information about the operation that failed. + /// This payload can be passed through to this function to see if there's + /// filesystem-related information about the error to return. + /// + /// Note that this function is fallible because not all stream-related + /// errors are filesystem-related errors. + filesystem-error-code: func(err: borrow) -> option } diff --git a/crates/wasi-http/wit/deps/io/streams.wit b/crates/wasi-http/wit/deps/io/streams.wit index 8240507976f7..55562d1cbfce 100644 --- a/crates/wasi-http/wit/deps/io/streams.wit +++ b/crates/wasi-http/wit/deps/io/streams.wit @@ -9,15 +9,37 @@ interface streams { use poll.{pollable} /// An error for input-stream and output-stream operations. - enum stream-error { + variant stream-error { /// The last operation (a write or flush) failed before completion. - last-operation-failed, + /// + /// More information is available in the `error` payload. + last-operation-failed(error), /// The stream is closed: no more input will be accepted by the /// stream. A closed output-stream will return this error on all /// future operations. closed } + /// Contextual error information about the last failure that happened on + /// a read, write, or flush from an `input-stream` or `output-stream`. + /// + /// This type is returned through the `stream-error` type whenever an + /// operation on a stream directly fails or an error is discovered + /// after-the-fact, for example when a write's failure shows up through a + /// later `flush` or `check-write`. + /// + /// Interfaces such as `wasi:filesystem/types` provide functionality to + /// further "downcast" this error into interface-specific error information. + resource error { + /// Returns a string that's suitable to assist humans in debugging this + /// error. + /// + /// The returned string will change across platforms and hosts which + /// means that parsing it, for example, would be a + /// platform-compatibility hazard. + to-debug-string: func() -> string + } + /// An input bytestream. /// /// `input-stream`s are *non-blocking* to the extent practical on underlying diff --git a/crates/wasi-preview1-component-adapter/src/lib.rs b/crates/wasi-preview1-component-adapter/src/lib.rs index acbf82eea54d..56ae3294f4d0 100644 --- a/crates/wasi-preview1-component-adapter/src/lib.rs +++ b/crates/wasi-preview1-component-adapter/src/lib.rs @@ -900,7 +900,9 @@ pub unsafe extern "C" fn fd_read( *nread = 0; return Ok(()); } - Err(_) => Err(ERRNO_IO)?, + Err(streams::StreamError::LastOperationFailed(e)) => { + Err(stream_error_to_errno(e))? + } }; assert_eq!(data.as_ptr(), ptr); @@ -925,6 +927,13 @@ pub unsafe extern "C" fn fd_read( }) } +fn stream_error_to_errno(err: streams::Error) -> Errno { + match filesystem::filesystem_error_code(&err) { + Some(code) => code.into(), + None => ERRNO_IO, + } +} + /// Read directory entries from a directory. /// When successful, the contents of the output buffer consist of a sequence of /// directory entries. Each directory entry consists of a `dirent` object, @@ -2160,7 +2169,10 @@ impl BlockingMode { bytes = rest; match output_stream.blocking_write_and_flush(chunk) { Ok(()) => {} - Err(_) => return Err(ERRNO_IO), + Err(streams::StreamError::Closed) => return Err(ERRNO_IO), + Err(streams::StreamError::LastOperationFailed(e)) => { + return Err(stream_error_to_errno(e)) + } } } Ok(total) @@ -2170,7 +2182,9 @@ impl BlockingMode { let permit = match output_stream.check_write() { Ok(n) => n, Err(streams::StreamError::Closed) => 0, - Err(streams::StreamError::LastOperationFailed) => return Err(ERRNO_IO), + Err(streams::StreamError::LastOperationFailed(e)) => { + return Err(stream_error_to_errno(e)) + } }; let len = bytes.len().min(permit as usize); @@ -2181,13 +2195,17 @@ impl BlockingMode { match output_stream.write(&bytes[..len]) { Ok(_) => {} Err(streams::StreamError::Closed) => return Ok(0), - Err(streams::StreamError::LastOperationFailed) => return Err(ERRNO_IO), + Err(streams::StreamError::LastOperationFailed(e)) => { + return Err(stream_error_to_errno(e)) + } } match output_stream.blocking_flush() { Ok(_) => {} Err(streams::StreamError::Closed) => return Ok(0), - Err(streams::StreamError::LastOperationFailed) => return Err(ERRNO_IO), + Err(streams::StreamError::LastOperationFailed(e)) => { + return Err(stream_error_to_errno(e)) + } } Ok(len) diff --git a/crates/wasi/src/preview2/command.rs b/crates/wasi/src/preview2/command.rs index 7702e2a706da..811e3cf18e2c 100644 --- a/crates/wasi/src/preview2/command.rs +++ b/crates/wasi/src/preview2/command.rs @@ -4,10 +4,6 @@ wasmtime::component::bindgen!({ world: "wasi:cli/command", tracing: true, async: true, - trappable_error_type: { - "wasi:filesystem/types"::"error-code": Error, - "wasi:sockets/tcp"::"error-code": Error, - }, with: { "wasi:filesystem/types": crate::preview2::bindings::filesystem::types, "wasi:filesystem/preopens": crate::preview2::bindings::filesystem::preopens, @@ -65,10 +61,6 @@ pub mod sync { world: "wasi:cli/command", tracing: true, async: false, - trappable_error_type: { - "wasi:filesystem/types"::"error-code": Error, - "wasi:sockets/tcp"::"error-code": Error, - }, with: { "wasi:filesystem/types": crate::preview2::bindings::sync_io::filesystem::types, "wasi:filesystem/preopens": crate::preview2::bindings::filesystem::preopens, diff --git a/crates/wasi/src/preview2/error.rs b/crates/wasi/src/preview2/error.rs index c3493b238bee..ccae912d4668 100644 --- a/crates/wasi/src/preview2/error.rs +++ b/crates/wasi/src/preview2/error.rs @@ -1,4 +1,6 @@ +use std::error::Error; use std::fmt; +use std::marker; /// An error returned from the `proc_exit` host syscall. /// @@ -14,3 +16,75 @@ impl fmt::Display for I32Exit { } impl std::error::Error for I32Exit {} + +/// A helper error type used by many other modules through type aliases. +/// +/// This type is an `Error` itself and is intended to be a representation of +/// either: +/// +/// * A custom error type `T` +/// * A trap, represented as `anyhow::Error` +/// +/// This error is created through either the `::trap` constructor representing a +/// full-fledged trap or the `From` constructor which is intended to be used +/// with `?`. The goal is to make normal errors `T` "automatic" but enable error +/// paths to return a `::trap` error optionally still as necessary without extra +/// boilerplate everywhere else. +/// +/// Note that this type isn't used directly but instead is intended to be used +/// as: +/// +/// ```rust,ignore +/// type MyError = TrappableError; +/// ``` +/// +/// where `MyError` is what you'll use throughout bindings code and +/// `bindgen::TheError` is the type that this represents as generated by the +/// `bindgen!` macro. +#[repr(transparent)] +pub struct TrappableError { + err: anyhow::Error, + _marker: marker::PhantomData, +} + +impl TrappableError { + pub fn trap(err: impl Into) -> TrappableError { + TrappableError { + err: err.into(), + _marker: marker::PhantomData, + } + } + + pub fn downcast(self) -> anyhow::Result + where + T: Error + Send + Sync + 'static, + { + self.err.downcast() + } +} + +impl From for TrappableError +where + T: Error + Send + Sync + 'static, +{ + fn from(error: T) -> Self { + Self { + err: error.into(), + _marker: marker::PhantomData, + } + } +} + +impl fmt::Debug for TrappableError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.err.fmt(f) + } +} + +impl fmt::Display for TrappableError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.err.fmt(f) + } +} + +impl Error for TrappableError {} diff --git a/crates/wasi/src/preview2/filesystem.rs b/crates/wasi/src/preview2/filesystem.rs index 9b8467a86ed3..ac788414995b 100644 --- a/crates/wasi/src/preview2/filesystem.rs +++ b/crates/wasi/src/preview2/filesystem.rs @@ -1,6 +1,7 @@ use crate::preview2::bindings::filesystem::types; use crate::preview2::{ - spawn_blocking, AbortOnDropJoinHandle, HostOutputStream, StreamError, Subscribe, + spawn_blocking, AbortOnDropJoinHandle, HostOutputStream, StreamError, Subscribe, TableError, + TrappableError, }; use anyhow::anyhow; use bytes::{Bytes, BytesMut}; @@ -8,6 +9,22 @@ use std::io; use std::mem; use std::sync::Arc; +pub type FsResult = Result; + +pub type FsError = TrappableError; + +impl From for FsError { + fn from(error: TableError) -> Self { + Self::trap(error) + } +} + +impl From for FsError { + fn from(error: io::Error) -> Self { + types::ErrorCode::from(error).into() + } +} + pub enum Descriptor { File(File), Dir(Dir), @@ -276,24 +293,22 @@ impl Subscribe for FileOutputStream { } pub struct ReaddirIterator( - std::sync::Mutex< - Box> + Send + 'static>, - >, + std::sync::Mutex> + Send + 'static>>, ); impl ReaddirIterator { pub(crate) fn new( - i: impl Iterator> + Send + 'static, + i: impl Iterator> + Send + 'static, ) -> Self { ReaddirIterator(std::sync::Mutex::new(Box::new(i))) } - pub(crate) fn next(&self) -> Result, types::Error> { + pub(crate) fn next(&self) -> FsResult> { self.0.lock().unwrap().next().transpose() } } impl IntoIterator for ReaddirIterator { - type Item = Result; + type Item = FsResult; type IntoIter = Box + Send>; fn into_iter(self) -> Self::IntoIter { diff --git a/crates/wasi/src/preview2/host/filesystem.rs b/crates/wasi/src/preview2/host/filesystem.rs index a9a57e46c489..b0c77db3860b 100644 --- a/crates/wasi/src/preview2/host/filesystem.rs +++ b/crates/wasi/src/preview2/host/filesystem.rs @@ -1,23 +1,17 @@ use crate::preview2::bindings::clocks::wall_clock; -use crate::preview2::bindings::filesystem::types::{HostDescriptor, HostDirectoryEntryStream}; -use crate::preview2::bindings::filesystem::{preopens, types}; +use crate::preview2::bindings::filesystem::preopens; +use crate::preview2::bindings::filesystem::types::{ + self, ErrorCode, HostDescriptor, HostDirectoryEntryStream, +}; use crate::preview2::bindings::io::streams::{InputStream, OutputStream}; use crate::preview2::filesystem::{Descriptor, Dir, File, ReaddirIterator}; use crate::preview2::filesystem::{FileInputStream, FileOutputStream}; -use crate::preview2::{DirPerms, FilePerms, Table, TableError, WasiView}; +use crate::preview2::{DirPerms, FilePerms, FsError, FsResult, Table, WasiView}; use anyhow::Context; use wasmtime::component::Resource; -use types::ErrorCode; - mod sync; -impl From for types::Error { - fn from(error: TableError) -> Self { - Self::trap(error.into()) - } -} - impl preopens::Host for T { fn get_directories( &mut self, @@ -35,7 +29,26 @@ impl preopens::Host for T { } #[async_trait::async_trait] -impl types::Host for T {} +impl types::Host for T { + fn convert_error_code(&mut self, err: FsError) -> anyhow::Result { + err.downcast() + } + + fn filesystem_error_code( + &mut self, + err: Resource, + ) -> anyhow::Result> { + let err = self.table_mut().get_resource(&err)?; + + // Currently `err` always comes from the stream implementation which + // uses standard reads/writes so only check for `std::io::Error` here. + if let Some(err) = err.downcast_ref::() { + return Ok(Some(ErrorCode::from(err))); + } + + Ok(None) + } +} #[async_trait::async_trait] impl HostDescriptor for T { @@ -45,7 +58,7 @@ impl HostDescriptor for T { offset: types::Filesize, len: types::Filesize, advice: types::Advice, - ) -> Result<(), types::Error> { + ) -> FsResult<()> { use system_interface::fs::{Advice as A, FileIoExt}; use types::Advice; @@ -64,7 +77,7 @@ impl HostDescriptor for T { Ok(()) } - async fn sync_data(&mut self, fd: Resource) -> Result<(), types::Error> { + async fn sync_data(&mut self, fd: Resource) -> FsResult<()> { let table = self.table(); match table.get_resource(&fd)? { @@ -94,7 +107,7 @@ impl HostDescriptor for T { async fn get_flags( &mut self, fd: Resource, - ) -> Result { + ) -> FsResult { use system_interface::fs::{FdFlags, GetSetFdFlags}; use types::DescriptorFlags; @@ -142,7 +155,7 @@ impl HostDescriptor for T { async fn get_type( &mut self, fd: Resource, - ) -> Result { + ) -> FsResult { let table = self.table(); match table.get_resource(&fd)? { @@ -158,7 +171,7 @@ impl HostDescriptor for T { &mut self, fd: Resource, size: types::Filesize, - ) -> Result<(), types::Error> { + ) -> FsResult<()> { let f = self.table().get_resource(&fd)?.file()?; if !f.perms.contains(FilePerms::WRITE) { Err(ErrorCode::NotPermitted)?; @@ -172,7 +185,7 @@ impl HostDescriptor for T { fd: Resource, atim: types::NewTimestamp, mtim: types::NewTimestamp, - ) -> Result<(), types::Error> { + ) -> FsResult<()> { use fs_set_times::SetTimes; let table = self.table(); @@ -203,7 +216,7 @@ impl HostDescriptor for T { fd: Resource, len: types::Filesize, offset: types::Filesize, - ) -> Result<(Vec, bool), types::Error> { + ) -> FsResult<(Vec, bool)> { use std::io::IoSliceMut; use system_interface::fs::FileIoExt; @@ -241,7 +254,7 @@ impl HostDescriptor for T { fd: Resource, buf: Vec, offset: types::Filesize, - ) -> Result { + ) -> FsResult { use std::io::IoSlice; use system_interface::fs::FileIoExt; @@ -261,7 +274,7 @@ impl HostDescriptor for T { async fn read_directory( &mut self, fd: Resource, - ) -> Result, types::Error> { + ) -> FsResult> { let table = self.table_mut(); let d = table.get_resource(&fd)?.dir()?; if !d.perms.contains(DirPerms::READ) { @@ -317,13 +330,13 @@ impl HostDescriptor for T { }); let entries = entries.map(|r| match r { Ok(r) => Ok(r), - Err(ReaddirError::Io(e)) => Err(types::Error::from(e)), + Err(ReaddirError::Io(e)) => Err(e.into()), Err(ReaddirError::IllegalSequence) => Err(ErrorCode::IllegalByteSequence.into()), }); Ok(table.push_resource(ReaddirIterator::new(entries))?) } - async fn sync(&mut self, fd: Resource) -> Result<(), types::Error> { + async fn sync(&mut self, fd: Resource) -> FsResult<()> { let table = self.table(); match table.get_resource(&fd)? { @@ -354,7 +367,7 @@ impl HostDescriptor for T { &mut self, fd: Resource, path: String, - ) -> Result<(), types::Error> { + ) -> FsResult<()> { let table = self.table(); let d = table.get_resource(&fd)?.dir()?; if !d.perms.contains(DirPerms::MUTATE) { @@ -364,10 +377,7 @@ impl HostDescriptor for T { Ok(()) } - async fn stat( - &mut self, - fd: Resource, - ) -> Result { + async fn stat(&mut self, fd: Resource) -> FsResult { let table = self.table(); match table.get_resource(&fd)? { Descriptor::File(f) => { @@ -388,7 +398,7 @@ impl HostDescriptor for T { fd: Resource, path_flags: types::PathFlags, path: String, - ) -> Result { + ) -> FsResult { let table = self.table(); let d = table.get_resource(&fd)?.dir()?; if !d.perms.contains(DirPerms::READ) { @@ -410,7 +420,7 @@ impl HostDescriptor for T { path: String, atim: types::NewTimestamp, mtim: types::NewTimestamp, - ) -> Result<(), types::Error> { + ) -> FsResult<()> { use cap_fs_ext::DirExt; let table = self.table(); @@ -450,7 +460,7 @@ impl HostDescriptor for T { old_path: String, new_descriptor: Resource, new_path: String, - ) -> Result<(), types::Error> { + ) -> FsResult<()> { let table = self.table(); let old_dir = table.get_resource(&fd)?.dir()?; if !old_dir.perms.contains(DirPerms::MUTATE) { @@ -480,7 +490,7 @@ impl HostDescriptor for T { // TODO: These are the permissions to use when creating a new file. // Not implemented yet. _mode: types::Modes, - ) -> Result, types::Error> { + ) -> FsResult> { use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt, OpenOptionsMaybeDirExt}; use system_interface::fs::{FdFlags, GetSetFdFlags}; use types::{DescriptorFlags, OpenFlags}; @@ -605,7 +615,7 @@ impl HostDescriptor for T { &mut self, fd: Resource, path: String, - ) -> Result { + ) -> FsResult { let table = self.table(); let d = table.get_resource(&fd)?.dir()?; if !d.perms.contains(DirPerms::READ) { @@ -622,7 +632,7 @@ impl HostDescriptor for T { &mut self, fd: Resource, path: String, - ) -> Result<(), types::Error> { + ) -> FsResult<()> { let table = self.table(); let d = table.get_resource(&fd)?.dir()?; if !d.perms.contains(DirPerms::MUTATE) { @@ -637,7 +647,7 @@ impl HostDescriptor for T { old_path: String, new_fd: Resource, new_path: String, - ) -> Result<(), types::Error> { + ) -> FsResult<()> { let table = self.table(); let old_dir = table.get_resource(&fd)?.dir()?; if !old_dir.perms.contains(DirPerms::MUTATE) { @@ -658,7 +668,7 @@ impl HostDescriptor for T { fd: Resource, src_path: String, dest_path: String, - ) -> Result<(), types::Error> { + ) -> FsResult<()> { // On windows, Dir.symlink is provided by DirExt #[cfg(windows)] use cap_fs_ext::DirExt; @@ -676,7 +686,7 @@ impl HostDescriptor for T { &mut self, fd: Resource, path: String, - ) -> Result<(), types::Error> { + ) -> FsResult<()> { use cap_fs_ext::DirExt; let table = self.table(); @@ -694,7 +704,7 @@ impl HostDescriptor for T { _path_flags: types::PathFlags, _path: String, _access: types::AccessType, - ) -> Result<(), types::Error> { + ) -> FsResult<()> { todo!("filesystem access_at is not implemented") } @@ -704,7 +714,7 @@ impl HostDescriptor for T { _path_flags: types::PathFlags, _path: String, _mode: types::Modes, - ) -> Result<(), types::Error> { + ) -> FsResult<()> { todo!("filesystem change_file_permissions_at is not implemented") } @@ -714,36 +724,27 @@ impl HostDescriptor for T { _path_flags: types::PathFlags, _path: String, _mode: types::Modes, - ) -> Result<(), types::Error> { + ) -> FsResult<()> { todo!("filesystem change_directory_permissions_at is not implemented") } - async fn lock_shared(&mut self, _fd: Resource) -> Result<(), types::Error> { + async fn lock_shared(&mut self, _fd: Resource) -> FsResult<()> { todo!("filesystem lock_shared is not implemented") } - async fn lock_exclusive( - &mut self, - _fd: Resource, - ) -> Result<(), types::Error> { + async fn lock_exclusive(&mut self, _fd: Resource) -> FsResult<()> { todo!("filesystem lock_exclusive is not implemented") } - async fn try_lock_shared( - &mut self, - _fd: Resource, - ) -> Result<(), types::Error> { + async fn try_lock_shared(&mut self, _fd: Resource) -> FsResult<()> { todo!("filesystem try_lock_shared is not implemented") } - async fn try_lock_exclusive( - &mut self, - _fd: Resource, - ) -> Result<(), types::Error> { + async fn try_lock_exclusive(&mut self, _fd: Resource) -> FsResult<()> { todo!("filesystem try_lock_exclusive is not implemented") } - async fn unlock(&mut self, _fd: Resource) -> Result<(), types::Error> { + async fn unlock(&mut self, _fd: Resource) -> FsResult<()> { todo!("filesystem unlock is not implemented") } @@ -751,7 +752,7 @@ impl HostDescriptor for T { &mut self, fd: Resource, offset: types::Filesize, - ) -> Result, types::Error> { + ) -> FsResult> { // Trap if fd lookup fails: let f = self.table().get_resource(&fd)?.file()?; @@ -774,7 +775,7 @@ impl HostDescriptor for T { &mut self, fd: Resource, offset: types::Filesize, - ) -> Result, types::Error> { + ) -> FsResult> { // Trap if fd lookup fails: let f = self.table().get_resource(&fd)?.file()?; @@ -798,7 +799,7 @@ impl HostDescriptor for T { fn append_via_stream( &mut self, fd: Resource, - ) -> Result, types::Error> { + ) -> FsResult> { // Trap if fd lookup fails: let f = self.table().get_resource(&fd)?.file()?; @@ -847,7 +848,7 @@ impl HostDescriptor for T { async fn metadata_hash( &mut self, fd: Resource, - ) -> Result { + ) -> FsResult { let table = self.table(); let meta = get_descriptor_metadata(table, fd).await?; Ok(calculate_metadata_hash(&meta)) @@ -857,7 +858,7 @@ impl HostDescriptor for T { fd: Resource, path_flags: types::PathFlags, path: String, - ) -> Result { + ) -> FsResult { let table = self.table(); let d = table.get_resource(&fd)?.dir()?; // No permissions check on metadata: if dir opened, allowed to stat it @@ -879,7 +880,7 @@ impl HostDirectoryEntryStream for T { async fn read_directory_entry( &mut self, stream: Resource, - ) -> Result, types::Error> { + ) -> FsResult> { let table = self.table(); let readdir = table.get_resource(&stream)?; readdir.next() @@ -894,7 +895,7 @@ impl HostDirectoryEntryStream for T { async fn get_descriptor_metadata( table: &Table, fd: Resource, -) -> Result { +) -> FsResult { match table.get_resource(&fd)? { Descriptor::File(f) => { // No permissions check on metadata: if opened, allowed to stat it @@ -932,100 +933,109 @@ fn calculate_metadata_hash(meta: &cap_std::fs::Metadata) -> types::MetadataHashV } #[cfg(unix)] -fn from_raw_os_error(err: Option) -> Option { +fn from_raw_os_error(err: Option) -> Option { use rustix::io::Errno as RustixErrno; if err.is_none() { return None; } Some(match RustixErrno::from_raw_os_error(err.unwrap()) { - RustixErrno::PIPE => ErrorCode::Pipe.into(), - RustixErrno::PERM => ErrorCode::NotPermitted.into(), - RustixErrno::NOENT => ErrorCode::NoEntry.into(), - RustixErrno::NOMEM => ErrorCode::InsufficientMemory.into(), - RustixErrno::IO => ErrorCode::Io.into(), - RustixErrno::BADF => ErrorCode::BadDescriptor.into(), - RustixErrno::BUSY => ErrorCode::Busy.into(), - RustixErrno::ACCESS => ErrorCode::Access.into(), - RustixErrno::NOTDIR => ErrorCode::NotDirectory.into(), - RustixErrno::ISDIR => ErrorCode::IsDirectory.into(), - RustixErrno::INVAL => ErrorCode::Invalid.into(), - RustixErrno::EXIST => ErrorCode::Exist.into(), - RustixErrno::FBIG => ErrorCode::FileTooLarge.into(), - RustixErrno::NOSPC => ErrorCode::InsufficientSpace.into(), - RustixErrno::SPIPE => ErrorCode::InvalidSeek.into(), - RustixErrno::MLINK => ErrorCode::TooManyLinks.into(), - RustixErrno::NAMETOOLONG => ErrorCode::NameTooLong.into(), - RustixErrno::NOTEMPTY => ErrorCode::NotEmpty.into(), - RustixErrno::LOOP => ErrorCode::Loop.into(), - RustixErrno::OVERFLOW => ErrorCode::Overflow.into(), - RustixErrno::ILSEQ => ErrorCode::IllegalByteSequence.into(), - RustixErrno::NOTSUP => ErrorCode::Unsupported.into(), - RustixErrno::ALREADY => ErrorCode::Already.into(), - RustixErrno::INPROGRESS => ErrorCode::InProgress.into(), - RustixErrno::INTR => ErrorCode::Interrupted.into(), - - // On some platforms.into(), these have the same value as other errno values. + RustixErrno::PIPE => ErrorCode::Pipe, + RustixErrno::PERM => ErrorCode::NotPermitted, + RustixErrno::NOENT => ErrorCode::NoEntry, + RustixErrno::NOMEM => ErrorCode::InsufficientMemory, + RustixErrno::IO => ErrorCode::Io, + RustixErrno::BADF => ErrorCode::BadDescriptor, + RustixErrno::BUSY => ErrorCode::Busy, + RustixErrno::ACCESS => ErrorCode::Access, + RustixErrno::NOTDIR => ErrorCode::NotDirectory, + RustixErrno::ISDIR => ErrorCode::IsDirectory, + RustixErrno::INVAL => ErrorCode::Invalid, + RustixErrno::EXIST => ErrorCode::Exist, + RustixErrno::FBIG => ErrorCode::FileTooLarge, + RustixErrno::NOSPC => ErrorCode::InsufficientSpace, + RustixErrno::SPIPE => ErrorCode::InvalidSeek, + RustixErrno::MLINK => ErrorCode::TooManyLinks, + RustixErrno::NAMETOOLONG => ErrorCode::NameTooLong, + RustixErrno::NOTEMPTY => ErrorCode::NotEmpty, + RustixErrno::LOOP => ErrorCode::Loop, + RustixErrno::OVERFLOW => ErrorCode::Overflow, + RustixErrno::ILSEQ => ErrorCode::IllegalByteSequence, + RustixErrno::NOTSUP => ErrorCode::Unsupported, + RustixErrno::ALREADY => ErrorCode::Already, + RustixErrno::INPROGRESS => ErrorCode::InProgress, + RustixErrno::INTR => ErrorCode::Interrupted, + + // On some platforms, these have the same value as other errno values. #[allow(unreachable_patterns)] - RustixErrno::OPNOTSUPP => ErrorCode::Unsupported.into(), + RustixErrno::OPNOTSUPP => ErrorCode::Unsupported, _ => return None, }) } #[cfg(windows)] -fn from_raw_os_error(raw_os_error: Option) -> Option { +fn from_raw_os_error(raw_os_error: Option) -> Option { use windows_sys::Win32::Foundation; Some(match raw_os_error.map(|code| code as u32) { - Some(Foundation::ERROR_FILE_NOT_FOUND) => ErrorCode::NoEntry.into(), - Some(Foundation::ERROR_PATH_NOT_FOUND) => ErrorCode::NoEntry.into(), - Some(Foundation::ERROR_ACCESS_DENIED) => ErrorCode::Access.into(), - Some(Foundation::ERROR_SHARING_VIOLATION) => ErrorCode::Access.into(), - Some(Foundation::ERROR_PRIVILEGE_NOT_HELD) => ErrorCode::NotPermitted.into(), - Some(Foundation::ERROR_INVALID_HANDLE) => ErrorCode::BadDescriptor.into(), - Some(Foundation::ERROR_INVALID_NAME) => ErrorCode::NoEntry.into(), - Some(Foundation::ERROR_NOT_ENOUGH_MEMORY) => ErrorCode::InsufficientMemory.into(), - Some(Foundation::ERROR_OUTOFMEMORY) => ErrorCode::InsufficientMemory.into(), - Some(Foundation::ERROR_DIR_NOT_EMPTY) => ErrorCode::NotEmpty.into(), - Some(Foundation::ERROR_NOT_READY) => ErrorCode::Busy.into(), - Some(Foundation::ERROR_BUSY) => ErrorCode::Busy.into(), - Some(Foundation::ERROR_NOT_SUPPORTED) => ErrorCode::Unsupported.into(), - Some(Foundation::ERROR_FILE_EXISTS) => ErrorCode::Exist.into(), - Some(Foundation::ERROR_BROKEN_PIPE) => ErrorCode::Pipe.into(), - Some(Foundation::ERROR_BUFFER_OVERFLOW) => ErrorCode::NameTooLong.into(), - Some(Foundation::ERROR_NOT_A_REPARSE_POINT) => ErrorCode::Invalid.into(), - Some(Foundation::ERROR_NEGATIVE_SEEK) => ErrorCode::Invalid.into(), - Some(Foundation::ERROR_DIRECTORY) => ErrorCode::NotDirectory.into(), - Some(Foundation::ERROR_ALREADY_EXISTS) => ErrorCode::Exist.into(), - Some(Foundation::ERROR_STOPPED_ON_SYMLINK) => ErrorCode::Loop.into(), - Some(Foundation::ERROR_DIRECTORY_NOT_SUPPORTED) => ErrorCode::IsDirectory.into(), + Some(Foundation::ERROR_FILE_NOT_FOUND) => ErrorCode::NoEntry, + Some(Foundation::ERROR_PATH_NOT_FOUND) => ErrorCode::NoEntry, + Some(Foundation::ERROR_ACCESS_DENIED) => ErrorCode::Access, + Some(Foundation::ERROR_SHARING_VIOLATION) => ErrorCode::Access, + Some(Foundation::ERROR_PRIVILEGE_NOT_HELD) => ErrorCode::NotPermitted, + Some(Foundation::ERROR_INVALID_HANDLE) => ErrorCode::BadDescriptor, + Some(Foundation::ERROR_INVALID_NAME) => ErrorCode::NoEntry, + Some(Foundation::ERROR_NOT_ENOUGH_MEMORY) => ErrorCode::InsufficientMemory, + Some(Foundation::ERROR_OUTOFMEMORY) => ErrorCode::InsufficientMemory, + Some(Foundation::ERROR_DIR_NOT_EMPTY) => ErrorCode::NotEmpty, + Some(Foundation::ERROR_NOT_READY) => ErrorCode::Busy, + Some(Foundation::ERROR_BUSY) => ErrorCode::Busy, + Some(Foundation::ERROR_NOT_SUPPORTED) => ErrorCode::Unsupported, + Some(Foundation::ERROR_FILE_EXISTS) => ErrorCode::Exist, + Some(Foundation::ERROR_BROKEN_PIPE) => ErrorCode::Pipe, + Some(Foundation::ERROR_BUFFER_OVERFLOW) => ErrorCode::NameTooLong, + Some(Foundation::ERROR_NOT_A_REPARSE_POINT) => ErrorCode::Invalid, + Some(Foundation::ERROR_NEGATIVE_SEEK) => ErrorCode::Invalid, + Some(Foundation::ERROR_DIRECTORY) => ErrorCode::NotDirectory, + Some(Foundation::ERROR_ALREADY_EXISTS) => ErrorCode::Exist, + Some(Foundation::ERROR_STOPPED_ON_SYMLINK) => ErrorCode::Loop, + Some(Foundation::ERROR_DIRECTORY_NOT_SUPPORTED) => ErrorCode::IsDirectory, _ => return None, }) } -impl From for types::Error { - fn from(err: std::io::Error) -> types::Error { +impl From for ErrorCode { + fn from(err: std::io::Error) -> ErrorCode { + ErrorCode::from(&err) + } +} + +impl<'a> From<&'a std::io::Error> for ErrorCode { + fn from(err: &'a std::io::Error) -> ErrorCode { match from_raw_os_error(err.raw_os_error()) { Some(errno) => errno, - None => match err.kind() { - std::io::ErrorKind::NotFound => ErrorCode::NoEntry.into(), - std::io::ErrorKind::PermissionDenied => ErrorCode::NotPermitted.into(), - std::io::ErrorKind::AlreadyExists => ErrorCode::Exist.into(), - std::io::ErrorKind::InvalidInput => ErrorCode::Invalid.into(), - _ => types::Error::trap(anyhow::anyhow!(err).context("Unknown OS error")), - }, + None => { + log::debug!("unknown raw os error: {err}"); + match err.kind() { + std::io::ErrorKind::NotFound => ErrorCode::NoEntry, + std::io::ErrorKind::PermissionDenied => ErrorCode::NotPermitted, + std::io::ErrorKind::AlreadyExists => ErrorCode::Exist, + std::io::ErrorKind::InvalidInput => ErrorCode::Invalid, + _ => ErrorCode::Io, + } + } } } } -impl From for types::Error { - fn from(err: cap_rand::Error) -> types::Error { +impl From for ErrorCode { + fn from(err: cap_rand::Error) -> ErrorCode { // I picked Error::Io as a 'reasonable default', FIXME dan is this ok? - from_raw_os_error(err.raw_os_error()).unwrap_or_else(|| types::Error::from(ErrorCode::Io)) + from_raw_os_error(err.raw_os_error()).unwrap_or(ErrorCode::Io) } } -impl From for types::Error { - fn from(_err: std::num::TryFromIntError) -> types::Error { - ErrorCode::Overflow.into() +impl From for ErrorCode { + fn from(_err: std::num::TryFromIntError) -> ErrorCode { + ErrorCode::Overflow } } @@ -1047,9 +1057,7 @@ fn descriptortype_from(ft: cap_std::fs::FileType) -> types::DescriptorType { } } -fn systemtimespec_from( - t: types::NewTimestamp, -) -> Result, types::Error> { +fn systemtimespec_from(t: types::NewTimestamp) -> FsResult> { use fs_set_times::SystemTimeSpec; use types::NewTimestamp; match t { @@ -1059,7 +1067,7 @@ fn systemtimespec_from( } } -fn systemtime_from(t: wall_clock::Datetime) -> Result { +fn systemtime_from(t: wall_clock::Datetime) -> FsResult { use std::time::{Duration, SystemTime}; SystemTime::UNIX_EPOCH .checked_add(Duration::new(t.seconds, t.nanoseconds)) diff --git a/crates/wasi/src/preview2/host/filesystem/sync.rs b/crates/wasi/src/preview2/host/filesystem/sync.rs index 36bb244b5719..6f6132a7b2af 100644 --- a/crates/wasi/src/preview2/host/filesystem/sync.rs +++ b/crates/wasi/src/preview2/host/filesystem/sync.rs @@ -1,10 +1,21 @@ use crate::preview2::bindings::filesystem::types as async_filesystem; use crate::preview2::bindings::sync_io::filesystem::types as sync_filesystem; use crate::preview2::bindings::sync_io::io::streams; -use crate::preview2::in_tokio; +use crate::preview2::{in_tokio, FsError, FsResult}; use wasmtime::component::Resource; -impl sync_filesystem::Host for T {} +impl sync_filesystem::Host for T { + fn convert_error_code(&mut self, err: FsError) -> anyhow::Result { + Ok(async_filesystem::Host::convert_error_code(self, err)?.into()) + } + + fn filesystem_error_code( + &mut self, + err: Resource, + ) -> anyhow::Result> { + Ok(async_filesystem::Host::filesystem_error_code(self, err)?.map(|e| e.into())) + } +} impl sync_filesystem::HostDescriptor for T { fn advise( @@ -13,16 +24,13 @@ impl sync_filesystem::HostDescriptor for T offset: sync_filesystem::Filesize, len: sync_filesystem::Filesize, advice: sync_filesystem::Advice, - ) -> Result<(), sync_filesystem::Error> { + ) -> FsResult<()> { Ok(in_tokio(async { async_filesystem::HostDescriptor::advise(self, fd, offset, len, advice.into()).await })?) } - fn sync_data( - &mut self, - fd: Resource, - ) -> Result<(), sync_filesystem::Error> { + fn sync_data(&mut self, fd: Resource) -> FsResult<()> { Ok(in_tokio(async { async_filesystem::HostDescriptor::sync_data(self, fd).await })?) @@ -31,14 +39,14 @@ impl sync_filesystem::HostDescriptor for T fn get_flags( &mut self, fd: Resource, - ) -> Result { + ) -> FsResult { Ok(in_tokio(async { async_filesystem::HostDescriptor::get_flags(self, fd).await })?.into()) } fn get_type( &mut self, fd: Resource, - ) -> Result { + ) -> FsResult { Ok(in_tokio(async { async_filesystem::HostDescriptor::get_type(self, fd).await })?.into()) } @@ -46,7 +54,7 @@ impl sync_filesystem::HostDescriptor for T &mut self, fd: Resource, size: sync_filesystem::Filesize, - ) -> Result<(), sync_filesystem::Error> { + ) -> FsResult<()> { Ok(in_tokio(async { async_filesystem::HostDescriptor::set_size(self, fd, size).await })?) @@ -57,7 +65,7 @@ impl sync_filesystem::HostDescriptor for T fd: Resource, atim: sync_filesystem::NewTimestamp, mtim: sync_filesystem::NewTimestamp, - ) -> Result<(), sync_filesystem::Error> { + ) -> FsResult<()> { Ok(in_tokio(async { async_filesystem::HostDescriptor::set_times(self, fd, atim.into(), mtim.into()).await })?) @@ -68,7 +76,7 @@ impl sync_filesystem::HostDescriptor for T fd: Resource, len: sync_filesystem::Filesize, offset: sync_filesystem::Filesize, - ) -> Result<(Vec, bool), sync_filesystem::Error> { + ) -> FsResult<(Vec, bool)> { Ok(in_tokio(async { async_filesystem::HostDescriptor::read(self, fd, len, offset).await })?) @@ -79,7 +87,7 @@ impl sync_filesystem::HostDescriptor for T fd: Resource, buf: Vec, offset: sync_filesystem::Filesize, - ) -> Result { + ) -> FsResult { Ok(in_tokio(async { async_filesystem::HostDescriptor::write(self, fd, buf, offset).await })?) @@ -88,16 +96,13 @@ impl sync_filesystem::HostDescriptor for T fn read_directory( &mut self, fd: Resource, - ) -> Result, sync_filesystem::Error> { + ) -> FsResult> { Ok(in_tokio(async { async_filesystem::HostDescriptor::read_directory(self, fd).await })?) } - fn sync( - &mut self, - fd: Resource, - ) -> Result<(), sync_filesystem::Error> { + fn sync(&mut self, fd: Resource) -> FsResult<()> { Ok(in_tokio(async { async_filesystem::HostDescriptor::sync(self, fd).await })?) @@ -107,7 +112,7 @@ impl sync_filesystem::HostDescriptor for T &mut self, fd: Resource, path: String, - ) -> Result<(), sync_filesystem::Error> { + ) -> FsResult<()> { Ok(in_tokio(async { async_filesystem::HostDescriptor::create_directory_at(self, fd, path).await })?) @@ -116,7 +121,7 @@ impl sync_filesystem::HostDescriptor for T fn stat( &mut self, fd: Resource, - ) -> Result { + ) -> FsResult { Ok(in_tokio(async { async_filesystem::HostDescriptor::stat(self, fd).await })?.into()) } @@ -125,7 +130,7 @@ impl sync_filesystem::HostDescriptor for T fd: Resource, path_flags: sync_filesystem::PathFlags, path: String, - ) -> Result { + ) -> FsResult { Ok(in_tokio(async { async_filesystem::HostDescriptor::stat_at(self, fd, path_flags.into(), path).await })? @@ -139,7 +144,7 @@ impl sync_filesystem::HostDescriptor for T path: String, atim: sync_filesystem::NewTimestamp, mtim: sync_filesystem::NewTimestamp, - ) -> Result<(), sync_filesystem::Error> { + ) -> FsResult<()> { Ok(in_tokio(async { async_filesystem::HostDescriptor::set_times_at( self, @@ -161,7 +166,7 @@ impl sync_filesystem::HostDescriptor for T old_path: String, new_descriptor: Resource, new_path: String, - ) -> Result<(), sync_filesystem::Error> { + ) -> FsResult<()> { Ok(in_tokio(async { async_filesystem::HostDescriptor::link_at( self, @@ -183,7 +188,7 @@ impl sync_filesystem::HostDescriptor for T oflags: sync_filesystem::OpenFlags, flags: sync_filesystem::DescriptorFlags, mode: sync_filesystem::Modes, - ) -> Result, sync_filesystem::Error> { + ) -> FsResult> { Ok(in_tokio(async { async_filesystem::HostDescriptor::open_at( self, @@ -206,7 +211,7 @@ impl sync_filesystem::HostDescriptor for T &mut self, fd: Resource, path: String, - ) -> Result { + ) -> FsResult { Ok(in_tokio(async { async_filesystem::HostDescriptor::readlink_at(self, fd, path).await })?) @@ -216,7 +221,7 @@ impl sync_filesystem::HostDescriptor for T &mut self, fd: Resource, path: String, - ) -> Result<(), sync_filesystem::Error> { + ) -> FsResult<()> { Ok(in_tokio(async { async_filesystem::HostDescriptor::remove_directory_at(self, fd, path).await })?) @@ -228,7 +233,7 @@ impl sync_filesystem::HostDescriptor for T old_path: String, new_fd: Resource, new_path: String, - ) -> Result<(), sync_filesystem::Error> { + ) -> FsResult<()> { Ok(in_tokio(async { async_filesystem::HostDescriptor::rename_at(self, fd, old_path, new_fd, new_path).await })?) @@ -239,7 +244,7 @@ impl sync_filesystem::HostDescriptor for T fd: Resource, src_path: String, dest_path: String, - ) -> Result<(), sync_filesystem::Error> { + ) -> FsResult<()> { Ok(in_tokio(async { async_filesystem::HostDescriptor::symlink_at(self, fd, src_path, dest_path).await })?) @@ -249,7 +254,7 @@ impl sync_filesystem::HostDescriptor for T &mut self, fd: Resource, path: String, - ) -> Result<(), sync_filesystem::Error> { + ) -> FsResult<()> { Ok(in_tokio(async { async_filesystem::HostDescriptor::unlink_file_at(self, fd, path).await })?) @@ -261,7 +266,7 @@ impl sync_filesystem::HostDescriptor for T path_flags: sync_filesystem::PathFlags, path: String, access: sync_filesystem::AccessType, - ) -> Result<(), sync_filesystem::Error> { + ) -> FsResult<()> { Ok(in_tokio(async { async_filesystem::HostDescriptor::access_at( self, @@ -280,7 +285,7 @@ impl sync_filesystem::HostDescriptor for T path_flags: sync_filesystem::PathFlags, path: String, mode: sync_filesystem::Modes, - ) -> Result<(), sync_filesystem::Error> { + ) -> FsResult<()> { Ok(in_tokio(async { async_filesystem::HostDescriptor::change_file_permissions_at( self, @@ -299,7 +304,7 @@ impl sync_filesystem::HostDescriptor for T path_flags: sync_filesystem::PathFlags, path: String, mode: sync_filesystem::Modes, - ) -> Result<(), sync_filesystem::Error> { + ) -> FsResult<()> { Ok(in_tokio(async { async_filesystem::HostDescriptor::change_directory_permissions_at( self, @@ -312,46 +317,31 @@ impl sync_filesystem::HostDescriptor for T })?) } - fn lock_shared( - &mut self, - fd: Resource, - ) -> Result<(), sync_filesystem::Error> { + fn lock_shared(&mut self, fd: Resource) -> FsResult<()> { Ok(in_tokio(async { async_filesystem::HostDescriptor::lock_shared(self, fd).await })?) } - fn lock_exclusive( - &mut self, - fd: Resource, - ) -> Result<(), sync_filesystem::Error> { + fn lock_exclusive(&mut self, fd: Resource) -> FsResult<()> { Ok(in_tokio(async { async_filesystem::HostDescriptor::lock_exclusive(self, fd).await })?) } - fn try_lock_shared( - &mut self, - fd: Resource, - ) -> Result<(), sync_filesystem::Error> { + fn try_lock_shared(&mut self, fd: Resource) -> FsResult<()> { Ok(in_tokio(async { async_filesystem::HostDescriptor::try_lock_shared(self, fd).await })?) } - fn try_lock_exclusive( - &mut self, - fd: Resource, - ) -> Result<(), sync_filesystem::Error> { + fn try_lock_exclusive(&mut self, fd: Resource) -> FsResult<()> { Ok(in_tokio(async { async_filesystem::HostDescriptor::try_lock_exclusive(self, fd).await })?) } - fn unlock( - &mut self, - fd: Resource, - ) -> Result<(), sync_filesystem::Error> { + fn unlock(&mut self, fd: Resource) -> FsResult<()> { Ok(in_tokio(async { async_filesystem::HostDescriptor::unlock(self, fd).await })?) @@ -361,7 +351,7 @@ impl sync_filesystem::HostDescriptor for T &mut self, fd: Resource, offset: sync_filesystem::Filesize, - ) -> Result, sync_filesystem::Error> { + ) -> FsResult> { Ok(async_filesystem::HostDescriptor::read_via_stream( self, fd, offset, )?) @@ -371,7 +361,7 @@ impl sync_filesystem::HostDescriptor for T &mut self, fd: Resource, offset: sync_filesystem::Filesize, - ) -> Result, sync_filesystem::Error> { + ) -> FsResult> { Ok(async_filesystem::HostDescriptor::write_via_stream( self, fd, offset, )?) @@ -380,7 +370,7 @@ impl sync_filesystem::HostDescriptor for T fn append_via_stream( &mut self, fd: Resource, - ) -> Result, sync_filesystem::Error> { + ) -> FsResult> { Ok(async_filesystem::HostDescriptor::append_via_stream( self, fd, )?) @@ -398,7 +388,7 @@ impl sync_filesystem::HostDescriptor for T fn metadata_hash( &mut self, fd: Resource, - ) -> Result { + ) -> FsResult { Ok( in_tokio(async { async_filesystem::HostDescriptor::metadata_hash(self, fd).await })? .into(), @@ -409,7 +399,7 @@ impl sync_filesystem::HostDescriptor for T fd: Resource, path_flags: sync_filesystem::PathFlags, path: String, - ) -> Result { + ) -> FsResult { Ok(in_tokio(async { async_filesystem::HostDescriptor::metadata_hash_at(self, fd, path_flags.into(), path) .await @@ -424,7 +414,7 @@ impl sync_filesystem::HostDirecto fn read_directory_entry( &mut self, stream: Resource, - ) -> Result, sync_filesystem::Error> { + ) -> FsResult> { Ok(in_tokio(async { async_filesystem::HostDirectoryEntryStream::read_directory_entry(self, stream).await })? @@ -484,15 +474,6 @@ impl From for sync_filesystem::ErrorCode { } } -impl From for sync_filesystem::Error { - fn from(other: async_filesystem::Error) -> Self { - match other.downcast() { - Ok(errorcode) => Self::from(sync_filesystem::ErrorCode::from(errorcode)), - Err(other) => Self::trap(other), - } - } -} - impl From for async_filesystem::Advice { fn from(other: sync_filesystem::Advice) -> Self { use sync_filesystem::Advice; diff --git a/crates/wasi/src/preview2/host/io.rs b/crates/wasi/src/preview2/host/io.rs index 92cfa0cdb0de..72796de5a8df 100644 --- a/crates/wasi/src/preview2/host/io.rs +++ b/crates/wasi/src/preview2/host/io.rs @@ -1,31 +1,32 @@ use crate::preview2::{ bindings::io::streams::{self, InputStream, OutputStream}, poll::subscribe, - stream::StreamError, - Pollable, TableError, WasiView, + Pollable, StreamError, StreamResult, WasiView, }; use wasmtime::component::Resource; -impl From for streams::Error { - fn from(e: TableError) -> streams::Error { - streams::Error::trap(e.into()) - } -} -impl From for streams::Error { - fn from(e: StreamError) -> streams::Error { - match e { - StreamError::Closed => streams::StreamError::Closed.into(), - StreamError::LastOperationFailed(e) => { - tracing::debug!("streams::StreamError::LastOperationFailed: {e:?}"); - streams::StreamError::LastOperationFailed.into() - } - StreamError::Trap(e) => streams::Error::trap(e), +impl streams::Host for T { + fn convert_stream_error(&mut self, err: StreamError) -> anyhow::Result { + match err { + StreamError::Closed => Ok(streams::StreamError::Closed), + StreamError::LastOperationFailed(e) => Ok(streams::StreamError::LastOperationFailed( + self.table_mut().push_resource(e)?, + )), + StreamError::Trap(e) => Err(e), } } } -#[async_trait::async_trait] -impl streams::Host for T {} +impl streams::HostError for T { + fn drop(&mut self, err: Resource) -> anyhow::Result<()> { + self.table_mut().delete_resource(err)?; + Ok(()) + } + + fn to_debug_string(&mut self, err: Resource) -> anyhow::Result { + Ok(format!("{:?}", self.table_mut().get_resource(&err)?)) + } +} #[async_trait::async_trait] impl streams::HostOutputStream for T { @@ -34,16 +35,12 @@ impl streams::HostOutputStream for T { Ok(()) } - fn check_write(&mut self, stream: Resource) -> Result { + fn check_write(&mut self, stream: Resource) -> StreamResult { let bytes = self.table_mut().get_resource_mut(&stream)?.check_write()?; Ok(bytes as u64) } - fn write( - &mut self, - stream: Resource, - bytes: Vec, - ) -> Result<(), streams::Error> { + fn write(&mut self, stream: Resource, bytes: Vec) -> StreamResult<()> { self.table_mut() .get_resource_mut(&stream)? .write(bytes.into())?; @@ -58,13 +55,13 @@ impl streams::HostOutputStream for T { &mut self, stream: Resource, bytes: Vec, - ) -> Result<(), streams::Error> { + ) -> StreamResult<()> { let s = self.table_mut().get_resource_mut(&stream)?; if bytes.len() > 4096 { - return Err(streams::Error::trap(anyhow::anyhow!( - "Buffer too large for blocking-write-and-flush (expected at most 4096)" - ))); + return Err(StreamError::trap( + "Buffer too large for blocking-write-and-flush (expected at most 4096)", + )); } let mut bytes = bytes::Bytes::from(bytes); @@ -85,13 +82,13 @@ impl streams::HostOutputStream for T { &mut self, stream: Resource, len: u64, - ) -> Result<(), streams::Error> { + ) -> StreamResult<()> { let s = self.table_mut().get_resource_mut(&stream)?; if len > 4096 { - return Err(streams::Error::trap(anyhow::anyhow!( - "Buffer too large for blocking-write-zeroes-and-flush (expected at most 4096)" - ))); + return Err(StreamError::trap( + "Buffer too large for blocking-write-zeroes-and-flush (expected at most 4096)", + )); } let mut len = len; @@ -108,26 +105,19 @@ impl streams::HostOutputStream for T { Ok(()) } - fn write_zeroes( - &mut self, - stream: Resource, - len: u64, - ) -> Result<(), streams::Error> { + fn write_zeroes(&mut self, stream: Resource, len: u64) -> StreamResult<()> { self.table_mut() .get_resource_mut(&stream)? .write_zeroes(len as usize)?; Ok(()) } - fn flush(&mut self, stream: Resource) -> Result<(), streams::Error> { + fn flush(&mut self, stream: Resource) -> StreamResult<()> { self.table_mut().get_resource_mut(&stream)?.flush()?; Ok(()) } - async fn blocking_flush( - &mut self, - stream: Resource, - ) -> Result<(), streams::Error> { + async fn blocking_flush(&mut self, stream: Resource) -> StreamResult<()> { let s = self.table_mut().get_resource_mut(&stream)?; s.flush()?; s.write_ready().await?; @@ -139,7 +129,7 @@ impl streams::HostOutputStream for T { _dst: Resource, _src: Resource, _len: u64, - ) -> Result { + ) -> StreamResult { // TODO: We can't get two streams at the same time because they both // carry the exclusive lifetime of `ctx`. When [`get_many_mut`] is // stabilized, that could allow us to add a `get_many_stream_mut` or @@ -168,7 +158,7 @@ impl streams::HostOutputStream for T { _dst: Resource, _src: Resource, _len: u64, - ) -> Result { + ) -> StreamResult { // TODO: once splice is implemented, figure out what the blocking semantics are for waiting // on src and dest here. todo!("stream splice is not implemented") @@ -178,7 +168,7 @@ impl streams::HostOutputStream for T { &mut self, _dst: Resource, _src: Resource, - ) -> Result { + ) -> StreamResult { // TODO: We can't get two streams at the same time because they both // carry the exclusive lifetime of `ctx`. When [`get_many_mut`] is // stabilized, that could allow us to add a `get_many_stream_mut` or @@ -204,11 +194,6 @@ impl streams::HostOutputStream for T { } } -impl From for streams::Error { - fn from(e: std::num::TryFromIntError) -> Self { - streams::Error::trap(anyhow::anyhow!("length overflow: {e:?}")) - } -} #[async_trait::async_trait] impl streams::HostInputStream for T { fn drop(&mut self, stream: Resource) -> anyhow::Result<()> { @@ -216,12 +201,8 @@ impl streams::HostInputStream for T { Ok(()) } - async fn read( - &mut self, - stream: Resource, - len: u64, - ) -> Result, streams::Error> { - let len = len.try_into()?; + async fn read(&mut self, stream: Resource, len: u64) -> StreamResult> { + let len = len.try_into().unwrap_or(usize::MAX); let bytes = match self.table_mut().get_resource_mut(&stream)? { InputStream::Host(s) => s.read(len)?, InputStream::File(s) => s.read(len).await?, @@ -234,19 +215,15 @@ impl streams::HostInputStream for T { &mut self, stream: Resource, len: u64, - ) -> Result, streams::Error> { + ) -> StreamResult> { if let InputStream::Host(s) = self.table_mut().get_resource_mut(&stream)? { s.ready().await; } self.read(stream, len).await } - async fn skip( - &mut self, - stream: Resource, - len: u64, - ) -> Result { - let len = len.try_into()?; + async fn skip(&mut self, stream: Resource, len: u64) -> StreamResult { + let len = len.try_into().unwrap_or(usize::MAX); let written = match self.table_mut().get_resource_mut(&stream)? { InputStream::Host(s) => s.skip(len)?, InputStream::File(s) => s.skip(len).await?, @@ -258,7 +235,7 @@ impl streams::HostInputStream for T { &mut self, stream: Resource, len: u64, - ) -> Result { + ) -> StreamResult { if let InputStream::Host(s) = self.table_mut().get_resource_mut(&stream)? { s.ready().await; } @@ -273,48 +250,53 @@ impl streams::HostInputStream for T { pub mod sync { use crate::preview2::{ bindings::io::streams::{ - self as async_streams, HostInputStream as AsyncHostInputStream, - HostOutputStream as AsyncHostOutputStream, + self as async_streams, Host as AsyncHost, HostError as AsyncHostError, + HostInputStream as AsyncHostInputStream, HostOutputStream as AsyncHostOutputStream, }, bindings::sync_io::io::poll::Pollable, bindings::sync_io::io::streams::{self, InputStream, OutputStream}, - in_tokio, WasiView, + in_tokio, StreamError, StreamResult, WasiView, }; use wasmtime::component::Resource; impl From for streams::StreamError { fn from(other: async_streams::StreamError) -> Self { match other { - async_streams::StreamError::LastOperationFailed => Self::LastOperationFailed, + async_streams::StreamError::LastOperationFailed(e) => Self::LastOperationFailed(e), async_streams::StreamError::Closed => Self::Closed, } } } - impl From for streams::Error { - fn from(other: async_streams::Error) -> Self { - match other.downcast() { - Ok(write_error) => streams::Error::from(streams::StreamError::from(write_error)), - Err(e) => streams::Error::trap(e), - } + + impl streams::Host for T { + fn convert_stream_error( + &mut self, + err: StreamError, + ) -> anyhow::Result { + Ok(AsyncHost::convert_stream_error(self, err)?.into()) } } - impl streams::Host for T {} + impl streams::HostError for T { + fn drop(&mut self, err: Resource) -> anyhow::Result<()> { + AsyncHostError::drop(self, err) + } + + fn to_debug_string(&mut self, err: Resource) -> anyhow::Result { + AsyncHostError::to_debug_string(self, err) + } + } impl streams::HostOutputStream for T { fn drop(&mut self, stream: Resource) -> anyhow::Result<()> { AsyncHostOutputStream::drop(self, stream) } - fn check_write(&mut self, stream: Resource) -> Result { + fn check_write(&mut self, stream: Resource) -> StreamResult { Ok(AsyncHostOutputStream::check_write(self, stream)?) } - fn write( - &mut self, - stream: Resource, - bytes: Vec, - ) -> Result<(), streams::Error> { + fn write(&mut self, stream: Resource, bytes: Vec) -> StreamResult<()> { Ok(AsyncHostOutputStream::write(self, stream, bytes)?) } @@ -322,7 +304,7 @@ pub mod sync { &mut self, stream: Resource, bytes: Vec, - ) -> Result<(), streams::Error> { + ) -> StreamResult<()> { Ok(in_tokio(async { AsyncHostOutputStream::blocking_write_and_flush(self, stream, bytes).await })?) @@ -332,7 +314,7 @@ pub mod sync { &mut self, stream: Resource, len: u64, - ) -> Result<(), streams::Error> { + ) -> StreamResult<()> { Ok(in_tokio(async { AsyncHostOutputStream::blocking_write_zeroes_and_flush(self, stream, len).await })?) @@ -345,22 +327,18 @@ pub mod sync { Ok(AsyncHostOutputStream::subscribe(self, stream)?) } - fn write_zeroes( - &mut self, - stream: Resource, - len: u64, - ) -> Result<(), streams::Error> { + fn write_zeroes(&mut self, stream: Resource, len: u64) -> StreamResult<()> { Ok(AsyncHostOutputStream::write_zeroes(self, stream, len)?) } - fn flush(&mut self, stream: Resource) -> Result<(), streams::Error> { + fn flush(&mut self, stream: Resource) -> StreamResult<()> { Ok(AsyncHostOutputStream::flush( self, Resource::new_borrow(stream.rep()), )?) } - fn blocking_flush(&mut self, stream: Resource) -> Result<(), streams::Error> { + fn blocking_flush(&mut self, stream: Resource) -> StreamResult<()> { Ok(in_tokio(async { AsyncHostOutputStream::blocking_flush(self, Resource::new_borrow(stream.rep())) .await @@ -372,7 +350,7 @@ pub mod sync { dst: Resource, src: Resource, len: u64, - ) -> Result { + ) -> StreamResult { Ok(in_tokio(async { AsyncHostOutputStream::splice(self, dst, src, len).await })?) @@ -383,7 +361,7 @@ pub mod sync { dst: Resource, src: Resource, len: u64, - ) -> Result { + ) -> StreamResult { Ok(in_tokio(async { AsyncHostOutputStream::blocking_splice(self, dst, src, len).await })?) @@ -393,7 +371,7 @@ pub mod sync { &mut self, dst: Resource, src: Resource, - ) -> Result { + ) -> StreamResult { Ok(in_tokio(async { AsyncHostOutputStream::forward(self, dst, src).await })?) @@ -405,11 +383,7 @@ pub mod sync { AsyncHostInputStream::drop(self, stream) } - fn read( - &mut self, - stream: Resource, - len: u64, - ) -> Result, streams::Error> { + fn read(&mut self, stream: Resource, len: u64) -> StreamResult> { Ok(in_tokio(async { AsyncHostInputStream::read(self, stream, len).await })?) @@ -419,23 +393,19 @@ pub mod sync { &mut self, stream: Resource, len: u64, - ) -> Result, streams::Error> { + ) -> StreamResult> { Ok(in_tokio(async { AsyncHostInputStream::blocking_read(self, stream, len).await })?) } - fn skip(&mut self, stream: Resource, len: u64) -> Result { + fn skip(&mut self, stream: Resource, len: u64) -> StreamResult { Ok(in_tokio(async { AsyncHostInputStream::skip(self, stream, len).await })?) } - fn blocking_skip( - &mut self, - stream: Resource, - len: u64, - ) -> Result { + fn blocking_skip(&mut self, stream: Resource, len: u64) -> StreamResult { Ok(in_tokio(async { AsyncHostInputStream::blocking_skip(self, stream, len).await })?) diff --git a/crates/wasi/src/preview2/host/network.rs b/crates/wasi/src/preview2/host/network.rs index 811213050e8e..b2a8ff59a700 100644 --- a/crates/wasi/src/preview2/host/network.rs +++ b/crates/wasi/src/preview2/host/network.rs @@ -2,11 +2,15 @@ use crate::preview2::bindings::sockets::network::{ self, ErrorCode, IpAddressFamily, IpSocketAddress, Ipv4Address, Ipv4SocketAddress, Ipv6Address, Ipv6SocketAddress, }; -use crate::preview2::{TableError, WasiView}; +use crate::preview2::{SocketError, WasiView}; use std::io; use wasmtime::component::Resource; -impl network::Host for T {} +impl network::Host for T { + fn convert_error_code(&mut self, error: SocketError) -> anyhow::Result { + error.downcast() + } +} impl crate::preview2::bindings::sockets::network::HostNetwork for T { fn drop(&mut self, this: Resource) -> Result<(), anyhow::Error> { @@ -18,13 +22,7 @@ impl crate::preview2::bindings::sockets::network::HostNetwork for T } } -impl From for network::Error { - fn from(error: TableError) -> Self { - Self::trap(error.into()) - } -} - -impl From for network::Error { +impl From for ErrorCode { fn from(error: io::Error) -> Self { match error.kind() { // Errors that we can directly map. @@ -39,24 +37,8 @@ impl From for network::Error { io::ErrorKind::Unsupported => ErrorCode::NotSupported, io::ErrorKind::OutOfMemory => ErrorCode::OutOfMemory, - // Errors we don't expect to see here. - io::ErrorKind::Interrupted | io::ErrorKind::ConnectionAborted => { - // Transient errors should be skipped. - return Self::trap(error.into()); - } - - // Errors not expected from network APIs. - io::ErrorKind::WriteZero - | io::ErrorKind::InvalidInput - | io::ErrorKind::InvalidData - | io::ErrorKind::BrokenPipe - | io::ErrorKind::NotFound - | io::ErrorKind::UnexpectedEof - | io::ErrorKind::AlreadyExists => return Self::trap(error.into()), - // Errors that don't correspond to a Rust `io::ErrorKind`. io::ErrorKind::Other => match error.raw_os_error() { - None => return Self::trap(error.into()), Some(libc::ENOBUFS) | Some(libc::ENOMEM) => ErrorCode::OutOfMemory, Some(libc::EOPNOTSUPP) => ErrorCode::NotSupported, Some(libc::ENETUNREACH) | Some(libc::EHOSTUNREACH) | Some(libc::ENETDOWN) => { @@ -65,7 +47,10 @@ impl From for network::Error { Some(libc::ECONNRESET) => ErrorCode::ConnectionReset, Some(libc::ECONNREFUSED) => ErrorCode::ConnectionRefused, Some(libc::EADDRINUSE) => ErrorCode::AddressInUse, - Some(_) => return Self::trap(error.into()), + Some(_) | None => { + log::debug!("unknown I/O error: {error}"); + ErrorCode::Unknown + } }, _ => { @@ -73,11 +58,10 @@ impl From for network::Error { ErrorCode::Unknown } } - .into() } } -impl From for network::Error { +impl From for ErrorCode { fn from(error: rustix::io::Errno) -> Self { std::io::Error::from(error).into() } diff --git a/crates/wasi/src/preview2/host/tcp.rs b/crates/wasi/src/preview2/host/tcp.rs index 6565a589413a..0cf88b9100a4 100644 --- a/crates/wasi/src/preview2/host/tcp.rs +++ b/crates/wasi/src/preview2/host/tcp.rs @@ -1,10 +1,10 @@ use crate::preview2::bindings::{ io::streams::{InputStream, OutputStream}, - sockets::network::{self, ErrorCode, IpAddressFamily, IpSocketAddress, Network}, + sockets::network::{ErrorCode, IpAddressFamily, IpSocketAddress, Network}, sockets::tcp::{self, ShutdownType}, }; use crate::preview2::tcp::{TcpSocket, TcpState}; -use crate::preview2::{Pollable, WasiView}; +use crate::preview2::{Pollable, SocketResult, WasiView}; use cap_net_ext::{Blocking, PoolExt, TcpListenerExt}; use cap_std::net::TcpListener; use io_lifetimes::AsSocketlike; @@ -21,7 +21,7 @@ impl crate::preview2::host::tcp::tcp::HostTcpSocket for T { this: Resource, network: Resource, local_address: IpSocketAddress, - ) -> Result<(), network::Error> { + ) -> SocketResult<()> { let table = self.table_mut(); let socket = table.get_resource(&this)?; @@ -44,7 +44,7 @@ impl crate::preview2::host::tcp::tcp::HostTcpSocket for T { Ok(()) } - fn finish_bind(&mut self, this: Resource) -> Result<(), network::Error> { + fn finish_bind(&mut self, this: Resource) -> SocketResult<()> { let table = self.table_mut(); let socket = table.get_resource_mut(&this)?; @@ -63,7 +63,7 @@ impl crate::preview2::host::tcp::tcp::HostTcpSocket for T { this: Resource, network: Resource, remote_address: IpSocketAddress, - ) -> Result<(), network::Error> { + ) -> SocketResult<()> { let table = self.table_mut(); let r = { let socket = table.get_resource(&this)?; @@ -107,7 +107,7 @@ impl crate::preview2::host::tcp::tcp::HostTcpSocket for T { fn finish_connect( &mut self, this: Resource, - ) -> Result<(Resource, Resource), network::Error> { + ) -> SocketResult<(Resource, Resource)> { let table = self.table_mut(); let socket = table.get_resource_mut(&this)?; @@ -145,7 +145,7 @@ impl crate::preview2::host::tcp::tcp::HostTcpSocket for T { Ok((input_stream, output_stream)) } - fn start_listen(&mut self, this: Resource) -> Result<(), network::Error> { + fn start_listen(&mut self, this: Resource) -> SocketResult<()> { let table = self.table_mut(); let socket = table.get_resource_mut(&this)?; @@ -166,7 +166,7 @@ impl crate::preview2::host::tcp::tcp::HostTcpSocket for T { Ok(()) } - fn finish_listen(&mut self, this: Resource) -> Result<(), network::Error> { + fn finish_listen(&mut self, this: Resource) -> SocketResult<()> { let table = self.table_mut(); let socket = table.get_resource_mut(&this)?; @@ -183,14 +183,11 @@ impl crate::preview2::host::tcp::tcp::HostTcpSocket for T { fn accept( &mut self, this: Resource, - ) -> Result< - ( - Resource, - Resource, - Resource, - ), - network::Error, - > { + ) -> SocketResult<( + Resource, + Resource, + Resource, + )> { let table = self.table(); let socket = table.get_resource(&this)?; @@ -222,10 +219,7 @@ impl crate::preview2::host::tcp::tcp::HostTcpSocket for T { Ok((tcp_socket, input_stream, output_stream)) } - fn local_address( - &mut self, - this: Resource, - ) -> Result { + fn local_address(&mut self, this: Resource) -> SocketResult { let table = self.table(); let socket = table.get_resource(&this)?; let addr = socket @@ -235,10 +229,7 @@ impl crate::preview2::host::tcp::tcp::HostTcpSocket for T { Ok(addr.into()) } - fn remote_address( - &mut self, - this: Resource, - ) -> Result { + fn remote_address(&mut self, this: Resource) -> SocketResult { let table = self.table(); let socket = table.get_resource(&this)?; let addr = socket @@ -296,17 +287,13 @@ impl crate::preview2::host::tcp::tcp::HostTcpSocket for T { } } - fn ipv6_only(&mut self, this: Resource) -> Result { + fn ipv6_only(&mut self, this: Resource) -> SocketResult { let table = self.table(); let socket = table.get_resource(&this)?; Ok(sockopt::get_ipv6_v6only(socket.tcp_socket())?) } - fn set_ipv6_only( - &mut self, - this: Resource, - value: bool, - ) -> Result<(), network::Error> { + fn set_ipv6_only(&mut self, this: Resource, value: bool) -> SocketResult<()> { let table = self.table(); let socket = table.get_resource(&this)?; Ok(sockopt::set_ipv6_v6only(socket.tcp_socket(), value)?) @@ -316,7 +303,7 @@ impl crate::preview2::host::tcp::tcp::HostTcpSocket for T { &mut self, this: Resource, value: u64, - ) -> Result<(), network::Error> { + ) -> SocketResult<()> { const MIN_BACKLOG: i32 = 1; const MAX_BACKLOG: i32 = i32::MAX; // OS'es will most likely limit it down even further. @@ -354,39 +341,31 @@ impl crate::preview2::host::tcp::tcp::HostTcpSocket for T { } } - fn keep_alive(&mut self, this: Resource) -> Result { + fn keep_alive(&mut self, this: Resource) -> SocketResult { let table = self.table(); let socket = table.get_resource(&this)?; Ok(sockopt::get_socket_keepalive(socket.tcp_socket())?) } - fn set_keep_alive( - &mut self, - this: Resource, - value: bool, - ) -> Result<(), network::Error> { + fn set_keep_alive(&mut self, this: Resource, value: bool) -> SocketResult<()> { let table = self.table(); let socket = table.get_resource(&this)?; Ok(sockopt::set_socket_keepalive(socket.tcp_socket(), value)?) } - fn no_delay(&mut self, this: Resource) -> Result { + fn no_delay(&mut self, this: Resource) -> SocketResult { let table = self.table(); let socket = table.get_resource(&this)?; Ok(sockopt::get_tcp_nodelay(socket.tcp_socket())?) } - fn set_no_delay( - &mut self, - this: Resource, - value: bool, - ) -> Result<(), network::Error> { + fn set_no_delay(&mut self, this: Resource, value: bool) -> SocketResult<()> { let table = self.table(); let socket = table.get_resource(&this)?; Ok(sockopt::set_tcp_nodelay(socket.tcp_socket(), value)?) } - fn unicast_hop_limit(&mut self, this: Resource) -> Result { + fn unicast_hop_limit(&mut self, this: Resource) -> SocketResult { let table = self.table(); let socket = table.get_resource(&this)?; @@ -407,7 +386,7 @@ impl crate::preview2::host::tcp::tcp::HostTcpSocket for T { &mut self, this: Resource, value: u8, - ) -> Result<(), network::Error> { + ) -> SocketResult<()> { let table = self.table(); let socket = table.get_resource(&this)?; @@ -420,10 +399,7 @@ impl crate::preview2::host::tcp::tcp::HostTcpSocket for T { } } - fn receive_buffer_size( - &mut self, - this: Resource, - ) -> Result { + fn receive_buffer_size(&mut self, this: Resource) -> SocketResult { let table = self.table(); let socket = table.get_resource(&this)?; Ok(sockopt::get_socket_recv_buffer_size(socket.tcp_socket())? as u64) @@ -433,7 +409,7 @@ impl crate::preview2::host::tcp::tcp::HostTcpSocket for T { &mut self, this: Resource, value: u64, - ) -> Result<(), network::Error> { + ) -> SocketResult<()> { let table = self.table(); let socket = table.get_resource(&this)?; let value = value.try_into().map_err(|_| ErrorCode::OutOfMemory)?; @@ -443,7 +419,7 @@ impl crate::preview2::host::tcp::tcp::HostTcpSocket for T { )?) } - fn send_buffer_size(&mut self, this: Resource) -> Result { + fn send_buffer_size(&mut self, this: Resource) -> SocketResult { let table = self.table(); let socket = table.get_resource(&this)?; Ok(sockopt::get_socket_send_buffer_size(socket.tcp_socket())? as u64) @@ -453,7 +429,7 @@ impl crate::preview2::host::tcp::tcp::HostTcpSocket for T { &mut self, this: Resource, value: u64, - ) -> Result<(), network::Error> { + ) -> SocketResult<()> { let table = self.table(); let socket = table.get_resource(&this)?; let value = value.try_into().map_err(|_| ErrorCode::OutOfMemory)?; @@ -471,7 +447,7 @@ impl crate::preview2::host::tcp::tcp::HostTcpSocket for T { &mut self, this: Resource, shutdown_type: ShutdownType, - ) -> Result<(), network::Error> { + ) -> SocketResult<()> { let table = self.table(); let socket = table.get_resource(&this)?; diff --git a/crates/wasi/src/preview2/host/tcp_create_socket.rs b/crates/wasi/src/preview2/host/tcp_create_socket.rs index 60fc34482399..417c03081b05 100644 --- a/crates/wasi/src/preview2/host/tcp_create_socket.rs +++ b/crates/wasi/src/preview2/host/tcp_create_socket.rs @@ -1,16 +1,13 @@ -use crate::preview2::bindings::{ - sockets::network::{self, IpAddressFamily}, - sockets::tcp_create_socket, -}; +use crate::preview2::bindings::{sockets::network::IpAddressFamily, sockets::tcp_create_socket}; use crate::preview2::tcp::TcpSocket; -use crate::preview2::WasiView; +use crate::preview2::{SocketResult, WasiView}; use wasmtime::component::Resource; impl tcp_create_socket::Host for T { fn create_tcp_socket( &mut self, address_family: IpAddressFamily, - ) -> Result, network::Error> { + ) -> SocketResult> { let socket = TcpSocket::new(address_family.into())?; let socket = self.table_mut().push_resource(socket)?; Ok(socket) diff --git a/crates/wasi/src/preview2/ip_name_lookup.rs b/crates/wasi/src/preview2/ip_name_lookup.rs index b4a02427b475..ce7cde93b9b9 100644 --- a/crates/wasi/src/preview2/ip_name_lookup.rs +++ b/crates/wasi/src/preview2/ip_name_lookup.rs @@ -1,9 +1,7 @@ use crate::preview2::bindings::sockets::ip_name_lookup::{Host, HostResolveAddressStream}; -use crate::preview2::bindings::sockets::network::{ - Error, ErrorCode, IpAddress, IpAddressFamily, Network, -}; +use crate::preview2::bindings::sockets::network::{ErrorCode, IpAddress, IpAddressFamily, Network}; use crate::preview2::poll::{subscribe, Pollable, Subscribe}; -use crate::preview2::{spawn_blocking, AbortOnDropJoinHandle, WasiView}; +use crate::preview2::{spawn_blocking, AbortOnDropJoinHandle, SocketError, WasiView}; use anyhow::Result; use std::io; use std::mem; @@ -25,7 +23,7 @@ impl Host for T { name: String, family: Option, include_unavailable: bool, - ) -> Result, Error> { + ) -> Result, SocketError> { let network = self.table().get_resource(&network)?; // `Host::parse` serves us two functions: @@ -87,7 +85,7 @@ impl HostResolveAddressStream for T { fn resolve_next_address( &mut self, resource: Resource, - ) -> Result, Error> { + ) -> Result, SocketError> { let stream = self.table_mut().get_resource_mut(&resource)?; loop { match stream { diff --git a/crates/wasi/src/preview2/mod.rs b/crates/wasi/src/preview2/mod.rs index 9b4a273b8af9..aecc414ea4b2 100644 --- a/crates/wasi/src/preview2/mod.rs +++ b/crates/wasi/src/preview2/mod.rs @@ -40,14 +40,15 @@ mod write_stream; pub use self::clocks::{HostMonotonicClock, HostWallClock}; pub use self::ctx::{WasiCtx, WasiCtxBuilder, WasiView}; -pub use self::error::I32Exit; -pub use self::filesystem::{DirPerms, FilePerms}; +pub use self::error::{I32Exit, TrappableError}; +pub use self::filesystem::{DirPerms, FilePerms, FsError, FsResult}; +pub use self::network::{Network, SocketError, SocketResult}; pub use self::poll::{subscribe, ClosureFuture, MakeFuture, Pollable, PollableFuture, Subscribe}; pub use self::random::{thread_rng, Deterministic}; -pub use self::stdio::{ - stderr, stdin, stdout, IsATTY, Stderr, Stdin, StdinStream, Stdout, StdoutStream, +pub use self::stdio::{stderr, stdin, stdout, IsATTY, Stderr, Stdin, Stdout}; +pub use self::stream::{ + HostInputStream, HostOutputStream, InputStream, OutputStream, StreamError, StreamResult, }; -pub use self::stream::{HostInputStream, HostOutputStream, InputStream, OutputStream, StreamError}; pub use self::table::{Table, TableError}; pub use cap_fs_ext::SystemTimeSpec; pub use cap_rand::RngCore; @@ -59,6 +60,8 @@ pub mod bindings { // have some functions in `only_imports` below for being async. pub mod sync_io { pub(crate) mod _internal { + use crate::preview2::{FsError, StreamError}; + wasmtime::component::bindgen!({ path: "wit", interfaces: " @@ -68,8 +71,8 @@ pub mod bindings { ", tracing: true, trappable_error_type: { - "wasi:io/streams"::"stream-error": Error, - "wasi:filesystem/types"::"error-code": Error, + "wasi:io/streams"::"stream-error": StreamError, + "wasi:filesystem/types"::"error-code": FsError, }, with: { "wasi:clocks/wall-clock": crate::preview2::bindings::clocks::wall_clock, @@ -78,6 +81,7 @@ pub mod bindings { "wasi:io/poll/pollable": super::super::io::poll::Pollable, "wasi:io/streams/input-stream": super::super::io::streams::InputStream, "wasi:io/streams/output-stream": super::super::io::streams::OutputStream, + "wasi:io/streams/error": super::super::io::streams::Error, } }); } @@ -149,9 +153,9 @@ pub mod bindings { "wasi:sockets/ip-name-lookup/resolve-address-stream": super::ip_name_lookup::ResolveAddressStream, }, trappable_error_type: { - "wasi:io/streams"::"stream-error": Error, - "wasi:filesystem/types"::"error-code": Error, - "wasi:sockets/network"::"error-code": Error, + "wasi:io/streams"::"stream-error": crate::preview2::StreamError, + "wasi:filesystem/types"::"error-code": crate::preview2::FsError, + "wasi:sockets/network"::"error-code": crate::preview2::SocketError, }, with: { "wasi:sockets/network/network": super::network::Network, @@ -160,6 +164,7 @@ pub mod bindings { "wasi:filesystem/types/descriptor": super::filesystem::Descriptor, "wasi:io/streams/input-stream": super::stream::InputStream, "wasi:io/streams/output-stream": super::stream::OutputStream, + "wasi:io/streams/error": super::stream::Error, "wasi:io/poll/pollable": super::poll::Pollable, "wasi:cli/terminal-input/terminal-input": super::stdio::TerminalInput, "wasi:cli/terminal-output/terminal-output": super::stdio::TerminalOutput, diff --git a/crates/wasi/src/preview2/network.rs b/crates/wasi/src/preview2/network.rs index 614130392d29..1f49e06f349f 100644 --- a/crates/wasi/src/preview2/network.rs +++ b/crates/wasi/src/preview2/network.rs @@ -1,6 +1,30 @@ +use crate::preview2::bindings::wasi::sockets::network::ErrorCode; +use crate::preview2::{TableError, TrappableError}; use cap_std::net::Pool; pub struct Network { pub pool: Pool, pub allow_ip_name_lookup: bool, } + +pub type SocketResult = Result; + +pub type SocketError = TrappableError; + +impl From for SocketError { + fn from(error: TableError) -> Self { + Self::trap(error) + } +} + +impl From for SocketError { + fn from(error: std::io::Error) -> Self { + ErrorCode::from(error).into() + } +} + +impl From for SocketError { + fn from(error: rustix::io::Errno) -> Self { + ErrorCode::from(error).into() + } +} diff --git a/crates/wasi/src/preview2/preview1.rs b/crates/wasi/src/preview2/preview1.rs index a4bdada9b7f6..1a94ad7d0213 100644 --- a/crates/wasi/src/preview2/preview1.rs +++ b/crates/wasi/src/preview2/preview1.rs @@ -8,7 +8,7 @@ use crate::preview2::bindings::{ filesystem::{preopens, types as filesystem}, io::{poll, streams}, }; -use crate::preview2::{IsATTY, StreamError, TableError, WasiView}; +use crate::preview2::{FsError, IsATTY, StreamError, StreamResult, TableError, WasiView}; use anyhow::{anyhow, bail, Context}; use std::borrow::Borrow; use std::collections::{BTreeMap, HashSet}; @@ -63,26 +63,16 @@ impl BlockingMode { 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(e) if matches!(e.downcast_ref(), Some(streams::StreamError::Closed)) => { - Ok(Vec::new()) - } - Err(e) => { - tracing::trace!("throwing away read error to report as Errno::Io: {e:?}"); - Err(types::Errno::Io.into()) - } + Err(StreamError::Closed) => Ok(Vec::new()), + Err(e) => Err(e.into()), } } BlockingMode::NonBlocking => { match streams::HostInputStream::read(host, input_stream, max_size).await { Ok(r) => Ok(r), - Err(e) if matches!(e.downcast_ref(), Some(streams::StreamError::Closed)) => { - Ok(Vec::new()) - } - Err(e) => { - tracing::trace!("throwing away read error to report as Errno::Io: {e:?}"); - Err(types::Errno::Io.into()) - } + Err(StreamError::Closed) => Ok(Vec::new()), + Err(e) => Err(e.into()), } } } @@ -92,7 +82,7 @@ impl BlockingMode { host: &mut (impl streams::Host + poll::Host), output_stream: Resource, mut bytes: &[u8], - ) -> Result { + ) -> StreamResult { use streams::HostOutputStream as Streams; match self { @@ -117,7 +107,7 @@ impl BlockingMode { BlockingMode::NonBlocking => { let n = match Streams::check_write(host, output_stream.borrowed()) { Ok(n) => n, - Err(e) if matches!(e.downcast_ref(), Some(streams::StreamError::Closed)) => 0, + Err(StreamError::Closed) => 0, Err(e) => Err(e)?, }; @@ -128,17 +118,13 @@ impl BlockingMode { match Streams::write(host, output_stream.borrowed(), bytes[..len].to_vec()) { Ok(()) => {} - Err(e) if matches!(e.downcast_ref(), Some(streams::StreamError::Closed)) => { - return Ok(0) - } + Err(StreamError::Closed) => return Ok(0), Err(e) => Err(e)?, } match Streams::blocking_flush(host, output_stream.borrowed()).await { Ok(()) => {} - Err(e) if matches!(e.downcast_ref(), Some(streams::StreamError::Closed)) => { - return Ok(0) - } + Err(StreamError::Closed) => return Ok(0), Err(e) => Err(e)?, }; @@ -576,25 +562,25 @@ impl wiggle::GuestErrorType for types::Errno { impl From for types::Error { fn from(err: StreamError) -> Self { - types::Error::from(streams::Error::from(err)) - } -} - -impl From for types::Error { - fn from(err: streams::Error) -> Self { - match err.downcast() { - Ok(se) => se.into(), - Err(t) => types::Error::trap(t), + match err { + StreamError::Closed => types::Errno::Io.into(), + StreamError::LastOperationFailed(e) => match e.downcast::() { + Ok(err) => filesystem::ErrorCode::from(err).into(), + Err(e) => { + log::debug!("dropping error {e:?}"); + types::Errno::Io.into() + } + }, + StreamError::Trap(e) => types::Error::trap(e), } } } -impl From for types::Error { - fn from(err: streams::StreamError) -> Self { - match err { - streams::StreamError::Closed | streams::StreamError::LastOperationFailed => { - types::Errno::Io.into() - } +impl From for types::Error { + fn from(err: FsError) -> Self { + match err.downcast() { + Ok(code) => code.into(), + Err(e) => types::Error::trap(e), } } } @@ -788,28 +774,6 @@ impl From for types::Error { } } -impl TryFrom for types::Errno { - type Error = anyhow::Error; - - fn try_from(err: filesystem::Error) -> Result { - match err.downcast() { - Ok(code) => Ok(code.into()), - Err(e) => Err(e), - } - } -} - -impl TryFrom for types::Error { - type Error = anyhow::Error; - - fn try_from(err: filesystem::Error) -> Result { - match err.downcast() { - Ok(code) => Ok(code.into()), - Err(e) => Err(e), - } - } -} - impl From for types::Error { fn from(err: TableError) -> Self { types::Error::trap(err.into()) @@ -2231,12 +2195,8 @@ impl< let fd = fd.borrowed(); let position = position.clone(); drop(t); - match self - .stat(fd) - .await - .map_err(|e| e.try_into().context("failed to call `stat`")) - { - Ok(filesystem::DescriptorStat { size, .. }) => { + match self.stat(fd).await? { + filesystem::DescriptorStat { size, .. } => { let pos = position.load(Ordering::Relaxed); let nbytes = size.saturating_sub(pos); types::Event { @@ -2253,16 +2213,6 @@ impl< }, } } - Err(Ok(error)) => types::Event { - userdata: sub.userdata, - error, - type_: types::Eventtype::FdRead, - fd_readwrite: types::EventFdReadwrite { - flags: types::Eventrwflags::empty(), - nbytes: 1, - }, - }, - Err(Err(error)) => return Err(types::Error::trap(error)), } } // TODO: Support sockets diff --git a/crates/wasi/src/preview2/stream.rs b/crates/wasi/src/preview2/stream.rs index 4143b9ad86c2..6200e1226d07 100644 --- a/crates/wasi/src/preview2/stream.rs +++ b/crates/wasi/src/preview2/stream.rs @@ -1,5 +1,6 @@ use crate::preview2::filesystem::FileInputStream; use crate::preview2::poll::Subscribe; +use crate::preview2::TableError; use anyhow::Result; use bytes::Bytes; @@ -19,24 +20,40 @@ pub trait HostInputStream: Subscribe { /// /// The [`StreamError`] return value communicates when this stream is /// closed, when a read fails, or when a trap should be generated. - fn read(&mut self, size: usize) -> Result; + fn read(&mut self, size: usize) -> StreamResult; /// Same as the `read` method except that bytes are skipped. /// /// Note that this method is non-blocking like `read` and returns the same /// errors. - fn skip(&mut self, nelem: usize) -> Result { + fn skip(&mut self, nelem: usize) -> StreamResult { let bs = self.read(nelem)?; Ok(bs.len()) } } +/// Representation of the `error` resource type in the `wasi:io/streams` +/// interface. +/// +/// This is currently `anyhow::Error` to retain full type information for +/// errors. +pub type Error = anyhow::Error; + +pub type StreamResult = Result; + #[derive(Debug)] pub enum StreamError { Closed, LastOperationFailed(anyhow::Error), Trap(anyhow::Error), } + +impl StreamError { + pub fn trap(msg: &str) -> StreamError { + StreamError::Trap(anyhow::anyhow!("{msg}")) + } +} + impl std::fmt::Display for StreamError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -46,6 +63,7 @@ impl std::fmt::Display for StreamError { } } } + impl std::error::Error for StreamError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { @@ -55,6 +73,12 @@ impl std::error::Error for StreamError { } } +impl From for StreamError { + fn from(error: TableError) -> Self { + Self::Trap(error.into()) + } +} + /// Host trait for implementing the `wasi:io/streams.output-stream` resource: /// A bytestream which can be written to. #[async_trait::async_trait] @@ -75,7 +99,7 @@ pub trait HostOutputStream: Subscribe { /// - stream is closed /// - prior operation ([`write`](Self::write) or [`flush`](Self::flush)) failed /// - caller performed an illegal operation (e.g. wrote more bytes than were permitted) - fn write(&mut self, bytes: Bytes) -> Result<(), StreamError>; + fn write(&mut self, bytes: Bytes) -> StreamResult<()>; /// Trigger a flush of any bytes buffered in this stream implementation. /// @@ -96,7 +120,7 @@ pub trait HostOutputStream: Subscribe { /// - stream is closed /// - prior operation ([`write`](Self::write) or [`flush`](Self::flush)) failed /// - caller performed an illegal operation (e.g. wrote more bytes than were permitted) - fn flush(&mut self) -> Result<(), StreamError>; + fn flush(&mut self) -> StreamResult<()>; /// Returns the number of bytes that are ready to be written to this stream. /// @@ -110,13 +134,13 @@ pub trait HostOutputStream: Subscribe { /// Returns an [`StreamError`] if: /// - stream is closed /// - prior operation ([`write`](Self::write) or [`flush`](Self::flush)) failed - fn check_write(&mut self) -> Result; + fn check_write(&mut self) -> StreamResult; /// Repeatedly write a byte to a stream. /// Important: this write must be non-blocking! /// Returning an Err which downcasts to a [`StreamError`] will be /// reported to Wasm as the empty error result. Otherwise, errors will trap. - fn write_zeroes(&mut self, nelem: usize) -> Result<(), StreamError> { + fn write_zeroes(&mut self, nelem: usize) -> StreamResult<()> { // TODO: We could optimize this to not allocate one big zeroed buffer, and instead write // repeatedly from a 'static buffer of zeros. let bs = Bytes::from_iter(core::iter::repeat(0 as u8).take(nelem)); @@ -126,7 +150,7 @@ pub trait HostOutputStream: Subscribe { /// Simultaneously waits for this stream to be writable and then returns how /// much may be written or the last error that happened. - async fn write_ready(&mut self) -> Result { + async fn write_ready(&mut self) -> StreamResult { self.ready().await; self.check_write() } diff --git a/crates/wasi/wit/deps/filesystem/types.wit b/crates/wasi/wit/deps/filesystem/types.wit index 3f69bf997a29..aecdd0ef354b 100644 --- a/crates/wasi/wit/deps/filesystem/types.wit +++ b/crates/wasi/wit/deps/filesystem/types.wit @@ -23,7 +23,7 @@ /// /// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md interface types { - use wasi:io/streams.{input-stream, output-stream} + use wasi:io/streams.{input-stream, output-stream, error} use wasi:clocks/wall-clock.{datetime} /// File size or length of a region within a file. @@ -795,4 +795,16 @@ interface types { /// Read a single directory entry from a `directory-entry-stream`. read-directory-entry: func() -> result, error-code> } + + /// Attempts to extract a filesystem-related `error-code` from the stream + /// `error` provided. + /// + /// Stream operations which return `stream-error::last-operation-failed` + /// have a payload with more information about the operation that failed. + /// This payload can be passed through to this function to see if there's + /// filesystem-related information about the error to return. + /// + /// Note that this function is fallible because not all stream-related + /// errors are filesystem-related errors. + filesystem-error-code: func(err: borrow) -> option } diff --git a/crates/wasi/wit/deps/io/streams.wit b/crates/wasi/wit/deps/io/streams.wit index 8240507976f7..55562d1cbfce 100644 --- a/crates/wasi/wit/deps/io/streams.wit +++ b/crates/wasi/wit/deps/io/streams.wit @@ -9,15 +9,37 @@ interface streams { use poll.{pollable} /// An error for input-stream and output-stream operations. - enum stream-error { + variant stream-error { /// The last operation (a write or flush) failed before completion. - last-operation-failed, + /// + /// More information is available in the `error` payload. + last-operation-failed(error), /// The stream is closed: no more input will be accepted by the /// stream. A closed output-stream will return this error on all /// future operations. closed } + /// Contextual error information about the last failure that happened on + /// a read, write, or flush from an `input-stream` or `output-stream`. + /// + /// This type is returned through the `stream-error` type whenever an + /// operation on a stream directly fails or an error is discovered + /// after-the-fact, for example when a write's failure shows up through a + /// later `flush` or `check-write`. + /// + /// Interfaces such as `wasi:filesystem/types` provide functionality to + /// further "downcast" this error into interface-specific error information. + resource error { + /// Returns a string that's suitable to assist humans in debugging this + /// error. + /// + /// The returned string will change across platforms and hosts which + /// means that parsing it, for example, would be a + /// platform-compatibility hazard. + to-debug-string: func() -> string + } + /// An input bytestream. /// /// `input-stream`s are *non-blocking* to the extent practical on underlying diff --git a/crates/wit-bindgen/src/lib.rs b/crates/wit-bindgen/src/lib.rs index 45fe6aedf3b1..7183e0bf232c 100644 --- a/crates/wit-bindgen/src/lib.rs +++ b/crates/wit-bindgen/src/lib.rs @@ -2,7 +2,7 @@ use crate::rust::{to_rust_ident, to_rust_upper_camel_case, RustGenerator, TypeMo use crate::types::{TypeInfo, Types}; use anyhow::{anyhow, bail, Context}; use heck::*; -use indexmap::IndexMap; +use indexmap::{IndexMap, IndexSet}; use std::collections::{BTreeMap, HashMap, HashSet}; use std::fmt::Write as _; use std::io::{Read, Write}; @@ -48,6 +48,7 @@ struct Wasmtime { interface_names: HashMap, with_name_counter: usize, interface_last_seen_as_import: HashMap, + trappable_errors: IndexMap, } struct ImportInterface { @@ -218,6 +219,16 @@ impl Wasmtime { fn generate(&mut self, resolve: &Resolve, id: WorldId) -> String { self.types.analyze(resolve, id); + for (i, te) in self.opts.trappable_error_type.iter().enumerate() { + let id = resolve_type_in_package(resolve, &te.wit_package_path, &te.wit_type_name) + .context(format!("resolving {:?}", te)) + .unwrap(); + let name = format!("_TrappableError{i}"); + uwriteln!(self.src, "type {name} = {};", te.rust_type_name); + let prev = self.trappable_errors.insert(id, name); + assert!(prev.is_none()); + } + let world = &resolve.worlds[id]; for (name, import) in world.imports.iter() { if !self.opts.only_interfaces || matches!(import, WorldItem::Interface(_)) { @@ -837,32 +848,15 @@ struct InterfaceGenerator<'a> { gen: &'a mut Wasmtime, resolve: &'a Resolve, current_interface: Option<(InterfaceId, &'a WorldKey, bool)>, - - /// A mapping of wit types to their rust type name equivalent. This is the pre-processed - /// version of `gen.opts.trappable_error_types`, where the types have been eagerly resolved. - trappable_errors: IndexMap, } impl<'a> InterfaceGenerator<'a> { fn new(gen: &'a mut Wasmtime, resolve: &'a Resolve) -> InterfaceGenerator<'a> { - let trappable_errors = gen - .opts - .trappable_error_type - .iter() - .map(|te| { - let id = resolve_type_in_package(resolve, &te.wit_package_path, &te.wit_type_name) - .context(format!("resolving {:?}", te))?; - Ok((id, te.rust_type_name.clone())) - }) - .collect::>>() - .unwrap(); - InterfaceGenerator { src: Source::default(), gen, resolve, current_interface: None, - trappable_errors, } } @@ -876,10 +870,6 @@ impl<'a> InterfaceGenerator<'a> { fn types(&mut self, id: InterfaceId) { for (name, id) in self.resolve.interfaces[id].types.iter() { self.define_type(name, *id); - - if let Some(rust_name) = self.trappable_errors.get(id) { - self.define_trappable_error_type(*id, rust_name.clone()) - } } } @@ -1467,9 +1457,11 @@ impl<'a> InterfaceGenerator<'a> { _ => return None, }; - let rust_type = self.trappable_errors.get(&error_typeid)?; + let name = self.gen.trappable_errors.get(&error_typeid)?; - Some((result, error_typeid, rust_type.clone())) + let mut path = self.path_to_root(); + uwrite!(path, "{name}"); + Some((result, error_typeid, path)) } fn generate_add_to_linker(&mut self, id: InterfaceId, name: &str) { @@ -1499,14 +1491,59 @@ impl<'a> InterfaceGenerator<'a> { } self.generate_function_trait_sig(func); } + + // Generate `convert_*` functions to convert custom trappable errors + // into the representation required by Wasmtime's component API. + let mut required_conversion_traits = IndexSet::new(); + let mut errors_converted = IndexSet::new(); + let my_error_types = iface + .types + .iter() + .filter(|(_, id)| self.gen.trappable_errors.contains_key(*id)) + .map(|(_, id)| *id); + let used_error_types = iface + .functions + .iter() + .filter_map(|(_, func)| self.special_case_trappable_error(&func.results)) + .map(|(_, id, _)| id); + for err in my_error_types.chain(used_error_types).collect::>() { + let custom_name = &self.gen.trappable_errors[&err]; + let err = &self.resolve.types[resolve_type_definition_id(self.resolve, err)]; + let err_name = err.name.as_ref().unwrap(); + let err_snake = err_name.to_snake_case(); + let err_camel = err_name.to_upper_camel_case(); + let owner = match err.owner { + TypeOwner::Interface(i) => i, + _ => unimplemented!(), + }; + match self.path_to_interface(owner) { + Some(path) => { + required_conversion_traits.insert(format!("{path}::Host")); + } + None => { + if errors_converted.insert(err_name) { + let root = self.path_to_root(); + uwriteln!( + self.src, + "fn convert_{err_snake}(&mut self, err: {root}{custom_name}) -> wasmtime::Result<{err_camel}>;" + ); + } + } + } + } uwriteln!(self.src, "}}"); - let where_clause = if self.gen.opts.async_.maybe_async() { + let mut where_clause = if self.gen.opts.async_.maybe_async() { "T: Send, U: Host + Send".to_string() } else { "U: Host".to_string() }; + for t in required_conversion_traits { + where_clause.push_str(" + "); + where_clause.push_str(&t); + } + uwriteln!( self.src, " @@ -1655,16 +1692,24 @@ impl<'a> InterfaceGenerator<'a> { ); } - if self.special_case_trappable_error(&func.results).is_some() { + if let Some((_, err, _)) = self.special_case_trappable_error(&func.results) { + let err = &self.resolve.types[resolve_type_definition_id(self.resolve, err)]; + let err_name = err.name.as_ref().unwrap(); + let owner = match err.owner { + TypeOwner::Interface(i) => i, + _ => unimplemented!(), + }; + let convert_trait = match self.path_to_interface(owner) { + Some(path) => format!("{path}::Host"), + None => format!("Host"), + }; + let convert = format!("{}::convert_{}", convert_trait, err_name.to_snake_case()); uwrite!( self.src, - "match r {{ - Ok(a) => Ok((Ok(a),)), - Err(e) => match e.downcast() {{ - Ok(api_error) => Ok((Err(api_error),)), - Err(anyhow_error) => Err(anyhow_error), - }} - }}" + "Ok((match r {{ + Ok(a) => Ok(a), + Err(e) => Err({convert}(host, e)?), + }},))" ); } else if func.results.iter_types().len() == 1 { uwrite!(self.src, "Ok((r?,))\n"); @@ -1699,9 +1744,7 @@ impl<'a> InterfaceGenerator<'a> { self.push_str(")"); self.push_str(" -> "); - if let Some((r, error_id, error_typename)) = - self.special_case_trappable_error(&func.results) - { + if let Some((r, _id, error_typename)) = self.special_case_trappable_error(&func.results) { // Functions which have a single result `result` get special // cased to use the host_wasmtime_rust::Error, making it possible // for them to trap or use `?` to propogate their errors @@ -1712,12 +1755,6 @@ impl<'a> InterfaceGenerator<'a> { self.push_str("()"); } self.push_str(","); - if let TypeOwner::Interface(id) = self.resolve.types[error_id].owner { - if let Some(path) = self.path_to_interface(id) { - self.push_str(&path); - self.push_str("::"); - } - } self.push_str(&error_typename); self.push_str(">"); } else { @@ -1867,53 +1904,6 @@ impl<'a> InterfaceGenerator<'a> { self.src.push_str("}\n"); } - fn define_trappable_error_type(&mut self, id: TypeId, rust_name: String) { - let info = self.info(id); - if self.lifetime_for(&info, TypeMode::Owned).is_some() { - panic!("wit error for {rust_name} is not 'static") - } - let abi_type = self.param_name(id); - - uwriteln!( - self.src, - " - #[derive(Debug)] - pub struct {rust_name} {{ - inner: anyhow::Error, - }} - impl std::fmt::Display for {rust_name} {{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{ - write!(f, \"{{}}\", self.inner) - }} - }} - impl std::error::Error for {rust_name} {{ - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {{ - self.inner.source() - }} - }} - impl {rust_name} {{ - pub fn trap(inner: anyhow::Error) -> Self {{ - Self {{ inner }} - }} - pub fn downcast(self) -> Result<{abi_type}, anyhow::Error> {{ - self.inner.downcast() - }} - pub fn downcast_ref(&self) -> Option<&{abi_type}> {{ - self.inner.downcast_ref() - }} - pub fn context(self, s: impl Into) -> Self {{ - Self {{ inner: self.inner.context(s.into()) }} - }} - }} - impl From<{abi_type}> for {rust_name} {{ - fn from(abi: {abi_type}) -> {rust_name} {{ - {rust_name} {{ inner: anyhow::Error::from(abi) }} - }} - }} - " - ); - } - fn rustdoc(&mut self, docs: &Docs) { let docs = match &docs.contents { Some(docs) => docs, diff --git a/tests/all/component_model/bindgen/results.rs b/tests/all/component_model/bindgen/results.rs index 6c0b3f8b6472..32a645682629 100644 --- a/tests/all/component_model/bindgen/results.rs +++ b/tests/all/component_model/bindgen/results.rs @@ -241,6 +241,39 @@ mod enum_error { trappable_error_type: { "inline:inline/imports"::e1: TrappableE1 } }); + // You can create concrete trap types which make it all the way out to the + // host caller, via downcast_ref below. + #[derive(Debug)] + pub struct MyTrap; + + impl std::fmt::Display for MyTrap { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } + } + impl std::error::Error for MyTrap {} + + pub enum TrappableE1 { + Normal(imports::E1), + MyTrap(MyTrap), + } + + // It is possible to define From impls that target these generated trappable + // types. This allows you to integrate libraries with other error types, or + // use your own more descriptive error types, and use ? to convert them at + // their throw site. + impl From for TrappableE1 { + fn from(t: MyTrap) -> TrappableE1 { + TrappableE1::MyTrap(t) + } + } + + impl From for TrappableE1 { + fn from(t: imports::E1) -> TrappableE1 { + TrappableE1::Normal(t) + } + } + #[test] fn run() -> Result<(), Error> { let engine = engine(); @@ -305,33 +338,18 @@ mod enum_error { ), )?; - // You can create concrete trap types which make it all the way out to the - // host caller, via downcast_ref below. - #[derive(Debug)] - struct MyTrap; - - impl std::fmt::Display for MyTrap { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } - } - impl std::error::Error for MyTrap {} - - // It is possible to define From impls that target these generated trappable - // types. This allows you to integrate libraries with other error types, or - // use your own more descriptive error types, and use ? to convert them at - // their throw site. - impl From for imports::TrappableE1 { - fn from(t: MyTrap) -> imports::TrappableE1 { - imports::TrappableE1::trap(anyhow!(t)) - } - } - #[derive(Default)] struct MyImports {} impl imports::Host for MyImports { - fn enum_error(&mut self, a: f64) -> Result { + fn convert_e1(&mut self, err: TrappableE1) -> anyhow::Result { + match err { + TrappableE1::Normal(e) => Ok(e), + TrappableE1::MyTrap(e) => Err(e.into()), + } + } + + fn enum_error(&mut self, a: f64) -> Result { if a == 0.0 { Ok(a) } else if a == 1.0 { @@ -405,6 +423,17 @@ mod record_error { trappable_error_type: { "inline:inline/imports"::"e2": TrappableE2 } }); + pub enum TrappableE2 { + Normal(imports::E2), + Trap(anyhow::Error), + } + + impl From for TrappableE2 { + fn from(t: imports::E2) -> TrappableE2 { + TrappableE2::Normal(t) + } + } + #[test] fn run() -> Result<(), Error> { let engine = engine(); @@ -476,7 +505,13 @@ mod record_error { struct MyImports {} impl imports::Host for MyImports { - fn record_error(&mut self, a: f64) -> Result { + fn convert_e2(&mut self, err: TrappableE2) -> anyhow::Result { + match err { + TrappableE2::Normal(e) => Ok(e), + TrappableE2::Trap(e) => Err(e.into()), + } + } + fn record_error(&mut self, a: f64) -> Result { if a == 0.0 { Ok(a) } else if a == 1.0 { @@ -485,7 +520,7 @@ mod record_error { col: 1312, })? } else { - Err(imports::TrappableE2::trap(anyhow!("record_error: trap"))) + Err(TrappableE2::Trap(anyhow!("record_error: trap"))) } } } @@ -559,6 +594,17 @@ mod variant_error { trappable_error_type: { "inline:inline/imports"::e3: TrappableE3 } }); + pub enum TrappableE3 { + Normal(imports::E3), + Trap(anyhow::Error), + } + + impl From for TrappableE3 { + fn from(t: imports::E3) -> TrappableE3 { + TrappableE3::Normal(t) + } + } + #[test] fn run() -> Result<(), Error> { let engine = engine(); @@ -653,7 +699,13 @@ mod variant_error { struct MyImports {} impl imports::Host for MyImports { - fn variant_error(&mut self, a: f64) -> Result { + fn convert_e3(&mut self, err: TrappableE3) -> anyhow::Result { + match err { + TrappableE3::Normal(e) => Ok(e), + TrappableE3::Trap(e) => Err(e.into()), + } + } + fn variant_error(&mut self, a: f64) -> Result { if a == 0.0 { Ok(a) } else if a == 1.0 { @@ -662,7 +714,7 @@ mod variant_error { col: 1312, }))? } else { - Err(imports::TrappableE3::trap(anyhow!("variant_error: trap"))) + Err(TrappableE3::Trap(anyhow!("variant_error: trap"))) } } } @@ -737,6 +789,17 @@ mod multiple_interfaces_error { trappable_error_type: { "inline:inline/types"::e1: TrappableE1 } }); + pub enum TrappableE1 { + Normal(types::E1), + Trap(anyhow::Error), + } + + impl From for TrappableE1 { + fn from(t: imports::E1) -> TrappableE1 { + TrappableE1::Normal(t) + } + } + #[test] fn run() -> Result<(), Error> { let engine = engine(); @@ -819,9 +882,9 @@ mod multiple_interfaces_error { // types. This allows you to integrate libraries with other error types, or // use your own more descriptive error types, and use ? to convert them at // their throw site. - impl From for types::TrappableE1 { - fn from(t: MyTrap) -> types::TrappableE1 { - types::TrappableE1::trap(anyhow!(t)) + impl From for TrappableE1 { + fn from(t: MyTrap) -> TrappableE1 { + TrappableE1::Trap(anyhow!(t)) } } @@ -829,7 +892,13 @@ mod multiple_interfaces_error { struct MyImports {} impl types::Host for MyImports { - fn enum_error(&mut self, a: f64) -> Result { + fn convert_e1(&mut self, err: TrappableE1) -> anyhow::Result { + match err { + TrappableE1::Normal(e) => Ok(e), + TrappableE1::Trap(e) => Err(e.into()), + } + } + fn enum_error(&mut self, a: f64) -> Result { if a == 0.0 { Ok(a) } else if a == 1.0 { @@ -841,7 +910,7 @@ mod multiple_interfaces_error { } impl imports::Host for MyImports { - fn enum_error(&mut self, a: f64) -> Result { + fn enum_error(&mut self, a: f64) -> Result { if a == 0.0 { Ok(a) } else if a == 1.0 {