Browse Source

Add an error resource to WASI streams (#7152) (#7169)

* Change `bindgen!`'s trappable error to take an input type

This commit removes the generated type for `trappable_error_type` from
the `bindgen!` macro to allow users to provide a type instead. This
works similarly as before with new conversion functions generated in
traits which are used to convert the custom error into the ABI
representation of what a WIT world expects.

There are a few motivations for this commit:

* This enables reducing the number of errors in play between async/sync
  bindings by using the same error type there.

* This avoids an auto-generated type which is more difficult to inspect
  than one that's written down already and the source can more easily be
  inspected.

* This enables richer conversions using `self` (e.g. `self.table_mut()`)
  between error types rather than purely relying on `Into`. This is
  important for #7017 where an error is going to be inserted into the
  table as it gets converted.

* Fix tests

* Update WASI to use new trappable errors

This commit deals with the fallout of the previous commit for the WASI
preview2 implementation. The main changes here are:

* Bindgen-generated `Error` types no longer exist. These are replaced
  with `TrappableError<T>` where type aliases are used such as

  ```rust
  type FsError = TrappableError<wasi::filesystem::types::ErrorCode>;
  ```

* Type synonyms such as `FsResult<T>` are now added for more
  conveniently writing down fallible signatures.

* Some various error conversions are updated from converting to the old
  `Error` type to now instead directly into corresponding `ErrorCode` types.

* A number of cases where unknown errors were turned into traps now
  return bland error codes and log the error instead since these aren't
  fatal events.

* The `StreamError` type does not use `TrappableError` since it has
  other variants that it's concerned with such as a
  `LastOperationFailed` variant which has an `anyhow::Error` payload.

* Some minor preview1 issues were fixed such as trapping errors being
  turned into normal I/O errors by accident.

* Add an `error` resource to WASI streams

This commit adds a new `error` resource to the `wasi:io/streams`
interface. This `error` resource is returned as part of
`last-operation-failed` and serves as a means to discover through other
interfaces more granular type information than a generic string. This
error type has a new function in the `filesystem` interface, for
example, which enables getting filesystem-related error codes from I/O
performed on filesystem-originating streams. This is plumbed through to
the adapter as well to return more than `ERRNO_IO` from failed
read/write operations now too.

This is not super fancy just yet but is intended to be a vector through
which future additions can be done. The main thing this enables is to
avoid dropping errors on the floor in the host and enabling the guest to
discover further information about I/O errors on streams.

Closes #7017

* Update crates/wasi-http/wit/deps/io/streams.wit



* Update wasi-http wit too

* Remove unnecessary clone

---------

Co-authored-by: Trevor Elliott <awesomelyawesome@gmail.com>
pull/7175/head
Alex Crichton 1 year ago
committed by GitHub
parent
commit
0573117736
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      crates/component-macro/src/bindgen.rs
  2. 42
      crates/component-macro/tests/codegen.rs
  3. 14
      crates/wasi-http/wit/deps/filesystem/types.wit
  4. 26
      crates/wasi-http/wit/deps/io/streams.wit
  5. 28
      crates/wasi-preview1-component-adapter/src/lib.rs
  6. 8
      crates/wasi/src/preview2/command.rs
  7. 74
      crates/wasi/src/preview2/error.rs
  8. 29
      crates/wasi/src/preview2/filesystem.rs
  9. 272
      crates/wasi/src/preview2/host/filesystem.rs
  10. 115
      crates/wasi/src/preview2/host/filesystem/sync.rs
  11. 180
      crates/wasi/src/preview2/host/io.rs
  12. 40
      crates/wasi/src/preview2/host/network.rs
  13. 82
      crates/wasi/src/preview2/host/tcp.rs
  14. 9
      crates/wasi/src/preview2/host/tcp_create_socket.rs
  15. 10
      crates/wasi/src/preview2/ip_name_lookup.rs
  16. 25
      crates/wasi/src/preview2/mod.rs
  17. 24
      crates/wasi/src/preview2/network.rs
  18. 102
      crates/wasi/src/preview2/preview1.rs
  19. 38
      crates/wasi/src/preview2/stream.rs
  20. 14
      crates/wasi/wit/deps/filesystem/types.wit
  21. 26
      crates/wasi/wit/deps/io/streams.wit
  22. 168
      crates/wit-bindgen/src/lib.rs
  23. 133
      tests/all/component_model/bindgen/results.rs

5
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<TrappableError>
input.parse::<Token![::]>()?;
let wit_type_name = ident_or_str(input)?;
input.parse::<Token![:]>()?;
let rust_type_name = input.parse::<Ident>()?.to_string();
let rust_type_name = input.parse::<syn::Path>()?.to_token_stream().to_string();
Ok(TrappableError {
wit_package_path,
wit_type_name,

42
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;
}

14
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<option<directory-entry>, 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<error>) -> option<error-code>
}

26
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

28
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)

8
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,

74
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<T>` 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<bindgen::TheError>;
/// ```
///
/// 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<T> {
err: anyhow::Error,
_marker: marker::PhantomData<T>,
}
impl<T> TrappableError<T> {
pub fn trap(err: impl Into<anyhow::Error>) -> TrappableError<T> {
TrappableError {
err: err.into(),
_marker: marker::PhantomData,
}
}
pub fn downcast(self) -> anyhow::Result<T>
where
T: Error + Send + Sync + 'static,
{
self.err.downcast()
}
}
impl<T> From<T> for TrappableError<T>
where
T: Error + Send + Sync + 'static,
{
fn from(error: T) -> Self {
Self {
err: error.into(),
_marker: marker::PhantomData,
}
}
}
impl<T> fmt::Debug for TrappableError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.err.fmt(f)
}
}
impl<T> fmt::Display for TrappableError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.err.fmt(f)
}
}
impl<T> Error for TrappableError<T> {}

29
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<T> = Result<T, FsError>;
pub type FsError = TrappableError<types::ErrorCode>;
impl From<TableError> for FsError {
fn from(error: TableError) -> Self {
Self::trap(error)
}
}
impl From<io::Error> 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<dyn Iterator<Item = Result<types::DirectoryEntry, types::Error>> + Send + 'static>,
>,
std::sync::Mutex<Box<dyn Iterator<Item = FsResult<types::DirectoryEntry>> + Send + 'static>>,
);
impl ReaddirIterator {
pub(crate) fn new(
i: impl Iterator<Item = Result<types::DirectoryEntry, types::Error>> + Send + 'static,
i: impl Iterator<Item = FsResult<types::DirectoryEntry>> + Send + 'static,
) -> Self {
ReaddirIterator(std::sync::Mutex::new(Box::new(i)))
}
pub(crate) fn next(&self) -> Result<Option<types::DirectoryEntry>, types::Error> {
pub(crate) fn next(&self) -> FsResult<Option<types::DirectoryEntry>> {
self.0.lock().unwrap().next().transpose()
}
}
impl IntoIterator for ReaddirIterator {
type Item = Result<types::DirectoryEntry, types::Error>;
type Item = FsResult<types::DirectoryEntry>;
type IntoIter = Box<dyn Iterator<Item = Self::Item> + Send>;
fn into_iter(self) -> Self::IntoIter {

272
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<TableError> for types::Error {
fn from(error: TableError) -> Self {
Self::trap(error.into())
}
}
impl<T: WasiView> preopens::Host for T {
fn get_directories(
&mut self,
@ -35,7 +29,26 @@ impl<T: WasiView> preopens::Host for T {
}
#[async_trait::async_trait]
impl<T: WasiView> types::Host for T {}
impl<T: WasiView> types::Host for T {
fn convert_error_code(&mut self, err: FsError) -> anyhow::Result<ErrorCode> {
err.downcast()
}
fn filesystem_error_code(
&mut self,
err: Resource<anyhow::Error>,
) -> anyhow::Result<Option<ErrorCode>> {
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::<std::io::Error>() {
return Ok(Some(ErrorCode::from(err)));
}
Ok(None)
}
}
#[async_trait::async_trait]
impl<T: WasiView> HostDescriptor for T {
@ -45,7 +58,7 @@ impl<T: WasiView> 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<T: WasiView> HostDescriptor for T {
Ok(())
}
async fn sync_data(&mut self, fd: Resource<types::Descriptor>) -> Result<(), types::Error> {
async fn sync_data(&mut self, fd: Resource<types::Descriptor>) -> FsResult<()> {
let table = self.table();
match table.get_resource(&fd)? {
@ -94,7 +107,7 @@ impl<T: WasiView> HostDescriptor for T {
async fn get_flags(
&mut self,
fd: Resource<types::Descriptor>,
) -> Result<types::DescriptorFlags, types::Error> {
) -> FsResult<types::DescriptorFlags> {
use system_interface::fs::{FdFlags, GetSetFdFlags};
use types::DescriptorFlags;
@ -142,7 +155,7 @@ impl<T: WasiView> HostDescriptor for T {
async fn get_type(
&mut self,
fd: Resource<types::Descriptor>,
) -> Result<types::DescriptorType, types::Error> {
) -> FsResult<types::DescriptorType> {
let table = self.table();
match table.get_resource(&fd)? {
@ -158,7 +171,7 @@ impl<T: WasiView> HostDescriptor for T {
&mut self,
fd: Resource<types::Descriptor>,
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<T: WasiView> HostDescriptor for T {
fd: Resource<types::Descriptor>,
atim: types::NewTimestamp,
mtim: types::NewTimestamp,
) -> Result<(), types::Error> {
) -> FsResult<()> {
use fs_set_times::SetTimes;
let table = self.table();
@ -203,7 +216,7 @@ impl<T: WasiView> HostDescriptor for T {
fd: Resource<types::Descriptor>,
len: types::Filesize,
offset: types::Filesize,
) -> Result<(Vec<u8>, bool), types::Error> {
) -> FsResult<(Vec<u8>, bool)> {
use std::io::IoSliceMut;
use system_interface::fs::FileIoExt;
@ -241,7 +254,7 @@ impl<T: WasiView> HostDescriptor for T {
fd: Resource<types::Descriptor>,
buf: Vec<u8>,
offset: types::Filesize,
) -> Result<types::Filesize, types::Error> {
) -> FsResult<types::Filesize> {
use std::io::IoSlice;
use system_interface::fs::FileIoExt;
@ -261,7 +274,7 @@ impl<T: WasiView> HostDescriptor for T {
async fn read_directory(
&mut self,
fd: Resource<types::Descriptor>,
) -> Result<Resource<types::DirectoryEntryStream>, types::Error> {
) -> FsResult<Resource<types::DirectoryEntryStream>> {
let table = self.table_mut();
let d = table.get_resource(&fd)?.dir()?;
if !d.perms.contains(DirPerms::READ) {
@ -317,13 +330,13 @@ impl<T: WasiView> 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<types::Descriptor>) -> Result<(), types::Error> {
async fn sync(&mut self, fd: Resource<types::Descriptor>) -> FsResult<()> {
let table = self.table();
match table.get_resource(&fd)? {
@ -354,7 +367,7 @@ impl<T: WasiView> HostDescriptor for T {
&mut self,
fd: Resource<types::Descriptor>,
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<T: WasiView> HostDescriptor for T {
Ok(())
}
async fn stat(
&mut self,
fd: Resource<types::Descriptor>,
) -> Result<types::DescriptorStat, types::Error> {
async fn stat(&mut self, fd: Resource<types::Descriptor>) -> FsResult<types::DescriptorStat> {
let table = self.table();
match table.get_resource(&fd)? {
Descriptor::File(f) => {
@ -388,7 +398,7 @@ impl<T: WasiView> HostDescriptor for T {
fd: Resource<types::Descriptor>,
path_flags: types::PathFlags,
path: String,
) -> Result<types::DescriptorStat, types::Error> {
) -> FsResult<types::DescriptorStat> {
let table = self.table();
let d = table.get_resource(&fd)?.dir()?;
if !d.perms.contains(DirPerms::READ) {
@ -410,7 +420,7 @@ impl<T: WasiView> 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<T: WasiView> HostDescriptor for T {
old_path: String,
new_descriptor: Resource<types::Descriptor>,
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<T: WasiView> HostDescriptor for T {
// TODO: These are the permissions to use when creating a new file.
// Not implemented yet.
_mode: types::Modes,
) -> Result<Resource<types::Descriptor>, types::Error> {
) -> FsResult<Resource<types::Descriptor>> {
use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt, OpenOptionsMaybeDirExt};
use system_interface::fs::{FdFlags, GetSetFdFlags};
use types::{DescriptorFlags, OpenFlags};
@ -605,7 +615,7 @@ impl<T: WasiView> HostDescriptor for T {
&mut self,
fd: Resource<types::Descriptor>,
path: String,
) -> Result<String, types::Error> {
) -> FsResult<String> {
let table = self.table();
let d = table.get_resource(&fd)?.dir()?;
if !d.perms.contains(DirPerms::READ) {
@ -622,7 +632,7 @@ impl<T: WasiView> HostDescriptor for T {
&mut self,
fd: Resource<types::Descriptor>,
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<T: WasiView> HostDescriptor for T {
old_path: String,
new_fd: Resource<types::Descriptor>,
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<T: WasiView> HostDescriptor for T {
fd: Resource<types::Descriptor>,
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<T: WasiView> HostDescriptor for T {
&mut self,
fd: Resource<types::Descriptor>,
path: String,
) -> Result<(), types::Error> {
) -> FsResult<()> {
use cap_fs_ext::DirExt;
let table = self.table();
@ -694,7 +704,7 @@ impl<T: WasiView> 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<T: WasiView> 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<T: WasiView> 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<types::Descriptor>) -> Result<(), types::Error> {
async fn lock_shared(&mut self, _fd: Resource<types::Descriptor>) -> FsResult<()> {
todo!("filesystem lock_shared is not implemented")
}
async fn lock_exclusive(
&mut self,
_fd: Resource<types::Descriptor>,
) -> Result<(), types::Error> {
async fn lock_exclusive(&mut self, _fd: Resource<types::Descriptor>) -> FsResult<()> {
todo!("filesystem lock_exclusive is not implemented")
}
async fn try_lock_shared(
&mut self,
_fd: Resource<types::Descriptor>,
) -> Result<(), types::Error> {
async fn try_lock_shared(&mut self, _fd: Resource<types::Descriptor>) -> FsResult<()> {
todo!("filesystem try_lock_shared is not implemented")
}
async fn try_lock_exclusive(
&mut self,
_fd: Resource<types::Descriptor>,
) -> Result<(), types::Error> {
async fn try_lock_exclusive(&mut self, _fd: Resource<types::Descriptor>) -> FsResult<()> {
todo!("filesystem try_lock_exclusive is not implemented")
}
async fn unlock(&mut self, _fd: Resource<types::Descriptor>) -> Result<(), types::Error> {
async fn unlock(&mut self, _fd: Resource<types::Descriptor>) -> FsResult<()> {
todo!("filesystem unlock is not implemented")
}
@ -751,7 +752,7 @@ impl<T: WasiView> HostDescriptor for T {
&mut self,
fd: Resource<types::Descriptor>,
offset: types::Filesize,
) -> Result<Resource<InputStream>, types::Error> {
) -> FsResult<Resource<InputStream>> {
// Trap if fd lookup fails:
let f = self.table().get_resource(&fd)?.file()?;
@ -774,7 +775,7 @@ impl<T: WasiView> HostDescriptor for T {
&mut self,
fd: Resource<types::Descriptor>,
offset: types::Filesize,
) -> Result<Resource<OutputStream>, types::Error> {
) -> FsResult<Resource<OutputStream>> {
// Trap if fd lookup fails:
let f = self.table().get_resource(&fd)?.file()?;
@ -798,7 +799,7 @@ impl<T: WasiView> HostDescriptor for T {
fn append_via_stream(
&mut self,
fd: Resource<types::Descriptor>,
) -> Result<Resource<OutputStream>, types::Error> {
) -> FsResult<Resource<OutputStream>> {
// Trap if fd lookup fails:
let f = self.table().get_resource(&fd)?.file()?;
@ -847,7 +848,7 @@ impl<T: WasiView> HostDescriptor for T {
async fn metadata_hash(
&mut self,
fd: Resource<types::Descriptor>,
) -> Result<types::MetadataHashValue, types::Error> {
) -> FsResult<types::MetadataHashValue> {
let table = self.table();
let meta = get_descriptor_metadata(table, fd).await?;
Ok(calculate_metadata_hash(&meta))
@ -857,7 +858,7 @@ impl<T: WasiView> HostDescriptor for T {
fd: Resource<types::Descriptor>,
path_flags: types::PathFlags,
path: String,
) -> Result<types::MetadataHashValue, types::Error> {
) -> FsResult<types::MetadataHashValue> {
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<T: WasiView> HostDirectoryEntryStream for T {
async fn read_directory_entry(
&mut self,
stream: Resource<types::DirectoryEntryStream>,
) -> Result<Option<types::DirectoryEntry>, types::Error> {
) -> FsResult<Option<types::DirectoryEntry>> {
let table = self.table();
let readdir = table.get_resource(&stream)?;
readdir.next()
@ -894,7 +895,7 @@ impl<T: WasiView> HostDirectoryEntryStream for T {
async fn get_descriptor_metadata(
table: &Table,
fd: Resource<types::Descriptor>,
) -> Result<cap_std::fs::Metadata, types::Error> {
) -> FsResult<cap_std::fs::Metadata> {
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<i32>) -> Option<types::Error> {
fn from_raw_os_error(err: Option<i32>) -> Option<ErrorCode> {
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<i32>) -> Option<types::Error> {
fn from_raw_os_error(raw_os_error: Option<i32>) -> Option<ErrorCode> {
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<std::io::Error> for types::Error {
fn from(err: std::io::Error) -> types::Error {
impl From<std::io::Error> 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<cap_rand::Error> for types::Error {
fn from(err: cap_rand::Error) -> types::Error {
impl From<cap_rand::Error> 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<std::num::TryFromIntError> for types::Error {
fn from(_err: std::num::TryFromIntError) -> types::Error {
ErrorCode::Overflow.into()
impl From<std::num::TryFromIntError> 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<Option<fs_set_times::SystemTimeSpec>, types::Error> {
fn systemtimespec_from(t: types::NewTimestamp) -> FsResult<Option<fs_set_times::SystemTimeSpec>> {
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<std::time::SystemTime, types::Error> {
fn systemtime_from(t: wall_clock::Datetime) -> FsResult<std::time::SystemTime> {
use std::time::{Duration, SystemTime};
SystemTime::UNIX_EPOCH
.checked_add(Duration::new(t.seconds, t.nanoseconds))

115
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<T: async_filesystem::Host> sync_filesystem::Host for T {}
impl<T: async_filesystem::Host> sync_filesystem::Host for T {
fn convert_error_code(&mut self, err: FsError) -> anyhow::Result<sync_filesystem::ErrorCode> {
Ok(async_filesystem::Host::convert_error_code(self, err)?.into())
}
fn filesystem_error_code(
&mut self,
err: Resource<streams::Error>,
) -> anyhow::Result<Option<sync_filesystem::ErrorCode>> {
Ok(async_filesystem::Host::filesystem_error_code(self, err)?.map(|e| e.into()))
}
}
impl<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T {
fn advise(
@ -13,16 +24,13 @@ impl<T: async_filesystem::HostDescriptor> 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<sync_filesystem::Descriptor>,
) -> Result<(), sync_filesystem::Error> {
fn sync_data(&mut self, fd: Resource<sync_filesystem::Descriptor>) -> FsResult<()> {
Ok(in_tokio(async {
async_filesystem::HostDescriptor::sync_data(self, fd).await
})?)
@ -31,14 +39,14 @@ impl<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
fn get_flags(
&mut self,
fd: Resource<sync_filesystem::Descriptor>,
) -> Result<sync_filesystem::DescriptorFlags, sync_filesystem::Error> {
) -> FsResult<sync_filesystem::DescriptorFlags> {
Ok(in_tokio(async { async_filesystem::HostDescriptor::get_flags(self, fd).await })?.into())
}
fn get_type(
&mut self,
fd: Resource<sync_filesystem::Descriptor>,
) -> Result<sync_filesystem::DescriptorType, sync_filesystem::Error> {
) -> FsResult<sync_filesystem::DescriptorType> {
Ok(in_tokio(async { async_filesystem::HostDescriptor::get_type(self, fd).await })?.into())
}
@ -46,7 +54,7 @@ impl<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
&mut self,
fd: Resource<sync_filesystem::Descriptor>,
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<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
fd: Resource<sync_filesystem::Descriptor>,
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<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
fd: Resource<sync_filesystem::Descriptor>,
len: sync_filesystem::Filesize,
offset: sync_filesystem::Filesize,
) -> Result<(Vec<u8>, bool), sync_filesystem::Error> {
) -> FsResult<(Vec<u8>, bool)> {
Ok(in_tokio(async {
async_filesystem::HostDescriptor::read(self, fd, len, offset).await
})?)
@ -79,7 +87,7 @@ impl<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
fd: Resource<sync_filesystem::Descriptor>,
buf: Vec<u8>,
offset: sync_filesystem::Filesize,
) -> Result<sync_filesystem::Filesize, sync_filesystem::Error> {
) -> FsResult<sync_filesystem::Filesize> {
Ok(in_tokio(async {
async_filesystem::HostDescriptor::write(self, fd, buf, offset).await
})?)
@ -88,16 +96,13 @@ impl<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
fn read_directory(
&mut self,
fd: Resource<sync_filesystem::Descriptor>,
) -> Result<Resource<sync_filesystem::DirectoryEntryStream>, sync_filesystem::Error> {
) -> FsResult<Resource<sync_filesystem::DirectoryEntryStream>> {
Ok(in_tokio(async {
async_filesystem::HostDescriptor::read_directory(self, fd).await
})?)
}
fn sync(
&mut self,
fd: Resource<sync_filesystem::Descriptor>,
) -> Result<(), sync_filesystem::Error> {
fn sync(&mut self, fd: Resource<sync_filesystem::Descriptor>) -> FsResult<()> {
Ok(in_tokio(async {
async_filesystem::HostDescriptor::sync(self, fd).await
})?)
@ -107,7 +112,7 @@ impl<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
&mut self,
fd: Resource<sync_filesystem::Descriptor>,
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<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
fn stat(
&mut self,
fd: Resource<sync_filesystem::Descriptor>,
) -> Result<sync_filesystem::DescriptorStat, sync_filesystem::Error> {
) -> FsResult<sync_filesystem::DescriptorStat> {
Ok(in_tokio(async { async_filesystem::HostDescriptor::stat(self, fd).await })?.into())
}
@ -125,7 +130,7 @@ impl<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
fd: Resource<sync_filesystem::Descriptor>,
path_flags: sync_filesystem::PathFlags,
path: String,
) -> Result<sync_filesystem::DescriptorStat, sync_filesystem::Error> {
) -> FsResult<sync_filesystem::DescriptorStat> {
Ok(in_tokio(async {
async_filesystem::HostDescriptor::stat_at(self, fd, path_flags.into(), path).await
})?
@ -139,7 +144,7 @@ impl<T: async_filesystem::HostDescriptor> 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<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
old_path: String,
new_descriptor: Resource<sync_filesystem::Descriptor>,
new_path: String,
) -> Result<(), sync_filesystem::Error> {
) -> FsResult<()> {
Ok(in_tokio(async {
async_filesystem::HostDescriptor::link_at(
self,
@ -183,7 +188,7 @@ impl<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
oflags: sync_filesystem::OpenFlags,
flags: sync_filesystem::DescriptorFlags,
mode: sync_filesystem::Modes,
) -> Result<Resource<sync_filesystem::Descriptor>, sync_filesystem::Error> {
) -> FsResult<Resource<sync_filesystem::Descriptor>> {
Ok(in_tokio(async {
async_filesystem::HostDescriptor::open_at(
self,
@ -206,7 +211,7 @@ impl<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
&mut self,
fd: Resource<sync_filesystem::Descriptor>,
path: String,
) -> Result<String, sync_filesystem::Error> {
) -> FsResult<String> {
Ok(in_tokio(async {
async_filesystem::HostDescriptor::readlink_at(self, fd, path).await
})?)
@ -216,7 +221,7 @@ impl<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
&mut self,
fd: Resource<sync_filesystem::Descriptor>,
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<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
old_path: String,
new_fd: Resource<sync_filesystem::Descriptor>,
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<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
fd: Resource<sync_filesystem::Descriptor>,
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<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
&mut self,
fd: Resource<sync_filesystem::Descriptor>,
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<T: async_filesystem::HostDescriptor> 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<T: async_filesystem::HostDescriptor> 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<T: async_filesystem::HostDescriptor> 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<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
})?)
}
fn lock_shared(
&mut self,
fd: Resource<sync_filesystem::Descriptor>,
) -> Result<(), sync_filesystem::Error> {
fn lock_shared(&mut self, fd: Resource<sync_filesystem::Descriptor>) -> FsResult<()> {
Ok(in_tokio(async {
async_filesystem::HostDescriptor::lock_shared(self, fd).await
})?)
}
fn lock_exclusive(
&mut self,
fd: Resource<sync_filesystem::Descriptor>,
) -> Result<(), sync_filesystem::Error> {
fn lock_exclusive(&mut self, fd: Resource<sync_filesystem::Descriptor>) -> FsResult<()> {
Ok(in_tokio(async {
async_filesystem::HostDescriptor::lock_exclusive(self, fd).await
})?)
}
fn try_lock_shared(
&mut self,
fd: Resource<sync_filesystem::Descriptor>,
) -> Result<(), sync_filesystem::Error> {
fn try_lock_shared(&mut self, fd: Resource<sync_filesystem::Descriptor>) -> FsResult<()> {
Ok(in_tokio(async {
async_filesystem::HostDescriptor::try_lock_shared(self, fd).await
})?)
}
fn try_lock_exclusive(
&mut self,
fd: Resource<sync_filesystem::Descriptor>,
) -> Result<(), sync_filesystem::Error> {
fn try_lock_exclusive(&mut self, fd: Resource<sync_filesystem::Descriptor>) -> FsResult<()> {
Ok(in_tokio(async {
async_filesystem::HostDescriptor::try_lock_exclusive(self, fd).await
})?)
}
fn unlock(
&mut self,
fd: Resource<sync_filesystem::Descriptor>,
) -> Result<(), sync_filesystem::Error> {
fn unlock(&mut self, fd: Resource<sync_filesystem::Descriptor>) -> FsResult<()> {
Ok(in_tokio(async {
async_filesystem::HostDescriptor::unlock(self, fd).await
})?)
@ -361,7 +351,7 @@ impl<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
&mut self,
fd: Resource<sync_filesystem::Descriptor>,
offset: sync_filesystem::Filesize,
) -> Result<Resource<streams::InputStream>, sync_filesystem::Error> {
) -> FsResult<Resource<streams::InputStream>> {
Ok(async_filesystem::HostDescriptor::read_via_stream(
self, fd, offset,
)?)
@ -371,7 +361,7 @@ impl<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
&mut self,
fd: Resource<sync_filesystem::Descriptor>,
offset: sync_filesystem::Filesize,
) -> Result<Resource<streams::OutputStream>, sync_filesystem::Error> {
) -> FsResult<Resource<streams::OutputStream>> {
Ok(async_filesystem::HostDescriptor::write_via_stream(
self, fd, offset,
)?)
@ -380,7 +370,7 @@ impl<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
fn append_via_stream(
&mut self,
fd: Resource<sync_filesystem::Descriptor>,
) -> Result<Resource<streams::OutputStream>, sync_filesystem::Error> {
) -> FsResult<Resource<streams::OutputStream>> {
Ok(async_filesystem::HostDescriptor::append_via_stream(
self, fd,
)?)
@ -398,7 +388,7 @@ impl<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
fn metadata_hash(
&mut self,
fd: Resource<sync_filesystem::Descriptor>,
) -> Result<sync_filesystem::MetadataHashValue, sync_filesystem::Error> {
) -> FsResult<sync_filesystem::MetadataHashValue> {
Ok(
in_tokio(async { async_filesystem::HostDescriptor::metadata_hash(self, fd).await })?
.into(),
@ -409,7 +399,7 @@ impl<T: async_filesystem::HostDescriptor> sync_filesystem::HostDescriptor for T
fd: Resource<sync_filesystem::Descriptor>,
path_flags: sync_filesystem::PathFlags,
path: String,
) -> Result<sync_filesystem::MetadataHashValue, sync_filesystem::Error> {
) -> FsResult<sync_filesystem::MetadataHashValue> {
Ok(in_tokio(async {
async_filesystem::HostDescriptor::metadata_hash_at(self, fd, path_flags.into(), path)
.await
@ -424,7 +414,7 @@ impl<T: async_filesystem::HostDirectoryEntryStream> sync_filesystem::HostDirecto
fn read_directory_entry(
&mut self,
stream: Resource<sync_filesystem::DirectoryEntryStream>,
) -> Result<Option<sync_filesystem::DirectoryEntry>, sync_filesystem::Error> {
) -> FsResult<Option<sync_filesystem::DirectoryEntry>> {
Ok(in_tokio(async {
async_filesystem::HostDirectoryEntryStream::read_directory_entry(self, stream).await
})?
@ -484,15 +474,6 @@ impl From<async_filesystem::ErrorCode> for sync_filesystem::ErrorCode {
}
}
impl From<async_filesystem::Error> 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<sync_filesystem::Advice> for async_filesystem::Advice {
fn from(other: sync_filesystem::Advice) -> Self {
use sync_filesystem::Advice;

180
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<TableError> for streams::Error {
fn from(e: TableError) -> streams::Error {
streams::Error::trap(e.into())
}
}
impl From<StreamError> 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<T: WasiView> streams::Host for T {
fn convert_stream_error(&mut self, err: StreamError) -> anyhow::Result<streams::StreamError> {
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<T: WasiView> streams::Host for T {}
impl<T: WasiView> streams::HostError for T {
fn drop(&mut self, err: Resource<streams::Error>) -> anyhow::Result<()> {
self.table_mut().delete_resource(err)?;
Ok(())
}
fn to_debug_string(&mut self, err: Resource<streams::Error>) -> anyhow::Result<String> {
Ok(format!("{:?}", self.table_mut().get_resource(&err)?))
}
}
#[async_trait::async_trait]
impl<T: WasiView> streams::HostOutputStream for T {
@ -34,16 +35,12 @@ impl<T: WasiView> streams::HostOutputStream for T {
Ok(())
}
fn check_write(&mut self, stream: Resource<OutputStream>) -> Result<u64, streams::Error> {
fn check_write(&mut self, stream: Resource<OutputStream>) -> StreamResult<u64> {
let bytes = self.table_mut().get_resource_mut(&stream)?.check_write()?;
Ok(bytes as u64)
}
fn write(
&mut self,
stream: Resource<OutputStream>,
bytes: Vec<u8>,
) -> Result<(), streams::Error> {
fn write(&mut self, stream: Resource<OutputStream>, bytes: Vec<u8>) -> StreamResult<()> {
self.table_mut()
.get_resource_mut(&stream)?
.write(bytes.into())?;
@ -58,13 +55,13 @@ impl<T: WasiView> streams::HostOutputStream for T {
&mut self,
stream: Resource<OutputStream>,
bytes: Vec<u8>,
) -> 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<T: WasiView> streams::HostOutputStream for T {
&mut self,
stream: Resource<OutputStream>,
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<T: WasiView> streams::HostOutputStream for T {
Ok(())
}
fn write_zeroes(
&mut self,
stream: Resource<OutputStream>,
len: u64,
) -> Result<(), streams::Error> {
fn write_zeroes(&mut self, stream: Resource<OutputStream>, len: u64) -> StreamResult<()> {
self.table_mut()
.get_resource_mut(&stream)?
.write_zeroes(len as usize)?;
Ok(())
}
fn flush(&mut self, stream: Resource<OutputStream>) -> Result<(), streams::Error> {
fn flush(&mut self, stream: Resource<OutputStream>) -> StreamResult<()> {
self.table_mut().get_resource_mut(&stream)?.flush()?;
Ok(())
}
async fn blocking_flush(
&mut self,
stream: Resource<OutputStream>,
) -> Result<(), streams::Error> {
async fn blocking_flush(&mut self, stream: Resource<OutputStream>) -> StreamResult<()> {
let s = self.table_mut().get_resource_mut(&stream)?;
s.flush()?;
s.write_ready().await?;
@ -139,7 +129,7 @@ impl<T: WasiView> streams::HostOutputStream for T {
_dst: Resource<OutputStream>,
_src: Resource<InputStream>,
_len: u64,
) -> Result<u64, streams::Error> {
) -> StreamResult<u64> {
// 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<T: WasiView> streams::HostOutputStream for T {
_dst: Resource<OutputStream>,
_src: Resource<InputStream>,
_len: u64,
) -> Result<u64, streams::Error> {
) -> StreamResult<u64> {
// 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<T: WasiView> streams::HostOutputStream for T {
&mut self,
_dst: Resource<OutputStream>,
_src: Resource<InputStream>,
) -> Result<u64, streams::Error> {
) -> StreamResult<u64> {
// 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<T: WasiView> streams::HostOutputStream for T {
}
}
impl From<std::num::TryFromIntError> for streams::Error {
fn from(e: std::num::TryFromIntError) -> Self {
streams::Error::trap(anyhow::anyhow!("length overflow: {e:?}"))
}
}
#[async_trait::async_trait]
impl<T: WasiView> streams::HostInputStream for T {
fn drop(&mut self, stream: Resource<InputStream>) -> anyhow::Result<()> {
@ -216,12 +201,8 @@ impl<T: WasiView> streams::HostInputStream for T {
Ok(())
}
async fn read(
&mut self,
stream: Resource<InputStream>,
len: u64,
) -> Result<Vec<u8>, streams::Error> {
let len = len.try_into()?;
async fn read(&mut self, stream: Resource<InputStream>, len: u64) -> StreamResult<Vec<u8>> {
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<T: WasiView> streams::HostInputStream for T {
&mut self,
stream: Resource<InputStream>,
len: u64,
) -> Result<Vec<u8>, streams::Error> {
) -> StreamResult<Vec<u8>> {
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<InputStream>,
len: u64,
) -> Result<u64, streams::Error> {
let len = len.try_into()?;
async fn skip(&mut self, stream: Resource<InputStream>, len: u64) -> StreamResult<u64> {
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<T: WasiView> streams::HostInputStream for T {
&mut self,
stream: Resource<InputStream>,
len: u64,
) -> Result<u64, streams::Error> {
) -> StreamResult<u64> {
if let InputStream::Host(s) = self.table_mut().get_resource_mut(&stream)? {
s.ready().await;
}
@ -273,48 +250,53 @@ impl<T: WasiView> 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<async_streams::StreamError> 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<async_streams::Error> 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<T: WasiView> streams::Host for T {
fn convert_stream_error(
&mut self,
err: StreamError,
) -> anyhow::Result<streams::StreamError> {
Ok(AsyncHost::convert_stream_error(self, err)?.into())
}
}
impl<T: WasiView> streams::Host for T {}
impl<T: WasiView> streams::HostError for T {
fn drop(&mut self, err: Resource<streams::Error>) -> anyhow::Result<()> {
AsyncHostError::drop(self, err)
}
fn to_debug_string(&mut self, err: Resource<streams::Error>) -> anyhow::Result<String> {
AsyncHostError::to_debug_string(self, err)
}
}
impl<T: WasiView> streams::HostOutputStream for T {
fn drop(&mut self, stream: Resource<OutputStream>) -> anyhow::Result<()> {
AsyncHostOutputStream::drop(self, stream)
}
fn check_write(&mut self, stream: Resource<OutputStream>) -> Result<u64, streams::Error> {
fn check_write(&mut self, stream: Resource<OutputStream>) -> StreamResult<u64> {
Ok(AsyncHostOutputStream::check_write(self, stream)?)
}
fn write(
&mut self,
stream: Resource<OutputStream>,
bytes: Vec<u8>,
) -> Result<(), streams::Error> {
fn write(&mut self, stream: Resource<OutputStream>, bytes: Vec<u8>) -> StreamResult<()> {
Ok(AsyncHostOutputStream::write(self, stream, bytes)?)
}
@ -322,7 +304,7 @@ pub mod sync {
&mut self,
stream: Resource<OutputStream>,
bytes: Vec<u8>,
) -> 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<OutputStream>,
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<OutputStream>,
len: u64,
) -> Result<(), streams::Error> {
fn write_zeroes(&mut self, stream: Resource<OutputStream>, len: u64) -> StreamResult<()> {
Ok(AsyncHostOutputStream::write_zeroes(self, stream, len)?)
}
fn flush(&mut self, stream: Resource<OutputStream>) -> Result<(), streams::Error> {
fn flush(&mut self, stream: Resource<OutputStream>) -> StreamResult<()> {
Ok(AsyncHostOutputStream::flush(
self,
Resource::new_borrow(stream.rep()),
)?)
}
fn blocking_flush(&mut self, stream: Resource<OutputStream>) -> Result<(), streams::Error> {
fn blocking_flush(&mut self, stream: Resource<OutputStream>) -> StreamResult<()> {
Ok(in_tokio(async {
AsyncHostOutputStream::blocking_flush(self, Resource::new_borrow(stream.rep()))
.await
@ -372,7 +350,7 @@ pub mod sync {
dst: Resource<OutputStream>,
src: Resource<InputStream>,
len: u64,
) -> Result<u64, streams::Error> {
) -> StreamResult<u64> {
Ok(in_tokio(async {
AsyncHostOutputStream::splice(self, dst, src, len).await
})?)
@ -383,7 +361,7 @@ pub mod sync {
dst: Resource<OutputStream>,
src: Resource<InputStream>,
len: u64,
) -> Result<u64, streams::Error> {
) -> StreamResult<u64> {
Ok(in_tokio(async {
AsyncHostOutputStream::blocking_splice(self, dst, src, len).await
})?)
@ -393,7 +371,7 @@ pub mod sync {
&mut self,
dst: Resource<OutputStream>,
src: Resource<InputStream>,
) -> Result<u64, streams::Error> {
) -> StreamResult<u64> {
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<InputStream>,
len: u64,
) -> Result<Vec<u8>, streams::Error> {
fn read(&mut self, stream: Resource<InputStream>, len: u64) -> StreamResult<Vec<u8>> {
Ok(in_tokio(async {
AsyncHostInputStream::read(self, stream, len).await
})?)
@ -419,23 +393,19 @@ pub mod sync {
&mut self,
stream: Resource<InputStream>,
len: u64,
) -> Result<Vec<u8>, streams::Error> {
) -> StreamResult<Vec<u8>> {
Ok(in_tokio(async {
AsyncHostInputStream::blocking_read(self, stream, len).await
})?)
}
fn skip(&mut self, stream: Resource<InputStream>, len: u64) -> Result<u64, streams::Error> {
fn skip(&mut self, stream: Resource<InputStream>, len: u64) -> StreamResult<u64> {
Ok(in_tokio(async {
AsyncHostInputStream::skip(self, stream, len).await
})?)
}
fn blocking_skip(
&mut self,
stream: Resource<InputStream>,
len: u64,
) -> Result<u64, streams::Error> {
fn blocking_skip(&mut self, stream: Resource<InputStream>, len: u64) -> StreamResult<u64> {
Ok(in_tokio(async {
AsyncHostInputStream::blocking_skip(self, stream, len).await
})?)

40
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<T: WasiView> network::Host for T {}
impl<T: WasiView> network::Host for T {
fn convert_error_code(&mut self, error: SocketError) -> anyhow::Result<ErrorCode> {
error.downcast()
}
}
impl<T: WasiView> crate::preview2::bindings::sockets::network::HostNetwork for T {
fn drop(&mut self, this: Resource<network::Network>) -> Result<(), anyhow::Error> {
@ -18,13 +22,7 @@ impl<T: WasiView> crate::preview2::bindings::sockets::network::HostNetwork for T
}
}
impl From<TableError> for network::Error {
fn from(error: TableError) -> Self {
Self::trap(error.into())
}
}
impl From<io::Error> for network::Error {
impl From<io::Error> for ErrorCode {
fn from(error: io::Error) -> Self {
match error.kind() {
// Errors that we can directly map.
@ -39,24 +37,8 @@ impl From<io::Error> 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<io::Error> 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<io::Error> for network::Error {
ErrorCode::Unknown
}
}
.into()
}
}
impl From<rustix::io::Errno> for network::Error {
impl From<rustix::io::Errno> for ErrorCode {
fn from(error: rustix::io::Errno) -> Self {
std::io::Error::from(error).into()
}

82
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<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
this: Resource<tcp::TcpSocket>,
network: Resource<Network>,
local_address: IpSocketAddress,
) -> Result<(), network::Error> {
) -> SocketResult<()> {
let table = self.table_mut();
let socket = table.get_resource(&this)?;
@ -44,7 +44,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
Ok(())
}
fn finish_bind(&mut self, this: Resource<tcp::TcpSocket>) -> Result<(), network::Error> {
fn finish_bind(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<()> {
let table = self.table_mut();
let socket = table.get_resource_mut(&this)?;
@ -63,7 +63,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
this: Resource<tcp::TcpSocket>,
network: Resource<Network>,
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<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
fn finish_connect(
&mut self,
this: Resource<tcp::TcpSocket>,
) -> Result<(Resource<InputStream>, Resource<OutputStream>), network::Error> {
) -> SocketResult<(Resource<InputStream>, Resource<OutputStream>)> {
let table = self.table_mut();
let socket = table.get_resource_mut(&this)?;
@ -145,7 +145,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
Ok((input_stream, output_stream))
}
fn start_listen(&mut self, this: Resource<tcp::TcpSocket>) -> Result<(), network::Error> {
fn start_listen(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<()> {
let table = self.table_mut();
let socket = table.get_resource_mut(&this)?;
@ -166,7 +166,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
Ok(())
}
fn finish_listen(&mut self, this: Resource<tcp::TcpSocket>) -> Result<(), network::Error> {
fn finish_listen(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<()> {
let table = self.table_mut();
let socket = table.get_resource_mut(&this)?;
@ -183,14 +183,11 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
fn accept(
&mut self,
this: Resource<tcp::TcpSocket>,
) -> Result<
(
Resource<tcp::TcpSocket>,
Resource<InputStream>,
Resource<OutputStream>,
),
network::Error,
> {
) -> SocketResult<(
Resource<tcp::TcpSocket>,
Resource<InputStream>,
Resource<OutputStream>,
)> {
let table = self.table();
let socket = table.get_resource(&this)?;
@ -222,10 +219,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
Ok((tcp_socket, input_stream, output_stream))
}
fn local_address(
&mut self,
this: Resource<tcp::TcpSocket>,
) -> Result<IpSocketAddress, network::Error> {
fn local_address(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<IpSocketAddress> {
let table = self.table();
let socket = table.get_resource(&this)?;
let addr = socket
@ -235,10 +229,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
Ok(addr.into())
}
fn remote_address(
&mut self,
this: Resource<tcp::TcpSocket>,
) -> Result<IpSocketAddress, network::Error> {
fn remote_address(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<IpSocketAddress> {
let table = self.table();
let socket = table.get_resource(&this)?;
let addr = socket
@ -296,17 +287,13 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
}
}
fn ipv6_only(&mut self, this: Resource<tcp::TcpSocket>) -> Result<bool, network::Error> {
fn ipv6_only(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<bool> {
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<tcp::TcpSocket>,
value: bool,
) -> Result<(), network::Error> {
fn set_ipv6_only(&mut self, this: Resource<tcp::TcpSocket>, 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<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
&mut self,
this: Resource<tcp::TcpSocket>,
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<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
}
}
fn keep_alive(&mut self, this: Resource<tcp::TcpSocket>) -> Result<bool, network::Error> {
fn keep_alive(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<bool> {
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<tcp::TcpSocket>,
value: bool,
) -> Result<(), network::Error> {
fn set_keep_alive(&mut self, this: Resource<tcp::TcpSocket>, 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<tcp::TcpSocket>) -> Result<bool, network::Error> {
fn no_delay(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<bool> {
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<tcp::TcpSocket>,
value: bool,
) -> Result<(), network::Error> {
fn set_no_delay(&mut self, this: Resource<tcp::TcpSocket>, 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<tcp::TcpSocket>) -> Result<u8, network::Error> {
fn unicast_hop_limit(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<u8> {
let table = self.table();
let socket = table.get_resource(&this)?;
@ -407,7 +386,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
&mut self,
this: Resource<tcp::TcpSocket>,
value: u8,
) -> Result<(), network::Error> {
) -> SocketResult<()> {
let table = self.table();
let socket = table.get_resource(&this)?;
@ -420,10 +399,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
}
}
fn receive_buffer_size(
&mut self,
this: Resource<tcp::TcpSocket>,
) -> Result<u64, network::Error> {
fn receive_buffer_size(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<u64> {
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<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
&mut self,
this: Resource<tcp::TcpSocket>,
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<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
)?)
}
fn send_buffer_size(&mut self, this: Resource<tcp::TcpSocket>) -> Result<u64, network::Error> {
fn send_buffer_size(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<u64> {
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<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
&mut self,
this: Resource<tcp::TcpSocket>,
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<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
&mut self,
this: Resource<tcp::TcpSocket>,
shutdown_type: ShutdownType,
) -> Result<(), network::Error> {
) -> SocketResult<()> {
let table = self.table();
let socket = table.get_resource(&this)?;

9
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<T: WasiView> tcp_create_socket::Host for T {
fn create_tcp_socket(
&mut self,
address_family: IpAddressFamily,
) -> Result<Resource<TcpSocket>, network::Error> {
) -> SocketResult<Resource<TcpSocket>> {
let socket = TcpSocket::new(address_family.into())?;
let socket = self.table_mut().push_resource(socket)?;
Ok(socket)

10
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<T: WasiView> Host for T {
name: String,
family: Option<IpAddressFamily>,
include_unavailable: bool,
) -> Result<Resource<ResolveAddressStream>, Error> {
) -> Result<Resource<ResolveAddressStream>, SocketError> {
let network = self.table().get_resource(&network)?;
// `Host::parse` serves us two functions:
@ -87,7 +85,7 @@ impl<T: WasiView> HostResolveAddressStream for T {
fn resolve_next_address(
&mut self,
resource: Resource<ResolveAddressStream>,
) -> Result<Option<IpAddress>, Error> {
) -> Result<Option<IpAddress>, SocketError> {
let stream = self.table_mut().get_resource_mut(&resource)?;
loop {
match stream {

25
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,

24
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<T> = Result<T, SocketError>;
pub type SocketError = TrappableError<ErrorCode>;
impl From<TableError> for SocketError {
fn from(error: TableError) -> Self {
Self::trap(error)
}
}
impl From<std::io::Error> for SocketError {
fn from(error: std::io::Error) -> Self {
ErrorCode::from(error).into()
}
}
impl From<rustix::io::Errno> for SocketError {
fn from(error: rustix::io::Errno) -> Self {
ErrorCode::from(error).into()
}
}

102
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<streams::OutputStream>,
mut bytes: &[u8],
) -> Result<usize, streams::Error> {
) -> StreamResult<usize> {
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<StreamError> for types::Error {
fn from(err: StreamError) -> Self {
types::Error::from(streams::Error::from(err))
}
}
impl From<streams::Error> 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::<std::io::Error>() {
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<streams::StreamError> for types::Error {
fn from(err: streams::StreamError) -> Self {
match err {
streams::StreamError::Closed | streams::StreamError::LastOperationFailed => {
types::Errno::Io.into()
}
impl From<FsError> 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<filesystem::ErrorCode> for types::Error {
}
}
impl TryFrom<filesystem::Error> for types::Errno {
type Error = anyhow::Error;
fn try_from(err: filesystem::Error) -> Result<Self, Self::Error> {
match err.downcast() {
Ok(code) => Ok(code.into()),
Err(e) => Err(e),
}
}
}
impl TryFrom<filesystem::Error> for types::Error {
type Error = anyhow::Error;
fn try_from(err: filesystem::Error) -> Result<Self, Self::Error> {
match err.downcast() {
Ok(code) => Ok(code.into()),
Err(e) => Err(e),
}
}
}
impl From<TableError> 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

38
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<Bytes, StreamError>;
fn read(&mut self, size: usize) -> StreamResult<Bytes>;
/// 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<usize, StreamError> {
fn skip(&mut self, nelem: usize) -> StreamResult<usize> {
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<T> = Result<T, StreamError>;
#[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<TableError> 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<usize, StreamError>;
fn check_write(&mut self) -> StreamResult<usize>;
/// 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<usize, StreamError> {
async fn write_ready(&mut self) -> StreamResult<usize> {
self.ready().await;
self.check_write()
}

14
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<option<directory-entry>, 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<error>) -> option<error-code>
}

26
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

168
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<InterfaceId, InterfaceName>,
with_name_counter: usize,
interface_last_seen_as_import: HashMap<InterfaceId, bool>,
trappable_errors: IndexMap<TypeId, String>,
}
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<TypeId, String>,
}
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::<anyhow::Result<IndexMap<_, _>>>()
.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::<Vec<_>>() {
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<ok,err>` get special
// cased to use the host_wasmtime_rust::Error<err>, 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<String>) -> 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,

133
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<MyTrap> for TrappableE1 {
fn from(t: MyTrap) -> TrappableE1 {
TrappableE1::MyTrap(t)
}
}
impl From<imports::E1> 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<MyTrap> 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<f64, imports::TrappableE1> {
fn convert_e1(&mut self, err: TrappableE1) -> anyhow::Result<imports::E1> {
match err {
TrappableE1::Normal(e) => Ok(e),
TrappableE1::MyTrap(e) => Err(e.into()),
}
}
fn enum_error(&mut self, a: f64) -> Result<f64, TrappableE1> {
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<imports::E2> 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<f64, imports::TrappableE2> {
fn convert_e2(&mut self, err: TrappableE2) -> anyhow::Result<imports::E2> {
match err {
TrappableE2::Normal(e) => Ok(e),
TrappableE2::Trap(e) => Err(e.into()),
}
}
fn record_error(&mut self, a: f64) -> Result<f64, TrappableE2> {
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<imports::E3> 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<f64, imports::TrappableE3> {
fn convert_e3(&mut self, err: TrappableE3) -> anyhow::Result<imports::E3> {
match err {
TrappableE3::Normal(e) => Ok(e),
TrappableE3::Trap(e) => Err(e.into()),
}
}
fn variant_error(&mut self, a: f64) -> Result<f64, TrappableE3> {
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<types::E1> 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<MyTrap> for types::TrappableE1 {
fn from(t: MyTrap) -> types::TrappableE1 {
types::TrappableE1::trap(anyhow!(t))
impl From<MyTrap> 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<f64, types::TrappableE1> {
fn convert_e1(&mut self, err: TrappableE1) -> anyhow::Result<imports::E1> {
match err {
TrappableE1::Normal(e) => Ok(e),
TrappableE1::Trap(e) => Err(e.into()),
}
}
fn enum_error(&mut self, a: f64) -> Result<f64, TrappableE1> {
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<f64, types::TrappableE1> {
fn enum_error(&mut self, a: f64) -> Result<f64, TrappableE1> {
if a == 0.0 {
Ok(a)
} else if a == 1.0 {

Loading…
Cancel
Save