From 5a96b0deaac09a6d1dce3391a4de689d75147c71 Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Mon, 29 Jun 2020 19:20:54 -0700 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=95=B3=20Add=20virtual=20pipes=20to?= =?UTF-8?q?=20wasi-common?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This introduces `Handle` implementations for readable and writable pipes, backed by arbitrary `Read` and `Write` types, respectively. In particular, this allows for easily providing, capturing, or redirecting WASI stdio without having to resort to OS-provided file descriptors. The implementation is based heavily on `wasi_common::virtfs::InMemoryFile`, but without inapplicable operations like `seek` or `allocate`. Note that these types are not 1:1 replacements for real pipes, because they do not support `poll_oneoff`. --- crates/wasi-common/src/lib.rs | 2 +- crates/wasi-common/src/virtfs.rs | 2 + crates/wasi-common/src/virtfs/pipe.rs | 412 ++++++++++++++++++++++++++ 3 files changed, 415 insertions(+), 1 deletion(-) create mode 100644 crates/wasi-common/src/virtfs/pipe.rs diff --git a/crates/wasi-common/src/lib.rs b/crates/wasi-common/src/lib.rs index e6f8cef6a6..65b5ea047d 100644 --- a/crates/wasi-common/src/lib.rs +++ b/crates/wasi-common/src/lib.rs @@ -32,7 +32,7 @@ mod poll; mod sandboxed_tty_writer; pub mod snapshots; mod sys; -mod virtfs; +pub mod virtfs; pub mod wasi; pub use ctx::{WasiCtx, WasiCtxBuilder, WasiCtxBuilderError}; diff --git a/crates/wasi-common/src/virtfs.rs b/crates/wasi-common/src/virtfs.rs index 141a9cc693..dddc8d73ea 100644 --- a/crates/wasi-common/src/virtfs.rs +++ b/crates/wasi-common/src/virtfs.rs @@ -11,6 +11,8 @@ use std::io::SeekFrom; use std::path::{Path, PathBuf}; use std::rc::Rc; +pub mod pipe; + /// An entry in a virtual filesystem pub enum VirtualDirEntry { /// The contents of a child directory diff --git a/crates/wasi-common/src/virtfs/pipe.rs b/crates/wasi-common/src/virtfs/pipe.rs new file mode 100644 index 0000000000..08244fb79b --- /dev/null +++ b/crates/wasi-common/src/virtfs/pipe.rs @@ -0,0 +1,412 @@ +//! Virtual pipes. +//! +//! These types provide easy implementations of `Handle` that mimic much of the behavior of Unix +//! pipes. These are particularly helpful for redirecting WASI stdio handles to destinations other +//! than OS files. +//! +//! Some convenience constructors are included for common backing types like `Vec` and `String`, +//! but the virtual pipes can be instantiated with any `Read` or `Write` type. +//! +//! Note that `poll_oneoff` is not supported for these types, so they do not match the behavior of +//! real pipes exactly. +use crate::handle::{Handle, HandleRights}; +use crate::wasi::{types, Errno, Result}; +use log::trace; +use std::any::Any; +use std::cell::{Cell, Ref, RefCell}; +use std::io::{self, Read, Write}; +use std::rc::Rc; + +/// A virtual pipe read end. +/// +/// A variety of `From` impls are provided so that common pipe types are easy to create. For example: +/// +/// ``` +/// # use wasi_common::WasiCtxBuilder; +/// # use wasi_common::virtfs::pipe::ReadPipe; +/// let mut ctx = WasiCtxBuilder::new(); +/// let stdin = ReadPipe::from("hello from stdin!"); +/// ctx.stdin(stdin); +/// ``` +#[derive(Clone, Debug)] +pub struct ReadPipe { + rights: Cell, + reader: Rc>, +} + +impl ReadPipe { + /// Create a new pipe from a `Read` type. + /// + /// All `Handle` read operations delegate to reading from this underlying reader. + pub fn new(r: R) -> Self { + Self::from_shared(Rc::new(RefCell::new(r))) + } + + /// Create a new pipe from a shareable `Read` type. + /// + /// All `Handle` read operations delegate to reading from this underlying reader. + pub fn from_shared(reader: Rc>) -> Self { + use types::Rights; + Self { + rights: Cell::new(HandleRights::new( + Rights::FD_DATASYNC + | Rights::FD_FDSTAT_SET_FLAGS + | Rights::FD_READ + | Rights::FD_SYNC + | Rights::FD_FILESTAT_GET + | Rights::POLL_FD_READWRITE, + Rights::empty(), + )), + reader, + } + } + + /// Try to convert this `ReadPipe` back to the underlying `R` type. + /// + /// This will fail with `Err(self)` if multiple references to the underlying `R` exist. + pub fn try_into_inner(mut self) -> std::result::Result { + match Rc::try_unwrap(self.reader) { + Ok(rc) => Ok(RefCell::into_inner(rc)), + Err(reader) => { + self.reader = reader; + Err(self) + } + } + } +} + +impl From> for ReadPipe>> { + fn from(r: Vec) -> Self { + Self::new(io::Cursor::new(r)) + } +} + +impl From<&[u8]> for ReadPipe>> { + fn from(r: &[u8]) -> Self { + Self::from(r.to_vec()) + } +} + +impl From for ReadPipe> { + fn from(r: String) -> Self { + Self::new(io::Cursor::new(r)) + } +} + +impl From<&str> for ReadPipe> { + fn from(r: &str) -> Self { + Self::from(r.to_string()) + } +} + +impl Handle for ReadPipe { + fn as_any(&self) -> &dyn Any { + self + } + + fn try_clone(&self) -> io::Result> { + Ok(Box::new(Self { + rights: self.rights.clone(), + reader: self.reader.clone(), + })) + } + + fn get_file_type(&self) -> types::Filetype { + types::Filetype::Unknown + } + + fn get_rights(&self) -> HandleRights { + self.rights.get() + } + + fn set_rights(&self, rights: HandleRights) { + self.rights.set(rights) + } + + fn advise( + &self, + _advice: types::Advice, + _offset: types::Filesize, + _len: types::Filesize, + ) -> Result<()> { + Err(Errno::Spipe) + } + + fn allocate(&self, _offset: types::Filesize, _len: types::Filesize) -> Result<()> { + Err(Errno::Spipe) + } + + fn fdstat_set_flags(&self, _fdflags: types::Fdflags) -> Result<()> { + // do nothing for now + Ok(()) + } + + fn filestat_get(&self) -> Result { + let stat = types::Filestat { + dev: 0, + ino: 0, + nlink: 0, + size: 0, + atim: 0, + ctim: 0, + mtim: 0, + filetype: self.get_file_type(), + }; + Ok(stat) + } + + fn filestat_set_size(&self, _st_size: types::Filesize) -> Result<()> { + Err(Errno::Spipe) + } + + fn preadv(&self, buf: &mut [io::IoSliceMut], offset: types::Filesize) -> Result { + if offset != 0 { + return Err(Errno::Spipe); + } + Ok(self.reader.borrow_mut().read_vectored(buf)?) + } + + fn seek(&self, _offset: io::SeekFrom) -> Result { + Err(Errno::Spipe) + } + + fn read_vectored(&self, iovs: &mut [io::IoSliceMut]) -> Result { + trace!("read_vectored(iovs={:?})", iovs); + Ok(self.reader.borrow_mut().read_vectored(iovs)?) + } + + fn create_directory(&self, _path: &str) -> Result<()> { + Err(Errno::Notdir) + } + + fn openat( + &self, + _path: &str, + _read: bool, + _write: bool, + _oflags: types::Oflags, + _fd_flags: types::Fdflags, + ) -> Result> { + Err(Errno::Notdir) + } + + fn link( + &self, + _old_path: &str, + _new_handle: Box, + _new_path: &str, + _follow: bool, + ) -> Result<()> { + Err(Errno::Notdir) + } + + fn readlink(&self, _path: &str, _buf: &mut [u8]) -> Result { + Err(Errno::Notdir) + } + + fn readlinkat(&self, _path: &str) -> Result { + Err(Errno::Notdir) + } + + fn rename(&self, _old_path: &str, _new_handle: Box, _new_path: &str) -> Result<()> { + Err(Errno::Notdir) + } + + fn remove_directory(&self, _path: &str) -> Result<()> { + Err(Errno::Notdir) + } + + fn symlink(&self, _old_path: &str, _new_path: &str) -> Result<()> { + Err(Errno::Notdir) + } + + fn unlink_file(&self, _path: &str) -> Result<()> { + Err(Errno::Notdir) + } +} + +/// A virtual pipe write end. +#[derive(Clone, Debug)] +pub struct WritePipe { + rights: Cell, + writer: Rc>, +} + +impl WritePipe { + /// Create a new pipe from a `Write` type. + /// + /// All `Handle` write operations delegate to writing to this underlying writer. + pub fn new(w: W) -> Self { + Self::from_shared(Rc::new(RefCell::new(w))) + } + + /// Create a new pipe from a shareable `Write` type. + /// + /// All `Handle` write operations delegate to writing to this underlying writer. + pub fn from_shared(writer: Rc>) -> Self { + use types::Rights; + Self { + rights: Cell::new(HandleRights::new( + Rights::FD_DATASYNC + | Rights::FD_FDSTAT_SET_FLAGS + | Rights::FD_SYNC + | Rights::FD_WRITE + | Rights::FD_FILESTAT_GET + | Rights::POLL_FD_READWRITE, + Rights::empty(), + )), + writer, + } + } + + /// Try to convert this `WritePipe` back to the underlying `W` type. + /// + /// This will fail with `Err(self)` if multiple references to the underlying `W` exist. + pub fn try_into_inner(mut self) -> std::result::Result { + match Rc::try_unwrap(self.writer) { + Ok(rc) => Ok(RefCell::into_inner(rc)), + Err(writer) => { + self.writer = writer; + Err(self) + } + } + } +} + +impl WritePipe>> { + /// Create a new writable virtual pipe backed by a `Vec` buffer. + pub fn new_in_memory() -> Self { + Self::new(io::Cursor::new(vec![])) + } + + /// Get a reference to the bytes contained in the underlying `Vec` buffer. + pub fn as_slice(&self) -> Ref<[u8]> { + Ref::map(self.writer.borrow(), |c| c.get_ref().as_slice()) + } +} + +impl Handle for WritePipe { + fn as_any(&self) -> &dyn Any { + self + } + + fn try_clone(&self) -> io::Result> { + Ok(Box::new(Self { + rights: self.rights.clone(), + writer: self.writer.clone(), + })) + } + + fn get_file_type(&self) -> types::Filetype { + types::Filetype::Unknown + } + + fn get_rights(&self) -> HandleRights { + self.rights.get() + } + + fn set_rights(&self, rights: HandleRights) { + self.rights.set(rights) + } + + fn advise( + &self, + _advice: types::Advice, + _offset: types::Filesize, + _len: types::Filesize, + ) -> Result<()> { + Err(Errno::Spipe) + } + + fn allocate(&self, _offset: types::Filesize, _len: types::Filesize) -> Result<()> { + Err(Errno::Spipe) + } + + fn fdstat_set_flags(&self, _fdflags: types::Fdflags) -> Result<()> { + // do nothing for now + Ok(()) + } + + fn filestat_get(&self) -> Result { + let stat = types::Filestat { + dev: 0, + ino: 0, + nlink: 0, + size: 0, + atim: 0, + ctim: 0, + mtim: 0, + filetype: self.get_file_type(), + }; + Ok(stat) + } + + fn filestat_set_size(&self, _st_size: types::Filesize) -> Result<()> { + Err(Errno::Spipe) + } + + fn pwritev(&self, buf: &[io::IoSlice], offset: types::Filesize) -> Result { + if offset != 0 { + return Err(Errno::Spipe); + } + Ok(self.writer.borrow_mut().write_vectored(buf)?) + } + + fn seek(&self, _offset: io::SeekFrom) -> Result { + Err(Errno::Spipe) + } + + fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result { + trace!("write_vectored(iovs={:?})", iovs); + Ok(self.writer.borrow_mut().write_vectored(iovs)?) + } + + fn create_directory(&self, _path: &str) -> Result<()> { + Err(Errno::Notdir) + } + + fn openat( + &self, + _path: &str, + _read: bool, + _write: bool, + _oflags: types::Oflags, + _fd_flags: types::Fdflags, + ) -> Result> { + Err(Errno::Notdir) + } + + fn link( + &self, + _old_path: &str, + _new_handle: Box, + _new_path: &str, + _follow: bool, + ) -> Result<()> { + Err(Errno::Notdir) + } + + fn readlink(&self, _path: &str, _buf: &mut [u8]) -> Result { + Err(Errno::Notdir) + } + + fn readlinkat(&self, _path: &str) -> Result { + Err(Errno::Notdir) + } + + fn rename(&self, _old_path: &str, _new_handle: Box, _new_path: &str) -> Result<()> { + Err(Errno::Notdir) + } + + fn remove_directory(&self, _path: &str) -> Result<()> { + Err(Errno::Notdir) + } + + fn symlink(&self, _old_path: &str, _new_path: &str) -> Result<()> { + Err(Errno::Notdir) + } + + fn unlink_file(&self, _path: &str) -> Result<()> { + Err(Errno::Notdir) + } +} From fddd94d23f5a3712ae6afbb9176fe8b32bcc5793 Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Wed, 1 Jul 2020 09:46:38 -0700 Subject: [PATCH 2/2] address review comments --- crates/wasi-common/src/virtfs/pipe.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/wasi-common/src/virtfs/pipe.rs b/crates/wasi-common/src/virtfs/pipe.rs index 08244fb79b..3067172f12 100644 --- a/crates/wasi-common/src/virtfs/pipe.rs +++ b/crates/wasi-common/src/virtfs/pipe.rs @@ -11,7 +11,6 @@ //! real pipes exactly. use crate::handle::{Handle, HandleRights}; use crate::wasi::{types, Errno, Result}; -use log::trace; use std::any::Any; use std::cell::{Cell, Ref, RefCell}; use std::io::{self, Read, Write}; @@ -48,14 +47,13 @@ impl ReadPipe { pub fn from_shared(reader: Rc>) -> Self { use types::Rights; Self { - rights: Cell::new(HandleRights::new( + rights: Cell::new(HandleRights::from_base( Rights::FD_DATASYNC | Rights::FD_FDSTAT_SET_FLAGS | Rights::FD_READ | Rights::FD_SYNC | Rights::FD_FILESTAT_GET | Rights::POLL_FD_READWRITE, - Rights::empty(), )), reader, } @@ -171,7 +169,6 @@ impl Handle for ReadPipe { } fn read_vectored(&self, iovs: &mut [io::IoSliceMut]) -> Result { - trace!("read_vectored(iovs={:?})", iovs); Ok(self.reader.borrow_mut().read_vectored(iovs)?) } @@ -246,14 +243,13 @@ impl WritePipe { pub fn from_shared(writer: Rc>) -> Self { use types::Rights; Self { - rights: Cell::new(HandleRights::new( + rights: Cell::new(HandleRights::from_base( Rights::FD_DATASYNC | Rights::FD_FDSTAT_SET_FLAGS | Rights::FD_SYNC | Rights::FD_WRITE | Rights::FD_FILESTAT_GET | Rights::POLL_FD_READWRITE, - Rights::empty(), )), writer, } @@ -357,7 +353,6 @@ impl Handle for WritePipe { } fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result { - trace!("write_vectored(iovs={:?})", iovs); Ok(self.writer.borrow_mut().write_vectored(iovs)?) }