Skip to content

Commit f6dd203

Browse files
committed
Add read_buf equivalents for positioned reads
Adds the following items under the `read_buf_at` feature: - `std::os::unix::fs::FileExt::read_buf_at` - `std::os::unix::fs::FileExt::read_buf_exact_at` - `std::os::windows::fs::FileExt::seek_read_buf`
1 parent 239e8b1 commit f6dd203

File tree

8 files changed

+307
-22
lines changed

8 files changed

+307
-22
lines changed

library/std/src/fs/tests.rs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,85 @@ fn file_test_io_read_write_at() {
490490
check!(fs::remove_file(&filename));
491491
}
492492

493+
#[test]
494+
#[cfg(unix)]
495+
fn test_read_buf_at() {
496+
use crate::os::unix::fs::FileExt;
497+
498+
let tmpdir = tmpdir();
499+
let filename = tmpdir.join("file_rt_io_file_test_read_buf_at.txt");
500+
{
501+
let oo = OpenOptions::new().create_new(true).write(true).read(true).clone();
502+
let mut file = check!(oo.open(&filename));
503+
check!(file.write_all(b"0123456789"));
504+
}
505+
{
506+
let mut file = check!(File::open(&filename));
507+
let mut buf: [MaybeUninit<u8>; 5] = [MaybeUninit::uninit(); 5];
508+
let mut buf = BorrowedBuf::from(buf.as_mut_slice());
509+
510+
// Potentially short read
511+
while buf.unfilled().capacity() > 0 {
512+
let len = buf.len();
513+
check!(file.read_buf_at(buf.unfilled(), 2 + len as u64));
514+
assert!(!buf.filled().is_empty());
515+
assert!(b"23456".starts_with(buf.filled()));
516+
assert_eq!(check!(file.stream_position()), 0);
517+
}
518+
assert_eq!(buf.filled(), b"23456");
519+
520+
// Already full
521+
check!(file.read_buf_at(buf.unfilled(), 3));
522+
check!(file.read_buf_at(buf.unfilled(), 10));
523+
assert_eq!(buf.filled(), b"23456");
524+
assert_eq!(check!(file.stream_position()), 0);
525+
526+
// Read past eof is noop
527+
check!(file.read_buf_at(buf.clear().unfilled(), 10));
528+
assert_eq!(buf.filled(), b"");
529+
check!(file.read_buf_at(buf.clear().unfilled(), 11));
530+
assert_eq!(buf.filled(), b"");
531+
assert_eq!(check!(file.stream_position()), 0);
532+
}
533+
check!(fs::remove_file(&filename));
534+
}
535+
536+
#[test]
537+
#[cfg(unix)]
538+
fn test_read_buf_exact_at() {
539+
use crate::os::unix::fs::FileExt;
540+
541+
let tmpdir = tmpdir();
542+
let filename = tmpdir.join("file_rt_io_file_test_read_buf_exact_at.txt");
543+
{
544+
let oo = OpenOptions::new().create_new(true).write(true).read(true).clone();
545+
let mut file = check!(oo.open(&filename));
546+
check!(file.write_all(b"0123456789"));
547+
}
548+
{
549+
let mut file = check!(File::open(&filename));
550+
let mut buf: [MaybeUninit<u8>; 5] = [MaybeUninit::uninit(); 5];
551+
let mut buf = BorrowedBuf::from(buf.as_mut_slice());
552+
553+
// Exact read
554+
check!(file.read_buf_exact_at(buf.unfilled(), 2));
555+
assert_eq!(buf.filled(), b"23456");
556+
assert_eq!(check!(file.stream_position()), 0);
557+
558+
// Already full
559+
check!(file.read_buf_exact_at(buf.unfilled(), 3));
560+
check!(file.read_buf_exact_at(buf.unfilled(), 10));
561+
assert_eq!(buf.filled(), b"23456");
562+
assert_eq!(check!(file.stream_position()), 0);
563+
564+
// Non-empty exact read past eof fails
565+
let err = file.read_buf_exact_at(buf.clear().unfilled(), 6).unwrap_err();
566+
assert_eq!(err.kind(), ErrorKind::UnexpectedEof);
567+
assert_eq!(check!(file.stream_position()), 0);
568+
}
569+
check!(fs::remove_file(&filename));
570+
}
571+
493572
#[test]
494573
#[cfg(unix)]
495574
fn set_get_unix_permissions() {
@@ -566,6 +645,39 @@ fn file_test_io_seek_read_write() {
566645
check!(fs::remove_file(&filename));
567646
}
568647

648+
#[test]
649+
#[cfg(windows)]
650+
fn test_seek_read_buf() {
651+
use crate::os::windows::fs::FileExt;
652+
653+
let tmpdir = tmpdir();
654+
let filename = tmpdir.join("file_rt_io_file_test_seek_read_buf.txt");
655+
{
656+
let oo = OpenOptions::new().create_new(true).write(true).read(true).clone();
657+
let mut file = check!(oo.open(&filename));
658+
check!(file.write_all(b"0123456789"));
659+
}
660+
{
661+
let mut file = check!(File::open(&filename));
662+
let mut buf: [MaybeUninit<u8>; 1] = [MaybeUninit::uninit()];
663+
let mut buf = BorrowedBuf::from(buf.as_mut_slice());
664+
665+
// Seek read
666+
check!(file.seek_read_buf(buf.unfilled(), 8));
667+
assert_eq!(buf.filled(), b"8");
668+
assert_eq!(check!(file.stream_position()), 9);
669+
670+
// Empty seek read
671+
check!(file.seek_read_buf(buf.unfilled(), 0));
672+
assert_eq!(buf.filled(), b"8");
673+
674+
// Seek read past eof
675+
check!(file.seek_read_buf(buf.clear().unfilled(), 10));
676+
assert_eq!(buf.filled(), b"");
677+
}
678+
check!(fs::remove_file(&filename));
679+
}
680+
569681
#[test]
570682
fn file_test_read_buf() {
571683
let tmpdir = tmpdir();

library/std/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@
330330
#![feature(bstr)]
331331
#![feature(bstr_internals)]
332332
#![feature(cast_maybe_uninit)]
333+
#![feature(cfg_select)]
333334
#![feature(char_internals)]
334335
#![feature(clone_to_uninit)]
335336
#![feature(const_cmp)]

library/std/src/os/unix/fs.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use super::platform::fs::MetadataExt as _;
1111
// Used for `File::read` on intra-doc links
1212
use crate::ffi::OsStr;
1313
use crate::fs::{self, OpenOptions, Permissions};
14+
use crate::io::BorrowedCursor;
1415
use crate::os::unix::io::{AsFd, AsRawFd};
1516
use crate::path::Path;
1617
use crate::sealed::Sealed;
@@ -130,6 +131,91 @@ pub trait FileExt {
130131
if !buf.is_empty() { Err(io::Error::READ_EXACT_EOF) } else { Ok(()) }
131132
}
132133

134+
/// Reads some bytes starting from a given offset into the buffer.
135+
///
136+
/// This equivalent to the [`read_at`](FileExt::read_at) method, except that it is passed a
137+
/// [`BorrowedCursor`] rather than `&mut [u8]` to allow use with uninitialized buffers. The new
138+
/// data will be appended to any existing contents of `buf`.
139+
///
140+
/// # Examples
141+
///
142+
/// ```no_run
143+
/// #![feature(core_io_borrowed_buf)]
144+
/// #![feature(read_buf_at)]
145+
///
146+
/// use std::io;
147+
/// use std::io::BorrowedBuf;
148+
/// use std::fs::File;
149+
/// use std::mem::MaybeUninit;
150+
/// use std::os::unix::prelude::*;
151+
///
152+
/// fn main() -> io::Result<()> {
153+
/// let mut file = File::open("pi.txt")?;
154+
///
155+
/// // Read some bytes starting from offset 2
156+
/// let mut buf: [MaybeUninit<u8>; 10] = [MaybeUninit::uninit(); 10];
157+
/// let mut buf = BorrowedBuf::from(buf.as_mut_slice());
158+
/// file.read_buf_at(buf.unfilled(), 2)?;
159+
///
160+
/// assert!(buf.filled().starts_with(b"1"));
161+
///
162+
/// Ok(())
163+
/// }
164+
/// ```
165+
#[unstable(feature = "read_buf_at", issue = "140771")]
166+
fn read_buf_at(&self, buf: BorrowedCursor<'_>, offset: u64) -> io::Result<()> {
167+
io::default_read_buf(|b| self.read_at(b, offset), buf)
168+
}
169+
170+
/// Reads the exact number of bytes required to fill the buffer from a given offset.
171+
///
172+
/// This is equivalent to the [`read_exact_at`](FileExt::read_exact_at) method, except that it
173+
/// is passed a [`BorrowedCursor`] rather than `&mut [u8]` to allow use with uninitialized
174+
/// buffers. The new data will be appended to any existing contents of `buf`.
175+
///
176+
/// # Examples
177+
///
178+
/// ```no_run
179+
/// #![feature(core_io_borrowed_buf)]
180+
/// #![feature(read_buf_at)]
181+
///
182+
/// use std::io;
183+
/// use std::io::BorrowedBuf;
184+
/// use std::fs::File;
185+
/// use std::mem::MaybeUninit;
186+
/// use std::os::unix::prelude::*;
187+
///
188+
/// fn main() -> io::Result<()> {
189+
/// let mut file = File::open("pi.txt")?;
190+
///
191+
/// // Read exactly 10 bytes starting from offset 2
192+
/// let mut buf: [MaybeUninit<u8>; 10] = [MaybeUninit::uninit(); 10];
193+
/// let mut buf = BorrowedBuf::from(buf.as_mut_slice());
194+
/// file.read_buf_exact_at(buf.unfilled(), 2)?;
195+
///
196+
/// assert_eq!(buf.filled(), b"1415926535");
197+
///
198+
/// Ok(())
199+
/// }
200+
/// ```
201+
#[unstable(feature = "read_buf_at", issue = "140771")]
202+
fn read_buf_exact_at(&self, mut buf: BorrowedCursor<'_>, mut offset: u64) -> io::Result<()> {
203+
while buf.capacity() > 0 {
204+
let prev_written = buf.written();
205+
match self.read_buf_at(buf.reborrow(), offset) {
206+
Ok(()) => {}
207+
Err(e) if e.is_interrupted() => {}
208+
Err(e) => return Err(e),
209+
}
210+
let n = buf.written() - prev_written;
211+
offset += n as u64;
212+
if n == 0 {
213+
return Err(io::Error::READ_EXACT_EOF);
214+
}
215+
}
216+
Ok(())
217+
}
218+
133219
/// Writes a number of bytes starting from a given offset.
134220
///
135221
/// Returns the number of bytes written.
@@ -264,6 +350,9 @@ impl FileExt for fs::File {
264350
fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
265351
self.as_inner().read_at(buf, offset)
266352
}
353+
fn read_buf_at(&self, buf: BorrowedCursor<'_>, offset: u64) -> io::Result<()> {
354+
self.as_inner().read_buf_at(buf, offset)
355+
}
267356
fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
268357
self.as_inner().read_vectored_at(bufs, offset)
269358
}

library/std/src/os/windows/fs.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#![stable(feature = "rust1", since = "1.0.0")]
66

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

53+
/// Seeks to a given position and reads some bytes into the buffer.
54+
///
55+
/// This is equivalent to the [`seek_read`](FileExt::seek_read) method, except that it is passed
56+
/// a [`BorrowedCursor`] rather than `&mut [u8]` to allow use with uninitialized buffers. The
57+
/// new data will be appended to any existing contents of `buf`.
58+
///
59+
/// Reading beyond the end of the file will always succeed without reading any bytes.
60+
///
61+
/// # Examples
62+
///
63+
/// ```no_run
64+
/// #![feature(core_io_borrowed_buf)]
65+
/// #![feature(read_buf_at)]
66+
///
67+
/// use std::io;
68+
/// use std::io::BorrowedBuf;
69+
/// use std::fs::File;
70+
/// use std::mem::MaybeUninit;
71+
/// use std::os::windows::prelude::*;
72+
///
73+
/// fn main() -> io::Result<()> {
74+
/// let mut file = File::open("pi.txt")?;
75+
///
76+
/// // Read some bytes starting from offset 2
77+
/// let mut buf: [MaybeUninit<u8>; 10] = [MaybeUninit::uninit(); 10];
78+
/// let mut buf = BorrowedBuf::from(buf.as_mut_slice());
79+
/// file.seek_read_buf(buf.unfilled(), 2)?;
80+
///
81+
/// assert!(buf.filled().starts_with(b"1"));
82+
///
83+
/// Ok(())
84+
/// }
85+
/// ```
86+
#[unstable(feature = "read_buf_at", issue = "140771")]
87+
fn seek_read_buf(&self, buf: BorrowedCursor<'_>, offset: u64) -> io::Result<()> {
88+
io::default_read_buf(|b| self.seek_read(b, offset), buf)
89+
}
90+
5291
/// Seeks to a given position and writes a number of bytes.
5392
///
5493
/// Returns the number of bytes written.
@@ -89,6 +128,10 @@ impl FileExt for fs::File {
89128
self.as_inner().read_at(buf, offset)
90129
}
91130

131+
fn seek_read_buf(&self, buf: BorrowedCursor<'_>, offset: u64) -> io::Result<()> {
132+
self.as_inner().read_buf_at(buf, offset)
133+
}
134+
92135
fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
93136
self.as_inner().write_at(buf, offset)
94137
}

0 commit comments

Comments
 (0)