use std::io;
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
use std::sync::atomic::{AtomicBool, Ordering};
use nix::poll::{poll, PollFd, PollFlags, PollTimeout};
use nix::sys::termios::{self, SetArg, Termios};
use crate::container::{Child, ExitStatus};
static WINCH_RECEIVED: AtomicBool = AtomicBool::new(false);
struct TerminalGuard {
fd: RawFd,
original: Termios,
}
impl TerminalGuard {
fn enter_raw(fd: RawFd) -> io::Result<Self> {
let borrowed = unsafe { std::os::fd::BorrowedFd::borrow_raw(fd) };
let original = termios::tcgetattr(borrowed).map_err(io::Error::from)?;
let mut raw = original.clone();
termios::cfmakeraw(&mut raw);
termios::tcsetattr(borrowed, SetArg::TCSANOW, &raw).map_err(io::Error::from)?;
Ok(TerminalGuard { fd, original })
}
}
impl Drop for TerminalGuard {
fn drop(&mut self) {
let borrowed = unsafe { std::os::fd::BorrowedFd::borrow_raw(self.fd) };
let _ = termios::tcsetattr(borrowed, SetArg::TCSANOW, &self.original);
}
}
pub struct InteractiveSession {
pub(crate) master: OwnedFd,
pub child: Child,
}
impl InteractiveSession {
pub fn run(mut self) -> io::Result<ExitStatus> {
let stdin_is_tty = unsafe { libc::isatty(libc::STDIN_FILENO) } == 1;
if stdin_is_tty {
if let Ok(ws) = get_winsize(libc::STDOUT_FILENO) {
let _ = set_winsize(self.master.as_raw_fd(), &ws);
}
}
install_sigwinch_handler();
let _guard = if stdin_is_tty {
Some(TerminalGuard::enter_raw(libc::STDIN_FILENO)?)
} else {
None
};
relay_loop(self.master.as_raw_fd())?;
self.child
.wait()
.map_err(|e| io::Error::other(e.to_string()))
}
}
fn relay_loop(master_fd: RawFd) -> io::Result<()> {
let mut buf = [0u8; 4096];
loop {
if WINCH_RECEIVED.swap(false, Ordering::Relaxed) {
if let Ok(ws) = get_winsize(libc::STDOUT_FILENO) {
let _ = set_winsize(master_fd, &ws);
}
}
let stdin_bfd = unsafe { std::os::fd::BorrowedFd::borrow_raw(libc::STDIN_FILENO) };
let master_bfd = unsafe { std::os::fd::BorrowedFd::borrow_raw(master_fd) };
let mut fds = [
PollFd::new(stdin_bfd, PollFlags::POLLIN),
PollFd::new(master_bfd, PollFlags::POLLIN),
];
let timeout = PollTimeout::from(100u16);
match poll(&mut fds, timeout) {
Ok(0) => continue, Ok(_) => {}
Err(nix::errno::Errno::EINTR) => continue, Err(e) => return Err(io::Error::from(e)),
}
if let Some(revents) = fds[0].revents() {
if revents.contains(PollFlags::POLLIN) {
let n = unsafe {
libc::read(libc::STDIN_FILENO, buf.as_mut_ptr() as *mut _, buf.len())
};
if n > 0 {
unsafe {
libc::write(master_fd, buf.as_ptr() as *const _, n as usize);
}
}
}
}
if let Some(revents) = fds[1].revents() {
if revents.contains(PollFlags::POLLIN) {
let n = unsafe { libc::read(master_fd, buf.as_mut_ptr() as *mut _, buf.len()) };
if n > 0 {
unsafe {
libc::write(libc::STDOUT_FILENO, buf.as_ptr() as *const _, n as usize);
}
} else {
break;
}
}
if revents.intersects(PollFlags::POLLHUP | PollFlags::POLLERR) {
break;
}
}
}
Ok(())
}
fn get_winsize(fd: RawFd) -> io::Result<libc::winsize> {
let mut ws = libc::winsize {
ws_row: 24,
ws_col: 80,
ws_xpixel: 0,
ws_ypixel: 0,
};
let ret = unsafe { libc::ioctl(fd, libc::TIOCGWINSZ as _, &mut ws) };
if ret < 0 {
return Err(io::Error::last_os_error());
}
Ok(ws)
}
fn set_winsize(fd: RawFd, ws: &libc::winsize) -> io::Result<()> {
let ret = unsafe { libc::ioctl(fd, libc::TIOCSWINSZ as _, ws) };
if ret < 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
fn install_sigwinch_handler() {
use nix::sys::signal::{signal, SigHandler, Signal};
extern "C" fn sigwinch_handler(_: libc::c_int) {
WINCH_RECEIVED.store(true, Ordering::Relaxed);
}
unsafe {
let _ = signal(Signal::SIGWINCH, SigHandler::Handler(sigwinch_handler));
}
}