diff --git a/crates/wasi-c2/src/lib.rs b/crates/wasi-c2/src/lib.rs index 82aa0e263a..eb581d06dc 100644 --- a/crates/wasi-c2/src/lib.rs +++ b/crates/wasi-c2/src/lib.rs @@ -8,6 +8,7 @@ pub mod snapshots; pub mod stdio; mod string_array; pub mod table; +pub mod virt; pub use ctx::WasiCtx; pub use dir::{DirCaps, WasiDir}; diff --git a/crates/wasi-c2/src/virt/mod.rs b/crates/wasi-c2/src/virt/mod.rs new file mode 100644 index 0000000000..97a59c2c4e --- /dev/null +++ b/crates/wasi-c2/src/virt/mod.rs @@ -0,0 +1 @@ +pub mod pipe; diff --git a/crates/wasi-c2/src/virt/pipe.rs b/crates/wasi-c2/src/virt/pipe.rs new file mode 100644 index 0000000000..cda5383dc5 --- /dev/null +++ b/crates/wasi-c2/src/virt/pipe.rs @@ -0,0 +1,349 @@ +// This is mostly stubs +#![allow(unused_variables, dead_code)] +//! Virtual pipes. +//! +//! These types provide easy implementations of `WasiFile` 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. +//! +use crate::file::{FdFlags, Filestat, Filetype, OFlags, WasiFile}; +use crate::Error; +use std::io::{self, Read, Write}; +use std::sync::{Arc, RwLock}; +use system_interface::fs::{Advice, FileIoExt}; + +/// 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_c2::WasiCtxBuilder; +/// # use wasi_c2::virt::pipe::ReadPipe; +/// let mut ctx = WasiCtx::builder(); +/// let stdin = ReadPipe::from("hello from stdin!"); +/// ctx.stdin(stdin); +/// ``` +#[derive(Debug)] +pub struct ReadPipe { + reader: Arc>, +} + +impl Clone for ReadPipe { + fn clone(&self) -> Self { + Self { + 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 { 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) -> Result { + match Arc::try_unwrap(self.reader) { + Ok(rc) => Ok(RwLock::into_inner(rc).unwrap()), + Err(reader) => { + self.reader = reader; + Err(self) + } + } + } + fn borrow(&self) -> std::sync::RwLockWriteGuard { + RwLock::write(&self.reader).unwrap() + } +} + +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 FileIoExt for ReadPipe { + fn advise(&self, offset: u64, len: u64, advice: Advice) -> io::Result<()> { + todo!() // advice cant be taken. do we ignore or throw error? + } + fn allocate(&self, offset: u64, len: u64) -> io::Result<()> { + todo!() // error: requires write, seek + } + fn read(&self, buf: &mut [u8]) -> io::Result { + self.borrow().read(buf) + } + fn read_exact(&self, buf: &mut [u8]) -> io::Result<()> { + self.borrow().read_exact(buf) + } + fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result { + todo!() // error: requires seek + } + fn read_exact_at(&self, buf: &mut [u8], offset: u64) -> io::Result<()> { + todo!() // error: requires seek + } + fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> io::Result { + self.borrow().read_vectored(bufs) + } + fn read_to_end(&self, buf: &mut Vec) -> io::Result { + self.borrow().read_to_end(buf) + } + fn read_to_string(&self, buf: &mut String) -> io::Result { + self.borrow().read_to_string(buf) + } + fn bytes(self) -> io::Bytes { + todo!() // impossible to construct this concrete iterator, fix in system-interface + } + fn take(self, limit: u64) -> io::Take { + todo!() // impossible to construct this concrete iterator, fix in system-interface + } + fn write(&self, buf: &[u8]) -> io::Result { + todo!() // error: requires write + } + fn write_all(&self, buf: &[u8]) -> io::Result<()> { + todo!() // error: requires write + } + fn write_at(&self, buf: &[u8], offset: u64) -> io::Result { + todo!() // error: requires write, seek + } + fn write_all_at(&self, buf: &[u8], offset: u64) -> io::Result<()> { + todo!() // error: requires write, seek + } + fn write_vectored(&self, bufs: &[io::IoSlice]) -> io::Result { + todo!() // error: requires write + } + fn write_fmt(&mut self, fmt: std::fmt::Arguments) -> io::Result<()> { + todo!() // error: requires write + } + fn flush(&self) -> io::Result<()> { + todo!() // error: requires write + } + fn seek(&self, pos: std::io::SeekFrom) -> io::Result { + todo!() // error: requires seek + } + fn stream_position(&self) -> io::Result { + todo!() // error: requires seek + } +} + +impl fs_set_times::SetTimes for ReadPipe { + fn set_times( + &self, + _: Option, + _: Option, + ) -> io::Result<()> { + todo!() + } +} +impl WasiFile for ReadPipe { + fn datasync(&self) -> Result<(), Error> { + Ok(()) // trivial: no implementation needed + } + fn sync(&self) -> Result<(), Error> { + Ok(()) // trivial + } + fn get_filetype(&self) -> Result { + Ok(Filetype::CharacterDevice) // XXX wrong + } + fn get_fdflags(&self) -> Result { + todo!() // do later + } + fn get_oflags(&self) -> Result { + todo!() // do later + } + fn set_oflags(&self, _flags: OFlags) -> Result<(), Error> { + todo!() // impossible? + } + fn get_filestat(&self) -> Result { + todo!() // do later + } + fn set_filestat_size(&self, _size: u64) -> Result<(), Error> { + todo!() // impossible? + } +} + +/// A virtual pipe write end. +#[derive(Debug)] +pub struct WritePipe { + writer: Arc>, +} + +impl Clone for WritePipe { + fn clone(&self) -> Self { + Self { + 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 { 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) -> Result { + match Arc::try_unwrap(self.writer) { + Ok(rc) => Ok(RwLock::into_inner(rc).unwrap()), + Err(writer) => { + self.writer = writer; + Err(self) + } + } + } + + fn borrow(&self) -> std::sync::RwLockWriteGuard { + RwLock::write(&self.writer).unwrap() + } +} + +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 FileIoExt for WritePipe { + fn advise(&self, offset: u64, len: u64, advice: Advice) -> io::Result<()> { + todo!() + } + fn allocate(&self, offset: u64, len: u64) -> io::Result<()> { + todo!() // error: requires seek + } + fn read(&self, buf: &mut [u8]) -> io::Result { + todo!() // error: requires read + } + fn read_exact(&self, buf: &mut [u8]) -> io::Result<()> { + todo!() // error: requires read + } + fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result { + todo!() // error: requires read, seek + } + fn read_exact_at(&self, buf: &mut [u8], offset: u64) -> io::Result<()> { + todo!() // error: requires read, seek + } + fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> io::Result { + todo!() // error: requires read + } + fn read_to_end(&self, buf: &mut Vec) -> io::Result { + todo!() // error: requires read + } + fn read_to_string(&self, buf: &mut String) -> io::Result { + todo!() // error: requires read + } + fn bytes(self) -> io::Bytes { + todo!() // error: requires read + } + fn take(self, limit: u64) -> io::Take { + todo!() // error::requires read + } + fn write(&self, buf: &[u8]) -> io::Result { + self.borrow().write(buf) + } + fn write_all(&self, buf: &[u8]) -> io::Result<()> { + self.borrow().write_all(buf) + } + fn write_at(&self, buf: &[u8], offset: u64) -> io::Result { + todo!() // error: requires seek + } + fn write_all_at(&self, buf: &[u8], offset: u64) -> io::Result<()> { + todo!() // error: requires seek + } + fn write_vectored(&self, bufs: &[io::IoSlice]) -> io::Result { + self.borrow().write_vectored(bufs) + } + fn write_fmt(&mut self, fmt: std::fmt::Arguments) -> io::Result<()> { + self.borrow().write_fmt(fmt) + } + fn flush(&self) -> io::Result<()> { + self.borrow().flush() + } + fn seek(&self, pos: std::io::SeekFrom) -> io::Result { + todo!() // error: requires seek + } + fn stream_position(&self) -> io::Result { + todo!() // error: requires seek + } +} + +impl fs_set_times::SetTimes for WritePipe { + fn set_times( + &self, + _: Option, + _: Option, + ) -> io::Result<()> { + todo!() // + } +} + +impl WasiFile for WritePipe { + fn datasync(&self) -> Result<(), Error> { + Ok(()) + } + fn sync(&self) -> Result<(), Error> { + Ok(()) + } + fn get_filetype(&self) -> Result { + Ok(Filetype::CharacterDevice) // XXX + } + fn get_fdflags(&self) -> Result { + todo!() + } + fn get_oflags(&self) -> Result { + todo!() + } + fn set_oflags(&self, _flags: OFlags) -> Result<(), Error> { + todo!() + } + fn get_filestat(&self) -> Result { + todo!() + } + fn set_filestat_size(&self, _size: u64) -> Result<(), Error> { + todo!() + } +}