#[cfg(unix)]
use std::cell::UnsafeCell;
#[cfg(unix)]
use std::mem::MaybeUninit;
#[cfg(unix)]
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
#[cfg(unix)]
struct TermiosStorage(UnsafeCell<MaybeUninit<libc::termios>>);
#[cfg(unix)]
unsafe impl Sync for TermiosStorage {}
#[cfg(unix)]
static HAS_ORIGINAL: AtomicBool = AtomicBool::new(false);
#[cfg(unix)]
static ORIGINAL_TERMIOS: TermiosStorage = TermiosStorage(UnsafeCell::new(MaybeUninit::uninit()));
#[cfg(unix)]
static PREV_SIGINT: AtomicUsize = AtomicUsize::new(0);
#[cfg(unix)]
static PREV_SIGTERM: AtomicUsize = AtomicUsize::new(0);
#[cfg(unix)]
extern "C" fn handle_signal(sig: libc::c_int) {
restore_terminal();
unsafe {
libc::signal(sig, libc::SIG_DFL);
libc::raise(sig);
}
}
#[cfg(unix)]
fn restore_terminal() {
if HAS_ORIGINAL.swap(false, Ordering::SeqCst) {
unsafe {
let fd = libc::STDIN_FILENO;
libc::tcflush(fd, libc::TCIFLUSH);
let orig_ptr = (*ORIGINAL_TERMIOS.0.get()).as_ptr();
libc::tcsetattr(fd, libc::TCSANOW, orig_ptr);
let _ = libc::write(libc::STDERR_FILENO, b"\x1b[?25h".as_ptr() as *const _, 6);
let prev_int = PREV_SIGINT.swap(0, Ordering::SeqCst);
if prev_int != 0 {
libc::signal(libc::SIGINT, prev_int as _);
}
let prev_term = PREV_SIGTERM.swap(0, Ordering::SeqCst);
if prev_term != 0 {
libc::signal(libc::SIGTERM, prev_term as _);
}
}
}
}
pub struct TerminalGuard {
active: bool,
}
impl TerminalGuard {
pub fn acquire() -> Self {
#[cfg(unix)]
{
let fd = libc::STDIN_FILENO;
unsafe {
if libc::isatty(fd) != 1 {
return Self { active: false };
}
if HAS_ORIGINAL
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
.is_ok()
{
let mut termios = MaybeUninit::uninit();
if libc::tcgetattr(fd, termios.as_mut_ptr()) == 0 {
*ORIGINAL_TERMIOS.0.get() = termios;
let sig_handler: extern "C" fn(libc::c_int) = handle_signal;
let prev_int = libc::signal(libc::SIGINT, sig_handler as _);
if prev_int != libc::SIG_ERR {
PREV_SIGINT.store(prev_int as usize, Ordering::SeqCst);
}
let prev_term = libc::signal(libc::SIGTERM, sig_handler as _);
if prev_term != libc::SIG_ERR {
PREV_SIGTERM.store(prev_term as usize, Ordering::SeqCst);
}
let mut modified = (*ORIGINAL_TERMIOS.0.get()).assume_init();
modified.c_lflag &= !(libc::ECHO | libc::ECHONL);
libc::tcsetattr(fd, libc::TCSANOW, &modified);
let _ =
libc::write(libc::STDERR_FILENO, b"\x1b[?25l".as_ptr() as *const _, 6);
return Self { active: true };
} else {
HAS_ORIGINAL.store(false, Ordering::SeqCst);
}
}
}
}
Self { active: false }
}
}
impl Drop for TerminalGuard {
fn drop(&mut self) {
if self.active {
#[cfg(unix)]
restore_terminal();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_terminal_guard_acquire_and_drop() {
let guard = TerminalGuard::acquire();
drop(guard);
}
#[test]
#[cfg(unix)]
fn test_terminal_guard_pty_and_restore() {
unsafe {
let master = libc::posix_openpt(libc::O_RDWR | libc::O_NOCTTY);
if master >= 0 {
if libc::grantpt(master) == 0 && libc::unlockpt(master) == 0 {
let slave_name = libc::ptsname(master);
if !slave_name.is_null() {
let slave = libc::open(slave_name, libc::O_RDWR | libc::O_NOCTTY);
if slave >= 0 {
let old_stdin = libc::dup(libc::STDIN_FILENO);
if libc::dup2(slave, libc::STDIN_FILENO) >= 0 {
let guard = TerminalGuard::acquire();
assert!(guard.active);
let second = TerminalGuard::acquire();
assert!(!second.active);
drop(second);
drop(guard);
assert!(!HAS_ORIGINAL.load(Ordering::SeqCst));
}
if old_stdin >= 0 {
libc::dup2(old_stdin, libc::STDIN_FILENO);
libc::close(old_stdin);
}
libc::close(slave);
}
}
}
libc::close(master);
}
}
}
#[test]
#[cfg(unix)]
fn test_restore_terminal_when_not_active() {
assert!(!HAS_ORIGINAL.load(Ordering::SeqCst));
restore_terminal();
assert!(!HAS_ORIGINAL.load(Ordering::SeqCst));
}
}