Skip to content

Commit

Permalink
Auto merge of #2357 - DebugSteven:epoll_create1-shim, r=oli-obk
Browse files Browse the repository at this point in the history
implement minimal epoll_create1 shim

Implements minimal shim for #602
  • Loading branch information
bors committed Dec 14, 2022
2 parents 1fca5a9 + 0dfa31b commit 205ab4f
Show file tree
Hide file tree
Showing 12 changed files with 377 additions and 41 deletions.
1 change: 1 addition & 0 deletions src/lib.rs
Expand Up @@ -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
)]
Expand Down
4 changes: 4 additions & 0 deletions src/machine.rs
Expand Up @@ -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.
Expand Down
35 changes: 20 additions & 15 deletions src/shims/unix/fs.rs
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}

Expand Down Expand Up @@ -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>,
Expand Down Expand Up @@ -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,
Expand Down
191 changes: 191 additions & 0 deletions src/shims/unix/linux/fd.rs
@@ -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))
}
}
53 changes: 53 additions & 0 deletions src/shims/unix/linux/fd/epoll.rs
@@ -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))
}
}
38 changes: 38 additions & 0 deletions src/shims/unix/linux/fd/event.rs
@@ -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))
}
}

0 comments on commit 205ab4f

Please sign in to comment.