Skip to content

Commit 82f7c70

Browse files
committed
Add FileExt traits
Addresses issue async-rs#576 to add pwrite/pread support to async_std for parity with std & tokio.
1 parent 0c35de1 commit 82f7c70

File tree

3 files changed

+293
-2
lines changed

3 files changed

+293
-2
lines changed

src/fs/file.rs

+31
Original file line numberDiff line numberDiff line change
@@ -939,4 +939,35 @@ mod tests {
939939
.unwrap();
940940
});
941941
}
942+
943+
#[cfg(target_os = "windows")]
944+
#[test]
945+
fn async_file_win_positional_io() {
946+
use super::os::windows::fs::FileExt;
947+
948+
crate::task::block_on(async move {
949+
let file = File::open(file!()).await.unwrap();
950+
assert_eq!(10u64, file.seek_write(&[5u8; 10], 10u64).await.unwrap());
951+
952+
let mut buf: [u8; 20];
953+
assert_eq!(20u64, file.seek_read(&buf, 0)).await.unwrap();
954+
assert_eq!(buf.iter(), [0u8; 10].iter().chain([5u8; 10].iter()));
955+
});
956+
}
957+
958+
#[cfg(target_os = "unix")]
959+
#[test]
960+
fn async_file_unix_positional_io() {
961+
use super::os::unix::fs::FileExt;
962+
963+
crate::task::block_on(async move {
964+
let file = File::open(file!()).await.unwrap();
965+
assert_eq!(10u64, file.write_all_at(&[5u8; 10], 10u64).await.unwrap());
966+
967+
let mut buf: [u8; 20];
968+
assert_eq!(20u64, file.read_exact_at(&buf, 0)).await.unwrap();
969+
assert_eq!(buf.iter(), [0u8; 10].iter().chain([5u8; 10].iter()));
970+
});
971+
972+
}
942973
}

src/os/unix/fs.rs

+192-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub async fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::Resu
3030
}
3131

3232
cfg_not_docs! {
33-
pub use std::os::unix::fs::{DirBuilderExt, DirEntryExt, OpenOptionsExt};
33+
pub use std::os::unix::fs::{DirBuilderExt, DirEntryExt, OpenOptionsExt, FileExt};
3434
}
3535

3636
cfg_docs! {
@@ -68,4 +68,195 @@ cfg_docs! {
6868
/// This options overwrites any previously set custom flags.
6969
fn custom_flags(&mut self, flags: i32) -> &mut Self;
7070
}
71+
72+
/// Unix-specific extensions to [`fs::File`].
73+
pub trait FileExt {
74+
/// Reads a number of bytes starting from a given offset.
75+
///
76+
/// Returns the number of bytes read.
77+
///
78+
/// The offset is relative to the start of the file and thus independent
79+
/// from the current cursor.
80+
///
81+
/// The current file cursor is not affected by this function.
82+
///
83+
/// Note that similar to [`File::read`], it is not an error to return with a
84+
/// short read.
85+
///
86+
/// [`File::read`]: fs::File::read
87+
///
88+
/// # Examples
89+
///
90+
/// ```no_run
91+
/// use async_std::io;
92+
/// use async_std::fs::File;
93+
/// use async_std::os::unix::prelude::FileExt;
94+
///
95+
/// async fn main() -> io::Result<()> {
96+
/// let mut buf = [0u8; 8];
97+
/// let file = File::open("foo.txt").await?;
98+
///
99+
/// // We now read 8 bytes from the offset 10.
100+
/// let num_bytes_read = file.read_at(&mut buf, 10).await?;
101+
/// println!("read {} bytes: {:?}", num_bytes_read, buf);
102+
/// Ok(())
103+
/// }
104+
/// ```
105+
async fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize>;
106+
107+
/// Reads the exact number of byte required to fill `buf` from the given offset.
108+
///
109+
/// The offset is relative to the start of the file and thus independent
110+
/// from the current cursor.
111+
///
112+
/// The current file cursor is not affected by this function.
113+
///
114+
/// Similar to [`io::Read::read_exact`] but uses [`read_at`] instead of `read`.
115+
///
116+
/// [`read_at`]: FileExt::read_at
117+
///
118+
/// # Errors
119+
///
120+
/// If this function encounters an error of the kind
121+
/// [`io::ErrorKind::Interrupted`] then the error is ignored and the operation
122+
/// will continue.
123+
///
124+
/// If this function encounters an "end of file" before completely filling
125+
/// the buffer, it returns an error of the kind [`io::ErrorKind::UnexpectedEof`].
126+
/// The contents of `buf` are unspecified in this case.
127+
///
128+
/// If any other read error is encountered then this function immediately
129+
/// returns. The contents of `buf` are unspecified in this case.
130+
///
131+
/// If this function returns an error, it is unspecified how many bytes it
132+
/// has read, but it will never read more than would be necessary to
133+
/// completely fill the buffer.
134+
///
135+
/// # Examples
136+
///
137+
/// ```no_run
138+
/// use async_std::io;
139+
/// use async_std::fs::File;
140+
/// use async_std::os::unix::prelude::FileExt;
141+
///
142+
/// async fn main() -> io::Result<()> {
143+
/// let mut buf = [0u8; 8];
144+
/// let file = File::open("foo.txt").await?;
145+
///
146+
/// // We now read exactly 8 bytes from the offset 10.
147+
/// file.read_exact_at(&mut buf, 10).await?;
148+
/// println!("read {} bytes: {:?}", buf.len(), buf);
149+
/// Ok(())
150+
/// }
151+
/// ```
152+
async fn read_exact_at(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> {
153+
while !buf.is_empty() {
154+
match self.read_at(buf, offset).await {
155+
Ok(0) => break,
156+
Ok(n) => {
157+
let tmp = buf;
158+
buf = &mut tmp[n..];
159+
offset += n as u64;
160+
}
161+
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
162+
Err(e) => return Err(e),
163+
}
164+
}
165+
if !buf.is_empty() {
166+
Err(io::Error::new(io::ErrorKind::UnexpectedEof, "failed to fill whole buffer"))
167+
} else {
168+
Ok(())
169+
}
170+
}
171+
172+
/// Writes a number of bytes starting from a given offset.
173+
///
174+
/// Returns the number of bytes written.
175+
///
176+
/// The offset is relative to the start of the file and thus independent
177+
/// from the current cursor.
178+
///
179+
/// The current file cursor is not affected by this function.
180+
///
181+
/// When writing beyond the end of the file, the file is appropriately
182+
/// extended and the intermediate bytes are initialized with the value 0.
183+
///
184+
/// Note that similar to [`File::write`], it is not an error to return a
185+
/// short write.
186+
///
187+
/// [`File::write`]: fs::File::write
188+
///
189+
/// # Examples
190+
///
191+
/// ```no_run
192+
/// use async_std::fs::File;
193+
/// use async_std::io;
194+
/// use async_std::os::unix::prelude::FileExt;
195+
///
196+
/// async fn main() -> io::Result<()> {
197+
/// let file = File::open("foo.txt").await?;
198+
///
199+
/// // We now write at the offset 10.
200+
/// file.write_at(b"sushi", 10).await?;
201+
/// Ok(())
202+
/// }
203+
/// ```
204+
async fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize>;
205+
206+
/// Attempts to write an entire buffer starting from a given offset.
207+
///
208+
/// The offset is relative to the start of the file and thus independent
209+
/// from the current cursor.
210+
///
211+
/// The current file cursor is not affected by this function.
212+
///
213+
/// This method will continuously call [`write_at`] until there is no more data
214+
/// to be written or an error of non-[`io::ErrorKind::Interrupted`] kind is
215+
/// returned. This method will not return until the entire buffer has been
216+
/// successfully written or such an error occurs. The first error that is
217+
/// not of [`io::ErrorKind::Interrupted`] kind generated from this method will be
218+
/// returned.
219+
///
220+
/// # Errors
221+
///
222+
/// This function will return the first error of
223+
/// non-[`io::ErrorKind::Interrupted`] kind that [`write_at`] returns.
224+
///
225+
/// [`write_at`]: FileExt::write_at
226+
///
227+
/// # Examples
228+
///
229+
/// ```no_run
230+
/// use async_std::fs::File;
231+
/// use async_std::io;
232+
/// use async_std::os::unix::prelude::FileExt;
233+
///
234+
/// async fn main() -> io::Result<()> {
235+
/// let file = File::open("foo.txt").await?;
236+
///
237+
/// // We now write at the offset 10.
238+
/// file.write_all_at(b"sushi", 10).await?;
239+
/// Ok(())
240+
/// }
241+
/// ```
242+
async fn write_all_at(&self, mut buf: &[u8], mut offset: u64) -> io::Result<()> {
243+
while !buf.is_empty() {
244+
match self.write_at(buf, offset).await {
245+
Ok(0) => {
246+
return Err(io::Error::new(
247+
io::ErrorKind::WriteZero,
248+
"failed to write whole buffer",
249+
));
250+
}
251+
Ok(n) => {
252+
buf = &buf[n..];
253+
offset += n as u64
254+
}
255+
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
256+
Err(e) => return Err(e),
257+
}
258+
}
259+
Ok(())
260+
}
261+
}
71262
}

src/os/windows/fs.rs

+70-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ pub async fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io:
5555
}
5656

5757
cfg_not_docs! {
58-
pub use std::os::windows::fs::{OpenOptionsExt};
58+
pub use std::os::windows::fs::{OpenOptionsExt, FileExt};
5959
}
6060

6161
cfg_docs! {
@@ -227,4 +227,73 @@ cfg_docs! {
227227
#[stable(feature = "open_options_ext", since = "1.10.0")]
228228
fn security_qos_flags(&mut self, flags: u32) -> &mut Self;
229229
}
230+
231+
/// Windows-specific extensions to [`fs::File`].
232+
pub trait FileExt {
233+
/// Seeks to a given position and reads a number of bytes.
234+
///
235+
/// Returns the number of bytes read.
236+
///
237+
/// The offset is relative to the start of the file and thus independent
238+
/// from the current cursor. The current cursor **is** affected by this
239+
/// function, it is set to the end of the read.
240+
///
241+
/// Reading beyond the end of the file will always return with a length of
242+
/// 0\.
243+
///
244+
/// Note that similar to `File::read`, it is not an error to return with a
245+
/// short read. When returning from such a short read, the file pointer is
246+
/// still updated.
247+
///
248+
/// # Examples
249+
///
250+
/// ```no_run
251+
/// use async_std::io;
252+
/// use async_std::fs::File;
253+
/// use async_std::os::windows::prelude::*;
254+
///
255+
/// fn main() -> io::Result<()> {
256+
/// let mut file = File::open("foo.txt").await?;
257+
/// let mut buffer = [0; 10];
258+
///
259+
/// // Read 10 bytes, starting 72 bytes from the
260+
/// // start of the file.
261+
/// file.seek_read(&mut buffer[..], 72).await?;
262+
/// Ok(())
263+
/// }
264+
/// ```
265+
fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result<usize>;
266+
267+
/// Seeks to a given position and writes a number of bytes.
268+
///
269+
/// Returns the number of bytes written.
270+
///
271+
/// The offset is relative to the start of the file and thus independent
272+
/// from the current cursor. The current cursor **is** affected by this
273+
/// function, it is set to the end of the write.
274+
///
275+
/// When writing beyond the end of the file, the file is appropriately
276+
/// extended and the intermediate bytes are left uninitialized.
277+
///
278+
/// Note that similar to `File::write`, it is not an error to return a
279+
/// short write. When returning from such a short write, the file pointer
280+
/// is still updated.
281+
///
282+
/// # Examples
283+
///
284+
/// ```no_run
285+
/// use async_std::fs::File;
286+
/// use async_std::os::windows::prelude::*;
287+
///
288+
/// fn main() -> std::io::Result<()> {
289+
/// let mut buffer = File::create("foo.txt").await?;
290+
///
291+
/// // Write a byte string starting 72 bytes from
292+
/// // the start of the file.
293+
/// buffer.seek_write(b"some bytes", 72).await?;
294+
/// Ok(())
295+
/// }
296+
/// ```
297+
fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result<usize>;
298+
}
230299
}

0 commit comments

Comments
 (0)