From 653c0912628382388ceb8a3cf29e88dad35b98ac Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 3 Jul 2020 16:20:37 -0700 Subject: [PATCH 1/2] Add `read_exact_at` and `write_all_at` to WASI's `FileExt` This adds `read_exact_at` and `write_all_at` to WASI's `FileExt`, similar to the Unix versions of the same names. --- src/libstd/sys/wasi/ext/fs.rs | 94 +++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/src/libstd/sys/wasi/ext/fs.rs b/src/libstd/sys/wasi/ext/fs.rs index 6696efa8871c2..10ea4fd34bda3 100644 --- a/src/libstd/sys/wasi/ext/fs.rs +++ b/src/libstd/sys/wasi/ext/fs.rs @@ -27,6 +27,58 @@ pub trait FileExt { /// [`File::read`]: ../../../../std/fs/struct.File.html#method.read_vectored fn read_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result; + /// Reads the exact number of byte required to fill `buf` from the given offset. + /// + /// The offset is relative to the start of the file and thus independent + /// from the current cursor. + /// + /// The current file cursor is not affected by this function. + /// + /// Similar to [`Read::read_exact`] but uses [`read_at`] instead of `read`. + /// + /// [`Read::read_exact`]: ../../../../std/io/trait.Read.html#method.read_exact + /// [`read_at`]: #tymethod.read_at + /// + /// # Errors + /// + /// If this function encounters an error of the kind + /// [`ErrorKind::Interrupted`] then the error is ignored and the operation + /// will continue. + /// + /// If this function encounters an "end of file" before completely filling + /// the buffer, it returns an error of the kind [`ErrorKind::UnexpectedEof`]. + /// The contents of `buf` are unspecified in this case. + /// + /// If any other read error is encountered then this function immediately + /// returns. The contents of `buf` are unspecified in this case. + /// + /// If this function returns an error, it is unspecified how many bytes it + /// has read, but it will never read more than would be necessary to + /// completely fill the buffer. + /// + /// [`ErrorKind::Interrupted`]: ../../../../std/io/enum.ErrorKind.html#variant.Interrupted + /// [`ErrorKind::UnexpectedEof`]: ../../../../std/io/enum.ErrorKind.html#variant.UnexpectedEof + #[stable(feature = "rw_exact_all_at", since = "1.33.0")] + fn read_exact_at(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> { + while !buf.is_empty() { + match self.read_at(buf, offset) { + Ok(0) => break, + Ok(n) => { + let tmp = buf; + buf = &mut tmp[n..]; + offset += n as u64; + } + Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} + Err(e) => return Err(e), + } + } + if !buf.is_empty() { + Err(io::Error::new(io::ErrorKind::UnexpectedEof, "failed to fill whole buffer")) + } else { + Ok(()) + } + } + /// Writes a number of bytes starting from a given offset. /// /// Returns the number of bytes written. @@ -45,6 +97,48 @@ pub trait FileExt { /// [`File::write`]: ../../../../std/fs/struct.File.html#method.write_vectored fn write_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result; + /// Attempts to write an entire buffer starting from a given offset. + /// + /// The offset is relative to the start of the file and thus independent + /// from the current cursor. + /// + /// The current file cursor is not affected by this function. + /// + /// This method will continuously call [`write_at`] until there is no more data + /// to be written or an error of non-[`ErrorKind::Interrupted`] kind is + /// returned. This method will not return until the entire buffer has been + /// successfully written or such an error occurs. The first error that is + /// not of [`ErrorKind::Interrupted`] kind generated from this method will be + /// returned. + /// + /// # Errors + /// + /// This function will return the first error of + /// non-[`ErrorKind::Interrupted`] kind that [`write_at`] returns. + /// + /// [`ErrorKind::Interrupted`]: ../../../../std/io/enum.ErrorKind.html#variant.Interrupted + /// [`write_at`]: #tymethod.write_at + #[stable(feature = "rw_exact_all_at", since = "1.33.0")] + fn write_all_at(&self, mut buf: &[u8], mut offset: u64) -> io::Result<()> { + while !buf.is_empty() { + match self.write_at(buf, offset) { + Ok(0) => { + return Err(io::Error::new( + io::ErrorKind::WriteZero, + "failed to write whole buffer", + )); + } + Ok(n) => { + buf = &buf[n..]; + offset += n as u64 + } + Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} + Err(e) => return Err(e), + } + } + Ok(()) + } + /// Returns the current position within the file. /// /// This corresponds to the `fd_tell` syscall and is similar to From 58fc61b79cd15fdafad6e15991553f5c33422ade Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Tue, 7 Jul 2020 15:56:07 -0700 Subject: [PATCH 2/2] Make WASI's FileExt's read_at/write_at consistent with other targets. Rename the existing read_at/write_at to read_vectored_at/write_vectored_at, for consistency with libstd's read_vectored/write_vectored. And, introduce new read_at/write_at functions which take a single buffer, similar to all other targets which provide these functions, so this will make it easier for applications to share code between WASI and other targets. Note that WASI's FileExt is currently unstable. --- src/libstd/sys/wasi/ext/fs.rs | 47 ++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/src/libstd/sys/wasi/ext/fs.rs b/src/libstd/sys/wasi/ext/fs.rs index 10ea4fd34bda3..f41c6626ccf12 100644 --- a/src/libstd/sys/wasi/ext/fs.rs +++ b/src/libstd/sys/wasi/ext/fs.rs @@ -12,6 +12,24 @@ use crate::sys_common::{AsInner, AsInnerMut, FromInner}; /// /// [`File`]: ../../../../std/fs/struct.File.html pub trait FileExt { + /// Reads a number of bytes starting from a given offset. + /// + /// Returns the number of bytes read. + /// + /// The offset is relative to the start of the file and thus independent + /// from the current cursor. + /// + /// The current file cursor is not affected by this function. + /// + /// Note that similar to [`File::read`], it is not an error to return with a + /// short read. + /// + /// [`File::read`]: ../../../../std/fs/struct.File.html#method.read + fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result { + let bufs = &mut [IoSliceMut::new(buf)]; + self.read_vectored_at(bufs, offset) + } + /// Reads a number of bytes starting from a given offset. /// /// Returns the number of bytes read. @@ -25,7 +43,7 @@ pub trait FileExt { /// return with a short read. /// /// [`File::read`]: ../../../../std/fs/struct.File.html#method.read_vectored - fn read_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result; + fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result; /// Reads the exact number of byte required to fill `buf` from the given offset. /// @@ -79,6 +97,27 @@ pub trait FileExt { } } + /// Writes a number of bytes starting from a given offset. + /// + /// Returns the number of bytes written. + /// + /// The offset is relative to the start of the file and thus independent + /// from the current cursor. + /// + /// The current file cursor is not affected by this function. + /// + /// When writing beyond the end of the file, the file is appropriately + /// extended and the intermediate bytes are initialized with the value 0. + /// + /// Note that similar to [`File::write`], it is not an error to return a + /// short write. + /// + /// [`File::write`]: ../../../../std/fs/struct.File.html#write.v + fn write_at(&self, buf: &[u8], offset: u64) -> io::Result { + let bufs = &[IoSlice::new(buf)]; + self.write_vectored_at(bufs, offset) + } + /// Writes a number of bytes starting from a given offset. /// /// Returns the number of bytes written. @@ -95,7 +134,7 @@ pub trait FileExt { /// short write. /// /// [`File::write`]: ../../../../std/fs/struct.File.html#method.write_vectored - fn write_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result; + fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result; /// Attempts to write an entire buffer starting from a given offset. /// @@ -199,11 +238,11 @@ pub trait FileExt { // FIXME: bind random_get maybe? - on crates.io for unix impl FileExt for fs::File { - fn read_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result { + fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result { self.as_inner().fd().pread(bufs, offset) } - fn write_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result { + fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result { self.as_inner().fd().pwrite(bufs, offset) }