Skip to content

Commit 42f4dd0

Browse files
committed
Implement RFC 1252 expanding the OpenOptions structure
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.
1 parent f1bcfdd commit 42f4dd0

File tree

6 files changed

+437
-177
lines changed

6 files changed

+437
-177
lines changed

src/libstd/fs.rs

Lines changed: 179 additions & 47 deletions
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, the contents of that file get overwritten, but it is
418+
/// not truncated.
419+
///
416420
/// # Examples
417421
///
418422
/// ```no_run
@@ -429,13 +433,29 @@ 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 atomic:
440+
/// no writes get mangled because another process writes at the same time.
441+
///
442+
/// One maybe obvious note when using append-mode: make sure that all data that
443+
/// belongs together, is written the the file in one operation. This can be done
444+
/// by concatenating strings before passing them to `write()`, or using a buffered
445+
/// writer (with a more than adequately sized buffer) and calling `flush()` when the
446+
/// message is complete.
447+
///
448+
/// If a file is opened with both read and append access, beware that after opening
449+
/// and after every write the position for reading may be set at the end of the file.
450+
/// So before writing save the current position (using `seek(SeekFrom::Current(0))`,
451+
/// and restore it before the next read.
432452
///
433453
/// # Examples
434454
///
435455
/// ```no_run
436456
/// use std::fs::OpenOptions;
437457
///
438-
/// let file = OpenOptions::new().write(true).append(true).open("foo.txt");
458+
/// let file = OpenOptions::new().append(true).open("foo.txt");
439459
/// ```
440460
#[stable(feature = "rust1", since = "1.0.0")]
441461
pub fn append(&mut self, append: bool) -> &mut OpenOptions {
@@ -447,6 +467,8 @@ impl OpenOptions {
447467
/// If a file is successfully opened with this option set it will truncate
448468
/// the file to 0 length if it already exists.
449469
///
470+
/// The file must be opened with write access for truncate to work.
471+
///
450472
/// # Examples
451473
///
452474
/// ```no_run
@@ -464,29 +486,92 @@ impl OpenOptions {
464486
/// This option indicates whether a new file will be created if the file
465487
/// does not yet already exist.
466488
///
489+
/// The file must be opened with write or append access in order to create
490+
/// a new file.
491+
///
467492
/// # Examples
468493
///
469494
/// ```no_run
470495
/// use std::fs::OpenOptions;
471496
///
472-
/// let file = OpenOptions::new().create(true).open("foo.txt");
497+
/// let file = OpenOptions::new().write(true).create(true).open("foo.txt");
473498
/// ```
474499
#[stable(feature = "rust1", since = "1.0.0")]
475500
pub fn create(&mut self, create: bool) -> &mut OpenOptions {
476501
self.0.create(create); self
477502
}
478503

504+
/// Sets the option to always create a new file.
505+
///
506+
/// This option indicates whether a new file will be created.
507+
/// No file is allowed to exist at the target location, also no (dangling)
508+
/// symlink.
509+
///
510+
/// if `.create_new(true)` is set, `.create()` and `.truncate()` are ignored.
511+
///
512+
/// The file must be opened with write or append access in order to create
513+
/// a new file.
514+
///
515+
/// # Examples
516+
///
517+
/// ```no_run
518+
/// use std::fs::OpenOptions;
519+
///
520+
/// let file = OpenOptions::new().write(true).create_new(true).open("foo.txt");
521+
/// ```
522+
#[stable(feature = "expand_open_options", since = "1.7.0")]
523+
pub fn create_new(&mut self, create_new: bool) -> &mut OpenOptions {
524+
self.0.create_new(create_new); self
525+
}
526+
527+
/// Pass custom open flags to the operating system.
528+
///
529+
/// Windows and the various flavours of Unix support flags that are not
530+
/// cross-platform, but that can be useful in some circumstances. On Unix they will
531+
/// be passed as the variable _flags_ to `open`, on Windows as the
532+
/// _dwFlagsAndAttributes_ parameter.
533+
///
534+
/// The cross-platform options of Rust can do magic: they can set any flag necessary
535+
/// to ensure it works as expected. For example, `.append(true)` on Unix not only
536+
/// sets the flag `O_APPEND`, but also automatically `O_WRONLY` or `O_RDWR`. This
537+
/// special treatment is not available for the custom flags.
538+
///
539+
/// Custom flags can only set flags, not remove flags set by Rusts options.
540+
///
541+
/// For the custom flags on Unix, the bits that define the access mode are masked
542+
/// out with `O_ACCMODE`, to ensure they do not interfere with the access mode set
543+
/// by Rusts options.
544+
///
545+
/// # Examples
546+
///
547+
/// ```rust,ignore
548+
/// extern crate libc;
549+
/// extern crate winapi;
550+
/// use std::fs::OpenOptions;
551+
///
552+
/// let options = OpenOptions::new().write(true);
553+
/// if cfg!(unix) { options.custom_flags(libc::O_NOFOLLOW); }
554+
/// if cfg!(windows) { options.custom_flags(winapi::FILE_FLAG_BACKUP_SEMANTICS); }
555+
/// let file = options.open("foo.txt");
556+
/// ```
557+
#[stable(feature = "expand_open_options", since = "1.7.0")]
558+
pub fn custom_flags(&mut self, flags: u32) -> &mut OpenOptions {
559+
self.0.custom_flags(flags); self
560+
}
561+
479562
/// Opens a file at `path` with the options specified by `self`.
480563
///
481564
/// # Errors
482565
///
483566
/// This function will return an error under a number of different
484567
/// circumstances, to include but not limited to:
485568
///
486-
/// * Opening a file that does not exist with read access.
569+
/// * Opening a file that does not exist without setting `create` or `create_new`.
487570
/// * Attempting to open a file with access that the user lacks
488571
/// permissions for
489572
/// * Filesystem-level errors (full disk, etc)
573+
/// * Invalid combinations of open options (truncate without write access,
574+
/// no access mode set, etc)
490575
///
491576
/// # Examples
492577
///
@@ -2098,61 +2183,108 @@ mod tests {
20982183

20992184
let mut r = OO::new(); r.read(true);
21002185
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-
2114-
check!(c(&rw).create(true).open(&tmpdir.join("c")));
2115-
assert!(tmpdir.join("c").exists());
2186+
let mut rw = OO::new(); rw.read(true).write(true);
2187+
let mut a = OO::new(); a.append(true);
2188+
let mut ra = OO::new(); ra.read(true).append(true);
2189+
2190+
let invalid_options = if cfg!(windows) { "The parameter is incorrect" }
2191+
else { "Invalid argument" };
2192+
2193+
// Test various combinations of creation modes and access modes.
2194+
//
2195+
// Allowed:
2196+
// creation mode | read | write | read-write | append | read-append |
2197+
// :-----------------------|:-----:|:-----:|:----------:|:------:|:-----------:|
2198+
// not set (open existing) | X | X | X | X | X |
2199+
// create | | X | X | X | X |
2200+
// truncate | | X | X | | |
2201+
// create and truncate | | X | X | | |
2202+
// create_new | | X | X | X | X |
2203+
//
2204+
// tested in reverse order, so 'create_new' creates the file, and 'open existing' opens it.
2205+
2206+
// write-only
2207+
check!(c(&w).create_new(true).open(&tmpdir.join("a")));
2208+
check!(c(&w).create(true).truncate(true).open(&tmpdir.join("a")));
2209+
check!(c(&w).truncate(true).open(&tmpdir.join("a")));
2210+
check!(c(&w).create(true).open(&tmpdir.join("a")));
2211+
check!(c(&w).open(&tmpdir.join("a")));
2212+
2213+
// read-only
2214+
error!(c(&r).create_new(true).open(&tmpdir.join("b")), invalid_options);
2215+
error!(c(&r).create(true).truncate(true).open(&tmpdir.join("b")), invalid_options);
2216+
error!(c(&r).truncate(true).open(&tmpdir.join("b")), invalid_options);
2217+
error!(c(&r).create(true).open(&tmpdir.join("b")), invalid_options);
2218+
check!(c(&r).open(&tmpdir.join("a"))); // try opening the file created with write_only
2219+
2220+
// read-write
2221+
check!(c(&rw).create_new(true).open(&tmpdir.join("c")));
2222+
check!(c(&rw).create(true).truncate(true).open(&tmpdir.join("c")));
2223+
check!(c(&rw).truncate(true).open(&tmpdir.join("c")));
21162224
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()));
2225+
check!(c(&rw).open(&tmpdir.join("c")));
2226+
2227+
// append
2228+
check!(c(&a).create_new(true).open(&tmpdir.join("d")));
2229+
error!(c(&a).create(true).truncate(true).open(&tmpdir.join("d")), invalid_options);
2230+
error!(c(&a).truncate(true).open(&tmpdir.join("d")), invalid_options);
2231+
check!(c(&a).create(true).open(&tmpdir.join("d")));
2232+
check!(c(&a).open(&tmpdir.join("d")));
2233+
2234+
// read-append
2235+
check!(c(&ra).create_new(true).open(&tmpdir.join("e")));
2236+
error!(c(&ra).create(true).truncate(true).open(&tmpdir.join("e")), invalid_options);
2237+
error!(c(&ra).truncate(true).open(&tmpdir.join("e")), invalid_options);
2238+
check!(c(&ra).create(true).open(&tmpdir.join("e")));
2239+
check!(c(&ra).open(&tmpdir.join("e")));
2240+
2241+
// Test opening a file without setting an access mode
2242+
let mut blank = OO::new();
2243+
error!(blank.create(true).open(&tmpdir.join("f")), invalid_options);
2244+
2245+
// Test write works
2246+
check!(check!(File::create(&tmpdir.join("h"))).write("foobar".as_bytes()));
2247+
2248+
// Test write fails for read-only
21402249
check!(r.open(&tmpdir.join("h")));
21412250
{
21422251
let mut f = check!(r.open(&tmpdir.join("h")));
21432252
assert!(f.write("wut".as_bytes()).is_err());
21442253
}
2254+
2255+
// Test write overwrites
2256+
{
2257+
let mut f = check!(c(&w).open(&tmpdir.join("h")));
2258+
check!(f.write("baz".as_bytes()));
2259+
}
2260+
{
2261+
let mut f = check!(c(&r).open(&tmpdir.join("h")));
2262+
let mut b = vec![0; 6];
2263+
check!(f.read(&mut b));
2264+
assert_eq!(b, "bazbar".as_bytes());
2265+
}
2266+
2267+
// Test truncate works
2268+
{
2269+
let mut f = check!(c(&w).truncate(true).open(&tmpdir.join("h")));
2270+
check!(f.write("foo".as_bytes()));
2271+
}
2272+
assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 3);
2273+
2274+
// Test append works
21452275
assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 3);
21462276
{
2147-
let mut f = check!(c(&w).append(true).open(&tmpdir.join("h")));
2277+
let mut f = check!(c(&a).open(&tmpdir.join("h")));
21482278
check!(f.write("bar".as_bytes()));
21492279
}
21502280
assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 6);
2281+
2282+
// Test .append(true) equals .write(true).append(true)
21512283
{
2152-
let mut f = check!(c(&w).truncate(true).open(&tmpdir.join("h")));
2153-
check!(f.write("bar".as_bytes()));
2284+
let mut f = check!(c(&w).append(true).open(&tmpdir.join("h")));
2285+
check!(f.write("baz".as_bytes()));
21542286
}
2155-
assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 3);
2287+
assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 9);
21562288
}
21572289

21582290
#[test]

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ pub trait OpenOptionsExt {
9999
///
100100
/// If a new file is created as part of a `File::open_opts` call then this
101101
/// specified `mode` will be used as the permission bits for the new file.
102+
/// If no `mode` is set, the default of `0o666` will be used.
103+
/// The operating system masks out bits with the systems `umask`, to produce
104+
/// the final permissions.
102105
#[stable(feature = "fs_ext", since = "1.1.0")]
103106
fn mode(&mut self, mode: raw::mode_t) -> &mut Self;
104107
}

0 commit comments

Comments
 (0)