blob: 1fcb49a083dd06c8b61495043527e86ee75c071d [file] [log] [blame]
use wasip2::cli;
use wasip2::io::streams::{Error, InputStream, OutputStream, StreamError};
use crate::io::{self, BorrowedBuf, BorrowedCursor};
pub struct Stdin(Option<InputStream>);
pub struct Stdout(Option<OutputStream>);
pub struct Stderr(Option<OutputStream>);
fn error_to_io(err: Error) -> io::Error {
// There exists a function in `wasi:filesystem` to optionally acquire an
// error code from an error, but the streams in use in this module are
// exclusively used with stdio meaning that a filesystem error is not
// possible here.
//
// In lieu of an error code, which WASIp2 does not specify, this instead
// carries along the `to_debug_string` implementation that the host
// supplies. If this becomes too expensive in the future this could also
// become `io::Error::from_raw_os_error(libc::EIO)` or similar.
io::Error::new(io::ErrorKind::Other, err.to_debug_string())
}
impl Stdin {
pub const fn new() -> Stdin {
Stdin(None)
}
fn stream(&mut self) -> &InputStream {
self.0.get_or_insert_with(cli::stdin::get_stdin)
}
}
impl io::Read for Stdin {
fn read(&mut self, data: &mut [u8]) -> io::Result<usize> {
let mut buf = BorrowedBuf::from(data);
self.read_buf(buf.unfilled())?;
Ok(buf.len())
}
fn read_buf(&mut self, mut buf: BorrowedCursor<'_>) -> io::Result<()> {
match self.stream().blocking_read(u64::try_from(buf.capacity()).unwrap()) {
Ok(result) => {
buf.append(&result);
Ok(())
}
Err(StreamError::Closed) => Ok(()),
Err(StreamError::LastOperationFailed(e)) => Err(error_to_io(e)),
}
}
}
impl Stdout {
pub const fn new() -> Stdout {
Stdout(None)
}
fn stream(&mut self) -> &OutputStream {
self.0.get_or_insert_with(cli::stdout::get_stdout)
}
}
fn write(stream: &OutputStream, buf: &[u8]) -> io::Result<usize> {
// WASIp2's `blocking_write_and_flush` function is defined as accepting no
// more than 4096 bytes. Larger writes can be issued by manually using
// `check_write`, `write`, and `blocking_flush`, but for now just go ahead
// and use `blocking_write_and_flush` and report a short write and let a
// higher level loop over the result.
const MAX: usize = 4096;
let buf = &buf[..buf.len().min(MAX)];
match stream.blocking_write_and_flush(buf) {
Ok(()) => Ok(buf.len()),
Err(StreamError::Closed) => Ok(0),
Err(StreamError::LastOperationFailed(e)) => Err(error_to_io(e)),
}
}
impl io::Write for Stdout {
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
write(self.stream(), data)
}
fn flush(&mut self) -> io::Result<()> {
// Note that `OutputStream` has a `flush` function but for stdio all
// writes are accompanied with a flush which means that this flush
// doesn't need to do anything.
Ok(())
}
}
impl Stderr {
pub const fn new() -> Stderr {
Stderr(None)
}
fn stream(&mut self) -> &OutputStream {
self.0.get_or_insert_with(cli::stderr::get_stderr)
}
}
impl io::Write for Stderr {
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
write(self.stream(), data)
}
fn flush(&mut self) -> io::Result<()> {
// See `Stdout::flush` for why this is a noop.
Ok(())
}
}
pub const STDIN_BUF_SIZE: usize = crate::sys::io::DEFAULT_BUF_SIZE;
pub fn is_ebadf(_err: &io::Error) -> bool {
// WASIp2 stdio streams are always available so ebadf never shows up.
false
}
pub fn panic_output() -> Option<impl io::Write> {
Some(Stderr::new())
}