ctrlc2/
lib.rs

1// Copyright (c) 2017 CtrlC developers
2// Licensed under the Apache License, Version 2.0
3// <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5// license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
6// at your option. All files in the project carrying such
7// notice may not be copied, modified, or distributed except
8// according to those terms.
9
10#![doc = include_str!("../README.md")]
11
12mod error;
13mod platform;
14pub use platform::Signal;
15mod signal;
16pub use signal::*;
17#[cfg(feature = "async")]
18mod r#async;
19#[cfg(feature = "async")]
20pub use r#async::AsyncCtrlC;
21
22pub use error::Error;
23use std::sync::Mutex;
24use std::sync::atomic::{AtomicBool, Ordering};
25
26static INIT: AtomicBool = AtomicBool::new(false);
27static INIT_LOCK: Mutex<()> = Mutex::new(());
28
29/// Register signal handler for Ctrl-C.
30///
31/// Starts a new dedicated signal handling thread. Should only be called once,
32/// typically at the start of your program.
33///
34/// The `user_handler` function is customizable and the return boolean value
35/// is indicating whether the user agreed terminate the program or not.
36///
37/// # Example
38/// ```no_run
39/// ctrlc2::set_handler(|| {println!("Hello world!"); true}).expect("Error setting Ctrl-C handler");
40/// ```
41///
42/// # Warning
43/// On Unix, the handler registration for `SIGINT`, (`SIGTERM` and `SIGHUP` if termination feature
44/// is enabled) or `SA_SIGINFO` posix signal handlers will be overwritten. On Windows, multiple
45/// handler routines are allowed, but they are called on a last-registered, first-called basis
46/// until the signal is handled.
47///
48/// ctrlc2::try_set_handler will error (on Unix) if another signal handler exists for the same
49/// signal(s) that ctrlc2 is trying to attach the handler to.
50///
51/// On Unix, signal dispositions and signal handlers are inherited by child processes created via
52/// `fork(2)` on, but not by child processes created via `execve(2)`.
53/// Signal handlers are not inherited on Windows.
54///
55/// # Errors
56/// Will return an error if a system error occurred while setting the handler.
57///
58/// # Panics
59/// Any panic in the handler will not be caught and will cause the signal handler thread to stop.
60pub fn set_handler<F>(user_handler: F) -> Result<std::thread::JoinHandle<()>, Error>
61where
62    F: FnMut() -> bool + 'static + Send,
63{
64    init_and_set_handler(user_handler, true)
65}
66
67/// The same as ctrlc2::set_handler but errors if a handler already exists for the signal(s).
68///
69/// The `user_handler` function is customizable and the return boolean value
70/// is indicating whether the user agreed terminate the program or not.
71///
72/// # Errors
73/// Will return an error if another handler exists or if a system error occurred while setting the
74/// handler.
75pub fn try_set_handler<F>(user_handler: F) -> Result<std::thread::JoinHandle<()>, Error>
76where
77    F: FnMut() -> bool + 'static + Send,
78{
79    init_and_set_handler(user_handler, false)
80}
81
82fn init_and_set_handler<F>(user_handler: F, overwrite: bool) -> Result<std::thread::JoinHandle<()>, Error>
83where
84    F: FnMut() -> bool + 'static + Send,
85{
86    if !INIT.load(Ordering::Acquire) {
87        let _guard = INIT_LOCK.lock().unwrap();
88
89        if !INIT.load(Ordering::Relaxed) {
90            let handle = set_handler_inner(user_handler, overwrite)?;
91            INIT.store(true, Ordering::Release);
92            return Ok(handle);
93        }
94    }
95
96    Err(Error::MultipleHandlers)
97}
98
99fn set_handler_inner<F>(mut user_handler: F, overwrite: bool) -> Result<std::thread::JoinHandle<()>, Error>
100where
101    F: FnMut() -> bool + 'static + Send,
102{
103    unsafe { platform::init_os_handler(overwrite)? };
104
105    let builder = std::thread::Builder::new()
106        .name("ctrl-c".into())
107        .spawn(move || {
108            loop {
109                unsafe { platform::block_ctrl_c() }.expect("Critical system error while waiting for Ctrl-C");
110
111                if user_handler() {
112                    break;
113                }
114            }
115        })
116        .map_err(Error::System)?;
117
118    Ok(builder)
119}
120
121/// Register signal handler in tokio runtime for Ctrl-C.
122#[cfg(feature = "tokio")]
123#[deprecated(
124    since = "3.6.4",
125    note = "Use 'async' feature instead. The 'tokio' feature is deprecated and will be removed in the future."
126)]
127pub async fn set_async_handler<F>(user_handler: F) -> tokio::task::JoinHandle<()>
128where
129    F: std::future::Future<Output = ()> + 'static + Send,
130{
131    tokio::spawn(async move {
132        let block = async move {
133            #[cfg(unix)]
134            {
135                #[cfg(not(feature = "termination"))]
136                tokio::signal::ctrl_c().await?;
137
138                #[cfg(feature = "termination")]
139                {
140                    use tokio::signal::unix::{SignalKind, signal};
141                    let mut kill_signal = signal(SignalKind::terminate())?;
142                    let mut int_signal = signal(SignalKind::interrupt())?;
143                    let mut hup_signal = signal(SignalKind::hangup())?;
144                    tokio::select! {
145                        _ = tokio::signal::ctrl_c() => {},
146                        _ = kill_signal.recv() => {},
147                        _ = int_signal.recv() => {},
148                        _ = hup_signal.recv() => {}
149                    }
150                }
151            }
152
153            #[cfg(windows)]
154            tokio::signal::ctrl_c().await?;
155
156            user_handler.await;
157
158            Ok::<(), std::io::Error>(())
159        };
160        if let Err(err) = block.await {
161            eprintln!("Critical system error while waiting for Ctrl-C: {err}");
162        }
163    })
164}