Merge pull request #4114 from RalfJung/fd-ref-refactor

FD handling: avoid unnecessary dynamic downcasts
This commit is contained in:
Ralf Jung 2024-12-28 19:48:14 +00:00 committed by GitHub
commit 22fd2f39ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 262 additions and 278 deletions

View file

@ -13,6 +13,8 @@
#![feature(strict_overflow_ops)]
#![feature(pointer_is_aligned_to)]
#![feature(unqualified_local_imports)]
#![feature(derive_coerce_pointee)]
#![feature(arbitrary_self_types)]
// Configure clippy and other lints
#![allow(
clippy::collapsible_else_if,

View file

@ -1,6 +1,7 @@
use std::any::Any;
use std::collections::BTreeMap;
use std::io::{IsTerminal, Read, SeekFrom, Write};
use std::marker::CoercePointee;
use std::ops::Deref;
use std::rc::{Rc, Weak};
use std::{fs, io};
@ -10,16 +11,126 @@ use rustc_abi::Size;
use crate::shims::unix::UnixFileDescription;
use crate::*;
/// A unique id for file descriptions. While we could use the address, considering that
/// is definitely unique, the address would expose interpreter internal state when used
/// for sorting things. So instead we generate a unique id per file description is the name
/// for all `dup`licates and is never reused.
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)]
pub struct FdId(usize);
#[derive(Debug, Clone)]
struct FdIdWith<T: ?Sized> {
id: FdId,
inner: T,
}
/// A refcounted pointer to a file description, also tracking the
/// globally unique ID of this file description.
#[repr(transparent)]
#[derive(CoercePointee, Debug)]
pub struct FileDescriptionRef<T: ?Sized>(Rc<FdIdWith<T>>);
impl<T: ?Sized> Clone for FileDescriptionRef<T> {
fn clone(&self) -> Self {
FileDescriptionRef(self.0.clone())
}
}
impl<T: ?Sized> Deref for FileDescriptionRef<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0.inner
}
}
impl<T: ?Sized> FileDescriptionRef<T> {
pub fn id(&self) -> FdId {
self.0.id
}
}
/// Holds a weak reference to the actual file description.
#[derive(Clone, Debug)]
pub struct WeakFileDescriptionRef<T: ?Sized>(Weak<FdIdWith<T>>);
impl<T: ?Sized> FileDescriptionRef<T> {
pub fn downgrade(this: &Self) -> WeakFileDescriptionRef<T> {
WeakFileDescriptionRef(Rc::downgrade(&this.0))
}
}
impl<T: ?Sized> WeakFileDescriptionRef<T> {
pub fn upgrade(&self) -> Option<FileDescriptionRef<T>> {
self.0.upgrade().map(FileDescriptionRef)
}
}
impl<T> VisitProvenance for WeakFileDescriptionRef<T> {
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
// A weak reference can never be the only reference to some pointer or place.
// Since the actual file description is tracked by strong ref somewhere,
// it is ok to make this a NOP operation.
}
}
/// A helper trait to indirectly allow downcasting on `Rc<FdIdWith<dyn _>>`.
/// Ideally we'd just add a `FdIdWith<Self>: Any` bound to the `FileDescription` trait,
/// but that does not allow upcasting.
pub trait FileDescriptionExt: 'static {
fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any>;
/// We wrap the regular `close` function generically, so both handle `Rc::into_inner`
/// and epoll interest management.
fn close_ref<'tcx>(
self: FileDescriptionRef<Self>,
communicate_allowed: bool,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>>;
}
impl<T: FileDescription + 'static> FileDescriptionExt for T {
fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any> {
self.0
}
fn close_ref<'tcx>(
self: FileDescriptionRef<Self>,
communicate_allowed: bool,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
match Rc::into_inner(self.0) {
Some(fd) => {
// Remove entry from the global epoll_event_interest table.
ecx.machine.epoll_interests.remove(fd.id);
fd.inner.close(communicate_allowed, ecx)
}
None => {
// Not the last reference.
interp_ok(Ok(()))
}
}
}
}
pub type DynFileDescriptionRef = FileDescriptionRef<dyn FileDescription>;
impl FileDescriptionRef<dyn FileDescription> {
pub fn downcast<T: FileDescription + 'static>(self) -> Option<FileDescriptionRef<T>> {
let inner = self.into_rc_any().downcast::<FdIdWith<T>>().ok()?;
Some(FileDescriptionRef(inner))
}
}
/// Represents an open file description.
pub trait FileDescription: std::fmt::Debug + Any {
pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
fn name(&self) -> &'static str;
/// Reads as much as possible into the given buffer `ptr`.
/// `len` indicates how many bytes we should try to read.
/// `dest` is where the return value should be stored: number of bytes read, or `-1` in case of error.
fn read<'tcx>(
&self,
_self_ref: &FileDescriptionRef,
self: FileDescriptionRef<Self>,
_communicate_allowed: bool,
_ptr: Pointer,
_len: usize,
@ -33,8 +144,7 @@ pub trait FileDescription: std::fmt::Debug + Any {
/// `len` indicates how many bytes we should try to write.
/// `dest` is where the return value should be stored: number of bytes written, or `-1` in case of error.
fn write<'tcx>(
&self,
_self_ref: &FileDescriptionRef,
self: FileDescriptionRef<Self>,
_communicate_allowed: bool,
_ptr: Pointer,
_len: usize,
@ -54,11 +164,15 @@ pub trait FileDescription: std::fmt::Debug + Any {
throw_unsup_format!("cannot seek on {}", self.name());
}
/// Close the file descriptor.
fn close<'tcx>(
self: Box<Self>,
self,
_communicate_allowed: bool,
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
) -> InterpResult<'tcx, io::Result<()>>
where
Self: Sized,
{
throw_unsup_format!("cannot close {}", self.name());
}
@ -77,21 +191,13 @@ pub trait FileDescription: std::fmt::Debug + Any {
}
}
impl dyn FileDescription {
#[inline(always)]
pub fn downcast<T: Any>(&self) -> Option<&T> {
(self as &dyn Any).downcast_ref()
}
}
impl FileDescription for io::Stdin {
fn name(&self) -> &'static str {
"stdin"
}
fn read<'tcx>(
&self,
_self_ref: &FileDescriptionRef,
self: FileDescriptionRef<Self>,
communicate_allowed: bool,
ptr: Pointer,
len: usize,
@ -103,7 +209,7 @@ impl FileDescription for io::Stdin {
// We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
helpers::isolation_abort_error("`read` from stdin")?;
}
let result = Read::read(&mut { self }, &mut bytes);
let result = Read::read(&mut &*self, &mut bytes);
match result {
Ok(read_size) => ecx.return_read_success(ptr, &bytes, read_size, dest),
Err(e) => ecx.set_last_error_and_return(e, dest),
@ -121,8 +227,7 @@ impl FileDescription for io::Stdout {
}
fn write<'tcx>(
&self,
_self_ref: &FileDescriptionRef,
self: FileDescriptionRef<Self>,
_communicate_allowed: bool,
ptr: Pointer,
len: usize,
@ -131,7 +236,7 @@ impl FileDescription for io::Stdout {
) -> InterpResult<'tcx> {
let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
// We allow writing to stderr even with isolation enabled.
let result = Write::write(&mut { self }, bytes);
let result = Write::write(&mut &*self, bytes);
// Stdout is buffered, flush to make sure it appears on the
// screen. This is the write() syscall of the interpreted
// program, we want it to correspond to a write() syscall on
@ -155,8 +260,7 @@ impl FileDescription for io::Stderr {
}
fn write<'tcx>(
&self,
_self_ref: &FileDescriptionRef,
self: FileDescriptionRef<Self>,
_communicate_allowed: bool,
ptr: Pointer,
len: usize,
@ -166,7 +270,7 @@ impl FileDescription for io::Stderr {
let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
// We allow writing to stderr even with isolation enabled.
// No need to flush, stderr is not buffered.
let result = Write::write(&mut { self }, bytes);
let result = Write::write(&mut &*self, bytes);
match result {
Ok(write_size) => ecx.return_write_success(write_size, dest),
Err(e) => ecx.set_last_error_and_return(e, dest),
@ -188,8 +292,7 @@ impl FileDescription for NullOutput {
}
fn write<'tcx>(
&self,
_self_ref: &FileDescriptionRef,
self: FileDescriptionRef<Self>,
_communicate_allowed: bool,
_ptr: Pointer,
len: usize,
@ -201,91 +304,10 @@ impl FileDescription for NullOutput {
}
}
/// Structure contains both the file description and its unique identifier.
#[derive(Clone, Debug)]
pub struct FileDescWithId<T: FileDescription + ?Sized> {
id: FdId,
file_description: Box<T>,
}
#[derive(Clone, Debug)]
pub struct FileDescriptionRef(Rc<FileDescWithId<dyn FileDescription>>);
impl Deref for FileDescriptionRef {
type Target = dyn FileDescription;
fn deref(&self) -> &Self::Target {
&*self.0.file_description
}
}
impl FileDescriptionRef {
fn new(fd: impl FileDescription, id: FdId) -> Self {
FileDescriptionRef(Rc::new(FileDescWithId { id, file_description: Box::new(fd) }))
}
pub fn close<'tcx>(
self,
communicate_allowed: bool,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
// Destroy this `Rc` using `into_inner` so we can call `close` instead of
// implicitly running the destructor of the file description.
let id = self.get_id();
match Rc::into_inner(self.0) {
Some(fd) => {
// Remove entry from the global epoll_event_interest table.
ecx.machine.epoll_interests.remove(id);
fd.file_description.close(communicate_allowed, ecx)
}
None => interp_ok(Ok(())),
}
}
pub fn downgrade(&self) -> WeakFileDescriptionRef {
WeakFileDescriptionRef { weak_ref: Rc::downgrade(&self.0) }
}
pub fn get_id(&self) -> FdId {
self.0.id
}
}
/// Holds a weak reference to the actual file description.
#[derive(Clone, Debug, Default)]
pub struct WeakFileDescriptionRef {
weak_ref: Weak<FileDescWithId<dyn FileDescription>>,
}
impl WeakFileDescriptionRef {
pub fn upgrade(&self) -> Option<FileDescriptionRef> {
if let Some(file_desc_with_id) = self.weak_ref.upgrade() {
return Some(FileDescriptionRef(file_desc_with_id));
}
None
}
}
impl VisitProvenance for WeakFileDescriptionRef {
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
// A weak reference can never be the only reference to some pointer or place.
// Since the actual file description is tracked by strong ref somewhere,
// it is ok to make this a NOP operation.
}
}
/// A unique id for file descriptions. While we could use the address, considering that
/// is definitely unique, the address would expose interpreter internal state when used
/// for sorting things. So instead we generate a unique id per file description is the name
/// for all `dup`licates and is never reused.
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)]
pub struct FdId(usize);
/// The file descriptor table
#[derive(Debug)]
pub struct FdTable {
pub fds: BTreeMap<i32, FileDescriptionRef>,
pub fds: BTreeMap<i32, DynFileDescriptionRef>,
/// Unique identifier for file description, used to differentiate between various file description.
next_file_description_id: FdId,
}
@ -313,8 +335,9 @@ impl FdTable {
fds
}
pub fn new_ref(&mut self, fd: impl FileDescription) -> FileDescriptionRef {
let file_handle = FileDescriptionRef::new(fd, self.next_file_description_id);
pub fn new_ref<T: FileDescription>(&mut self, fd: T) -> FileDescriptionRef<T> {
let file_handle =
FileDescriptionRef(Rc::new(FdIdWith { id: self.next_file_description_id, inner: fd }));
self.next_file_description_id = FdId(self.next_file_description_id.0.strict_add(1));
file_handle
}
@ -325,12 +348,16 @@ impl FdTable {
self.insert(fd_ref)
}
pub fn insert(&mut self, fd_ref: FileDescriptionRef) -> i32 {
pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> i32 {
self.insert_with_min_num(fd_ref, 0)
}
/// Insert a file description, giving it a file descriptor that is at least `min_fd_num`.
pub fn insert_with_min_num(&mut self, file_handle: FileDescriptionRef, min_fd_num: i32) -> i32 {
pub fn insert_with_min_num(
&mut self,
file_handle: DynFileDescriptionRef,
min_fd_num: i32,
) -> i32 {
// Find the lowest unused FD, starting from min_fd. If the first such unused FD is in
// between used FDs, the find_map combinator will return it. If the first such unused FD
// is after all other used FDs, the find_map combinator will return None, and we will use
@ -356,12 +383,12 @@ impl FdTable {
new_fd_num
}
pub fn get(&self, fd_num: i32) -> Option<FileDescriptionRef> {
pub fn get(&self, fd_num: i32) -> Option<DynFileDescriptionRef> {
let fd = self.fds.get(&fd_num)?;
Some(fd.clone())
}
pub fn remove(&mut self, fd_num: i32) -> Option<FileDescriptionRef> {
pub fn remove(&mut self, fd_num: i32) -> Option<DynFileDescriptionRef> {
self.fds.remove(&fd_num)
}

View file

@ -88,7 +88,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// If old_fd and new_fd point to the same description, then `dup_fd` ensures we keep the underlying file description alive.
if let Some(old_new_fd) = this.machine.fds.fds.insert(new_fd_num, fd) {
// Ignore close error (not interpreter's) according to dup2() doc.
old_new_fd.close(this.machine.communicate(), this)?.ok();
old_new_fd.close_ref(this.machine.communicate(), this)?.ok();
}
}
interp_ok(Scalar::from_i32(new_fd_num))
@ -122,7 +122,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
};
let result = fd.as_unix().flock(this.machine.communicate(), parsed_op)?;
drop(fd);
// return `0` if flock is successful
let result = result.map(|()| 0i32);
interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
@ -198,7 +197,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let Some(fd) = this.machine.fds.remove(fd_num) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
};
let result = fd.close(this.machine.communicate(), this)?;
let result = fd.close_ref(this.machine.communicate(), this)?;
// return `0` if close is successful
let result = result.map(|()| 0i32);
interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
@ -246,7 +245,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// `usize::MAX` because it is bounded by the host's `isize`.
match offset {
None => fd.read(&fd, communicate, buf, count, dest, this)?,
None => fd.read(communicate, buf, count, dest, this)?,
Some(offset) => {
let Ok(offset) = u64::try_from(offset) else {
return this.set_last_error_and_return(LibcError("EINVAL"), dest);
@ -286,7 +285,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
};
match offset {
None => fd.write(&fd, communicate, buf, count, dest, this)?,
None => fd.write(communicate, buf, count, dest, this)?,
Some(offset) => {
let Ok(offset) = u64::try_from(offset) else {
return this.set_last_error_and_return(LibcError("EINVAL"), dest);

View file

@ -31,8 +31,7 @@ impl FileDescription for FileHandle {
}
fn read<'tcx>(
&self,
_self_ref: &FileDescriptionRef,
self: FileDescriptionRef<Self>,
communicate_allowed: bool,
ptr: Pointer,
len: usize,
@ -49,8 +48,7 @@ impl FileDescription for FileHandle {
}
fn write<'tcx>(
&self,
_self_ref: &FileDescriptionRef,
self: FileDescriptionRef<Self>,
communicate_allowed: bool,
ptr: Pointer,
len: usize,
@ -76,7 +74,7 @@ impl FileDescription for FileHandle {
}
fn close<'tcx>(
self: Box<Self>,
self,
communicate_allowed: bool,
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
@ -87,7 +85,7 @@ impl FileDescription for FileHandle {
// to handle possible errors correctly.
let result = self.file.sync_all();
// Now we actually close the file and return the result.
drop(*self);
drop(self.file);
interp_ok(result)
} else {
// We drop the file, this closes it but ignores any errors
@ -96,7 +94,7 @@ impl FileDescription for FileHandle {
// `/dev/urandom` which are read-only. Check
// https://github.com/rust-lang/miri/issues/999#issuecomment-568920439
// for a deeper discussion.
drop(*self);
drop(self.file);
interp_ok(Ok(()))
}
}
@ -1311,22 +1309,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
};
// FIXME: Support ftruncate64 for all FDs
let FileHandle { file, writable } = fd.downcast::<FileHandle>().ok_or_else(|| {
let file = fd.downcast::<FileHandle>().ok_or_else(|| {
err_unsup_format!("`ftruncate64` is only supported on file-backed file descriptors")
})?;
if *writable {
if file.writable {
if let Ok(length) = length.try_into() {
let result = file.set_len(length);
drop(fd);
let result = file.file.set_len(length);
let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
interp_ok(Scalar::from_i32(result))
} else {
drop(fd);
this.set_last_error_and_return_i32(LibcError("EINVAL"))
}
} else {
drop(fd);
// The file is not writable
this.set_last_error_and_return_i32(LibcError("EINVAL"))
}
@ -1358,11 +1353,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
};
// Only regular files support synchronization.
let FileHandle { file, writable } = fd.downcast::<FileHandle>().ok_or_else(|| {
let file = fd.downcast::<FileHandle>().ok_or_else(|| {
err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
})?;
let io_result = maybe_sync_file(file, *writable, File::sync_all);
drop(fd);
let io_result = maybe_sync_file(&file.file, file.writable, File::sync_all);
interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
}
@ -1382,11 +1376,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
};
// Only regular files support synchronization.
let FileHandle { file, writable } = fd.downcast::<FileHandle>().ok_or_else(|| {
let file = fd.downcast::<FileHandle>().ok_or_else(|| {
err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
})?;
let io_result = maybe_sync_file(file, *writable, File::sync_data);
drop(fd);
let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
}
@ -1425,11 +1418,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
};
// Only regular files support synchronization.
let FileHandle { file, writable } = fd.downcast::<FileHandle>().ok_or_else(|| {
let file = fd.downcast::<FileHandle>().ok_or_else(|| {
err_unsup_format!("`sync_data_range` is only supported on file-backed file descriptors")
})?;
let io_result = maybe_sync_file(file, *writable, File::sync_data);
drop(fd);
let io_result = maybe_sync_file(&file.file, file.writable, File::sync_data);
interp_ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
}

View file

@ -5,7 +5,9 @@ use std::rc::{Rc, Weak};
use std::time::Duration;
use crate::concurrency::VClock;
use crate::shims::files::{FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef};
use crate::shims::files::{
DynFileDescriptionRef, FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef,
};
use crate::shims::unix::UnixFileDescription;
use crate::*;
@ -22,7 +24,13 @@ struct Epoll {
// it.
ready_list: Rc<ReadyList>,
/// A list of thread ids blocked on this epoll instance.
thread_id: RefCell<Vec<ThreadId>>,
blocked_tid: RefCell<Vec<ThreadId>>,
}
impl VisitProvenance for Epoll {
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
// No provenance anywhere in this type.
}
}
/// EpollEventInstance contains information that will be returned by epoll_wait.
@ -68,7 +76,7 @@ pub struct EpollEventInterest {
/// Ready list of the epoll instance under which this EpollEventInterest is registered.
ready_list: Rc<ReadyList>,
/// The epoll file description that this EpollEventInterest is registered under.
weak_epfd: WeakFileDescriptionRef,
weak_epfd: WeakFileDescriptionRef<Epoll>,
}
/// EpollReadyEvents reflects the readiness of a file description.
@ -146,7 +154,7 @@ impl FileDescription for Epoll {
}
fn close<'tcx>(
self: Box<Self>,
self,
_communicate_allowed: bool,
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
@ -281,7 +289,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let Some(fd_ref) = this.machine.fds.get(fd) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
};
let id = fd_ref.get_id();
let id = fd_ref.id();
if op == epoll_ctl_add || op == epoll_ctl_mod {
// Read event bitmask and data from epoll_event passed by caller.
@ -343,7 +351,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
events,
data,
ready_list: Rc::clone(ready_list),
weak_epfd: epfd.downgrade(),
weak_epfd: FileDescriptionRef::downgrade(&epoll_file_description),
}));
if op == epoll_ctl_add {
@ -360,7 +368,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// Notification will be returned for current epfd if there is event in the file
// descriptor we registered.
check_and_update_one_event_interest(&fd_ref, interest, id, this)?;
check_and_update_one_event_interest(&fd_ref, &interest, id, this)?;
interp_ok(Scalar::from_i32(0))
} else if op == epoll_ctl_del {
let epoll_key = (id, fd);
@ -452,24 +460,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let Some(epfd) = this.machine.fds.get(epfd_value) else {
return this.set_last_error_and_return(LibcError("EBADF"), dest);
};
// Create a weak ref of epfd and pass it to callback so we will make sure that epfd
// is not close after the thread unblocks.
let weak_epfd = epfd.downgrade();
let Some(epfd) = epfd.downcast::<Epoll>() else {
return this.set_last_error_and_return(LibcError("EBADF"), dest);
};
// We just need to know if the ready list is empty and borrow the thread_ids out.
// The whole logic is wrapped inside a block so we don't need to manually drop epfd later.
let ready_list_empty;
let mut thread_ids;
{
let epoll_file_description = epfd
.downcast::<Epoll>()
.ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))?;
ready_list_empty = epoll_file_description.ready_list.mapping.borrow().is_empty();
thread_ids = epoll_file_description.thread_id.borrow_mut();
}
let ready_list_empty = epfd.ready_list.mapping.borrow().is_empty();
if timeout == 0 || !ready_list_empty {
// If the ready list is not empty, or the timeout is 0, we can return immediately.
return_ready_list(epfd_value, weak_epfd, dest, &event, this)?;
return_ready_list(&epfd, dest, &event, this)?;
} else {
// Blocking
let timeout = match timeout {
@ -484,31 +483,30 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
);
}
};
thread_ids.push(this.active_thread());
// Record this thread as blocked.
epfd.blocked_tid.borrow_mut().push(this.active_thread());
// And block it.
let dest = dest.clone();
// We keep a strong ref to the underlying `Epoll` to make sure it sticks around.
// This means there'll be a leak if we never wake up, but that anyway would imply
// a thread is permanently blocked so this is fine.
this.block_thread(
BlockReason::Epoll,
timeout,
callback!(
@capture<'tcx> {
epfd_value: i32,
weak_epfd: WeakFileDescriptionRef,
epfd: FileDescriptionRef<Epoll>,
dest: MPlaceTy<'tcx>,
event: MPlaceTy<'tcx>,
}
@unblock = |this| {
return_ready_list(epfd_value, weak_epfd, &dest, &event, this)?;
return_ready_list(&epfd, &dest, &event, this)?;
interp_ok(())
}
@timeout = |this| {
// No notification after blocking timeout.
let Some(epfd) = weak_epfd.upgrade() else {
throw_unsup_format!("epoll FD {epfd_value} got closed while blocking.")
};
// Remove the current active thread_id from the blocked thread_id list.
epfd.downcast::<Epoll>()
.ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))?
.thread_id.borrow_mut()
epfd
.blocked_tid.borrow_mut()
.retain(|&id| id != this.active_thread());
this.write_int(0, &dest)?;
interp_ok(())
@ -528,21 +526,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
/// do not call this function when an FD didn't have anything happen to it!
fn check_and_update_readiness(
&mut self,
fd_ref: &FileDescriptionRef,
fd_ref: DynFileDescriptionRef,
) -> InterpResult<'tcx, ()> {
let this = self.eval_context_mut();
let id = fd_ref.get_id();
let id = fd_ref.id();
let mut waiter = Vec::new();
// Get a list of EpollEventInterest that is associated to a specific file description.
if let Some(epoll_interests) = this.machine.epoll_interests.get_epoll_interest(id) {
for weak_epoll_interest in epoll_interests {
if let Some(epoll_interest) = weak_epoll_interest.upgrade() {
let is_updated = check_and_update_one_event_interest(
fd_ref,
epoll_interest.clone(),
id,
this,
)?;
let is_updated =
check_and_update_one_event_interest(&fd_ref, &epoll_interest, id, this)?;
if is_updated {
// Edge-triggered notification only notify one thread even if there are
// multiple threads blocked on the same epfd.
@ -553,10 +547,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// holds a strong ref to epoll_interest.
let epfd = epoll_interest.borrow().weak_epfd.upgrade().unwrap();
// FIXME: We can randomly pick a thread to unblock.
let epoll = epfd.downcast::<Epoll>().unwrap();
if let Some(thread_id) = epoll.thread_id.borrow_mut().pop() {
if let Some(thread_id) = epfd.blocked_tid.borrow_mut().pop() {
waiter.push(thread_id);
};
}
@ -595,8 +586,8 @@ fn ready_list_next(
/// notification was added/updated. Unlike check_and_update_readiness, this function sends a
/// notification to only one epoll instance.
fn check_and_update_one_event_interest<'tcx>(
fd_ref: &FileDescriptionRef,
interest: Rc<RefCell<EpollEventInterest>>,
fd_ref: &DynFileDescriptionRef,
interest: &Rc<RefCell<EpollEventInterest>>,
id: FdId,
ecx: &MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, bool> {
@ -627,21 +618,12 @@ fn check_and_update_one_event_interest<'tcx>(
/// Stores the ready list of the `epfd` epoll instance into `events` (which must be an array),
/// and the number of returned events into `dest`.
fn return_ready_list<'tcx>(
epfd_value: i32,
weak_epfd: WeakFileDescriptionRef,
epfd: &FileDescriptionRef<Epoll>,
dest: &MPlaceTy<'tcx>,
events: &MPlaceTy<'tcx>,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx> {
let Some(epfd) = weak_epfd.upgrade() else {
throw_unsup_format!("epoll FD {epfd_value} got closed while blocking.")
};
let epoll_file_description = epfd
.downcast::<Epoll>()
.ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))?;
let ready_list = epoll_file_description.get_ready_list();
let ready_list = epfd.get_ready_list();
let mut ready_list = ready_list.mapping.borrow_mut();
let mut num_of_events: i32 = 0;

View file

@ -20,7 +20,7 @@ const MAX_COUNTER: u64 = u64::MAX - 1;
///
/// <https://man.netbsd.org/eventfd.2>
#[derive(Debug)]
struct Event {
struct EventFd {
/// The object contains an unsigned 64-bit integer (uint64_t) counter that is maintained by the
/// kernel. This counter is initialized with the value specified in the argument initval.
counter: Cell<u64>,
@ -32,13 +32,13 @@ struct Event {
blocked_write_tid: RefCell<Vec<ThreadId>>,
}
impl FileDescription for Event {
impl FileDescription for EventFd {
fn name(&self) -> &'static str {
"event"
}
fn close<'tcx>(
self: Box<Self>,
self,
_communicate_allowed: bool,
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
@ -47,8 +47,7 @@ impl FileDescription for Event {
/// Read the counter in the buffer and return the counter if succeeded.
fn read<'tcx>(
&self,
self_ref: &FileDescriptionRef,
self: FileDescriptionRef<Self>,
_communicate_allowed: bool,
ptr: Pointer,
len: usize,
@ -65,7 +64,7 @@ impl FileDescription for Event {
// Turn the pointer into a place at the right type.
let buf_place = ecx.ptr_to_mplace_unaligned(ptr, ty);
eventfd_read(buf_place, dest, self_ref, ecx)
eventfd_read(buf_place, dest, self, ecx)
}
/// A write call adds the 8-byte integer value supplied in
@ -81,8 +80,7 @@ impl FileDescription for Event {
/// supplied buffer is less than 8 bytes, or if an attempt is
/// made to write the value 0xffffffffffffffff.
fn write<'tcx>(
&self,
self_ref: &FileDescriptionRef,
self: FileDescriptionRef<Self>,
_communicate_allowed: bool,
ptr: Pointer,
len: usize,
@ -99,7 +97,7 @@ impl FileDescription for Event {
// Turn the pointer into a place at the right type.
let buf_place = ecx.ptr_to_mplace_unaligned(ptr, ty);
eventfd_write(buf_place, dest, self_ref, ecx)
eventfd_write(buf_place, dest, self, ecx)
}
fn as_unix(&self) -> &dyn UnixFileDescription {
@ -107,7 +105,7 @@ impl FileDescription for Event {
}
}
impl UnixFileDescription for Event {
impl UnixFileDescription for EventFd {
fn get_epoll_ready_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadyEvents> {
// We only check the status of EPOLLIN and EPOLLOUT flags for eventfd. If other event flags
// need to be supported in the future, the check should be added here.
@ -169,7 +167,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let fds = &mut this.machine.fds;
let fd_value = fds.insert_new(Event {
let fd_value = fds.insert_new(EventFd {
counter: Cell::new(val.into()),
is_nonblock,
clock: RefCell::new(VClock::default()),
@ -186,13 +184,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
fn eventfd_write<'tcx>(
buf_place: MPlaceTy<'tcx>,
dest: &MPlaceTy<'tcx>,
eventfd_ref: &FileDescriptionRef,
eventfd: FileDescriptionRef<EventFd>,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx> {
// Since we pass the weak file description ref, it is guaranteed to be
// an eventfd file description.
let eventfd = eventfd_ref.downcast::<Event>().unwrap();
// Figure out which value we should add.
let num = ecx.read_scalar(&buf_place)?.to_u64()?;
// u64::MAX as input is invalid because the maximum value of counter is u64::MAX - 1.
@ -210,10 +204,6 @@ fn eventfd_write<'tcx>(
// Store new counter value.
eventfd.counter.set(new_count);
// The state changed; we check and update the status of all supported event
// types for current file description.
ecx.check_and_update_readiness(eventfd_ref)?;
// Unblock *all* threads previously blocked on `read`.
// We need to take out the blocked thread ids and unblock them together,
// because `unblock_threads` may block them again and end up re-adding the
@ -224,6 +214,10 @@ fn eventfd_write<'tcx>(
ecx.unblock_thread(thread_id, BlockReason::Eventfd)?;
}
// The state changed; we check and update the status of all supported event
// types for current file description.
ecx.check_and_update_readiness(eventfd)?;
// Return how many bytes we consumed from the user-provided buffer.
return ecx.write_int(buf_place.layout.size.bytes(), dest);
}
@ -237,7 +231,7 @@ fn eventfd_write<'tcx>(
eventfd.blocked_write_tid.borrow_mut().push(ecx.active_thread());
let weak_eventfd = eventfd_ref.downgrade();
let weak_eventfd = FileDescriptionRef::downgrade(&eventfd);
ecx.block_thread(
BlockReason::Eventfd,
None,
@ -246,13 +240,13 @@ fn eventfd_write<'tcx>(
num: u64,
buf_place: MPlaceTy<'tcx>,
dest: MPlaceTy<'tcx>,
weak_eventfd: WeakFileDescriptionRef,
weak_eventfd: WeakFileDescriptionRef<EventFd>,
}
@unblock = |this| {
// When we get unblocked, try again. We know the ref is still valid,
// otherwise there couldn't be a `write` that unblocks us.
let eventfd_ref = weak_eventfd.upgrade().unwrap();
eventfd_write(buf_place, &dest, &eventfd_ref, this)
eventfd_write(buf_place, &dest, eventfd_ref, this)
}
),
);
@ -266,13 +260,9 @@ fn eventfd_write<'tcx>(
fn eventfd_read<'tcx>(
buf_place: MPlaceTy<'tcx>,
dest: &MPlaceTy<'tcx>,
eventfd_ref: &FileDescriptionRef,
eventfd: FileDescriptionRef<EventFd>,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx> {
// Since we pass the weak file description ref to the callback function, it is guaranteed to be
// an eventfd file description.
let eventfd = eventfd_ref.downcast::<Event>().unwrap();
// Set counter to 0, get old value.
let counter = eventfd.counter.replace(0);
@ -285,7 +275,7 @@ fn eventfd_read<'tcx>(
eventfd.blocked_read_tid.borrow_mut().push(ecx.active_thread());
let weak_eventfd = eventfd_ref.downgrade();
let weak_eventfd = FileDescriptionRef::downgrade(&eventfd);
ecx.block_thread(
BlockReason::Eventfd,
None,
@ -293,13 +283,13 @@ fn eventfd_read<'tcx>(
@capture<'tcx> {
buf_place: MPlaceTy<'tcx>,
dest: MPlaceTy<'tcx>,
weak_eventfd: WeakFileDescriptionRef,
weak_eventfd: WeakFileDescriptionRef<EventFd>,
}
@unblock = |this| {
// When we get unblocked, try again. We know the ref is still valid,
// otherwise there couldn't be a `write` that unblocks us.
let eventfd_ref = weak_eventfd.upgrade().unwrap();
eventfd_read(buf_place, &dest, &eventfd_ref, this)
eventfd_read(buf_place, &dest, eventfd_ref, this)
}
),
);
@ -310,10 +300,6 @@ fn eventfd_read<'tcx>(
// Return old counter value into user-space buffer.
ecx.write_int(counter, &buf_place)?;
// The state changed; we check and update the status of all supported event
// types for current file description.
ecx.check_and_update_readiness(eventfd_ref)?;
// Unblock *all* threads previously blocked on `write`.
// We need to take out the blocked thread ids and unblock them together,
// because `unblock_threads` may block them again and end up re-adding the
@ -324,6 +310,10 @@ fn eventfd_read<'tcx>(
ecx.unblock_thread(thread_id, BlockReason::Eventfd)?;
}
// The state changed; we check and update the status of all supported event
// types for current file description.
ecx.check_and_update_readiness(eventfd)?;
// Tell userspace how many bytes we put into the buffer.
return ecx.write_int(buf_place.layout.size.bytes(), dest);
}

View file

@ -31,7 +31,7 @@ struct AnonSocket {
/// The `AnonSocket` file descriptor that is our "peer", and that holds the buffer we are
/// writing to. This is a weak reference because the other side may be closed before us; all
/// future writes will then trigger EPIPE.
peer_fd: OnceCell<WeakFileDescriptionRef>,
peer_fd: OnceCell<WeakFileDescriptionRef<AnonSocket>>,
/// Indicates whether the peer has lost data when the file description is closed.
/// This flag is set to `true` if the peer's `readbuf` is non-empty at the time
/// of closure.
@ -58,7 +58,7 @@ impl Buffer {
}
impl AnonSocket {
fn peer_fd(&self) -> &WeakFileDescriptionRef {
fn peer_fd(&self) -> &WeakFileDescriptionRef<AnonSocket> {
self.peer_fd.get().unwrap()
}
}
@ -69,7 +69,7 @@ impl FileDescription for AnonSocket {
}
fn close<'tcx>(
self: Box<Self>,
self,
_communicate_allowed: bool,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
@ -78,37 +78,35 @@ impl FileDescription for AnonSocket {
// notify the peer that data lost has happened in current file description.
if let Some(readbuf) = &self.readbuf {
if !readbuf.borrow().buf.is_empty() {
peer_fd.downcast::<AnonSocket>().unwrap().peer_lost_data.set(true);
peer_fd.peer_lost_data.set(true);
}
}
// Notify peer fd that close has happened, since that can unblock reads and writes.
ecx.check_and_update_readiness(&peer_fd)?;
ecx.check_and_update_readiness(peer_fd)?;
}
interp_ok(Ok(()))
}
fn read<'tcx>(
&self,
self_ref: &FileDescriptionRef,
self: FileDescriptionRef<Self>,
_communicate_allowed: bool,
ptr: Pointer,
len: usize,
dest: &MPlaceTy<'tcx>,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx> {
anonsocket_read(self_ref, len, ptr, dest, ecx)
anonsocket_read(self, len, ptr, dest, ecx)
}
fn write<'tcx>(
&self,
self_ref: &FileDescriptionRef,
self: FileDescriptionRef<Self>,
_communicate_allowed: bool,
ptr: Pointer,
len: usize,
dest: &MPlaceTy<'tcx>,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx> {
anonsocket_write(self_ref, ptr, len, dest, ecx)
anonsocket_write(self, ptr, len, dest, ecx)
}
fn as_unix(&self) -> &dyn UnixFileDescription {
@ -118,14 +116,12 @@ impl FileDescription for AnonSocket {
/// Write to AnonSocket based on the space available and return the written byte size.
fn anonsocket_write<'tcx>(
self_ref: &FileDescriptionRef,
self_ref: FileDescriptionRef<AnonSocket>,
ptr: Pointer,
len: usize,
dest: &MPlaceTy<'tcx>,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx> {
let self_anonsocket = self_ref.downcast::<AnonSocket>().unwrap();
// Always succeed on write size 0.
// ("If count is zero and fd refers to a file other than a regular file, the results are not specified.")
if len == 0 {
@ -133,13 +129,13 @@ fn anonsocket_write<'tcx>(
}
// We are writing to our peer's readbuf.
let Some(peer_fd) = self_anonsocket.peer_fd().upgrade() else {
let Some(peer_fd) = self_ref.peer_fd().upgrade() else {
// If the upgrade from Weak to Rc fails, it indicates that all read ends have been
// closed. It is an error to write even if there would be space.
return ecx.set_last_error_and_return(ErrorKind::BrokenPipe, dest);
};
let Some(writebuf) = &peer_fd.downcast::<AnonSocket>().unwrap().readbuf else {
let Some(writebuf) = &peer_fd.readbuf else {
// Writing to the read end of a pipe.
return ecx.set_last_error_and_return(IoError::LibcError("EBADF"), dest);
};
@ -147,21 +143,21 @@ fn anonsocket_write<'tcx>(
// Let's see if we can write.
let available_space = MAX_SOCKETPAIR_BUFFER_CAPACITY.strict_sub(writebuf.borrow().buf.len());
if available_space == 0 {
if self_anonsocket.is_nonblock {
if self_ref.is_nonblock {
// Non-blocking socketpair with a full buffer.
return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest);
} else {
self_ref.blocked_write_tid.borrow_mut().push(ecx.active_thread());
// Blocking socketpair with a full buffer.
// Block the current thread; only keep a weak ref for this.
let weak_self_ref = self_ref.downgrade();
let weak_self_ref = FileDescriptionRef::downgrade(&self_ref);
let dest = dest.clone();
self_anonsocket.blocked_write_tid.borrow_mut().push(ecx.active_thread());
ecx.block_thread(
BlockReason::UnnamedSocket,
None,
callback!(
@capture<'tcx> {
weak_self_ref: WeakFileDescriptionRef,
weak_self_ref: WeakFileDescriptionRef<AnonSocket>,
ptr: Pointer,
len: usize,
dest: MPlaceTy<'tcx>,
@ -170,7 +166,7 @@ fn anonsocket_write<'tcx>(
// If we got unblocked, then our peer successfully upgraded its weak
// ref to us. That means we can also upgrade our weak ref.
let self_ref = weak_self_ref.upgrade().unwrap();
anonsocket_write(&self_ref, ptr, len, &dest, this)
anonsocket_write(self_ref, ptr, len, &dest, this)
}
),
);
@ -190,16 +186,15 @@ fn anonsocket_write<'tcx>(
// Need to stop accessing peer_fd so that it can be notified.
drop(writebuf);
// Notification should be provided for peer fd as it became readable.
// The kernel does this even if the fd was already readable before, so we follow suit.
ecx.check_and_update_readiness(&peer_fd)?;
let peer_anonsocket = peer_fd.downcast::<AnonSocket>().unwrap();
// Unblock all threads that are currently blocked on peer_fd's read.
let waiting_threads = std::mem::take(&mut *peer_anonsocket.blocked_read_tid.borrow_mut());
let waiting_threads = std::mem::take(&mut *peer_fd.blocked_read_tid.borrow_mut());
// FIXME: We can randomize the order of unblocking.
for thread_id in waiting_threads {
ecx.unblock_thread(thread_id, BlockReason::UnnamedSocket)?;
}
// Notification should be provided for peer fd as it became readable.
// The kernel does this even if the fd was already readable before, so we follow suit.
ecx.check_and_update_readiness(peer_fd)?;
return ecx.return_write_success(actual_write_size, dest);
}
@ -208,31 +203,29 @@ fn anonsocket_write<'tcx>(
/// Read from AnonSocket and return the number of bytes read.
fn anonsocket_read<'tcx>(
self_ref: &FileDescriptionRef,
self_ref: FileDescriptionRef<AnonSocket>,
len: usize,
ptr: Pointer,
dest: &MPlaceTy<'tcx>,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx> {
let self_anonsocket = self_ref.downcast::<AnonSocket>().unwrap();
// Always succeed on read size 0.
if len == 0 {
return ecx.return_read_success(ptr, &[], 0, dest);
}
let Some(readbuf) = &self_anonsocket.readbuf else {
let Some(readbuf) = &self_ref.readbuf else {
// FIXME: This should return EBADF, but there's no nice way to do that as there's no
// corresponding ErrorKind variant.
throw_unsup_format!("reading from the write end of a pipe")
};
if readbuf.borrow_mut().buf.is_empty() {
if self_anonsocket.peer_fd().upgrade().is_none() {
if self_ref.peer_fd().upgrade().is_none() {
// Socketpair with no peer and empty buffer.
// 0 bytes successfully read indicates end-of-file.
return ecx.return_read_success(ptr, &[], 0, dest);
} else if self_anonsocket.is_nonblock {
} else if self_ref.is_nonblock {
// Non-blocking socketpair with writer and empty buffer.
// https://linux.die.net/man/2/read
// EAGAIN or EWOULDBLOCK can be returned for socket,
@ -240,17 +233,17 @@ fn anonsocket_read<'tcx>(
// Since there is no ErrorKind for EAGAIN, WouldBlock is used.
return ecx.set_last_error_and_return(ErrorKind::WouldBlock, dest);
} else {
self_ref.blocked_read_tid.borrow_mut().push(ecx.active_thread());
// Blocking socketpair with writer and empty buffer.
// Block the current thread; only keep a weak ref for this.
let weak_self_ref = self_ref.downgrade();
let weak_self_ref = FileDescriptionRef::downgrade(&self_ref);
let dest = dest.clone();
self_anonsocket.blocked_read_tid.borrow_mut().push(ecx.active_thread());
ecx.block_thread(
BlockReason::UnnamedSocket,
None,
callback!(
@capture<'tcx> {
weak_self_ref: WeakFileDescriptionRef,
weak_self_ref: WeakFileDescriptionRef<AnonSocket>,
len: usize,
ptr: Pointer,
dest: MPlaceTy<'tcx>,
@ -259,7 +252,7 @@ fn anonsocket_read<'tcx>(
// If we got unblocked, then our peer successfully upgraded its weak
// ref to us. That means we can also upgrade our weak ref.
let self_ref = weak_self_ref.upgrade().unwrap();
anonsocket_read(&self_ref, len, ptr, &dest, this)
anonsocket_read(self_ref, len, ptr, &dest, this)
}
),
);
@ -287,16 +280,15 @@ fn anonsocket_read<'tcx>(
// don't know what that *certain number* is, we will provide a notification every time
// a read is successful. This might result in our epoll emulation providing more
// notifications than the real system.
if let Some(peer_fd) = self_anonsocket.peer_fd().upgrade() {
ecx.check_and_update_readiness(&peer_fd)?;
let peer_anonsocket = peer_fd.downcast::<AnonSocket>().unwrap();
if let Some(peer_fd) = self_ref.peer_fd().upgrade() {
// Unblock all threads that are currently blocked on peer_fd's write.
let waiting_threads =
std::mem::take(&mut *peer_anonsocket.blocked_write_tid.borrow_mut());
let waiting_threads = std::mem::take(&mut *peer_fd.blocked_write_tid.borrow_mut());
// FIXME: We can randomize the order of unblocking.
for thread_id in waiting_threads {
ecx.unblock_thread(thread_id, BlockReason::UnnamedSocket)?;
}
// Notify epoll waiters.
ecx.check_and_update_readiness(peer_fd)?;
};
return ecx.return_read_success(ptr, &bytes, actual_read_size, dest);
@ -323,7 +315,7 @@ impl UnixFileDescription for AnonSocket {
// Check if is writable.
if let Some(peer_fd) = self.peer_fd().upgrade() {
if let Some(writebuf) = &peer_fd.downcast::<AnonSocket>().unwrap().readbuf {
if let Some(writebuf) = &peer_fd.readbuf {
let data_size = writebuf.borrow().buf.len();
let available_space = MAX_SOCKETPAIR_BUFFER_CAPACITY.strict_sub(data_size);
if available_space != 0 {
@ -429,8 +421,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
});
// Make the file descriptions point to each other.
fd0.downcast::<AnonSocket>().unwrap().peer_fd.set(fd1.downgrade()).unwrap();
fd1.downcast::<AnonSocket>().unwrap().peer_fd.set(fd0.downgrade()).unwrap();
fd0.peer_fd.set(FileDescriptionRef::downgrade(&fd1)).unwrap();
fd1.peer_fd.set(FileDescriptionRef::downgrade(&fd0)).unwrap();
// Insert the file description to the fd table, generating the file descriptors.
let sv0 = fds.insert(fd0);
@ -497,8 +489,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
});
// Make the file descriptions point to each other.
fd0.downcast::<AnonSocket>().unwrap().peer_fd.set(fd1.downgrade()).unwrap();
fd1.downcast::<AnonSocket>().unwrap().peer_fd.set(fd0.downgrade()).unwrap();
fd0.peer_fd.set(FileDescriptionRef::downgrade(&fd1)).unwrap();
fd1.peer_fd.set(FileDescriptionRef::downgrade(&fd0)).unwrap();
// Insert the file description to the fd table, generating the file descriptors.
let pipefd0 = fds.insert(fd0);