#[cfg(windows)]
mod win {
pub const CTRL_C_EVENT: u32 = 0;
pub type Handler = unsafe extern "system" fn(::std::ffi::c_uint) -> i32;
#[link(name = "kernel32")]
unsafe extern "system" {
pub safe fn SetConsoleCtrlHandler(handlerroutine: Option<Handler>, add: i32) -> i32;
pub safe fn GenerateConsoleCtrlEvent(dwctrlevent: u32, dwprocessgroupid: u32) -> i32;
}
}
#[cfg(not(windows))]
use std::os::unix::io::RawFd;
#[cfg(not(windows))]
use std::sync::atomic::AtomicI32;
#[cfg(not(windows))]
use std::thread::JoinHandle;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use bao_engine::execution_control::ExecutionControl;
#[cfg(not(windows))]
static PIPE_WRITE_FD: AtomicI32 = AtomicI32::new(-1);
static BRIDGE_ACTIVE: AtomicBool = AtomicBool::new(false);
struct Shared {
control: Mutex<Option<ExecutionControl>>,
eaten_unhandled: AtomicBool,
}
#[cfg(not(windows))]
extern "C" fn bridge_sigint_handler(_sig: libc::c_int) {
let fd = PIPE_WRITE_FD.load(Ordering::Relaxed);
if fd >= 0 {
let byte: u8 = 1;
unsafe {
let _ = libc::write(fd, &byte as *const u8 as *const libc::c_void, 1);
}
}
}
#[cfg(not(windows))]
fn watcher_loop(read_fd: RawFd, shared: &Shared) {
let mut byte = [0u8; 1];
loop {
let n = unsafe { libc::read(read_fd, byte.as_mut_ptr() as *mut libc::c_void, 1) };
if n == 1 {
let guard = shared.control.lock().unwrap_or_else(|e| e.into_inner());
if let Some(control) = guard.as_ref() {
control.cancel();
} else {
drop(guard);
shared.eaten_unhandled.store(true, Ordering::Release);
}
} else if n == 0 {
break; } else {
let err = std::io::Error::last_os_error();
if err.kind() == std::io::ErrorKind::Interrupted {
continue;
}
break; }
}
unsafe {
libc::close(read_fd);
}
}
#[cfg(windows)]
extern "system" fn bridge_ctrl_handler(ctrl_type: ::std::ffi::c_uint) -> i32 {
if ctrl_type != win::CTRL_C_EVENT {
return 0; }
if let Some(shared) = SHARED.get() {
let guard = shared.control.lock().unwrap_or_else(|e| e.into_inner());
if guard.as_ref().is_some() {
guard.as_ref().unwrap().cancel();
} else {
drop(guard);
shared.eaten_unhandled.store(true, Ordering::Release);
}
return 1; }
0
}
#[cfg(windows)]
static SHARED: ::std::sync::OnceLock<Arc<Shared>> = ::std::sync::OnceLock::new();
#[cfg(not(windows))]
pub struct InterruptBridge {
shared: Arc<Shared>,
watcher: Option<JoinHandle<()>>,
write_fd: RawFd,
prev_action: libc::sigaction,
}
#[cfg(windows)]
pub struct InterruptBridge {
shared: Arc<Shared>,
}
#[doc(hidden)]
impl InterruptBridge {
#[cfg(not(windows))]
pub fn install() -> Self {
assert!(
!BRIDGE_ACTIVE.swap(true, Ordering::AcqRel),
"InterruptBridge: at most one bridge per process"
);
let mut fds = [0 as libc::c_int; 2];
unsafe {
assert_eq!(
libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC),
0,
"InterruptBridge: pipe2 failed"
);
}
let (read_fd, write_fd) = (fds[0], fds[1]);
PIPE_WRITE_FD.store(write_fd, Ordering::Release);
let shared = Arc::new(Shared {
control: Mutex::new(None),
eaten_unhandled: AtomicBool::new(false),
});
let watcher_shared = Arc::clone(&shared);
let watcher = std::thread::Builder::new()
.name("bao-sigint-bridge".to_string())
.spawn(move || watcher_loop(read_fd, &watcher_shared))
.expect("InterruptBridge: watcher spawn failed");
unsafe {
let mut sa: libc::sigaction = std::mem::zeroed();
sa.sa_sigaction = bridge_sigint_handler as *const () as usize;
libc::sigemptyset(&mut sa.sa_mask);
sa.sa_flags = libc::SA_RESTART;
let mut prev: libc::sigaction = std::mem::zeroed();
assert_eq!(
libc::sigaction(libc::SIGINT, &sa, &mut prev),
0,
"InterruptBridge: sigaction failed"
);
InterruptBridge {
shared,
watcher: Some(watcher),
write_fd,
prev_action: prev,
}
}
}
#[cfg(windows)]
pub fn install() -> Self {
assert!(
!BRIDGE_ACTIVE.swap(true, Ordering::AcqRel),
"InterruptBridge: at most one bridge per process"
);
let shared = Arc::new(Shared {
control: Mutex::new(None),
eaten_unhandled: AtomicBool::new(false),
});
let shared_clone = shared.clone();
let _ = SHARED.set(shared_clone);
assert_ne!(
unsafe { win::SetConsoleCtrlHandler(Some(bridge_ctrl_handler), 1) },
0,
"InterruptBridge: SetConsoleCtrlHandler failed"
);
InterruptBridge { shared }
}
pub fn arm(&self, control: &ExecutionControl) {
*self.shared.control.lock().unwrap_or_else(|e| e.into_inner()) = Some(control.clone());
}
pub fn disarm(&mut self) {
*self.shared.control.lock().unwrap_or_else(|e| e.into_inner()) = None;
}
}
#[cfg(not(windows))]
impl Drop for InterruptBridge {
fn drop(&mut self) {
unsafe {
libc::sigaction(libc::SIGINT, &self.prev_action, std::ptr::null_mut());
}
PIPE_WRITE_FD.store(-1, Ordering::Release);
unsafe {
libc::close(self.write_fd);
}
if let Some(handle) = self.watcher.take() {
let _ = handle.join();
}
BRIDGE_ACTIVE.store(false, Ordering::Release);
if self.shared.eaten_unhandled.load(Ordering::Acquire) {
unsafe {
libc::raise(libc::SIGINT);
}
}
}
}
#[cfg(windows)]
impl Drop for InterruptBridge {
fn drop(&mut self) {
unsafe {
win::SetConsoleCtrlHandler(Some(bridge_ctrl_handler), 0);
}
BRIDGE_ACTIVE.store(false, Ordering::Release);
if self.shared.eaten_unhandled.load(Ordering::Acquire) {
unsafe {
win::GenerateConsoleCtrlEvent(win::CTRL_C_EVENT, 0);
}
}
}
}