use std::{
any::Any,
fmt::{Display, Formatter},
io::{stdout, Cursor, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write},
sync::{Arc, Mutex, RwLock},
};
use wasi_common::{
file::{Advice, FdFlags, FileType, Filestat},
Error, ErrorExt, SystemTimeSpec, WasiFile,
};
type StdOutVec = Arc<RwLock<Vec<Mutex<Cursor<Vec<u8>>>>>>;
#[derive(Clone, Debug)]
pub struct StdoutCapture {
echo: bool,
writers: StdOutVec,
index: usize,
}
impl PartialEq for StdoutCapture {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.writers, &other.writers) && self.index == other.index
}
}
impl Display for StdoutCapture {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
let streams = RwLock::read(&self.writers).unwrap();
if streams.len() == 1 {
write!(f, "{}", self.content()).unwrap();
} else {
for (i, stream) in streams.iter().enumerate() {
writeln!(f, " --- process {i} stdout ---").unwrap();
let stream = stream.lock().unwrap();
let content = String::from_utf8_lossy(stream.get_ref()).to_string();
write!(f, "{content}").unwrap();
}
}
Ok(())
}
}
impl StdoutCapture {
pub fn new(echo: bool) -> Self {
Self {
echo,
writers: Arc::new(RwLock::new(vec![Mutex::new(Cursor::new(Vec::new()))])),
index: 0,
}
}
pub fn only_reference(&self) -> bool {
Arc::strong_count(&self.writers) == 1
}
pub fn next(&self) -> Self {
let index = {
let mut writers = RwLock::write(&self.writers).unwrap();
writers.push(Mutex::new(Cursor::new(Vec::new())));
writers.len() - 1
};
Self {
echo: self.echo,
writers: self.writers.clone(),
index,
}
}
pub fn is_empty(&self) -> bool {
let streams = RwLock::read(&self.writers).unwrap();
streams.iter().all(|stream| {
let stream = stream.lock().unwrap();
stream.get_ref().is_empty()
})
}
pub fn content(&self) -> String {
let streams = RwLock::read(&self.writers).unwrap();
let stream = streams[self.index].lock().unwrap();
String::from_utf8_lossy(stream.get_ref()).to_string()
}
pub fn push_str(&self, content: &str) {
let streams = RwLock::read(&self.writers).unwrap();
let mut stream = streams[self.index].lock().unwrap();
write!(stream, "{content}").unwrap();
}
}
#[wiggle::async_trait]
impl WasiFile for StdoutCapture {
fn as_any(&self) -> &dyn Any {
self
}
async fn datasync(&self) -> Result<(), Error> {
Ok(())
}
async fn sync(&self) -> Result<(), Error> {
Ok(())
}
async fn get_filetype(&self) -> Result<FileType, Error> {
Ok(FileType::Pipe)
}
async fn get_fdflags(&self) -> Result<FdFlags, Error> {
Ok(FdFlags::APPEND)
}
async fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> {
Err(Error::badf())
}
async fn get_filestat(&self) -> Result<Filestat, Error> {
Ok(Filestat {
device_id: 0,
inode: 0,
filetype: self.get_filetype().await?,
nlink: 0,
size: 0, atim: None,
mtim: None,
ctim: None,
})
}
async fn set_filestat_size(&self, _size: u64) -> Result<(), Error> {
Err(Error::badf())
}
async fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> {
Err(Error::badf())
}
async fn allocate(&self, _offset: u64, _len: u64) -> Result<(), Error> {
Err(Error::badf())
}
async fn read_vectored<'a>(&self, _bufs: &mut [IoSliceMut<'a>]) -> Result<u64, Error> {
Err(Error::badf())
}
async fn read_vectored_at<'a>(
&self,
_bufs: &mut [IoSliceMut<'a>],
_offset: u64,
) -> Result<u64, Error> {
Err(Error::badf())
}
async fn write_vectored<'a>(&self, bufs: &[IoSlice<'a>]) -> Result<u64, Error> {
let streams = RwLock::read(&self.writers).unwrap();
let mut stream = streams[self.index].lock().unwrap();
let n = stream.write_vectored(bufs)?;
if self.echo {
stream.seek(SeekFrom::End(-(n as i64)))?;
let mut echo = vec![0; n];
stream.read_exact(&mut echo)?;
stdout().write_all(&echo)?;
}
Ok(n.try_into()?)
}
async fn write_vectored_at<'a>(
&self,
_bufs: &[IoSlice<'a>],
_offset: u64,
) -> Result<u64, Error> {
Err(Error::badf())
}
async fn seek(&self, _pos: SeekFrom) -> Result<u64, Error> {
Err(Error::badf())
}
async fn peek(&self, _buf: &mut [u8]) -> Result<u64, Error> {
Err(Error::badf())
}
async fn set_times(
&self,
_atime: Option<SystemTimeSpec>,
_mtime: Option<SystemTimeSpec>,
) -> Result<(), Error> {
Err(Error::badf())
}
fn num_ready_bytes(&self) -> Result<u64, Error> {
Ok(0)
}
fn isatty(&self) -> bool {
false
}
async fn readable(&self) -> Result<(), Error> {
Err(Error::badf())
}
async fn writable(&self) -> Result<(), Error> {
Err(Error::badf())
}
async fn sock_accept(&self, _fdflags: FdFlags) -> Result<Box<dyn WasiFile>, Error> {
Err(Error::badf())
}
}