1
1
use std:: env;
2
2
use std:: fmt:: Display ;
3
3
use std:: fs;
4
- use std:: io;
5
- use std:: io:: { BufRead , BufReader } ;
4
+ use std:: io:: { self , BufRead , BufReader } ;
6
5
use std:: mem;
7
- use std:: os:: unix :: io :: AsRawFd ;
6
+ use std:: os:: fd :: { AsRawFd , RawFd } ;
8
7
use std:: str;
9
8
10
9
#[ cfg( not( target_os = "macos" ) ) ]
@@ -18,7 +17,7 @@ pub(crate) use crate::common_term::*;
18
17
pub ( crate ) const DEFAULT_WIDTH : u16 = 80 ;
19
18
20
19
#[ inline]
21
- pub ( crate ) fn is_a_terminal ( out : & Term ) -> bool {
20
+ pub ( crate ) fn is_a_terminal ( out : & impl AsRawFd ) -> bool {
22
21
unsafe { libc:: isatty ( out. as_raw_fd ( ) ) != 0 }
23
22
}
24
23
@@ -66,41 +65,73 @@ pub(crate) fn terminal_size(out: &Term) -> Option<(u16, u16)> {
66
65
}
67
66
}
68
67
69
- pub ( crate ) fn read_secure ( ) -> io:: Result < String > {
70
- let mut f_tty;
71
- let fd = unsafe {
72
- if libc:: isatty ( libc:: STDIN_FILENO ) == 1 {
73
- f_tty = None ;
74
- libc:: STDIN_FILENO
75
- } else {
76
- let f = fs:: OpenOptions :: new ( )
77
- . read ( true )
78
- . write ( true )
79
- . open ( "/dev/tty" ) ?;
80
- let fd = f. as_raw_fd ( ) ;
81
- f_tty = Some ( BufReader :: new ( f) ) ;
82
- fd
68
+ enum Input < T > {
69
+ Stdin ( io:: Stdin ) ,
70
+ File ( T ) ,
71
+ }
72
+
73
+ fn unbuffered_input ( ) -> io:: Result < Input < fs:: File > > {
74
+ let stdin = io:: stdin ( ) ;
75
+ if is_a_terminal ( & stdin) {
76
+ Ok ( Input :: Stdin ( stdin) )
77
+ } else {
78
+ let f = fs:: OpenOptions :: new ( )
79
+ . read ( true )
80
+ . write ( true )
81
+ . open ( "/dev/tty" ) ?;
82
+ Ok ( Input :: File ( f) )
83
+ }
84
+ }
85
+
86
+ fn buffered_input ( ) -> io:: Result < Input < BufReader < fs:: File > > > {
87
+ Ok ( match unbuffered_input ( ) ? {
88
+ Input :: Stdin ( s) => Input :: Stdin ( s) ,
89
+ Input :: File ( f) => Input :: File ( BufReader :: new ( f) ) ,
90
+ } )
91
+ }
92
+
93
+ // NB: this is not a full BufRead implementation because io::Stdin does not implement BufRead.
94
+ impl < T : BufRead > Input < T > {
95
+ fn read_line ( & mut self , buf : & mut String ) -> io:: Result < usize > {
96
+ match self {
97
+ Self :: Stdin ( s) => s. read_line ( buf) ,
98
+ Self :: File ( f) => f. read_line ( buf) ,
83
99
}
84
- } ;
100
+ }
101
+ }
102
+
103
+ impl AsRawFd for Input < fs:: File > {
104
+ fn as_raw_fd ( & self ) -> RawFd {
105
+ match self {
106
+ Self :: Stdin ( s) => s. as_raw_fd ( ) ,
107
+ Self :: File ( f) => f. as_raw_fd ( ) ,
108
+ }
109
+ }
110
+ }
111
+
112
+ impl AsRawFd for Input < BufReader < fs:: File > > {
113
+ fn as_raw_fd ( & self ) -> RawFd {
114
+ match self {
115
+ Self :: Stdin ( s) => s. as_raw_fd ( ) ,
116
+ Self :: File ( f) => f. get_ref ( ) . as_raw_fd ( ) ,
117
+ }
118
+ }
119
+ }
120
+
121
+ pub ( crate ) fn read_secure ( ) -> io:: Result < String > {
122
+ let mut input = buffered_input ( ) ?;
85
123
86
124
let mut termios = mem:: MaybeUninit :: uninit ( ) ;
87
- c_result ( || unsafe { libc:: tcgetattr ( fd , termios. as_mut_ptr ( ) ) } ) ?;
125
+ c_result ( || unsafe { libc:: tcgetattr ( input . as_raw_fd ( ) , termios. as_mut_ptr ( ) ) } ) ?;
88
126
let mut termios = unsafe { termios. assume_init ( ) } ;
89
127
let original = termios;
90
128
termios. c_lflag &= !libc:: ECHO ;
91
- c_result ( || unsafe { libc:: tcsetattr ( fd , libc:: TCSAFLUSH , & termios) } ) ?;
129
+ c_result ( || unsafe { libc:: tcsetattr ( input . as_raw_fd ( ) , libc:: TCSAFLUSH , & termios) } ) ?;
92
130
let mut rv = String :: new ( ) ;
93
131
94
- let read_rv = if let Some ( f) = & mut f_tty {
95
- f. read_line ( & mut rv)
96
- } else {
97
- io:: stdin ( ) . read_line ( & mut rv)
98
- } ;
132
+ let read_rv = input. read_line ( & mut rv) ;
99
133
100
- c_result ( || unsafe { libc:: tcsetattr ( fd, libc:: TCSAFLUSH , & original) } ) ?;
101
-
102
- // Ensure the fd is only closed after everything has been restored.
103
- drop ( f_tty) ;
134
+ c_result ( || unsafe { libc:: tcsetattr ( input. as_raw_fd ( ) , libc:: TCSAFLUSH , & original) } ) ?;
104
135
105
136
read_rv. map ( |_| {
106
137
let len = rv. trim_end_matches ( & [ '\r' , '\n' ] [ ..] ) . len ( ) ;
@@ -109,7 +140,7 @@ pub(crate) fn read_secure() -> io::Result<String> {
109
140
} )
110
141
}
111
142
112
- fn poll_fd ( fd : i32 , timeout : i32 ) -> io:: Result < bool > {
143
+ fn poll_fd ( fd : RawFd , timeout : i32 ) -> io:: Result < bool > {
113
144
let mut pollfd = libc:: pollfd {
114
145
fd,
115
146
events : libc:: POLLIN ,
@@ -124,7 +155,7 @@ fn poll_fd(fd: i32, timeout: i32) -> io::Result<bool> {
124
155
}
125
156
126
157
#[ cfg( target_os = "macos" ) ]
127
- fn select_fd ( fd : i32 , timeout : i32 ) -> io:: Result < bool > {
158
+ fn select_fd ( fd : RawFd , timeout : i32 ) -> io:: Result < bool > {
128
159
unsafe {
129
160
let mut read_fd_set: libc:: fd_set = mem:: zeroed ( ) ;
130
161
@@ -156,7 +187,7 @@ fn select_fd(fd: i32, timeout: i32) -> io::Result<bool> {
156
187
}
157
188
}
158
189
159
- fn select_or_poll_term_fd ( fd : i32 , timeout : i32 ) -> io:: Result < bool > {
190
+ fn select_or_poll_term_fd ( fd : RawFd , timeout : i32 ) -> io:: Result < bool > {
160
191
// There is a bug on macos that ttys cannot be polled, only select()
161
192
// works. However given how problematic select is in general, we
162
193
// normally want to use poll there too.
@@ -169,7 +200,7 @@ fn select_or_poll_term_fd(fd: i32, timeout: i32) -> io::Result<bool> {
169
200
poll_fd ( fd, timeout)
170
201
}
171
202
172
- fn read_single_char ( fd : i32 ) -> io:: Result < Option < char > > {
203
+ fn read_single_char ( fd : RawFd ) -> io:: Result < Option < char > > {
173
204
// timeout of zero means that it will not block
174
205
let is_ready = select_or_poll_term_fd ( fd, 0 ) ?;
175
206
@@ -188,7 +219,7 @@ fn read_single_char(fd: i32) -> io::Result<Option<char>> {
188
219
// Similar to libc::read. Read count bytes into slice buf from descriptor fd.
189
220
// If successful, return the number of bytes read.
190
221
// Will return an error if nothing was read, i.e when called at end of file.
191
- fn read_bytes ( fd : i32 , buf : & mut [ u8 ] , count : u8 ) -> io:: Result < u8 > {
222
+ fn read_bytes ( fd : RawFd , buf : & mut [ u8 ] , count : u8 ) -> io:: Result < u8 > {
192
223
let read = unsafe { libc:: read ( fd, buf. as_mut_ptr ( ) as * mut _ , count as usize ) } ;
193
224
if read < 0 {
194
225
Err ( io:: Error :: last_os_error ( ) )
@@ -207,7 +238,7 @@ fn read_bytes(fd: i32, buf: &mut [u8], count: u8) -> io::Result<u8> {
207
238
}
208
239
}
209
240
210
- fn read_single_key_impl ( fd : i32 ) -> Result < Key , io:: Error > {
241
+ fn read_single_key_impl ( fd : RawFd ) -> Result < Key , io:: Error > {
211
242
loop {
212
243
match read_single_char ( fd) ? {
213
244
Some ( '\x1b' ) => {
@@ -301,27 +332,17 @@ fn read_single_key_impl(fd: i32) -> Result<Key, io::Error> {
301
332
}
302
333
303
334
pub ( crate ) fn read_single_key ( ctrlc_key : bool ) -> io:: Result < Key > {
304
- let tty_f;
305
- let fd = unsafe {
306
- if libc:: isatty ( libc:: STDIN_FILENO ) == 1 {
307
- libc:: STDIN_FILENO
308
- } else {
309
- tty_f = fs:: OpenOptions :: new ( )
310
- . read ( true )
311
- . write ( true )
312
- . open ( "/dev/tty" ) ?;
313
- tty_f. as_raw_fd ( )
314
- }
315
- } ;
335
+ let input = unbuffered_input ( ) ?;
336
+
316
337
let mut termios = core:: mem:: MaybeUninit :: uninit ( ) ;
317
- c_result ( || unsafe { libc:: tcgetattr ( fd , termios. as_mut_ptr ( ) ) } ) ?;
338
+ c_result ( || unsafe { libc:: tcgetattr ( input . as_raw_fd ( ) , termios. as_mut_ptr ( ) ) } ) ?;
318
339
let mut termios = unsafe { termios. assume_init ( ) } ;
319
340
let original = termios;
320
341
unsafe { libc:: cfmakeraw ( & mut termios) } ;
321
342
termios. c_oflag = original. c_oflag ;
322
- c_result ( || unsafe { libc:: tcsetattr ( fd , libc:: TCSADRAIN , & termios) } ) ?;
323
- let rv: io:: Result < Key > = read_single_key_impl ( fd ) ;
324
- c_result ( || unsafe { libc:: tcsetattr ( fd , libc:: TCSADRAIN , & original) } ) ?;
343
+ c_result ( || unsafe { libc:: tcsetattr ( input . as_raw_fd ( ) , libc:: TCSADRAIN , & termios) } ) ?;
344
+ let rv: io:: Result < Key > = read_single_key_impl ( input . as_raw_fd ( ) ) ;
345
+ c_result ( || unsafe { libc:: tcsetattr ( input . as_raw_fd ( ) , libc:: TCSADRAIN , & original) } ) ?;
325
346
326
347
// if the user hit ^C we want to signal SIGINT to ourselves.
327
348
if let Err ( ref err) = rv {
0 commit comments