about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/miri/src/shims/files.rs16
-rw-r--r--src/tools/miri/src/shims/unix/fd.rs21
-rw-r--r--src/tools/miri/src/shims/unix/linux_like/eventfd.rs5
-rw-r--r--src/tools/miri/src/shims/windows/fs.rs22
-rw-r--r--src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs7
-rw-r--r--src/tools/miri/tests/fail-dep/libc/libc-read-and-uninit-premature-eof.rs8
-rw-r--r--src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs3
-rw-r--r--src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.rs25
-rw-r--r--src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.stderr4
-rw-r--r--src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.rs29
-rw-r--r--src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.stderr4
-rw-r--r--src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs7
-rw-r--r--src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs44
-rw-r--r--src/tools/miri/tests/pass-dep/libc/libc-fs.rs49
-rw-r--r--src/tools/miri/tests/pass-dep/libc/libc-pipe.rs42
-rw-r--r--src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs81
-rw-r--r--src/tools/miri/tests/pass/shims/fs.rs32
-rw-r--r--src/tools/miri/tests/pass/shims/pipe.rs4
-rw-r--r--src/tools/miri/tests/utils/fs.rs2
-rw-r--r--src/tools/miri/tests/utils/libc.rs44
20 files changed, 326 insertions, 123 deletions
diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs
index 606d1ffbea6..0d4642c6ad0 100644
--- a/src/tools/miri/src/shims/files.rs
+++ b/src/tools/miri/src/shims/files.rs
@@ -1,7 +1,7 @@
 use std::any::Any;
 use std::collections::BTreeMap;
 use std::fs::{File, Metadata};
-use std::io::{IsTerminal, Seek, SeekFrom, Write};
+use std::io::{ErrorKind, IsTerminal, Seek, SeekFrom, Write};
 use std::marker::CoercePointee;
 use std::ops::Deref;
 use std::rc::{Rc, Weak};
@@ -167,6 +167,11 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
         throw_unsup_format!("cannot write to {}", self.name());
     }
 
+    /// Determines whether this FD non-deterministically has its reads and writes shortened.
+    fn nondet_short_accesses(&self) -> bool {
+        true
+    }
+
     /// Seeks to the given offset (which can be relative to the beginning, end, or current position).
     /// Returns the new position from the start of the stream.
     fn seek<'tcx>(
@@ -334,6 +339,15 @@ impl FileDescription for FileHandle {
     ) -> InterpResult<'tcx> {
         assert!(communicate_allowed, "isolation should have prevented even opening a file");
 
+        if !self.writable {
+            // Linux hosts return EBADF here which we can't translate via the platform-independent
+            // code since it does not map to any `io::ErrorKind` -- so if we don't do anything
+            // special, we'd throw an "unsupported error code" here. Windows returns something that
+            // gets translated to `PermissionDenied`. That seems like a good value so let's just use
+            // this everywhere, even if it means behavior on Unix targets does not match the real
+            // thing.
+            return finish.call(ecx, Err(ErrorKind::PermissionDenied.into()));
+        }
         let result = ecx.write_to_host(&self.file, len, ptr)?;
         finish.call(ecx, result)
     }
diff --git a/src/tools/miri/src/shims/unix/fd.rs b/src/tools/miri/src/shims/unix/fd.rs
index 71102d9f2f3..b420955501c 100644
--- a/src/tools/miri/src/shims/unix/fd.rs
+++ b/src/tools/miri/src/shims/unix/fd.rs
@@ -4,6 +4,7 @@
 use std::io;
 use std::io::ErrorKind;
 
+use rand::Rng;
 use rustc_abi::Size;
 
 use crate::helpers::check_min_vararg_count;
@@ -263,9 +264,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             return this.set_last_error_and_return(LibcError("EBADF"), dest);
         };
 
+        // Non-deterministically decide to further reduce the count, simulating a partial read (but
+        // never to 0, that has different behavior).
+        let count =
+            if fd.nondet_short_accesses() && count >= 2 && this.machine.rng.get_mut().random() {
+                count / 2
+            } else {
+                count
+            };
+
         trace!("read: FD mapped to {fd:?}");
         // 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
+        // because it was a target's `usize`. Also we are sure that it's smaller than
         // `usize::MAX` because it is bounded by the host's `isize`.
 
         let finish = {
@@ -328,6 +338,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             return this.set_last_error_and_return(LibcError("EBADF"), dest);
         };
 
+        // Non-deterministically decide to further reduce the count, simulating a partial write (but
+        // never to 0, that has different behavior).
+        let count =
+            if fd.nondet_short_accesses() && count >= 2 && this.machine.rng.get_mut().random() {
+                count / 2
+            } else {
+                count
+            };
+
         let finish = {
             let dest = dest.clone();
             callback!(
diff --git a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs
index ee7deb8d383..2d35ef064db 100644
--- a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs
+++ b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs
@@ -37,6 +37,11 @@ impl FileDescription for EventFd {
         "event"
     }
 
+    fn nondet_short_accesses(&self) -> bool {
+        // We always read and write exactly one `u64`.
+        false
+    }
+
     fn close<'tcx>(
         self,
         _communicate_allowed: bool,
diff --git a/src/tools/miri/src/shims/windows/fs.rs b/src/tools/miri/src/shims/windows/fs.rs
index 72e016c12e9..e4ec1b0130c 100644
--- a/src/tools/miri/src/shims/windows/fs.rs
+++ b/src/tools/miri/src/shims/windows/fs.rs
@@ -462,6 +462,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         };
         let io_status_info = this.project_field_named(&io_status_block, "Information")?;
 
+        // It seems like short writes are not a thing on Windows, so we don't truncate `count` here.
+        // FIXME: if we are on a Unix host, short host writes are still visible to the program!
+
         let finish = {
             let io_status = io_status.clone();
             let io_status_info = io_status_info.clone();
@@ -491,7 +494,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 }}
             )
         };
-
         desc.write(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
 
         // Return status is written to `dest` and `io_status_block` on callback completion.
@@ -556,6 +558,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         };
         let io_status_info = this.project_field_named(&io_status_block, "Information")?;
 
+        let fd = match handle {
+            Handle::File(fd) => fd,
+            _ => this.invalid_handle("NtWriteFile")?,
+        };
+
+        let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtReadFile")? };
+
+        // It seems like short reads are not a thing on Windows, so we don't truncate `count` here.
+        // FIXME: if we are on a Unix host, short host reads are still visible to the program!
+
         let finish = {
             let io_status = io_status.clone();
             let io_status_info = io_status_info.clone();
@@ -585,14 +597,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 }}
             )
         };
-
-        let fd = match handle {
-            Handle::File(fd) => fd,
-            _ => this.invalid_handle("NtWriteFile")?,
-        };
-
-        let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtReadFile")? };
-
         desc.read(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
 
         // See NtWriteFile for commentary on this
diff --git a/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs b/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs
index 314ce90cfb5..f6ec5be61bb 100644
--- a/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs
+++ b/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs
@@ -10,6 +10,9 @@ use std::convert::TryInto;
 use std::thread;
 use std::thread::spawn;
 
+#[path = "../../utils/libc.rs"]
+mod libc_utils;
+
 #[track_caller]
 fn check_epoll_wait<const N: usize>(epfd: i32, expected_notifications: &[(u32, u64)]) {
     let epoll_event = libc::epoll_event { events: 0, u64: 0 };
@@ -69,12 +72,12 @@ fn main() {
         unsafe { VAL_ONE = 41 };
 
         let data = "abcde".as_bytes().as_ptr();
-        let res = unsafe { libc::write(fds_a[0], data as *const libc::c_void, 5) };
+        let res = unsafe { libc_utils::write_all(fds_a[0], data as *const libc::c_void, 5) };
         assert_eq!(res, 5);
 
         unsafe { VAL_TWO = 51 };
 
-        let res = unsafe { libc::write(fds_b[0], data as *const libc::c_void, 5) };
+        let res = unsafe { libc_utils::write_all(fds_b[0], data as *const libc::c_void, 5) };
         assert_eq!(res, 5);
     });
     thread::yield_now();
diff --git a/src/tools/miri/tests/fail-dep/libc/libc-read-and-uninit-premature-eof.rs b/src/tools/miri/tests/fail-dep/libc/libc-read-and-uninit-premature-eof.rs
index 1dc334486c3..e2fd6463a11 100644
--- a/src/tools/miri/tests/fail-dep/libc/libc-read-and-uninit-premature-eof.rs
+++ b/src/tools/miri/tests/fail-dep/libc/libc-read-and-uninit-premature-eof.rs
@@ -10,6 +10,9 @@ use std::mem::MaybeUninit;
 #[path = "../../utils/mod.rs"]
 mod utils;
 
+#[path = "../../utils/libc.rs"]
+mod libc_utils;
+
 fn main() {
     let path =
         utils::prepare_with_content("fail-libc-read-and-uninit-premature-eof.txt", &[1u8, 2, 3]);
@@ -18,8 +21,9 @@ fn main() {
         let fd = libc::open(cpath.as_ptr(), libc::O_RDONLY);
         assert_ne!(fd, -1);
         let mut buf: MaybeUninit<[u8; 4]> = std::mem::MaybeUninit::uninit();
-        // Read 4 bytes from a 3-byte file.
-        assert_eq!(libc::read(fd, buf.as_mut_ptr().cast::<std::ffi::c_void>(), 4), 3);
+        // Read as much as we can from a 3-byte file.
+        let res = libc_utils::read_all(fd, buf.as_mut_ptr().cast::<std::ffi::c_void>(), 4);
+        assert!(res == 3);
         buf.assume_init(); //~ERROR: encountered uninitialized memory, but expected an integer
         assert_eq!(libc::close(fd), 0);
     }
diff --git a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs
index f6f2e2b9312..054cb812d9e 100644
--- a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs
+++ b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs
@@ -75,9 +75,10 @@ fn main() {
     });
 
     let thread3 = spawn(move || {
+        // Just a single write, so we only wake up one of them.
         let data = "abcde".as_bytes().as_ptr();
         let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
-        assert_eq!(res, 5);
+        assert!(res > 0 && res <= 5);
     });
 
     thread1.join().unwrap();
diff --git a/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.rs b/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.rs
index b3839859500..0fecfb8f663 100644
--- a/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.rs
+++ b/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.rs
@@ -4,6 +4,7 @@
 // test_race depends on a deterministic schedule.
 //@compile-flags: -Zmiri-deterministic-concurrency
 //@error-in-other-file: deadlock
+//@require-annotations-for-level: error
 
 use std::thread;
 
@@ -22,24 +23,26 @@ fn main() {
     assert_eq!(res, 0);
     let thread1 = thread::spawn(move || {
         // Let this thread block on read.
-        let mut buf: [u8; 3] = [0; 3];
+        let mut buf: [u8; 1] = [0; 1];
         let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
-        assert_eq!(res, 3);
-        assert_eq!(&buf, "abc".as_bytes());
+        assert_eq!(res, buf.len().cast_signed());
+        assert_eq!(&buf, "a".as_bytes());
     });
     let thread2 = thread::spawn(move || {
         // Let this thread block on read.
-        let mut buf: [u8; 3] = [0; 3];
-        let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
-        //~^ERROR: deadlocked
-        assert_eq!(res, 3);
-        assert_eq!(&buf, "abc".as_bytes());
+        let mut buf: [u8; 1] = [0; 1];
+        let res = unsafe {
+            libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
+            //~^ERROR: deadlock
+        };
+        assert_eq!(res, buf.len().cast_signed());
+        assert_eq!(&buf, "a".as_bytes());
     });
     let thread3 = thread::spawn(move || {
         // Unblock thread1 by writing something.
-        let data = "abc".as_bytes().as_ptr();
-        let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
-        assert_eq!(res, 3);
+        let data = "a".as_bytes();
+        let res = unsafe { libc::write(fds[0], data.as_ptr() as *const libc::c_void, data.len()) };
+        assert_eq!(res, data.len().cast_signed());
     });
     thread1.join().unwrap();
     thread2.join().unwrap();
diff --git a/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.stderr b/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.stderr
index 9f19a60e6ae..99d242ec7da 100644
--- a/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.stderr
+++ b/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.stderr
@@ -23,8 +23,8 @@ error: the evaluated program deadlocked
 error: the evaluated program deadlocked
   --> tests/fail-dep/libc/socketpair_block_read_twice.rs:LL:CC
    |
-LL |         let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
-   |                                                                                                 ^ this thread got stuck here
+LL |             libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
+   |                                                                                  ^ this thread got stuck here
    |
    = note: BACKTRACE on thread `unnamed-ID`:
    = note: inside closure at tests/fail-dep/libc/socketpair_block_read_twice.rs:LL:CC
diff --git a/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.rs b/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.rs
index 7d84d87ebbb..048938c091e 100644
--- a/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.rs
+++ b/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.rs
@@ -4,16 +4,20 @@
 // test_race depends on a deterministic schedule.
 //@compile-flags: -Zmiri-deterministic-concurrency
 //@error-in-other-file: deadlock
+//@require-annotations-for-level: error
 
 use std::thread;
 
+#[path = "../../utils/libc.rs"]
+mod libc_utils;
+
 // Test the behaviour of a thread being blocked on write, get unblocked, then blocked again.
 
 // The expected execution is
 // 1. Thread 1 blocks.
 // 2. Thread 2 blocks.
 // 3. Thread 3 unblocks both thread 1 and thread 2.
-// 4. Thread 1 reads.
+// 4. Thread 1 writes.
 // 5. Thread 2's `write` can never complete -> deadlocked.
 fn main() {
     let mut fds = [-1, -1];
@@ -21,27 +25,28 @@ fn main() {
     assert_eq!(res, 0);
     let arr1: [u8; 212992] = [1; 212992];
     // Exhaust the space in the buffer so the subsequent write will block.
-    let res = unsafe { libc::write(fds[0], arr1.as_ptr() as *const libc::c_void, 212992) };
+    let res =
+        unsafe { libc_utils::write_all(fds[0], arr1.as_ptr() as *const libc::c_void, 212992) };
     assert_eq!(res, 212992);
     let thread1 = thread::spawn(move || {
-        let data = "abc".as_bytes().as_ptr();
+        let data = "a".as_bytes();
         // The write below will be blocked because the buffer is already full.
-        let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
-        assert_eq!(res, 3);
+        let res = unsafe { libc::write(fds[0], data.as_ptr() as *const libc::c_void, data.len()) };
+        assert_eq!(res, data.len().cast_signed());
     });
     let thread2 = thread::spawn(move || {
-        let data = "abc".as_bytes().as_ptr();
+        let data = "a".as_bytes();
         // The write below will be blocked because the buffer is already full.
-        let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
-        //~^ERROR: deadlocked
-        assert_eq!(res, 3);
+        let res = unsafe { libc::write(fds[0], data.as_ptr() as *const libc::c_void, data.len()) };
+        //~^ERROR: deadlock
+        assert_eq!(res, data.len().cast_signed());
     });
     let thread3 = thread::spawn(move || {
         // Unblock thread1 by freeing up some space.
-        let mut buf: [u8; 3] = [0; 3];
+        let mut buf: [u8; 1] = [0; 1];
         let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
-        assert_eq!(res, 3);
-        assert_eq!(buf, [1, 1, 1]);
+        assert_eq!(res, buf.len().cast_signed());
+        assert_eq!(buf, [1]);
     });
     thread1.join().unwrap();
     thread2.join().unwrap();
diff --git a/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.stderr b/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.stderr
index b29cd70f35e..f766500d331 100644
--- a/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.stderr
+++ b/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.stderr
@@ -23,8 +23,8 @@ error: the evaluated program deadlocked
 error: the evaluated program deadlocked
   --> tests/fail-dep/libc/socketpair_block_write_twice.rs:LL:CC
    |
-LL |         let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
-   |                                                                              ^ this thread got stuck here
+LL |         let res = unsafe { libc::write(fds[0], data.as_ptr() as *const libc::c_void, data.len()) };
+   |                                                                                                ^ this thread got stuck here
    |
    = note: BACKTRACE on thread `unnamed-ID`:
    = note: inside closure at tests/fail-dep/libc/socketpair_block_write_twice.rs:LL:CC
diff --git a/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs
index 54ebfa9d198..c97206487a1 100644
--- a/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs
+++ b/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs
@@ -6,6 +6,9 @@ use std::convert::TryInto;
 use std::thread;
 use std::thread::spawn;
 
+#[path = "../../utils/libc.rs"]
+mod libc_utils;
+
 // This is a set of testcases for blocking epoll.
 
 fn main() {
@@ -97,7 +100,7 @@ fn test_epoll_block_then_unblock() {
     let thread1 = spawn(move || {
         thread::yield_now();
         let data = "abcde".as_bytes().as_ptr();
-        let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
+        let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
         assert_eq!(res, 5);
     });
     check_epoll_wait::<1>(epfd, &[(expected_event, expected_value)], 10);
@@ -130,7 +133,7 @@ fn test_notification_after_timeout() {
 
     // Trigger epoll notification after timeout.
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
 
     // Check the result of the notification.
diff --git a/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs
index dc3ab2828fa..7130790b86d 100644
--- a/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs
+++ b/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs
@@ -2,6 +2,9 @@
 
 use std::convert::TryInto;
 
+#[path = "../../utils/libc.rs"]
+mod libc_utils;
+
 fn main() {
     test_epoll_socketpair();
     test_epoll_socketpair_both_sides();
@@ -64,7 +67,7 @@ fn test_epoll_socketpair() {
 
     // Write to fd[0]
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
 
     // Register fd[1] with EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP
@@ -85,7 +88,7 @@ fn test_epoll_socketpair() {
 
     // Write some more to fd[0].
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
 
     // This did not change the readiness of fd[1]. And yet, we're seeing the event reported
@@ -153,7 +156,7 @@ fn test_epoll_ctl_del() {
 
     // Write to fd[0]
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
 
     // Register fd[1] with EPOLLIN|EPOLLOUT|EPOLLET
@@ -182,7 +185,7 @@ fn test_two_epoll_instance() {
 
     // Write to the socketpair.
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
 
     // Register one side of the socketpair with EPOLLIN | EPOLLOUT | EPOLLET.
@@ -224,7 +227,7 @@ fn test_two_same_fd_in_same_epoll_instance() {
 
     // Write to the socketpair.
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
 
     //Two notification should be received.
@@ -243,7 +246,7 @@ fn test_epoll_eventfd() {
 
     // Write to the eventfd instance.
     let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes();
-    let res = unsafe { libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
+    let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
     assert_eq!(res, 8);
 
     // Create an epoll instance.
@@ -282,7 +285,7 @@ fn test_epoll_socketpair_both_sides() {
 
     // Write to fds[1].
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
 
     //Two notification should be received.
@@ -297,7 +300,8 @@ fn test_epoll_socketpair_both_sides() {
 
     // Read from fds[0].
     let mut buf: [u8; 5] = [0; 5];
-    let res = unsafe { libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
+    let res =
+        unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
     assert_eq!(res, 5);
     assert_eq!(buf, "abcde".as_bytes());
 
@@ -325,7 +329,7 @@ fn test_closed_fd() {
 
     // Write to the eventfd instance.
     let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes();
-    let res = unsafe { libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
+    let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
     assert_eq!(res, 8);
 
     // Close the eventfd.
@@ -371,7 +375,8 @@ fn test_not_fully_closed_fd() {
 
     // Write to the eventfd instance to produce notification.
     let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes();
-    let res = unsafe { libc::write(newfd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
+    let res =
+        unsafe { libc_utils::write_all(newfd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
     assert_eq!(res, 8);
 
     // Close the dupped fd.
@@ -391,7 +396,7 @@ fn test_event_overwrite() {
 
     // Write to the eventfd instance.
     let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes();
-    let res = unsafe { libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
+    let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
     assert_eq!(res, 8);
 
     // Create an epoll instance.
@@ -445,7 +450,7 @@ fn test_socketpair_read() {
 
     // Write 5 bytes to fds[1].
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
 
     //Two notification should be received.
@@ -460,7 +465,8 @@ fn test_socketpair_read() {
 
     // Read 3 bytes from fds[0].
     let mut buf: [u8; 3] = [0; 3];
-    let res = unsafe { libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
+    let res =
+        unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
     assert_eq!(res, 3);
     assert_eq!(buf, "abc".as_bytes());
 
@@ -478,7 +484,8 @@ fn test_socketpair_read() {
 
     // Read until the buffer is empty.
     let mut buf: [u8; 2] = [0; 2];
-    let res = unsafe { libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
+    let res =
+        unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
     assert_eq!(res, 2);
     assert_eq!(buf, "de".as_bytes());
 
@@ -510,8 +517,9 @@ fn test_no_notification_for_unregister_flag() {
 
     // Write to fd[1].
     let data = "abcde".as_bytes().as_ptr();
-    let res: i32 =
-        unsafe { libc::write(fds[1], data as *const libc::c_void, 5).try_into().unwrap() };
+    let res: i32 = unsafe {
+        libc_utils::write_all(fds[1], data as *const libc::c_void, 5).try_into().unwrap()
+    };
     assert_eq!(res, 5);
 
     // Check result from epoll_wait. Since we didn't register EPOLLIN flag, the notification won't
@@ -546,7 +554,7 @@ fn test_socketpair_epollerr() {
 
     // Write to fd[0]
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
 
     // Close fds[1].
@@ -717,6 +725,6 @@ fn test_issue_3858() {
 
     // Write to the eventfd instance.
     let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes();
-    let res = unsafe { libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
+    let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
     assert_eq!(res, 8);
 }
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 0ff48c389e8..86cf2a041f0 100644
--- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs
+++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs
@@ -14,6 +14,9 @@ use std::path::PathBuf;
 #[path = "../../utils/mod.rs"]
 mod utils;
 
+#[path = "../../utils/libc.rs"]
+mod libc_utils;
+
 fn main() {
     test_dup();
     test_dup_stdout_stderr();
@@ -74,8 +77,8 @@ fn test_dup_stdout_stderr() {
     unsafe {
         let new_stdout = libc::fcntl(1, libc::F_DUPFD, 0);
         let new_stderr = libc::fcntl(2, libc::F_DUPFD, 0);
-        libc::write(new_stdout, bytes.as_ptr() as *const libc::c_void, bytes.len());
-        libc::write(new_stderr, bytes.as_ptr() as *const libc::c_void, bytes.len());
+        libc_utils::write_all(new_stdout, bytes.as_ptr() as *const libc::c_void, bytes.len());
+        libc_utils::write_all(new_stderr, bytes.as_ptr() as *const libc::c_void, bytes.len());
     }
 }
 
@@ -92,16 +95,24 @@ fn test_dup() {
         let new_fd2 = libc::dup2(fd, 8);
 
         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 first_len = libc::read(fd, first_buf.as_mut_ptr() as *mut libc::c_void, 4);
+        assert!(first_len > 0);
+        let first_len = first_len as usize;
+        assert_eq!(first_buf[..first_len], bytes[..first_len]);
+        let remaining_bytes = &bytes[first_len..];
 
         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 second_len = libc::read(new_fd, second_buf.as_mut_ptr() as *mut libc::c_void, 4);
+        assert!(second_len > 0);
+        let second_len = second_len as usize;
+        assert_eq!(second_buf[..second_len], remaining_bytes[..second_len]);
+        let remaining_bytes = &remaining_bytes[second_len..];
 
         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");
+        let third_len = libc::read(new_fd2, third_buf.as_mut_ptr() as *mut libc::c_void, 4);
+        assert!(third_len > 0);
+        let third_len = third_len as usize;
+        assert_eq!(third_buf[..third_len], remaining_bytes[..third_len]);
     }
 }
 
@@ -145,7 +156,7 @@ fn test_ftruncate<T: From<i32>>(
     let bytes = b"hello";
     let path = utils::prepare("miri_test_libc_fs_ftruncate.txt");
     let mut file = File::create(&path).unwrap();
-    file.write(bytes).unwrap();
+    file.write_all(bytes).unwrap();
     file.sync_all().unwrap();
     assert_eq!(file.metadata().unwrap().len(), 5);
 
@@ -402,10 +413,10 @@ fn test_read_and_uninit() {
         unsafe {
             let fd = libc::open(cpath.as_ptr(), libc::O_RDONLY);
             assert_ne!(fd, -1);
-            let mut buf: MaybeUninit<[u8; 2]> = std::mem::MaybeUninit::uninit();
-            assert_eq!(libc::read(fd, buf.as_mut_ptr().cast::<std::ffi::c_void>(), 2), 2);
+            let mut buf: MaybeUninit<u8> = std::mem::MaybeUninit::uninit();
+            assert_eq!(libc::read(fd, buf.as_mut_ptr().cast::<std::ffi::c_void>(), 1), 1);
             let buf = buf.assume_init();
-            assert_eq!(buf, [1, 2]);
+            assert_eq!(buf, 1);
             assert_eq!(libc::close(fd), 0);
         }
         remove_file(&path).unwrap();
@@ -413,14 +424,22 @@ fn test_read_and_uninit() {
     {
         // We test that if we requested to read 4 bytes, but actually read 3 bytes, then
         // 3 bytes (not 4) will be overwritten, and remaining byte will be left as-is.
-        let path = utils::prepare_with_content("pass-libc-read-and-uninit-2.txt", &[1u8, 2, 3]);
+        let data = [1u8, 2, 3];
+        let path = utils::prepare_with_content("pass-libc-read-and-uninit-2.txt", &data);
         let cpath = CString::new(path.clone().into_os_string().into_encoded_bytes()).unwrap();
         unsafe {
             let fd = libc::open(cpath.as_ptr(), libc::O_RDONLY);
             assert_ne!(fd, -1);
             let mut buf = [42u8; 5];
-            assert_eq!(libc::read(fd, buf.as_mut_ptr().cast::<std::ffi::c_void>(), 4), 3);
-            assert_eq!(buf, [1, 2, 3, 42, 42]);
+            let res = libc::read(fd, buf.as_mut_ptr().cast::<std::ffi::c_void>(), 4);
+            assert!(res > 0 && res < 4);
+            for i in 0..buf.len() {
+                assert_eq!(
+                    buf[i],
+                    if i < res as usize { data[i] } else { 42 },
+                    "wrong result at pos {i}"
+                );
+            }
             assert_eq!(libc::close(fd), 0);
         }
         remove_file(&path).unwrap();
diff --git a/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs b/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs
index bc755af864c..ffbcf633b98 100644
--- a/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs
+++ b/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs
@@ -2,6 +2,10 @@
 // test_race depends on a deterministic schedule.
 //@compile-flags: -Zmiri-deterministic-concurrency
 use std::thread;
+
+#[path = "../../utils/libc.rs"]
+mod libc_utils;
+
 fn main() {
     test_pipe();
     test_pipe_threaded();
@@ -26,21 +30,29 @@ fn test_pipe() {
 
     // Read size == data available in buffer.
     let data = "12345".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
     let mut buf3: [u8; 5] = [0; 5];
-    let res = unsafe { libc::read(fds[0], buf3.as_mut_ptr().cast(), buf3.len() as libc::size_t) };
+    let res = unsafe {
+        libc_utils::read_all(fds[0], buf3.as_mut_ptr().cast(), buf3.len() as libc::size_t)
+    };
     assert_eq!(res, 5);
     assert_eq!(buf3, "12345".as_bytes());
 
     // Read size > data available in buffer.
-    let data = "123".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 3) };
+    let data = "123".as_bytes();
+    let res = unsafe { libc_utils::write_all(fds[1], data.as_ptr() as *const libc::c_void, 3) };
     assert_eq!(res, 3);
     let mut buf4: [u8; 5] = [0; 5];
     let res = unsafe { libc::read(fds[0], buf4.as_mut_ptr().cast(), buf4.len() as libc::size_t) };
-    assert_eq!(res, 3);
-    assert_eq!(&buf4[0..3], "123".as_bytes());
+    assert!(res > 0 && res <= 3);
+    let res = res as usize;
+    assert_eq!(buf4[..res], data[..res]);
+    if res < 3 {
+        // Drain the rest from the read end.
+        let res = unsafe { libc_utils::read_all(fds[0], buf4[res..].as_mut_ptr().cast(), 3 - res) };
+        assert!(res > 0);
+    }
 }
 
 fn test_pipe_threaded() {
@@ -51,7 +63,7 @@ fn test_pipe_threaded() {
     let thread1 = thread::spawn(move || {
         let mut buf: [u8; 5] = [0; 5];
         let res: i64 = unsafe {
-            libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
+            libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
                 .try_into()
                 .unwrap()
         };
@@ -60,7 +72,7 @@ fn test_pipe_threaded() {
     });
     thread::yield_now();
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
     thread1.join().unwrap();
 
@@ -68,11 +80,12 @@ fn test_pipe_threaded() {
     let thread2 = thread::spawn(move || {
         thread::yield_now();
         let data = "12345".as_bytes().as_ptr();
-        let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
+        let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
         assert_eq!(res, 5);
     });
     let mut buf: [u8; 5] = [0; 5];
-    let res = unsafe { libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
+    let res =
+        unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
     assert_eq!(res, 5);
     assert_eq!(buf, "12345".as_bytes());
     thread2.join().unwrap();
@@ -90,7 +103,7 @@ fn test_race() {
         // write() from the main thread will occur before the read() here
         // because preemption is disabled and the main thread yields after write().
         let res: i32 = unsafe {
-            libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
+            libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
                 .try_into()
                 .unwrap()
         };
@@ -101,7 +114,7 @@ fn test_race() {
     });
     unsafe { VAL = 1 };
     let data = "a".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 1) };
+    let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 1) };
     assert_eq!(res, 1);
     thread::yield_now();
     thread1.join().unwrap();
@@ -186,11 +199,12 @@ fn test_pipe_fcntl_threaded() {
         // the socket is now "non-blocking", the shim needs to deal correctly
         // with threads that were blocked before the socket was made non-blocking.
         let data = "abcde".as_bytes().as_ptr();
-        let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
+        let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
         assert_eq!(res, 5);
     });
     // The `read` below will block.
-    let res = unsafe { libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
+    let res =
+        unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
     thread1.join().unwrap();
     assert_eq!(res, 5);
 }
diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs b/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs
index c36f6b11224..9c211ffbdbe 100644
--- a/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs
+++ b/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs
@@ -6,6 +6,10 @@
 #![allow(static_mut_refs)]
 
 use std::thread;
+
+#[path = "../../utils/libc.rs"]
+mod libc_utils;
+
 fn main() {
     test_socketpair();
     test_socketpair_threaded();
@@ -22,54 +26,71 @@ fn test_socketpair() {
 
     // Read size == data available in buffer.
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
     let mut buf: [u8; 5] = [0; 5];
-    let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
+    let res =
+        unsafe { libc_utils::read_all(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
     assert_eq!(res, 5);
     assert_eq!(buf, "abcde".as_bytes());
 
     // Read size > data available in buffer.
-    let data = "abc".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
+    let data = "abc".as_bytes();
+    let res = unsafe { libc_utils::write_all(fds[0], data.as_ptr() as *const libc::c_void, 3) };
     assert_eq!(res, 3);
     let mut buf2: [u8; 5] = [0; 5];
     let res = unsafe { libc::read(fds[1], buf2.as_mut_ptr().cast(), buf2.len() as libc::size_t) };
-    assert_eq!(res, 3);
-    assert_eq!(&buf2[0..3], "abc".as_bytes());
+    assert!(res > 0 && res <= 3);
+    let res = res as usize;
+    assert_eq!(buf2[..res], data[..res]);
+    if res < 3 {
+        // Drain the rest from the read end.
+        let res = unsafe { libc_utils::read_all(fds[1], buf2[res..].as_mut_ptr().cast(), 3 - res) };
+        assert!(res > 0);
+    }
 
     // Test read and write from another direction.
     // Read size == data available in buffer.
     let data = "12345".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
     let mut buf3: [u8; 5] = [0; 5];
-    let res = unsafe { libc::read(fds[0], buf3.as_mut_ptr().cast(), buf3.len() as libc::size_t) };
+    let res = unsafe {
+        libc_utils::read_all(fds[0], buf3.as_mut_ptr().cast(), buf3.len() as libc::size_t)
+    };
     assert_eq!(res, 5);
     assert_eq!(buf3, "12345".as_bytes());
 
     // Read size > data available in buffer.
-    let data = "123".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 3) };
+    let data = "123".as_bytes();
+    let res = unsafe { libc_utils::write_all(fds[1], data.as_ptr() as *const libc::c_void, 3) };
     assert_eq!(res, 3);
     let mut buf4: [u8; 5] = [0; 5];
     let res = unsafe { libc::read(fds[0], buf4.as_mut_ptr().cast(), buf4.len() as libc::size_t) };
-    assert_eq!(res, 3);
-    assert_eq!(&buf4[0..3], "123".as_bytes());
+    assert!(res > 0 && res <= 3);
+    let res = res as usize;
+    assert_eq!(buf4[..res], data[..res]);
+    if res < 3 {
+        // Drain the rest from the read end.
+        let res = unsafe { libc_utils::read_all(fds[0], buf4[res..].as_mut_ptr().cast(), 3 - res) };
+        assert!(res > 0);
+    }
 
     // Test when happens when we close one end, with some data in the buffer.
-    let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
+    let res = unsafe { libc_utils::write_all(fds[0], data.as_ptr() as *const libc::c_void, 3) };
     assert_eq!(res, 3);
     unsafe { libc::close(fds[0]) };
     // Reading the other end should return that data, then EOF.
     let mut buf: [u8; 5] = [0; 5];
-    let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
+    let res =
+        unsafe { libc_utils::read_all(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
     assert_eq!(res, 3);
     assert_eq!(&buf[0..3], "123".as_bytes());
-    let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
+    let res =
+        unsafe { libc_utils::read_all(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
     assert_eq!(res, 0); // 0-sized read: EOF.
     // Writing the other end should emit EPIPE.
-    let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 1) };
+    let res = unsafe { libc_utils::write_all(fds[1], data.as_ptr() as *const libc::c_void, 1) };
     assert_eq!(res, -1);
     assert_eq!(std::io::Error::last_os_error().raw_os_error(), Some(libc::EPIPE));
 }
@@ -82,7 +103,7 @@ fn test_socketpair_threaded() {
     let thread1 = thread::spawn(move || {
         let mut buf: [u8; 5] = [0; 5];
         let res: i64 = unsafe {
-            libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
+            libc_utils::read_all(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
                 .try_into()
                 .unwrap()
         };
@@ -91,7 +112,7 @@ fn test_socketpair_threaded() {
     });
     thread::yield_now();
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
     thread1.join().unwrap();
 
@@ -99,11 +120,12 @@ fn test_socketpair_threaded() {
     let thread2 = thread::spawn(move || {
         thread::yield_now();
         let data = "12345".as_bytes().as_ptr();
-        let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
+        let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
         assert_eq!(res, 5);
     });
     let mut buf: [u8; 5] = [0; 5];
-    let res = unsafe { libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
+    let res =
+        unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
     assert_eq!(res, 5);
     assert_eq!(buf, "12345".as_bytes());
     thread2.join().unwrap();
@@ -119,7 +141,7 @@ fn test_race() {
         // write() from the main thread will occur before the read() here
         // because preemption is disabled and the main thread yields after write().
         let res: i32 = unsafe {
-            libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
+            libc_utils::read_all(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
                 .try_into()
                 .unwrap()
         };
@@ -130,7 +152,7 @@ fn test_race() {
     });
     unsafe { VAL = 1 };
     let data = "a".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 1) };
+    let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 1) };
     assert_eq!(res, 1);
     thread::yield_now();
     thread1.join().unwrap();
@@ -144,14 +166,16 @@ fn test_blocking_read() {
     let thread1 = thread::spawn(move || {
         // Let this thread block on read.
         let mut buf: [u8; 3] = [0; 3];
-        let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
+        let res = unsafe {
+            libc_utils::read_all(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
+        };
         assert_eq!(res, 3);
         assert_eq!(&buf, "abc".as_bytes());
     });
     let thread2 = thread::spawn(move || {
         // Unblock thread1 by doing writing something.
         let data = "abc".as_bytes().as_ptr();
-        let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
+        let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 3) };
         assert_eq!(res, 3);
     });
     thread1.join().unwrap();
@@ -165,18 +189,21 @@ fn test_blocking_write() {
     assert_eq!(res, 0);
     let arr1: [u8; 212992] = [1; 212992];
     // Exhaust the space in the buffer so the subsequent write will block.
-    let res = unsafe { libc::write(fds[0], arr1.as_ptr() as *const libc::c_void, 212992) };
+    let res =
+        unsafe { libc_utils::write_all(fds[0], arr1.as_ptr() as *const libc::c_void, 212992) };
     assert_eq!(res, 212992);
     let thread1 = thread::spawn(move || {
         let data = "abc".as_bytes().as_ptr();
         // The write below will be blocked because the buffer is already full.
-        let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
+        let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 3) };
         assert_eq!(res, 3);
     });
     let thread2 = thread::spawn(move || {
         // Unblock thread1 by freeing up some space.
         let mut buf: [u8; 3] = [0; 3];
-        let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
+        let res = unsafe {
+            libc_utils::read_all(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
+        };
         assert_eq!(res, 3);
         assert_eq!(buf, [1, 1, 1]);
     });
diff --git a/src/tools/miri/tests/pass/shims/fs.rs b/src/tools/miri/tests/pass/shims/fs.rs
index 9d5725773e6..e7f11c54704 100644
--- a/src/tools/miri/tests/pass/shims/fs.rs
+++ b/src/tools/miri/tests/pass/shims/fs.rs
@@ -17,6 +17,10 @@ mod utils;
 fn main() {
     test_path_conversion();
     test_file();
+    // Partial reads/writes are apparently not a thing on Windows.
+    if cfg!(not(windows)) {
+        test_file_partial_reads_writes();
+    }
     test_file_create_new();
     test_metadata();
     test_seek();
@@ -53,7 +57,7 @@ fn test_file() {
     file.write(&mut []).unwrap();
     assert_eq!(file.metadata().unwrap().len(), 0);
 
-    file.write(bytes).unwrap();
+    file.write_all(bytes).unwrap();
     assert_eq!(file.metadata().unwrap().len(), bytes.len() as u64);
     // Test opening, reading and closing a file.
     let mut file = File::open(&path).unwrap();
@@ -66,10 +70,36 @@ fn test_file() {
 
     assert!(!file.is_terminal());
 
+    // Writing to a file opened for reading should error (and not stop interpretation). std does not
+    // categorize the error so we don't check for details.
+    file.write(&[]).unwrap_err();
+
     // Removing file should succeed.
     remove_file(&path).unwrap();
 }
 
+fn test_file_partial_reads_writes() {
+    let path = utils::prepare_with_content("miri_test_fs_file.txt", b"abcdefg");
+
+    // Ensure we sometimes do incomplete writes.
+    let got_short_write = (0..16).any(|_| {
+        let _ = remove_file(&path); // FIXME(win, issue #4483): errors if the file already exists
+        let mut file = File::create(&path).unwrap();
+        file.write(&[0; 4]).unwrap() != 4
+    });
+    assert!(got_short_write);
+    // Ensure we sometimes do incomplete reads.
+    let got_short_read = (0..16).any(|_| {
+        let mut file = File::open(&path).unwrap();
+        let mut buf = [0; 4];
+        file.read(&mut buf).unwrap() != 4
+    });
+    assert!(got_short_read);
+
+    // Clean up
+    remove_file(&path).unwrap();
+}
+
 fn test_file_clone() {
     let bytes = b"Hello, World!\n";
     let path = utils::prepare_with_content("miri_test_fs_file_clone.txt", bytes);
diff --git a/src/tools/miri/tests/pass/shims/pipe.rs b/src/tools/miri/tests/pass/shims/pipe.rs
index c47feb8774a..4915e54c533 100644
--- a/src/tools/miri/tests/pass/shims/pipe.rs
+++ b/src/tools/miri/tests/pass/shims/pipe.rs
@@ -4,8 +4,8 @@ use std::io::{Read, Write, pipe};
 
 fn main() {
     let (mut ping_rx, mut ping_tx) = pipe().unwrap();
-    ping_tx.write(b"hello").unwrap();
+    ping_tx.write_all(b"hello").unwrap();
     let mut buf: [u8; 5] = [0; 5];
-    ping_rx.read(&mut buf).unwrap();
+    ping_rx.read_exact(&mut buf).unwrap();
     assert_eq!(&buf, "hello".as_bytes());
 }
diff --git a/src/tools/miri/tests/utils/fs.rs b/src/tools/miri/tests/utils/fs.rs
index 7340908626f..7d75b3fced3 100644
--- a/src/tools/miri/tests/utils/fs.rs
+++ b/src/tools/miri/tests/utils/fs.rs
@@ -1,6 +1,6 @@
 use std::ffi::OsString;
-use std::fs;
 use std::path::PathBuf;
+use std::{fs, io};
 
 use super::miri_extern;
 
diff --git a/src/tools/miri/tests/utils/libc.rs b/src/tools/miri/tests/utils/libc.rs
new file mode 100644
index 00000000000..1a3cd067c04
--- /dev/null
+++ b/src/tools/miri/tests/utils/libc.rs
@@ -0,0 +1,44 @@
+//! Utils that need libc.
+#![allow(dead_code)]
+
+pub unsafe fn read_all(
+    fd: libc::c_int,
+    buf: *mut libc::c_void,
+    count: libc::size_t,
+) -> libc::ssize_t {
+    assert!(count > 0);
+    let mut read_so_far = 0;
+    while read_so_far < count {
+        let res = libc::read(fd, buf.add(read_so_far), count - read_so_far);
+        if res < 0 {
+            return res;
+        }
+        if res == 0 {
+            // EOF
+            break;
+        }
+        read_so_far += res as libc::size_t;
+    }
+    return read_so_far as libc::ssize_t;
+}
+
+pub unsafe fn write_all(
+    fd: libc::c_int,
+    buf: *const libc::c_void,
+    count: libc::size_t,
+) -> libc::ssize_t {
+    assert!(count > 0);
+    let mut written_so_far = 0;
+    while written_so_far < count {
+        let res = libc::write(fd, buf.add(written_so_far), count - written_so_far);
+        if res < 0 {
+            return res;
+        }
+        if res == 0 {
+            // EOF?
+            break;
+        }
+        written_so_far += res as libc::size_t;
+    }
+    return written_so_far as libc::ssize_t;
+}