14
14
/// (although much more robust than in the original code)
15
15
///
16
16
use std:: io:: { self , Error , ErrorKind , Read } ;
17
- use std:: os:: fd:: { AsFd , AsRawFd } ;
17
+ use std:: os:: fd:: { AsFd , AsRawFd , BorrowedFd } ;
18
+ use std:: time:: Instant ;
18
19
use std:: { fs, mem} ;
19
20
20
21
use libc:: { tcsetattr, termios, ECHO , ECHONL , ICANON , TCSANOW , VEOF , VERASE , VKILL } ;
21
22
22
23
use crate :: cutils:: cerr;
24
+ use crate :: system:: time:: Duration ;
23
25
24
26
use super :: securemem:: PamBuffer ;
25
27
@@ -177,6 +179,66 @@ fn write_unbuffered(sink: &mut dyn io::Write, text: &[u8]) -> io::Result<()> {
177
179
sink. flush ( )
178
180
}
179
181
182
+ struct TimeoutRead < ' a > {
183
+ timeout_at : Option < Instant > ,
184
+ fd : BorrowedFd < ' a > ,
185
+ }
186
+
187
+ impl < ' a > TimeoutRead < ' a > {
188
+ fn new ( fd : BorrowedFd < ' a > , timeout : Option < Duration > ) -> TimeoutRead < ' a > {
189
+ TimeoutRead {
190
+ timeout_at : timeout. map ( |timeout| Instant :: now ( ) + timeout. into ( ) ) ,
191
+ fd,
192
+ }
193
+ }
194
+ }
195
+
196
+ impl io:: Read for TimeoutRead < ' _ > {
197
+ fn read ( & mut self , buf : & mut [ u8 ] ) -> io:: Result < usize > {
198
+ let pollmask = libc:: POLLIN | libc:: POLLRDHUP ;
199
+
200
+ let mut pollfd = [ libc:: pollfd {
201
+ fd : self . fd . as_raw_fd ( ) ,
202
+ events : pollmask,
203
+ revents : 0 ,
204
+ } ; 1 ] ;
205
+
206
+ let timeout = match self . timeout_at {
207
+ Some ( timeout_at) => {
208
+ let now = Instant :: now ( ) ;
209
+ if now > timeout_at {
210
+ return Err ( io:: Error :: from ( ErrorKind :: TimedOut ) ) ;
211
+ }
212
+
213
+ ( timeout_at - now)
214
+ . as_millis ( )
215
+ . try_into ( )
216
+ . unwrap_or ( i32:: MAX )
217
+ }
218
+ None => -1 ,
219
+ } ;
220
+
221
+ // SAFETY: pollfd is initialized and its length matches
222
+ cerr ( unsafe { libc:: poll ( pollfd. as_mut_ptr ( ) , pollfd. len ( ) as u64 , timeout) } ) ?;
223
+
224
+ // There may yet be data waiting to be read even if POLLHUP is set.
225
+ if pollfd[ 0 ] . revents & ( pollmask | libc:: POLLHUP ) > 0 {
226
+ // SAFETY: buf is initialized and its length matches
227
+ let ret = cerr ( unsafe {
228
+ libc:: read (
229
+ self . fd . as_raw_fd ( ) ,
230
+ buf. as_mut_ptr ( ) as * mut libc:: c_void ,
231
+ buf. len ( ) ,
232
+ )
233
+ } ) ?;
234
+
235
+ Ok ( ret as usize )
236
+ } else {
237
+ Err ( io:: Error :: from ( io:: ErrorKind :: TimedOut ) )
238
+ }
239
+ }
240
+ }
241
+
180
242
/// A data structure representing either /dev/tty or /dev/stdin+stderr
181
243
pub enum Terminal < ' a > {
182
244
Tty ( fs:: File ) ,
@@ -201,20 +263,23 @@ impl Terminal<'_> {
201
263
202
264
/// Reads input with TTY echo disabled
203
265
pub fn read_password ( & mut self ) -> io:: Result < PamBuffer > {
204
- let input = self . source ( ) ;
266
+ let mut input = self . source_timeout ( None ) ;
205
267
let _hide_input = HiddenInput :: new ( false ) ?;
206
- read_unbuffered ( input)
268
+ read_unbuffered ( & mut input)
207
269
}
208
270
209
271
/// Reads input with TTY echo disabled, but do provide visual feedback while typing.
210
272
pub fn read_password_with_feedback ( & mut self ) -> io:: Result < PamBuffer > {
211
- if let Some ( hide_input ) = HiddenInput :: new ( true ) ? {
212
- match self {
213
- Terminal :: StdIE ( x , y ) => read_unbuffered_with_feedback ( x , y , & hide_input ) ,
214
- Terminal :: Tty ( x ) => read_unbuffered_with_feedback ( & mut & * x , & mut & * x , & hide_input) ,
273
+ match ( HiddenInput :: new ( true ) ?, self ) {
274
+ ( Some ( hide_input ) , Terminal :: StdIE ( stdin , stdout ) ) => {
275
+ let mut reader = TimeoutRead :: new ( stdin . as_fd ( ) , None ) ;
276
+ read_unbuffered_with_feedback ( & mut reader , stdout , & hide_input)
215
277
}
216
- } else {
217
- read_unbuffered ( self . source ( ) )
278
+ ( Some ( hide_input) , Terminal :: Tty ( file) ) => {
279
+ let mut reader = TimeoutRead :: new ( file. as_fd ( ) , None ) ;
280
+ read_unbuffered_with_feedback ( & mut reader, & mut & * file, & hide_input)
281
+ }
282
+ ( None , term) => read_unbuffered ( & mut term. source_timeout ( None ) ) ,
218
283
}
219
284
}
220
285
@@ -242,6 +307,13 @@ impl Terminal<'_> {
242
307
}
243
308
}
244
309
310
+ fn source_timeout ( & self , timeout : Option < Duration > ) -> TimeoutRead {
311
+ match self {
312
+ Terminal :: StdIE ( stdin, _) => TimeoutRead :: new ( stdin. as_fd ( ) , timeout) ,
313
+ Terminal :: Tty ( file) => TimeoutRead :: new ( file. as_fd ( ) , timeout) ,
314
+ }
315
+ }
316
+
245
317
fn sink ( & mut self ) -> & mut dyn io:: Write {
246
318
match self {
247
319
Terminal :: StdIE ( _, x) => x,
0 commit comments