//! 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::{ Advice, Fdflags, Filesize, Filestat, Filetype, Handle, HandleRights, Oflags, Rights, }; use crate::{Error, Result}; use std::any::Any; use std::io::{self, Read, Write}; use std::sync::{Arc, RwLock}; /// 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(Debug)] pub struct ReadPipe { rights: RwLock, reader: Arc>, } impl Clone for ReadPipe { fn clone(&self) -> Self { Self { rights: RwLock::new(*self.rights.read().unwrap()), reader: self.reader.clone(), } } } 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(Arc::new(RwLock::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: Arc>) -> Self { Self { rights: RwLock::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, )), 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 Arc::try_unwrap(self.reader) { Ok(rc) => Ok(RwLock::into_inner(rc).unwrap()), 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.clone())) } fn get_file_type(&self) -> Filetype { Filetype::Unknown } fn get_rights(&self) -> HandleRights { *self.rights.read().unwrap() } fn set_rights(&self, rights: HandleRights) { *self.rights.write().unwrap() = rights; } fn advise(&self, _advice: Advice, _offset: Filesize, _len: Filesize) -> Result<()> { Err(Error::Spipe) } fn allocate(&self, _offset: Filesize, _len: Filesize) -> Result<()> { Err(Error::Spipe) } fn fdstat_set_flags(&self, _fdflags: Fdflags) -> Result<()> { // do nothing for now Ok(()) } fn filestat_get(&self) -> Result { let stat = 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: Filesize) -> Result<()> { Err(Error::Spipe) } fn preadv(&self, buf: &mut [io::IoSliceMut], offset: Filesize) -> Result { if offset != 0 { return Err(Error::Spipe); } Ok(self.reader.write().unwrap().read_vectored(buf)?) } fn seek(&self, _offset: io::SeekFrom) -> Result { Err(Error::Spipe) } fn read_vectored(&self, iovs: &mut [io::IoSliceMut]) -> Result { Ok(self.reader.write().unwrap().read_vectored(iovs)?) } fn create_directory(&self, _path: &str) -> Result<()> { Err(Error::Notdir) } fn openat( &self, _path: &str, _read: bool, _write: bool, _oflags: Oflags, _fd_flags: Fdflags, ) -> Result> { Err(Error::Notdir) } fn link( &self, _old_path: &str, _new_handle: Box, _new_path: &str, _follow: bool, ) -> Result<()> { Err(Error::Notdir) } fn readlink(&self, _path: &str, _buf: &mut [u8]) -> Result { Err(Error::Notdir) } fn readlinkat(&self, _path: &str) -> Result { Err(Error::Notdir) } fn rename(&self, _old_path: &str, _new_handle: Box, _new_path: &str) -> Result<()> { Err(Error::Notdir) } fn remove_directory(&self, _path: &str) -> Result<()> { Err(Error::Notdir) } fn symlink(&self, _old_path: &str, _new_path: &str) -> Result<()> { Err(Error::Notdir) } fn unlink_file(&self, _path: &str) -> Result<()> { Err(Error::Notdir) } } /// A virtual pipe write end. #[derive(Debug)] pub struct WritePipe { rights: RwLock, writer: Arc>, } impl Clone for WritePipe { fn clone(&self) -> Self { Self { rights: RwLock::new(*self.rights.read().unwrap()), writer: self.writer.clone(), } } } 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(Arc::new(RwLock::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: Arc>) -> Self { Self { rights: RwLock::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, )), 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 Arc::try_unwrap(self.writer) { Ok(rc) => Ok(RwLock::into_inner(rc).unwrap()), 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![])) } } impl Handle for WritePipe { fn as_any(&self) -> &dyn Any { self } fn try_clone(&self) -> io::Result> { Ok(Box::new(self.clone())) } fn get_file_type(&self) -> Filetype { Filetype::Unknown } fn get_rights(&self) -> HandleRights { *self.rights.read().unwrap() } fn set_rights(&self, rights: HandleRights) { *self.rights.write().unwrap() = rights; } fn advise(&self, _advice: Advice, _offset: Filesize, _len: Filesize) -> Result<()> { Err(Error::Spipe) } fn allocate(&self, _offset: Filesize, _len: Filesize) -> Result<()> { Err(Error::Spipe) } fn fdstat_set_flags(&self, _fdflags: Fdflags) -> Result<()> { // do nothing for now Ok(()) } fn filestat_get(&self) -> Result { let stat = 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: Filesize) -> Result<()> { Err(Error::Spipe) } fn pwritev(&self, buf: &[io::IoSlice], offset: Filesize) -> Result { if offset != 0 { return Err(Error::Spipe); } Ok(self.writer.write().unwrap().write_vectored(buf)?) } fn seek(&self, _offset: io::SeekFrom) -> Result { Err(Error::Spipe) } fn write_vectored(&self, iovs: &[io::IoSlice]) -> Result { Ok(self.writer.write().unwrap().write_vectored(iovs)?) } fn create_directory(&self, _path: &str) -> Result<()> { Err(Error::Notdir) } fn openat( &self, _path: &str, _read: bool, _write: bool, _oflags: Oflags, _fd_flags: Fdflags, ) -> Result> { Err(Error::Notdir) } fn link( &self, _old_path: &str, _new_handle: Box, _new_path: &str, _follow: bool, ) -> Result<()> { Err(Error::Notdir) } fn readlink(&self, _path: &str, _buf: &mut [u8]) -> Result { Err(Error::Notdir) } fn readlinkat(&self, _path: &str) -> Result { Err(Error::Notdir) } fn rename(&self, _old_path: &str, _new_handle: Box, _new_path: &str) -> Result<()> { Err(Error::Notdir) } fn remove_directory(&self, _path: &str) -> Result<()> { Err(Error::Notdir) } fn symlink(&self, _old_path: &str, _new_path: &str) -> Result<()> { Err(Error::Notdir) } fn unlink_file(&self, _path: &str) -> Result<()> { Err(Error::Notdir) } }