use std::io::{self, Read, Write};
use std::os::fd::AsRawFd;
use std::process::Child;
use std::time::Instant;
const CHUNK: usize = 16 * 1024;
struct Sink {
name: &'static str,
pipe: Option<Box<dyn Read>>,
bytes: Vec<u8>,
dropped: usize,
}
impl Sink {
fn pump(&mut self) -> bool {
let mut moved = false;
let mut buf = [0u8; CHUNK];
while let Some(pipe) = self.pipe.as_mut() {
match pipe.read(&mut buf) {
Ok(n) if n > 0 => {
let room = n.min(super::CAPTURE_LIMIT.saturating_sub(self.bytes.len()));
self.bytes
.extend_from_slice(buf.get(..room).unwrap_or_default());
self.dropped += n - room;
moved = true;
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => break,
_ => self.pipe = None,
}
}
moved
}
fn elided(&self) -> String {
if self.dropped == 0 {
return String::new();
}
format!(
"\nthrall: {} exceeded this box's {}-byte capture limit; {} further bytes were dropped\n",
self.name,
super::CAPTURE_LIMIT,
self.dropped
)
}
}
pub(crate) struct Pipes {
stdin: Option<std::process::ChildStdin>,
payload: Vec<u8>,
written: usize,
out: Sink,
err: Sink,
}
impl Pipes {
pub(crate) fn new(child: &mut Child, payload: Vec<u8>) -> Self {
Self {
stdin: unblocked(child.stdin.take()),
payload,
written: 0,
out: sink("stdout", reading(child.stdout.take())),
err: sink("stderr", reading(child.stderr.take())),
}
}
pub(crate) fn pump(&mut self) -> bool {
let fed = self.feed();
let out = self.out.pump();
let err = self.err.pump();
fed || out || err
}
pub(crate) fn settle(&mut self, until: Instant) {
while self.pump() && Instant::now() < until {}
}
pub(crate) fn take(self) -> Drained {
let note = format!("{}{}", self.out.elided(), self.err.elided());
Drained {
out: self.out.bytes,
err: self.err.bytes,
note,
}
}
fn feed(&mut self) -> bool {
let Some(pipe) = self.stdin.as_mut() else {
return false;
};
let mut moved = false;
loop {
let rest = self.payload.get(self.written..).unwrap_or_default();
if rest.is_empty() {
break;
}
match pipe.write(rest) {
Ok(n) if n > 0 => {
self.written += n;
moved = true;
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => return moved,
_ => break,
}
}
self.stdin = None;
moved
}
}
pub(crate) struct Drained {
pub(crate) out: Vec<u8>,
pub(crate) err: Vec<u8>,
pub(crate) note: String,
}
fn sink(name: &'static str, pipe: Option<Box<dyn Read>>) -> Sink {
Sink {
name,
pipe,
bytes: Vec::new(),
dropped: 0,
}
}
fn unblocked<T: AsRawFd>(pipe: Option<T>) -> Option<T> {
if let Some(pipe) = pipe.as_ref() {
crate::sys::nonblocking(pipe.as_raw_fd());
}
pipe
}
fn reading<T: AsRawFd + Read + 'static>(pipe: Option<T>) -> Option<Box<dyn Read>> {
unblocked(pipe).map(|pipe| Box::new(pipe) as Box<dyn Read>)
}