Skip to content

Commit

Permalink
Define new extension traits
Browse files Browse the repository at this point in the history
  • Loading branch information
Stjepan Glavina committed Oct 4, 2020
1 parent 6cc5c5f commit f4cc6e5
Show file tree
Hide file tree
Showing 2 changed files with 264 additions and 18 deletions.
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,9 @@ readme = "README.md"
[dependencies]
blocking = "1.0.0"
futures-lite = "1.2.0"

[target.'cfg(unix)'.dev-dependencies]
libc = "0.2.78"

[target.'cfg(windows)'.dev-dependencies]
winapi = "0.3.9"
276 changes: 258 additions & 18 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};

#[cfg(unix)]
use std::os::unix::fs::{DirEntryExt as _, OpenOptionsExt as _};

#[cfg(windows)]
use std::os::windows::fs::OpenOptionsExt as _;

use blocking::{unblock, Unblock};
use futures_lite::io::{AsyncRead, AsyncSeek, AsyncWrite, AsyncWriteExt};
use futures_lite::stream::Stream;
Expand Down Expand Up @@ -247,8 +253,7 @@ pub async fn read<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
///
/// let mut entries = async_fs::read_dir(".").await?;
///
/// while let Some(res) = entries.next().await {
/// let entry = res?;
/// while let Some(entry) = entries.try_next().await? {
/// println!("{}", entry.file_name().to_string_lossy());
/// }
/// # std::io::Result::Ok(()) });
Expand Down Expand Up @@ -326,8 +331,7 @@ impl DirEntry {
/// # futures_lite::future::block_on(async {
/// let mut dir = async_fs::read_dir(".").await?;
///
/// while let Some(res) = dir.next().await {
/// let entry = res?;
/// while let Some(entry) = dir.try_next().await? {
/// println!("{:?}", entry.path());
/// }
/// # std::io::Result::Ok(()) });
Expand Down Expand Up @@ -359,8 +363,7 @@ impl DirEntry {
/// # futures_lite::future::block_on(async {
/// let mut dir = async_fs::read_dir(".").await?;
///
/// while let Some(res) = dir.next().await {
/// let entry = res?;
/// while let Some(entry) = dir.try_next().await? {
/// println!("{:?}", entry.metadata().await?);
/// }
/// # std::io::Result::Ok(()) });
Expand Down Expand Up @@ -392,8 +395,7 @@ impl DirEntry {
/// # futures_lite::future::block_on(async {
/// let mut dir = async_fs::read_dir(".").await?;
///
/// while let Some(res) = dir.next().await {
/// let entry = res?;
/// while let Some(entry) = dir.try_next().await? {
/// println!("{:?}", entry.file_type().await?);
/// }
/// # std::io::Result::Ok(()) });
Expand All @@ -413,8 +415,7 @@ impl DirEntry {
/// # futures_lite::future::block_on(async {
/// let mut dir = async_fs::read_dir(".").await?;
///
/// while let Some(res) = dir.next().await {
/// let entry = res?;
/// while let Some(entry) = dir.try_next().await? {
/// println!("{}", entry.file_name().to_string_lossy());
/// }
/// # std::io::Result::Ok(()) });
Expand All @@ -437,7 +438,7 @@ impl Clone for DirEntry {
}

#[cfg(unix)]
impl std::os::unix::fs::DirEntryExt for DirEntry {
impl unix::DirEntryExt for DirEntry {
fn ino(&self) -> u64 {
self.0.ino()
}
Expand Down Expand Up @@ -764,7 +765,7 @@ impl DirBuilder {
}

#[cfg(unix)]
impl std::os::unix::fs::DirBuilderExt for DirBuilder {
impl unix::DirBuilderExt for DirBuilder {
fn mode(&mut self, mode: u32) -> &mut Self {
self.mode = Some(mode);
self
Expand Down Expand Up @@ -1428,7 +1429,7 @@ impl Default for OpenOptions {
}

#[cfg(unix)]
impl std::os::unix::fs::OpenOptionsExt for OpenOptions {
impl unix::OpenOptionsExt for OpenOptions {
fn mode(&mut self, mode: u32) -> &mut Self {
self.0.mode(mode);
self
Expand All @@ -1441,7 +1442,7 @@ impl std::os::unix::fs::OpenOptionsExt for OpenOptions {
}

#[cfg(windows)]
impl std::os::windows::fs::OpenOptionsExt for OpenOptions {
impl windows::OpenOptionsExt for OpenOptions {
fn access_mode(&mut self, access: u32) -> &mut Self {
self.0.access_mode(access);
self
Expand Down Expand Up @@ -1474,9 +1475,7 @@ pub mod unix {
use super::*;

#[doc(no_inline)]
pub use std::os::unix::fs::{
DirBuilderExt, DirEntryExt, FileTypeExt, MetadataExt, OpenOptionsExt, PermissionsExt,
};
pub use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};

/// Creates a new symbolic link on the filesystem.
///
Expand All @@ -1494,6 +1493,92 @@ pub mod unix {
let dst = dst.as_ref().to_owned();
unblock(move || std::os::unix::fs::symlink(&src, &dst)).await
}

/// Unix-specific extensions to [`DirBuilder`].
pub trait DirBuilderExt {
/// Sets the mode to create new directories with.
///
/// This option defaults to `0o777`.
///
/// # Examples
///
/// ```no_run
/// use async_fs::{DirBuilder, unix::DirBuilderExt};
///
/// let mut builder = DirBuilder::new();
/// builder.mode(0o755);
/// ```
fn mode(&mut self, mode: u32) -> &mut Self;
}

/// Unix-specific extension methods for [`DirEntry`].
pub trait DirEntryExt {
/// Returns the underlying `d_ino` field in the contained `dirent` structure.
///
/// # Examples
///
/// ```no_run
/// use async_fs::unix::DirEntryExt;
/// use futures_lite::stream::StreamExt;
///
/// # futures_lite::future::block_on(async {
/// let mut entries = async_fs::read_dir(".").await?;
///
/// while let Some(entry) = entries.try_next().await? {
/// println!("{:?}: {}", entry.file_name(), entry.ino());
/// }
/// # std::io::Result::Ok(()) });
/// ```
fn ino(&self) -> u64;
}

/// Unix-specific extensions to [`OpenOptions`].
pub trait OpenOptionsExt {
/// Sets the mode bits that a new file will be created with.
///
/// If a new file is created as part of an [`OpenOptions::open()`] call then this
/// specified `mode` will be used as the permission bits for the new file.
///
/// If no `mode` is set, the default of `0o666` will be used.
/// The operating system masks out bits with the system's `umask`, to produce
/// the final permissions.
///
/// # Examples
///
/// ```no_run
/// use async_fs::{OpenOptions, unix::OpenOptionsExt};
///
/// # futures_lite::future::block_on(async {
/// let mut options = OpenOptions::new();
/// // Read/write permissions for owner and read permissions for others.
/// options.mode(0o644);
/// let file = options.open("foo.txt").await?;
/// # std::io::Result::Ok(()) });
/// ```
fn mode(&mut self, mode: u32) -> &mut Self;

/// Passes custom flags to the `flags` argument of `open`.
///
/// The bits that define the access mode are masked out with `O_ACCMODE`, to
/// ensure they do not interfere with the access mode set by Rust's options.
///
/// Custom flags can only set flags, not remove flags set by Rust's options.
/// This options overwrites any previously set custom flags.
///
/// # Examples
///
/// ```no_run
/// use async_fs::{OpenOptions, unix::OpenOptionsExt};
///
/// # futures_lite::future::block_on(async {
/// let mut options = OpenOptions::new();
/// options.write(true);
/// options.custom_flags(libc::O_NOFOLLOW);
/// let file = options.open("foo.txt").await?;
/// # std::io::Result::Ok(()) });
/// ```
fn custom_flags(&mut self, flags: i32) -> &mut Self;
}
}

/// Windows-specific extensions.
Expand All @@ -1502,7 +1587,7 @@ pub mod windows {
use super::*;

#[doc(no_inline)]
pub use std::os::windows::fs::{MetadataExt, OpenOptionsExt};
pub use std::os::windows::fs::MetadataExt;

/// Creates a new directory symbolic link on the filesystem.
///
Expand Down Expand Up @@ -1537,4 +1622,159 @@ pub mod windows {
let dst = dst.as_ref().to_owned();
unblock(move || std::os::windows::fs::symlink_file(&src, &dst)).await
}

/// 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_fs::{OpenOptions, windows::OpenOptionsExt};
///
/// # futures_lite::future::block_on(async {
/// // 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?;
/// # std::io::Result::Ok(()) });
/// ```
///
/// [`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_fs::{OpenOptions, windows::OpenOptionsExt};
///
/// # futures_lite::future::block_on(async {
/// // 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?;
/// # std::io::Result::Ok(()) });
/// ```
///
/// [`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
/// use async_fs::{OpenOptions, windows::OpenOptionsExt};
///
/// # futures_lite::future::block_on(async {
/// let file = OpenOptions::new()
/// .create(true)
/// .write(true)
/// .custom_flags(winapi::um::winbase::FILE_FLAG_DELETE_ON_CLOSE)
/// .open("foo.txt")
/// .await?;
/// # std::io::Result::Ok(()) });
/// ```
///
/// [`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
/// use async_fs::{OpenOptions, windows::OpenOptionsExt};
///
/// # futures_lite::future::block_on(async {
/// let file = OpenOptions::new()
/// .write(true)
/// .create(true)
/// .attributes(winapi::um::winnt::FILE_ATTRIBUTE_HIDDEN)
/// .open("foo.txt")
/// .await?;
/// # std::io::Result::Ok(()) });
/// ```
///
/// [`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
/// use async_fs::{OpenOptions, windows::OpenOptionsExt};
///
/// # futures_lite::future::block_on(async {
/// let file = OpenOptions::new()
/// .write(true)
/// .create(true)
/// .security_qos_flags(winapi::um::winbase::SECURITY_IDENTIFICATION)
/// .open(r"\\.\pipe\MyPipe")
/// .await?;
/// # std::io::Result::Ok(()) });
/// ```
///
/// [`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;
}
}

2 comments on commit f4cc6e5

@taiki-e
Copy link
Collaborator

@taiki-e taiki-e commented on f4cc6e5 Oct 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stjepang: It may be preferable to seal these extension traits: rust-lang/rust#77089 (comment) (I'm not sure if the standard library actually can extend those traits in the future, but if they run crater and there is no breakage, breaking changes may be accepted.)

Copy link

@ghost ghost commented on f4cc6e5 Oct 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, although I really don't expect anyone to implement extension traits from async_fs... :)

Please sign in to comment.