Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Add OpenOption extension flags for sync, dsync, rsync, and nofollow.
This avoids the need to do a `set_fd_flags` afterward, which can be complex
to do due to Windows' need to reopen the file.

Fixes #146.
  • Loading branch information
sunfishcode committed Jan 30, 2023
commit 515a28eda76242125e97e3ae230b0b479e1c79d9
2 changes: 2 additions & 0 deletions cap-fs-ext/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod is_file_read_write;
mod metadata_ext;
mod open_options_follow_ext;
mod open_options_maybe_dir_ext;
mod open_options_sync_ext;
mod reopen;

pub use dir_entry_ext::DirEntryExt;
Expand All @@ -28,6 +29,7 @@ pub use is_file_read_write::IsFileReadWrite;
pub use metadata_ext::MetadataExt;
pub use open_options_follow_ext::OpenOptionsFollowExt;
pub use open_options_maybe_dir_ext::OpenOptionsMaybeDirExt;
pub use open_options_sync_ext::OpenOptionsSyncExt;
pub use reopen::Reopen;

/// Re-export these to allow them to be used with `Reuse`.
Expand Down
57 changes: 57 additions & 0 deletions cap-fs-ext/src/open_options_sync_ext.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/// Extension trait for `cap_primitives::fs::OpenOptions` which adds
/// `sync`, `dsync`, `rsync`, and `nonblock` functions for controlling various
/// I/O modes for the opened file.
pub trait OpenOptionsSyncExt {
/// Requests write operations complete as defined by synchronized I/O file
/// integrity completion.
fn sync(&mut self, enable: bool) -> &mut Self;

/// Requests write operations complete as defined by synchronized I/O data
/// integrity completion.
fn dsync(&mut self, enable: bool) -> &mut Self;

/// Requests read operations complete as defined by the level of integrity
/// specified by `sync` and `dsync`.
fn rsync(&mut self, enable: bool) -> &mut Self;

/// Requests that I/O operations fail with `std::io::ErrorKind::WouldBlock`
/// if they would otherwise block.
///
/// This option is commonly not implemented for regular files, so blocking
/// may still occur.
fn nonblock(&mut self, enable: bool) -> &mut Self;
}

impl OpenOptionsSyncExt for cap_primitives::fs::OpenOptions {
#[inline]
fn sync(&mut self, enable: bool) -> &mut Self {
// `sync` functionality is implemented within `cap_primitives`;
// we're just exposing it here since `OpenOptions` is re-exported by
// `cap_std` etc. and `sync` isn't in `std`.
self._cap_fs_ext_sync(enable)
}

#[inline]
fn dsync(&mut self, enable: bool) -> &mut Self {
// `dsync` functionality is implemented within `cap_primitives`;
// we're just exposing it here since `OpenOptions` is re-exported by
// `cap_std` etc. and `dsync` isn't in `std`.
self._cap_fs_ext_dsync(enable)
}

#[inline]
fn rsync(&mut self, enable: bool) -> &mut Self {
// `rsync` functionality is implemented within `cap_primitives`;
// we're just exposing it here since `OpenOptions` is re-exported by
// `cap_std` etc. and `rsync` isn't in `std`.
self._cap_fs_ext_rsync(enable)
}

#[inline]
fn nonblock(&mut self, enable: bool) -> &mut Self {
// `nonblock` functionality is implemented within `cap_primitives`;
// we're just exposing it here since `OpenOptions` is re-exported by
// `cap_std` etc. and `nonblock` isn't in `std`.
self._cap_fs_ext_nonblock(enable)
}
}
84 changes: 84 additions & 0 deletions cap-primitives/src/fs/open_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ pub struct OpenOptions {
pub(crate) create_new: bool,
pub(crate) dir_required: bool,
pub(crate) maybe_dir: bool,
pub(crate) sync: bool,
pub(crate) dsync: bool,
pub(crate) rsync: bool,
pub(crate) nonblock: bool,
pub(crate) readdir_required: bool,
pub(crate) follow: FollowSymlinks,

Expand All @@ -48,6 +52,10 @@ impl OpenOptions {
create_new: false,
dir_required: false,
maybe_dir: false,
sync: false,
dsync: false,
rsync: false,
nonblock: false,
readdir_required: false,
follow: FollowSymlinks::Yes,

Expand Down Expand Up @@ -133,6 +141,34 @@ impl OpenOptions {
self
}

/// Sets the option to enable fixme
#[inline]
pub(crate) fn sync(&mut self, enable: bool) -> &mut Self {
self.sync = enable;
self
}

/// Sets the option to enable fixme
#[inline]
pub(crate) fn dsync(&mut self, enable: bool) -> &mut Self {
self.dsync = enable;
self
}

/// Sets the option to enable fixme
#[inline]
pub(crate) fn rsync(&mut self, enable: bool) -> &mut Self {
self.rsync = enable;
self
}

/// Sets the option to enable fixme
#[inline]
pub(crate) fn nonblock(&mut self, enable: bool) -> &mut Self {
self.nonblock = enable;
self
}

/// Sets the option to request the ability to read directory entries.
#[inline]
pub(crate) fn readdir_required(&mut self, readdir_required: bool) -> &mut Self {
Expand Down Expand Up @@ -161,6 +197,50 @@ impl OpenOptions {
pub fn _cap_fs_ext_maybe_dir(&mut self, maybe_dir: bool) -> &mut Self {
self.maybe_dir(maybe_dir)
}

/// Wrapper to allow `sync` to be exposed by the `cap-fs-ext` crate.
///
/// This is hidden from the main API since this functionality isn't present
/// in `std`. Use `cap_fs_ext::OpenOptionsSyncExt` instead of
/// calling this directly.
#[doc(hidden)]
#[inline]
pub fn _cap_fs_ext_sync(&mut self, enable: bool) -> &mut Self {
self.sync(enable)
}

/// Wrapper to allow `dsync` to be exposed by the `cap-fs-ext` crate.
///
/// This is hidden from the main API since this functionality isn't present
/// in `std`. Use `cap_fs_ext::OpenOptionsSyncExt` instead of
/// calling this directly.
#[doc(hidden)]
#[inline]
pub fn _cap_fs_ext_dsync(&mut self, enable: bool) -> &mut Self {
self.dsync(enable)
}

/// Wrapper to allow `rsync` to be exposed by the `cap-fs-ext` crate.
///
/// This is hidden from the main API since this functionality isn't present
/// in `std`. Use `cap_fs_ext::OpenOptionsSyncExt` instead of
/// calling this directly.
#[doc(hidden)]
#[inline]
pub fn _cap_fs_ext_rsync(&mut self, enable: bool) -> &mut Self {
self.rsync(enable)
}

/// Wrapper to allow `nonblock` to be exposed by the `cap-fs-ext` crate.
///
/// This is hidden from the main API since this functionality isn't present
/// in `std`. Use `cap_fs_ext::OpenOptionsSyncExt` instead of
/// calling this directly.
#[doc(hidden)]
#[inline]
pub fn _cap_fs_ext_nonblock(&mut self, enable: bool) -> &mut Self {
self.nonblock(enable)
}
}

#[cfg(unix)]
Expand Down Expand Up @@ -292,6 +372,10 @@ impl arbitrary::Arbitrary<'_> for OpenOptions {
.create_new(<bool as Arbitrary>::arbitrary(u)?)
.dir_required(<bool as Arbitrary>::arbitrary(u)?)
.maybe_dir(<bool as Arbitrary>::arbitrary(u)?)
.sync(<bool as Arbitrary>::arbitrary(u)?)
.dsync(<bool as Arbitrary>::arbitrary(u)?)
.rsync(<bool as Arbitrary>::arbitrary(u)?)
.nonblock(<bool as Arbitrary>::arbitrary(u)?)
.readdir_required(<bool as Arbitrary>::arbitrary(u)?)
.follow(<FollowSymlinks as Arbitrary>::arbitrary(u)?)
.clone())
Expand Down
27 changes: 27 additions & 0 deletions cap-primitives/src/rustix/fs/oflags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,33 @@ pub(in super::super) fn compute_oflags(options: &OpenOptions) -> io::Result<OFla
if options.follow == FollowSymlinks::No {
oflags |= OFlags::NOFOLLOW;
}
if options.sync {
oflags |= OFlags::SYNC;
}
if options.dsync {
#[cfg(not(any(target_os = "freebsd",)))]
{
oflags |= OFlags::DSYNC;
}

// Where needed, approximate `DSYNC` with `SYNC`.
#[cfg(any(target_os = "freebsd",))]
{
oflags |= OFlags::SYNC;
}
}
#[cfg(not(any(
target_os = "ios",
target_os = "macos",
target_os = "freebsd",
target_os = "fuchsia"
)))]
if options.rsync {
oflags |= OFlags::RSYNC;
}
if options.nonblock {
oflags |= OFlags::NONBLOCK;
}
if options.dir_required {
oflags |= OFlags::DIRECTORY;

Expand Down
8 changes: 7 additions & 1 deletion cap-primitives/src/windows/fs/oflags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use crate::fs::{FollowSymlinks, OpenOptions};
use std::fs;
use std::os::windows::fs::OpenOptionsExt;
use windows_sys::Win32::Storage::FileSystem::{
FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_DELETE,
FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_FLAG_WRITE_THROUGH,
FILE_SHARE_DELETE,
};

/// Translate the given `cap_std` into `std` options. Also return a bool
Expand Down Expand Up @@ -34,6 +35,11 @@ pub(in super::super) fn open_options_to_std(opts: &OpenOptions) -> (fs::OpenOpti
// lookups on Windows.
share_mode &= !FILE_SHARE_DELETE;
}
// This matches system-interface's `set_fd_flags` interpretation of these
// flags on Windows.
if opts.sync || opts.dsync {
custom_flags |= FILE_FLAG_WRITE_THROUGH;
}
let mut std_opts = fs::OpenOptions::new();
std_opts
.read(opts.read)
Expand Down
27 changes: 27 additions & 0 deletions tests/fs_additional.rs
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,33 @@ fn maybe_dir() {
check!(tmpdir.open_with("dir", OpenOptions::new().read(true).maybe_dir(true)));
}

#[test]
fn sync() {
use cap_fs_ext::OpenOptionsSyncExt;

let tmpdir = tmpdir();
check!(tmpdir.create("file"));

check!(tmpdir.open_with("file", OpenOptions::new().write(true).sync(true)));
check!(tmpdir.open_with("file", OpenOptions::new().write(true).dsync(true)));
check!(tmpdir.open_with(
"file",
OpenOptions::new()
.read(true)
.write(true)
.sync(true)
.rsync(true)
));
check!(tmpdir.open_with(
"file",
OpenOptions::new()
.read(true)
.write(true)
.dsync(true)
.rsync(true)
));
}

#[test]
#[cfg(not(windows))]
fn reopen_fd() {
Expand Down