//@only-target: linux android illumos

use std::convert::TryInto;

#[path = "../../utils/libc.rs"]
mod libc_utils;
use libc_utils::epoll::*;
use libc_utils::*;

fn main() {
    test_epoll_socketpair();
    test_epoll_socketpair_both_sides();
    test_socketpair_read();
    test_epoll_eventfd();

    test_event_overwrite();
    test_not_fully_closed_fd();
    test_closed_fd();
    test_two_epoll_instance();
    test_no_notification_for_unregister_flag();
    test_epoll_ctl_mod();
    test_epoll_ctl_del();
    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();
    test_epoll_ctl_epfd_equal_fd();
    test_epoll_ctl_notification();
    test_issue_3858();
    test_issue_4374();
    test_issue_4374_reads();
}

#[track_caller]
fn check_epoll_wait<const N: usize>(epfd: i32, expected_notifications: &[(u32, u64)]) {
    let epoll_event = libc::epoll_event { events: 0, u64: 0 };
    let mut array: [libc::epoll_event; N] = [epoll_event; N];
    let maxsize = N;
    let array_ptr = array.as_mut_ptr();
    let res = unsafe { libc::epoll_wait(epfd, array_ptr, maxsize.try_into().unwrap(), 0) };
    if res < 0 {
        panic!("epoll_wait failed: {}", std::io::Error::last_os_error());
    }
    let got_notifications =
        unsafe { std::slice::from_raw_parts(array_ptr, res.try_into().unwrap()) };
    let got_notifications = got_notifications.iter().map(|e| (e.events, e.u64)).collect::<Vec<_>>();
    assert_eq!(got_notifications, expected_notifications, "got wrong notifications");
}

fn test_epoll_socketpair() {
    // Create an epoll instance.
    let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap();

    // Create a socketpair instance.
    let mut fds = [-1, -1];
    errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) });

    // Write to fd[0]
    write_all_from_slice(fds[0], b"abcde").unwrap();

    // Register fd[1] with EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP
    epoll_ctl_add(epfd, fds[1], EPOLLIN | EPOLLOUT | EPOLLET | EPOLLRDHUP).unwrap();

    // Check result from epoll_wait.
    check_epoll_wait_noblock::<8>(epfd, &[Ev { data: fds[1], events: EPOLLIN | EPOLLOUT }]);

    // Check that this is indeed using "ET" (edge-trigger) semantics: a second epoll should return nothing.
    check_epoll_wait_noblock::<8>(epfd, &[]);

    // Write some more to fd[0].
    write_all_from_slice(fds[0], b"abcde").unwrap();

    // This did not change the readiness of fd[1], so we should get no event.
    // However, Linux seems to always deliver spurious events to the peer on each write,
    // so we match that.
    check_epoll_wait_noblock::<8>(epfd, &[Ev { data: fds[1], events: EPOLLIN | EPOLLOUT }]);

    // Close the peer socketpair.
    errno_check(unsafe { libc::close(fds[0]) });

    // Check result from epoll_wait. We expect to get a read, write, HUP notification from the close
    // since closing an FD always unblocks reads and writes on its peer.
    check_epoll_wait_noblock::<8>(
        epfd,
        &[Ev { data: fds[1], events: EPOLLIN | EPOLLOUT | EPOLLHUP | EPOLLRDHUP }],
    );
}

// This test first registers a file description with a flag that does not lead to notification,
// then EPOLL_CTL_MOD to add another flag that will lead to notification.
// Also check that the new data value set via MOD is applied properly.
fn test_epoll_ctl_mod() {
    // Create an epoll instance.
    let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap();

    // Create a socketpair instance.
    let mut fds = [-1, -1];
    errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) });

    // Register fd[1] with EPOLLIN|EPOLLET, and data of "0".
    epoll_ctl(epfd, EPOLL_CTL_ADD, fds[1], Ev { events: EPOLLIN | EPOLLET, data: 0 }).unwrap();

    // Check result from epoll_wait. No notification would be returned.
    check_epoll_wait_noblock::<8>(epfd, &[]);

    // Use EPOLL_CTL_MOD to change to EPOLLOUT flag and data.
    epoll_ctl(epfd, EPOLL_CTL_MOD, fds[1], Ev { events: EPOLLOUT | EPOLLET, data: 1 }).unwrap();

    // Check result from epoll_wait. EPOLLOUT notification and new data is expected.
    check_epoll_wait_noblock::<8>(epfd, &[Ev { events: EPOLLOUT, data: 1 }]);

    // Write to fds[1] and read from fds[0] to make the notification ready again
    // (relying on there always being an event when the buffer gets emptied).
    write_all_from_slice(fds[1], "abc".as_bytes()).unwrap();
    read_all_into_array::<3>(fds[0]).unwrap();

    // Now that the event is already ready, change the "data" value.
    epoll_ctl(epfd, EPOLL_CTL_MOD, fds[1], Ev { events: EPOLLOUT | EPOLLET, data: 2 }).unwrap();

    // Receive event, with latest data value.
    check_epoll_wait_noblock::<8>(epfd, &[Ev { events: EPOLLOUT, data: 2 }]);

    // Do another update that changes nothing.
    epoll_ctl(epfd, EPOLL_CTL_MOD, fds[1], Ev { events: EPOLLOUT | EPOLLET, data: 2 }).unwrap();

    // This re-triggers the event, even if it's the same flags as before.
    check_epoll_wait_noblock::<8>(epfd, &[Ev { events: EPOLLOUT, data: 2 }]);
}

fn test_epoll_ctl_del() {
    // Create an epoll instance.
    let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap();

    // Create a socketpair instance.
    let mut fds = [-1, -1];
    errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) });

    // Write to fd[0]
    let data = b"abcde".as_ptr();
    let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) };
    assert_eq!(res, 5);

    // Register fd[1] with EPOLLIN|EPOLLOUT|EPOLLET
    let mut ev = libc::epoll_event {
        events: (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET) as u32,
        u64: u64::try_from(fds[1]).unwrap(),
    };
    let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[1], &mut ev) };
    assert_eq!(res, 0);

    // Test EPOLL_CTL_DEL.
    let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_DEL, fds[1], &mut ev) };
    assert_eq!(res, 0);
    check_epoll_wait::<8>(epfd, &[]);
}

// This test is for one fd registered under two different epoll instance.
fn test_two_epoll_instance() {
    // Create two epoll instance.
    let epfd1 = unsafe { libc::epoll_create1(0) };
    assert_ne!(epfd1, -1);
    let epfd2 = unsafe { libc::epoll_create1(0) };
    assert_ne!(epfd2, -1);

    // Create a socketpair instance.
    let mut fds = [-1, -1];
    errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) });

    // Write to the socketpair.
    let data = b"abcde".as_ptr();
    let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) };
    assert_eq!(res, 5);

    // Register one side of the socketpair with EPOLLIN | EPOLLOUT | EPOLLET.
    epoll_ctl_add(epfd1, fds[1], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap();
    epoll_ctl_add(epfd2, fds[1], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap();

    // Notification should be received from both instance of epoll.
    let expected_event = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap();
    let expected_value = u64::try_from(fds[1]).unwrap();
    check_epoll_wait::<8>(epfd1, &[(expected_event, expected_value)]);
    check_epoll_wait::<8>(epfd2, &[(expected_event, expected_value)]);
}

// This test is for two same file description registered under the same epoll instance through dup.
// Notification should be provided for both.
fn test_two_same_fd_in_same_epoll_instance() {
    // Create an epoll instance.
    let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap();

    // Create a socketpair instance.
    let mut fds = [-1, -1];
    errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) });

    // Dup the fd.
    let newfd = unsafe { libc::dup(fds[1]) };
    assert_ne!(newfd, -1);

    // Register both fd to the same epoll instance.
    let mut ev = libc::epoll_event {
        events: (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).cast_unsigned(),
        u64: 5u64,
    };
    let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[1], &mut ev) };
    assert_eq!(res, 0);
    let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, newfd, &mut ev) };
    assert_eq!(res, 0);

    // Write to the socketpair.
    let data = b"abcde".as_ptr();
    let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) };
    assert_eq!(res, 5);

    // Two notification should be received.
    let expected_event = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap();
    let expected_value = 5u64;
    check_epoll_wait::<8>(
        epfd,
        &[(expected_event, expected_value), (expected_event, expected_value)],
    );
}

fn test_epoll_eventfd() {
    // Create an eventfd instance.
    let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC;
    let fd = errno_result(unsafe { libc::eventfd(0, flags) }).unwrap();

    // Write 1 to the eventfd instance.
    libc_utils::write_all_from_slice(fd, &1_u64.to_ne_bytes()).unwrap();

    // Create an epoll instance.
    let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap();

    // Register eventfd with EPOLLIN | EPOLLOUT | EPOLLET
    epoll_ctl_add(epfd, fd, libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap();

    // Check result from epoll_wait.
    let expected_event = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap();
    let expected_value = u64::try_from(fd).unwrap();
    check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]);

    // Write 0 to the eventfd.
    libc_utils::write_all_from_slice(fd, &0_u64.to_ne_bytes()).unwrap();

    // This does not change the status, so we should get no event.
    // However, Linux performs a spurious wakeup.
    check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]);

    // Read from the eventfd.
    libc_utils::read_all_into_array::<8>(fd).unwrap();

    // This consumes the event, so the read status is gone. However, deactivation
    // does not trigger an event.
    // Still, we see a spurious wakeup.
    let expected_event = u32::try_from(libc::EPOLLOUT).unwrap();
    check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]);

    // Write the maximum possible value.
    let sized_8_data: [u8; 8] = (u64::MAX - 1).to_ne_bytes();
    let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
    assert_eq!(res, 8);

    // This reactivates reads, therefore triggering an event. Writing is no longer possible.
    let expected_event = u32::try_from(libc::EPOLLIN).unwrap();
    check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]);
}

// When read/write happened on one side of the socketpair, only the other side will be notified.
fn test_epoll_socketpair_both_sides() {
    // Create an epoll instance.
    let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap();

    // Create a socketpair instance.
    let mut fds = [-1, -1];
    errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) });

    // Register both fd to the same epoll instance.
    epoll_ctl_add(epfd, fds[0], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap();
    epoll_ctl_add(epfd, fds[1], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap();

    // Write to fds[1].
    // (We do the write after the register here, unlike in `test_epoll_socketpair`, to ensure
    // we cover both orders in which this could be done.)
    let data = b"abcde".as_ptr();
    let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
    assert_eq!(res, 5);

    // Two notification should be received.
    let expected_event0 = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap();
    let expected_value0 = fds[0] as u64;
    let expected_event1 = u32::try_from(libc::EPOLLOUT).unwrap();
    let expected_value1 = fds[1] as u64;
    check_epoll_wait::<8>(
        epfd,
        &[(expected_event0, expected_value0), (expected_event1, expected_value1)],
    );

    // Read from fds[0].
    let mut buf: [u8; 5] = [0; 5];
    let res =
        unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
    assert_eq!(res, 5);
    assert_eq!(buf, *b"abcde");

    // The state of fds[1] does not change (was writable, is writable).
    // However, we force a spurious wakeup as the read buffer just got emptied.
    // fds[0] lost its readability, but becoming less active is not considered an "edge".
    check_epoll_wait::<8>(epfd, &[(expected_event1, expected_value1)]);
}

// When file description is fully closed, epoll_wait should not provide any notification for
// that file description.
fn test_closed_fd() {
    // Create an epoll instance.
    let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap();

    // Create an eventfd instance.
    let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC;
    let fd = errno_result(unsafe { libc::eventfd(0, flags) }).unwrap();

    // Register eventfd with EPOLLIN | EPOLLOUT | EPOLLET
    epoll_ctl_add(epfd, fd, libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap();

    // Write to the eventfd instance.
    let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes();
    let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
    assert_eq!(res, 8);

    // Close the eventfd.
    errno_check(unsafe { libc::close(fd) });

    // No notification should be provided because the file description is closed.
    check_epoll_wait::<8>(epfd, &[]);
}

// When a certain file descriptor registered with epoll is closed, but the underlying file description
// is not closed, notification should still be provided.
//
// This is a quirk of epoll being described in https://man7.org/linux/man-pages/man7/epoll.7.html
// A file descriptor is removed from an interest list only after all the file descriptors
// referring to the underlying open file description have been closed.
fn test_not_fully_closed_fd() {
    // Create an epoll instance.
    let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap();

    // Create an eventfd instance.
    let fd =
        errno_result(unsafe { libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC) }).unwrap();

    // Dup the fd.
    let newfd = errno_result(unsafe { libc::dup(fd) }).unwrap();

    // Register eventfd with EPOLLIN | EPOLLOUT | EPOLLET
    epoll_ctl_add(epfd, fd, libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap();

    // Close the original fd that being used to register with epoll.
    errno_check(unsafe { libc::close(fd) });

    // Notification should still be provided because the file description is not closed.
    let expected_event = u32::try_from(libc::EPOLLOUT).unwrap();
    let expected_value = fd as u64;
    check_epoll_wait::<1>(epfd, &[(expected_event, expected_value)]);

    // Write to the eventfd instance to produce notification.
    let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes();
    let res =
        unsafe { libc_utils::write_all(newfd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
    assert_eq!(res, 8);

    // Close the dupped fd.
    errno_check(unsafe { libc::close(newfd) });

    // No notification should be provided.
    check_epoll_wait::<1>(epfd, &[]);
}

// Each time a notification is provided, it should reflect the file description's readiness
// at the moment the latest event occurred.
fn test_event_overwrite() {
    // Create an eventfd instance.
    let fd =
        errno_result(unsafe { libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC) }).unwrap();

    // Write to the eventfd instance.
    let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes();
    let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
    assert_eq!(res, 8);

    // Create an epoll instance.
    let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap();

    // Register eventfd with EPOLLIN | EPOLLOUT | EPOLLET
    let mut ev = libc::epoll_event {
        events: (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).cast_unsigned(),
        u64: u64::try_from(fd).unwrap(),
    };
    let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd, &mut ev) };
    assert_eq!(res, 0);

    // Read from the eventfd instance.
    let mut buf: [u8; 8] = [0; 8];
    let res = unsafe { libc::read(fd, buf.as_mut_ptr().cast(), 8) };
    assert_eq!(res, 8);

    // Check result from epoll_wait.
    let expected_event = u32::try_from(libc::EPOLLOUT).unwrap();
    let expected_value = u64::try_from(fd).unwrap();
    check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]);
}

// An epoll notification will be provided for every succesful read in a socketpair.
// This behaviour differs from the real system.
fn test_socketpair_read() {
    // Create an epoll instance.
    let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap();

    // Create a socketpair instance.
    let mut fds = [-1, -1];
    errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) });

    // Register both fd to the same epoll instance.
    let mut ev = libc::epoll_event {
        events: (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).cast_unsigned(),
        u64: fds[0] as u64,
    };
    let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[0], &mut ev) };
    assert_eq!(res, 0);
    let mut ev = libc::epoll_event {
        events: (libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).cast_unsigned(),
        u64: fds[1] as u64,
    };
    let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[1], &mut ev) };
    assert_eq!(res, 0);

    // Write a bunch of data bytes to fds[1].
    let data = [42u8; 1024];
    let res =
        unsafe { libc_utils::write_all(fds[1], data.as_ptr() as *const libc::c_void, data.len()) };
    assert_eq!(res, data.len() as isize);

    // Two notification should be received.
    let expected_event0 = u32::try_from(libc::EPOLLIN | libc::EPOLLOUT).unwrap();
    let expected_value0 = fds[0] as u64;
    let expected_event1 = u32::try_from(libc::EPOLLOUT).unwrap();
    let expected_value1 = fds[1] as u64;
    check_epoll_wait::<8>(
        epfd,
        &[(expected_event0, expected_value0), (expected_event1, expected_value1)],
    );

    // Read some of the data from fds[0].
    let mut buf = [0; 512];
    let res =
        unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
    assert_eq!(res, buf.len() as isize);

    // fds[1] did not change, it is still writable, so we get no event.
    let expected_event = u32::try_from(libc::EPOLLOUT).unwrap();
    let expected_value = fds[1] as u64;
    check_epoll_wait::<8>(epfd, &[]);

    // Read until the buffer is empty.
    let rest = data.len() - buf.len();
    let res =
        unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), rest as libc::size_t) };
    assert_eq!(res, rest as isize);

    // Now we get a notification that fds[1] can be written. This is spurious since it
    // could already be written before, but Linux seems to always emit a notification for
    // the writer when a read empties the buffer.
    check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]);
}

// This is to test whether flag that we don't register won't trigger notification.
fn test_no_notification_for_unregister_flag() {
    // Create an epoll instance.
    let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap();

    // Create a socketpair instance.
    let mut fds = [-1, -1];
    errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) });

    // Register fd[0] with EPOLLOUT|EPOLLET.
    let mut ev = libc::epoll_event {
        events: (libc::EPOLLOUT | libc::EPOLLET).cast_unsigned(),
        u64: u64::try_from(fds[0]).unwrap(),
    };
    let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fds[0], &mut ev) };
    assert_eq!(res, 0);

    // Write to fd[1].
    let data = b"abcde".as_ptr();
    let res: i32 = unsafe {
        libc_utils::write_all(fds[1], data as *const libc::c_void, 5).try_into().unwrap()
    };
    assert_eq!(res, 5);

    // Check result from epoll_wait. Since we didn't register EPOLLIN flag, the notification won't
    // contain EPOLLIN even though fds[0] is now readable.
    let expected_event = u32::try_from(libc::EPOLLOUT).unwrap();
    let expected_value = u64::try_from(fds[0]).unwrap();
    check_epoll_wait::<8>(epfd, &[(expected_event, expected_value)]);
}

fn test_epoll_wait_maxevent_zero() {
    // Create an epoll instance.
    let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap();
    // It is ok to use a dangling pointer here because it will error out before the
    // pointer actually gets accessed.
    let array_ptr = std::ptr::without_provenance_mut::<libc::epoll_event>(0x100);
    let res = unsafe { libc::epoll_wait(epfd, array_ptr, 0, 0) };
    let e = std::io::Error::last_os_error();
    assert_eq!(e.raw_os_error(), Some(libc::EINVAL));
    assert_eq!(res, -1);
}

fn test_socketpair_epollerr() {
    // Create an epoll instance.
    let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap();

    // Create a socketpair instance.
    let mut fds = [-1, -1];
    errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) });

    // Write to fd[0]
    let data = b"abcde".as_ptr();
    let res = unsafe { libc_utils::write_all(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.
    errno_check(unsafe { libc::close(fds[1]) });

    // Register fd[1] with EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP
    epoll_ctl_add(epfd, fds[0], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET | libc::EPOLLRDHUP)
        .unwrap();

    // 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[0]).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() {
    // Create an epoll instance.
    let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap();

    // Create a socketpair instance.
    let mut fds = [-1, -1];
    errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) });

    // Register both fd to the same epoll instance.
    epoll_ctl_add(epfd, fds[0], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap();
    epoll_ctl_add(epfd, fds[1], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap();

    // Two notification should be received. But we only provide buffer for one event.
    let expected_event0 = u32::try_from(libc::EPOLLOUT).unwrap();
    let expected_value0 = fds[0] as u64;
    check_epoll_wait::<1>(epfd, &[(expected_event0, expected_value0)]);

    // Previous event should be returned for the second epoll_wait.
    let expected_event1 = u32::try_from(libc::EPOLLOUT).unwrap();
    let expected_value1 = fds[1] as u64;
    check_epoll_wait::<1>(epfd, &[(expected_event1, expected_value1)]);
}

// This is testing if closing an fd that is already in ready list will cause an empty entry in
// returned notification.
// Related discussion in https://github.com/rust-lang/miri/pull/3818#discussion_r1720679440.
fn test_ready_list_fetching_logic() {
    // Create an epoll instance.
    let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap();

    // Create two eventfd instances.
    let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC;
    let fd0 = errno_result(unsafe { libc::eventfd(0, flags) }).unwrap();
    let fd1 = errno_result(unsafe { libc::eventfd(0, flags) }).unwrap();

    // Register both fd to the same epoll instance. At this point, both of them are on the ready list.
    epoll_ctl_add(epfd, fd0, libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap();
    epoll_ctl_add(epfd, fd1, libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap();

    // Close fd0 so the first entry in the ready list will be empty.
    errno_check(unsafe { libc::close(fd0) });

    // Notification for fd1 should be returned.
    let expected_event1 = u32::try_from(libc::EPOLLOUT).unwrap();
    let expected_value1 = fd1 as u64;
    check_epoll_wait::<1>(epfd, &[(expected_event1, expected_value1)]);
}

// In epoll_ctl, if the value of epfd equals to fd, EFAULT should be returned.
// (The docs say loops cause EINVAL, but experiments show it is EFAULT.)
fn test_epoll_ctl_epfd_equal_fd() {
    // Create an epoll instance.
    let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap();

    let array_ptr = std::ptr::without_provenance_mut::<libc::epoll_event>(0x100);
    let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, epfd, array_ptr) };
    let e = std::io::Error::last_os_error();
    assert_eq!(e.raw_os_error(), Some(libc::EFAULT));
    assert_eq!(res, -1);
}

// We previously used check_and_update_readiness the moment a file description is registered in an
// epoll instance. But this has an unfortunate side effect of returning notification to another
// epfd that shouldn't receive notification.
fn test_epoll_ctl_notification() {
    // Create an epoll instance.
    let epfd0 = unsafe { libc::epoll_create1(0) };
    assert_ne!(epfd0, -1);

    // Create a socketpair instance.
    let mut fds = [-1, -1];
    errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) });

    // Register one side of the socketpair with epoll.
    epoll_ctl_add(epfd0, fds[0], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap();

    // epoll_wait to clear notification for epfd0.
    let expected_event = u32::try_from(libc::EPOLLOUT).unwrap();
    let expected_value = fds[0] as u64;
    check_epoll_wait::<1>(epfd0, &[(expected_event, expected_value)]);

    // Create another epoll instance.
    let epfd1 = unsafe { libc::epoll_create1(0) };
    assert_ne!(epfd1, -1);

    // Register the same file description for epfd1.
    epoll_ctl_add(epfd1, fds[0], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap();
    check_epoll_wait::<1>(epfd1, &[(expected_event, expected_value)]);

    // Previously this epoll_wait will receive a notification, but we shouldn't return notification
    // for this epfd, because there is no I/O event between the two epoll_wait.
    check_epoll_wait::<1>(epfd0, &[]);
}

// Test for ICE caused by weak epoll interest upgrade succeed, but the attempt to retrieve
// the epoll instance based on the epoll file descriptor value failed. EpollEventInterest
// should store a WeakFileDescriptionRef instead of the file descriptor number, so if the
// epoll instance is duped, it'd still be usable after `close` is called on the original
// epoll file descriptor.
// https://github.com/rust-lang/miri/issues/3858
fn test_issue_3858() {
    // Create an eventfd instance.
    let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC;
    let fd = errno_result(unsafe { libc::eventfd(0, flags) }).unwrap();

    // Create an epoll instance.
    let epfd = errno_result(unsafe { libc::epoll_create1(0) }).unwrap();

    // Register eventfd with EPOLLIN | EPOLLET.
    let mut ev = libc::epoll_event {
        events: (libc::EPOLLIN | libc::EPOLLET).cast_unsigned(),
        u64: u64::try_from(fd).unwrap(),
    };
    let res = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd, &mut ev) };
    assert_eq!(res, 0);

    // Dup the epoll instance.
    let newfd = unsafe { libc::dup(epfd) };
    assert_ne!(newfd, -1);

    // Close the old epoll instance, so the new FD is now the only FD.
    errno_check(unsafe { libc::close(epfd) });

    // Write to the eventfd instance.
    let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes();
    let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
    assert_eq!(res, 8);
}

/// Ensure that if a socket becomes un-writable, we don't see it any more.
fn test_issue_4374() {
    // Create an epoll instance.
    let epfd0 = unsafe { libc::epoll_create1(0) };
    assert_ne!(epfd0, -1);

    // Create a socketpair instance, make it non-blocking.
    let mut fds = [-1, -1];
    errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) });
    assert_eq!(unsafe { libc::fcntl(fds[0], libc::F_SETFL, libc::O_NONBLOCK) }, 0);
    assert_eq!(unsafe { libc::fcntl(fds[1], libc::F_SETFL, libc::O_NONBLOCK) }, 0);

    // Register fds[0] with epoll while it is writable (but not readable).
    epoll_ctl_add(epfd0, fds[0], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap();

    // Fill up fds[0] so that it is not writable any more.
    let zeros = [0u8; 512];
    loop {
        let res = unsafe {
            libc_utils::write_all(fds[0], zeros.as_ptr() as *const libc::c_void, zeros.len())
        };
        if res < 0 {
            break;
        }
    }

    // This should have canceled the previous readiness, so now we get nothing.
    check_epoll_wait::<1>(epfd0, &[]);
}

/// Same as above, but for becoming un-readable.
fn test_issue_4374_reads() {
    // Create an epoll instance.
    let epfd0 = unsafe { libc::epoll_create1(0) };
    assert_ne!(epfd0, -1);

    // Create a socketpair instance, make it non-blocking.
    let mut fds = [-1, -1];
    errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) });
    assert_eq!(unsafe { libc::fcntl(fds[0], libc::F_SETFL, libc::O_NONBLOCK) }, 0);
    assert_eq!(unsafe { libc::fcntl(fds[1], libc::F_SETFL, libc::O_NONBLOCK) }, 0);

    // Write to fds[1] so that fds[0] becomes readable.
    let data = b"abcde".as_ptr();
    let res: i32 = unsafe {
        libc_utils::write_all(fds[1], data as *const libc::c_void, 5).try_into().unwrap()
    };
    assert_eq!(res, 5);

    // Register fds[0] with epoll while it is readable.
    epoll_ctl_add(epfd0, fds[0], libc::EPOLLIN | libc::EPOLLOUT | libc::EPOLLET).unwrap();

    // Read fds[0] so it is no longer readable.
    let mut buf = [0u8; 512];
    let res = unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr() as *mut libc::c_void, 5) };
    assert_eq!(res, 5);

    // We should now still see a notification, but only about it being writable.
    let expected_event = u32::try_from(libc::EPOLLOUT).unwrap();
    let expected_value = fds[0] as u64;
    check_epoll_wait::<1>(epfd0, &[(expected_event, expected_value)]);
}
