Skip to content

Commit 8dd5e9e

Browse files
committed
Avoid manual polling
Rather than manually implementing non-blocking reads using polling, simply configure the underlying file descriptor to be non-blocking where necessary. Replace libc calls with the normal abstractions from the standard library. This makes the code less error prone and properly encodes type system invariants (such as `io::Read::read` requiring its receiver to be mutable) which were previously not preserved because the implementation used raw file descriptors.
1 parent f37cb6e commit 8dd5e9e

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<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
// NB: this is not a full BufRead implementation because io::Stdin does not implement BufRead.
98107
impl<T: BufRead> Input<T> {
99108
fn read_line(&mut self, buf: &mut String) -> io::Result<usize> {
@@ -144,199 +153,132 @@ pub(crate) fn read_secure() -> io::Result<String> {
144153
})
145154
}
146155

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

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

245-
fn read_single_key_impl(fd: RawFd) -> Result<Key, io::Error> {
246-
loop {
247-
match read_single_char(fd)? {
248-
Some('\x1b') => {
249-
// Escape was read, keep reading in case we find a familiar key
250-
break if let Some(c1) = read_single_char(fd)? {
251-
if c1 == '[' {
252-
if let Some(c2) = read_single_char(fd)? {
253-
match c2 {
254-
'A' => Ok(Key::ArrowUp),
255-
'B' => Ok(Key::ArrowDown),
256-
'C' => Ok(Key::ArrowRight),
257-
'D' => Ok(Key::ArrowLeft),
258-
'H' => Ok(Key::Home),
259-
'F' => Ok(Key::End),
260-
'Z' => Ok(Key::BackTab),
261-
_ => {
262-
let c3 = read_single_char(fd)?;
263-
if let Some(c3) = c3 {
264-
if c3 == '~' {
265-
match c2 {
266-
'1' => Ok(Key::Home), // tmux
267-
'2' => Ok(Key::Insert),
268-
'3' => Ok(Key::Del),
269-
'4' => Ok(Key::End), // tmux
270-
'5' => Ok(Key::PageUp),
271-
'6' => Ok(Key::PageDown),
272-
'7' => Ok(Key::Home), // xrvt
273-
'8' => Ok(Key::End), // xrvt
274-
_ => Ok(Key::UnknownEscSeq(vec![c1, c2, c3])),
275-
}
276-
} else {
277-
Ok(Key::UnknownEscSeq(vec![c1, c2, c3]))
194+
fn read_single_key_impl<T: Read + AsRawFd>(input: &mut T) -> Result<Key, io::Error> {
195+
let mut buf = [0u8; 1];
196+
read_bytes(input, &mut buf)?;
197+
let [c] = buf;
198+
match c {
199+
b'\x1b' => {
200+
// Escape was read, keep reading in case we find a familiar key
201+
if let Some(c1) = read_single_char(input)? {
202+
if c1 == '[' {
203+
if let Some(c2) = read_single_char(input)? {
204+
match c2 {
205+
'A' => Ok(Key::ArrowUp),
206+
'B' => Ok(Key::ArrowDown),
207+
'C' => Ok(Key::ArrowRight),
208+
'D' => Ok(Key::ArrowLeft),
209+
'H' => Ok(Key::Home),
210+
'F' => Ok(Key::End),
211+
'Z' => Ok(Key::BackTab),
212+
_ => {
213+
let c3 = read_single_char(input)?;
214+
if let Some(c3) = c3 {
215+
if c3 == '~' {
216+
match c2 {
217+
'1' => Ok(Key::Home), // tmux
218+
'2' => Ok(Key::Insert),
219+
'3' => Ok(Key::Del),
220+
'4' => Ok(Key::End), // tmux
221+
'5' => Ok(Key::PageUp),
222+
'6' => Ok(Key::PageDown),
223+
'7' => Ok(Key::Home), // xrvt
224+
'8' => Ok(Key::End), // xrvt
225+
_ => Ok(Key::UnknownEscSeq(vec![c1, c2, c3])),
278226
}
279227
} else {
280-
// \x1b[ and 1 more char
281-
Ok(Key::UnknownEscSeq(vec![c1, c2]))
228+
Ok(Key::UnknownEscSeq(vec![c1, c2, c3]))
282229
}
230+
} else {
231+
// \x1b[ and 1 more char
232+
Ok(Key::UnknownEscSeq(vec![c1, c2]))
283233
}
284234
}
285-
} else {
286-
// \x1b[ and no more input
287-
Ok(Key::UnknownEscSeq(vec![c1]))
288235
}
289236
} else {
290-
// char after escape is not [
237+
// \x1b[ and no more input
291238
Ok(Key::UnknownEscSeq(vec![c1]))
292239
}
293240
} else {
294-
//nothing after escape
295-
Ok(Key::Escape)
296-
};
241+
// char after escape is not [
242+
Ok(Key::UnknownEscSeq(vec![c1]))
243+
}
244+
} else {
245+
//nothing after escape
246+
Ok(Key::Escape)
297247
}
298-
Some(c) => {
299-
let byte = c as u8;
248+
}
249+
byte => {
250+
if byte & 224u8 == 192u8 {
251+
// a two byte unicode character
252+
let mut buf: [u8; 2] = [byte, 0];
253+
read_bytes(input, &mut buf[1..])?;
254+
Ok(key_from_utf8(&buf))
255+
} else if byte & 240u8 == 224u8 {
256+
// a three byte unicode character
257+
let mut buf: [u8; 3] = [byte, 0, 0];
258+
read_bytes(input, &mut buf[1..])?;
259+
Ok(key_from_utf8(&buf))
260+
} else if byte & 248u8 == 240u8 {
261+
// a four byte unicode character
300262
let mut buf: [u8; 4] = [byte, 0, 0, 0];
301-
302-
break if byte & 224u8 == 192u8 {
303-
// a two byte unicode character
304-
read_bytes(fd, &mut buf[1..], 1)?;
305-
Ok(key_from_utf8(&buf[..2]))
306-
} else if byte & 240u8 == 224u8 {
307-
// a three byte unicode character
308-
read_bytes(fd, &mut buf[1..], 2)?;
309-
Ok(key_from_utf8(&buf[..3]))
310-
} else if byte & 248u8 == 240u8 {
311-
// a four byte unicode character
312-
read_bytes(fd, &mut buf[1..], 3)?;
313-
Ok(key_from_utf8(&buf[..4]))
314-
} else {
315-
Ok(match c {
316-
'\n' | '\r' => Key::Enter,
317-
'\x7f' => Key::Backspace,
318-
'\t' => Key::Tab,
319-
'\x01' => Key::Home, // Control-A (home)
320-
'\x05' => Key::End, // Control-E (end)
321-
'\x08' => Key::Backspace, // Control-H (8) (Identical to '\b')
322-
_ => Key::Char(c),
323-
})
324-
};
325-
}
326-
None => {
327-
// there is no subsequent byte ready to be read, block and wait for input
328-
// negative timeout means that it will block indefinitely
329-
match select_or_poll_term_fd(fd, -1) {
330-
Ok(_) => continue,
331-
Err(_) => break Err(io::Error::last_os_error()),
332-
}
263+
read_bytes(input, &mut buf[1..])?;
264+
Ok(key_from_utf8(&buf))
265+
} else {
266+
Ok(match c as char {
267+
'\n' | '\r' => Key::Enter,
268+
'\x7f' => Key::Backspace,
269+
'\t' => Key::Tab,
270+
'\x01' => Key::Home, // Control-A (home)
271+
'\x05' => Key::End, // Control-E (end)
272+
'\x08' => Key::Backspace, // Control-H (8) (Identical to '\b')
273+
c => Key::Char(c),
274+
})
333275
}
334276
}
335277
}
336278
}
337279

338280
pub(crate) fn read_single_key(ctrlc_key: bool) -> io::Result<Key> {
339-
let input = Input::unbuffered()?;
281+
let mut input = Input::unbuffered()?;
340282

341283
let mut termios = core::mem::MaybeUninit::uninit();
342284
c_result(|| unsafe { libc::tcgetattr(input.as_raw_fd(), termios.as_mut_ptr()) })?;
@@ -345,7 +287,7 @@ pub(crate) fn read_single_key(ctrlc_key: bool) -> io::Result<Key> {
345287
unsafe { libc::cfmakeraw(&mut termios) };
346288
termios.c_oflag = original.c_oflag;
347289
c_result(|| unsafe { libc::tcsetattr(input.as_raw_fd(), libc::TCSADRAIN, &termios) })?;
348-
let rv = read_single_key_impl(input.as_raw_fd());
290+
let rv = read_single_key_impl(&mut input);
349291
c_result(|| unsafe { libc::tcsetattr(input.as_raw_fd(), libc::TCSADRAIN, &original) })?;
350292

351293
// if the user hit ^C we want to signal SIGINT to ourselves.

0 commit comments

Comments
 (0)