Skip to content

Follow up PR for eventfd shim #3661

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 15 additions & 13 deletions src/shims/unix/linux/eventfd.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Linux `eventfd` implementation.
use std::io;
use std::io::{Error, ErrorKind};
use std::mem;

use rustc_target::abi::Endian;

Expand All @@ -9,8 +10,8 @@ use crate::{concurrency::VClock, *};

use self::shims::unix::fd::FileDescriptor;

/// Minimum size of u8 array to hold u64 value.
const U64_MIN_ARRAY_SIZE: usize = 8;
// We'll only do reads and writes in chunks of size u64.
const U64_ARRAY_SIZE: usize = mem::size_of::<u64>();

/// Maximum value that the eventfd counter can hold.
const MAX_COUNTER: u64 = u64::MAX - 1;
Expand Down Expand Up @@ -51,7 +52,7 @@ impl FileDescription for Event {
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
// Check the size of slice, and return error only if the size of the slice < 8.
let Some(bytes) = bytes.first_chunk_mut::<U64_MIN_ARRAY_SIZE>() else {
let Some(bytes) = bytes.first_chunk_mut::<U64_ARRAY_SIZE>() else {
return Ok(Err(Error::from(ErrorKind::InvalidInput)));
};
// Block when counter == 0.
Expand All @@ -63,15 +64,15 @@ impl FileDescription for Event {
throw_unsup_format!("eventfd: blocking is unsupported");
}
} else {
// Prevent false alarm in data race detection when doing synchronisation via eventfd.
// Synchronize with all prior `write` calls to this FD.
ecx.acquire_clock(&self.clock);
// Return the counter in the host endianness using the buffer provided by caller.
*bytes = match ecx.tcx.sess.target.endian {
Endian::Little => self.counter.to_le_bytes(),
Endian::Big => self.counter.to_be_bytes(),
};
self.counter = 0;
return Ok(Ok(U64_MIN_ARRAY_SIZE));
return Ok(Ok(U64_ARRAY_SIZE));
}
}

Expand All @@ -94,7 +95,7 @@ impl FileDescription for Event {
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
// Check the size of slice, and return error only if the size of the slice < 8.
let Some(bytes) = bytes.first_chunk::<U64_MIN_ARRAY_SIZE>() else {
let Some(bytes) = bytes.first_chunk::<U64_ARRAY_SIZE>() else {
return Ok(Err(Error::from(ErrorKind::InvalidInput)));
};
// Convert from bytes to int according to host endianness.
Expand All @@ -110,8 +111,10 @@ impl FileDescription for Event {
// Else, block.
match self.counter.checked_add(num) {
Some(new_count @ 0..=MAX_COUNTER) => {
// Prevent false alarm in data race detection when doing synchronisation via eventfd.
self.clock.join(&ecx.release_clock().unwrap());
// Future `read` calls will synchronize with this write, so update the FD clock.
if let Some(clock) = &ecx.release_clock() {
self.clock.join(clock);
}
self.counter = new_count;
}
None | Some(u64::MAX) => {
Expand All @@ -123,7 +126,7 @@ impl FileDescription for Event {
}
}
};
Ok(Ok(U64_MIN_ARRAY_SIZE))
Ok(Ok(U64_ARRAY_SIZE))
}
}

Expand Down Expand Up @@ -163,19 +166,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
}

let mut is_nonblock = false;
// Unload the flag that we support.
// Unset the flag that we support.
// After unloading, flags != 0 means other flags are used.
if flags & efd_cloexec == efd_cloexec {
// cloexec is ignored because Miri does not support exec.
flags &= !efd_cloexec;
}
if flags & efd_nonblock == efd_nonblock {
flags &= !efd_nonblock;
is_nonblock = true;
}
if flags != 0 {
let einval = this.eval_libc("EINVAL");
this.set_last_error(einval)?;
return Ok(Scalar::from_i32(-1));
throw_unsup_format!("eventfd: encountered unknown unsupported flags {:#x}", flags);
}

let fd = this.machine.fds.insert_fd(FileDescriptor::new(Event {
Expand Down
3 changes: 1 addition & 2 deletions tests/fail-dep/libc/libc_eventfd_read_block.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//@ignore-target-windows: No eventfd on Windows
//@ignore-target-apple: No eventfd in macos
//@only-target-linux
fn main() {
// eventfd read will block when EFD_NONBLOCK flag is clear and counter = 0.
// This will pass when blocking is implemented.
Expand Down
3 changes: 1 addition & 2 deletions tests/fail-dep/libc/libc_eventfd_write_block.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//@ignore-target-windows: No eventfd on Windows
//@ignore-target-apple: No eventfd in macos
//@only-target-linux
fn main() {
// eventfd write will block when EFD_NONBLOCK flag is clear
// and the addition caused counter to exceed u64::MAX - 1.
Expand Down
19 changes: 13 additions & 6 deletions tests/pass-dep/libc/libc-eventfd.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//@ignore-target-windows: No eventfd in windows
//@ignore-target-apple: No eventfd in macos
//@only-target-linux
// test_race depends on a deterministic schedule.
//@compile-flags: -Zmiri-preemption-rate=0

Expand Down Expand Up @@ -42,9 +41,11 @@ fn test_read_write() {
// value -1.
let mut buf: [u8; 8] = [0; 8];
let res = read_bytes(fd, &mut buf);
let e = std::io::Error::last_os_error();
assert_eq!(e.raw_os_error(), Some(libc::EAGAIN));
assert_eq!(res, -1);

// Write with supplied buffer that > 8 bytes should be allowed.
// Write with supplied buffer bigger than 8 bytes should be allowed.
let sized_9_data: [u8; 9];
if cfg!(target_endian = "big") {
// Adjust the data based on the endianness of host system.
Expand All @@ -55,26 +56,32 @@ fn test_read_write() {
let res = write_bytes(fd, sized_9_data);
assert_eq!(res, 8);

// Read with supplied buffer that < 8 bytes should fail with return
// Read with supplied buffer smaller than 8 bytes should fail with return
// value -1.
let mut buf: [u8; 7] = [1; 7];
let res = read_bytes(fd, &mut buf);
let e = std::io::Error::last_os_error();
assert_eq!(e.raw_os_error(), Some(libc::EINVAL));
assert_eq!(res, -1);

// Write with supplied buffer that < 8 bytes should fail with return
// Write with supplied buffer smaller than 8 bytes should fail with return
// value -1.
let size_7_data: [u8; 7] = [1; 7];
let res = write_bytes(fd, size_7_data);
let e = std::io::Error::last_os_error();
assert_eq!(e.raw_os_error(), Some(libc::EINVAL));
assert_eq!(res, -1);

// Read with supplied buffer > 8 bytes should be allowed.
// Read with supplied buffer bigger than 8 bytes should be allowed.
let mut buf: [u8; 9] = [1; 9];
let res = read_bytes(fd, &mut buf);
assert_eq!(res, 8);

// Write u64::MAX should fail.
let u64_max_bytes: [u8; 8] = [255; 8];
let res = write_bytes(fd, u64_max_bytes);
let e = std::io::Error::last_os_error();
assert_eq!(e.raw_os_error(), Some(libc::EINVAL));
assert_eq!(res, -1);
}

Expand Down