|
| 1 | +use crate::io::{self, BorrowedBuf, BorrowedCursor}; |
| 2 | +use wasip2::cli; |
| 3 | +use wasip2::io::streams::{Error, InputStream, OutputStream, StreamError}; |
| 4 | + |
| 5 | +pub struct Stdin(Option<InputStream>); |
| 6 | +pub struct Stdout(Option<OutputStream>); |
| 7 | +pub struct Stderr(Option<OutputStream>); |
| 8 | + |
| 9 | +fn error_to_io(err: Error) -> io::Error { |
| 10 | + // There exists a function in `wasi:filesystem` to optionally acquire an |
| 11 | + // error code from an error, but the streams in use in this module are |
| 12 | + // exclusively used with stdio meaning that a filesystem error is not |
| 13 | + // possible here. |
| 14 | + // |
| 15 | + // In lieu of an error code, which WASIp2 does not specify, this instead |
| 16 | + // carries along the `to_debug_string` implementation that the host |
| 17 | + // supplies. If this becomes too expensive in the future this could also |
| 18 | + // become `io::Error::from_raw_os_error(libc::EIO)` or similar. |
| 19 | + io::Error::new(io::ErrorKind::Other, err.to_debug_string()) |
| 20 | +} |
| 21 | + |
| 22 | +impl Stdin { |
| 23 | + pub const fn new() -> Stdin { |
| 24 | + Stdin(None) |
| 25 | + } |
| 26 | + |
| 27 | + fn stream(&mut self) -> &InputStream { |
| 28 | + self.0.get_or_insert_with(cli::stdin::get_stdin) |
| 29 | + } |
| 30 | +} |
| 31 | + |
| 32 | +impl io::Read for Stdin { |
| 33 | + fn read(&mut self, data: &mut [u8]) -> io::Result<usize> { |
| 34 | + let mut buf = BorrowedBuf::from(data); |
| 35 | + self.read_buf(buf.unfilled())?; |
| 36 | + Ok(buf.len()) |
| 37 | + } |
| 38 | + |
| 39 | + fn read_buf(&mut self, mut buf: BorrowedCursor<'_>) -> io::Result<()> { |
| 40 | + match self.stream().blocking_read(u64::try_from(buf.capacity()).unwrap()) { |
| 41 | + Ok(result) => { |
| 42 | + buf.append(&result); |
| 43 | + Ok(()) |
| 44 | + } |
| 45 | + Err(StreamError::Closed) => Ok(()), |
| 46 | + Err(StreamError::LastOperationFailed(e)) => Err(error_to_io(e)), |
| 47 | + } |
| 48 | + } |
| 49 | +} |
| 50 | + |
| 51 | +impl Stdout { |
| 52 | + pub const fn new() -> Stdout { |
| 53 | + Stdout(None) |
| 54 | + } |
| 55 | + |
| 56 | + fn stream(&mut self) -> &OutputStream { |
| 57 | + self.0.get_or_insert_with(cli::stdout::get_stdout) |
| 58 | + } |
| 59 | +} |
| 60 | + |
| 61 | +fn write(stream: &OutputStream, buf: &[u8]) -> io::Result<usize> { |
| 62 | + // WASIp2's `blocking_write_and_flush` function is defined as accepting no |
| 63 | + // more than 4096 bytes. Larger writes can be issued by manually using |
| 64 | + // `check_write`, `write`, and `blocking_flush`, but for now just go ahead |
| 65 | + // and use `blocking_write_and_flush` and report a short write and let a |
| 66 | + // higher level loop over the result. |
| 67 | + const MAX: usize = 4096; |
| 68 | + let buf = &buf[..buf.len().min(MAX)]; |
| 69 | + match stream.blocking_write_and_flush(buf) { |
| 70 | + Ok(()) => Ok(buf.len()), |
| 71 | + Err(StreamError::Closed) => Ok(0), |
| 72 | + Err(StreamError::LastOperationFailed(e)) => Err(error_to_io(e)), |
| 73 | + } |
| 74 | +} |
| 75 | + |
| 76 | +impl io::Write for Stdout { |
| 77 | + fn write(&mut self, data: &[u8]) -> io::Result<usize> { |
| 78 | + write(self.stream(), data) |
| 79 | + } |
| 80 | + |
| 81 | + fn flush(&mut self) -> io::Result<()> { |
| 82 | + // Note that `OutputStream` has a `flush` function but for stdio all |
| 83 | + // writes are accompanied with a flush which means that this flush |
| 84 | + // doesn't need to do anything. |
| 85 | + Ok(()) |
| 86 | + } |
| 87 | +} |
| 88 | + |
| 89 | +impl Stderr { |
| 90 | + pub const fn new() -> Stderr { |
| 91 | + Stderr(None) |
| 92 | + } |
| 93 | + |
| 94 | + fn stream(&mut self) -> &OutputStream { |
| 95 | + self.0.get_or_insert_with(cli::stderr::get_stderr) |
| 96 | + } |
| 97 | +} |
| 98 | + |
| 99 | +impl io::Write for Stderr { |
| 100 | + fn write(&mut self, data: &[u8]) -> io::Result<usize> { |
| 101 | + write(self.stream(), data) |
| 102 | + } |
| 103 | + |
| 104 | + fn flush(&mut self) -> io::Result<()> { |
| 105 | + // See `Stdout::flush` for why this is a noop. |
| 106 | + Ok(()) |
| 107 | + } |
| 108 | +} |
| 109 | + |
| 110 | +pub const STDIN_BUF_SIZE: usize = crate::sys::io::DEFAULT_BUF_SIZE; |
| 111 | + |
| 112 | +pub fn is_ebadf(_err: &io::Error) -> bool { |
| 113 | + // WASIp2 stdio streams are always available so ebadf never shows up. |
| 114 | + false |
| 115 | +} |
| 116 | + |
| 117 | +pub fn panic_output() -> Option<impl io::Write> { |
| 118 | + Some(Stderr::new()) |
| 119 | +} |
0 commit comments