Skip to content

Commit 56daa8a

Browse files
author
Pat Hickey
authored
Use wiggle "trappable error" to implement wasi-common (#5279)
* convert wasi-common from defining its own error to using wiggle trappable error * wasi-common impl crates: switch error strategy * wasmtime-wasi: error is trappable, and no longer requires UserErrorConversion * docs * typo * readdir: windows fixes * fix windows scheduler errors fun fact! the Send and Recv errors here that just had a `.context` on them were previously not being captured in the downcasting either. They need to be traps, and would have ended up that way by ommission, but you'd never actually know that by reading the code!
1 parent de6e4a4 commit 56daa8a

12 files changed

Lines changed: 461 additions & 467 deletions

File tree

crates/wasi-common/cap-std-sync/src/dir.rs

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,27 @@ impl WasiDir for Dir {
138138
&self,
139139
cursor: ReaddirCursor,
140140
) -> Result<Box<dyn Iterator<Item = Result<ReaddirEntity, Error>> + Send>, Error> {
141+
// We need to keep a full-fidelity io Error around to check for a special failure mode
142+
// on windows, but also this function can fail due to an illegal byte sequence in a
143+
// filename, which we can't construct an io Error to represent.
144+
enum ReaddirError {
145+
Io(std::io::Error),
146+
IllegalSequence,
147+
}
148+
impl From<std::io::Error> for ReaddirError {
149+
fn from(e: std::io::Error) -> ReaddirError {
150+
ReaddirError::Io(e)
151+
}
152+
}
153+
141154
// cap_std's read_dir does not include . and .., we should prepend these.
142155
// Why does the Ok contain a tuple? We can't construct a cap_std::fs::DirEntry, and we don't
143156
// have enough info to make a ReaddirEntity yet.
144157
let dir_meta = self.0.dir_metadata()?;
145158
let rd = vec![
146159
{
147160
let name = ".".to_owned();
148-
Ok((FileType::Directory, dir_meta.ino(), name))
161+
Ok::<_, ReaddirError>((FileType::Directory, dir_meta.ino(), name))
149162
},
150163
{
151164
let name = "..".to_owned();
@@ -163,24 +176,22 @@ impl WasiDir for Dir {
163176
let name = entry
164177
.file_name()
165178
.into_string()
166-
.map_err(|_| Error::illegal_byte_sequence().context("filename"))?;
179+
.map_err(|_| ReaddirError::IllegalSequence)?;
167180
Ok((filetype, inode, name))
168181
});
169182

170183
// On Windows, filter out files like `C:\DumpStack.log.tmp` which we
171184
// can't get a full metadata for.
172185
#[cfg(windows)]
173-
let entries = entries.filter(|entry: &Result<_, wasi_common::Error>| {
186+
let entries = entries.filter(|entry| {
174187
use windows_sys::Win32::Foundation::{
175188
ERROR_ACCESS_DENIED, ERROR_SHARING_VIOLATION,
176189
};
177-
if let Err(err) = entry {
178-
if let Some(err) = err.downcast_ref::<std::io::Error>() {
179-
if err.raw_os_error() == Some(ERROR_SHARING_VIOLATION as i32)
180-
|| err.raw_os_error() == Some(ERROR_ACCESS_DENIED as i32)
181-
{
182-
return false;
183-
}
190+
if let Err(ReaddirError::Io(err)) = entry {
191+
if err.raw_os_error() == Some(ERROR_SHARING_VIOLATION as i32)
192+
|| err.raw_os_error() == Some(ERROR_ACCESS_DENIED as i32)
193+
{
194+
return false;
184195
}
185196
}
186197
true
@@ -197,7 +208,8 @@ impl WasiDir for Dir {
197208
inode,
198209
name,
199210
}),
200-
Err(e) => Err(e),
211+
Err(ReaddirError::Io(e)) => Err(e.into()),
212+
Err(ReaddirError::IllegalSequence) => Err(Error::illegal_byte_sequence()),
201213
})
202214
.skip(u64::from(cursor) as usize);
203215

crates/wasi-common/cap-std-sync/src/sched/unix.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> {
4747
match rustix::io::poll(&mut pollfds, poll_timeout) {
4848
Ok(ready) => break ready,
4949
Err(rustix::io::Errno::INTR) => continue,
50-
Err(err) => return Err(err.into()),
50+
Err(err) => return Err(std::io::Error::from(err).into()),
5151
}
5252
};
5353
if ready > 0 {

crates/wasi-common/cap-std-sync/src/sched/windows.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
// We suspect there are bugs in this scheduler, however, we have not
99
// taken the time to improve it. See bug #2880.
1010

11-
use anyhow::Context;
1211
use once_cell::sync::Lazy;
1312
use std::ops::Deref;
1413
use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, TryRecvError};
@@ -73,7 +72,7 @@ pub async fn poll_oneoff_<'a>(
7372
if !stdin_read_subs.is_empty() {
7473
let state = STDIN_POLL
7574
.lock()
76-
.map_err(|_| Error::trap("failed to take lock of STDIN_POLL"))?
75+
.map_err(|_| Error::trap(anyhow::Error::msg("failed to take lock of STDIN_POLL")))?
7776
.poll(waitmode)?;
7877
for readsub in stdin_read_subs.into_iter() {
7978
match state {
@@ -167,34 +166,36 @@ impl StdinPoll {
167166
// Clean up possibly unread result from previous poll.
168167
Ok(_) | Err(TryRecvError::Empty) => {}
169168
Err(TryRecvError::Disconnected) => {
170-
return Err(Error::trap("StdinPoll notify_rx channel closed"))
169+
return Err(Error::trap(anyhow::Error::msg(
170+
"StdinPoll notify_rx channel closed",
171+
)))
171172
}
172173
}
173174

174175
// Notify the worker thread to poll stdin
175176
self.request_tx
176177
.send(())
177-
.context("request_tx channel closed")?;
178+
.map_err(|_| Error::trap(anyhow::Error::msg("request_tx channel closed")))?;
178179

179180
// Wait for the worker thread to send a readiness notification
180181
match wait_mode {
181182
WaitMode::Timeout(timeout) => match self.notify_rx.recv_timeout(timeout) {
182183
Ok(r) => Ok(r),
183184
Err(RecvTimeoutError::Timeout) => Ok(PollState::TimedOut),
184-
Err(RecvTimeoutError::Disconnected) => {
185-
Err(Error::trap("StdinPoll notify_rx channel closed"))
186-
}
185+
Err(RecvTimeoutError::Disconnected) => Err(Error::trap(anyhow::Error::msg(
186+
"StdinPoll notify_rx channel closed",
187+
))),
187188
},
188189
WaitMode::Infinite => self
189190
.notify_rx
190191
.recv()
191-
.context("StdinPoll notify_rx channel closed"),
192+
.map_err(|_| Error::trap(anyhow::Error::msg("StdinPoll notify_rx channel closed"))),
192193
WaitMode::Immediate => match self.notify_rx.try_recv() {
193194
Ok(r) => Ok(r),
194195
Err(TryRecvError::Empty) => Ok(PollState::NotReady),
195-
Err(TryRecvError::Disconnected) => {
196-
Err(Error::trap("StdinPoll notify_rx channel closed"))
197-
}
196+
Err(TryRecvError::Disconnected) => Err(Error::trap(anyhow::Error::msg(
197+
"StdinPoll notify_rx channel closed",
198+
))),
198199
},
199200
}
200201
}

crates/wasi-common/cap-std-sync/src/stdio.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,9 @@ macro_rules! wasi_file_write_impl {
128128
}
129129
async fn write_vectored<'a>(&mut self, bufs: &[io::IoSlice<'a>]) -> Result<u64, Error> {
130130
let n = (&*self.0.as_filelike_view::<File>()).write_vectored(bufs)?;
131-
Ok(n.try_into().map_err(|c| Error::range().context(c))?)
131+
Ok(n.try_into().map_err(|_| {
132+
Error::range().context("converting write_vectored total length")
133+
})?)
132134
}
133135
async fn write_vectored_at<'a>(
134136
&mut self,

crates/wasi-common/src/error.rs

Lines changed: 8 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -1,145 +1,15 @@
1-
//! `wasi_common::Error` is now `anyhow::Error`.
1+
//! wasi-common uses an [`Error`] type which represents either a preview 1 [`Errno`] enum, on
2+
//! [`anyhow::Error`] for trapping execution.
23
//!
3-
//! Snapshots (right now only `wasi_common::snapshots::preview_1`) contains
4-
//! all of the logic for transforming an `Error` into the snapshot's own
5-
//! `Errno`. They may do so by downcasting the error into any of:
6-
//! * `std::io::Error` - these are thrown by `std`, `cap_std`, etc for most of
7-
//! the operations WASI is concerned with.
8-
//! * `wasi_common::ErrorKind` - these are a subset of the Errnos, and are
9-
//! constructed directly by wasi-common or an impl rather than coming from the
10-
//! OS or some library which doesn't know about WASI.
11-
//! * `wiggle::GuestError`
12-
//! * `std::num::TryFromIntError`
13-
//! * `std::str::Utf8Error`
14-
//! and then applying specialized logic to translate each of those into
15-
//! `Errno`s.
16-
//!
17-
//! The `wasi_common::ErrorExt` trait provides human-friendly constructors for
18-
//! the `wasi_common::ErrorKind` variants .
19-
//!
20-
//! If you throw an error that does not downcast to one of those, it will turn
21-
//! into a `wiggle::Trap` and terminate execution.
22-
//!
23-
//! The real value of using `anyhow::Error` here is being able to use
24-
//! `anyhow::Result::context` to aid in debugging of errors.
4+
//! The user can construct an [`Error`] out of an [`Errno`] using the `From`/`Into` traits.
5+
//! They may also use [`Error::trap`] to construct an error that traps execution. The contents
6+
//! can be inspected with [`Error::downcast`] and [`Error::downcast_ref`]. Additional context
7+
//! can be provided with the [`Error::context`] method. This context is only observable with the
8+
//! `Display` and `Debug` impls of the error.
259
26-
pub use anyhow::{Context, Error};
10+
pub use crate::snapshots::preview_1::error::{Errno, Error, ErrorExt};
2711
use std::fmt;
2812

29-
/// Internal error type for the `wasi-common` crate.
30-
///
31-
/// This Contains variants of the WASI `$errno` type that are used internally
32-
/// by the crate, and which aren't one-to-one with a `std::io::ErrorKind`
33-
/// error.
34-
///
35-
/// When the Rust [io_error_more] feature is stabilized, that will enable
36-
/// us to replace several more of these codes with `std::io::ErrorKind` codes.
37-
///
38-
/// [io_error_more]: https://doc.rust-lang.org/beta/unstable-book/library-features/io-error-more.html
39-
#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
40-
#[non_exhaustive]
41-
pub enum ErrorKind {
42-
/// Errno::TooBig: Argument list too long
43-
#[error("TooBig: Argument list too long")]
44-
TooBig,
45-
/// Errno::Badf: Bad file descriptor
46-
#[error("Badf: Bad file descriptor")]
47-
Badf,
48-
/// Errno::Ilseq: Illegal byte sequence
49-
#[error("Ilseq: Illegal byte sequence")]
50-
Ilseq,
51-
/// Errno::Io: I/O error
52-
#[error("Io: I/O error")]
53-
Io,
54-
/// Errno::Nametoolong: Filename too long
55-
#[error("Nametoolong: Filename too long")]
56-
Nametoolong,
57-
/// Errno::Notdir: Not a directory or a symbolic link to a directory.
58-
#[error("Notdir: Not a directory or a symbolic link to a directory")]
59-
Notdir,
60-
/// Errno::Notsup: Not supported, or operation not supported on socket.
61-
#[error("Notsup: Not supported, or operation not supported on socket")]
62-
Notsup,
63-
/// Errno::Overflow: Value too large to be stored in data type.
64-
#[error("Overflow: Value too large to be stored in data type")]
65-
Overflow,
66-
/// Errno::Range: Result too large
67-
#[error("Range: Result too large")]
68-
Range,
69-
/// Errno::Spipe: Invalid seek
70-
#[error("Spipe: Invalid seek")]
71-
Spipe,
72-
/// Errno::Perm: Permission denied
73-
#[error("Permission denied")]
74-
Perm,
75-
}
76-
77-
pub trait ErrorExt {
78-
fn trap(msg: impl Into<String>) -> Self;
79-
fn not_found() -> Self;
80-
fn too_big() -> Self;
81-
fn badf() -> Self;
82-
fn exist() -> Self;
83-
fn illegal_byte_sequence() -> Self;
84-
fn invalid_argument() -> Self;
85-
fn io() -> Self;
86-
fn name_too_long() -> Self;
87-
fn not_dir() -> Self;
88-
fn not_supported() -> Self;
89-
fn overflow() -> Self;
90-
fn range() -> Self;
91-
fn seek_pipe() -> Self;
92-
fn perm() -> Self;
93-
}
94-
95-
impl ErrorExt for Error {
96-
fn trap(msg: impl Into<String>) -> Self {
97-
anyhow::anyhow!(msg.into())
98-
}
99-
fn not_found() -> Self {
100-
std::io::Error::from(std::io::ErrorKind::NotFound).into()
101-
}
102-
fn too_big() -> Self {
103-
ErrorKind::TooBig.into()
104-
}
105-
fn badf() -> Self {
106-
ErrorKind::Badf.into()
107-
}
108-
fn exist() -> Self {
109-
std::io::Error::from(std::io::ErrorKind::AlreadyExists).into()
110-
}
111-
fn illegal_byte_sequence() -> Self {
112-
ErrorKind::Ilseq.into()
113-
}
114-
fn invalid_argument() -> Self {
115-
std::io::Error::from(std::io::ErrorKind::InvalidInput).into()
116-
}
117-
fn io() -> Self {
118-
ErrorKind::Io.into()
119-
}
120-
fn name_too_long() -> Self {
121-
ErrorKind::Nametoolong.into()
122-
}
123-
fn not_dir() -> Self {
124-
ErrorKind::Notdir.into()
125-
}
126-
fn not_supported() -> Self {
127-
ErrorKind::Notsup.into()
128-
}
129-
fn overflow() -> Self {
130-
ErrorKind::Overflow.into()
131-
}
132-
fn range() -> Self {
133-
ErrorKind::Range.into()
134-
}
135-
fn seek_pipe() -> Self {
136-
ErrorKind::Spipe.into()
137-
}
138-
fn perm() -> Self {
139-
ErrorKind::Perm.into()
140-
}
141-
}
142-
14313
/// An error returned from the `proc_exit` host syscall.
14414
///
14515
/// Embedders can test if an error returned from wasm is this error, in which

crates/wasi-common/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ pub use cap_rand::RngCore;
6666
pub use clocks::{SystemTimeSpec, WasiClocks, WasiMonotonicClock, WasiSystemClock};
6767
pub use ctx::WasiCtx;
6868
pub use dir::WasiDir;
69-
pub use error::{Context, Error, ErrorExt, ErrorKind, I32Exit};
69+
pub use error::{Error, ErrorExt, I32Exit};
7070
pub use file::WasiFile;
7171
pub use sched::{Poll, WasiSched};
7272
pub use string_array::StringArrayError;

0 commit comments

Comments
 (0)