Skip to content

Commit 820cc81

Browse files
committed
Add FileExt traits
Addresses issue #576 to add pwrite/pread support to async_std for parity with std & tokio.
1 parent 5ee022b commit 820cc81

File tree

4 files changed

+301
-2
lines changed

4 files changed

+301
-2
lines changed

Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ std = [
5353
"futures-channel",
5454
"async-channel",
5555
"async-lock",
56+
"async-trait",
5657
]
5758
alloc = [
5859
"futures-core/alloc",
@@ -77,6 +78,7 @@ pin-project-lite = { version = "0.2.0", optional = true }
7778
pin-utils = { version = "0.1.0-alpha.4", optional = true }
7879
slab = { version = "0.4.2", optional = true }
7980
async-channel = { version = "1.5.1", optional = true }
81+
async-trait = { version = "0.1.42", optional = true }
8082

8183
# Devdepencency, but they are not allowed to be optional :/
8284
surf = { version = "2.0.0", optional = true }

src/fs/file.rs

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

src/os/unix/fs.rs

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

src/os/windows/fs.rs

+73-1
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,12 @@ 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! {
62+
use async_trait::async_trait;
63+
6264
/// Windows-specific extensions to `OpenOptions`.
6365
pub trait OpenOptionsExt {
6466
/// Overrides the `dwDesiredAccess` argument to the call to [`CreateFile`]
@@ -223,4 +225,74 @@ cfg_docs! {
223225
/// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-security_impersonation_level
224226
fn security_qos_flags(&mut self, flags: u32) -> &mut Self;
225227
}
228+
229+
/// Windows-specific extensions to [`fs::File`].
230+
#[async_trait]
231+
pub trait FileExt {
232+
/// Seeks to a given position and reads a number of bytes.
233+
///
234+
/// Returns the number of bytes read.
235+
///
236+
/// The offset is relative to the start of the file and thus independent
237+
/// from the current cursor. The current cursor **is** affected by this
238+
/// function, it is set to the end of the read.
239+
///
240+
/// Reading beyond the end of the file will always return with a length of
241+
/// 0\.
242+
///
243+
/// Note that similar to `File::read`, it is not an error to return with a
244+
/// short read. When returning from such a short read, the file pointer is
245+
/// still updated.
246+
///
247+
/// # Examples
248+
///
249+
/// ```no_run
250+
/// use async_std::io;
251+
/// use async_std::fs::File;
252+
/// use async_std::os::windows::prelude::*;
253+
///
254+
/// fn main() -> io::Result<()> {
255+
/// let mut file = File::open("foo.txt").await?;
256+
/// let mut buffer = [0; 10];
257+
///
258+
/// // Read 10 bytes, starting 72 bytes from the
259+
/// // start of the file.
260+
/// file.seek_read(&mut buffer[..], 72).await?;
261+
/// Ok(())
262+
/// }
263+
/// ```
264+
async fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result<usize>;
265+
266+
/// Seeks to a given position and writes a number of bytes.
267+
///
268+
/// Returns the number of bytes written.
269+
///
270+
/// The offset is relative to the start of the file and thus independent
271+
/// from the current cursor. The current cursor **is** affected by this
272+
/// function, it is set to the end of the write.
273+
///
274+
/// When writing beyond the end of the file, the file is appropriately
275+
/// extended and the intermediate bytes are left uninitialized.
276+
///
277+
/// Note that similar to `File::write`, it is not an error to return a
278+
/// short write. When returning from such a short write, the file pointer
279+
/// is still updated.
280+
///
281+
/// # Examples
282+
///
283+
/// ```no_run
284+
/// use async_std::fs::File;
285+
/// use async_std::os::windows::prelude::*;
286+
///
287+
/// fn main() -> std::io::Result<()> {
288+
/// let mut buffer = File::create("foo.txt").await?;
289+
///
290+
/// // Write a byte string starting 72 bytes from
291+
/// // the start of the file.
292+
/// buffer.seek_write(b"some bytes", 72).await?;
293+
/// Ok(())
294+
/// }
295+
/// ```
296+
async fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result<usize>;
297+
}
226298
}

0 commit comments

Comments
 (0)