Skip to content

Commit a7aac30

Browse files
committed
Avoid manual polling
Use blocking and non-blocking reads instead.
1 parent f2cd7e2 commit a7aac30

File tree

1 file changed

+61
-115
lines changed

1 file changed

+61
-115
lines changed

src/unix_term.rs

Lines changed: 61 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::env;
22
use std::fmt::Display;
33
use std::fs;
4-
use std::io::{self, BufRead, BufReader};
4+
use std::io::{self, BufRead, BufReader, Read};
55
use std::mem;
66
use std::os::fd::{AsRawFd, RawFd};
77
use std::str;
@@ -94,6 +94,15 @@ impl Input<BufReader<fs::File>> {
9494
}
9595
}
9696

97+
impl<T: Read> Read for Input<T> {
98+
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
99+
match self {
100+
Self::Stdin(s) => s.read(buf),
101+
Self::File(f) => f.read(buf),
102+
}
103+
}
104+
}
105+
97106
impl<T: BufRead> Input<T> {
98107
fn read_line(&mut self, buf: &mut String) -> io::Result<usize> {
99108
match self {
@@ -143,112 +152,55 @@ pub(crate) fn read_secure() -> io::Result<String> {
143152
})
144153
}
145154

146-
fn poll_fd(fd: RawFd, timeout: i32) -> io::Result<bool> {
147-
let mut pollfd = libc::pollfd {
148-
fd,
149-
events: libc::POLLIN,
150-
revents: 0,
151-
};
152-
let ret = unsafe { libc::poll(&mut pollfd as *mut _, 1, timeout) };
153-
if ret < 0 {
154-
Err(io::Error::last_os_error())
155-
} else {
156-
Ok(pollfd.revents & libc::POLLIN != 0)
157-
}
158-
}
159-
160-
#[cfg(target_os = "macos")]
161-
fn select_fd(fd: RawFd, timeout: i32) -> io::Result<bool> {
162-
unsafe {
163-
let mut read_fd_set: libc::fd_set = mem::zeroed();
164-
165-
let mut timeout_val;
166-
let timeout = if timeout < 0 {
167-
std::ptr::null_mut()
168-
} else {
169-
timeout_val = libc::timeval {
170-
tv_sec: (timeout / 1000) as _,
171-
tv_usec: (timeout * 1000) as _,
172-
};
173-
&mut timeout_val
174-
};
175-
176-
libc::FD_ZERO(&mut read_fd_set);
177-
libc::FD_SET(fd, &mut read_fd_set);
178-
let ret = libc::select(
179-
fd + 1,
180-
&mut read_fd_set,
181-
std::ptr::null_mut(),
182-
std::ptr::null_mut(),
183-
timeout,
184-
);
185-
if ret < 0 {
186-
Err(io::Error::last_os_error())
187-
} else {
188-
Ok(libc::FD_ISSET(fd, &read_fd_set))
155+
fn read_single_char<T: Read + AsRawFd>(input: &mut T) -> io::Result<Option<char>> {
156+
let original = unsafe { libc::fcntl(input.as_raw_fd(), libc::F_GETFL) };
157+
c_result(|| unsafe {
158+
libc::fcntl(
159+
input.as_raw_fd(),
160+
libc::F_SETFL,
161+
original | libc::O_NONBLOCK,
162+
)
163+
})?;
164+
let mut buf = [0u8; 1];
165+
let result = input.read_exact(&mut buf);
166+
c_result(|| unsafe { libc::fcntl(input.as_raw_fd(), libc::F_SETFL, original) })?;
167+
match result {
168+
Ok(()) => {
169+
let [c] = buf;
170+
Ok(Some(c as char))
189171
}
190-
}
191-
}
192-
193-
fn select_or_poll_term_fd(fd: RawFd, timeout: i32) -> io::Result<bool> {
194-
// There is a bug on macos that ttys cannot be polled, only select()
195-
// works. However given how problematic select is in general, we
196-
// normally want to use poll there too.
197-
#[cfg(target_os = "macos")]
198-
{
199-
if unsafe { libc::isatty(fd) == 1 } {
200-
return select_fd(fd, timeout);
172+
Err(err) => {
173+
if err.kind() == io::ErrorKind::WouldBlock {
174+
Ok(None)
175+
} else {
176+
Err(err)
177+
}
201178
}
202179
}
203-
poll_fd(fd, timeout)
204180
}
205181

206-
fn read_single_char(fd: RawFd) -> io::Result<Option<char>> {
207-
// timeout of zero means that it will not block
208-
let is_ready = select_or_poll_term_fd(fd, 0)?;
209-
210-
if is_ready {
211-
// if there is something to be read, take 1 byte from it
212-
let mut buf: [u8; 1] = [0];
213-
214-
read_bytes(fd, &mut buf, 1)?;
215-
Ok(Some(buf[0] as char))
216-
} else {
217-
//there is nothing to be read
218-
Ok(None)
219-
}
220-
}
221-
222-
// Similar to libc::read. Read count bytes into slice buf from descriptor fd.
223-
// If successful, return the number of bytes read.
224-
// Will return an error if nothing was read, i.e when called at end of file.
225-
fn read_bytes(fd: RawFd, buf: &mut [u8], count: u8) -> io::Result<u8> {
226-
let read = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut _, count as usize) };
227-
if read < 0 {
228-
Err(io::Error::last_os_error())
229-
} else if read == 0 {
230-
Err(io::Error::new(
231-
io::ErrorKind::UnexpectedEof,
232-
"Reached end of file",
233-
))
234-
} else if buf[0] == b'\x03' {
235-
Err(io::Error::new(
182+
fn read_bytes(input: &mut impl Read, buf: &mut [u8]) -> io::Result<()> {
183+
input.read_exact(buf)?;
184+
match buf {
185+
[b'\x03', ..] => Err(io::Error::new(
236186
io::ErrorKind::Interrupted,
237187
"read interrupted",
238-
))
239-
} else {
240-
Ok(read as u8)
188+
)),
189+
_ => Ok(()),
241190
}
242191
}
243192

244-
fn read_single_key_impl(fd: RawFd) -> Result<Key, io::Error> {
193+
fn read_single_key_impl<T: Read + AsRawFd>(input: &mut T) -> Result<Key, io::Error> {
245194
loop {
246-
match read_single_char(fd)? {
247-
Some('\x1b') => {
195+
let mut buf = [0u8; 1];
196+
input.read_exact(&mut buf)?;
197+
let [c] = buf;
198+
match c {
199+
b'\x1b' => {
248200
// Escape was read, keep reading in case we find a familiar key
249-
break if let Some(c1) = read_single_char(fd)? {
201+
break if let Some(c1) = read_single_char(input)? {
250202
if c1 == '[' {
251-
if let Some(c2) = read_single_char(fd)? {
203+
if let Some(c2) = read_single_char(input)? {
252204
match c2 {
253205
'A' => Ok(Key::ArrowUp),
254206
'B' => Ok(Key::ArrowDown),
@@ -258,7 +210,7 @@ fn read_single_key_impl(fd: RawFd) -> Result<Key, io::Error> {
258210
'F' => Ok(Key::End),
259211
'Z' => Ok(Key::BackTab),
260212
_ => {
261-
let c3 = read_single_char(fd)?;
213+
let c3 = read_single_char(input)?;
262214
if let Some(c3) = c3 {
263215
if c3 == '~' {
264216
match c2 {
@@ -294,48 +246,42 @@ fn read_single_key_impl(fd: RawFd) -> Result<Key, io::Error> {
294246
Ok(Key::Escape)
295247
};
296248
}
297-
Some(c) => {
249+
c => {
298250
let byte = c as u8;
299-
let mut buf: [u8; 4] = [byte, 0, 0, 0];
300251

301252
break if byte & 224u8 == 192u8 {
302253
// a two byte unicode character
303-
read_bytes(fd, &mut buf[1..], 1)?;
304-
Ok(key_from_utf8(&buf[..2]))
254+
let mut buf: [u8; 2] = [byte, 0];
255+
read_bytes(input, &mut buf[1..])?;
256+
Ok(key_from_utf8(&buf))
305257
} else if byte & 240u8 == 224u8 {
306258
// a three byte unicode character
307-
read_bytes(fd, &mut buf[1..], 2)?;
308-
Ok(key_from_utf8(&buf[..3]))
259+
let mut buf: [u8; 3] = [byte, 0, 0];
260+
read_bytes(input, &mut buf[1..])?;
261+
Ok(key_from_utf8(&buf))
309262
} else if byte & 248u8 == 240u8 {
310263
// a four byte unicode character
311-
read_bytes(fd, &mut buf[1..], 3)?;
312-
Ok(key_from_utf8(&buf[..4]))
264+
let mut buf: [u8; 4] = [byte, 0, 0, 0];
265+
read_bytes(input, &mut buf[1..])?;
266+
Ok(key_from_utf8(&buf))
313267
} else {
314-
Ok(match c {
268+
Ok(match c as char {
315269
'\n' | '\r' => Key::Enter,
316270
'\x7f' => Key::Backspace,
317271
'\t' => Key::Tab,
318272
'\x01' => Key::Home, // Control-A (home)
319273
'\x05' => Key::End, // Control-E (end)
320274
'\x08' => Key::Backspace, // Control-H (8) (Identical to '\b')
321-
_ => Key::Char(c),
275+
c => Key::Char(c),
322276
})
323277
};
324278
}
325-
None => {
326-
// there is no subsequent byte ready to be read, block and wait for input
327-
// negative timeout means that it will block indefinitely
328-
match select_or_poll_term_fd(fd, -1) {
329-
Ok(_) => continue,
330-
Err(_) => break Err(io::Error::last_os_error()),
331-
}
332-
}
333279
}
334280
}
335281
}
336282

337283
pub(crate) fn read_single_key(ctrlc_key: bool) -> io::Result<Key> {
338-
let input = Input::<fs::File>::new()?;
284+
let mut input = Input::<fs::File>::new()?;
339285

340286
let mut termios = core::mem::MaybeUninit::uninit();
341287
c_result(|| unsafe { libc::tcgetattr(input.as_raw_fd(), termios.as_mut_ptr()) })?;
@@ -344,7 +290,7 @@ pub(crate) fn read_single_key(ctrlc_key: bool) -> io::Result<Key> {
344290
unsafe { libc::cfmakeraw(&mut termios) };
345291
termios.c_oflag = original.c_oflag;
346292
c_result(|| unsafe { libc::tcsetattr(input.as_raw_fd(), libc::TCSADRAIN, &termios) })?;
347-
let rv: io::Result<Key> = read_single_key_impl(input.as_raw_fd());
293+
let rv: io::Result<Key> = read_single_key_impl(&mut input);
348294
c_result(|| unsafe { libc::tcsetattr(input.as_raw_fd(), libc::TCSADRAIN, &original) })?;
349295

350296
// if the user hit ^C we want to signal SIGINT to outselves.

0 commit comments

Comments
 (0)