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#![warn(missing_docs)]
11
12//! Cross platform handling of Ctrl-C signals.
13//!
14//! [HandlerRoutine]:https://msdn.microsoft.com/en-us/library/windows/desktop/ms683242.aspx
15//!
16//! [set_handler()](fn.set_handler.html) allows setting a handler closure which is executed on
17//! `Ctrl+C`. On Unix, this corresponds to a `SIGINT` signal. On windows, `Ctrl+C` corresponds to
18//! [`CTRL_C_EVENT`][HandlerRoutine] or [`CTRL_BREAK_EVENT`][HandlerRoutine].
19//!
20//! Setting a handler will start a new dedicated signal handling thread where we
21//! execute the handler each time we receive a `Ctrl+C` signal. There can only be
22//! one handler, you would typically set one at the start of your program.
23//!
24//! # Example
25//! ```no_run
26//! # #[allow(clippy::needless_doctest_main)]
27//! use std::sync::atomic::{AtomicBool, Ordering};
28//! use std::sync::Arc;
29//!
30//! fn main() {
31//! let running = Arc::new(AtomicBool::new(true));
32//! let r = running.clone();
33//!
34//! ctrlc::set_handler(move || {
35//! r.store(false, Ordering::SeqCst);
36//! }).expect("Error setting Ctrl-C handler");
37//!
38//! println!("Waiting for Ctrl-C...");
39//! while running.load(Ordering::SeqCst) {}
40//! println!("Got it! Exiting...");
41//! }
42//! ```
43//!
44//! # Handling SIGTERM and SIGHUP
45//! Handling of `SIGTERM and SIGHUP` can be enabled with `termination` feature. If this is enabled,
46//! the handler specified by `set_handler()` will be executed for `SIGINT`, `SIGTERM` and `SIGHUP`.
47//!
48
49#[macro_use]
50
51mod error;
52mod platform;
53pub use platform::Signal;
54mod signal;
55pub use signal::*;
56
57pub use error::Error;
58use std::sync::atomic::{AtomicBool, Ordering};
59use std::sync::Mutex;
60use std::thread;
61
62static INIT: AtomicBool = AtomicBool::new(false);
63static INIT_LOCK: Mutex<()> = Mutex::new(());
64
65/// Register signal handler for Ctrl-C.
66///
67/// Starts a new dedicated signal handling thread. Should only be called once,
68/// typically at the start of your program.
69///
70/// # Example
71/// ```no_run
72/// ctrlc::set_handler(|| println!("Hello world!")).expect("Error setting Ctrl-C handler");
73/// ```
74///
75/// # Warning
76/// On Unix, the handler registration for `SIGINT`, (`SIGTERM` and `SIGHUP` if termination feature
77/// is enabled) or `SA_SIGINFO` posix signal handlers will be overwritten. On Windows, multiple
78/// handler routines are allowed, but they are called on a last-registered, first-called basis
79/// until the signal is handled.
80///
81/// ctrlc::try_set_handler will error (on Unix) if another signal handler exists for the same
82/// signal(s) that ctrlc is trying to attach the handler to.
83///
84/// On Unix, signal dispositions and signal handlers are inherited by child processes created via
85/// `fork(2)` on, but not by child processes created via `execve(2)`.
86/// Signal handlers are not inherited on Windows.
87///
88/// # Errors
89/// Will return an error if a system error occurred while setting the handler.
90///
91/// # Panics
92/// Any panic in the handler will not be caught and will cause the signal handler thread to stop.
93pub fn set_handler<F>(user_handler: F) -> Result<(), Error>
94where
95 F: FnMut() + 'static + Send,
96{
97 init_and_set_handler(user_handler, overwrite:true)
98}
99
100/// The same as ctrlc::set_handler but errors if a handler already exists for the signal(s).
101///
102/// # Errors
103/// Will return an error if another handler exists or if a system error occurred while setting the
104/// handler.
105pub fn try_set_handler<F>(user_handler: F) -> Result<(), Error>
106where
107 F: FnMut() + 'static + Send,
108{
109 init_and_set_handler(user_handler, overwrite:false)
110}
111
112fn init_and_set_handler<F>(user_handler: F, overwrite: bool) -> Result<(), Error>
113where
114 F: FnMut() + 'static + Send,
115{
116 if !INIT.load(order:Ordering::Acquire) {
117 let _guard: MutexGuard<'_, ()> = INIT_LOCK.lock().unwrap();
118
119 if !INIT.load(order:Ordering::Relaxed) {
120 set_handler_inner(user_handler, overwrite)?;
121 INIT.store(val:true, order:Ordering::Release);
122 return Ok(());
123 }
124 }
125
126 Err(Error::MultipleHandlers)
127}
128
129fn set_handler_inner<F>(mut user_handler: F, overwrite: bool) -> Result<(), Error>
130where
131 F: FnMut() + 'static + Send,
132{
133 unsafe {
134 match platform::init_os_handler(overwrite) {
135 Ok(_) => {}
136 Err(err: Errno) => {
137 return Err(err.into());
138 }
139 }
140 }
141
142 thread::Builder::new()
143 .name("ctrl-c".into())
144 .spawn(move || loop {
145 unsafe {
146 platform::block_ctrl_c().expect("Critical system error while waiting for Ctrl-C");
147 }
148 user_handler();
149 })
150 .expect(msg:"failed to spawn thread");
151
152 Ok(())
153}
154