Auto merge of #3829 - tiif:edgecase, r=oli-obk

epoll: handle edge case for epoll_ctl

There is a test case that revealed that our implementation differs from the real system:
 - Set up an epoll watching the FD
- Call epoll_wait
- Set up another epoll watching the same FD
- Call epoll_wait on the first epoll. Nothing should be reported!

This happened because, in ``epoll_ctl``, we used ``check_and_update_readiness``, which is a function that would return notification for all epoll file description that registered a particular file description. But we shouldn't do that because no notification should be returned if there is no I/O activity between two ``epoll_wait`` (every first ``epoll_wait`` that happens after ``epoll_ctl`` is an exception, we should return notification that reflects the readiness of file description).

r? `@oli-obk`
This commit is contained in:
bors 2024-08-24 12:34:36 +00:00
commit dbfd066ed8
4 changed files with 105 additions and 24 deletions

View File

@ -269,10 +269,10 @@ fn epoll_ctl(
let mut interest_list = epoll_file_description.interest_list.borrow_mut();
let ready_list = &epoll_file_description.ready_list;
let Some(file_descriptor) = this.machine.fds.get(fd) else {
let Some(fd_ref) = this.machine.fds.get(fd) else {
return Ok(Scalar::from_i32(this.fd_not_found()?));
};
let id = file_descriptor.get_id();
let id = fd_ref.get_id();
if op == epoll_ctl_add || op == epoll_ctl_mod {
// Read event bitmask and data from epoll_event passed by caller.
@ -332,7 +332,6 @@ fn epoll_ctl(
}
}
let id = file_descriptor.get_id();
// Create an epoll_interest.
let interest = Rc::new(RefCell::new(EpollEventInterest {
file_descriptor: fd,
@ -344,7 +343,7 @@ fn epoll_ctl(
if op == epoll_ctl_add {
// Insert an epoll_interest to global epoll_interest list.
this.machine.epoll_interests.insert_epoll_interest(id, Rc::downgrade(&interest));
interest_list.insert(epoll_key, interest);
interest_list.insert(epoll_key, Rc::clone(&interest));
} else {
// Directly modify the epoll_interest so the global epoll_event_interest table
// will be updated too.
@ -353,9 +352,9 @@ fn epoll_ctl(
epoll_interest.data = data;
}
// Readiness will be updated immediately when the epoll_event_interest is added or modified.
this.check_and_update_readiness(&file_descriptor)?;
// Notification will be returned for current epfd if there is event in the file
// descriptor we registered.
check_and_update_one_event_interest(&fd_ref, interest, id, this)?;
return Ok(Scalar::from_i32(0));
} else if op == epoll_ctl_del {
let epoll_key = (id, fd);
@ -489,25 +488,9 @@ fn check_and_update_readiness(&self, fd_ref: &FileDescriptionRef) -> InterpResul
let id = fd_ref.get_id();
// Get a list of EpollEventInterest that is associated to a specific file description.
if let Some(epoll_interests) = this.machine.epoll_interests.get_epoll_interest(id) {
let epoll_ready_events = fd_ref.get_epoll_ready_events()?;
// Get the bitmask of ready events.
let ready_events = epoll_ready_events.get_event_bitmask(this);
for weak_epoll_interest in epoll_interests {
if let Some(epoll_interest) = weak_epoll_interest.upgrade() {
// This checks if any of the events specified in epoll_event_interest.events
// match those in ready_events.
let epoll_event_interest = epoll_interest.borrow();
let flags = epoll_event_interest.events & ready_events;
// If there is any event that we are interested in being specified as ready,
// insert an epoll_return to the ready list.
if flags != 0 {
let epoll_key = (id, epoll_event_interest.file_descriptor);
let ready_list = &mut epoll_event_interest.ready_list.borrow_mut();
let event_instance =
EpollEventInstance::new(flags, epoll_event_interest.data);
ready_list.insert(epoll_key, event_instance);
}
check_and_update_one_event_interest(fd_ref, epoll_interest, id, this)?;
}
}
}
@ -532,3 +515,30 @@ fn ready_list_next(
}
return None;
}
/// This helper function checks whether an epoll notification should be triggered for a specific
/// epoll_interest and, if necessary, triggers the notification. Unlike check_and_update_readiness,
/// this function sends a notification to only one epoll instance.
fn check_and_update_one_event_interest<'tcx>(
fd_ref: &FileDescriptionRef,
interest: Rc<RefCell<EpollEventInterest>>,
id: FdId,
ecx: &MiriInterpCx<'tcx>,
) -> InterpResult<'tcx> {
// Get the bitmask of ready events for a file description.
let ready_events_bitmask = fd_ref.get_epoll_ready_events()?.get_event_bitmask(ecx);
let epoll_event_interest = interest.borrow();
// This checks if any of the events specified in epoll_event_interest.events
// match those in ready_events.
let flags = epoll_event_interest.events & ready_events_bitmask;
// If there is any event that we are interested in being specified as ready,
// insert an epoll_return to the ready list.
if flags != 0 {
let epoll_key = (id, epoll_event_interest.file_descriptor);
let ready_list = &mut epoll_event_interest.ready_list.borrow_mut();
let event_instance = EpollEventInstance::new(flags, epoll_event_interest.data);
// Triggers the notification by inserting it to the ready list.
ready_list.insert(epoll_key, event_instance);
}
Ok(())
}

View File

@ -0,0 +1,18 @@
//@only-target-linux
// This is a test for registering unsupported fd with epoll.
// Register epoll fd with epoll is allowed in real system, but we do not support this.
fn main() {
// Create two epoll instance.
let epfd0 = unsafe { libc::epoll_create1(0) };
assert_ne!(epfd0, -1);
let epfd1 = unsafe { libc::epoll_create1(0) };
assert_ne!(epfd1, -1);
// Register epoll with epoll.
let mut ev =
libc::epoll_event { events: (libc::EPOLLIN | libc::EPOLLET) as _, u64: epfd1 as u64 };
let res = unsafe { libc::epoll_ctl(epfd0, libc::EPOLL_CTL_ADD, epfd1, &mut ev) };
//~^ERROR: epoll does not support this file description
assert_eq!(res, 0);
}

View File

@ -0,0 +1,14 @@
error: unsupported operation: epoll: epoll does not support this file description
--> $DIR/libc_epoll_unsupported_fd.rs:LL:CC
|
LL | let res = unsafe { libc::epoll_ctl(epfd0, libc::EPOLL_CTL_ADD, epfd1, &mut ev) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ epoll: epoll does not support this file description
|
= help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support
= note: BACKTRACE:
= note: inside `main` at $DIR/libc_epoll_unsupported_fd.rs:LL:CC
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error

View File

@ -22,6 +22,7 @@ fn main() {
test_epoll_lost_events();
test_ready_list_fetching_logic();
test_epoll_ctl_epfd_equal_fd();
test_epoll_ctl_notification();
}
// Using `as` cast since `EPOLLET` wraps around
@ -644,3 +645,41 @@ fn test_epoll_ctl_epfd_equal_fd() {
assert_eq!(e.raw_os_error(), Some(libc::EINVAL));
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];
let res = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
assert_eq!(res, 0);
// Register one side of the socketpair with epoll.
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fds[0] as u64 };
let res = unsafe { libc::epoll_ctl(epfd0, libc::EPOLL_CTL_ADD, fds[0], &mut ev) };
assert_eq!(res, 0);
// 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.
let mut ev = libc::epoll_event { events: EPOLL_IN_OUT_ET, u64: fds[0] as u64 };
let res = unsafe { libc::epoll_ctl(epfd1, libc::EPOLL_CTL_ADD, fds[0], &mut ev) };
assert_eq!(res, 0);
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, &[]);
}