1
+ // Copyright (c) 2015 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
+ extern crate ctrlc;
11
+
12
+ #[ cfg( unix) ]
13
+ mod platform {
14
+ extern crate nix;
15
+
16
+ use :: std:: io;
17
+
18
+ pub unsafe fn setup ( ) -> io:: Result < ( ) > {
19
+ Ok ( ( ) )
20
+ }
21
+
22
+ pub unsafe fn cleanup ( ) -> io:: Result < ( ) > {
23
+ Ok ( ( ) )
24
+ }
25
+
26
+ pub unsafe fn raise_ctrl_c ( ) {
27
+ self :: nix:: sys:: signal:: raise ( self :: nix:: sys:: signal:: SIGINT ) . unwrap ( ) ;
28
+ }
29
+
30
+ pub unsafe fn print ( fmt : :: std:: fmt:: Arguments ) {
31
+ use self :: io:: Write ;
32
+ let stdout = :: std:: io:: stdout ( ) ;
33
+ stdout. lock ( ) . write_fmt ( fmt) . unwrap ( ) ;
34
+ }
35
+ }
36
+
37
+ #[ cfg( windows) ]
38
+ mod platform {
39
+ extern crate winapi;
40
+ extern crate kernel32;
41
+
42
+ use std:: io;
43
+ use std:: ptr;
44
+ use self :: winapi:: winnt:: { CHAR , HANDLE } ;
45
+ use self :: winapi:: minwindef:: DWORD ;
46
+
47
+ /// Stores a piped stdout handle or a cache that gets flushed when we reattached to the old console.
48
+ enum Output {
49
+ Pipe ( HANDLE ) ,
50
+ Cached ( Vec < u8 > ) ,
51
+ }
52
+
53
+ static mut OLD_OUT : * mut Output = 0 as * mut Output ;
54
+
55
+ impl io:: Write for Output {
56
+ fn write ( & mut self , buf : & [ u8 ] ) -> io:: Result < usize > {
57
+ match * self {
58
+ Output :: Pipe ( handle) => unsafe {
59
+ use self :: winapi:: winnt:: VOID ;
60
+
61
+ let mut n = 0u32 ;
62
+ if self :: kernel32:: WriteFile (
63
+ handle,
64
+ buf. as_ptr ( ) as * const VOID ,
65
+ buf. len ( ) as DWORD ,
66
+ & mut n as * mut DWORD ,
67
+ ptr:: null_mut ( )
68
+ ) == 0 {
69
+ Err ( io:: Error :: last_os_error ( ) )
70
+ } else {
71
+ Ok ( n as usize )
72
+ }
73
+ } ,
74
+ Output :: Cached ( ref mut s) => s. write ( buf) ,
75
+ }
76
+ }
77
+
78
+ fn flush ( & mut self ) -> io:: Result < ( ) > {
79
+ Ok ( ( ) )
80
+ }
81
+ }
82
+
83
+ impl Output {
84
+ /// Stores current piped stdout or creates a new output cache that will
85
+ /// be written to stdout at a later time.
86
+ fn new ( ) -> io:: Result < Output > {
87
+ use self :: winapi:: shlobj:: INVALID_HANDLE_VALUE ;
88
+
89
+ unsafe {
90
+ let stdout = self :: kernel32:: GetStdHandle ( winapi:: STD_OUTPUT_HANDLE ) ;
91
+ if stdout. is_null ( ) || stdout == INVALID_HANDLE_VALUE {
92
+ return Err ( io:: Error :: last_os_error ( ) ) ;
93
+ }
94
+
95
+ let mut out = 0u32 ;
96
+ match self :: kernel32:: GetConsoleMode ( stdout, & mut out as * mut DWORD ) {
97
+ 0 => Ok ( Output :: Pipe ( stdout) ) ,
98
+ _ => Ok ( Output :: Cached ( Vec :: new ( ) ) ) ,
99
+ }
100
+ }
101
+ }
102
+
103
+ /// Set stdout/stderr and flush cache.
104
+ unsafe fn set_as_std ( self ) -> io:: Result < ( ) > {
105
+ let stdout = match self {
106
+ Output :: Pipe ( h) => h,
107
+ Output :: Cached ( _) => get_stdout ( ) ?,
108
+ } ;
109
+
110
+ if self :: kernel32:: SetStdHandle ( winapi:: STD_OUTPUT_HANDLE , stdout) == 0 {
111
+ return Err ( io:: Error :: last_os_error ( ) ) ;
112
+ }
113
+
114
+ if self :: kernel32:: SetStdHandle ( winapi:: STD_ERROR_HANDLE , stdout) == 0 {
115
+ return Err ( io:: Error :: last_os_error ( ) ) ;
116
+ }
117
+
118
+ match self {
119
+ Output :: Pipe ( _) => Ok ( ( ) ) ,
120
+ Output :: Cached ( ref s) => {
121
+ // Write cached output
122
+ use self :: io:: Write ;
123
+ let out = io:: stdout ( ) ;
124
+ out. lock ( ) . write_all ( & s[ ..] ) ?;
125
+ Ok ( ( ) )
126
+ }
127
+ }
128
+ }
129
+ }
130
+
131
+ unsafe fn get_stdout ( ) -> io:: Result < HANDLE > {
132
+ use self :: winapi:: winnt:: { GENERIC_READ , GENERIC_WRITE , FILE_SHARE_WRITE } ;
133
+ use self :: winapi:: shlobj:: INVALID_HANDLE_VALUE ;
134
+ use self :: winapi:: fileapi:: OPEN_EXISTING ;
135
+
136
+ let stdout = self :: kernel32:: CreateFileA (
137
+ "CONOUT$\0 " . as_ptr ( ) as * const CHAR ,
138
+ GENERIC_READ | GENERIC_WRITE ,
139
+ FILE_SHARE_WRITE ,
140
+ ptr:: null_mut ( ) ,
141
+ OPEN_EXISTING ,
142
+ 0 ,
143
+ ptr:: null_mut ( )
144
+ ) ;
145
+
146
+ if stdout. is_null ( ) || stdout == INVALID_HANDLE_VALUE {
147
+ Err ( io:: Error :: last_os_error ( ) )
148
+ } else {
149
+ Ok ( stdout)
150
+ }
151
+ }
152
+
153
+ /// Detach from the current console and create a new one,
154
+ /// We do this because GenerateConsoleCtrlEvent() sends ctrl-c events
155
+ /// to all processes on the same console. We want events to be received
156
+ /// only by our process.
157
+ ///
158
+ /// This breaks rust's stdout pre 1.18.0. Rust used to
159
+ /// [cache the std handles](https://github.com/rust-lang/rust/pull/40516)
160
+ ///
161
+ pub unsafe fn setup ( ) -> io:: Result < ( ) > {
162
+ let old_out = Output :: new ( ) ?;
163
+
164
+ if self :: kernel32:: FreeConsole ( ) == 0 {
165
+ return Err ( io:: Error :: last_os_error ( ) ) ;
166
+ }
167
+
168
+ if self :: kernel32:: AllocConsole ( ) == 0 {
169
+ return Err ( io:: Error :: last_os_error ( ) ) ;
170
+ }
171
+
172
+ // AllocConsole will not always set stdout/stderr to the to the console buffer
173
+ // of the new terminal.
174
+
175
+ let stdout = get_stdout ( ) ?;
176
+ if self :: kernel32:: SetStdHandle ( winapi:: STD_OUTPUT_HANDLE , stdout) == 0 {
177
+ return Err ( io:: Error :: last_os_error ( ) ) ;
178
+ }
179
+
180
+ if self :: kernel32:: SetStdHandle ( winapi:: STD_ERROR_HANDLE , stdout) == 0 {
181
+ return Err ( io:: Error :: last_os_error ( ) ) ;
182
+ }
183
+
184
+ OLD_OUT = Box :: into_raw ( Box :: new ( old_out) ) ;
185
+
186
+ Ok ( ( ) )
187
+ }
188
+
189
+ /// Reattach to the old console.
190
+ pub unsafe fn cleanup ( ) -> io:: Result < ( ) > {
191
+ if self :: kernel32:: FreeConsole ( ) == 0 {
192
+ return Err ( io:: Error :: last_os_error ( ) ) ;
193
+ }
194
+
195
+ if self :: kernel32:: AttachConsole ( winapi:: wincon:: ATTACH_PARENT_PROCESS ) == 0 {
196
+ return Err ( io:: Error :: last_os_error ( ) ) ;
197
+ }
198
+
199
+ Box :: from_raw ( OLD_OUT ) . set_as_std ( ) ?;
200
+
201
+ Ok ( ( ) )
202
+ }
203
+
204
+ /// This will signal the whole process group.
205
+ pub unsafe fn raise_ctrl_c ( ) {
206
+ assert ! ( self :: kernel32:: GenerateConsoleCtrlEvent ( self :: winapi:: CTRL_C_EVENT , 0 ) != 0 ) ;
207
+ }
208
+
209
+ /// Print to both consoles, this is not thread safe.
210
+ pub unsafe fn print ( fmt : :: std:: fmt:: Arguments ) {
211
+ use self :: io:: Write ;
212
+ {
213
+ let stdout = io:: stdout ( ) ;
214
+ stdout. lock ( ) . write_fmt ( fmt) . unwrap ( ) ;
215
+ }
216
+ {
217
+ assert ! ( !OLD_OUT . is_null( ) ) ;
218
+ ( * OLD_OUT ) . write_fmt ( fmt) . unwrap ( ) ;
219
+ }
220
+ }
221
+ }
222
+
223
+ fn test_set_handler ( ) {
224
+ let ( tx, rx) = :: std:: sync:: mpsc:: channel ( ) ;
225
+ ctrlc:: set_handler ( move || {
226
+ tx. send ( true ) . unwrap ( ) ;
227
+ } ) . unwrap ( ) ;
228
+
229
+ unsafe { platform:: raise_ctrl_c ( ) ; }
230
+
231
+ rx. recv_timeout ( :: std:: time:: Duration :: from_secs ( 10 ) ) . unwrap ( ) ;
232
+
233
+ match ctrlc:: set_handler ( || { } ) {
234
+ Err ( ctrlc:: Error :: MultipleHandlers ) => { } ,
235
+ ret => panic ! ( "{:?}" , ret) ,
236
+ }
237
+ }
238
+
239
+ macro_rules! run_tests {
240
+ ( $( $test_fn: ident) ,* ) => {
241
+ unsafe {
242
+ platform:: print( format_args!( "\n " ) ) ;
243
+ $(
244
+ platform:: print( format_args!( "test tests::{} ... " , stringify!( $test_fn) ) ) ;
245
+ $test_fn( ) ;
246
+ platform:: print( format_args!( "ok\n " ) ) ;
247
+ ) *
248
+ platform:: print( format_args!( "\n " ) ) ;
249
+ }
250
+ }
251
+ }
252
+
253
+ fn main ( ) {
254
+ unsafe { platform:: setup ( ) . unwrap ( ) ; }
255
+
256
+ let default = std:: panic:: take_hook ( ) ;
257
+ std:: panic:: set_hook ( Box :: new ( move |info| {
258
+ unsafe { platform:: cleanup ( ) . unwrap ( ) ; }
259
+ ( default) ( info) ;
260
+ } ) ) ;
261
+
262
+ run_tests ! ( test_set_handler) ;
263
+
264
+ unsafe { platform:: cleanup ( ) . unwrap ( ) ; }
265
+ }
0 commit comments