diff --git a/src/shims/files.rs b/src/shims/files.rs index 606d1ffbea..0d4642c6ad 100644 --- a/src/shims/files.rs +++ b/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/shims/unix/fd.rs b/src/shims/unix/fd.rs index 71102d9f2f..b420955501 100644 --- a/src/shims/unix/fd.rs +++ b/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/shims/unix/linux_like/eventfd.rs b/src/shims/unix/linux_like/eventfd.rs index ee7deb8d38..2d35ef064d 100644 --- a/src/shims/unix/linux_like/eventfd.rs +++ b/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/shims/windows/fs.rs b/src/shims/windows/fs.rs index 72e016c12e..e4ec1b0130 100644 --- a/src/shims/windows/fs.rs +++ b/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/tests/fail-dep/libc/libc-epoll-data-race.rs b/tests/fail-dep/libc/libc-epoll-data-race.rs index 314ce90cfb..f6ec5be61b 100644 --- a/tests/fail-dep/libc/libc-epoll-data-race.rs +++ b/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(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/tests/fail-dep/libc/libc-read-and-uninit-premature-eof.rs b/tests/fail-dep/libc/libc-read-and-uninit-premature-eof.rs index 1dc334486c..e2fd6463a1 100644 --- a/tests/fail-dep/libc/libc-read-and-uninit-premature-eof.rs +++ b/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::(), 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::(), 4); + assert!(res == 3); buf.assume_init(); //~ERROR: encountered uninitialized memory, but expected an integer assert_eq!(libc::close(fd), 0); } diff --git a/tests/fail-dep/libc/libc_epoll_block_two_thread.rs b/tests/fail-dep/libc/libc_epoll_block_two_thread.rs index f6f2e2b931..054cb812d9 100644 --- a/tests/fail-dep/libc/libc_epoll_block_two_thread.rs +++ b/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/tests/fail-dep/libc/socketpair_block_read_twice.rs b/tests/fail-dep/libc/socketpair_block_read_twice.rs index b383985950..0fecfb8f66 100644 --- a/tests/fail-dep/libc/socketpair_block_read_twice.rs +++ b/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/tests/fail-dep/libc/socketpair_block_read_twice.stderr b/tests/fail-dep/libc/socketpair_block_read_twice.stderr index 9f19a60e6a..99d242ec7d 100644 --- a/tests/fail-dep/libc/socketpair_block_read_twice.stderr +++ b/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/tests/fail-dep/libc/socketpair_block_write_twice.rs b/tests/fail-dep/libc/socketpair_block_write_twice.rs index 7d84d87ebb..048938c091 100644 --- a/tests/fail-dep/libc/socketpair_block_write_twice.rs +++ b/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/tests/fail-dep/libc/socketpair_block_write_twice.stderr b/tests/fail-dep/libc/socketpair_block_write_twice.stderr index b29cd70f35..f766500d33 100644 --- a/tests/fail-dep/libc/socketpair_block_write_twice.stderr +++ b/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/tests/pass-dep/libc/libc-epoll-blocking.rs b/tests/pass-dep/libc/libc-epoll-blocking.rs index 54ebfa9d19..c97206487a 100644 --- a/tests/pass-dep/libc/libc-epoll-blocking.rs +++ b/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/tests/pass-dep/libc/libc-epoll-no-blocking.rs b/tests/pass-dep/libc/libc-epoll-no-blocking.rs index dc3ab2828f..7130790b86 100644 --- a/tests/pass-dep/libc/libc-epoll-no-blocking.rs +++ b/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/tests/pass-dep/libc/libc-fs.rs b/tests/pass-dep/libc/libc-fs.rs index 0ff48c389e..86cf2a041f 100644 --- a/tests/pass-dep/libc/libc-fs.rs +++ b/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>( 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::(), 2), 2); + let mut buf: MaybeUninit = std::mem::MaybeUninit::uninit(); + assert_eq!(libc::read(fd, buf.as_mut_ptr().cast::(), 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::(), 4), 3); - assert_eq!(buf, [1, 2, 3, 42, 42]); + let res = libc::read(fd, buf.as_mut_ptr().cast::(), 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/tests/pass-dep/libc/libc-pipe.rs b/tests/pass-dep/libc/libc-pipe.rs index bc755af864..ffbcf633b9 100644 --- a/tests/pass-dep/libc/libc-pipe.rs +++ b/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/tests/pass-dep/libc/libc-socketpair.rs b/tests/pass-dep/libc/libc-socketpair.rs index c36f6b1122..9c211ffbdb 100644 --- a/tests/pass-dep/libc/libc-socketpair.rs +++ b/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/tests/pass/shims/fs.rs b/tests/pass/shims/fs.rs index 9d5725773e..e7f11c5470 100644 --- a/tests/pass/shims/fs.rs +++ b/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/tests/pass/shims/pipe.rs b/tests/pass/shims/pipe.rs index c47feb8774..4915e54c53 100644 --- a/tests/pass/shims/pipe.rs +++ b/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/tests/utils/fs.rs b/tests/utils/fs.rs index 7340908626..7d75b3fced 100644 --- a/tests/utils/fs.rs +++ b/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/tests/utils/libc.rs b/tests/utils/libc.rs new file mode 100644 index 0000000000..1a3cd067c0 --- /dev/null +++ b/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; +}