Skip to content

Commit d182d1b

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

File tree

4 files changed

+298
-2
lines changed

4 files changed

+298
-2
lines changed

Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ unstable = [
3838
"std",
3939
"async-io",
4040
"async-process",
41+
"file-ext",
4142
]
4243
attributes = ["async-attributes"]
4344
std = [
@@ -61,6 +62,7 @@ alloc = [
6162
tokio1 = ["async-global-executor/tokio"]
6263
tokio02 = ["async-global-executor/tokio02"]
6364
tokio03 = ["async-global-executor/tokio03"]
65+
file-ext = ["async-trait"]
6466

6567
[dependencies]
6668
async-attributes = { version = "1.1.1", optional = true }
@@ -77,6 +79,7 @@ pin-project-lite = { version = "0.2.0", optional = true }
7779
pin-utils = { version = "0.1.0-alpha.4", optional = true }
7880
slab = { version = "0.4.2", optional = true }
7981
async-channel = { version = "1.5.1", optional = true }
82+
async-trait = { version = "0.1.42", optional = true }
8083

8184
# Devdepencency, but they are not allowed to be optional :/
8285
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

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

src/os/windows/fs.rs

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

0 commit comments

Comments
 (0)