use super::PtyProcess;
use crate::unix::pty_process::PtyProcessOptions;
use libc::SIGWINCH;
use nix::sys::select::FdSet;
use nix::{
errno::Errno,
sys::{select, time::TimeVal, wait::WaitStatus},
};
use signal_hook::iterator::Signals;
use std::{
fs::File,
io::{self, Read, Write},
os::fd::AsFd,
process::Command,
};
pub struct PtySession {
pub process: PtyProcess,
pub process_stdout: File,
pub process_stdin: File,
}
impl PtySession {
pub fn new(command: Command) -> io::Result<Self> {
let process = PtyProcess::new(
command,
PtyProcessOptions {
echo: true,
..Default::default()
},
)?;
let process_stdin = process.get_file_handle()?;
let process_stdout = process.get_file_handle()?;
Ok(Self {
process,
process_stdout,
process_stdin,
})
}
pub fn send<B: AsRef<[u8]>>(&mut self, s: B) -> io::Result<usize> {
self.process_stdin.write(s.as_ref())
}
pub fn send_line(&mut self, line: &str) -> io::Result<usize> {
let mut len = self.send(line)?;
len += self.process_stdin.write(&[b'\n'])?;
Ok(len)
}
pub fn flush(&mut self) -> io::Result<()> {
self.process_stdin.flush()
}
pub fn interact(&mut self) -> io::Result<Option<i32>> {
self.flush()?;
let original_mode = self.process.set_raw()?;
let process_stdout_clone = self.process_stdout.try_clone()?;
let process_stdout_fd = process_stdout_clone.as_fd();
let stdin = std::io::stdin();
let stdin_fd = stdin.as_fd();
let mut fd_set = FdSet::new();
fd_set.insert(process_stdout_fd);
fd_set.insert(stdin_fd);
let mut buf = [0u8; 2048];
let mut signals = Signals::new([SIGWINCH])?;
let exit_status = loop {
let status = self.process.status();
if status != Some(WaitStatus::StillAlive) {
break status;
}
for signal in signals.pending() {
match signal {
SIGWINCH => {
let mut size: libc::winsize = unsafe { std::mem::zeroed() };
let res = unsafe {
libc::ioctl(libc::STDOUT_FILENO, libc::TIOCGWINSZ, &mut size)
};
if res == 0 {
self.process.set_window_size(size)?;
}
}
_ => unreachable!(),
}
}
let mut select_timeout = TimeVal::new(4, 0);
let mut select_set = fd_set;
let res = select::select(None, &mut select_set, None, None, &mut select_timeout);
if let Err(error) = res {
if error == Errno::EINTR {
continue;
} else {
self.process.set_mode(original_mode)?;
return Err(std::io::Error::from(error));
}
} else {
if select_set.contains(process_stdout_fd) {
let bytes_read = self.process_stdout.read(&mut buf).unwrap_or(0);
if bytes_read > 0 {
io::stdout().write_all(&buf[..bytes_read])?;
io::stdout().flush()?;
}
}
if select_set.contains(stdin_fd) {
let bytes_read = io::stdin().read(&mut buf)?;
self.process_stdin.write_all(&buf[..bytes_read])?;
self.process_stdin.flush()?;
}
}
};
self.process.set_mode(original_mode)?;
match exit_status {
Some(WaitStatus::Exited(_, code)) => Ok(Some(code)),
Some(WaitStatus::Signaled(_, signal, _)) => Ok(Some(128 + signal as i32)),
_ => Ok(None),
}
}
}