Skip to content

Commit b87d472

Browse files
authored
Merge pull request #53 from meldafert/pass-on-sigint
Handle SIGINT gracefully
2 parents db0be8e + f0dcd85 commit b87d472

File tree

3 files changed

+61
-26
lines changed

3 files changed

+61
-26
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ zip = "0.5"
4242
maplit = "1"
4343
webbrowser = "0.5"
4444
basic_tcp_proxy = "0.2"
45-
ctrlc = "3"
45+
signal-hook = "0.2"
4646

4747
[package.metadata.rpm]
4848
package = "vopono"

src/exec.rs

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,15 @@ use super::util::{get_existing_namespaces, get_target_subnet};
1212
use super::vpn::{verify_auth, Protocol};
1313
use anyhow::{anyhow, bail};
1414
use log::{debug, error, info, warn};
15+
use signal_hook::{iterator::Signals, SIGINT};
1516
use std::io::{self, Write};
1617
use std::net::{IpAddr, Ipv4Addr};
1718

1819
pub fn exec(command: ExecCommand) -> anyhow::Result<()> {
20+
// this captures all sigint signals
21+
// ignore for now, they are automatically passed on to the child
22+
let signals = Signals::new(&[SIGINT])?;
23+
1924
let provider: VpnProvider;
2025
let server_name: String;
2126
let protocol: Protocol;
@@ -276,37 +281,50 @@ pub fn exec(command: ExecCommand) -> anyhow::Result<()> {
276281
"Process {} still running, assumed to be daemon - will leave network namespace alive until ctrl+C received",
277282
pid
278283
);
279-
stay_alive(pid)?;
284+
stay_alive(Some(pid), signals)?;
280285
} else if command.keep_alive {
281286
info!("Keep-alive flag active - will leave network namespace alive until ctrl+C received");
282-
stay_alive(pid)?;
287+
stay_alive(None, signals)?;
283288
}
284289

285290
Ok(())
286291
}
287292

288293
// Block waiting for SIGINT
289-
fn stay_alive(pid: u32) -> anyhow::Result<()> {
290-
let recv = ctrl_channel(pid);
291-
recv?.recv().unwrap();
292-
Ok(())
293-
}
294-
295-
// Handle waiting for SIGINT
296-
fn ctrl_channel(pid: u32) -> Result<std::sync::mpsc::Receiver<()>, ctrlc::Error> {
294+
fn stay_alive(pid: Option<u32>, mut signals: Signals) -> anyhow::Result<()> {
297295
let (sender, receiver) = std::sync::mpsc::channel();
298-
ctrlc::set_handler(move || {
299-
let _ = sender.send(());
300-
info!(
301-
"SIGINT received, killing process {} and terminating...",
302-
pid
303-
);
304-
nix::sys::signal::kill(
305-
nix::unistd::Pid::from_raw(pid as i32),
306-
nix::sys::signal::Signal::SIGKILL,
307-
)
308-
.ok();
309-
})?;
310296

311-
Ok(receiver)
297+
// discard old signals
298+
for _old in signals.pending() {
299+
// pass, just empty the iterator
300+
}
301+
302+
let handle = signals.handle();
303+
304+
let thread = std::thread::spawn(move || {
305+
for _sig in signals.forever() {
306+
if let Some(pid) = pid {
307+
info!(
308+
"SIGINT received, killing process {} and terminating...",
309+
pid
310+
);
311+
nix::sys::signal::kill(
312+
nix::unistd::Pid::from_raw(pid as i32),
313+
nix::sys::signal::Signal::SIGKILL,
314+
)
315+
.ok();
316+
} else {
317+
info!("SIGINT received, terminating...",);
318+
}
319+
let _ = sender.send(());
320+
}
321+
});
322+
323+
// this blocks until sender sends, so until sigint is received
324+
receiver.recv().unwrap();
325+
326+
handle.close();
327+
thread.join().unwrap();
328+
329+
Ok(())
312330
}

src/util/mod.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,14 +248,31 @@ pub fn clean_dead_namespaces() -> anyhow::Result<()> {
248248
}
249249

250250
pub fn elevate_privileges() -> anyhow::Result<()> {
251+
use signal_hook::{cleanup, flag, SIGINT};
252+
use std::sync::atomic::{AtomicBool, Ordering};
253+
use std::sync::Arc;
254+
251255
// Check if already running as root
252256
if nix::unistd::getuid().as_raw() != 0 {
253257
info!("Calling sudo for elevated privileges, current user will be used as default user");
254258
let args: Vec<String> = std::env::args().collect();
255259

260+
let terminated = Arc::new(AtomicBool::new(false));
261+
flag::register(SIGINT, Arc::clone(&terminated))?;
262+
256263
debug!("Args: {:?}", &args);
257-
Command::new("sudo").arg("-E").args(args).status()?;
258-
// Do we want to block here to ensure stdout kept alive? Does it matter?
264+
// status blocks until the process has ended
265+
let _status = Command::new("sudo").arg("-E").args(args).status()?;
266+
267+
cleanup::cleanup_signal(SIGINT)?;
268+
269+
if terminated.load(Ordering::SeqCst) {
270+
// we received a sigint,
271+
// so we want to pass it on by terminating with a sigint
272+
nix::sys::signal::kill(nix::unistd::getpid(), nix::sys::signal::Signal::SIGINT)
273+
.expect("failed to send SIGINT");
274+
}
275+
259276
std::process::exit(0);
260277
} else if std::env::var("SUDO_USER").is_err() {
261278
warn!("Running vopono as root user directly!");

0 commit comments

Comments
 (0)