Add EPOLLER support

This commit is contained in:
tiif 2024-08-17 16:03:59 +08:00
parent 93700208a8
commit 483120d6ce
3 changed files with 83 additions and 6 deletions

View File

@ -76,11 +76,19 @@ pub struct EpollReadyEvents {
/// epollrdhup also gets set when only the write half is closed, which is possible
/// via `shutdown(_, SHUT_WR)`.
pub epollhup: bool,
/// Error condition happened on the associated file descriptor.
pub epollerr: bool,
}
impl EpollReadyEvents {
pub fn new() -> Self {
EpollReadyEvents { epollin: false, epollout: false, epollrdhup: false, epollhup: false }
EpollReadyEvents {
epollin: false,
epollout: false,
epollrdhup: false,
epollhup: false,
epollerr: false,
}
}
pub fn get_event_bitmask<'tcx>(&self, ecx: &MiriInterpCx<'tcx>) -> u32 {
@ -88,6 +96,7 @@ impl EpollReadyEvents {
let epollout = ecx.eval_libc_u32("EPOLLOUT");
let epollrdhup = ecx.eval_libc_u32("EPOLLRDHUP");
let epollhup = ecx.eval_libc_u32("EPOLLHUP");
let epollerr = ecx.eval_libc_u32("EPOLLERR");
let mut bitmask = 0;
if self.epollin {
@ -102,6 +111,9 @@ impl EpollReadyEvents {
if self.epollhup {
bitmask |= epollhup;
}
if self.epollerr {
bitmask |= epollerr;
}
bitmask
}
}
@ -229,6 +241,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let epollrdhup = this.eval_libc_u32("EPOLLRDHUP");
let epollet = this.eval_libc_u32("EPOLLET");
let epollhup = this.eval_libc_u32("EPOLLHUP");
let epollerr = this.eval_libc_u32("EPOLLERR");
// Fail on unsupported operations.
if op & epoll_ctl_add != epoll_ctl_add
@ -261,10 +274,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// Unset the flag we support to discover if any unsupported flags are used.
let mut flags = events;
// epoll_wait(2) will always wait for epollhup; it is not
// epoll_wait(2) will always wait for epollhup and epollerr; it is not
// necessary to set it in events when calling epoll_ctl().
// So we will always set this event type.
// So we will always set these two event types.
events |= epollhup;
events |= epollerr;
if events & epollet != epollet {
// We only support edge-triggered notification for now.
@ -284,6 +298,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
if flags & epollhup == epollhup {
flags &= !epollhup;
}
if flags & epollerr == epollerr {
flags &= !epollerr;
}
if flags != 0 {
throw_unsup_format!(
"epoll_ctl: encountered unknown unsupported flags {:#x}",

View File

@ -2,7 +2,7 @@
//! are entirely implemented inside Miri.
//! We also use the same infrastructure to implement unnamed pipes.
use std::cell::{OnceCell, RefCell};
use std::cell::{Cell, OnceCell, RefCell};
use std::collections::VecDeque;
use std::io;
use std::io::{Error, ErrorKind, Read};
@ -27,6 +27,10 @@ struct AnonSocket {
/// writing to. This is a weak reference because the other side may be closed before us; all
/// future writes will then trigger EPIPE.
peer_fd: OnceCell<WeakFileDescriptionRef>,
/// Indicates whether the peer has lost data when the file description is closed.
/// This flag is set to `true` if the peer's `readbuf` is non-empty at the time
/// of closure.
peer_lost_data: Cell<bool>,
is_nonblock: bool,
}
@ -91,6 +95,10 @@ impl FileDescription for AnonSocket {
// for read and write.
epoll_ready_events.epollin = true;
epoll_ready_events.epollout = true;
// If there is data lost in peer_fd, set EPOLLERR.
if self.peer_lost_data.get() {
epoll_ready_events.epollerr = true;
}
}
Ok(epoll_ready_events)
}
@ -101,6 +109,13 @@ impl FileDescription for AnonSocket {
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
if let Some(peer_fd) = self.peer_fd().upgrade() {
// If the current readbuf is non-empty when the file description is closed,
// notify the peer that data lost has happened in current file description.
if let Some(readbuf) = &self.readbuf {
if !readbuf.borrow().buf.is_empty() {
peer_fd.downcast::<AnonSocket>().unwrap().peer_lost_data.set(true);
}
}
// Notify peer fd that close has happened, since that can unblock reads and writes.
ecx.check_and_update_readiness(&peer_fd)?;
}
@ -290,11 +305,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let fd0 = fds.new_ref(AnonSocket {
readbuf: Some(RefCell::new(Buffer::new())),
peer_fd: OnceCell::new(),
peer_lost_data: Cell::new(false),
is_nonblock: is_sock_nonblock,
});
let fd1 = fds.new_ref(AnonSocket {
readbuf: Some(RefCell::new(Buffer::new())),
peer_fd: OnceCell::new(),
peer_lost_data: Cell::new(false),
is_nonblock: is_sock_nonblock,
});
@ -340,10 +357,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let fd0 = fds.new_ref(AnonSocket {
readbuf: Some(RefCell::new(Buffer::new())),
peer_fd: OnceCell::new(),
peer_lost_data: Cell::new(false),
is_nonblock: false,
});
let fd1 = fds.new_ref(AnonSocket {
readbuf: None,
peer_fd: OnceCell::new(),
peer_lost_data: Cell::new(false),
is_nonblock: false,
});
let fd1 =
fds.new_ref(AnonSocket { readbuf: None, peer_fd: OnceCell::new(), is_nonblock: false });
// Make the file descriptions point to each other.
fd0.downcast::<AnonSocket>().unwrap().peer_fd.set(fd1.downgrade()).unwrap();

View File

@ -20,6 +20,7 @@ fn main() {
test_pointer();
test_two_same_fd_in_same_epoll_instance();
test_epoll_wait_maxevent_zero();
test_socketpair_epollerr();
test_epoll_lost_events();
test_ready_list_fetching_logic();
}
@ -551,6 +552,43 @@ fn test_epoll_wait_maxevent_zero() {
assert_eq!(res, -1);
}
fn test_socketpair_epollerr() {
// Create an epoll instance.
let epfd = unsafe { libc::epoll_create1(0) };
assert_ne!(epfd, -1);
// Create a socketpair instance.
let mut fds = [-1, -1];
let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(res, 0);
// Write to fd[0]
let data = "abcde".as_bytes().as_ptr();
let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
assert_eq!(res, 5);
// Close fds[1].
// EPOLLERR will be triggered if we close peer fd that still has data in its read buffer.
let res = unsafe { libc::close(fds[1]) };
assert_eq!(res, 0);
// Register fd[1] with EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP
let mut ev = libc::epoll_event {
events: (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET | libc::EPOLLRDHUP) as _,
u64: u64::try_from(fds[1]).unwrap(),
};
let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[0], &mut ev) };
assert_ne!(res, -1);
// Check result from epoll_wait.
let expected_event = u32::try_from(
libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLHUP | libc::EPOLLRDHUP | libc::EPOLLERR,
)
.unwrap();
let expected_value = u64::try_from(fds[1]).unwrap();
check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]);
}
// This is a test for https://github.com/rust-lang/miri/issues/3812,
// epoll can lose events if they don't fit in the output buffer.
fn test_epoll_lost_events() {