diff --git a/src/lib.rs b/src/lib.rs index d65bd10e6ffd2..97750cb78cdcb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 )] diff --git a/src/machine.rs b/src/machine.rs index 72f71db34b6c3..a7a61a1f44c7d 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -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. diff --git a/src/shims/unix/fs.rs b/src/shims/unix/fs.rs index ce2b0143b561c..c46506e20acd3 100644 --- a/src/shims/unix/fs.rs +++ b/src/shims/unix/fs.rs @@ -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>, + pub handles: BTreeMap>, } impl VisitTags for FileHandler { @@ -297,7 +302,7 @@ impl FileHandler { FileHandler { handles } } - fn insert_fd(&mut self, file_handle: Box) -> i32 { + pub fn insert_fd(&mut self, file_handle: Box) -> 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` instead of `i32` directly because some fs functions return different integer - /// types (like `read`, that returns an `i64`). - fn handle_not_found>(&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, @@ -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` instead of `i32` directly because some fs functions return different integer + /// types (like `read`, that returns an `i64`). + fn handle_not_found>(&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, diff --git a/src/shims/unix/linux/fd.rs b/src/shims/unix/linux/fd.rs new file mode 100644 index 0000000000000..212b7936341a7 --- /dev/null +++ b/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()`. + /// + /// + fn epoll_create1( + &mut self, + flags: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + 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. + /// + /// + fn epoll_ctl( + &mut self, + epfd: &OpTy<'tcx, Provenance>, + op: &OpTy<'tcx, Provenance>, + fd: &OpTy<'tcx, Provenance>, + event: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + 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. + /// + /// + fn eventfd( + &mut self, + val: &OpTy<'tcx, Provenance>, + flags: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + 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: + /// + /// + /// + fn socketpair( + &mut self, + domain: &OpTy<'tcx, Provenance>, + type_: &OpTy<'tcx, Provenance>, + protocol: &OpTy<'tcx, Provenance>, + sv: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + 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)) + } +} diff --git a/src/shims/unix/linux/fd/epoll.rs b/src/shims/unix/linux/fd/epoll.rs new file mode 100644 index 0000000000000..eb86773e6b68a --- /dev/null +++ b/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, +} + +/// 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: +/// +/// +#[derive(Clone, Debug)] +pub struct EpollEvent { + pub events: u32, + /// `Scalar` is used to represent the + /// `epoll_data` type union. + pub data: Scalar, +} + +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> { + Ok(Box::new(self.clone())) + } + + fn is_tty(&self) -> bool { + false + } + + fn close<'tcx>( + self: Box, + _communicate_allowed: bool, + ) -> InterpResult<'tcx, io::Result> { + Ok(Ok(0)) + } +} diff --git a/src/shims/unix/linux/fd/event.rs b/src/shims/unix/linux/fd/event.rs new file mode 100644 index 0000000000000..e87ff56bee3a3 --- /dev/null +++ b/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: +/// +/// +#[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> { + Ok(Box::new(Event { val: self.val })) + } + + fn is_tty(&self) -> bool { + false + } + + fn close<'tcx>( + self: Box, + _communicate_allowed: bool, + ) -> InterpResult<'tcx, io::Result> { + Ok(Ok(0)) + } +} diff --git a/src/shims/unix/linux/fd/socketpair.rs b/src/shims/unix/linux/fd/socketpair.rs new file mode 100644 index 0000000000000..036d3a2e31ee5 --- /dev/null +++ b/src/shims/unix/linux/fd/socketpair.rs @@ -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> { + Ok(Box::new(SocketPair)) + } + + fn is_tty(&self) -> bool { + false + } + + fn close<'tcx>( + self: Box, + _communicate_allowed: bool, + ) -> InterpResult<'tcx, io::Result> { + Ok(Ok(0)) + } +} diff --git a/src/shims/unix/linux/foreign_items.rs b/src/shims/unix/linux/foreign_items.rs index ff7b2b352f2d1..be541deae4041 100644 --- a/src/shims/unix/linux/foreign_items.rs +++ b/src/shims/unix/linux/foreign_items.rs @@ -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" => { diff --git a/src/shims/unix/linux/mod.rs b/src/shims/unix/linux/mod.rs index 498eb57c57fe0..437764c3824eb 100644 --- a/src/shims/unix/linux/mod.rs +++ b/src/shims/unix/linux/mod.rs @@ -1,3 +1,4 @@ pub mod dlsym; +pub mod fd; pub mod foreign_items; pub mod sync; diff --git a/tests/fail/crates/tokio_mvp.rs b/tests/fail/crates/tokio_mvp.rs deleted file mode 100644 index 7cb42c09a9677..0000000000000 --- a/tests/fail/crates/tokio_mvp.rs +++ /dev/null @@ -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() {} diff --git a/tests/fail/crates/tokio_mvp.stderr b/tests/fail/crates/tokio_mvp.stderr deleted file mode 100644 index 1e7dfaa749904..0000000000000 --- a/tests/fail/crates/tokio_mvp.stderr +++ /dev/null @@ -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 - diff --git a/tests/pass-dep/tokio_mvp.rs b/tests/pass-dep/tokio_mvp.rs new file mode 100644 index 0000000000000..642168253c2fa --- /dev/null +++ b/tests/pass-dep/tokio_mvp.rs @@ -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() {}