#![allow(dead_code)]
use std::{
io::{BufRead, BufReader},
path::PathBuf,
process::{Child, Command, Stdio},
thread,
time::{Duration, Instant},
};
pub struct PtyPair {
pub master: PathBuf,
pub slave: PathBuf,
socat: Child,
}
impl PtyPair {
pub fn new() -> Result<Self, String> {
let mut child = Command::new("socat")
.args(["-d", "-d", "PTY,raw,echo=0", "PTY,raw,echo=0"])
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.map_err(|e| format!("spawn socat: {e}"))?;
let stderr = child
.stderr
.take()
.ok_or_else(|| "socat stderr was not piped".to_string())?;
let reader = BufReader::new(stderr);
let mut ptys: Vec<PathBuf> = Vec::with_capacity(2);
for line in reader.lines().map_while(Result::ok) {
if let Some(idx) = line.find("PTY is ") {
let path = line[idx + "PTY is ".len()..].trim().to_string();
ptys.push(PathBuf::from(path));
if ptys.len() == 2 {
break;
}
}
}
if ptys.len() != 2 {
let _ = child.kill();
let _ = child.wait();
return Err(format!("socat did not emit 2 PTY paths (got {ptys:?})"));
}
thread::sleep(Duration::from_millis(100));
let slave = ptys.remove(1);
let master = ptys.remove(0);
Ok(Self {
master,
slave,
socat: child,
})
}
}
impl Drop for PtyPair {
fn drop(&mut self) {
let _ = self.socat.kill();
let _ = self.socat.wait();
}
}
#[must_use]
pub fn socat_available() -> bool {
Command::new("socat")
.arg("-V")
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}
#[must_use]
pub fn wait_with_timeout(
child: &mut std::process::Child,
timeout: Duration,
) -> Option<std::process::ExitStatus> {
let start = Instant::now();
while start.elapsed() < timeout {
match child.try_wait() {
Ok(Some(status)) => return Some(status),
Ok(None) => thread::sleep(Duration::from_millis(50)),
Err(_) => return None,
}
}
None
}