Files
wasmtime/crates/wasi-common/src/sys/stdio.rs
Sakarias Johansson 22ad43b430 Add filestat_get for stdout, stdin and stderr
This makes fstat work for stdout, stdin and stderr as expected.
This seemed like the only reasonable functions to implement from the
filestat_* set, for stdout, stdin and stderr.

Fixes #2515
2020-12-16 14:33:42 +01:00

242 lines
7.8 KiB
Rust

// The reason we have a separate Stdio wrappers is to correctly facilitate redirects on Windows.
// To elaborate further, in POSIX, we can get a stdio handle by opening a specific fd {0,1,2}.
// On Windows however, we need to issue a syscall that's separate from standard Windows "open"
// to get a console handle, and this is GetStdHandle. This is exactly what Rust does and what
// is wrapped inside their Stdio object in the libstd. We wrap it here as well because of this
// nuance on Windows:
//
// The standard handles of a process may be redirected by a call to SetStdHandle, in which
// case GetStdHandle returns the redirected handle.
//
// The MSDN also says this however:
//
// If the standard handles have been redirected, you can specify the CONIN$ value in a call
// to the CreateFile function to get a handle to a console's input buffer. Similarly, you
// can specify the CONOUT$ value to get a handle to a console's active screen buffer.
//
// TODO it might worth re-investigating the suitability of this type on Windows.
use super::{fd, AsFile};
use crate::handle::{Fdflags, Filestat, Filetype, Handle, HandleRights, Rights, RightsExt, Size};
use crate::sandboxed_tty_writer::SandboxedTTYWriter;
use crate::{Error, Result};
use std::any::Any;
use std::cell::Cell;
use std::convert::TryInto;
use std::io::{self, Read, Write};
pub(crate) trait StdinExt: Sized {
/// Create `Stdin` from `io::stdin`.
fn stdin() -> io::Result<Box<dyn Handle>>;
}
#[derive(Debug, Clone)]
pub(crate) struct Stdin {
pub(crate) file_type: Filetype,
pub(crate) rights: Cell<HandleRights>,
}
impl Handle for Stdin {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
Ok(Box::new(self.clone()))
}
fn get_file_type(&self) -> Filetype {
self.file_type
}
fn get_rights(&self) -> HandleRights {
self.rights.get()
}
fn set_rights(&self, new_rights: HandleRights) {
self.rights.set(new_rights)
}
// FdOps
fn fdstat_get(&self) -> Result<Fdflags> {
fd::fdstat_get(&*self.as_file()?)
}
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
if let Some(_) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? {
// OK, this means we should somehow update the underlying os handle,
// and we can't do that with `std::io::std{in, out, err}`, so we'll
// panic for now.
panic!("Tried updating Fdflags on Stdio handle by re-opening as file!");
}
Ok(())
}
fn filestat_get(&self) -> Result<Filestat> {
fd::filestat_get(&*self.as_file()?)
}
fn read_vectored(&self, iovs: &mut [io::IoSliceMut]) -> Result<usize> {
let nread = io::stdin().read_vectored(iovs)?;
Ok(nread)
}
}
pub(crate) trait StdoutExt: Sized {
/// Create `Stdout` from `io::stdout`.
fn stdout() -> io::Result<Box<dyn Handle>>;
}
#[derive(Debug, Clone)]
pub(crate) struct Stdout {
pub(crate) file_type: Filetype,
pub(crate) rights: Cell<HandleRights>,
}
impl Handle for Stdout {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
Ok(Box::new(self.clone()))
}
fn get_file_type(&self) -> Filetype {
self.file_type
}
fn get_rights(&self) -> HandleRights {
self.rights.get()
}
fn set_rights(&self, new_rights: HandleRights) {
self.rights.set(new_rights)
}
// FdOps
fn fdstat_get(&self) -> Result<Fdflags> {
fd::fdstat_get(&*self.as_file()?)
}
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
if let Some(_) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? {
// OK, this means we should somehow update the underlying os handle,
// and we can't do that with `std::io::std{in, out, err}`, so we'll
// panic for now.
panic!("Tried updating Fdflags on Stdio handle by re-opening as file!");
}
Ok(())
}
fn filestat_get(&self) -> Result<Filestat> {
fd::filestat_get(&*self.as_file()?)
}
fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result<usize> {
// lock for the duration of the scope
let stdout = io::stdout();
let mut stdout = stdout.lock();
let nwritten = if self.is_tty() {
SandboxedTTYWriter::new(&mut stdout).write_vectored(&iovs)?
} else {
stdout.write_vectored(iovs)?
};
stdout.flush()?;
Ok(nwritten)
}
}
pub(crate) trait StderrExt: Sized {
/// Create `Stderr` from `io::stderr`.
fn stderr() -> io::Result<Box<dyn Handle>>;
}
#[derive(Debug, Clone)]
pub(crate) struct Stderr {
pub(crate) file_type: Filetype,
pub(crate) rights: Cell<HandleRights>,
}
impl Handle for Stderr {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
Ok(Box::new(self.clone()))
}
fn get_file_type(&self) -> Filetype {
self.file_type
}
fn get_rights(&self) -> HandleRights {
self.rights.get()
}
fn set_rights(&self, new_rights: HandleRights) {
self.rights.set(new_rights)
}
// FdOps
fn fdstat_get(&self) -> Result<Fdflags> {
fd::fdstat_get(&*self.as_file()?)
}
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
if let Some(_) = fd::fdstat_set_flags(&*self.as_file()?, fdflags)? {
// OK, this means we should somehow update the underlying os handle,
// and we can't do that with `std::io::std{in, out, err}`, so we'll
// panic for now.
panic!("Tried updating Fdflags on Stdio handle by re-opening as file!");
}
Ok(())
}
fn filestat_get(&self) -> Result<Filestat> {
fd::filestat_get(&*self.as_file()?)
}
fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result<usize> {
// Always sanitize stderr, even if it's not directly connected to a tty,
// because stderr is meant for diagnostics rather than binary output,
// and may be redirected to a file which could end up being displayed
// on a tty later.
let nwritten = SandboxedTTYWriter::new(&mut io::stderr()).write_vectored(&iovs)?;
Ok(nwritten)
}
}
#[derive(Debug, Clone)]
pub(crate) struct NullDevice {
pub(crate) rights: Cell<HandleRights>,
pub(crate) fd_flags: Cell<Fdflags>,
}
impl NullDevice {
pub(crate) fn new() -> Self {
let rights = HandleRights::new(
Rights::character_device_base(),
Rights::character_device_inheriting(),
);
let rights = Cell::new(rights);
let fd_flags = Fdflags::empty();
let fd_flags = Cell::new(fd_flags);
Self { rights, fd_flags }
}
}
impl Handle for NullDevice {
fn as_any(&self) -> &dyn Any {
self
}
fn try_clone(&self) -> io::Result<Box<dyn Handle>> {
Ok(Box::new(self.clone()))
}
fn get_file_type(&self) -> Filetype {
Filetype::CharacterDevice
}
fn get_rights(&self) -> HandleRights {
self.rights.get()
}
fn set_rights(&self, rights: HandleRights) {
self.rights.set(rights)
}
// FdOps
fn fdstat_get(&self) -> Result<Fdflags> {
Ok(self.fd_flags.get())
}
fn fdstat_set_flags(&self, fdflags: Fdflags) -> Result<()> {
self.fd_flags.set(fdflags);
Ok(())
}
fn read_vectored(&self, _iovs: &mut [io::IoSliceMut]) -> Result<usize> {
Ok(0)
}
fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result<usize> {
let mut total_len = 0u32;
for iov in iovs {
let len: Size = iov.len().try_into()?;
total_len = total_len.checked_add(len).ok_or(Error::Overflow)?;
}
Ok(total_len as usize)
}
}