Skip to content

Commit 44caf41

Browse files
committed
Avoid manual polling
Use blocking and non-blocking reads instead.
1 parent ae109ce commit 44caf41

File tree

1 file changed

+109
-167
lines changed

1 file changed

+109
-167
lines changed

src/unix_term.rs

Lines changed: 109 additions & 167 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,199 +152,132 @@ 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 = read_bytes(input, &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> {
245-
loop {
246-
match read_single_char(fd)? {
247-
Some('\x1b') => {
248-
// Escape was read, keep reading in case we find a familiar key
249-
break if let Some(c1) = read_single_char(fd)? {
250-
if c1 == '[' {
251-
if let Some(c2) = read_single_char(fd)? {
252-
match c2 {
253-
'A' => Ok(Key::ArrowUp),
254-
'B' => Ok(Key::ArrowDown),
255-
'C' => Ok(Key::ArrowRight),
256-
'D' => Ok(Key::ArrowLeft),
257-
'H' => Ok(Key::Home),
258-
'F' => Ok(Key::End),
259-
'Z' => Ok(Key::BackTab),
260-
_ => {
261-
let c3 = read_single_char(fd)?;
262-
if let Some(c3) = c3 {
263-
if c3 == '~' {
264-
match c2 {
265-
'1' => Ok(Key::Home), // tmux
266-
'2' => Ok(Key::Insert),
267-
'3' => Ok(Key::Del),
268-
'4' => Ok(Key::End), // tmux
269-
'5' => Ok(Key::PageUp),
270-
'6' => Ok(Key::PageDown),
271-
'7' => Ok(Key::Home), // xrvt
272-
'8' => Ok(Key::End), // xrvt
273-
_ => Ok(Key::UnknownEscSeq(vec![c1, c2, c3])),
274-
}
275-
} else {
276-
Ok(Key::UnknownEscSeq(vec![c1, c2, c3]))
193+
fn read_single_key_impl<T: Read + AsRawFd>(input: &mut T) -> Result<Key, io::Error> {
194+
let mut buf = [0u8; 1];
195+
read_bytes(input, &mut buf)?;
196+
let [c] = buf;
197+
match c {
198+
b'\x1b' => {
199+
// Escape was read, keep reading in case we find a familiar key
200+
if let Some(c1) = read_single_char(input)? {
201+
if c1 == '[' {
202+
if let Some(c2) = read_single_char(input)? {
203+
match c2 {
204+
'A' => Ok(Key::ArrowUp),
205+
'B' => Ok(Key::ArrowDown),
206+
'C' => Ok(Key::ArrowRight),
207+
'D' => Ok(Key::ArrowLeft),
208+
'H' => Ok(Key::Home),
209+
'F' => Ok(Key::End),
210+
'Z' => Ok(Key::BackTab),
211+
_ => {
212+
let c3 = read_single_char(input)?;
213+
if let Some(c3) = c3 {
214+
if c3 == '~' {
215+
match c2 {
216+
'1' => Ok(Key::Home), // tmux
217+
'2' => Ok(Key::Insert),
218+
'3' => Ok(Key::Del),
219+
'4' => Ok(Key::End), // tmux
220+
'5' => Ok(Key::PageUp),
221+
'6' => Ok(Key::PageDown),
222+
'7' => Ok(Key::Home), // xrvt
223+
'8' => Ok(Key::End), // xrvt
224+
_ => Ok(Key::UnknownEscSeq(vec![c1, c2, c3])),
277225
}
278226
} else {
279-
// \x1b[ and 1 more char
280-
Ok(Key::UnknownEscSeq(vec![c1, c2]))
227+
Ok(Key::UnknownEscSeq(vec![c1, c2, c3]))
281228
}
229+
} else {
230+
// \x1b[ and 1 more char
231+
Ok(Key::UnknownEscSeq(vec![c1, c2]))
282232
}
283233
}
284-
} else {
285-
// \x1b[ and no more input
286-
Ok(Key::UnknownEscSeq(vec![c1]))
287234
}
288235
} else {
289-
// char after escape is not [
236+
// \x1b[ and no more input
290237
Ok(Key::UnknownEscSeq(vec![c1]))
291238
}
292239
} else {
293-
//nothing after escape
294-
Ok(Key::Escape)
295-
};
240+
// char after escape is not [
241+
Ok(Key::UnknownEscSeq(vec![c1]))
242+
}
243+
} else {
244+
//nothing after escape
245+
Ok(Key::Escape)
296246
}
297-
Some(c) => {
298-
let byte = c as u8;
247+
}
248+
byte => {
249+
if byte & 224u8 == 192u8 {
250+
// a two byte unicode character
251+
let mut buf: [u8; 2] = [byte, 0];
252+
read_bytes(input, &mut buf[1..])?;
253+
Ok(key_from_utf8(&buf))
254+
} else if byte & 240u8 == 224u8 {
255+
// a three byte unicode character
256+
let mut buf: [u8; 3] = [byte, 0, 0];
257+
read_bytes(input, &mut buf[1..])?;
258+
Ok(key_from_utf8(&buf))
259+
} else if byte & 248u8 == 240u8 {
260+
// a four byte unicode character
299261
let mut buf: [u8; 4] = [byte, 0, 0, 0];
300-
301-
break if byte & 224u8 == 192u8 {
302-
// a two byte unicode character
303-
read_bytes(fd, &mut buf[1..], 1)?;
304-
Ok(key_from_utf8(&buf[..2]))
305-
} else if byte & 240u8 == 224u8 {
306-
// a three byte unicode character
307-
read_bytes(fd, &mut buf[1..], 2)?;
308-
Ok(key_from_utf8(&buf[..3]))
309-
} else if byte & 248u8 == 240u8 {
310-
// a four byte unicode character
311-
read_bytes(fd, &mut buf[1..], 3)?;
312-
Ok(key_from_utf8(&buf[..4]))
313-
} else {
314-
Ok(match c {
315-
'\n' | '\r' => Key::Enter,
316-
'\x7f' => Key::Backspace,
317-
'\t' => Key::Tab,
318-
'\x01' => Key::Home, // Control-A (home)
319-
'\x05' => Key::End, // Control-E (end)
320-
'\x08' => Key::Backspace, // Control-H (8) (Identical to '\b')
321-
_ => Key::Char(c),
322-
})
323-
};
324-
}
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-
}
262+
read_bytes(input, &mut buf[1..])?;
263+
Ok(key_from_utf8(&buf))
264+
} else {
265+
Ok(match c as char {
266+
'\n' | '\r' => Key::Enter,
267+
'\x7f' => Key::Backspace,
268+
'\t' => Key::Tab,
269+
'\x01' => Key::Home, // Control-A (home)
270+
'\x05' => Key::End, // Control-E (end)
271+
'\x08' => Key::Backspace, // Control-H (8) (Identical to '\b')
272+
c => Key::Char(c),
273+
})
332274
}
333275
}
334276
}
335277
}
336278

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

340282
let mut termios = core::mem::MaybeUninit::uninit();
341283
c_result(|| unsafe { libc::tcgetattr(input.as_raw_fd(), termios.as_mut_ptr()) })?;
@@ -344,7 +286,7 @@ pub(crate) fn read_single_key(ctrlc_key: bool) -> io::Result<Key> {
344286
unsafe { libc::cfmakeraw(&mut termios) };
345287
termios.c_oflag = original.c_oflag;
346288
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());
289+
let rv: io::Result<Key> = read_single_key_impl(&mut input);
348290
c_result(|| unsafe { libc::tcsetattr(input.as_raw_fd(), libc::TCSADRAIN, &original) })?;
349291

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

0 commit comments

Comments
 (0)