about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/miri/src/shims/unix/linux/epoll.rs23
-rw-r--r--src/tools/miri/src/shims/unix/unnamed_socket.rs28
-rw-r--r--src/tools/miri/tests/pass-dep/libc/libc-epoll.rs38
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 f58a6d294b9..fb1e0afdf9e 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 5f4640a2d84..745f27398d0 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<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();
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 763263b4630..e28cafd3c28 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() {