105 lines
3.3 KiB
Rust
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);
|
|
}
|
|
}
|
|
}
|