diff --git a/Cargo.toml b/Cargo.toml index 9d7ed23a0..a6b133c3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ std = [ "futures-channel", "async-channel", "async-lock", + "async-trait", ] alloc = [ "futures-core/alloc", @@ -77,6 +78,7 @@ pin-project-lite = { version = "0.2.0", optional = true } pin-utils = { version = "0.1.0-alpha.4", optional = true } slab = { version = "0.4.2", optional = true } async-channel = { version = "1.5.1", optional = true } +async-trait = { version = "0.1.42", optional = true } # Devdepencency, but they are not allowed to be optional :/ surf = { version = "2.0.0", optional = true } diff --git a/src/fs/file.rs b/src/fs/file.rs index e8cad7ad7..d32d4b547 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -903,4 +903,70 @@ mod tests { assert_eq!(len as u64, file.metadata().await.unwrap().len()); }); } + + #[cfg(target_os = "windows")] + #[test] + fn async_file_win_openext() { + use super::os::windows::fs::OpenOptionsExt; + const FILE_FLAG_NO_BUFFERING: u32 = 0x2000_0000; + const FILE_FLAG_RANDOM_ACCESS: u32 = 0x1000_0000; + + crate::task::block_on(async move { + OpenOptions::new() + .read(true) + .write(true) + .create_new(true) + .custom_flags(FILE_FLAG_NO_BUFFERING | FILE_FLAG_RANDOM_ACCESS) + .open(file!()).await.unwrap(); + }); + } + + #[cfg(target_os = "unix")] + #[test] + fn async_file_unix_openext() { + use super::os::unix::fs::OpenOptionsExt; + const O_DIRECT: i32 = 0o0_040_000; + + crate::task::block_on(async move { + OpenOptions::new() + .read(true) + .write(true) + .create_new(true) + .custom_flags(O_DIRECT) + .open(file!()) + .await + .unwrap(); + }); + } + + #[cfg(target_os = "windows")] + #[test] + fn async_file_win_positional_io() { + use super::os::windows::fs::FileExt; + + crate::task::block_on(async move { + let file = File::open(file!()).await.unwrap(); + assert_eq!(10u64, file.seek_write(&[5u8; 10], 10u64).await.unwrap()); + + let mut buf: [u8; 20]; + assert_eq!(20u64, file.seek_read(&buf, 0)).await.unwrap(); + assert_eq!(buf.iter(), [0u8; 10].iter().chain([5u8; 10].iter())); + }); + } + + #[cfg(target_os = "unix")] + #[test] + fn async_file_unix_positional_io() { + use super::os::unix::fs::FileExt; + + crate::task::block_on(async move { + let file = File::open(file!()).await.unwrap(); + assert_eq!(10u64, file.write_all_at(&[5u8; 10], 10u64).await.unwrap()); + + let mut buf: [u8; 20]; + assert_eq!(20u64, file.read_exact_at(&buf, 0)).await.unwrap(); + assert_eq!(buf.iter(), [0u8; 10].iter().chain([5u8; 10].iter())); + }); + + } } diff --git a/src/fs/open_options.rs b/src/fs/open_options.rs index 91ad8cab5..9e310e4f1 100644 --- a/src/fs/open_options.rs +++ b/src/fs/open_options.rs @@ -298,9 +298,9 @@ impl Default for OpenOptions { } cfg_unix! { - use crate::os::unix::fs::OpenOptionsExt; + use crate::os::unix::fs::OpenOptionsExt as UnixOpenOptionsExt; - impl OpenOptionsExt for OpenOptions { + impl UnixOpenOptionsExt for OpenOptions { fn mode(&mut self, mode: u32) -> &mut Self { self.0.mode(mode); self @@ -312,3 +312,36 @@ cfg_unix! { } } } + +cfg_unstable_default! { +cfg_windows! { + use crate::os::windows::fs::OpenOptionsExt as WindowsOpenOptionsExt; + + impl WindowsOpenOptionsExt for OpenOptions { + fn access_mode(&mut self, access: u32) -> &mut OpenOptions { + self.0.access_mode(access); + self + } + + fn share_mode(&mut self, share: u32) -> &mut OpenOptions { + self.0.share_mode(share); + self + } + + fn custom_flags(&mut self, flags: u32) -> &mut OpenOptions { + self.0.custom_flags(flags); + self + } + + fn attributes(&mut self, attributes: u32) -> &mut OpenOptions { + self.0.attributes(attributes); + self + } + + fn security_qos_flags(&mut self, flags: u32) -> &mut OpenOptions { + self.0.security_qos_flags(flags); + self + } + } +} +} diff --git a/src/os/unix/fs.rs b/src/os/unix/fs.rs index 498b3a97f..6bd7f244d 100644 --- a/src/os/unix/fs.rs +++ b/src/os/unix/fs.rs @@ -30,10 +30,12 @@ pub async fn symlink, Q: AsRef>(src: P, dst: Q) -> io::Resu } cfg_not_docs! { - pub use std::os::unix::fs::{DirBuilderExt, DirEntryExt, OpenOptionsExt}; + pub use std::os::unix::fs::{DirBuilderExt, DirEntryExt, OpenOptionsExt, FileExt}; } cfg_docs! { + use async_trait::async_trait; + /// Unix-specific extensions to `DirBuilder`. pub trait DirBuilderExt { /// Sets the mode to create new directories with. This option defaults to @@ -68,4 +70,196 @@ cfg_docs! { /// This options overwrites any previously set custom flags. fn custom_flags(&mut self, flags: i32) -> &mut Self; } + + /// Unix-specific extensions to [`fs::File`]. + #[async_trait] + 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`]: fs::File::read + /// + /// # Examples + /// + /// ```no_run + /// use async_std::io; + /// use async_std::fs::File; + /// use async_std::os::unix::prelude::FileExt; + /// + /// async fn main() -> io::Result<()> { + /// let mut buf = [0u8; 8]; + /// let file = File::open("foo.txt").await?; + /// + /// // We now read 8 bytes from the offset 10. + /// let num_bytes_read = file.read_at(&mut buf, 10).await?; + /// println!("read {} bytes: {:?}", num_bytes_read, buf); + /// Ok(()) + /// } + /// ``` + async fn read_at(&self, buf: &mut [u8], 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 [`io::Read::read_exact`] but uses [`read_at`] instead of `read`. + /// + /// [`read_at`]: FileExt::read_at + /// + /// # Errors + /// + /// If this function encounters an error of the kind + /// [`io::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 [`io::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. + /// + /// # Examples + /// + /// ```no_run + /// use async_std::io; + /// use async_std::fs::File; + /// use async_std::os::unix::prelude::FileExt; + /// + /// async fn main() -> io::Result<()> { + /// let mut buf = [0u8; 8]; + /// let file = File::open("foo.txt").await?; + /// + /// // We now read exactly 8 bytes from the offset 10. + /// file.read_exact_at(&mut buf, 10).await?; + /// println!("read {} bytes: {:?}", buf.len(), buf); + /// Ok(()) + /// } + /// ``` + async fn read_exact_at(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> { + while !buf.is_empty() { + match self.read_at(buf, offset).await { + 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. + /// + /// 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`]: fs::File::write + /// + /// # Examples + /// + /// ```no_run + /// use async_std::fs::File; + /// use async_std::io; + /// use async_std::os::unix::prelude::FileExt; + /// + /// async fn main() -> io::Result<()> { + /// let file = File::open("foo.txt").await?; + /// + /// // We now write at the offset 10. + /// file.write_at(b"sushi", 10).await?; + /// Ok(()) + /// } + /// ``` + async fn write_at(&self, buf: &[u8], 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-[`io::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 [`io::ErrorKind::Interrupted`] kind generated from this method will be + /// returned. + /// + /// # Errors + /// + /// This function will return the first error of + /// non-[`io::ErrorKind::Interrupted`] kind that [`write_at`] returns. + /// + /// [`write_at`]: FileExt::write_at + /// + /// # Examples + /// + /// ```no_run + /// use async_std::fs::File; + /// use async_std::io; + /// use async_std::os::unix::prelude::FileExt; + /// + /// async fn main() -> io::Result<()> { + /// let file = File::open("foo.txt").await?; + /// + /// // We now write at the offset 10. + /// file.write_all_at(b"sushi", 10).await?; + /// Ok(()) + /// } + /// ``` + async fn write_all_at(&self, mut buf: &[u8], mut offset: u64) -> io::Result<()> { + while !buf.is_empty() { + match self.write_at(buf, offset).await { + 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(()) + } + } } diff --git a/src/os/windows/fs.rs b/src/os/windows/fs.rs index 243f3819d..19577dd50 100644 --- a/src/os/windows/fs.rs +++ b/src/os/windows/fs.rs @@ -53,3 +53,246 @@ pub async fn symlink_file, Q: AsRef>(src: P, dst: Q) -> io: let dst = dst.as_ref().to_owned(); spawn_blocking(move || std::os::windows::fs::symlink_file(&src, &dst)).await } + +cfg_not_docs! { + pub use std::os::windows::fs::{OpenOptionsExt, FileExt}; +} + +cfg_docs! { + use async_trait::async_trait; + + /// Windows-specific extensions to `OpenOptions`. + pub trait OpenOptionsExt { + /// Overrides the `dwDesiredAccess` argument to the call to [`CreateFile`] + /// with the specified value. + /// + /// This will override the `read`, `write`, and `append` flags on the + /// `OpenOptions` structure. This method provides fine-grained control over + /// the permissions to read, write and append data, attributes (like hidden + /// and system), and extended attributes. + /// + /// # Examples + /// + /// ```no_run + /// use async_std::fs::OpenOptions; + /// use async_std::os::windows::prelude::*; + /// + /// // Open without read and write permission, for example if you only need + /// // to call `stat` on the file + /// let file = OpenOptions::new().access_mode(0).open("foo.txt").await?; + /// ``` + /// + /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea + fn access_mode(&mut self, access: u32) -> &mut Self; + + /// Overrides the `dwShareMode` argument to the call to [`CreateFile`] with + /// the specified value. + /// + /// By default `share_mode` is set to + /// `FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE`. This allows + /// other processes to read, write, and delete/rename the same file + /// while it is open. Removing any of the flags will prevent other + /// processes from performing the corresponding operation until the file + /// handle is closed. + /// + /// # Examples + /// + /// ```no_run + /// use async_std::fs::OpenOptions; + /// use async_std::os::windows::prelude::*; + /// + /// // Do not allow others to read or modify this file while we have it open + /// // for writing. + /// let file = OpenOptions::new() + /// .write(true) + /// .share_mode(0) + /// .open("foo.txt") + /// .await?; + /// ``` + /// + /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea + fn share_mode(&mut self, val: u32) -> &mut Self; + + /// Sets extra flags for the `dwFileFlags` argument to the call to + /// [`CreateFile2`] to the specified value (or combines it with + /// `attributes` and `security_qos_flags` to set the `dwFlagsAndAttributes` + /// for [`CreateFile`]). + /// + /// Custom flags can only set flags, not remove flags set by Rust's options. + /// This option overwrites any previously set custom flags. + /// + /// # Examples + /// + /// ```no_run + /// # #[cfg(for_demonstration_only)] + /// extern crate winapi; + /// # mod winapi { pub const FILE_FLAG_DELETE_ON_CLOSE: u32 = 0x04000000; } + /// + /// use async_std::fs::OpenOptions; + /// use async_std::os::windows::prelude::*; + /// + /// let file = OpenOptions::new() + /// .create(true) + /// .write(true) + /// .custom_flags(winapi::FILE_FLAG_DELETE_ON_CLOSE) + /// .open("foo.txt") + /// .await?; + /// ``` + /// + /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea + /// [`CreateFile2`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2 + fn custom_flags(&mut self, flags: u32) -> &mut Self; + + /// Sets the `dwFileAttributes` argument to the call to [`CreateFile2`] to + /// the specified value (or combines it with `custom_flags` and + /// `security_qos_flags` to set the `dwFlagsAndAttributes` for + /// [`CreateFile`]). + /// + /// If a _new_ file is created because it does not yet exist and + /// `.create(true)` or `.create_new(true)` are specified, the new file is + /// given the attributes declared with `.attributes()`. + /// + /// If an _existing_ file is opened with `.create(true).truncate(true)`, its + /// existing attributes are preserved and combined with the ones declared + /// with `.attributes()`. + /// + /// In all other cases the attributes get ignored. + /// + /// # Examples + /// + /// ```no_run + /// # #[cfg(for_demonstration_only)] + /// extern crate winapi; + /// # mod winapi { pub const FILE_ATTRIBUTE_HIDDEN: u32 = 2; } + /// + /// use async_std::fs::OpenOptions; + /// use async_std::os::windows::prelude::*; + /// + /// let file = OpenOptions::new() + /// .write(true) + /// .create(true) + /// .attributes(winapi::FILE_ATTRIBUTE_HIDDEN) + /// .open("foo.txt") + /// .await?; + /// ``` + /// + /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea + /// [`CreateFile2`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2 + fn attributes(&mut self, val: u32) -> &mut Self; + + /// Sets the `dwSecurityQosFlags` argument to the call to [`CreateFile2`] to + /// the specified value (or combines it with `custom_flags` and `attributes` + /// to set the `dwFlagsAndAttributes` for [`CreateFile`]). + /// + /// By default `security_qos_flags` is not set. It should be specified when + /// opening a named pipe, to control to which degree a server process can + /// act on behalf of a client process (security impersonation level). + /// + /// When `security_qos_flags` is not set, a malicious program can gain the + /// elevated privileges of a privileged Rust process when it allows opening + /// user-specified paths, by tricking it into opening a named pipe. So + /// arguably `security_qos_flags` should also be set when opening arbitrary + /// paths. However the bits can then conflict with other flags, specifically + /// `FILE_FLAG_OPEN_NO_RECALL`. + /// + /// For information about possible values, see [Impersonation Levels] on the + /// Windows Dev Center site. The `SECURITY_SQOS_PRESENT` flag is set + /// automatically when using this method. + + /// # Examples + /// + /// ```no_run + /// # #[cfg(for_demonstration_only)] + /// extern crate winapi; + /// # mod winapi { pub const SECURITY_IDENTIFICATION: u32 = 0; } + /// use async_std::fs::OpenOptions; + /// use async_std::os::windows::prelude::*; + /// + /// let file = OpenOptions::new() + /// .write(true) + /// .create(true) + /// + /// // Sets the flag value to `SecurityIdentification`. + /// .security_qos_flags(winapi::SECURITY_IDENTIFICATION) + /// + /// .open(r"\\.\pipe\MyPipe") + /// .await?; + /// ``` + /// + /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea + /// [`CreateFile2`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2 + /// [Impersonation Levels]: + /// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-security_impersonation_level + fn security_qos_flags(&mut self, flags: u32) -> &mut Self; + } + + /// Windows-specific extensions to [`fs::File`]. + #[async_trait] + pub trait FileExt { + /// Seeks to a given position and reads a number of bytes. + /// + /// 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 cursor **is** affected by this + /// function, it is set to the end of the read. + /// + /// Reading beyond the end of the file will always return with a length of + /// 0\. + /// + /// Note that similar to `File::read`, it is not an error to return with a + /// short read. When returning from such a short read, the file pointer is + /// still updated. + /// + /// # Examples + /// + /// ```no_run + /// use async_std::io; + /// use async_std::fs::File; + /// use async_std::os::windows::prelude::*; + /// + /// fn main() -> io::Result<()> { + /// let mut file = File::open("foo.txt").await?; + /// let mut buffer = [0; 10]; + /// + /// // Read 10 bytes, starting 72 bytes from the + /// // start of the file. + /// file.seek_read(&mut buffer[..], 72).await?; + /// Ok(()) + /// } + /// ``` + async fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result; + + /// Seeks to a given position and writes a number of bytes. + /// + /// 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 cursor **is** affected by this + /// function, it is set to the end of the write. + /// + /// When writing beyond the end of the file, the file is appropriately + /// extended and the intermediate bytes are left uninitialized. + /// + /// Note that similar to `File::write`, it is not an error to return a + /// short write. When returning from such a short write, the file pointer + /// is still updated. + /// + /// # Examples + /// + /// ```no_run + /// use async_std::fs::File; + /// use async_std::os::windows::prelude::*; + /// + /// fn main() -> std::io::Result<()> { + /// let mut buffer = File::create("foo.txt").await?; + /// + /// // Write a byte string starting 72 bytes from + /// // the start of the file. + /// buffer.seek_write(b"some bytes", 72).await?; + /// Ok(()) + /// } + /// ``` + async fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result; + } +}