Skip to content

Add read_buf equivalents for positioned reads #140459

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions library/std/src/fs/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,85 @@ fn file_test_io_read_write_at() {
check!(fs::remove_file(&filename));
}

#[test]
#[cfg(unix)]
fn test_read_buf_at() {
use crate::os::unix::fs::FileExt;

let tmpdir = tmpdir();
let filename = tmpdir.join("file_rt_io_file_test_read_buf_at.txt");
{
let oo = OpenOptions::new().create_new(true).write(true).read(true).clone();
let mut file = check!(oo.open(&filename));
check!(file.write_all(b"0123456789"));
}
{
let mut file = check!(File::open(&filename));
let mut buf: [MaybeUninit<u8>; 5] = [MaybeUninit::uninit(); 5];
let mut buf = BorrowedBuf::from(buf.as_mut_slice());

// Fill entire buffer with potentially short reads
while buf.unfilled().capacity() > 0 {
let len = buf.len();
check!(file.read_buf_at(buf.unfilled(), 2 + len as u64));
assert!(!buf.filled().is_empty());
assert!(b"23456".starts_with(buf.filled()));
assert_eq!(check!(file.stream_position()), 0);
}
assert_eq!(buf.filled(), b"23456");

// Already full
check!(file.read_buf_at(buf.unfilled(), 3));
check!(file.read_buf_at(buf.unfilled(), 10));
assert_eq!(buf.filled(), b"23456");
assert_eq!(check!(file.stream_position()), 0);

// Read past eof is noop
check!(file.read_buf_at(buf.clear().unfilled(), 10));
assert_eq!(buf.filled(), b"");
check!(file.read_buf_at(buf.clear().unfilled(), 11));
assert_eq!(buf.filled(), b"");
assert_eq!(check!(file.stream_position()), 0);
}
check!(fs::remove_file(&filename));
}

#[test]
#[cfg(unix)]
fn test_read_buf_exact_at() {
use crate::os::unix::fs::FileExt;

let tmpdir = tmpdir();
let filename = tmpdir.join("file_rt_io_file_test_read_buf_exact_at.txt");
{
let oo = OpenOptions::new().create_new(true).write(true).read(true).clone();
let mut file = check!(oo.open(&filename));
check!(file.write_all(b"0123456789"));
}
{
let mut file = check!(File::open(&filename));
let mut buf: [MaybeUninit<u8>; 5] = [MaybeUninit::uninit(); 5];
let mut buf = BorrowedBuf::from(buf.as_mut_slice());

// Exact read
check!(file.read_buf_exact_at(buf.unfilled(), 2));
assert_eq!(buf.filled(), b"23456");
assert_eq!(check!(file.stream_position()), 0);

// Already full
check!(file.read_buf_exact_at(buf.unfilled(), 3));
check!(file.read_buf_exact_at(buf.unfilled(), 10));
assert_eq!(buf.filled(), b"23456");
assert_eq!(check!(file.stream_position()), 0);

// Non-empty exact read past eof fails
let err = file.read_buf_exact_at(buf.clear().unfilled(), 6).unwrap_err();
assert_eq!(err.kind(), ErrorKind::UnexpectedEof);
assert_eq!(check!(file.stream_position()), 0);
}
check!(fs::remove_file(&filename));
}

#[test]
#[cfg(unix)]
fn set_get_unix_permissions() {
Expand Down Expand Up @@ -566,6 +645,39 @@ fn file_test_io_seek_read_write() {
check!(fs::remove_file(&filename));
}

#[test]
#[cfg(windows)]
fn test_seek_read_buf() {
use crate::os::windows::fs::FileExt;

let tmpdir = tmpdir();
let filename = tmpdir.join("file_rt_io_file_test_seek_read_buf.txt");
{
let oo = OpenOptions::new().create_new(true).write(true).read(true).clone();
let mut file = check!(oo.open(&filename));
check!(file.write_all(b"0123456789"));
}
{
let mut file = check!(File::open(&filename));
let mut buf: [MaybeUninit<u8>; 1] = [MaybeUninit::uninit()];
let mut buf = BorrowedBuf::from(buf.as_mut_slice());

// Seek read
check!(file.seek_read_buf(buf.unfilled(), 8));
assert_eq!(buf.filled(), b"8");
assert_eq!(check!(file.stream_position()), 9);

// Empty seek read
check!(file.seek_read_buf(buf.unfilled(), 0));
assert_eq!(buf.filled(), b"8");

// Seek read past eof
check!(file.seek_read_buf(buf.clear().unfilled(), 10));
assert_eq!(buf.filled(), b"");
}
check!(fs::remove_file(&filename));
}

#[test]
fn file_test_read_buf() {
let tmpdir = tmpdir();
Expand Down
1 change: 1 addition & 0 deletions library/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@
#![feature(bstr)]
#![feature(bstr_internals)]
#![feature(cast_maybe_uninit)]
#![feature(cfg_select)]
#![feature(char_internals)]
#![feature(clone_to_uninit)]
#![feature(const_cmp)]
Expand Down
89 changes: 89 additions & 0 deletions library/std/src/os/unix/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use super::platform::fs::MetadataExt as _;
// Used for `File::read` on intra-doc links
use crate::ffi::OsStr;
use crate::fs::{self, OpenOptions, Permissions};
use crate::io::BorrowedCursor;
use crate::os::unix::io::{AsFd, AsRawFd};
use crate::path::Path;
use crate::sealed::Sealed;
Expand Down Expand Up @@ -130,6 +131,91 @@ pub trait FileExt {
if !buf.is_empty() { Err(io::Error::READ_EXACT_EOF) } else { Ok(()) }
}

/// Reads some bytes starting from a given offset into the buffer.
///
/// This equivalent to the [`read_at`](FileExt::read_at) method, except that it is passed a
/// [`BorrowedCursor`] rather than `&mut [u8]` to allow use with uninitialized buffers. The new
/// data will be appended to any existing contents of `buf`.
///
/// # Examples
///
/// ```no_run
/// #![feature(core_io_borrowed_buf)]
/// #![feature(read_buf_at)]
///
/// use std::io;
/// use std::io::BorrowedBuf;
/// use std::fs::File;
/// use std::mem::MaybeUninit;
/// use std::os::unix::prelude::*;
///
/// fn main() -> io::Result<()> {
/// let mut file = File::open("pi.txt")?;
///
/// // Read some bytes starting from offset 2
/// let mut buf: [MaybeUninit<u8>; 10] = [MaybeUninit::uninit(); 10];
/// let mut buf = BorrowedBuf::from(buf.as_mut_slice());
/// file.read_buf_at(buf.unfilled(), 2)?;
///
/// assert!(buf.filled().starts_with(b"1"));
///
/// Ok(())
/// }
/// ```
#[unstable(feature = "read_buf_at", issue = "140771")]
fn read_buf_at(&self, buf: BorrowedCursor<'_>, offset: u64) -> io::Result<()> {
io::default_read_buf(|b| self.read_at(b, offset), buf)
}

/// Reads the exact number of bytes required to fill the buffer from a given offset.
///
/// This is equivalent to the [`read_exact_at`](FileExt::read_exact_at) method, except that it
/// is passed a [`BorrowedCursor`] rather than `&mut [u8]` to allow use with uninitialized
/// buffers. The new data will be appended to any existing contents of `buf`.
///
/// # Examples
///
/// ```no_run
/// #![feature(core_io_borrowed_buf)]
/// #![feature(read_buf_at)]
///
/// use std::io;
/// use std::io::BorrowedBuf;
/// use std::fs::File;
/// use std::mem::MaybeUninit;
/// use std::os::unix::prelude::*;
///
/// fn main() -> io::Result<()> {
/// let mut file = File::open("pi.txt")?;
///
/// // Read exactly 10 bytes starting from offset 2
/// let mut buf: [MaybeUninit<u8>; 10] = [MaybeUninit::uninit(); 10];
/// let mut buf = BorrowedBuf::from(buf.as_mut_slice());
/// file.read_buf_exact_at(buf.unfilled(), 2)?;
///
/// assert_eq!(buf.filled(), b"1415926535");
///
/// Ok(())
/// }
/// ```
#[unstable(feature = "read_buf_at", issue = "140771")]
fn read_buf_exact_at(&self, mut buf: BorrowedCursor<'_>, mut offset: u64) -> io::Result<()> {
while buf.capacity() > 0 {
let prev_written = buf.written();
match self.read_buf_at(buf.reborrow(), offset) {
Ok(()) => {}
Err(e) if e.is_interrupted() => {}
Err(e) => return Err(e),
}
let n = buf.written() - prev_written;
offset += n as u64;
if n == 0 {
return Err(io::Error::READ_EXACT_EOF);
}
}
Ok(())
}

/// Writes a number of bytes starting from a given offset.
///
/// Returns the number of bytes written.
Expand Down Expand Up @@ -264,6 +350,9 @@ impl FileExt for fs::File {
fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
self.as_inner().read_at(buf, offset)
}
fn read_buf_at(&self, buf: BorrowedCursor<'_>, offset: u64) -> io::Result<()> {
self.as_inner().read_buf_at(buf, offset)
}
fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
self.as_inner().read_vectored_at(bufs, offset)
}
Expand Down
43 changes: 43 additions & 0 deletions library/std/src/os/windows/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#![stable(feature = "rust1", since = "1.0.0")]

use crate::fs::{self, Metadata, OpenOptions};
use crate::io::BorrowedCursor;
use crate::path::Path;
use crate::sealed::Sealed;
use crate::sys_common::{AsInner, AsInnerMut, IntoInner};
Expand Down Expand Up @@ -49,6 +50,44 @@ pub trait FileExt {
#[stable(feature = "file_offset", since = "1.15.0")]
fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result<usize>;

/// Seeks to a given position and reads some bytes into the buffer.
///
/// This is equivalent to the [`seek_read`](FileExt::seek_read) method, except that it is passed
/// a [`BorrowedCursor`] rather than `&mut [u8]` to allow use with uninitialized buffers. The
/// new data will be appended to any existing contents of `buf`.
///
/// Reading beyond the end of the file will always succeed without reading any bytes.
///
/// # Examples
///
/// ```no_run
/// #![feature(core_io_borrowed_buf)]
/// #![feature(read_buf_at)]
///
/// use std::io;
/// use std::io::BorrowedBuf;
/// use std::fs::File;
/// use std::mem::MaybeUninit;
/// use std::os::windows::prelude::*;
///
/// fn main() -> io::Result<()> {
/// let mut file = File::open("pi.txt")?;
///
/// // Read some bytes starting from offset 2
/// let mut buf: [MaybeUninit<u8>; 10] = [MaybeUninit::uninit(); 10];
/// let mut buf = BorrowedBuf::from(buf.as_mut_slice());
/// file.seek_read_buf(buf.unfilled(), 2)?;
///
/// assert!(buf.filled().starts_with(b"1"));
///
/// Ok(())
/// }
/// ```
#[unstable(feature = "read_buf_at", issue = "140771")]
fn seek_read_buf(&self, buf: BorrowedCursor<'_>, offset: u64) -> io::Result<()> {
io::default_read_buf(|b| self.seek_read(b, offset), buf)
}

/// Seeks to a given position and writes a number of bytes.
///
/// Returns the number of bytes written.
Expand Down Expand Up @@ -89,6 +128,10 @@ impl FileExt for fs::File {
self.as_inner().read_at(buf, offset)
}

fn seek_read_buf(&self, buf: BorrowedCursor<'_>, offset: u64) -> io::Result<()> {
self.as_inner().read_buf_at(buf, offset)
}

fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
self.as_inner().write_at(buf, offset)
}
Expand Down
Loading
Loading