From 483120d6cee9607f0a95dda469e800161d4e3441 Mon Sep 17 00:00:00 2001 From: tiif Date: Sat, 17 Aug 2024 16:03:59 +0800 Subject: [PATCH] Add EPOLLER support --- src/tools/miri/src/shims/unix/linux/epoll.rs | 23 +++++++++-- .../miri/src/shims/unix/unnamed_socket.rs | 28 ++++++++++++-- .../miri/tests/pass-dep/libc/libc-epoll.rs | 38 +++++++++++++++++++ 3 files changed, 83 insertions(+), 6 deletions(-) diff --git a/src/tools/miri/src/shims/unix/linux/epoll.rs b/src/tools/miri/src/shims/unix/linux/epoll.rs index f58a6d294b940..fb1e0afdf9ed7 100644 --- a/src/tools/miri/src/shims/unix/linux/epoll.rs +++ b/src/tools/miri/src/shims/unix/linux/epoll.rs @@ -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}", diff --git a/src/tools/miri/src/shims/unix/unnamed_socket.rs b/src/tools/miri/src/shims/unix/unnamed_socket.rs index 5f4640a2d840c..745f27398d0a8 100644 --- a/src/tools/miri/src/shims/unix/unnamed_socket.rs +++ b/src/tools/miri/src/shims/unix/unnamed_socket.rs @@ -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, + /// 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, 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::().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::().unwrap().peer_fd.set(fd1.downgrade()).unwrap(); diff --git a/src/tools/miri/tests/pass-dep/libc/libc-epoll.rs b/src/tools/miri/tests/pass-dep/libc/libc-epoll.rs index 763263b463097..e28cafd3c285b 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-epoll.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-epoll.rs @@ -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() {