Skip to content

Commit 2f206fa

Browse files
committed
Add eventfd shim
1 parent b3ed85b commit 2f206fa

File tree

6 files changed

+257
-19
lines changed

6 files changed

+257
-19
lines changed

src/shims/unix/linux/eventfd.rs

+94-19
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
//! Linux `eventfd` implementation.
2-
//! Currently just a stub.
32
use std::io;
3+
use std::io::{Error, ErrorKind};
44

55
use rustc_target::abi::Endian;
66

77
use crate::shims::unix::*;
8-
use crate::*;
8+
use crate::{concurrency::VClock, *};
99

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

12+
/// Minimum size of u8 array to hold u64 value.
13+
const U64_MIN_ARRAY_SIZE: usize = 8;
14+
15+
/// Maximum value that the eventfd counter can hold.
16+
const MAX_COUNTER: u64 = u64::MAX - 1;
17+
1218
/// A kind of file descriptor created by `eventfd`.
1319
/// The `Event` type isn't currently written to by `eventfd`.
1420
/// The interface is meant to keep track of objects associated
@@ -20,7 +26,9 @@ use self::shims::unix::fd::FileDescriptor;
2026
struct Event {
2127
/// The object contains an unsigned 64-bit integer (uint64_t) counter that is maintained by the
2228
/// kernel. This counter is initialized with the value specified in the argument initval.
23-
val: u64,
29+
counter: u64,
30+
is_nonblock: bool,
31+
clock: VClock,
2432
}
2533

2634
impl FileDescription for Event {
@@ -35,6 +43,38 @@ impl FileDescription for Event {
3543
Ok(Ok(()))
3644
}
3745

46+
/// Read the counter in the buffer and return the counter if succeeded.
47+
fn read<'tcx>(
48+
&mut self,
49+
_communicate_allowed: bool,
50+
bytes: &mut [u8],
51+
ecx: &mut MiriInterpCx<'tcx>,
52+
) -> InterpResult<'tcx, io::Result<usize>> {
53+
// Check the size of slice, and return error only if the size of the slice < 8.
54+
let Some(bytes) = bytes.first_chunk_mut::<U64_MIN_ARRAY_SIZE>() else {
55+
return Ok(Err(Error::from(ErrorKind::InvalidInput)));
56+
};
57+
// Block when counter == 0.
58+
if self.counter == 0 {
59+
if self.is_nonblock {
60+
return Ok(Err(Error::from(ErrorKind::WouldBlock)));
61+
} else {
62+
//FIXME: blocking is not supported
63+
throw_unsup_format!("eventfd: blocking is unsupported");
64+
}
65+
} else {
66+
// Prevent false alarm in data race detection when doing synchronisation via eventfd.
67+
ecx.acquire_clock(&self.clock);
68+
// Return the counter in the host endianness using the buffer provided by caller.
69+
*bytes = match ecx.tcx.sess.target.endian {
70+
Endian::Little => self.counter.to_le_bytes(),
71+
Endian::Big => self.counter.to_be_bytes(),
72+
};
73+
self.counter = 0;
74+
return Ok(Ok(U64_MIN_ARRAY_SIZE));
75+
}
76+
}
77+
3878
/// A write call adds the 8-byte integer value supplied in
3979
/// its buffer (in native endianness) to the counter. The maximum value that may be
4080
/// stored in the counter is the largest unsigned 64-bit value
@@ -53,16 +93,37 @@ impl FileDescription for Event {
5393
bytes: &[u8],
5494
ecx: &mut MiriInterpCx<'tcx>,
5595
) -> InterpResult<'tcx, io::Result<usize>> {
56-
let bytes: [u8; 8] = bytes.try_into().unwrap(); // FIXME fail gracefully when this has the wrong size
57-
// Convert from target endianness to host endianness.
96+
// Check the size of slice, and return error only if the size of the slice < 8.
97+
let Some(bytes) = bytes.first_chunk::<U64_MIN_ARRAY_SIZE>() else {
98+
return Ok(Err(Error::from(ErrorKind::InvalidInput)));
99+
};
100+
// Convert from bytes to int according to host endianness.
58101
let num = match ecx.tcx.sess.target.endian {
59-
Endian::Little => u64::from_le_bytes(bytes),
60-
Endian::Big => u64::from_be_bytes(bytes),
102+
Endian::Little => u64::from_le_bytes(*bytes),
103+
Endian::Big => u64::from_be_bytes(*bytes),
104+
};
105+
// u64::MAX as input is invalid because the maximum value of counter is u64::MAX - 1.
106+
if num == u64::MAX {
107+
return Ok(Err(Error::from(ErrorKind::InvalidInput)));
108+
}
109+
// If the addition does not let the counter to exceed the maximum value, update the counter.
110+
// Else, block.
111+
match self.counter.checked_add(num) {
112+
Some(new_count @ 0..=MAX_COUNTER) => {
113+
// Prevent false alarm in data race detection when doing synchronisation via eventfd.
114+
self.clock.join(&ecx.release_clock().unwrap());
115+
self.counter = new_count;
116+
}
117+
None | Some(u64::MAX) => {
118+
if self.is_nonblock {
119+
return Ok(Err(Error::from(ErrorKind::WouldBlock)));
120+
} else {
121+
//FIXME: blocking is not supported
122+
throw_unsup_format!("eventfd: blocking is unsupported");
123+
}
124+
}
61125
};
62-
// FIXME handle blocking when addition results in exceeding the max u64 value
63-
// or fail with EAGAIN if the file descriptor is nonblocking.
64-
self.val = self.val.checked_add(num).unwrap();
65-
Ok(Ok(8))
126+
Ok(Ok(U64_MIN_ARRAY_SIZE))
66127
}
67128
}
68129

@@ -87,27 +148,41 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
87148
fn eventfd(&mut self, val: &OpTy<'tcx>, flags: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
88149
let this = self.eval_context_mut();
89150

151+
// eventfd is Linux specific.
152+
this.assert_target_os("linux", "eventfd");
153+
90154
let val = this.read_scalar(val)?.to_u32()?;
91-
let flags = this.read_scalar(flags)?.to_i32()?;
155+
let mut flags = this.read_scalar(flags)?.to_i32()?;
92156

93157
let efd_cloexec = this.eval_libc_i32("EFD_CLOEXEC");
94158
let efd_nonblock = this.eval_libc_i32("EFD_NONBLOCK");
95159
let efd_semaphore = this.eval_libc_i32("EFD_SEMAPHORE");
96160

97-
if flags & (efd_cloexec | efd_nonblock | efd_semaphore) != flags {
98-
throw_unsup_format!("eventfd: flag {flags:#x} is unsupported");
161+
if flags & efd_semaphore == efd_semaphore {
162+
throw_unsup_format!("eventfd: EFD_SEMAPHORE is unsupported");
99163
}
164+
165+
let mut is_nonblock = false;
166+
// Unload the flag that we support.
167+
// After unloading, flags != 0 means other flags are used.
100168
if flags & efd_cloexec == efd_cloexec {
101-
// cloexec does nothing as we don't support `exec`
169+
flags &= !efd_cloexec;
102170
}
103171
if flags & efd_nonblock == efd_nonblock {
104-
// FIXME remember the nonblock flag
172+
flags &= !efd_nonblock;
173+
is_nonblock = true;
105174
}
106-
if flags & efd_semaphore == efd_semaphore {
107-
throw_unsup_format!("eventfd: EFD_SEMAPHORE is unsupported");
175+
if flags != 0 {
176+
let einval = this.eval_libc("EINVAL");
177+
this.set_last_error(einval)?;
178+
return Ok(Scalar::from_i32(-1));
108179
}
109180

110-
let fd = this.machine.fds.insert_fd(FileDescriptor::new(Event { val: val.into() }));
181+
let fd = this.machine.fds.insert_fd(FileDescriptor::new(Event {
182+
counter: val.into(),
183+
is_nonblock,
184+
clock: VClock::default(),
185+
}));
111186
Ok(Scalar::from_i32(fd))
112187
}
113188
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//@ignore-target-windows: No eventfd on Windows
2+
//@ignore-target-apple: No eventfd in macos
3+
fn main() {
4+
// eventfd read will block when EFD_NONBLOCK flag is clear and counter = 0.
5+
// This will pass when blocking is implemented.
6+
let flags = libc::EFD_CLOEXEC;
7+
let fd = unsafe { libc::eventfd(0, flags) };
8+
let mut buf: [u8; 8] = [0; 8];
9+
let _res: i32 = unsafe {
10+
libc::read(fd, buf.as_mut_ptr().cast(), buf.len() as libc::size_t).try_into().unwrap() //~ERROR: blocking is unsupported
11+
};
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: unsupported operation: eventfd: blocking is unsupported
2+
--> $DIR/libc_eventfd_read_block.rs:LL:CC
3+
|
4+
LL | libc::read(fd, buf.as_mut_ptr().cast(), buf.len() as libc::size_t).try_into().unwrap()
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ eventfd: blocking is unsupported
6+
|
7+
= help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support
8+
= note: BACKTRACE:
9+
= note: inside `main` at $DIR/libc_eventfd_read_block.rs:LL:CC
10+
11+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
12+
13+
error: aborting due to 1 previous error
14+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//@ignore-target-windows: No eventfd on Windows
2+
//@ignore-target-apple: No eventfd in macos
3+
fn main() {
4+
// eventfd write will block when EFD_NONBLOCK flag is clear
5+
// and the addition caused counter to exceed u64::MAX - 1.
6+
// This will pass when blocking is implemented.
7+
let flags = libc::EFD_CLOEXEC;
8+
let fd = unsafe { libc::eventfd(0, flags) };
9+
// Write u64 - 1.
10+
let mut sized_8_data: [u8; 8] = (u64::MAX - 1).to_ne_bytes();
11+
let res: i64 = unsafe {
12+
libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap()
13+
};
14+
assert_eq!(res, 8);
15+
16+
// Write 1.
17+
sized_8_data = 1_u64.to_ne_bytes();
18+
// Write 1 to the counter.
19+
let _res: i64 = unsafe {
20+
libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap() //~ERROR: blocking is unsupported
21+
};
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: unsupported operation: eventfd: blocking is unsupported
2+
--> $DIR/libc_eventfd_write_block.rs:LL:CC
3+
|
4+
LL | libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8).try_into().unwrap()
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ eventfd: blocking is unsupported
6+
|
7+
= help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support
8+
= note: BACKTRACE:
9+
= note: inside `main` at $DIR/libc_eventfd_write_block.rs:LL:CC
10+
11+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
12+
13+
error: aborting due to 1 previous error
14+

tests/pass-dep/libc/libc-eventfd.rs

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
//@ignore-target-windows: No eventfd in windows
2+
//@ignore-target-apple: No eventfd in macos
3+
// test_race depends on a deterministic schedule.
4+
//@compile-flags: -Zmiri-preemption-rate=0
5+
6+
use std::thread;
7+
8+
fn main() {
9+
test_read_write();
10+
test_race();
11+
}
12+
13+
fn read_bytes<const N: usize>(fd: i32, buf: &mut [u8; N]) -> i32 {
14+
let res: i32 = unsafe { libc::read(fd, buf.as_mut_ptr().cast(), N).try_into().unwrap() };
15+
return res;
16+
}
17+
18+
fn write_bytes<const N: usize>(fd: i32, data: [u8; N]) -> i32 {
19+
let res: i32 =
20+
unsafe { libc::write(fd, data.as_ptr() as *const libc::c_void, N).try_into().unwrap() };
21+
return res;
22+
}
23+
24+
fn test_read_write() {
25+
let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC;
26+
let fd = unsafe { libc::eventfd(0, flags) };
27+
let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes();
28+
// Write 1 to the counter.
29+
let res = write_bytes(fd, sized_8_data);
30+
assert_eq!(res, 8);
31+
32+
// Read 1 from the counter.
33+
let mut buf: [u8; 8] = [0; 8];
34+
let res = read_bytes(fd, &mut buf);
35+
// Read returns number of bytes has been read, which is always 8.
36+
assert_eq!(res, 8);
37+
// Check the value of counter read.
38+
let counter = u64::from_ne_bytes(buf);
39+
assert_eq!(counter, 1);
40+
41+
// After read, the counter is currently 0, read counter 0 should fail with return
42+
// value -1.
43+
let mut buf: [u8; 8] = [0; 8];
44+
let res = read_bytes(fd, &mut buf);
45+
assert_eq!(res, -1);
46+
47+
// Write with supplied buffer that > 8 bytes should be allowed.
48+
let sized_9_data: [u8; 9];
49+
if cfg!(target_endian = "big") {
50+
// Adjust the data based on the endianness of host system.
51+
sized_9_data = [0, 0, 0, 0, 0, 0, 0, 1, 0];
52+
} else {
53+
sized_9_data = [1, 0, 0, 0, 0, 0, 0, 0, 0];
54+
}
55+
let res = write_bytes(fd, sized_9_data);
56+
assert_eq!(res, 8);
57+
58+
// Read with supplied buffer that < 8 bytes should fail with return
59+
// value -1.
60+
let mut buf: [u8; 7] = [1; 7];
61+
let res = read_bytes(fd, &mut buf);
62+
assert_eq!(res, -1);
63+
64+
// Write with supplied buffer that < 8 bytes should fail with return
65+
// value -1.
66+
let size_7_data: [u8; 7] = [1; 7];
67+
let res = write_bytes(fd, size_7_data);
68+
assert_eq!(res, -1);
69+
70+
// Read with supplied buffer > 8 bytes should be allowed.
71+
let mut buf: [u8; 9] = [1; 9];
72+
let res = read_bytes(fd, &mut buf);
73+
assert_eq!(res, 8);
74+
75+
// Write u64::MAX should fail.
76+
let u64_max_bytes: [u8; 8] = [255; 8];
77+
let res = write_bytes(fd, u64_max_bytes);
78+
assert_eq!(res, -1);
79+
}
80+
81+
fn test_race() {
82+
static mut VAL: u8 = 0;
83+
let flags = libc::EFD_NONBLOCK | libc::EFD_CLOEXEC;
84+
let fd = unsafe { libc::eventfd(0, flags) };
85+
let thread1 = thread::spawn(move || {
86+
let mut buf: [u8; 8] = [0; 8];
87+
let res = read_bytes(fd, &mut buf);
88+
// read returns number of bytes has been read, which is always 8.
89+
assert_eq!(res, 8);
90+
let counter = u64::from_ne_bytes(buf);
91+
assert_eq!(counter, 1);
92+
unsafe { assert_eq!(VAL, 1) };
93+
});
94+
unsafe { VAL = 1 };
95+
let data: [u8; 8] = 1_u64.to_ne_bytes();
96+
let res = write_bytes(fd, data);
97+
// write returns number of bytes written, which is always 8.
98+
assert_eq!(res, 8);
99+
thread::yield_now();
100+
thread1.join().unwrap();
101+
}

0 commit comments

Comments
 (0)