diff --git a/Cargo.lock b/Cargo.lock index cd39589..ddfb849 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,6 +66,8 @@ dependencies = [ "nix", "serde", "serde_json", + "signal-hook", + "signal-hook-mio", "termion", ] @@ -259,6 +261,36 @@ dependencies = [ "serde", ] +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + [[package]] name = "strsim" version = "0.10.0" diff --git a/Cargo.toml b/Cargo.toml index 8c0110c..b652b47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,5 @@ termion = "2.0.1" serde = { version = "1.0.189", features = ["derive"] } serde_json = "1.0.107" clap = { version = "4.4.7", features = ["derive"] } +signal-hook-mio = { version = "0.2.3", features = ["support-v0_8"] } +signal-hook = "0.3.17" diff --git a/src/pty.rs b/src/pty.rs index c595377..2fc809f 100644 --- a/src/pty.rs +++ b/src/pty.rs @@ -1,5 +1,8 @@ +use anyhow::bail; use mio::unix::SourceFd; use nix::{fcntl, libc, pty, sys::signal, sys::wait, unistd, unistd::ForkResult}; +use signal_hook::consts::signal::*; +use signal_hook_mio::v0_8::Signals; use std::ffi::{CString, NulError}; use std::fs; use std::io::{self, Read, Write}; @@ -26,9 +29,13 @@ pub fn exec, R: Recorder>( let result = unsafe { pty::forkpty(Some(&winsize), None) }?; match result.fork_result { - ForkResult::Parent { child } => { - handle_parent(result.master.as_raw_fd(), tty, child, recorder) - } + ForkResult::Parent { child } => handle_parent( + result.master.as_raw_fd(), + tty, + child, + winsize_override, + recorder, + ), ForkResult::Child => { handle_child(args, env)?; @@ -37,41 +44,14 @@ pub fn exec, R: Recorder>( } } -fn open_tty() -> io::Result { - fs::OpenOptions::new() - .read(true) - .write(true) - .open("/dev/tty") -} - -fn get_tty_size(tty_fd: i32, winsize_override: (Option, Option)) -> pty::Winsize { - let mut winsize = pty::Winsize { - ws_row: 24, - ws_col: 80, - ws_xpixel: 0, - ws_ypixel: 0, - }; - - unsafe { libc::ioctl(tty_fd, libc::TIOCGWINSZ, &mut winsize) }; - - if let Some(cols) = winsize_override.0 { - winsize.ws_col = cols; - } - - if let Some(rows) = winsize_override.1 { - winsize.ws_row = rows; - } - - winsize -} - fn handle_parent( master_fd: RawFd, tty: fs::File, child: unistd::Pid, + winsize_override: (Option, Option), recorder: &mut R, ) -> anyhow::Result { - let copy_result = copy(master_fd, tty, recorder); + let copy_result = copy(master_fd, tty, winsize_override, recorder); let wait_result = wait::waitpid(child, None); copy_result?; @@ -85,9 +65,15 @@ fn handle_parent( const MASTER: mio::Token = mio::Token(0); const TTY: mio::Token = mio::Token(1); +const SIGNAL: mio::Token = mio::Token(2); const BUF_SIZE: usize = 128 * 1024; -fn copy(master_fd: RawFd, tty: fs::File, recorder: &mut R) -> anyhow::Result<()> { +fn copy( + master_fd: RawFd, + tty: fs::File, + winsize_override: (Option, Option), + recorder: &mut R, +) -> anyhow::Result<()> { let mut master = unsafe { fs::File::from_raw_fd(master_fd) }; let mut poll = mio::Poll::new()?; let mut events = mio::Events::with_capacity(128); @@ -95,6 +81,7 @@ fn copy(master_fd: RawFd, tty: fs::File, recorder: &mut R) -> anyho let mut tty = tty.into_raw_mode()?; let tty_fd = tty.as_raw_fd(); let mut tty_source = SourceFd(&tty_fd); + let mut signals = Signals::new([SIGWINCH])?; let mut buf = [0u8; BUF_SIZE]; let mut input: Vec = Vec::with_capacity(BUF_SIZE); let mut output: Vec = Vec::with_capacity(BUF_SIZE); @@ -103,14 +90,24 @@ fn copy(master_fd: RawFd, tty: fs::File, recorder: &mut R) -> anyho set_non_blocking(&master_fd)?; set_non_blocking(&tty_fd)?; + poll.registry() .register(&mut master_source, MASTER, mio::Interest::READABLE)?; poll.registry() .register(&mut tty_source, TTY, mio::Interest::READABLE)?; + poll.registry() + .register(&mut signals, SIGNAL, mio::Interest::READABLE)?; + loop { - poll.poll(&mut events, None).unwrap(); + if let Err(e) = poll.poll(&mut events, None) { + if e.kind() == io::ErrorKind::Interrupted { + continue; + } else { + bail!(e); + } + } for event in events.iter() { match event.token() { @@ -191,6 +188,15 @@ fn copy(master_fd: RawFd, tty: fs::File, recorder: &mut R) -> anyho } } + SIGNAL => { + for signal in signals.pending() { + if signal == SIGWINCH { + let winsize = get_tty_size(tty_fd, winsize_override); + set_pty_size(master_fd, &winsize); + } + } + } + _ => (), } } @@ -210,6 +216,38 @@ fn handle_child>(args: &[S], env: &[CString]) -> anyhow::Result<() unsafe { libc::_exit(1) } } +fn open_tty() -> io::Result { + fs::OpenOptions::new() + .read(true) + .write(true) + .open("/dev/tty") +} + +fn get_tty_size(tty_fd: i32, winsize_override: (Option, Option)) -> pty::Winsize { + let mut winsize = pty::Winsize { + ws_row: 24, + ws_col: 80, + ws_xpixel: 0, + ws_ypixel: 0, + }; + + unsafe { libc::ioctl(tty_fd, libc::TIOCGWINSZ, &mut winsize) }; + + if let Some(cols) = winsize_override.0 { + winsize.ws_col = cols; + } + + if let Some(rows) = winsize_override.1 { + winsize.ws_row = rows; + } + + winsize +} + +fn set_pty_size(pty_fd: i32, winsize: &pty::Winsize) { + unsafe { libc::ioctl(pty_fd, libc::TIOCSWINSZ, winsize) }; +} + fn set_non_blocking(fd: &RawFd) -> Result<(), io::Error> { use fcntl::{fcntl, FcntlArg::*, OFlag};