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 | |
51 | mod error; |
52 | mod platform; |
53 | pub use platform::Signal; |
54 | mod signal; |
55 | pub use signal::*; |
56 | |
57 | pub use error::Error; |
58 | use std::sync::atomic::{AtomicBool, Ordering}; |
59 | use std::sync::Mutex; |
60 | use std::thread; |
61 | |
62 | static INIT: AtomicBool = AtomicBool::new(false); |
63 | static 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. |
93 | pub fn set_handler<F>(user_handler: F) -> Result<(), Error> |
94 | where |
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. |
105 | pub fn try_set_handler<F>(user_handler: F) -> Result<(), Error> |
106 | where |
107 | F: FnMut() + 'static + Send, |
108 | { |
109 | init_and_set_handler(user_handler, overwrite:false) |
110 | } |
111 | |
112 | fn init_and_set_handler<F>(user_handler: F, overwrite: bool) -> Result<(), Error> |
113 | where |
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 | |
129 | fn set_handler_inner<F>(mut user_handler: F, overwrite: bool) -> Result<(), Error> |
130 | where |
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 | |