Skip to content

Commit 0ad2154

Browse files
authored
Add OpenOption extension flags for sync, dsync, rsync, and nofollow. (#292)
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.
1 parent 86784bf commit 0ad2154

File tree

6 files changed

+204
-1
lines changed

6 files changed

+204
-1
lines changed

cap-fs-ext/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ mod is_file_read_write;
1717
mod metadata_ext;
1818
mod open_options_follow_ext;
1919
mod open_options_maybe_dir_ext;
20+
mod open_options_sync_ext;
2021
mod reopen;
2122

2223
pub use dir_entry_ext::DirEntryExt;
@@ -28,6 +29,7 @@ pub use is_file_read_write::IsFileReadWrite;
2829
pub use metadata_ext::MetadataExt;
2930
pub use open_options_follow_ext::OpenOptionsFollowExt;
3031
pub use open_options_maybe_dir_ext::OpenOptionsMaybeDirExt;
32+
pub use open_options_sync_ext::OpenOptionsSyncExt;
3133
pub use reopen::Reopen;
3234

3335
/// Re-export these to allow them to be used with `Reuse`.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/// Extension trait for `cap_primitives::fs::OpenOptions` which adds
2+
/// `sync`, `dsync`, `rsync`, and `nonblock` functions for controlling various
3+
/// I/O modes for the opened file.
4+
pub trait OpenOptionsSyncExt {
5+
/// Requests write operations complete as defined by synchronized I/O file
6+
/// integrity completion.
7+
fn sync(&mut self, enable: bool) -> &mut Self;
8+
9+
/// Requests write operations complete as defined by synchronized I/O data
10+
/// integrity completion.
11+
fn dsync(&mut self, enable: bool) -> &mut Self;
12+
13+
/// Requests read operations complete as defined by the level of integrity
14+
/// specified by `sync` and `dsync`.
15+
fn rsync(&mut self, enable: bool) -> &mut Self;
16+
17+
/// Requests that I/O operations fail with `std::io::ErrorKind::WouldBlock`
18+
/// if they would otherwise block.
19+
///
20+
/// This option is commonly not implemented for regular files, so blocking
21+
/// may still occur.
22+
fn nonblock(&mut self, enable: bool) -> &mut Self;
23+
}
24+
25+
impl OpenOptionsSyncExt for cap_primitives::fs::OpenOptions {
26+
#[inline]
27+
fn sync(&mut self, enable: bool) -> &mut Self {
28+
// `sync` functionality is implemented within `cap_primitives`;
29+
// we're just exposing it here since `OpenOptions` is re-exported by
30+
// `cap_std` etc. and `sync` isn't in `std`.
31+
self._cap_fs_ext_sync(enable)
32+
}
33+
34+
#[inline]
35+
fn dsync(&mut self, enable: bool) -> &mut Self {
36+
// `dsync` functionality is implemented within `cap_primitives`;
37+
// we're just exposing it here since `OpenOptions` is re-exported by
38+
// `cap_std` etc. and `dsync` isn't in `std`.
39+
self._cap_fs_ext_dsync(enable)
40+
}
41+
42+
#[inline]
43+
fn rsync(&mut self, enable: bool) -> &mut Self {
44+
// `rsync` functionality is implemented within `cap_primitives`;
45+
// we're just exposing it here since `OpenOptions` is re-exported by
46+
// `cap_std` etc. and `rsync` isn't in `std`.
47+
self._cap_fs_ext_rsync(enable)
48+
}
49+
50+
#[inline]
51+
fn nonblock(&mut self, enable: bool) -> &mut Self {
52+
// `nonblock` functionality is implemented within `cap_primitives`;
53+
// we're just exposing it here since `OpenOptions` is re-exported by
54+
// `cap_std` etc. and `nonblock` isn't in `std`.
55+
self._cap_fs_ext_nonblock(enable)
56+
}
57+
}

cap-primitives/src/fs/open_options.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ pub struct OpenOptions {
2525
pub(crate) create_new: bool,
2626
pub(crate) dir_required: bool,
2727
pub(crate) maybe_dir: bool,
28+
pub(crate) sync: bool,
29+
pub(crate) dsync: bool,
30+
pub(crate) rsync: bool,
31+
pub(crate) nonblock: bool,
2832
pub(crate) readdir_required: bool,
2933
pub(crate) follow: FollowSymlinks,
3034

@@ -48,6 +52,10 @@ impl OpenOptions {
4852
create_new: false,
4953
dir_required: false,
5054
maybe_dir: false,
55+
sync: false,
56+
dsync: false,
57+
rsync: false,
58+
nonblock: false,
5159
readdir_required: false,
5260
follow: FollowSymlinks::Yes,
5361

@@ -133,6 +141,34 @@ impl OpenOptions {
133141
self
134142
}
135143

144+
/// Sets the option to enable fixme
145+
#[inline]
146+
pub(crate) fn sync(&mut self, enable: bool) -> &mut Self {
147+
self.sync = enable;
148+
self
149+
}
150+
151+
/// Sets the option to enable fixme
152+
#[inline]
153+
pub(crate) fn dsync(&mut self, enable: bool) -> &mut Self {
154+
self.dsync = enable;
155+
self
156+
}
157+
158+
/// Sets the option to enable fixme
159+
#[inline]
160+
pub(crate) fn rsync(&mut self, enable: bool) -> &mut Self {
161+
self.rsync = enable;
162+
self
163+
}
164+
165+
/// Sets the option to enable fixme
166+
#[inline]
167+
pub(crate) fn nonblock(&mut self, enable: bool) -> &mut Self {
168+
self.nonblock = enable;
169+
self
170+
}
171+
136172
/// Sets the option to request the ability to read directory entries.
137173
#[inline]
138174
pub(crate) fn readdir_required(&mut self, readdir_required: bool) -> &mut Self {
@@ -161,6 +197,50 @@ impl OpenOptions {
161197
pub fn _cap_fs_ext_maybe_dir(&mut self, maybe_dir: bool) -> &mut Self {
162198
self.maybe_dir(maybe_dir)
163199
}
200+
201+
/// Wrapper to allow `sync` to be exposed by the `cap-fs-ext` crate.
202+
///
203+
/// This is hidden from the main API since this functionality isn't present
204+
/// in `std`. Use `cap_fs_ext::OpenOptionsSyncExt` instead of
205+
/// calling this directly.
206+
#[doc(hidden)]
207+
#[inline]
208+
pub fn _cap_fs_ext_sync(&mut self, enable: bool) -> &mut Self {
209+
self.sync(enable)
210+
}
211+
212+
/// Wrapper to allow `dsync` to be exposed by the `cap-fs-ext` crate.
213+
///
214+
/// This is hidden from the main API since this functionality isn't present
215+
/// in `std`. Use `cap_fs_ext::OpenOptionsSyncExt` instead of
216+
/// calling this directly.
217+
#[doc(hidden)]
218+
#[inline]
219+
pub fn _cap_fs_ext_dsync(&mut self, enable: bool) -> &mut Self {
220+
self.dsync(enable)
221+
}
222+
223+
/// Wrapper to allow `rsync` to be exposed by the `cap-fs-ext` crate.
224+
///
225+
/// This is hidden from the main API since this functionality isn't present
226+
/// in `std`. Use `cap_fs_ext::OpenOptionsSyncExt` instead of
227+
/// calling this directly.
228+
#[doc(hidden)]
229+
#[inline]
230+
pub fn _cap_fs_ext_rsync(&mut self, enable: bool) -> &mut Self {
231+
self.rsync(enable)
232+
}
233+
234+
/// Wrapper to allow `nonblock` to be exposed by the `cap-fs-ext` crate.
235+
///
236+
/// This is hidden from the main API since this functionality isn't present
237+
/// in `std`. Use `cap_fs_ext::OpenOptionsSyncExt` instead of
238+
/// calling this directly.
239+
#[doc(hidden)]
240+
#[inline]
241+
pub fn _cap_fs_ext_nonblock(&mut self, enable: bool) -> &mut Self {
242+
self.nonblock(enable)
243+
}
164244
}
165245

166246
#[cfg(unix)]
@@ -292,6 +372,10 @@ impl arbitrary::Arbitrary<'_> for OpenOptions {
292372
.create_new(<bool as Arbitrary>::arbitrary(u)?)
293373
.dir_required(<bool as Arbitrary>::arbitrary(u)?)
294374
.maybe_dir(<bool as Arbitrary>::arbitrary(u)?)
375+
.sync(<bool as Arbitrary>::arbitrary(u)?)
376+
.dsync(<bool as Arbitrary>::arbitrary(u)?)
377+
.rsync(<bool as Arbitrary>::arbitrary(u)?)
378+
.nonblock(<bool as Arbitrary>::arbitrary(u)?)
295379
.readdir_required(<bool as Arbitrary>::arbitrary(u)?)
296380
.follow(<FollowSymlinks as Arbitrary>::arbitrary(u)?)
297381
.clone())

cap-primitives/src/rustix/fs/oflags.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,33 @@ pub(in super::super) fn compute_oflags(options: &OpenOptions) -> io::Result<OFla
99
if options.follow == FollowSymlinks::No {
1010
oflags |= OFlags::NOFOLLOW;
1111
}
12+
if options.sync {
13+
oflags |= OFlags::SYNC;
14+
}
15+
if options.dsync {
16+
#[cfg(not(any(target_os = "freebsd",)))]
17+
{
18+
oflags |= OFlags::DSYNC;
19+
}
20+
21+
// Where needed, approximate `DSYNC` with `SYNC`.
22+
#[cfg(any(target_os = "freebsd",))]
23+
{
24+
oflags |= OFlags::SYNC;
25+
}
26+
}
27+
#[cfg(not(any(
28+
target_os = "ios",
29+
target_os = "macos",
30+
target_os = "freebsd",
31+
target_os = "fuchsia"
32+
)))]
33+
if options.rsync {
34+
oflags |= OFlags::RSYNC;
35+
}
36+
if options.nonblock {
37+
oflags |= OFlags::NONBLOCK;
38+
}
1239
if options.dir_required {
1340
oflags |= OFlags::DIRECTORY;
1441

cap-primitives/src/windows/fs/oflags.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ use crate::fs::{FollowSymlinks, OpenOptions};
22
use std::fs;
33
use std::os::windows::fs::OpenOptionsExt;
44
use windows_sys::Win32::Storage::FileSystem::{
5-
FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_DELETE,
5+
FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_FLAG_WRITE_THROUGH,
6+
FILE_SHARE_DELETE,
67
};
78

89
/// Translate the given `cap_std` into `std` options. Also return a bool
@@ -34,6 +35,11 @@ pub(in super::super) fn open_options_to_std(opts: &OpenOptions) -> (fs::OpenOpti
3435
// lookups on Windows.
3536
share_mode &= !FILE_SHARE_DELETE;
3637
}
38+
// This matches system-interface's `set_fd_flags` interpretation of these
39+
// flags on Windows.
40+
if opts.sync || opts.dsync {
41+
custom_flags |= FILE_FLAG_WRITE_THROUGH;
42+
}
3743
let mut std_opts = fs::OpenOptions::new();
3844
std_opts
3945
.read(opts.read)

tests/fs_additional.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,33 @@ fn maybe_dir() {
899899
check!(tmpdir.open_with("dir", OpenOptions::new().read(true).maybe_dir(true)));
900900
}
901901

902+
#[test]
903+
fn sync() {
904+
use cap_fs_ext::OpenOptionsSyncExt;
905+
906+
let tmpdir = tmpdir();
907+
check!(tmpdir.create("file"));
908+
909+
check!(tmpdir.open_with("file", OpenOptions::new().write(true).sync(true)));
910+
check!(tmpdir.open_with("file", OpenOptions::new().write(true).dsync(true)));
911+
check!(tmpdir.open_with(
912+
"file",
913+
OpenOptions::new()
914+
.read(true)
915+
.write(true)
916+
.sync(true)
917+
.rsync(true)
918+
));
919+
check!(tmpdir.open_with(
920+
"file",
921+
OpenOptions::new()
922+
.read(true)
923+
.write(true)
924+
.dsync(true)
925+
.rsync(true)
926+
));
927+
}
928+
902929
#[test]
903930
#[cfg(not(windows))]
904931
fn reopen_fd() {

0 commit comments

Comments
 (0)