implement minimal epoll_create1 shim

This commit is contained in:
DebugSteven 2022-07-11 09:29:54 -06:00
parent 8b19af0582
commit 1b51f372eb
12 changed files with 377 additions and 41 deletions

View file

@ -27,6 +27,7 @@
clippy::single_element_loop,
clippy::needless_return,
clippy::bool_to_int_with_if,
clippy::box_default,
// We are not implementing queries here so it's fine
rustc::potential_query_instability
)]

View file

@ -31,6 +31,10 @@ use crate::{
*,
};
/// The number of the available real-time signal with the lowest priority.
/// Dummy constant related to epoll, must be between 32 and 64.
pub const SIGRTMAX: i32 = 42;
/// Extra data stored with each stack frame
pub struct FrameExtra<'tcx> {
/// Extra data for Stacked Borrows.

View file

@ -17,20 +17,25 @@ use crate::shims::os_str::bytes_to_os_str;
use crate::*;
use shims::os_str::os_str_to_bytes;
use shims::time::system_time_to_duration;
use shims::unix::linux::fd::epoll::Epoll;
#[derive(Debug)]
struct FileHandle {
pub struct FileHandle {
file: File,
writable: bool,
}
trait FileDescriptor: std::fmt::Debug {
pub trait FileDescriptor: std::fmt::Debug {
fn name(&self) -> &'static str;
fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> {
throw_unsup_format!("{} cannot be used as FileHandle", self.name());
}
fn as_epoll_handle<'tcx>(&mut self) -> InterpResult<'tcx, &mut Epoll> {
throw_unsup_format!("not an epoll file descriptor");
}
fn read<'tcx>(
&mut self,
_communicate_allowed: bool,
@ -274,7 +279,7 @@ impl FileDescriptor for NullOutput {
#[derive(Debug)]
pub struct FileHandler {
handles: BTreeMap<i32, Box<dyn FileDescriptor>>,
pub handles: BTreeMap<i32, Box<dyn FileDescriptor>>,
}
impl VisitTags for FileHandler {
@ -297,7 +302,7 @@ impl FileHandler {
FileHandler { handles }
}
fn insert_fd(&mut self, file_handle: Box<dyn FileDescriptor>) -> i32 {
pub fn insert_fd(&mut self, file_handle: Box<dyn FileDescriptor>) -> i32 {
self.insert_fd_with_min_fd(file_handle, 0)
}
@ -376,17 +381,6 @@ trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx
Ok(0)
}
/// Function used when a handle is not found inside `FileHandler`. It returns `Ok(-1)`and sets
/// the last OS error to `libc::EBADF` (invalid file descriptor). This function uses
/// `T: From<i32>` instead of `i32` directly because some fs functions return different integer
/// types (like `read`, that returns an `i64`).
fn handle_not_found<T: From<i32>>(&mut self) -> InterpResult<'tcx, T> {
let this = self.eval_context_mut();
let ebadf = this.eval_libc("EBADF");
this.set_last_error(ebadf)?;
Ok((-1).into())
}
fn file_type_to_d_type(
&mut self,
file_type: std::io::Result<FileType>,
@ -726,6 +720,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
))
}
/// Function used when a handle is not found inside `FileHandler`. It returns `Ok(-1)`and sets
/// the last OS error to `libc::EBADF` (invalid file descriptor). This function uses
/// `T: From<i32>` instead of `i32` directly because some fs functions return different integer
/// types (like `read`, that returns an `i64`).
fn handle_not_found<T: From<i32>>(&mut self) -> InterpResult<'tcx, T> {
let this = self.eval_context_mut();
let ebadf = this.eval_libc("EBADF");
this.set_last_error(ebadf)?;
Ok((-1).into())
}
fn read(
&mut self,
fd: i32,

View file

@ -0,0 +1,191 @@
use rustc_middle::ty::ScalarInt;
use crate::*;
use epoll::{Epoll, EpollEvent};
use event::Event;
use socketpair::SocketPair;
use shims::unix::fs::EvalContextExt as _;
pub mod epoll;
pub mod event;
pub mod socketpair;
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
/// This function returns a file descriptor referring to the new `Epoll` instance. This file
/// descriptor is used for all subsequent calls to the epoll interface. If the `flags` argument
/// is 0, then this function is the same as `epoll_create()`.
///
/// <https://linux.die.net/man/2/epoll_create1>
fn epoll_create1(
&mut self,
flags: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let flags = this.read_scalar(flags)?.to_i32()?;
let epoll_cloexec = this.eval_libc_i32("EPOLL_CLOEXEC");
if flags == epoll_cloexec {
// Miri does not support exec, so this flag has no effect.
} else if flags != 0 {
throw_unsup_format!("epoll_create1 flags {flags} are not implemented");
}
let fd = this.machine.file_handler.insert_fd(Box::new(Epoll::default()));
Ok(Scalar::from_i32(fd))
}
/// This function performs control operations on the `Epoll` instance referred to by the file
/// descriptor `epfd`. It requests that the operation `op` be performed for the target file
/// descriptor, `fd`.
///
/// Valid values for the op argument are:
/// `EPOLL_CTL_ADD` - Register the target file descriptor `fd` on the `Epoll` instance referred
/// to by the file descriptor `epfd` and associate the event `event` with the internal file
/// linked to `fd`.
/// `EPOLL_CTL_MOD` - Change the event `event` associated with the target file descriptor `fd`.
/// `EPOLL_CTL_DEL` - Deregister the target file descriptor `fd` from the `Epoll` instance
/// referred to by `epfd`. The `event` is ignored and can be null.
///
/// <https://linux.die.net/man/2/epoll_ctl>
fn epoll_ctl(
&mut self,
epfd: &OpTy<'tcx, Provenance>,
op: &OpTy<'tcx, Provenance>,
fd: &OpTy<'tcx, Provenance>,
event: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let epfd = this.read_scalar(epfd)?.to_i32()?;
let op = this.read_scalar(op)?.to_i32()?;
let fd = this.read_scalar(fd)?.to_i32()?;
let _event = this.read_scalar(event)?.to_pointer(this)?;
let epoll_ctl_add = this.eval_libc_i32("EPOLL_CTL_ADD");
let epoll_ctl_mod = this.eval_libc_i32("EPOLL_CTL_MOD");
let epoll_ctl_del = this.eval_libc_i32("EPOLL_CTL_DEL");
if op == epoll_ctl_add || op == epoll_ctl_mod {
let event = this.deref_operand(event)?;
let events = this.mplace_field(&event, 0)?;
let events = this.read_scalar(&events.into())?.to_u32()?;
let data = this.mplace_field(&event, 1)?;
let data = this.read_scalar(&data.into())?;
let event = EpollEvent { events, data };
if let Some(epfd) = this.machine.file_handler.handles.get_mut(&epfd) {
let epfd = epfd.as_epoll_handle()?;
epfd.file_descriptors.insert(fd, event);
Ok(Scalar::from_i32(0))
} else {
Ok(Scalar::from_i32(this.handle_not_found()?))
}
} else if op == epoll_ctl_del {
if let Some(epfd) = this.machine.file_handler.handles.get_mut(&epfd) {
let epfd = epfd.as_epoll_handle()?;
epfd.file_descriptors.remove(&fd);
Ok(Scalar::from_i32(0))
} else {
Ok(Scalar::from_i32(this.handle_not_found()?))
}
} else {
let einval = this.eval_libc("EINVAL");
this.set_last_error(einval)?;
Ok(Scalar::from_i32(-1))
}
}
/// This function creates an `Event` that is used as an event wait/notify mechanism by
/// user-space applications, and by the kernel to notify user-space applications of events.
/// The `Event` contains an `u64` counter maintained by the kernel. The counter is initialized
/// with the value specified in the `initval` argument.
///
/// A new file descriptor referring to the `Event` is returned. The `read`, `write`, `poll`,
/// `select`, and `close` operations can be performed on the file descriptor. For more
/// information on these operations, see the man page linked below.
///
/// The `flags` are not currently implemented for eventfd.
/// The `flags` may be bitwise ORed to change the behavior of `eventfd`:
/// `EFD_CLOEXEC` - Set the close-on-exec (`FD_CLOEXEC`) flag on the new file descriptor.
/// `EFD_NONBLOCK` - Set the `O_NONBLOCK` file status flag on the new open file description.
/// `EFD_SEMAPHORE` - miri does not support semaphore-like semantics.
///
/// <https://linux.die.net/man/2/eventfd>
fn eventfd(
&mut self,
val: &OpTy<'tcx, Provenance>,
flags: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let val = this.read_scalar(val)?.to_u32()?;
let flags = this.read_scalar(flags)?.to_i32()?;
let efd_cloexec = this.eval_libc_i32("EFD_CLOEXEC");
let efd_nonblock = this.eval_libc_i32("EFD_NONBLOCK");
let efd_semaphore = this.eval_libc_i32("EFD_SEMAPHORE");
if flags & (efd_cloexec | efd_nonblock | efd_semaphore) == 0 {
throw_unsup_format!("{flags} is unsupported");
}
// FIXME handle the cloexec and nonblock flags
if flags & efd_cloexec == efd_cloexec {}
if flags & efd_nonblock == efd_nonblock {}
if flags & efd_semaphore == efd_semaphore {
throw_unsup_format!("EFD_SEMAPHORE is unsupported");
}
let fh = &mut this.machine.file_handler;
let fd = fh.insert_fd(Box::new(Event { val }));
Ok(Scalar::from_i32(fd))
}
/// Currently this function creates new `SocketPair`s without specifying the domain, type, or
/// protocol of the new socket and these are stored in the socket values `sv` argument.
///
/// This function creates an unnamed pair of connected sockets in the specified domain, of the
/// specified type, and using the optionally specified protocol.
///
/// The `domain` argument specified a communication domain; this selects the protocol family
/// used for communication. The socket `type` specifies the communication semantics.
/// The `protocol` specifies a particular protocol to use with the socket. Normally there's
/// only a single protocol supported for a particular socket type within a given protocol
/// family, in which case `protocol` can be specified as 0. It is possible that many protocols
/// exist and in that case, a particular protocol must be specified.
///
/// For more information on the arguments see the socket manpage:
/// <https://linux.die.net/man/2/socket>
///
/// <https://linux.die.net/man/2/socketpair>
fn socketpair(
&mut self,
domain: &OpTy<'tcx, Provenance>,
type_: &OpTy<'tcx, Provenance>,
protocol: &OpTy<'tcx, Provenance>,
sv: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let _domain = this.read_scalar(domain)?.to_i32()?;
let _type_ = this.read_scalar(type_)?.to_i32()?;
let _protocol = this.read_scalar(protocol)?.to_i32()?;
let sv = this.deref_operand(sv)?;
let fh = &mut this.machine.file_handler;
let sv0 = fh.insert_fd(Box::new(SocketPair));
let sv0 = ScalarInt::try_from_int(sv0, sv.layout.size).unwrap();
let sv1 = fh.insert_fd(Box::new(SocketPair));
let sv1 = ScalarInt::try_from_int(sv1, sv.layout.size).unwrap();
this.write_scalar(sv0, &sv.into())?;
this.write_scalar(sv1, &sv.offset(sv.layout.size, sv.layout, this)?.into())?;
Ok(Scalar::from_i32(0))
}
}

View file

@ -0,0 +1,53 @@
use crate::*;
use crate::shims::unix::fs::FileDescriptor;
use rustc_data_structures::fx::FxHashMap;
use std::io;
/// An `Epoll` file descriptor connects file handles and epoll events
#[derive(Clone, Debug, Default)]
pub struct Epoll {
/// The file descriptors we are watching, and what we are watching for.
pub file_descriptors: FxHashMap<i32, EpollEvent>,
}
/// Epoll Events associate events with data.
/// These fields are currently unused by miri.
/// This matches the `epoll_event` struct defined
/// by the epoll_ctl man page. For more information
/// see the man page:
///
/// <https://man7.org/linux/man-pages/man2/epoll_ctl.2.html>
#[derive(Clone, Debug)]
pub struct EpollEvent {
pub events: u32,
/// `Scalar<Provenance>` is used to represent the
/// `epoll_data` type union.
pub data: Scalar<Provenance>,
}
impl FileDescriptor for Epoll {
fn name(&self) -> &'static str {
"epoll"
}
fn as_epoll_handle<'tcx>(&mut self) -> InterpResult<'tcx, &mut Epoll> {
Ok(self)
}
fn dup<'tcx>(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(self.clone()))
}
fn is_tty(&self) -> bool {
false
}
fn close<'tcx>(
self: Box<Self>,
_communicate_allowed: bool,
) -> InterpResult<'tcx, io::Result<i32>> {
Ok(Ok(0))
}
}

View file

@ -0,0 +1,38 @@
use crate::shims::unix::fs::FileDescriptor;
use rustc_const_eval::interpret::InterpResult;
use std::io;
/// A kind of file descriptor created by `eventfd`.
/// The `Event` type isn't currently written to by `eventfd`.
/// The interface is meant to keep track of objects associated
/// with a file descriptor. For more information see the man
/// page below:
///
/// <https://man.netbsd.org/eventfd.2>
#[derive(Debug)]
pub struct Event {
pub val: u32,
}
impl FileDescriptor for Event {
fn name(&self) -> &'static str {
"event"
}
fn dup<'tcx>(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(Event { val: self.val }))
}
fn is_tty(&self) -> bool {
false
}
fn close<'tcx>(
self: Box<Self>,
_communicate_allowed: bool,
) -> InterpResult<'tcx, io::Result<i32>> {
Ok(Ok(0))
}
}

View file

@ -0,0 +1,32 @@
use crate::*;
use crate::shims::unix::fs::FileDescriptor;
use std::io;
/// Pair of connected sockets.
///
/// We currently don't allow sending any data through this pair, so this can be just a dummy.
#[derive(Debug)]
pub struct SocketPair;
impl FileDescriptor for SocketPair {
fn name(&self) -> &'static str {
"socketpair"
}
fn dup<'tcx>(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(SocketPair))
}
fn is_tty(&self) -> bool {
false
}
fn close<'tcx>(
self: Box<Self>,
_communicate_allowed: bool,
) -> InterpResult<'tcx, io::Result<i32>> {
Ok(Ok(0))
}
}

View file

@ -1,9 +1,11 @@
use rustc_span::Symbol;
use rustc_target::spec::abi::Abi;
use crate::machine::SIGRTMAX;
use crate::*;
use shims::foreign_items::EmulateByNameResult;
use shims::unix::fs::EvalContextExt as _;
use shims::unix::linux::fd::EvalContextExt as _;
use shims::unix::linux::sync::futex;
use shims::unix::sync::EvalContextExt as _;
use shims::unix::thread::EvalContextExt as _;
@ -42,6 +44,35 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let result = this.sync_file_range(fd, offset, nbytes, flags)?;
this.write_scalar(result, dest)?;
}
"epoll_create1" => {
let [flag] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.epoll_create1(flag)?;
this.write_scalar(result, dest)?;
}
"epoll_ctl" => {
let [epfd, op, fd, event] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.epoll_ctl(epfd, op, fd, event)?;
this.write_scalar(result, dest)?;
}
"eventfd" => {
let [val, flag] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.eventfd(val, flag)?;
this.write_scalar(result, dest)?;
}
"socketpair" => {
let [domain, type_, protocol, sv] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.socketpair(domain, type_, protocol, sv)?;
this.write_scalar(result, dest)?;
}
"__libc_current_sigrtmax" => {
let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.write_scalar(Scalar::from_i32(SIGRTMAX), dest)?;
}
// Threading
"pthread_condattr_setclock" => {

View file

@ -1,3 +1,4 @@
pub mod dlsym;
pub mod fd;
pub mod foreign_items;
pub mod sync;

View file

@ -1,7 +0,0 @@
//@compile-flags: -Zmiri-disable-isolation
//@error-pattern: can't call foreign function: epoll_create1
//@normalize-stderr-test: " = note: inside .*\n" -> ""
//@only-target-linux: the errors differ too much between platforms
#[tokio::main]
async fn main() {}

View file

@ -1,19 +0,0 @@
error: unsupported operation: can't call foreign function: epoll_create1
--> CARGO_REGISTRY/.../epoll.rs:LL:CC
|
LL | let res = syscall!(epoll_create1(libc::EPOLL_CLOEXEC));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ can't call foreign function: epoll_create1
|
= help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support
= note: BACKTRACE:
note: inside `main`
--> $DIR/tokio_mvp.rs:LL:CC
|
LL | #[tokio::main]
| ^^^^^^^^^^^^^^
= note: this error originates in the macro `syscall` which comes from the expansion of the attribute macro `tokio::main` (in Nightly builds, run with -Z macro-backtrace for more info)
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to previous error

View file

@ -0,0 +1,6 @@
// Need to disable preemption to stay on the supported MVP codepath in mio.
//@compile-flags: -Zmiri-disable-isolation -Zmiri-permissive-provenance -Zmiri-preemption-rate=0
//@only-target-x86_64-unknown-linux: support for tokio exists only on linux and x86
#[tokio::main]
async fn main() {}