about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2016-01-20 17:19:21 +0000
committerbors <bors@rust-lang.org>2016-01-20 17:19:21 +0000
commit292af75f8d69b3856f7cb1555654bad7b54a9cc4 (patch)
tree937bd35b65822cce760088ea50ec1b3906fd986a /src
parentc4c9628de7d4e970b2cb43d0f1a4c8f9ad03aca1 (diff)
parentae30294771e3c5c65a2d70be0e09b5bec2490c66 (diff)
downloadrust-292af75f8d69b3856f7cb1555654bad7b54a9cc4.tar.gz
rust-292af75f8d69b3856f7cb1555654bad7b54a9cc4.zip
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.
Diffstat (limited to 'src')
-rw-r--r--src/libstd/fs.rs209
-rw-r--r--src/libstd/sys/unix/ext/fs.rs35
-rw-r--r--src/libstd/sys/unix/fs.rs91
-rw-r--r--src/libstd/sys/windows/c.rs34
-rw-r--r--src/libstd/sys/windows/ext/fs.rs132
-rw-r--r--src/libstd/sys/windows/fs.rs156
6 files changed, 480 insertions, 177 deletions
diff --git a/src/libstd/fs.rs b/src/libstd/fs.rs
index 635ed91f35d..414a0ebd11f 100644
--- a/src/libstd/fs.rs
+++ b/src/libstd/fs.rs
@@ -375,7 +375,7 @@ impl<'a> Seek for &'a File {
 }
 
 impl OpenOptions {
-    /// Creates a blank net set of options ready for configuration.
+    /// Creates a blank new set of options ready for configuration.
     ///
     /// All options are initially set to `false`.
     ///
@@ -384,7 +384,8 @@ impl OpenOptions {
     /// ```no_run
     /// use std::fs::OpenOptions;
     ///
-    /// let file = OpenOptions::new().open("foo.txt");
+    /// let mut options = OpenOptions::new();
+    /// let file = options.read(true).open("foo.txt");
     /// ```
     #[stable(feature = "rust1", since = "1.0.0")]
     pub fn new() -> OpenOptions {
@@ -413,6 +414,9 @@ impl OpenOptions {
     /// This option, when true, will indicate that the file should be
     /// `write`-able if opened.
     ///
+    /// If a file already exist, any write calls on the file will overwrite its
+    /// contents, without truncating it.
+    ///
     /// # Examples
     ///
     /// ```no_run
@@ -429,13 +433,30 @@ impl OpenOptions {
     ///
     /// This option, when true, means that writes will append to a file instead
     /// of overwriting previous contents.
+    /// Note that setting `.write(true).append(true)` has the same effect as
+    /// setting only `.append(true)`.
+    ///
+    /// For most filesystems the operating system guarantees all writes are
+    /// atomic: no writes get mangled because another process writes at the same
+    /// time.
+    ///
+    /// One maybe obvious note when using append-mode: make sure that all data
+    /// that belongs together, is written the the file in one operation. This
+    /// can be done by concatenating strings before passing them to `write()`,
+    /// or using a buffered writer (with a more than adequately sized buffer)
+    /// and calling `flush()` when the message is complete.
+    ///
+    /// If a file is opened with both read and append access, beware that after
+    /// opening and after every write the position for reading may be set at the
+    /// end of the file. So before writing save the current position (using
+    /// `seek(SeekFrom::Current(0))`, and restore it before the next read.
     ///
     /// # Examples
     ///
     /// ```no_run
     /// use std::fs::OpenOptions;
     ///
-    /// let file = OpenOptions::new().write(true).append(true).open("foo.txt");
+    /// let file = OpenOptions::new().append(true).open("foo.txt");
     /// ```
     #[stable(feature = "rust1", since = "1.0.0")]
     pub fn append(&mut self, append: bool) -> &mut OpenOptions {
@@ -447,6 +468,8 @@ impl OpenOptions {
     /// If a file is successfully opened with this option set it will truncate
     /// the file to 0 length if it already exists.
     ///
+    /// The file must be opened with write access for truncate to work.
+    ///
     /// # Examples
     ///
     /// ```no_run
@@ -464,18 +487,54 @@ impl OpenOptions {
     /// This option indicates whether a new file will be created if the file
     /// does not yet already exist.
     ///
+    /// The file must be opened with write or append access in order to create
+    /// a new file.
+    ///
     /// # Examples
     ///
     /// ```no_run
     /// use std::fs::OpenOptions;
     ///
-    /// let file = OpenOptions::new().create(true).open("foo.txt");
+    /// let file = OpenOptions::new().write(true).create(true).open("foo.txt");
     /// ```
     #[stable(feature = "rust1", since = "1.0.0")]
     pub fn create(&mut self, create: bool) -> &mut OpenOptions {
         self.0.create(create); self
     }
 
+    /// Sets the option to always create a new file.
+    ///
+    /// This option indicates whether a new file will be created.
+    /// No file is allowed to exist at the target location, also no (dangling)
+    /// symlink.
+    ///
+    /// This option is usefull because it as atomic. Otherwise between checking
+    /// whether a file exists and creating a new one, the file may have been
+    /// created by another process (a TOCTOU race condition / attack).
+    ///
+    /// If `.create_new(true)` is set, `.create()` and `.truncate()` are
+    /// ignored.
+    ///
+    /// The file must be opened with write or append access in order to create
+    /// a new file.
+    ///
+    /// # Examples
+    ///
+    /// ```no_run
+    /// #![feature(expand_open_options)]
+    /// use std::fs::OpenOptions;
+    ///
+    /// let file = OpenOptions::new().write(true)
+    ///                              .create_new(true)
+    ///                              .open("foo.txt");
+    /// ```
+    #[unstable(feature = "expand_open_options",
+               reason = "recently added",
+               issue = "30014")]
+    pub fn create_new(&mut self, create_new: bool) -> &mut OpenOptions {
+        self.0.create_new(create_new); self
+    }
+
     /// Opens a file at `path` with the options specified by `self`.
     ///
     /// # Errors
@@ -483,10 +542,13 @@ impl OpenOptions {
     /// This function will return an error under a number of different
     /// circumstances, to include but not limited to:
     ///
-    /// * Opening a file that does not exist with read access.
+    /// * Opening a file that does not exist without setting `create` or
+    ///   `create_new`.
     /// * Attempting to open a file with access that the user lacks
     ///   permissions for
     /// * Filesystem-level errors (full disk, etc)
+    /// * Invalid combinations of open options (truncate without write access,
+    ///   no access mode set, etc)
     ///
     /// # Examples
     ///
@@ -2098,61 +2160,114 @@ mod tests {
 
         let mut r = OO::new(); r.read(true);
         let mut w = OO::new(); w.write(true);
-        let mut rw = OO::new(); rw.write(true).read(true);
-
-        match r.open(&tmpdir.join("a")) {
-            Ok(..) => panic!(), Err(..) => {}
-        }
-
-        // Perform each one twice to make sure that it succeeds the second time
-        // (where the file exists)
-        check!(c(&w).create(true).open(&tmpdir.join("b")));
-        assert!(tmpdir.join("b").exists());
-        check!(c(&w).create(true).open(&tmpdir.join("b")));
-        check!(w.open(&tmpdir.join("b")));
-
+        let mut rw = OO::new(); rw.read(true).write(true);
+        let mut a = OO::new(); a.append(true);
+        let mut ra = OO::new(); ra.read(true).append(true);
+
+        let invalid_options = if cfg!(windows) { "The parameter is incorrect" }
+                              else { "Invalid argument" };
+
+        // Test various combinations of creation modes and access modes.
+        //
+        // Allowed:
+        // creation mode           | read  | write | read-write | append | read-append |
+        // :-----------------------|:-----:|:-----:|:----------:|:------:|:-----------:|
+        // not set (open existing) |   X   |   X   |     X      |   X    |      X      |
+        // create                  |       |   X   |     X      |   X    |      X      |
+        // truncate                |       |   X   |     X      |        |             |
+        // create and truncate     |       |   X   |     X      |        |             |
+        // create_new              |       |   X   |     X      |   X    |      X      |
+        //
+        // tested in reverse order, so 'create_new' creates the file, and 'open existing' opens it.
+
+        // write-only
+        check!(c(&w).create_new(true).open(&tmpdir.join("a")));
+        check!(c(&w).create(true).truncate(true).open(&tmpdir.join("a")));
+        check!(c(&w).truncate(true).open(&tmpdir.join("a")));
+        check!(c(&w).create(true).open(&tmpdir.join("a")));
+        check!(c(&w).open(&tmpdir.join("a")));
+
+        // read-only
+        error!(c(&r).create_new(true).open(&tmpdir.join("b")), invalid_options);
+        error!(c(&r).create(true).truncate(true).open(&tmpdir.join("b")), invalid_options);
+        error!(c(&r).truncate(true).open(&tmpdir.join("b")), invalid_options);
+        error!(c(&r).create(true).open(&tmpdir.join("b")), invalid_options);
+        check!(c(&r).open(&tmpdir.join("a"))); // try opening the file created with write_only
+
+        // read-write
+        check!(c(&rw).create_new(true).open(&tmpdir.join("c")));
+        check!(c(&rw).create(true).truncate(true).open(&tmpdir.join("c")));
+        check!(c(&rw).truncate(true).open(&tmpdir.join("c")));
         check!(c(&rw).create(true).open(&tmpdir.join("c")));
-        assert!(tmpdir.join("c").exists());
-        check!(c(&rw).create(true).open(&tmpdir.join("c")));
-        check!(rw.open(&tmpdir.join("c")));
-
-        check!(c(&w).append(true).create(true).open(&tmpdir.join("d")));
-        assert!(tmpdir.join("d").exists());
-        check!(c(&w).append(true).create(true).open(&tmpdir.join("d")));
-        check!(c(&w).append(true).open(&tmpdir.join("d")));
-
-        check!(c(&rw).append(true).create(true).open(&tmpdir.join("e")));
-        assert!(tmpdir.join("e").exists());
-        check!(c(&rw).append(true).create(true).open(&tmpdir.join("e")));
-        check!(c(&rw).append(true).open(&tmpdir.join("e")));
-
-        check!(c(&w).truncate(true).create(true).open(&tmpdir.join("f")));
-        assert!(tmpdir.join("f").exists());
-        check!(c(&w).truncate(true).create(true).open(&tmpdir.join("f")));
-        check!(c(&w).truncate(true).open(&tmpdir.join("f")));
-
-        check!(c(&rw).truncate(true).create(true).open(&tmpdir.join("g")));
-        assert!(tmpdir.join("g").exists());
-        check!(c(&rw).truncate(true).create(true).open(&tmpdir.join("g")));
-        check!(c(&rw).truncate(true).open(&tmpdir.join("g")));
-
-        check!(check!(File::create(&tmpdir.join("h"))).write("foo".as_bytes()));
+        check!(c(&rw).open(&tmpdir.join("c")));
+
+        // append
+        check!(c(&a).create_new(true).open(&tmpdir.join("d")));
+        error!(c(&a).create(true).truncate(true).open(&tmpdir.join("d")), invalid_options);
+        error!(c(&a).truncate(true).open(&tmpdir.join("d")), invalid_options);
+        check!(c(&a).create(true).open(&tmpdir.join("d")));
+        check!(c(&a).open(&tmpdir.join("d")));
+
+        // read-append
+        check!(c(&ra).create_new(true).open(&tmpdir.join("e")));
+        error!(c(&ra).create(true).truncate(true).open(&tmpdir.join("e")), invalid_options);
+        error!(c(&ra).truncate(true).open(&tmpdir.join("e")), invalid_options);
+        check!(c(&ra).create(true).open(&tmpdir.join("e")));
+        check!(c(&ra).open(&tmpdir.join("e")));
+
+        // Test opening a file without setting an access mode
+        let mut blank = OO::new();
+         error!(blank.create(true).open(&tmpdir.join("f")), invalid_options);
+
+        // Test write works
+        check!(check!(File::create(&tmpdir.join("h"))).write("foobar".as_bytes()));
+
+        // Test write fails for read-only
         check!(r.open(&tmpdir.join("h")));
         {
             let mut f = check!(r.open(&tmpdir.join("h")));
             assert!(f.write("wut".as_bytes()).is_err());
         }
+
+        // Test write overwrites
+        {
+            let mut f = check!(c(&w).open(&tmpdir.join("h")));
+            check!(f.write("baz".as_bytes()));
+        }
+        {
+            let mut f = check!(c(&r).open(&tmpdir.join("h")));
+            let mut b = vec![0; 6];
+            check!(f.read(&mut b));
+            assert_eq!(b, "bazbar".as_bytes());
+        }
+
+        // Test truncate works
+        {
+            let mut f = check!(c(&w).truncate(true).open(&tmpdir.join("h")));
+            check!(f.write("foo".as_bytes()));
+        }
+        assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 3);
+
+        // Test append works
         assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 3);
         {
-            let mut f = check!(c(&w).append(true).open(&tmpdir.join("h")));
+            let mut f = check!(c(&a).open(&tmpdir.join("h")));
             check!(f.write("bar".as_bytes()));
         }
         assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 6);
+
+        // Test .append(true) equals .write(true).append(true)
         {
-            let mut f = check!(c(&w).truncate(true).open(&tmpdir.join("h")));
-            check!(f.write("bar".as_bytes()));
+            let mut f = check!(c(&w).append(true).open(&tmpdir.join("h")));
+            check!(f.write("baz".as_bytes()));
         }
-        assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 3);
+        assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 9);
+    }
+
+    #[test]
+    fn _assert_send_sync() {
+        fn _assert_send_sync<T: Send + Sync>() {}
+        _assert_send_sync::<OpenOptions>();
     }
 
     #[test]
diff --git a/src/libstd/sys/unix/ext/fs.rs b/src/libstd/sys/unix/ext/fs.rs
index 8d21ba8f661..2a3117864d0 100644
--- a/src/libstd/sys/unix/ext/fs.rs
+++ b/src/libstd/sys/unix/ext/fs.rs
@@ -118,8 +118,38 @@ pub trait OpenOptionsExt {
     ///
     /// If a new file is created as part of a `File::open_opts` call then this
     /// specified `mode` will be used as the permission bits for the new file.
+    /// If no `mode` is set, the default of `0o666` will be used.
+    /// The operating system masks out bits with the systems `umask`, to produce
+    /// the final permissions.
     #[stable(feature = "fs_ext", since = "1.1.0")]
     fn mode(&mut self, mode: raw::mode_t) -> &mut Self;
+
+    /// Pass custom flags to the `flags` agument of `open`.
+    ///
+    /// The bits that define the access mode are masked out with `O_ACCMODE`, to
+    /// ensure they do not interfere with the access mode set by Rusts options.
+    ///
+    /// Custom flags can only set flags, not remove flags set by Rusts options.
+    /// This options overwrites any previously set custom flags.
+    ///
+    /// # Examples
+    ///
+    /// ```rust,ignore
+    /// extern crate libc;
+    /// use std::fs::OpenOptions;
+    /// use std::os::unix::fs::OpenOptionsExt;
+    ///
+    /// let mut options = OpenOptions::new();
+    /// options.write(true);
+    /// if cfg!(unix) {
+    ///     options.custom_flags(libc::O_NOFOLLOW);
+    /// }
+    /// let file = options.open("foo.txt");
+    /// ```
+    #[unstable(feature = "expand_open_options",
+               reason = "recently added",
+               issue = "30014")]
+    fn custom_flags(&mut self, flags: i32) -> &mut Self;
 }
 
 #[stable(feature = "fs_ext", since = "1.1.0")]
@@ -127,6 +157,10 @@ impl OpenOptionsExt for OpenOptions {
     fn mode(&mut self, mode: raw::mode_t) -> &mut OpenOptions {
         self.as_inner_mut().mode(mode); self
     }
+
+    fn custom_flags(&mut self, flags: i32) -> &mut OpenOptions {
+        self.as_inner_mut().custom_flags(flags); self
+    }
 }
 
 // Hm, why are there casts here to the returned type, shouldn't the types always
@@ -281,4 +315,3 @@ impl DirBuilderExt for fs::DirBuilder {
         self
     }
 }
-
diff --git a/src/libstd/sys/unix/fs.rs b/src/libstd/sys/unix/fs.rs
index 10fda3fcd7f..d2d2ce35d84 100644
--- a/src/libstd/sys/unix/fs.rs
+++ b/src/libstd/sys/unix/fs.rs
@@ -50,9 +50,15 @@ pub struct DirEntry {
 
 #[derive(Clone)]
 pub struct OpenOptions {
-    flags: c_int,
+    // generic
     read: bool,
     write: bool,
+    append: bool,
+    truncate: bool,
+    create: bool,
+    create_new: bool,
+    // system-specific
+    custom_flags: i32,
     mode: mode_t,
 }
 
@@ -233,43 +239,60 @@ impl DirEntry {
 impl OpenOptions {
     pub fn new() -> OpenOptions {
         OpenOptions {
-            flags: libc::O_CLOEXEC,
+            // generic
             read: false,
             write: false,
+            append: false,
+            truncate: false,
+            create: false,
+            create_new: false,
+            // system-specific
+            custom_flags: 0,
             mode: 0o666,
         }
     }
 
-    pub fn read(&mut self, read: bool) {
-        self.read = read;
-    }
-
-    pub fn write(&mut self, write: bool) {
-        self.write = write;
-    }
-
-    pub fn append(&mut self, append: bool) {
-        self.flag(libc::O_APPEND, append);
-    }
-
-    pub fn truncate(&mut self, truncate: bool) {
-        self.flag(libc::O_TRUNC, truncate);
-    }
-
-    pub fn create(&mut self, create: bool) {
-        self.flag(libc::O_CREAT, create);
-    }
-
-    pub fn mode(&mut self, mode: raw::mode_t) {
-        self.mode = mode as mode_t;
+    pub fn read(&mut self, read: bool) { self.read = read; }
+    pub fn write(&mut self, write: bool) { self.write = write; }
+    pub fn append(&mut self, append: bool) { self.append = append; }
+    pub fn truncate(&mut self, truncate: bool) { self.truncate = truncate; }
+    pub fn create(&mut self, create: bool) { self.create = create; }
+    pub fn create_new(&mut self, create_new: bool) { self.create_new = create_new; }
+
+    pub fn custom_flags(&mut self, flags: i32) { self.custom_flags = flags; }
+    pub fn mode(&mut self, mode: raw::mode_t) { self.mode = mode as mode_t; }
+
+    fn get_access_mode(&self) -> io::Result<c_int> {
+        match (self.read, self.write, self.append) {
+            (true,  false, false) => Ok(libc::O_RDONLY),
+            (false, true,  false) => Ok(libc::O_WRONLY),
+            (true,  true,  false) => Ok(libc::O_RDWR),
+            (false, _,     true)  => Ok(libc::O_WRONLY | libc::O_APPEND),
+            (true,  _,     true)  => Ok(libc::O_RDWR | libc::O_APPEND),
+            (false, false, false) => Err(Error::from_raw_os_error(libc::EINVAL)),
+        }
     }
 
-    fn flag(&mut self, bit: c_int, on: bool) {
-        if on {
-            self.flags |= bit;
-        } else {
-            self.flags &= !bit;
+    fn get_creation_mode(&self) -> io::Result<c_int> {
+        match (self.write, self.append) {
+            (true, false) => {}
+            (false, false) =>
+                if self.truncate || self.create || self.create_new {
+                    return Err(Error::from_raw_os_error(libc::EINVAL));
+                },
+            (_, true) =>
+                if self.truncate && !self.create_new {
+                    return Err(Error::from_raw_os_error(libc::EINVAL));
+                },
         }
+
+        Ok(match (self.create, self.truncate, self.create_new) {
+                (false, false, false) => 0,
+                (true,  false, false) => libc::O_CREAT,
+                (false, true,  false) => libc::O_TRUNC,
+                (true,  true,  false) => libc::O_CREAT | libc::O_TRUNC,
+                (_,      _,    true)  => libc::O_CREAT | libc::O_EXCL,
+           })
     }
 }
 
@@ -280,12 +303,10 @@ impl File {
     }
 
     pub fn open_c(path: &CStr, opts: &OpenOptions) -> io::Result<File> {
-        let flags = opts.flags | match (opts.read, opts.write) {
-            (true, true) => libc::O_RDWR,
-            (false, true) => libc::O_WRONLY,
-            (true, false) |
-            (false, false) => libc::O_RDONLY,
-        };
+        let flags = libc::O_CLOEXEC |
+                    try!(opts.get_access_mode()) |
+                    try!(opts.get_creation_mode()) |
+                    (opts.custom_flags as c_int & !libc::O_ACCMODE);
         let fd = try!(cvt_r(|| unsafe {
             libc::open(path.as_ptr(), flags, opts.mode as c_int)
         }));
diff --git a/src/libstd/sys/windows/c.rs b/src/libstd/sys/windows/c.rs
index 34e32d0d5b6..6e8090a2235 100644
--- a/src/libstd/sys/windows/c.rs
+++ b/src/libstd/sys/windows/c.rs
@@ -93,16 +93,30 @@ pub const CREATE_NEW: DWORD = 1;
 pub const OPEN_ALWAYS: DWORD = 4;
 pub const OPEN_EXISTING: DWORD = 3;
 pub const TRUNCATE_EXISTING: DWORD = 5;
-pub const FILE_APPEND_DATA: DWORD = 0x00000004;
+
 pub const FILE_READ_DATA: DWORD = 0x00000001;
 pub const FILE_WRITE_DATA: DWORD = 0x00000002;
-pub const STANDARD_RIGHTS_READ: DWORD = 0x20000;
-pub const STANDARD_RIGHTS_WRITE: DWORD = 0x20000;
-pub const FILE_WRITE_EA: DWORD = 0x00000010;
+pub const FILE_APPEND_DATA: DWORD = 0x00000004;
 pub const FILE_READ_EA: DWORD = 0x00000008;
-pub const SYNCHRONIZE: DWORD = 0x00100000;
-pub const FILE_WRITE_ATTRIBUTES: DWORD = 0x00000100;
+pub const FILE_WRITE_EA: DWORD = 0x00000010;
+pub const FILE_EXECUTE: DWORD = 0x00000020;
 pub const FILE_READ_ATTRIBUTES: DWORD = 0x00000080;
+pub const FILE_WRITE_ATTRIBUTES: DWORD = 0x00000100;
+
+pub const DELETE: DWORD = 0x00008000;
+pub const READ_CONTROL: DWORD = 0x00020000;
+pub const WRITE_DAC: DWORD = 0x00040000;
+pub const WRITE_OWNER: DWORD = 0x00080000;
+pub const SYNCHRONIZE: DWORD = 0x00100000;
+
+pub const GENERIC_READ: DWORD = 0x80000000;
+pub const GENERIC_WRITE: DWORD = 0x40000000;
+pub const GENERIC_EXECUTE: DWORD = 0x20000000;
+pub const GENERIC_ALL: DWORD = 0x10000000;
+
+pub const STANDARD_RIGHTS_READ: DWORD = READ_CONTROL;
+pub const STANDARD_RIGHTS_WRITE: DWORD = READ_CONTROL;
+pub const STANDARD_RIGHTS_EXECUTE: DWORD = READ_CONTROL;
 pub const FILE_GENERIC_READ: DWORD = STANDARD_RIGHTS_READ | FILE_READ_DATA |
                                      FILE_READ_ATTRIBUTES |
                                      FILE_READ_EA |
@@ -113,6 +127,14 @@ pub const FILE_GENERIC_WRITE: DWORD = STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA |
                                       FILE_APPEND_DATA |
                                       SYNCHRONIZE;
 
+pub const SECURITY_ANONYMOUS: DWORD = 0 << 16;
+pub const SECURITY_IDENTIFICATION: DWORD = 1 << 16;
+pub const SECURITY_IMPERSONATION: DWORD = 2 << 16;
+pub const SECURITY_DELEGATION: DWORD = 3 << 16;
+pub const SECURITY_CONTEXT_TRACKING: DWORD = 0x00040000;
+pub const SECURITY_EFFECTIVE_ONLY: DWORD = 0x00080000;
+pub const SECURITY_SQOS_PRESENT: DWORD = 0x00100000;
+
 #[repr(C)]
 #[derive(Copy)]
 pub struct WIN32_FIND_DATAW {
diff --git a/src/libstd/sys/windows/ext/fs.rs b/src/libstd/sys/windows/ext/fs.rs
index 0d78d4de42b..d060c902fba 100644
--- a/src/libstd/sys/windows/ext/fs.rs
+++ b/src/libstd/sys/windows/ext/fs.rs
@@ -25,45 +25,131 @@ use sys_common::{AsInnerMut, AsInner};
 pub trait OpenOptionsExt {
     /// Overrides the `dwDesiredAccess` argument to the call to `CreateFile`
     /// with the specified value.
-    fn desired_access(&mut self, access: u32) -> &mut Self;
-
-    /// Overrides the `dwCreationDisposition` argument to the call to
-    /// `CreateFile` with the specified value.
     ///
-    /// This will override any values of the standard `create` flags, for
-    /// example.
-    fn creation_disposition(&mut self, val: u32) -> &mut Self;
-
-    /// Overrides the `dwFlagsAndAttributes` argument to the call to
-    /// `CreateFile` with the specified value.
+    /// This will override the `read`, `write`, and `append` flags on the
+    /// `OpenOptions` structure. This method provides fine-grained control over
+    /// the permissions to read, write and append data, attributes (like hidden
+    /// and system) and extended attributes.
+    ///
+    /// # Examples
     ///
-    /// This will override any values of the standard flags on the
-    /// `OpenOptions` structure.
-    fn flags_and_attributes(&mut self, val: u32) -> &mut Self;
+    /// ```no_run
+    /// #![feature(open_options_ext)]
+    /// use std::fs::OpenOptions;
+    /// use std::os::windows::fs::OpenOptionsExt;
+    ///
+    /// // Open without read and write permission, for example if you only need
+    /// // to call `stat()` on the file
+    /// let file = OpenOptions::new().access_mode(0).open("foo.txt");
+    /// ```
+    fn access_mode(&mut self, access: u32) -> &mut Self;
 
     /// Overrides the `dwShareMode` argument to the call to `CreateFile` with
     /// the specified value.
     ///
-    /// This will override any values of the standard flags on the
-    /// `OpenOptions` structure.
+    /// By default `share_mode` is set to
+    /// `FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE`. Specifying
+    /// less permissions denies others to read from, write to and/or delete the
+    /// file while it is open.
+    ///
+    /// # Examples
+    ///
+    /// ```no_run
+    /// #![feature(open_options_ext)]
+    /// use std::fs::OpenOptions;
+    /// use std::os::windows::fs::OpenOptionsExt;
+    ///
+    /// // Do not allow others to read or modify this file while we have it open
+    /// // for writing
+    /// let file = OpenOptions::new().write(true)
+    ///                              .share_mode(0)
+    ///                              .open("foo.txt");
+    /// ```
     fn share_mode(&mut self, val: u32) -> &mut Self;
+
+    /// Sets extra flags for the `dwFileFlags` argument to the call to
+    /// `CreateFile2` (or combines it with `attributes` and `security_qos_flags`
+    /// to set the `dwFlagsAndAttributes` for `CreateFile`).
+    ///
+    /// Custom flags can only set flags, not remove flags set by Rusts options.
+    /// This options overwrites any previously set custom flags.
+    ///
+    /// # Examples
+    ///
+    /// ```rust,ignore
+    /// extern crate winapi;
+    /// use std::fs::OpenOptions;
+    /// use std::os::windows::fs::OpenOptionsExt;
+    ///
+    /// let mut options = OpenOptions::new();
+    /// options.create(true).write(true);
+    /// if cfg!(windows) {
+    ///     options.custom_flags(winapi::FILE_FLAG_DELETE_ON_CLOSE);
+    /// }
+    /// let file = options.open("foo.txt");
+    /// ```
+    #[unstable(feature = "expand_open_options",
+               reason = "recently added",
+               issue = "30014")]
+    fn custom_flags(&mut self, flags: u32) -> &mut Self;
+
+    /// Sets the `dwFileAttributes` argument to the call to `CreateFile2` to
+    /// the specified value (or combines it with `custom_flags` and
+    /// `security_qos_flags` to set the `dwFlagsAndAttributes` for
+    /// `CreateFile`).
+    ///
+    /// If a _new_ file is created because it does not yet exist and
+    ///`.create(true)` or `.create_new(true)` are specified, the new file is
+    /// given the attributes declared with `.attributes()`.
+    ///
+    /// If an _existing_ file is opened with `.create(true).truncate(true)`, its
+    /// existing attributes are preserved and combined with the ones declared
+    /// with `.attributes()`.
+    ///
+    /// In all other cases the attributes get ignored.
+    ///
+    /// # Examples
+    ///
+    /// ```rust,ignore
+    /// #![feature(open_options_ext)]
+    /// extern crate winapi;
+    /// use std::fs::OpenOptions;
+    /// use std::os::windows::fs::OpenOptionsExt;
+    ///
+    /// let file = OpenOptions::new().write(true).create(true)
+    ///                              .attributes(winapi::FILE_ATTRIBUTE_HIDDEN)
+    ///                              .open("foo.txt");
+    /// ```
+    fn attributes(&mut self, val: u32) -> &mut Self;
+
+    /// Sets the `dwSecurityQosFlags` argument to the call to `CreateFile2` to
+    /// the specified value (or combines it with `custom_flags` and `attributes`
+    /// to set the `dwFlagsAndAttributes` for `CreateFile`).
+    fn security_qos_flags(&mut self, flags: u32) -> &mut OpenOptions;
 }
 
 #[unstable(feature = "open_options_ext",
            reason = "may require more thought/methods",
            issue = "27720")]
 impl OpenOptionsExt for OpenOptions {
-    fn desired_access(&mut self, access: u32) -> &mut OpenOptions {
-        self.as_inner_mut().desired_access(access); self
+    fn access_mode(&mut self, access: u32) -> &mut OpenOptions {
+        self.as_inner_mut().access_mode(access); self
     }
-    fn creation_disposition(&mut self, access: u32) -> &mut OpenOptions {
-        self.as_inner_mut().creation_disposition(access); self
+
+    fn share_mode(&mut self, share: u32) -> &mut OpenOptions {
+        self.as_inner_mut().share_mode(share); self
     }
-    fn flags_and_attributes(&mut self, access: u32) -> &mut OpenOptions {
-        self.as_inner_mut().flags_and_attributes(access); self
+
+    fn custom_flags(&mut self, flags: u32) -> &mut OpenOptions {
+        self.as_inner_mut().custom_flags(flags); self
     }
-    fn share_mode(&mut self, access: u32) -> &mut OpenOptions {
-        self.as_inner_mut().share_mode(access); self
+
+    fn attributes(&mut self, attributes: u32) -> &mut OpenOptions {
+        self.as_inner_mut().attributes(attributes); self
+    }
+
+    fn security_qos_flags(&mut self, flags: u32) -> &mut OpenOptions {
+        self.as_inner_mut().security_qos_flags(flags); self
     }
 }
 
diff --git a/src/libstd/sys/windows/fs.rs b/src/libstd/sys/windows/fs.rs
index 9db7ab53459..60e3f7c22bd 100644
--- a/src/libstd/sys/windows/fs.rs
+++ b/src/libstd/sys/windows/fs.rs
@@ -54,18 +54,22 @@ pub struct DirEntry {
     data: c::WIN32_FIND_DATAW,
 }
 
-#[derive(Clone, Default)]
+#[derive(Clone)]
 pub struct OpenOptions {
-    create: bool,
-    append: bool,
+    // generic
     read: bool,
     write: bool,
+    append: bool,
     truncate: bool,
-    desired_access: Option<c::DWORD>,
-    share_mode: Option<c::DWORD>,
-    creation_disposition: Option<c::DWORD>,
-    flags_and_attributes: Option<c::DWORD>,
-    security_attributes: usize, // *mut T doesn't have a Default impl
+    create: bool,
+    create_new: bool,
+    // system-specific
+    custom_flags: u32,
+    access_mode: Option<c::DWORD>,
+    attributes: c::DWORD,
+    share_mode: c::DWORD,
+    security_qos_flags: c::DWORD,
+    security_attributes: usize, // FIXME: should be a reference
 }
 
 #[derive(Clone, PartialEq, Eq, Debug)]
@@ -151,68 +155,86 @@ impl DirEntry {
 }
 
 impl OpenOptions {
-    pub fn new() -> OpenOptions { Default::default() }
+    pub fn new() -> OpenOptions {
+        OpenOptions {
+            // generic
+            read: false,
+            write: false,
+            append: false,
+            truncate: false,
+            create: false,
+            create_new: false,
+            // system-specific
+            custom_flags: 0,
+            access_mode: None,
+            share_mode: c::FILE_SHARE_READ | c::FILE_SHARE_WRITE | c::FILE_SHARE_DELETE,
+            attributes: 0,
+            security_qos_flags: 0,
+            security_attributes: 0,
+        }
+    }
+
     pub fn read(&mut self, read: bool) { self.read = read; }
     pub fn write(&mut self, write: bool) { self.write = write; }
     pub fn append(&mut self, append: bool) { self.append = append; }
-    pub fn create(&mut self, create: bool) { self.create = create; }
     pub fn truncate(&mut self, truncate: bool) { self.truncate = truncate; }
-    pub fn creation_disposition(&mut self, val: u32) {
-        self.creation_disposition = Some(val);
-    }
-    pub fn flags_and_attributes(&mut self, val: u32) {
-        self.flags_and_attributes = Some(val);
-    }
-    pub fn desired_access(&mut self, val: u32) {
-        self.desired_access = Some(val);
-    }
-    pub fn share_mode(&mut self, val: u32) {
-        self.share_mode = Some(val);
-    }
+    pub fn create(&mut self, create: bool) { self.create = create; }
+    pub fn create_new(&mut self, create_new: bool) { self.create_new = create_new; }
+
+    pub fn custom_flags(&mut self, flags: u32) { self.custom_flags = flags; }
+    pub fn access_mode(&mut self, access_mode: u32) { self.access_mode = Some(access_mode); }
+    pub fn share_mode(&mut self, share_mode: u32) { self.share_mode = share_mode; }
+    pub fn attributes(&mut self, attrs: u32) { self.attributes = attrs; }
+    pub fn security_qos_flags(&mut self, flags: u32) { self.security_qos_flags = flags; }
     pub fn security_attributes(&mut self, attrs: c::LPSECURITY_ATTRIBUTES) {
         self.security_attributes = attrs as usize;
     }
 
-    fn get_desired_access(&self) -> c::DWORD {
-        self.desired_access.unwrap_or({
-            let mut base = if self.read {c::FILE_GENERIC_READ} else {0} |
-                           if self.write {c::FILE_GENERIC_WRITE} else {0};
-            if self.append {
-                base &= !c::FILE_WRITE_DATA;
-                base |= c::FILE_APPEND_DATA;
-            }
-            base
-        })
+    fn get_access_mode(&self) -> io::Result<c::DWORD> {
+        const ERROR_INVALID_PARAMETER: i32 = 87;
+
+        match (self.read, self.write, self.append, self.access_mode) {
+            (_, _, _, Some(mode)) => Ok(mode),
+            (true,  false, false, None) => Ok(c::GENERIC_READ),
+            (false, true,  false, None) => Ok(c::GENERIC_WRITE),
+            (true,  true,  false, None) => Ok(c::GENERIC_READ | c::GENERIC_WRITE),
+            (false, _,     true,  None) => Ok(c::FILE_GENERIC_WRITE & !c::FILE_WRITE_DATA),
+            (true,  _,     true,  None) => Ok(c::GENERIC_READ |
+                                              (c::FILE_GENERIC_WRITE & !c::FILE_WRITE_DATA)),
+            (false, false, false, None) => Err(Error::from_raw_os_error(ERROR_INVALID_PARAMETER)),
+        }
     }
 
-    fn get_share_mode(&self) -> c::DWORD {
-        // libuv has a good comment about this, but the basic idea is that
-        // we try to emulate unix semantics by enabling all sharing by
-        // allowing things such as deleting a file while it's still open.
-        self.share_mode.unwrap_or(c::FILE_SHARE_READ |
-                                  c::FILE_SHARE_WRITE |
-                                  c::FILE_SHARE_DELETE)
-    }
-
-    fn get_creation_disposition(&self) -> c::DWORD {
-        self.creation_disposition.unwrap_or({
-            match (self.create, self.truncate) {
-                (true, true) => c::CREATE_ALWAYS,
-                (true, false) => c::OPEN_ALWAYS,
-                (false, false) => c::OPEN_EXISTING,
-                (false, true) => {
-                    if self.write && !self.append {
-                        c::CREATE_ALWAYS
-                    } else {
-                        c::TRUNCATE_EXISTING
-                    }
-                }
-            }
-        })
+    fn get_creation_mode(&self) -> io::Result<c::DWORD> {
+        const ERROR_INVALID_PARAMETER: i32 = 87;
+
+        match (self.write, self.append) {
+            (true, false) => {}
+            (false, false) =>
+                if self.truncate || self.create || self.create_new {
+                    return Err(Error::from_raw_os_error(ERROR_INVALID_PARAMETER));
+                },
+            (_, true) =>
+                if self.truncate && !self.create_new {
+                    return Err(Error::from_raw_os_error(ERROR_INVALID_PARAMETER));
+                },
+        }
+
+        Ok(match (self.create, self.truncate, self.create_new) {
+                (false, false, false) => c::OPEN_EXISTING,
+                (true,  false, false) => c::OPEN_ALWAYS,
+                (false, true,  false) => c::TRUNCATE_EXISTING,
+                (true,  true,  false) => c::CREATE_ALWAYS,
+                (_,      _,    true)  => c::CREATE_NEW,
+           })
     }
 
     fn get_flags_and_attributes(&self) -> c::DWORD {
-        self.flags_and_attributes.unwrap_or(c::FILE_ATTRIBUTE_NORMAL)
+        self.custom_flags |
+        self.attributes |
+        self.security_qos_flags |
+        if self.security_qos_flags != 0 { c::SECURITY_SQOS_PRESENT } else { 0 } |
+        if self.create_new { c::FILE_FLAG_OPEN_REPARSE_POINT } else { 0 }
     }
 }
 
@@ -221,8 +243,8 @@ impl File {
         let mut opts = OpenOptions::new();
         opts.read(!write);
         opts.write(write);
-        opts.flags_and_attributes(c::FILE_FLAG_OPEN_REPARSE_POINT |
-                                  c::FILE_FLAG_BACKUP_SEMANTICS);
+        opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT |
+                          c::FILE_FLAG_BACKUP_SEMANTICS);
         File::open(path, &opts)
     }
 
@@ -230,10 +252,10 @@ impl File {
         let path = try!(to_u16s(path));
         let handle = unsafe {
             c::CreateFileW(path.as_ptr(),
-                           opts.get_desired_access(),
-                           opts.get_share_mode(),
+                           try!(opts.get_access_mode()),
+                           opts.share_mode,
                            opts.security_attributes as *mut _,
-                           opts.get_creation_disposition(),
+                           try!(opts.get_creation_mode()),
                            opts.get_flags_and_attributes(),
                            ptr::null_mut())
         };
@@ -533,7 +555,10 @@ pub fn stat(p: &Path) -> io::Result<FileAttr> {
     // metadata information is.
     if attr.is_reparse_point() {
         let mut opts = OpenOptions::new();
-        opts.flags_and_attributes(c::FILE_FLAG_BACKUP_SEMANTICS);
+        // No read or write permissions are necessary
+        opts.access_mode(0);
+        // This flag is so we can open directories too
+        opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS);
         let file = try!(File::open(p, &opts));
         file.file_attr()
     } else {
@@ -577,9 +602,10 @@ fn get_path(f: &File) -> io::Result<PathBuf> {
 
 pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {
     let mut opts = OpenOptions::new();
-    opts.read(true);
+    // No read or write permissions are necessary
+    opts.access_mode(0);
     // This flag is so we can open directories too
-    opts.flags_and_attributes(c::FILE_FLAG_BACKUP_SEMANTICS);
+    opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS);
     let f = try!(File::open(p, &opts));
     get_path(&f)
 }