Skip to content

Commit 292af75

Browse files
committed
Auto merge of #30872 - pitdicker:expand_open_options, r=alexcrichton
Tracking issue: #30014 This implements the RFC and makes a few other changes. I have added a few extra tests, and made the Windows and Unix code as similar as possible. Part of the RFC mentions the unstable OpenOptionsExt trait on Windows (see #27720). I have added a few extra methods to future-proof it for CreateFile2.
2 parents c4c9628 + ae30294 commit 292af75

File tree

6 files changed

+480
-177
lines changed

6 files changed

+480
-177
lines changed

src/libstd/fs.rs

+162-47
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ impl<'a> Seek for &'a File {
375375
}
376376

377377
impl OpenOptions {
378-
/// Creates a blank net set of options ready for configuration.
378+
/// Creates a blank new set of options ready for configuration.
379379
///
380380
/// All options are initially set to `false`.
381381
///
@@ -384,7 +384,8 @@ impl OpenOptions {
384384
/// ```no_run
385385
/// use std::fs::OpenOptions;
386386
///
387-
/// let file = OpenOptions::new().open("foo.txt");
387+
/// let mut options = OpenOptions::new();
388+
/// let file = options.read(true).open("foo.txt");
388389
/// ```
389390
#[stable(feature = "rust1", since = "1.0.0")]
390391
pub fn new() -> OpenOptions {
@@ -413,6 +414,9 @@ impl OpenOptions {
413414
/// This option, when true, will indicate that the file should be
414415
/// `write`-able if opened.
415416
///
417+
/// If a file already exist, any write calls on the file will overwrite its
418+
/// contents, without truncating it.
419+
///
416420
/// # Examples
417421
///
418422
/// ```no_run
@@ -429,13 +433,30 @@ impl OpenOptions {
429433
///
430434
/// This option, when true, means that writes will append to a file instead
431435
/// of overwriting previous contents.
436+
/// Note that setting `.write(true).append(true)` has the same effect as
437+
/// setting only `.append(true)`.
438+
///
439+
/// For most filesystems the operating system guarantees all writes are
440+
/// atomic: no writes get mangled because another process writes at the same
441+
/// time.
442+
///
443+
/// One maybe obvious note when using append-mode: make sure that all data
444+
/// that belongs together, is written the the file in one operation. This
445+
/// can be done by concatenating strings before passing them to `write()`,
446+
/// or using a buffered writer (with a more than adequately sized buffer)
447+
/// and calling `flush()` when the message is complete.
448+
///
449+
/// If a file is opened with both read and append access, beware that after
450+
/// opening and after every write the position for reading may be set at the
451+
/// end of the file. So before writing save the current position (using
452+
/// `seek(SeekFrom::Current(0))`, and restore it before the next read.
432453
///
433454
/// # Examples
434455
///
435456
/// ```no_run
436457
/// use std::fs::OpenOptions;
437458
///
438-
/// let file = OpenOptions::new().write(true).append(true).open("foo.txt");
459+
/// let file = OpenOptions::new().append(true).open("foo.txt");
439460
/// ```
440461
#[stable(feature = "rust1", since = "1.0.0")]
441462
pub fn append(&mut self, append: bool) -> &mut OpenOptions {
@@ -447,6 +468,8 @@ impl OpenOptions {
447468
/// If a file is successfully opened with this option set it will truncate
448469
/// the file to 0 length if it already exists.
449470
///
471+
/// The file must be opened with write access for truncate to work.
472+
///
450473
/// # Examples
451474
///
452475
/// ```no_run
@@ -464,29 +487,68 @@ impl OpenOptions {
464487
/// This option indicates whether a new file will be created if the file
465488
/// does not yet already exist.
466489
///
490+
/// The file must be opened with write or append access in order to create
491+
/// a new file.
492+
///
467493
/// # Examples
468494
///
469495
/// ```no_run
470496
/// use std::fs::OpenOptions;
471497
///
472-
/// let file = OpenOptions::new().create(true).open("foo.txt");
498+
/// let file = OpenOptions::new().write(true).create(true).open("foo.txt");
473499
/// ```
474500
#[stable(feature = "rust1", since = "1.0.0")]
475501
pub fn create(&mut self, create: bool) -> &mut OpenOptions {
476502
self.0.create(create); self
477503
}
478504

505+
/// Sets the option to always create a new file.
506+
///
507+
/// This option indicates whether a new file will be created.
508+
/// No file is allowed to exist at the target location, also no (dangling)
509+
/// symlink.
510+
///
511+
/// This option is usefull because it as atomic. Otherwise between checking
512+
/// whether a file exists and creating a new one, the file may have been
513+
/// created by another process (a TOCTOU race condition / attack).
514+
///
515+
/// If `.create_new(true)` is set, `.create()` and `.truncate()` are
516+
/// ignored.
517+
///
518+
/// The file must be opened with write or append access in order to create
519+
/// a new file.
520+
///
521+
/// # Examples
522+
///
523+
/// ```no_run
524+
/// #![feature(expand_open_options)]
525+
/// use std::fs::OpenOptions;
526+
///
527+
/// let file = OpenOptions::new().write(true)
528+
/// .create_new(true)
529+
/// .open("foo.txt");
530+
/// ```
531+
#[unstable(feature = "expand_open_options",
532+
reason = "recently added",
533+
issue = "30014")]
534+
pub fn create_new(&mut self, create_new: bool) -> &mut OpenOptions {
535+
self.0.create_new(create_new); self
536+
}
537+
479538
/// Opens a file at `path` with the options specified by `self`.
480539
///
481540
/// # Errors
482541
///
483542
/// This function will return an error under a number of different
484543
/// circumstances, to include but not limited to:
485544
///
486-
/// * Opening a file that does not exist with read access.
545+
/// * Opening a file that does not exist without setting `create` or
546+
/// `create_new`.
487547
/// * Attempting to open a file with access that the user lacks
488548
/// permissions for
489549
/// * Filesystem-level errors (full disk, etc)
550+
/// * Invalid combinations of open options (truncate without write access,
551+
/// no access mode set, etc)
490552
///
491553
/// # Examples
492554
///
@@ -2098,61 +2160,114 @@ mod tests {
20982160

20992161
let mut r = OO::new(); r.read(true);
21002162
let mut w = OO::new(); w.write(true);
2101-
let mut rw = OO::new(); rw.write(true).read(true);
2102-
2103-
match r.open(&tmpdir.join("a")) {
2104-
Ok(..) => panic!(), Err(..) => {}
2105-
}
2106-
2107-
// Perform each one twice to make sure that it succeeds the second time
2108-
// (where the file exists)
2109-
check!(c(&w).create(true).open(&tmpdir.join("b")));
2110-
assert!(tmpdir.join("b").exists());
2111-
check!(c(&w).create(true).open(&tmpdir.join("b")));
2112-
check!(w.open(&tmpdir.join("b")));
2113-
2163+
let mut rw = OO::new(); rw.read(true).write(true);
2164+
let mut a = OO::new(); a.append(true);
2165+
let mut ra = OO::new(); ra.read(true).append(true);
2166+
2167+
let invalid_options = if cfg!(windows) { "The parameter is incorrect" }
2168+
else { "Invalid argument" };
2169+
2170+
// Test various combinations of creation modes and access modes.
2171+
//
2172+
// Allowed:
2173+
// creation mode | read | write | read-write | append | read-append |
2174+
// :-----------------------|:-----:|:-----:|:----------:|:------:|:-----------:|
2175+
// not set (open existing) | X | X | X | X | X |
2176+
// create | | X | X | X | X |
2177+
// truncate | | X | X | | |
2178+
// create and truncate | | X | X | | |
2179+
// create_new | | X | X | X | X |
2180+
//
2181+
// tested in reverse order, so 'create_new' creates the file, and 'open existing' opens it.
2182+
2183+
// write-only
2184+
check!(c(&w).create_new(true).open(&tmpdir.join("a")));
2185+
check!(c(&w).create(true).truncate(true).open(&tmpdir.join("a")));
2186+
check!(c(&w).truncate(true).open(&tmpdir.join("a")));
2187+
check!(c(&w).create(true).open(&tmpdir.join("a")));
2188+
check!(c(&w).open(&tmpdir.join("a")));
2189+
2190+
// read-only
2191+
error!(c(&r).create_new(true).open(&tmpdir.join("b")), invalid_options);
2192+
error!(c(&r).create(true).truncate(true).open(&tmpdir.join("b")), invalid_options);
2193+
error!(c(&r).truncate(true).open(&tmpdir.join("b")), invalid_options);
2194+
error!(c(&r).create(true).open(&tmpdir.join("b")), invalid_options);
2195+
check!(c(&r).open(&tmpdir.join("a"))); // try opening the file created with write_only
2196+
2197+
// read-write
2198+
check!(c(&rw).create_new(true).open(&tmpdir.join("c")));
2199+
check!(c(&rw).create(true).truncate(true).open(&tmpdir.join("c")));
2200+
check!(c(&rw).truncate(true).open(&tmpdir.join("c")));
21142201
check!(c(&rw).create(true).open(&tmpdir.join("c")));
2115-
assert!(tmpdir.join("c").exists());
2116-
check!(c(&rw).create(true).open(&tmpdir.join("c")));
2117-
check!(rw.open(&tmpdir.join("c")));
2118-
2119-
check!(c(&w).append(true).create(true).open(&tmpdir.join("d")));
2120-
assert!(tmpdir.join("d").exists());
2121-
check!(c(&w).append(true).create(true).open(&tmpdir.join("d")));
2122-
check!(c(&w).append(true).open(&tmpdir.join("d")));
2123-
2124-
check!(c(&rw).append(true).create(true).open(&tmpdir.join("e")));
2125-
assert!(tmpdir.join("e").exists());
2126-
check!(c(&rw).append(true).create(true).open(&tmpdir.join("e")));
2127-
check!(c(&rw).append(true).open(&tmpdir.join("e")));
2128-
2129-
check!(c(&w).truncate(true).create(true).open(&tmpdir.join("f")));
2130-
assert!(tmpdir.join("f").exists());
2131-
check!(c(&w).truncate(true).create(true).open(&tmpdir.join("f")));
2132-
check!(c(&w).truncate(true).open(&tmpdir.join("f")));
2133-
2134-
check!(c(&rw).truncate(true).create(true).open(&tmpdir.join("g")));
2135-
assert!(tmpdir.join("g").exists());
2136-
check!(c(&rw).truncate(true).create(true).open(&tmpdir.join("g")));
2137-
check!(c(&rw).truncate(true).open(&tmpdir.join("g")));
2138-
2139-
check!(check!(File::create(&tmpdir.join("h"))).write("foo".as_bytes()));
2202+
check!(c(&rw).open(&tmpdir.join("c")));
2203+
2204+
// append
2205+
check!(c(&a).create_new(true).open(&tmpdir.join("d")));
2206+
error!(c(&a).create(true).truncate(true).open(&tmpdir.join("d")), invalid_options);
2207+
error!(c(&a).truncate(true).open(&tmpdir.join("d")), invalid_options);
2208+
check!(c(&a).create(true).open(&tmpdir.join("d")));
2209+
check!(c(&a).open(&tmpdir.join("d")));
2210+
2211+
// read-append
2212+
check!(c(&ra).create_new(true).open(&tmpdir.join("e")));
2213+
error!(c(&ra).create(true).truncate(true).open(&tmpdir.join("e")), invalid_options);
2214+
error!(c(&ra).truncate(true).open(&tmpdir.join("e")), invalid_options);
2215+
check!(c(&ra).create(true).open(&tmpdir.join("e")));
2216+
check!(c(&ra).open(&tmpdir.join("e")));
2217+
2218+
// Test opening a file without setting an access mode
2219+
let mut blank = OO::new();
2220+
error!(blank.create(true).open(&tmpdir.join("f")), invalid_options);
2221+
2222+
// Test write works
2223+
check!(check!(File::create(&tmpdir.join("h"))).write("foobar".as_bytes()));
2224+
2225+
// Test write fails for read-only
21402226
check!(r.open(&tmpdir.join("h")));
21412227
{
21422228
let mut f = check!(r.open(&tmpdir.join("h")));
21432229
assert!(f.write("wut".as_bytes()).is_err());
21442230
}
2231+
2232+
// Test write overwrites
2233+
{
2234+
let mut f = check!(c(&w).open(&tmpdir.join("h")));
2235+
check!(f.write("baz".as_bytes()));
2236+
}
2237+
{
2238+
let mut f = check!(c(&r).open(&tmpdir.join("h")));
2239+
let mut b = vec![0; 6];
2240+
check!(f.read(&mut b));
2241+
assert_eq!(b, "bazbar".as_bytes());
2242+
}
2243+
2244+
// Test truncate works
2245+
{
2246+
let mut f = check!(c(&w).truncate(true).open(&tmpdir.join("h")));
2247+
check!(f.write("foo".as_bytes()));
2248+
}
2249+
assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 3);
2250+
2251+
// Test append works
21452252
assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 3);
21462253
{
2147-
let mut f = check!(c(&w).append(true).open(&tmpdir.join("h")));
2254+
let mut f = check!(c(&a).open(&tmpdir.join("h")));
21482255
check!(f.write("bar".as_bytes()));
21492256
}
21502257
assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 6);
2258+
2259+
// Test .append(true) equals .write(true).append(true)
21512260
{
2152-
let mut f = check!(c(&w).truncate(true).open(&tmpdir.join("h")));
2153-
check!(f.write("bar".as_bytes()));
2261+
let mut f = check!(c(&w).append(true).open(&tmpdir.join("h")));
2262+
check!(f.write("baz".as_bytes()));
21542263
}
2155-
assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 3);
2264+
assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 9);
2265+
}
2266+
2267+
#[test]
2268+
fn _assert_send_sync() {
2269+
fn _assert_send_sync<T: Send + Sync>() {}
2270+
_assert_send_sync::<OpenOptions>();
21562271
}
21572272

21582273
#[test]

src/libstd/sys/unix/ext/fs.rs

+34-1
Original file line numberDiff line numberDiff line change
@@ -118,15 +118,49 @@ pub trait OpenOptionsExt {
118118
///
119119
/// If a new file is created as part of a `File::open_opts` call then this
120120
/// specified `mode` will be used as the permission bits for the new file.
121+
/// If no `mode` is set, the default of `0o666` will be used.
122+
/// The operating system masks out bits with the systems `umask`, to produce
123+
/// the final permissions.
121124
#[stable(feature = "fs_ext", since = "1.1.0")]
122125
fn mode(&mut self, mode: raw::mode_t) -> &mut Self;
126+
127+
/// Pass custom flags to the `flags` agument of `open`.
128+
///
129+
/// The bits that define the access mode are masked out with `O_ACCMODE`, to
130+
/// ensure they do not interfere with the access mode set by Rusts options.
131+
///
132+
/// Custom flags can only set flags, not remove flags set by Rusts options.
133+
/// This options overwrites any previously set custom flags.
134+
///
135+
/// # Examples
136+
///
137+
/// ```rust,ignore
138+
/// extern crate libc;
139+
/// use std::fs::OpenOptions;
140+
/// use std::os::unix::fs::OpenOptionsExt;
141+
///
142+
/// let mut options = OpenOptions::new();
143+
/// options.write(true);
144+
/// if cfg!(unix) {
145+
/// options.custom_flags(libc::O_NOFOLLOW);
146+
/// }
147+
/// let file = options.open("foo.txt");
148+
/// ```
149+
#[unstable(feature = "expand_open_options",
150+
reason = "recently added",
151+
issue = "30014")]
152+
fn custom_flags(&mut self, flags: i32) -> &mut Self;
123153
}
124154

125155
#[stable(feature = "fs_ext", since = "1.1.0")]
126156
impl OpenOptionsExt for OpenOptions {
127157
fn mode(&mut self, mode: raw::mode_t) -> &mut OpenOptions {
128158
self.as_inner_mut().mode(mode); self
129159
}
160+
161+
fn custom_flags(&mut self, flags: i32) -> &mut OpenOptions {
162+
self.as_inner_mut().custom_flags(flags); self
163+
}
130164
}
131165

132166
// Hm, why are there casts here to the returned type, shouldn't the types always
@@ -281,4 +315,3 @@ impl DirBuilderExt for fs::DirBuilder {
281315
self
282316
}
283317
}
284-

0 commit comments

Comments
 (0)