Skip to content

Commit 02dec12

Browse files
committed
fix(unix): retry EINTR in poll/select/read
Fixes #212. read_single_key treats any Err(Interrupted) from read_single_key_impl as Ctrl-C and forwards it via libc::raise(SIGINT). That's correct only for the synthesized Err(Interrupted) read_bytes returns when it sees a \x03 byte. EINTR returned by poll/select/read because of an unrelated signal (SIGCHLD, SIGWINCH, etc.) takes the same path and incorrectly kills the program with SIGINT. Retry EINTR inside the syscall wrappers so Err(Interrupted) reaching read_single_key unambiguously means a \x03 byte was read.
1 parent 586efad commit 02dec12

2 files changed

Lines changed: 63 additions & 49 deletions

File tree

src/unix_term.rs

Lines changed: 60 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -157,43 +157,53 @@ fn poll_fd(fd: RawFd, timeout: i32) -> io::Result<bool> {
157157
events: libc::POLLIN,
158158
revents: 0,
159159
};
160-
let ret = unsafe { libc::poll(&mut pollfd as *mut _, 1, timeout) };
161-
if ret < 0 {
162-
Err(io::Error::last_os_error())
163-
} else {
164-
Ok(pollfd.revents & libc::POLLIN != 0)
160+
loop {
161+
let ret = unsafe { libc::poll(&mut pollfd as *mut _, 1, timeout) };
162+
if ret < 0 {
163+
let err = io::Error::last_os_error();
164+
if err.kind() == io::ErrorKind::Interrupted {
165+
continue;
166+
}
167+
return Err(err);
168+
}
169+
return Ok(pollfd.revents & libc::POLLIN != 0);
165170
}
166171
}
167172

168173
#[cfg(target_os = "macos")]
169174
fn select_fd(fd: RawFd, timeout: i32) -> io::Result<bool> {
170175
unsafe {
171-
let mut read_fd_set: libc::fd_set = mem::zeroed();
176+
loop {
177+
let mut read_fd_set: libc::fd_set = mem::zeroed();
172178

173-
let mut timeout_val;
174-
let timeout = if timeout < 0 {
175-
ptr::null_mut()
176-
} else {
177-
timeout_val = libc::timeval {
178-
tv_sec: (timeout / 1000) as _,
179-
tv_usec: (timeout * 1000) as _,
179+
let mut timeout_val;
180+
let timeout = if timeout < 0 {
181+
ptr::null_mut()
182+
} else {
183+
timeout_val = libc::timeval {
184+
tv_sec: (timeout / 1000) as _,
185+
tv_usec: (timeout * 1000) as _,
186+
};
187+
&mut timeout_val
180188
};
181-
&mut timeout_val
182-
};
183-
184-
libc::FD_ZERO(&mut read_fd_set);
185-
libc::FD_SET(fd, &mut read_fd_set);
186-
let ret = libc::select(
187-
fd + 1,
188-
&mut read_fd_set,
189-
ptr::null_mut(),
190-
ptr::null_mut(),
191-
timeout,
192-
);
193-
if ret < 0 {
194-
Err(io::Error::last_os_error())
195-
} else {
196-
Ok(libc::FD_ISSET(fd, &read_fd_set))
189+
190+
libc::FD_ZERO(&mut read_fd_set);
191+
libc::FD_SET(fd, &mut read_fd_set);
192+
let ret = libc::select(
193+
fd + 1,
194+
&mut read_fd_set,
195+
ptr::null_mut(),
196+
ptr::null_mut(),
197+
timeout,
198+
);
199+
if ret < 0 {
200+
let err = io::Error::last_os_error();
201+
if err.kind() == io::ErrorKind::Interrupted {
202+
continue;
203+
}
204+
return Err(err);
205+
}
206+
return Ok(libc::FD_ISSET(fd, &read_fd_set));
197207
}
198208
}
199209
}
@@ -231,21 +241,27 @@ fn read_single_char(fd: RawFd) -> io::Result<Option<char>> {
231241
// If successful, return the number of bytes read.
232242
// Will return an error if nothing was read, i.e when called at end of file.
233243
fn read_bytes(fd: RawFd, buf: &mut [u8], count: u8) -> io::Result<u8> {
234-
let read = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut _, count as usize) };
235-
if read < 0 {
236-
Err(io::Error::last_os_error())
237-
} else if read == 0 {
238-
Err(io::Error::new(
239-
io::ErrorKind::UnexpectedEof,
240-
"Reached end of file",
241-
))
242-
} else if buf[0] == b'\x03' {
243-
Err(io::Error::new(
244-
io::ErrorKind::Interrupted,
245-
"read interrupted",
246-
))
247-
} else {
248-
Ok(read as u8)
244+
loop {
245+
let read = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut _, count as usize) };
246+
if read < 0 {
247+
let err = io::Error::last_os_error();
248+
if err.kind() == io::ErrorKind::Interrupted {
249+
continue;
250+
}
251+
return Err(err);
252+
} else if read == 0 {
253+
return Err(io::Error::new(
254+
io::ErrorKind::UnexpectedEof,
255+
"Reached end of file",
256+
));
257+
} else if buf[0] == b'\x03' {
258+
return Err(io::Error::new(
259+
io::ErrorKind::Interrupted,
260+
"read interrupted",
261+
));
262+
} else {
263+
return Ok(read as u8);
264+
}
249265
}
250266
}
251267

src/windows_term/mod.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -405,11 +405,9 @@ pub(crate) fn read_secure() -> io::Result<String> {
405405
Key::Enter => {
406406
break;
407407
}
408-
Key::Char('\x08') => {
409-
if !rv.is_empty() {
410-
let new_len = rv.len() - 1;
411-
rv.truncate(new_len);
412-
}
408+
Key::Char('\x08') if !rv.is_empty() => {
409+
let new_len = rv.len() - 1;
410+
rv.truncate(new_len);
413411
}
414412
Key::Char(c) => {
415413
rv.push(c);

0 commit comments

Comments
 (0)