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 ) ,
@@ -200,21 +262,27 @@ impl Terminal<'_> {
200
262
}
201
263
202
264
/// Reads input with TTY echo disabled
203
- pub fn read_password ( & mut self ) -> io:: Result < PamBuffer > {
204
- let input = self . source ( ) ;
265
+ pub fn read_password ( & mut self , timeout : Option < Duration > ) -> io:: Result < PamBuffer > {
266
+ let mut input = self . source_timeout ( timeout ) ;
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
- 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) ,
272
+ pub fn read_password_with_feedback (
273
+ & mut self ,
274
+ timeout : Option < Duration > ,
275
+ ) -> io:: Result < PamBuffer > {
276
+ match ( HiddenInput :: new ( true ) ?, self ) {
277
+ ( Some ( hide_input) , Terminal :: StdIE ( stdin, stdout) ) => {
278
+ let mut reader = TimeoutRead :: new ( stdin. as_fd ( ) , timeout) ;
279
+ read_unbuffered_with_feedback ( & mut reader, stdout, & hide_input)
215
280
}
216
- } else {
217
- read_unbuffered ( self . source ( ) )
281
+ ( Some ( hide_input) , Terminal :: Tty ( file) ) => {
282
+ let mut reader = TimeoutRead :: new ( file. as_fd ( ) , timeout) ;
283
+ read_unbuffered_with_feedback ( & mut reader, & mut & * file, & hide_input)
284
+ }
285
+ ( None , term) => read_unbuffered ( & mut term. source_timeout ( timeout) ) ,
218
286
}
219
287
}
220
288
@@ -242,6 +310,13 @@ impl Terminal<'_> {
242
310
}
243
311
}
244
312
313
+ fn source_timeout ( & self , timeout : Option < Duration > ) -> TimeoutRead {
314
+ match self {
315
+ Terminal :: StdIE ( stdin, _) => TimeoutRead :: new ( stdin. as_fd ( ) , timeout) ,
316
+ Terminal :: Tty ( file) => TimeoutRead :: new ( file. as_fd ( ) , timeout) ,
317
+ }
318
+ }
319
+
245
320
fn sink ( & mut self ) -> & mut dyn io:: Write {
246
321
match self {
247
322
Terminal :: StdIE ( _, x) => x,
0 commit comments