about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/miri/src/shims/unix/fd.rs41
-rw-r--r--src/tools/miri/src/shims/unix/foreign_items.rs13
-rw-r--r--src/tools/miri/tests/pass-dep/libc/libc-fs.rs26
3 files changed, 72 insertions, 8 deletions
diff --git a/src/tools/miri/src/shims/unix/fd.rs b/src/tools/miri/src/shims/unix/fd.rs
index 599f78e712a..7f6a0978103 100644
--- a/src/tools/miri/src/shims/unix/fd.rs
+++ b/src/tools/miri/src/shims/unix/fd.rs
@@ -273,6 +273,32 @@ impl FdTable {
 
 impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
 pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
+    fn dup(&mut self, old_fd: i32) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let Some(dup_fd) = this.machine.fds.dup(old_fd) else {
+            return this.fd_not_found();
+        };
+        Ok(this.machine.fds.insert_fd_with_min_fd(dup_fd, 0))
+    }
+
+    fn dup2(&mut self, old_fd: i32, new_fd: i32) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+
+        let Some(dup_fd) = this.machine.fds.dup(old_fd) else {
+            return this.fd_not_found();
+        };
+        if new_fd != old_fd {
+            // Close new_fd if it is previously opened.
+            // 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(file_descriptor) = this.machine.fds.fds.insert(new_fd, dup_fd) {
+                // Ignore close error (not interpreter's) according to dup2() doc.
+                file_descriptor.close(this.machine.communicate())?.ok();
+            }
+        }
+        Ok(new_fd)
+    }
+
     fn fcntl(&mut self, args: &[OpTy<'tcx>]) -> InterpResult<'tcx, i32> {
         let this = self.eval_context_mut();
 
@@ -334,14 +360,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
         let fd = this.read_scalar(fd_op)?.to_i32()?;
 
-        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()?
-        }))
+        let Some(file_descriptor) = this.machine.fds.remove(fd) else {
+            return Ok(Scalar::from_i32(this.fd_not_found()?));
+        };
+        let result = file_descriptor.close(this.machine.communicate())?;
+        // return `0` if close is successful
+        let result = result.map(|()| 0i32);
+        Ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
     }
 
     /// Function used when a file descriptor does not exist. It returns `Ok(-1)`and sets
diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs
index 53ad40cfd2c..2421f9244f3 100644
--- a/src/tools/miri/src/shims/unix/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/foreign_items.rs
@@ -115,6 +115,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 let result = this.fcntl(args)?;
                 this.write_scalar(Scalar::from_i32(result), dest)?;
             }
+            "dup" => {
+                let [old_fd] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let old_fd = this.read_scalar(old_fd)?.to_i32()?;
+                let new_fd = this.dup(old_fd)?;
+                this.write_scalar(Scalar::from_i32(new_fd), dest)?;
+            }
+            "dup2" => {
+                let [old_fd, new_fd] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                let old_fd = this.read_scalar(old_fd)?.to_i32()?;
+                let new_fd = this.read_scalar(new_fd)?.to_i32()?;
+                let result = this.dup2(old_fd, new_fd)?;
+                this.write_scalar(Scalar::from_i32(result), dest)?;
+            }
 
             // File and file system access
             "open" | "open64" => {
diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs
index 80c9757e9c9..da685e5c6b7 100644
--- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs
+++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs
@@ -15,6 +15,7 @@ use std::path::PathBuf;
 mod utils;
 
 fn main() {
+    test_dup();
     test_dup_stdout_stderr();
     test_canonicalize_too_long();
     test_rename();
@@ -74,6 +75,31 @@ fn test_dup_stdout_stderr() {
     }
 }
 
+fn test_dup() {
+    let bytes = b"dup and dup2";
+    let path = utils::prepare_with_content("miri_test_libc_dup.txt", bytes);
+
+    let mut name = path.into_os_string();
+    name.push("\0");
+    let name_ptr = name.as_bytes().as_ptr().cast::<libc::c_char>();
+    unsafe {
+        let fd = libc::open(name_ptr, libc::O_RDONLY);
+        let mut first_buf = [0u8; 4];
+        libc::read(fd, first_buf.as_mut_ptr() as *mut libc::c_void, 4);
+        assert_eq!(&first_buf, b"dup ");
+
+        let new_fd = libc::dup(fd);
+        let mut second_buf = [0u8; 4];
+        libc::read(new_fd, second_buf.as_mut_ptr() as *mut libc::c_void, 4);
+        assert_eq!(&second_buf, b"and ");
+
+        let new_fd2 = libc::dup2(fd, 8);
+        let mut third_buf = [0u8; 4];
+        libc::read(new_fd2, third_buf.as_mut_ptr() as *mut libc::c_void, 4);
+        assert_eq!(&third_buf, b"dup2");
+    }
+}
+
 fn test_canonicalize_too_long() {
     // Make sure we get an error for long paths.
     let too_long = "x/".repeat(libc::PATH_MAX.try_into().unwrap());