use std::io::{self, BufRead, BufReader, Read, Write};
use std::process::{Child, ChildStderr, Command, Stdio};
use std::thread::{self, JoinHandle};
pub(crate) struct Transport {
pub reader: BufReader<Box<dyn Read + Send>>,
pub writer: Box<dyn Write + Send>,
_child: Option<Child>,
_stderr_thread: Option<JoinHandle<()>>,
}
impl Transport {
pub fn stdio() -> Self {
Self {
reader: BufReader::with_capacity(64 * 1024, Box::new(io::stdin())),
writer: Box::new(io::stdout()),
_child: None,
_stderr_thread: None,
}
}
pub fn exec(command: &str) -> io::Result<Self> {
let mut child = Command::new(if cfg!(windows) { "cmd" } else { "sh" })
.args(if cfg!(windows) {
vec!["/c", command]
} else {
vec!["-c", command]
})
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(|e| io::Error::other(format!("failed to exec '{command}': {e}")))?;
let child_stdout = child.stdout.take().expect("child stdout piped");
let child_stdin = child.stdin.take().expect("child stdin piped");
let child_stderr = child.stderr.take().expect("child stderr piped");
let stderr_thread = spawn_stderr_forwarder(child_stderr);
Ok(Self {
reader: BufReader::with_capacity(64 * 1024, Box::new(child_stdout)),
writer: Box::new(child_stdin),
_child: Some(child),
_stderr_thread: Some(stderr_thread),
})
}
pub fn name(&self) -> &'static str {
if self._child.is_some() {
"exec"
} else {
"stdio"
}
}
pub fn into_parts(
self,
) -> (
BufReader<Box<dyn Read + Send>>,
Box<dyn Write + Send>,
TransportGuard,
) {
(
self.reader,
self.writer,
TransportGuard {
_child: self._child,
_stderr_thread: self._stderr_thread,
},
)
}
}
pub(crate) struct TransportGuard {
_child: Option<Child>,
_stderr_thread: Option<JoinHandle<()>>,
}
impl Drop for TransportGuard {
fn drop(&mut self) {
if let Some(ref mut child) = self._child {
let _ = child.kill();
let _ = child.wait();
}
}
}
fn spawn_stderr_forwarder(stderr: ChildStderr) -> JoinHandle<()> {
thread::spawn(move || {
let reader = BufReader::new(stderr);
let mut lines = reader.lines();
while let Some(Ok(line)) = lines.next() {
eprintln!("[remote] {line}");
}
})
}