use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
static CHILD_ACTIVE: AtomicBool = AtomicBool::new(false);
static INTERRUPTS: AtomicU32 = AtomicU32::new(0);
static INTERRUPTED: AtomicBool = AtomicBool::new(false);
pub fn init() {
imp::capture();
imp::install_handler();
}
pub fn restore() {
imp::restore();
}
pub fn interrupted() -> bool {
INTERRUPTED.load(Ordering::SeqCst)
}
pub fn child_begin() {
INTERRUPTS.store(0, Ordering::SeqCst);
CHILD_ACTIVE.store(true, Ordering::SeqCst);
}
pub fn child_end() {
CHILD_ACTIVE.store(false, Ordering::SeqCst);
restore();
}
pub fn confine(child: &std::process::Child) {
imp::confine(child);
}
const RESET_SEQUENCE: &str = "\x1b[?25h\x1b[0m";
#[cfg(windows)]
mod imp {
use std::os::windows::io::AsRawHandle;
use std::sync::atomic::{AtomicIsize, AtomicU32, Ordering};
use windows_sys::Win32::Foundation::{FALSE, HANDLE, TRUE};
use windows_sys::Win32::System::Console::{
CTRL_BREAK_EVENT, CTRL_C_EVENT, GetConsoleMode, GetStdHandle, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, SetConsoleCtrlHandler, SetConsoleMode,
};
use windows_sys::Win32::System::JobObjects::{
AssignProcessToJobObject, CreateJobObjectW, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, JOBOBJECT_EXTENDED_LIMIT_INFORMATION,
JobObjectExtendedLimitInformation, SetInformationJobObject, TerminateJobObject,
};
static STDIN_MODE: AtomicU32 = AtomicU32::new(0);
static STDOUT_MODE: AtomicU32 = AtomicU32::new(0);
static STDIN_CAPTURED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
static STDOUT_CAPTURED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
static JOB: AtomicIsize = AtomicIsize::new(0);
pub fn capture() {
unsafe {
let mut mode = 0u32;
let stdin = GetStdHandle(STD_INPUT_HANDLE);
if GetConsoleMode(stdin, &mut mode) != 0 {
STDIN_MODE.store(mode, Ordering::SeqCst);
STDIN_CAPTURED.store(true, Ordering::SeqCst);
}
let stdout = GetStdHandle(STD_OUTPUT_HANDLE);
if GetConsoleMode(stdout, &mut mode) != 0 {
STDOUT_MODE.store(mode, Ordering::SeqCst);
STDOUT_CAPTURED.store(true, Ordering::SeqCst);
}
}
}
pub fn restore() {
unsafe {
if STDIN_CAPTURED.load(Ordering::SeqCst) {
SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), STDIN_MODE.load(Ordering::SeqCst));
}
if STDOUT_CAPTURED.load(Ordering::SeqCst) {
SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), STDOUT_MODE.load(Ordering::SeqCst));
use std::io::Write;
let mut stdout = std::io::stdout();
let _ = stdout.write_all(super::RESET_SEQUENCE.as_bytes());
let _ = stdout.flush();
}
}
}
pub fn install_handler() {
unsafe {
SetConsoleCtrlHandler(Some(handler), TRUE);
}
}
unsafe extern "system" fn handler(ctrl_type: u32) -> windows_sys::core::BOOL {
if ctrl_type != CTRL_C_EVENT && ctrl_type != CTRL_BREAK_EVENT {
return FALSE;
}
super::INTERRUPTED.store(true, Ordering::SeqCst);
if super::CHILD_ACTIVE.load(Ordering::SeqCst) {
if super::INTERRUPTS.fetch_add(1, Ordering::SeqCst) >= 1 {
let job = JOB.load(Ordering::SeqCst);
if job != 0 {
unsafe { TerminateJobObject(job as HANDLE, 130) };
}
}
return TRUE;
}
restore();
std::process::exit(130);
}
pub fn confine(child: &std::process::Child) {
unsafe {
let job = CreateJobObjectW(std::ptr::null(), std::ptr::null());
if job.is_null() {
return;
}
let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = std::mem::zeroed();
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
let ok = SetInformationJobObject(
job,
JobObjectExtendedLimitInformation,
std::ptr::from_ref(&info).cast(),
std::mem::size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
);
if ok != 0 {
AssignProcessToJobObject(job, child.as_raw_handle() as HANDLE);
}
JOB.store(job as isize, Ordering::SeqCst);
}
}
}
#[cfg(unix)]
mod imp {
use std::cell::UnsafeCell;
use std::mem::MaybeUninit;
use std::sync::atomic::{AtomicBool, Ordering};
struct TermiosSlot(UnsafeCell<MaybeUninit<libc::termios>>);
unsafe impl Sync for TermiosSlot {}
static ORIGINAL: TermiosSlot = TermiosSlot(UnsafeCell::new(MaybeUninit::uninit()));
static CAPTURED: AtomicBool = AtomicBool::new(false);
pub fn capture() {
unsafe {
let slot = ORIGINAL.0.get();
if libc::tcgetattr(libc::STDIN_FILENO, (*slot).as_mut_ptr()) == 0 {
CAPTURED.store(true, Ordering::SeqCst);
}
}
}
pub fn restore() {
if !CAPTURED.load(Ordering::SeqCst) {
return;
}
unsafe {
let slot = ORIGINAL.0.get();
libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, (*slot).as_ptr());
let sequence = super::RESET_SEQUENCE;
libc::write(libc::STDOUT_FILENO, sequence.as_ptr().cast(), sequence.len());
}
}
pub fn install_handler() {
unsafe {
let mut action: libc::sigaction = std::mem::zeroed();
action.sa_sigaction = handler as *const std::ffi::c_void as libc::sighandler_t;
action.sa_flags = libc::SA_RESTART;
libc::sigaction(libc::SIGINT, &action, std::ptr::null_mut());
}
}
extern "C" fn handler(_signal: libc::c_int) {
super::INTERRUPTED.store(true, Ordering::SeqCst);
if super::CHILD_ACTIVE.load(Ordering::SeqCst) {
super::INTERRUPTS.fetch_add(1, Ordering::SeqCst);
return;
}
restore();
unsafe { libc::_exit(130) };
}
pub fn confine(_child: &std::process::Child) {}
}