Files
minivless/src-tauri/src/guardian.rs
T

105 lines
3.3 KiB
Rust

//! A pipe-lifetime guardian is necessary: exec of a capability-bearing binary clears
//! Linux PR_SET_PDEATHSIG. Closing the GUI's pipe (even after SIGKILL) stops the core.
use std::{
io::Read,
path::Path,
process::{Child, Command, Stdio},
sync::{
atomic::{AtomicBool, Ordering},
mpsc, Arc,
},
time::{Duration, Instant},
};
pub fn terminate(child: &mut Child) {
if matches!(child.try_wait(), Ok(Some(_))) {
return;
}
// Child remains unreaped until wait below, so its PID cannot be reused here.
unsafe {
libc::kill(child.id() as i32, libc::SIGTERM);
}
let deadline = Instant::now() + Duration::from_secs(3);
while Instant::now() < deadline {
if matches!(child.try_wait(), Ok(Some(_))) {
return;
}
std::thread::sleep(Duration::from_millis(30));
}
let _ = child.kill();
let _ = child.wait();
}
pub fn run(binary: &Path, config: &Path) -> i32 {
let stopping = Arc::new(AtomicBool::new(false));
for signal in [libc::SIGTERM, libc::SIGINT, libc::SIGHUP] {
if signal_hook::flag::register(signal, stopping.clone()).is_err() {
return 1;
}
}
// The separate lock is also held by the guardian during GUI-crash cleanup.
let lock_dir = config.parent().and_then(Path::parent);
let _lock = match lock_dir.and_then(|dir| crate::tunnel::core_lock(dir).ok()) {
Some(lock) => lock,
None => {
eprintln!("Another tunnel is still running or stopping");
return 1;
}
};
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || {
let mut byte = [0; 1];
let _ = std::io::stdin().read(&mut byte);
let _ = tx.send(());
});
let mut child = match Command::new(binary)
.args(["run", "--disable-color", "-c"])
// Pinned sing-tun uses PATH to find resolvectl. DNS interception is native
// nftables; disable external helpers so it never changes resolved settings
// or asks Polkit for additional privileges. All core I/O is in-process.
.env("PATH", "")
.arg(config)
.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
{
Ok(child) => child,
Err(_) => {
eprintln!("Cannot start sing-box");
cleanup(config);
return 1;
}
};
let code = loop {
if stopping.load(Ordering::Relaxed) || rx.try_recv().is_ok() {
terminate(&mut child);
break 0;
}
match child.try_wait() {
Ok(Some(s)) => break s.code().unwrap_or(1),
Err(_) => {
terminate(&mut child);
break 1;
}
_ => {}
}
std::thread::sleep(Duration::from_millis(30));
};
cleanup(config);
code
}
pub fn cleanup(config: &Path) {
// Only files created by our runtime writer are eligible for deletion.
if config.file_name().is_some_and(|s| s == "config.json") {
if let Some(dir) = config.parent().filter(|p| {
p.file_name()
.is_some_and(|n| n.to_string_lossy().starts_with("runtime-"))
}) {
let _ = std::fs::remove_file(config);
let _ = std::fs::remove_dir(dir);
}
}
}