use std::{
io,
sync::{
Mutex, OnceLock,
atomic::{AtomicI32, Ordering},
},
};
use super::{claim::live, table::unmount_under};
const CAUGHT: [libc::c_int; 4] = [libc::SIGINT, libc::SIGTERM, libc::SIGHUP, libc::SIGQUIT];
static WAKE: AtomicI32 = AtomicI32::new(-1);
pub fn unmount_on_signal() -> io::Result<()> {
static INSTALLED: OnceLock<io::Result<()>> = OnceLock::new();
match INSTALLED.get_or_init(install) {
Ok(()) => Ok(()),
Err(err) => Err(io::Error::new(err.kind(), err.to_string())),
}
}
fn install() -> io::Result<()> {
let mut fds = [0 as libc::c_int; 2];
if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
return Err(io::Error::last_os_error());
}
let [read, write] = fds;
for fd in [read, write] {
unsafe { libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) };
}
std::thread::Builder::new()
.name("virtx-unmount-on-signal".into())
.spawn(move || watch(read))?;
WAKE.store(write, Ordering::Relaxed);
for signal in CAUGHT {
let mut action: libc::sigaction = unsafe { std::mem::zeroed() };
action.sa_sigaction = poke as *const () as usize;
action.sa_flags = libc::SA_RESTART;
unsafe { libc::sigemptyset(&mut action.sa_mask) };
let mut was: libc::sigaction = unsafe { std::mem::zeroed() };
if unsafe { libc::sigaction(signal, &action, &mut was) } != 0 {
return Err(io::Error::last_os_error());
}
if let Ok(mut saved) = PREVIOUS.lock() {
saved.push((signal, Disposition(was)));
}
}
Ok(())
}
struct Disposition(libc::sigaction);
unsafe impl Send for Disposition {}
static PREVIOUS: Mutex<Vec<(libc::c_int, Disposition)>> = Mutex::new(Vec::new());
extern "C" fn poke(signal: libc::c_int) {
let fd = WAKE.load(Ordering::Relaxed);
if fd < 0 {
return;
}
let byte = signal as u8;
unsafe { libc::write(fd, (&raw const byte).cast(), 1) };
}
fn watch(read: libc::c_int) -> ! {
let mut byte = 0u8;
let signal = loop {
let n = unsafe { libc::read(read, (&raw mut byte).cast(), 1) };
if n == 1 {
break byte as libc::c_int;
}
if n < 0 && io::Error::last_os_error().kind() == io::ErrorKind::Interrupted {
continue;
}
loop {
std::thread::park();
}
};
for mountpoint in live() {
if !unmount_under(&mountpoint) {
eprintln!(
"virtx: {} did not come down on signal {signal} — unmount it by hand",
mountpoint.display()
);
}
}
if let Ok(saved) = PREVIOUS.lock() {
for (signal, disposition) in saved.iter() {
unsafe { libc::sigaction(*signal, &disposition.0, std::ptr::null_mut()) };
}
}
unsafe { libc::raise(signal) };
loop {
std::thread::park();
}
}