7
7
// notice may not be copied, modified, or distributed except
8
8
// according to those terms.
9
9
10
+ #![ warn( missing_docs) ]
11
+
10
12
//! A simple easy to use wrapper around Ctrl-C.
11
13
//! # Example
12
14
//! ```no_run
@@ -32,120 +34,121 @@ use std::thread;
32
34
33
35
static INIT : AtomicBool = ATOMIC_BOOL_INIT ;
34
36
37
+ /// Ctrl-C error.
35
38
#[ derive( Debug ) ]
36
39
pub enum Error {
37
- Init ( String ) ,
38
- MultipleHandlers ( String ) ,
39
- SetHandler ,
40
+ /// Ctrl-C signal handler already registered.
41
+ MultipleHandlers ,
42
+ /// Unexpected system error.
43
+ System ( std:: io:: Error ) ,
40
44
}
41
45
42
46
impl fmt:: Display for Error {
43
47
fn fmt ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
44
48
use std:: error:: Error ;
45
-
46
- write ! ( f, "CtrlC error: {}" , self . description( ) )
49
+ write ! ( f, "Ctrl-C error: {}" , self . description( ) )
47
50
}
48
51
}
49
52
50
53
impl std:: error:: Error for Error {
51
54
fn description ( & self ) -> & str {
52
55
match * self {
53
- Error :: Init ( ref msg) => & msg,
54
- Error :: MultipleHandlers ( ref msg) => & msg,
55
- Error :: SetHandler => "Error setting handler"
56
+ Error :: MultipleHandlers => "Ctrl-C signal handler already registered" ,
57
+ Error :: System ( _) => "Unexpected system error" ,
58
+ }
59
+ }
60
+
61
+ fn cause ( & self ) -> Option < & std:: error:: Error > {
62
+ match * self {
63
+ Error :: System ( ref e) => Some ( e) ,
64
+ _ => None ,
56
65
}
57
66
}
58
67
}
59
68
60
69
#[ cfg( unix) ]
61
70
mod platform {
62
- extern crate libc;
63
-
64
- use :: Error ;
65
- use self :: libc:: c_int;
66
- use self :: libc:: { signal, sighandler_t, SIGINT , SIG_ERR , EINTR } ;
67
-
68
- type PipeReadEnd = i32 ;
69
- type PipeWriteEnd = i32 ;
70
- pub static mut PIPE_FDS : ( PipeReadEnd , PipeWriteEnd ) = ( -1 , -1 ) ;
71
-
72
- pub use self :: libc:: { c_void, fcntl, FD_CLOEXEC , F_SETFD , pipe, read, write} ;
73
-
74
- #[ cfg( feature = "termination" ) ]
75
- use self :: libc:: SIGTERM ;
76
-
77
- extern "C" {
78
- #[ cfg_attr( any( target_os = "macos" ,
79
- target_os = "ios" ,
80
- target_os = "freebsd" ) ,
81
- link_name = "__error" ) ]
82
- #[ cfg_attr( target_os = "dragonfly" ,
83
- link_name = "__dfly_error" ) ]
84
- #[ cfg_attr( any( target_os = "openbsd" ,
85
- target_os = "bitrig" ,
86
- target_os = "android" ) ,
87
- link_name = "__errno" ) ]
88
- #[ cfg_attr( target_os = "linux" ,
89
- link_name = "__errno_location" ) ]
90
- fn errno_location ( ) -> * mut c_int ;
91
- }
71
+ extern crate nix;
92
72
93
- unsafe extern "C" fn os_handler ( _: c_int ) {
94
- // Assuming this always succeeds. Can't really handle errors in any meaningful way.
95
- write ( PIPE_FDS . 1 , & mut 0u8 as * mut _ as * mut c_void , 1 ) ;
96
- }
73
+ use super :: Error ;
74
+ use self :: nix:: unistd;
75
+ use self :: nix:: sys:: signal;
76
+ use std:: os:: unix:: io:: RawFd ;
77
+ use std:: io;
97
78
98
- #[ cfg( feature = "termination" ) ]
99
- #[ inline]
100
- unsafe fn set_os_handler ( handler : unsafe extern "C" fn ( c_int ) ) -> Result < ( ) , Error > {
101
- if signal ( SIGINT , :: std:: mem:: transmute :: < _ , sighandler_t > ( handler) ) == SIG_ERR {
102
- return Err ( Error :: SetHandler ) ;
103
- }
104
- if signal ( SIGTERM , :: std:: mem:: transmute :: < _ , sighandler_t > ( handler) ) == SIG_ERR {
105
- return Err ( Error :: SetHandler ) ;
106
- }
107
- Ok ( ( ) )
108
- }
79
+ static mut PIPE : ( RawFd , RawFd ) = ( -1 , -1 ) ;
109
80
110
- #[ cfg( not( feature = "termination" ) ) ]
111
- #[ inline]
112
- unsafe fn set_os_handler ( handler : unsafe extern "C" fn ( c_int ) ) -> Result < ( ) , Error > {
113
- if signal ( SIGINT , :: std:: mem:: transmute :: < _ , sighandler_t > ( handler) ) == SIG_ERR {
114
- return Err ( Error :: SetHandler ) ;
81
+ extern fn os_handler ( _: nix:: c_int ) {
82
+ // Assuming this always succeeds. Can't really handle errors in any meaningful way.
83
+ unsafe {
84
+ unistd:: write ( PIPE . 1 , & [ 0u8 ] ) . is_ok ( ) ;
115
85
}
116
- Ok ( ( ) )
117
86
}
118
87
119
- /// Register os signal handler, must be called before calling `block_ctrl_c()`.
120
- /// Should only be called once.
88
+ /// Register os signal handler.
89
+ ///
90
+ /// Must be called before calling [`block_ctrl_c()`](fn.block_ctrl_c.html)
91
+ /// and should only be called once.
92
+ ///
93
+ /// # Errors
94
+ /// Will return an error if a recoverable system error occur. An error should leave
95
+ /// the system in a constant state, where trying again might succeed.
96
+ ///
97
+ /// # Panics
98
+ /// Will panic if a non recoverable system error occur.
99
+ ///
121
100
#[ inline]
122
101
pub unsafe fn init_os_handler ( ) -> Result < ( ) , Error > {
123
- let mut fds = [ 0i32 , 0 ] ;
124
- let pipe_fds = fds. as_mut_ptr ( ) ;
125
- if pipe ( pipe_fds) == -1 {
126
- return Err ( Error :: Init ( format ! ( "pipe failed with error {}" , * errno_location( ) ) ) ) ;
127
- }
128
- PIPE_FDS = ( * pipe_fds. offset ( 0 ) , * pipe_fds. offset ( 1 ) ) ;
129
- if fcntl ( PIPE_FDS . 0 , F_SETFD , FD_CLOEXEC ) == -1 {
130
- return Err ( Error :: Init ( format ! ( "fcntl failed with error {}" , * errno_location( ) ) ) ) ;
131
- }
132
- if fcntl ( PIPE_FDS . 1 , F_SETFD , FD_CLOEXEC ) == -1 {
133
- return Err ( Error :: Init ( format ! ( "fcntl failed with error {}" , * errno_location( ) ) ) ) ;
134
- }
135
- set_os_handler ( os_handler)
102
+ // Should only fail if invalid parameters, FD limit or out of memory.
103
+ PIPE = unistd:: pipe2 ( nix:: fcntl:: O_CLOEXEC ) . map_err ( |e| Error :: System ( e. into ( ) ) ) ?;
104
+
105
+ let handler = signal:: SigHandler :: Handler ( os_handler) ;
106
+ let new_action = signal:: SigAction :: new ( handler,
107
+ signal:: SA_RESTART ,
108
+ signal:: SigSet :: empty ( )
109
+ ) ;
110
+
111
+ // Should only fail if invalid parameters,
112
+ signal:: sigaction ( signal:: Signal :: SIGINT , & new_action) . unwrap ( ) ;
113
+
114
+ #[ cfg( feature = "termination" ) ]
115
+ signal:: sigaction ( signal:: Signal :: SIGTERM , & new_action) . unwrap ( ) ;
116
+
117
+ // TODO: Maybe throw an error if old action is not SigDfl.
118
+ // Inspecting a SigAction is currently not possible with nix.
119
+
120
+ Ok ( ( ) )
136
121
}
137
122
138
123
/// Blocks until a Ctrl-C signal is received.
124
+ ///
125
+ /// Must be called after calling [`init_os_handler()`](fn.init_os_handler.html).
126
+ ///
127
+ /// # Errors
128
+ /// Will return an error if a system error occurs.
129
+ ///
139
130
#[ inline]
140
- pub unsafe fn block_ctrl_c ( ) {
141
- let mut buf = 0u8 ;
131
+ pub unsafe fn block_ctrl_c ( ) -> Result < ( ) , Error > {
132
+ let mut buf = [ 0u8 ] ;
133
+
142
134
loop {
143
- if read ( PIPE_FDS . 0 , & mut buf as * mut _ as * mut c_void , 1 ) == -1 {
144
- assert_eq ! ( * errno_location( ) , EINTR ) ;
145
- } else {
146
- break ;
135
+ // TODO: Can we safely convert the pipe fd into a std::io::Read reader
136
+ // with std::os::unix::io::FromRawFd, this would handle EINTR
137
+ // and everything for us.
138
+ match unistd:: read ( PIPE . 0 , & mut buf[ ..] ) {
139
+ Ok ( 1 ) => break ,
140
+ Ok ( _) => return Err ( Error :: System ( io:: ErrorKind :: UnexpectedEof . into ( ) ) . into ( ) ) ,
141
+ Err ( nix:: Error :: Sys ( nix:: Errno :: EINTR ) ) => { } ,
142
+ Err ( e) => return Err ( Error :: System ( e. into ( ) ) ) ,
147
143
}
148
144
}
145
+
146
+ Ok ( ( ) )
147
+ }
148
+
149
+ #[ cfg( test) ]
150
+ pub fn raise_ctrl_c ( ) {
151
+ signal:: raise ( signal:: Signal :: SIGINT ) . unwrap ( ) ;
149
152
}
150
153
}
151
154
@@ -154,65 +157,122 @@ mod platform {
154
157
extern crate winapi;
155
158
extern crate kernel32;
156
159
157
- use :: Error ;
158
- use self :: kernel32:: { SetConsoleCtrlHandler , CreateSemaphoreA , ReleaseSemaphore ,
159
- WaitForSingleObject } ;
160
- use self :: winapi:: { HANDLE , BOOL , DWORD , TRUE , FALSE , INFINITE , WAIT_OBJECT_0 , c_long} ;
161
-
160
+ use super :: Error ;
161
+ use self :: winapi:: { HANDLE , BOOL , DWORD , TRUE , FALSE , c_long} ;
162
162
use std:: ptr;
163
+ use std:: io;
163
164
164
165
const MAX_SEM_COUNT : c_long = 255 ;
165
166
static mut SEMAPHORE : HANDLE = 0 as HANDLE ;
166
167
167
168
unsafe extern "system" fn os_handler ( _: DWORD ) -> BOOL {
168
- // ReleaseSemaphore() should only fail when the semaphore
169
- // counter has reached its maximum value or if the semaphore
170
- // is invalid, we can therefore safely ignore return value.
171
- ReleaseSemaphore ( SEMAPHORE , 1 , ptr:: null_mut ( ) ) ;
169
+ // Assuming this always succeeds. Can't really handle errors in any meaningful way.
170
+ self :: kernel32:: ReleaseSemaphore ( SEMAPHORE , 1 , ptr:: null_mut ( ) ) ;
172
171
TRUE
173
172
}
174
173
175
- /// Register os signal handler, must be called before calling block_ctrl_c().
176
- /// Should only be called once.
174
+ /// Register os signal handler.
175
+ ///
176
+ /// Must be called before calling [`block_ctrl_c()`](fn.block_ctrl_c.html)
177
+ /// and should only be called once.
178
+ ///
179
+ /// # Errors
180
+ /// Will return an error if a recoverable system error occur. An error should leave
181
+ /// the system in a constant state, where trying again might succeed.
182
+ ///
183
+ /// # Panics
184
+ /// Will panic if a non recoverable system error occur.
185
+ ///
177
186
#[ inline]
178
187
pub unsafe fn init_os_handler ( ) -> Result < ( ) , Error > {
179
- SEMAPHORE = CreateSemaphoreA ( ptr:: null_mut ( ) , 0 , MAX_SEM_COUNT , ptr:: null ( ) ) ;
188
+ SEMAPHORE = self :: kernel32 :: CreateSemaphoreA ( ptr:: null_mut ( ) , 0 , MAX_SEM_COUNT , ptr:: null ( ) ) ;
180
189
if SEMAPHORE . is_null ( ) {
181
- return Err ( Error :: Init ( "CreateSemaphoreA failed" . into ( ) ) ) ;
190
+ return Err ( Error :: System ( io :: Error :: last_os_error ( ) ) ) ;
182
191
}
183
- if SetConsoleCtrlHandler ( Some ( os_handler) , TRUE ) == FALSE {
184
- return Err ( Error :: SetHandler ) ;
192
+
193
+ if self :: kernel32:: SetConsoleCtrlHandler ( Some ( os_handler) , TRUE ) == FALSE {
194
+ let e = io:: Error :: last_os_error ( ) ;
195
+ self :: kernel32:: CloseHandle ( SEMAPHORE ) ;
196
+ SEMAPHORE = 0 as HANDLE ;
197
+ return Err ( Error :: System ( e) ) ;
185
198
}
199
+
186
200
Ok ( ( ) )
187
201
}
188
202
189
203
/// Blocks until a Ctrl-C signal is received.
204
+ ///
205
+ /// Must be called after calling [`init_os_handler()`](fn.init_os_handler.html).
206
+ ///
207
+ /// # Errors
208
+ /// Will return an error if a system error occurs.
209
+ ///
190
210
#[ inline]
191
- pub unsafe fn block_ctrl_c ( ) {
192
- assert_eq ! ( WaitForSingleObject ( SEMAPHORE , INFINITE ) , WAIT_OBJECT_0 ) ;
211
+ pub unsafe fn block_ctrl_c ( ) -> Result < ( ) , Error > {
212
+ use self :: winapi:: { INFINITE , WAIT_OBJECT_0 , WAIT_FAILED } ;
213
+
214
+ match self :: kernel32:: WaitForSingleObject ( SEMAPHORE , INFINITE ) {
215
+ WAIT_OBJECT_0 => Ok ( ( ) ) ,
216
+ WAIT_FAILED => Err ( Error :: System ( io:: Error :: last_os_error ( ) ) ) ,
217
+ ret => Err ( Error :: System ( io:: Error :: new (
218
+ io:: ErrorKind :: Other ,
219
+ format ! ( "WaitForSingleObject(), unexpected return value \" {:x}\" " , ret) ,
220
+ ) ) ) ,
221
+ }
222
+ }
223
+
224
+ #[ cfg( test) ]
225
+ pub fn raise_ctrl_c ( ) {
226
+ unsafe {
227
+ // This will signal the whole process group.
228
+ assert ! ( self :: kernel32:: GenerateConsoleCtrlEvent ( self :: winapi:: CTRL_C_EVENT , 0 ) != 0 ) ;
229
+ }
193
230
}
194
231
}
195
232
196
233
/// Sets up the signal handler for Ctrl-C.
234
+ ///
197
235
/// # Example
198
236
/// ```
199
237
/// ctrlc::set_handler(|| println!("Hello world!")).expect("Error setting Ctrl-C handler");
200
238
/// ```
239
+ ///
240
+ /// # Warning
241
+ /// On Unix, any existing SIGINT, SIGTERM(if termination feature is enabled) or SA_SIGINFO posix
242
+ /// signal handlers will be overwritten. On Windows, multiple CTRL+C handlers are allowed, but only
243
+ /// the first one will be executed.
244
+ ///
245
+ /// # Errors
246
+ /// Will return an error if another `ctrlc::set_handler()` handler exists or if a recoverable
247
+ /// system error occur while setting the handler. An error should leave the system in a
248
+ /// constant state, where trying again might succeed.
249
+ ///
250
+ /// # Panics
251
+ /// Will panic if a non recoverable system error occur while setting the handler.
252
+ /// The handler runs on its own thread, any panics in handler or other system errors
253
+ /// on this thread will not be caught, leading to thread being brought down.
254
+ ///
201
255
pub fn set_handler < F > ( user_handler : F ) -> Result < ( ) , Error >
202
256
where F : Fn ( ) -> ( ) + ' static + Send
203
257
{
204
- if INIT . swap ( true , Ordering :: SeqCst ) != false {
205
- return Err ( Error :: MultipleHandlers ( "Ctrl-C signal handler already registered" . into ( ) ) ) ;
258
+ if INIT . compare_and_swap ( false , true , Ordering :: SeqCst ) {
259
+ return Err ( Error :: MultipleHandlers ) ;
206
260
}
207
261
208
262
unsafe {
209
- try!( platform:: init_os_handler ( ) ) ;
263
+ match platform:: init_os_handler ( ) {
264
+ Ok ( _) => { } ,
265
+ err => {
266
+ INIT . store ( false , Ordering :: SeqCst ) ;
267
+ return err;
268
+ } ,
269
+ }
210
270
}
211
271
212
272
thread:: spawn ( move || {
213
273
loop {
214
274
unsafe {
215
- platform:: block_ctrl_c ( ) ;
275
+ platform:: block_ctrl_c ( ) . expect ( "Critical system error while waiting for Ctrl-C" ) ;
216
276
}
217
277
user_handler ( ) ;
218
278
}
@@ -221,8 +281,19 @@ pub fn set_handler<F>(user_handler: F) -> Result<(), Error>
221
281
Ok ( ( ) )
222
282
}
223
283
224
- #[ test]
225
- fn test_multiple_handlers ( ) {
226
- assert ! ( set_handler( || { } ) . is_ok( ) ) ;
227
- assert ! ( set_handler( || { } ) . is_err( ) ) ;
284
+ #[ cfg( test) ]
285
+ mod tests {
286
+ #[ test]
287
+ fn test_set_handler ( ) {
288
+ let ( tx, rx) = :: std:: sync:: mpsc:: channel ( ) ;
289
+ super :: set_handler ( move || {
290
+ tx. send ( true ) . unwrap ( ) ;
291
+ } ) . unwrap ( ) ;
292
+
293
+ super :: platform:: raise_ctrl_c ( ) ;
294
+
295
+ rx. recv_timeout ( :: std:: time:: Duration :: from_secs ( 1 ) ) . unwrap ( ) ;
296
+
297
+ assert ! ( super :: set_handler( || { } ) . is_err( ) ) ;
298
+ }
228
299
}
0 commit comments