use std::{
ffi::c_int,
fs::{File, OpenOptions},
io::{self, Read, Write},
mem::MaybeUninit,
os::fd::{AsRawFd, RawFd},
sync::atomic::{AtomicBool, Ordering},
};
use libc::{
c_void, cfgetispeed, cfgetospeed, cfmakeraw, cfsetispeed, cfsetospeed, ioctl, sigaction,
sigemptyset, sighandler_t, siginfo_t, sigset_t, tcflag_t, tcgetattr, tcsetattr, termios,
winsize, CS7, CS8, ECHO, ECHOCTL, ECHOE, ECHOK, ECHOKE, ECHONL, ICANON, ICRNL, IEXTEN, IGNCR,
IGNPAR, IMAXBEL, INLCR, INPCK, ISIG, ISTRIP, IUTF8, IXANY, IXOFF, IXON, NOFLSH, OCRNL, OLCUC,
ONLCR, ONLRET, ONOCR, OPOST, PARENB, PARMRK, PARODD, PENDIN, SIGTTOU, TCSADRAIN, TCSAFLUSH,
TIOCGWINSZ, TIOCSWINSZ, TOSTOP,
};
use super::Terminal;
use crate::{cutils::cerr, system::interface::ProcessId};
const INPUT_FLAGS: tcflag_t = IGNPAR
| PARMRK
| INPCK
| ISTRIP
| INLCR
| IGNCR
| ICRNL
| IXON
| IXANY
| IXOFF
| IMAXBEL
| IUTF8;
const OUTPUT_FLAGS: tcflag_t = OPOST | OLCUC | ONLCR | OCRNL | ONOCR | ONLRET;
const CONTROL_FLAGS: tcflag_t = CS7 | CS8 | PARENB | PARODD;
const LOCAL_FLAGS: tcflag_t = ISIG
| ICANON
| ECHO
| ECHOE
| ECHOK
| ECHONL
| NOFLSH
| TOSTOP
| IEXTEN
| ECHOCTL
| ECHOKE
| PENDIN;
static GOT_SIGTTOU: AtomicBool = AtomicBool::new(false);
extern "C" fn on_sigttou(_signal: c_int, _info: *mut siginfo_t, _: *mut c_void) {
GOT_SIGTTOU.store(true, Ordering::SeqCst);
}
fn tcsetattr_nobg(fd: c_int, flags: c_int, tp: *const termios) -> io::Result<()> {
let mut original_action = MaybeUninit::<sigaction>::uninit();
let action = sigaction {
sa_sigaction: on_sigttou as sighandler_t,
sa_mask: {
let mut sa_mask = MaybeUninit::<sigset_t>::uninit();
unsafe { sigemptyset(sa_mask.as_mut_ptr()) };
unsafe { sa_mask.assume_init() }
},
sa_flags: 0,
sa_restorer: None,
};
GOT_SIGTTOU.store(false, Ordering::SeqCst);
unsafe { sigaction(SIGTTOU, &action, original_action.as_mut_ptr()) };
let result = loop {
match cerr(unsafe { tcsetattr(fd, flags, tp) }) {
Ok(_) => break Ok(()),
Err(err) => {
let got_sigttou = GOT_SIGTTOU.load(Ordering::SeqCst);
if got_sigttou || err.kind() != io::ErrorKind::Interrupted {
break Err(err);
}
}
}
};
unsafe { sigaction(SIGTTOU, original_action.as_ptr(), std::ptr::null_mut()) };
result
}
pub struct UserTerm {
tty: File,
original_termios: MaybeUninit<termios>,
changed: bool,
}
impl UserTerm {
pub fn open() -> io::Result<Self> {
Ok(Self {
tty: OpenOptions::new().read(true).write(true).open("/dev/tty")?,
original_termios: MaybeUninit::uninit(),
changed: false,
})
}
pub fn copy_to<D: AsRawFd>(&self, dst: &D) -> io::Result<()> {
let src = self.tty.as_raw_fd();
let dst = dst.as_raw_fd();
let mut tt_src = MaybeUninit::<termios>::uninit();
let mut tt_dst = MaybeUninit::<termios>::uninit();
let mut wsize = MaybeUninit::<winsize>::uninit();
cerr(unsafe { tcgetattr(src, tt_src.as_mut_ptr()) })?;
cerr(unsafe { tcgetattr(dst, tt_dst.as_mut_ptr()) })?;
let tt_src = unsafe { tt_src.assume_init() };
let mut tt_dst = unsafe { tt_dst.assume_init() };
tt_dst.c_iflag &= !INPUT_FLAGS;
tt_dst.c_oflag &= !OUTPUT_FLAGS;
tt_dst.c_cflag &= !CONTROL_FLAGS;
tt_dst.c_lflag &= !LOCAL_FLAGS;
tt_dst.c_iflag |= tt_src.c_iflag & INPUT_FLAGS;
tt_dst.c_oflag |= tt_src.c_oflag & OUTPUT_FLAGS;
tt_dst.c_cflag |= tt_src.c_cflag & CONTROL_FLAGS;
tt_dst.c_lflag |= tt_src.c_lflag & LOCAL_FLAGS;
tt_dst.c_cc.copy_from_slice(&tt_src.c_cc);
{
let mut speed = unsafe { cfgetospeed(&tt_src) };
if speed == libc::B0 {
speed = libc::B38400;
}
unsafe { cfsetospeed(&mut tt_dst, speed) };
speed = unsafe { cfgetispeed(&tt_src) };
unsafe { cfsetispeed(&mut tt_dst, speed) };
}
tcsetattr_nobg(dst, TCSAFLUSH, &tt_dst)?;
cerr(unsafe { ioctl(src, TIOCGWINSZ, &mut wsize) })?;
cerr(unsafe { ioctl(dst, TIOCSWINSZ, &wsize) })?;
Ok(())
}
pub fn set_raw_mode(&mut self, with_signals: bool) -> io::Result<()> {
let fd = self.tty.as_raw_fd();
if !self.changed {
cerr(unsafe { tcgetattr(fd, self.original_termios.as_mut_ptr()) })?;
}
let mut term = unsafe { self.original_termios.assume_init() };
unsafe { cfmakeraw(&mut term) };
if with_signals {
term.c_cflag |= ISIG;
}
tcsetattr_nobg(fd, TCSADRAIN, &term)?;
self.changed = true;
Ok(())
}
pub fn restore(&mut self, flush: bool) -> io::Result<()> {
if self.changed {
let fd = self.tty.as_raw_fd();
let flags = if flush { TCSAFLUSH } else { TCSADRAIN };
tcsetattr_nobg(fd, flags, self.original_termios.as_ptr())?;
self.changed = false;
}
Ok(())
}
pub fn tcsetpgrp_nobg(&self, pgrp: ProcessId) -> io::Result<()> {
let mut original_action = MaybeUninit::<sigaction>::uninit();
let action = sigaction {
sa_sigaction: on_sigttou as sighandler_t,
sa_mask: {
let mut sa_mask = MaybeUninit::<sigset_t>::uninit();
unsafe { sigemptyset(sa_mask.as_mut_ptr()) };
unsafe { sa_mask.assume_init() }
},
sa_flags: 0,
sa_restorer: None,
};
GOT_SIGTTOU.store(false, Ordering::SeqCst);
unsafe { sigaction(SIGTTOU, &action, original_action.as_mut_ptr()) };
let result = loop {
match self.tty.tcsetpgrp(pgrp) {
Ok(()) => break Ok(()),
Err(err) => {
let got_sigttou = GOT_SIGTTOU.load(Ordering::SeqCst);
if got_sigttou || err.kind() != io::ErrorKind::Interrupted {
break Err(err);
}
}
}
};
unsafe { sigaction(SIGTTOU, original_action.as_ptr(), std::ptr::null_mut()) };
result
}
}
impl AsRawFd for UserTerm {
fn as_raw_fd(&self) -> RawFd {
self.tty.as_raw_fd()
}
}
impl Read for UserTerm {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.tty.read(buf)
}
}
impl Write for UserTerm {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.tty.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.tty.flush()
}
}