From 515a28eda76242125e97e3ae230b0b479e1c79d9 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 30 Jan 2023 12:56:35 -0800 Subject: [PATCH] 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. --- cap-fs-ext/src/lib.rs | 2 + cap-fs-ext/src/open_options_sync_ext.rs | 57 +++++++++++++++++ cap-primitives/src/fs/open_options.rs | 84 +++++++++++++++++++++++++ cap-primitives/src/rustix/fs/oflags.rs | 27 ++++++++ cap-primitives/src/windows/fs/oflags.rs | 8 ++- tests/fs_additional.rs | 27 ++++++++ 6 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 cap-fs-ext/src/open_options_sync_ext.rs diff --git a/cap-fs-ext/src/lib.rs b/cap-fs-ext/src/lib.rs index 8298b40c..6cad2cc3 100644 --- a/cap-fs-ext/src/lib.rs +++ b/cap-fs-ext/src/lib.rs @@ -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; @@ -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`. diff --git a/cap-fs-ext/src/open_options_sync_ext.rs b/cap-fs-ext/src/open_options_sync_ext.rs new file mode 100644 index 00000000..eb4c12f9 --- /dev/null +++ b/cap-fs-ext/src/open_options_sync_ext.rs @@ -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) + } +} diff --git a/cap-primitives/src/fs/open_options.rs b/cap-primitives/src/fs/open_options.rs index 89dc750e..ea86f86a 100644 --- a/cap-primitives/src/fs/open_options.rs +++ b/cap-primitives/src/fs/open_options.rs @@ -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, @@ -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, @@ -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 { @@ -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)] @@ -292,6 +372,10 @@ impl arbitrary::Arbitrary<'_> for OpenOptions { .create_new(::arbitrary(u)?) .dir_required(::arbitrary(u)?) .maybe_dir(::arbitrary(u)?) + .sync(::arbitrary(u)?) + .dsync(::arbitrary(u)?) + .rsync(::arbitrary(u)?) + .nonblock(::arbitrary(u)?) .readdir_required(::arbitrary(u)?) .follow(::arbitrary(u)?) .clone()) diff --git a/cap-primitives/src/rustix/fs/oflags.rs b/cap-primitives/src/rustix/fs/oflags.rs index bfdcbad2..6c12296a 100644 --- a/cap-primitives/src/rustix/fs/oflags.rs +++ b/cap-primitives/src/rustix/fs/oflags.rs @@ -9,6 +9,33 @@ pub(in super::super) fn compute_oflags(options: &OpenOptions) -> io::Result (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) diff --git a/tests/fs_additional.rs b/tests/fs_additional.rs index a09b7a3d..465c861f 100644 --- a/tests/fs_additional.rs +++ b/tests/fs_additional.rs @@ -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() {