about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-05-04 09:52:39 +0000
committerbors <bors@rust-lang.org>2024-05-04 09:52:39 +0000
commit7b57f122b93f3eb37b6dd8db2fc1bf2ae621c393 (patch)
treea23f8cf9fe432bafd6cea2c6da5c31e036b839cc
parent705d48cff9b18daaf506297b05ae8c2e4577411b (diff)
parent459c6ce944ccef19f5da60d8ee1fbdccf685e2b3 (diff)
downloadrust-7b57f122b93f3eb37b6dd8db2fc1bf2ae621c393.tar.gz
rust-7b57f122b93f3eb37b6dd8db2fc1bf2ae621c393.zip
Auto merge of #3533 - Luv-Ray:file-descriptors-to-refcount-references, r=RalfJung
Make file descriptors into refcount references

fixes #3525

Remove `fn dup` in `trait FileDescription`, define `struct FileDescriptor(Rc<RefCell<dyn FileDescription>>)`, and use `BTreeMap<i32, FileDescriptor>` in `FdTable`.

---

There are some refactors similar to the following form:
```rust
{  // origin:
    if let Some(file_descriptor) = this.machine.fds.get_mut(fd) {
        // write file_descriptor
        this.try_unwrap_io_result(result)
    } else {
        this.fd_not_found()
    }
}
{  // now:
    let Some(mut file_descriptor) = this.machine.fds.get_mut(fd) else {
        return this.fd_not_found();
    };
    // write file_descriptor
    drop(file_descriptor);
    this.try_unwrap_io_result(result)
}
```
The origin form can't compile because as using `RefCell` to get interior mutability, `fn get_mut` return `Option<std::cell::RefMut<'_, dyn FileDescription>>` instead of `Option<&mut dyn FileDescription>` now, and the `deref_mut` on `file_descriptor: RefMut` will cause borrow `this` as mutable more than once at a time.
So this form of refactors and manual drops are are implemented to avoid borrowing `this` at the same time.
-rw-r--r--src/tools/miri/src/shims/unix/fd.rs172
-rw-r--r--src/tools/miri/src/shims/unix/fs.rs120
-rw-r--r--src/tools/miri/src/shims/unix/linux/epoll.rs69
-rw-r--r--src/tools/miri/src/shims/unix/linux/eventfd.rs15
-rw-r--r--src/tools/miri/src/shims/unix/mod.rs2
-rw-r--r--src/tools/miri/src/shims/unix/socket.rs16
6 files changed, 193 insertions, 201 deletions
diff --git a/src/tools/miri/src/shims/unix/fd.rs b/src/tools/miri/src/shims/unix/fd.rs
index 18a41f6c667..566988cba1f 100644
--- a/src/tools/miri/src/shims/unix/fd.rs
+++ b/src/tools/miri/src/shims/unix/fd.rs
@@ -2,8 +2,10 @@
 //! standard file descriptors (stdin/stdout/stderr).
 
 use std::any::Any;
+use std::cell::{Ref, RefCell, RefMut};
 use std::collections::BTreeMap;
 use std::io::{self, ErrorKind, IsTerminal, Read, SeekFrom, Write};
+use std::rc::Rc;
 
 use rustc_middle::ty::TyCtxt;
 use rustc_target::abi::Size;
@@ -12,7 +14,7 @@ use crate::shims::unix::*;
 use crate::*;
 
 /// Represents an open file descriptor.
-pub trait FileDescriptor: std::fmt::Debug + Any {
+pub trait FileDescription: std::fmt::Debug + Any {
     fn name(&self) -> &'static str;
 
     fn read<'tcx>(
@@ -44,13 +46,10 @@ pub trait FileDescriptor: std::fmt::Debug + Any {
     fn close<'tcx>(
         self: Box<Self>,
         _communicate_allowed: bool,
-    ) -> InterpResult<'tcx, io::Result<i32>> {
+    ) -> InterpResult<'tcx, io::Result<()>> {
         throw_unsup_format!("cannot close {}", self.name());
     }
 
-    /// Return a new file descriptor *that refers to the same underlying object*.
-    fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>>;
-
     fn is_tty(&self, _communicate_allowed: bool) -> bool {
         // Most FDs are not tty's and the consequence of a wrong `false` are minor,
         // so we use a default impl here.
@@ -58,7 +57,7 @@ pub trait FileDescriptor: std::fmt::Debug + Any {
     }
 }
 
-impl dyn FileDescriptor {
+impl dyn FileDescription {
     #[inline(always)]
     pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
         (self as &dyn Any).downcast_ref()
@@ -70,7 +69,7 @@ impl dyn FileDescriptor {
     }
 }
 
-impl FileDescriptor for io::Stdin {
+impl FileDescription for io::Stdin {
     fn name(&self) -> &'static str {
         "stdin"
     }
@@ -88,16 +87,12 @@ impl FileDescriptor for io::Stdin {
         Ok(Read::read(self, bytes))
     }
 
-    fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
-        Ok(Box::new(io::stdin()))
-    }
-
     fn is_tty(&self, communicate_allowed: bool) -> bool {
         communicate_allowed && self.is_terminal()
     }
 }
 
-impl FileDescriptor for io::Stdout {
+impl FileDescription for io::Stdout {
     fn name(&self) -> &'static str {
         "stdout"
     }
@@ -120,16 +115,12 @@ impl FileDescriptor for io::Stdout {
         Ok(result)
     }
 
-    fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
-        Ok(Box::new(io::stdout()))
-    }
-
     fn is_tty(&self, communicate_allowed: bool) -> bool {
         communicate_allowed && self.is_terminal()
     }
 }
 
-impl FileDescriptor for io::Stderr {
+impl FileDescription for io::Stderr {
     fn name(&self) -> &'static str {
         "stderr"
     }
@@ -145,10 +136,6 @@ impl FileDescriptor for io::Stderr {
         Ok(Write::write(&mut { self }, bytes))
     }
 
-    fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
-        Ok(Box::new(io::stderr()))
-    }
-
     fn is_tty(&self, communicate_allowed: bool) -> bool {
         communicate_allowed && self.is_terminal()
     }
@@ -158,7 +145,7 @@ impl FileDescriptor for io::Stderr {
 #[derive(Debug)]
 pub struct NullOutput;
 
-impl FileDescriptor for NullOutput {
+impl FileDescription for NullOutput {
     fn name(&self) -> &'static str {
         "stderr and stdout"
     }
@@ -172,16 +159,30 @@ impl FileDescriptor for NullOutput {
         // We just don't write anything, but report to the user that we did.
         Ok(Ok(bytes.len()))
     }
+}
+
+#[derive(Clone, Debug)]
+pub struct FileDescriptor(Rc<RefCell<Box<dyn FileDescription>>>);
+
+impl FileDescriptor {
+    pub fn new<T: FileDescription>(fd: T) -> Self {
+        FileDescriptor(Rc::new(RefCell::new(Box::new(fd))))
+    }
 
-    fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
-        Ok(Box::new(NullOutput))
+    pub fn close<'ctx>(self, communicate_allowed: bool) -> InterpResult<'ctx, io::Result<()>> {
+        // Destroy this `Rc` using `into_inner` so we can call `close` instead of
+        // implicitly running the destructor of the file description.
+        match Rc::into_inner(self.0) {
+            Some(fd) => RefCell::into_inner(fd).close(communicate_allowed),
+            None => Ok(Ok(())),
+        }
     }
 }
 
 /// The file descriptor table
 #[derive(Debug)]
 pub struct FdTable {
-    pub fds: BTreeMap<i32, Box<dyn FileDescriptor>>,
+    pub fds: BTreeMap<i32, FileDescriptor>,
 }
 
 impl VisitProvenance for FdTable {
@@ -192,28 +193,24 @@ impl VisitProvenance for FdTable {
 
 impl FdTable {
     pub(crate) fn new(mute_stdout_stderr: bool) -> FdTable {
-        let mut fds: BTreeMap<_, Box<dyn FileDescriptor>> = BTreeMap::new();
-        fds.insert(0i32, Box::new(io::stdin()));
+        let mut fds: BTreeMap<_, FileDescriptor> = BTreeMap::new();
+        fds.insert(0i32, FileDescriptor::new(io::stdin()));
         if mute_stdout_stderr {
-            fds.insert(1i32, Box::new(NullOutput));
-            fds.insert(2i32, Box::new(NullOutput));
+            fds.insert(1i32, FileDescriptor::new(NullOutput));
+            fds.insert(2i32, FileDescriptor::new(NullOutput));
         } else {
-            fds.insert(1i32, Box::new(io::stdout()));
-            fds.insert(2i32, Box::new(io::stderr()));
+            fds.insert(1i32, FileDescriptor::new(io::stdout()));
+            fds.insert(2i32, FileDescriptor::new(io::stderr()));
         }
         FdTable { fds }
     }
 
-    pub fn insert_fd(&mut self, file_handle: Box<dyn FileDescriptor>) -> i32 {
+    pub fn insert_fd(&mut self, file_handle: FileDescriptor) -> i32 {
         self.insert_fd_with_min_fd(file_handle, 0)
     }
 
     /// Insert a new FD that is at least `min_fd`.
-    pub fn insert_fd_with_min_fd(
-        &mut self,
-        file_handle: Box<dyn FileDescriptor>,
-        min_fd: i32,
-    ) -> i32 {
+    pub fn insert_fd_with_min_fd(&mut self, file_handle: FileDescriptor, min_fd: 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
@@ -239,15 +236,22 @@ impl FdTable {
         new_fd
     }
 
-    pub fn get(&self, fd: i32) -> Option<&dyn FileDescriptor> {
-        Some(&**self.fds.get(&fd)?)
+    pub fn get(&self, fd: i32) -> Option<Ref<'_, dyn FileDescription>> {
+        let fd = self.fds.get(&fd)?;
+        Some(Ref::map(fd.0.borrow(), |fd| fd.as_ref()))
     }
 
-    pub fn get_mut(&mut self, fd: i32) -> Option<&mut dyn FileDescriptor> {
-        Some(&mut **self.fds.get_mut(&fd)?)
+    pub fn get_mut(&self, fd: i32) -> Option<RefMut<'_, dyn FileDescription>> {
+        let fd = self.fds.get(&fd)?;
+        Some(RefMut::map(fd.0.borrow_mut(), |fd| fd.as_mut()))
     }
 
-    pub fn remove(&mut self, fd: i32) -> Option<Box<dyn FileDescriptor>> {
+    pub fn dup(&self, fd: i32) -> Option<FileDescriptor> {
+        let fd = self.fds.get(&fd)?;
+        Some(fd.clone())
+    }
+
+    pub fn remove(&mut self, fd: i32) -> Option<FileDescriptor> {
         self.fds.remove(&fd)
     }
 
@@ -296,17 +300,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
             }
             let start = this.read_scalar(&args[2])?.to_i32()?;
 
-            match this.machine.fds.get_mut(fd) {
-                Some(file_descriptor) => {
-                    let dup_result = file_descriptor.dup();
-                    match dup_result {
-                        Ok(dup_fd) => Ok(this.machine.fds.insert_fd_with_min_fd(dup_fd, start)),
-                        Err(e) => {
-                            this.set_last_error_from_io_error(e.kind())?;
-                            Ok(-1)
-                        }
-                    }
-                }
+            match this.machine.fds.dup(fd) {
+                Some(dup_fd) => Ok(this.machine.fds.insert_fd_with_min_fd(dup_fd, start)),
                 None => this.fd_not_found(),
             }
         } else if this.tcx.sess.target.os == "macos" && cmd == this.eval_libc_i32("F_FULLFSYNC") {
@@ -330,6 +325,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
 
         Ok(Scalar::from_i32(if let Some(file_descriptor) = this.machine.fds.remove(fd) {
             let result = file_descriptor.close(this.machine.communicate())?;
+            // return `0` if close is successful
+            let result = result.map(|()| 0i32);
             this.try_unwrap_io_result(result)?
         } else {
             this.fd_not_found()?
@@ -369,32 +366,33 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
             .min(u64::try_from(isize::MAX).unwrap());
         let communicate = this.machine.communicate();
 
-        if let Some(file_descriptor) = this.machine.fds.get_mut(fd) {
-            trace!("read: FD mapped to {:?}", file_descriptor);
-            // We want to read at most `count` bytes. We are sure that `count` is not negative
-            // because it was a target's `usize`. Also we are sure that its smaller than
-            // `usize::MAX` because it is bounded by the host's `isize`.
-            let mut bytes = vec![0; usize::try_from(count).unwrap()];
-            // `File::read` never returns a value larger than `count`,
-            // so this cannot fail.
-            let result = file_descriptor
-                .read(communicate, &mut bytes, *this.tcx)?
-                .map(|c| i64::try_from(c).unwrap());
-
-            match result {
-                Ok(read_bytes) => {
-                    // If reading to `bytes` did not fail, we write those bytes to the buffer.
-                    this.write_bytes_ptr(buf, bytes)?;
-                    Ok(read_bytes)
-                }
-                Err(e) => {
-                    this.set_last_error_from_io_error(e.kind())?;
-                    Ok(-1)
-                }
-            }
-        } else {
+        let Some(mut file_descriptor) = this.machine.fds.get_mut(fd) else {
             trace!("read: FD not found");
-            this.fd_not_found()
+            return this.fd_not_found();
+        };
+
+        trace!("read: FD mapped to {:?}", file_descriptor);
+        // We want to read at most `count` bytes. We are sure that `count` is not negative
+        // because it was a target's `usize`. Also we are sure that its smaller than
+        // `usize::MAX` because it is bounded by the host's `isize`.
+        let mut bytes = vec![0; usize::try_from(count).unwrap()];
+        // `File::read` never returns a value larger than `count`,
+        // so this cannot fail.
+        let result = file_descriptor
+            .read(communicate, &mut bytes, *this.tcx)?
+            .map(|c| i64::try_from(c).unwrap());
+        drop(file_descriptor);
+
+        match result {
+            Ok(read_bytes) => {
+                // If reading to `bytes` did not fail, we write those bytes to the buffer.
+                this.write_bytes_ptr(buf, bytes)?;
+                Ok(read_bytes)
+            }
+            Err(e) => {
+                this.set_last_error_from_io_error(e.kind())?;
+                Ok(-1)
+            }
         }
     }
 
@@ -419,13 +417,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         let communicate = this.machine.communicate();
 
         let bytes = this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(count))?.to_owned();
-        if let Some(file_descriptor) = this.machine.fds.get_mut(fd) {
-            let result = file_descriptor
-                .write(communicate, &bytes, *this.tcx)?
-                .map(|c| i64::try_from(c).unwrap());
-            this.try_unwrap_io_result(result)
-        } else {
-            this.fd_not_found()
-        }
+        let Some(mut file_descriptor) = this.machine.fds.get_mut(fd) else {
+            return this.fd_not_found();
+        };
+
+        let result = file_descriptor
+            .write(communicate, &bytes, *this.tcx)?
+            .map(|c| i64::try_from(c).unwrap());
+        drop(file_descriptor);
+
+        this.try_unwrap_io_result(result)
     }
 }
diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs
index 0bf0e3d52c3..058747916c0 100644
--- a/src/tools/miri/src/shims/unix/fs.rs
+++ b/src/tools/miri/src/shims/unix/fs.rs
@@ -17,15 +17,17 @@ use crate::shims::unix::*;
 use crate::*;
 use shims::time::system_time_to_duration;
 
+use self::fd::FileDescriptor;
+
 #[derive(Debug)]
 struct FileHandle {
     file: File,
     writable: bool,
 }
 
-impl FileDescriptor for FileHandle {
+impl FileDescription for FileHandle {
     fn name(&self) -> &'static str {
-        "FILE"
+        "file"
     }
 
     fn read<'tcx>(
@@ -60,16 +62,14 @@ impl FileDescriptor for FileHandle {
     fn close<'tcx>(
         self: Box<Self>,
         communicate_allowed: bool,
-    ) -> InterpResult<'tcx, io::Result<i32>> {
+    ) -> InterpResult<'tcx, io::Result<()>> {
         assert!(communicate_allowed, "isolation should have prevented even opening a file");
         // We sync the file if it was opened in a mode different than read-only.
         if self.writable {
             // `File::sync_all` does the checks that are done when closing a file. We do this to
             // to handle possible errors correctly.
-            let result = self.file.sync_all().map(|_| 0i32);
-            // Now we actually close the file.
-            drop(self);
-            // And return the result.
+            let result = self.file.sync_all();
+            // Now we actually close the file and return the result.
             Ok(result)
         } else {
             // We drop the file, this closes it but ignores any errors
@@ -78,16 +78,10 @@ impl FileDescriptor 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);
-            Ok(Ok(0))
+            Ok(Ok(()))
         }
     }
 
-    fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
-        let duplicated = self.file.try_clone()?;
-        Ok(Box::new(FileHandle { file: duplicated, writable: self.writable }))
-    }
-
     fn is_tty(&self, communicate_allowed: bool) -> bool {
         communicate_allowed && self.file.is_terminal()
     }
@@ -399,7 +393,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
 
         let fd = options.open(path).map(|file| {
             let fh = &mut this.machine.fds;
-            fh.insert_fd(Box::new(FileHandle { file, writable }))
+            fh.insert_fd(FileDescriptor::new(FileHandle { file, writable }))
         });
 
         this.try_unwrap_io_result(fd)
@@ -428,14 +422,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         };
 
         let communicate = this.machine.communicate();
-        Ok(Scalar::from_i64(if let Some(file_descriptor) = this.machine.fds.get_mut(fd) {
-            let result = file_descriptor
-                .seek(communicate, seek_from)?
-                .map(|offset| i64::try_from(offset).unwrap());
-            this.try_unwrap_io_result(result)?
-        } else {
-            this.fd_not_found()?
-        }))
+
+        let Some(mut file_descriptor) = this.machine.fds.get_mut(fd) else {
+            return Ok(Scalar::from_i64(this.fd_not_found()?));
+        };
+        let result = file_descriptor
+            .seek(communicate, seek_from)?
+            .map(|offset| i64::try_from(offset).unwrap());
+        drop(file_descriptor);
+
+        let result = this.try_unwrap_io_result(result)?;
+        Ok(Scalar::from_i64(result))
     }
 
     fn unlink(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
@@ -1131,32 +1128,35 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
             return Ok(Scalar::from_i32(this.fd_not_found()?));
         }
 
-        Ok(Scalar::from_i32(if let Some(file_descriptor) = this.machine.fds.get_mut(fd) {
-            // FIXME: Support ftruncate64 for all FDs
-            let FileHandle { file, writable } =
-                file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| {
-                    err_unsup_format!(
-                        "`ftruncate64` is only supported on file-backed file descriptors"
-                    )
-                })?;
-            if *writable {
-                if let Ok(length) = length.try_into() {
-                    let result = file.set_len(length);
-                    this.try_unwrap_io_result(result.map(|_| 0i32))?
-                } else {
-                    let einval = this.eval_libc("EINVAL");
-                    this.set_last_error(einval)?;
-                    -1
-                }
+        let Some(file_descriptor) = this.machine.fds.get(fd) else {
+            return Ok(Scalar::from_i32(this.fd_not_found()?));
+        };
+
+        // FIXME: Support ftruncate64 for all FDs
+        let FileHandle { file, writable } =
+            file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| {
+                err_unsup_format!("`ftruncate64` is only supported on file-backed file descriptors")
+            })?;
+
+        if *writable {
+            if let Ok(length) = length.try_into() {
+                let result = file.set_len(length);
+                drop(file_descriptor);
+                let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
+                Ok(Scalar::from_i32(result))
             } else {
-                // The file is not writable
+                drop(file_descriptor);
                 let einval = this.eval_libc("EINVAL");
                 this.set_last_error(einval)?;
-                -1
+                Ok(Scalar::from_i32(-1))
             }
         } else {
-            this.fd_not_found()?
-        }))
+            drop(file_descriptor);
+            // The file is not writable
+            let einval = this.eval_libc("EINVAL");
+            this.set_last_error(einval)?;
+            Ok(Scalar::from_i32(-1))
+        }
     }
 
     fn fsync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
@@ -1190,6 +1190,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                 err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
             })?;
         let io_result = maybe_sync_file(file, *writable, File::sync_all);
+        drop(file_descriptor);
         this.try_unwrap_io_result(io_result)
     }
 
@@ -1214,6 +1215,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                 err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
             })?;
         let io_result = maybe_sync_file(file, *writable, File::sync_data);
+        drop(file_descriptor);
         this.try_unwrap_io_result(io_result)
     }
 
@@ -1263,6 +1265,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                 )
             })?;
         let io_result = maybe_sync_file(file, *writable, File::sync_data);
+        drop(file_descriptor);
         Ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
     }
 
@@ -1498,7 +1501,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
             match file {
                 Ok(f) => {
                     let fh = &mut this.machine.fds;
-                    let fd = fh.insert_fd(Box::new(FileHandle { file: f, writable: true }));
+                    let fd =
+                        fh.insert_fd(FileDescriptor::new(FileHandle { file: f, writable: true }));
                     return Ok(fd);
                 }
                 Err(e) =>
@@ -1563,21 +1567,21 @@ impl FileMetadata {
         ecx: &mut MiriInterpCx<'_, 'tcx>,
         fd: i32,
     ) -> InterpResult<'tcx, Option<FileMetadata>> {
-        let option = ecx.machine.fds.get(fd);
-        let file = match option {
-            Some(file_descriptor) =>
-                &file_descriptor
-                    .downcast_ref::<FileHandle>()
-                    .ok_or_else(|| {
-                        err_unsup_format!(
-                            "obtaining metadata is only supported on file-backed file descriptors"
-                        )
-                    })?
-                    .file,
-            None => return ecx.fd_not_found().map(|_: i32| None),
+        let Some(file_descriptor) = ecx.machine.fds.get(fd) else {
+            return ecx.fd_not_found().map(|_: i32| None);
         };
-        let metadata = file.metadata();
 
+        let file = &file_descriptor
+            .downcast_ref::<FileHandle>()
+            .ok_or_else(|| {
+                err_unsup_format!(
+                    "obtaining metadata is only supported on file-backed file descriptors"
+                )
+            })?
+            .file;
+
+        let metadata = file.metadata();
+        drop(file_descriptor);
         FileMetadata::from_meta(ecx, metadata)
     }
 
diff --git a/src/tools/miri/src/shims/unix/linux/epoll.rs b/src/tools/miri/src/shims/unix/linux/epoll.rs
index 5161d91ca36..48a0ba01976 100644
--- a/src/tools/miri/src/shims/unix/linux/epoll.rs
+++ b/src/tools/miri/src/shims/unix/linux/epoll.rs
@@ -5,6 +5,8 @@ use rustc_data_structures::fx::FxHashMap;
 use crate::shims::unix::*;
 use crate::*;
 
+use self::shims::unix::fd::FileDescriptor;
+
 /// An `Epoll` file descriptor connects file handles and epoll events
 #[derive(Clone, Debug, Default)]
 struct Epoll {
@@ -29,22 +31,16 @@ struct EpollEvent {
     data: Scalar<Provenance>,
 }
 
-impl FileDescriptor for Epoll {
+impl FileDescription for Epoll {
     fn name(&self) -> &'static str {
         "epoll"
     }
 
-    fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
-        // FIXME: this is probably wrong -- check if the `dup`ed descriptor truly uses an
-        // independent event set.
-        Ok(Box::new(self.clone()))
-    }
-
     fn close<'tcx>(
         self: Box<Self>,
         _communicate_allowed: bool,
-    ) -> InterpResult<'tcx, io::Result<i32>> {
-        Ok(Ok(0))
+    ) -> InterpResult<'tcx, io::Result<()>> {
+        Ok(Ok(()))
     }
 }
 
@@ -70,7 +66,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
             throw_unsup_format!("epoll_create1 flags {flags} are not implemented");
         }
 
-        let fd = this.machine.fds.insert_fd(Box::new(Epoll::default()));
+        let fd = this.machine.fds.insert_fd(FileDescriptor::new(Epoll::default()));
         Ok(Scalar::from_i32(fd))
     }
 
@@ -114,27 +110,25 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
             let data = this.read_scalar(&data)?;
             let event = EpollEvent { events, data };
 
-            if let Some(epfd) = this.machine.fds.get_mut(epfd) {
-                let epfd = epfd
-                    .downcast_mut::<Epoll>()
-                    .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?;
+            let Some(mut epfd) = this.machine.fds.get_mut(epfd) else {
+                return Ok(Scalar::from_i32(this.fd_not_found()?));
+            };
+            let epfd = epfd
+                .downcast_mut::<Epoll>()
+                .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?;
 
-                epfd.file_descriptors.insert(fd, event);
-                Ok(Scalar::from_i32(0))
-            } else {
-                Ok(Scalar::from_i32(this.fd_not_found()?))
-            }
+            epfd.file_descriptors.insert(fd, event);
+            Ok(Scalar::from_i32(0))
         } else if op == epoll_ctl_del {
-            if let Some(epfd) = this.machine.fds.get_mut(epfd) {
-                let epfd = epfd
-                    .downcast_mut::<Epoll>()
-                    .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?;
-
-                epfd.file_descriptors.remove(&fd);
-                Ok(Scalar::from_i32(0))
-            } else {
-                Ok(Scalar::from_i32(this.fd_not_found()?))
-            }
+            let Some(mut epfd) = this.machine.fds.get_mut(epfd) else {
+                return Ok(Scalar::from_i32(this.fd_not_found()?));
+            };
+            let epfd = epfd
+                .downcast_mut::<Epoll>()
+                .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?;
+
+            epfd.file_descriptors.remove(&fd);
+            Ok(Scalar::from_i32(0))
         } else {
             let einval = this.eval_libc("EINVAL");
             this.set_last_error(einval)?;
@@ -185,15 +179,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         let _maxevents = this.read_scalar(maxevents)?.to_i32()?;
         let _timeout = this.read_scalar(timeout)?.to_i32()?;
 
-        if let Some(epfd) = this.machine.fds.get_mut(epfd) {
-            let _epfd = epfd
-                .downcast_mut::<Epoll>()
-                .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))?;
+        let Some(mut epfd) = this.machine.fds.get_mut(epfd) else {
+            return Ok(Scalar::from_i32(this.fd_not_found()?));
+        };
+        let _epfd = epfd
+            .downcast_mut::<Epoll>()
+            .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))?;
 
-            // FIXME return number of events ready when scheme for marking events ready exists
-            throw_unsup_format!("returning ready events from epoll_wait is not yet implemented");
-        } else {
-            Ok(Scalar::from_i32(this.fd_not_found()?))
-        }
+        // FIXME return number of events ready when scheme for marking events ready exists
+        throw_unsup_format!("returning ready events from epoll_wait is not yet implemented");
     }
 }
diff --git a/src/tools/miri/src/shims/unix/linux/eventfd.rs b/src/tools/miri/src/shims/unix/linux/eventfd.rs
index 452527017fe..a865f2efff9 100644
--- a/src/tools/miri/src/shims/unix/linux/eventfd.rs
+++ b/src/tools/miri/src/shims/unix/linux/eventfd.rs
@@ -8,6 +8,8 @@ use rustc_target::abi::Endian;
 use crate::shims::unix::*;
 use crate::*;
 
+use self::shims::unix::fd::FileDescriptor;
+
 /// 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
@@ -22,21 +24,16 @@ struct Event {
     val: u64,
 }
 
-impl FileDescriptor for Event {
+impl FileDescription for Event {
     fn name(&self) -> &'static str {
         "event"
     }
 
-    fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
-        // FIXME: this is wrong, the new and old FD should refer to the same event object!
-        Ok(Box::new(Event { val: self.val }))
-    }
-
     fn close<'tcx>(
         self: Box<Self>,
         _communicate_allowed: bool,
-    ) -> InterpResult<'tcx, io::Result<i32>> {
-        Ok(Ok(0))
+    ) -> InterpResult<'tcx, io::Result<()>> {
+        Ok(Ok(()))
     }
 
     /// A write call adds the 8-byte integer value supplied in
@@ -115,7 +112,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
             throw_unsup_format!("eventfd: EFD_SEMAPHORE is unsupported");
         }
 
-        let fd = this.machine.fds.insert_fd(Box::new(Event { val: val.into() }));
+        let fd = this.machine.fds.insert_fd(FileDescriptor::new(Event { val: val.into() }));
         Ok(Scalar::from_i32(fd))
     }
 }
diff --git a/src/tools/miri/src/shims/unix/mod.rs b/src/tools/miri/src/shims/unix/mod.rs
index 144593aa2fc..bede2fbd389 100644
--- a/src/tools/miri/src/shims/unix/mod.rs
+++ b/src/tools/miri/src/shims/unix/mod.rs
@@ -13,7 +13,7 @@ mod linux;
 mod macos;
 
 pub use env::UnixEnvVars;
-pub use fd::{FdTable, FileDescriptor};
+pub use fd::{FdTable, FileDescription};
 pub use fs::DirTable;
 // All the Unix-specific extension traits
 pub use env::EvalContextExt as _;
diff --git a/src/tools/miri/src/shims/unix/socket.rs b/src/tools/miri/src/shims/unix/socket.rs
index 84ddd746fb5..11fd83f57e6 100644
--- a/src/tools/miri/src/shims/unix/socket.rs
+++ b/src/tools/miri/src/shims/unix/socket.rs
@@ -3,26 +3,24 @@ use std::io;
 use crate::shims::unix::*;
 use crate::*;
 
+use self::fd::FileDescriptor;
+
 /// Pair of connected sockets.
 ///
 /// We currently don't allow sending any data through this pair, so this can be just a dummy.
 #[derive(Debug)]
 struct SocketPair;
 
-impl FileDescriptor for SocketPair {
+impl FileDescription for SocketPair {
     fn name(&self) -> &'static str {
         "socketpair"
     }
 
-    fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
-        Ok(Box::new(SocketPair))
-    }
-
     fn close<'tcx>(
         self: Box<Self>,
         _communicate_allowed: bool,
-    ) -> InterpResult<'tcx, io::Result<i32>> {
-        Ok(Ok(0))
+    ) -> InterpResult<'tcx, io::Result<()>> {
+        Ok(Ok(()))
     }
 }
 
@@ -52,9 +50,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         // FIXME: fail on unsupported inputs
 
         let fds = &mut this.machine.fds;
-        let sv0 = fds.insert_fd(Box::new(SocketPair));
+        let sv0 = fds.insert_fd(FileDescriptor::new(SocketPair));
         let sv0 = Scalar::try_from_int(sv0, sv.layout.size).unwrap();
-        let sv1 = fds.insert_fd(Box::new(SocketPair));
+        let sv1 = fds.insert_fd(FileDescriptor::new(SocketPair));
         let sv1 = Scalar::try_from_int(sv1, sv.layout.size).unwrap();
 
         this.write_scalar(sv0, &sv)?;