]> git.lizzy.rs Git - rust.git/blobdiff - src/libstd/fs.rs
Auto merge of #31093 - tshepang:misc-doc-improvements, r=steveklabnik
[rust.git] / src / libstd / fs.rs
index 635ed91f35da41c5335c36d4c5cd57e569de3d53..e40a3d06f77537201336b442ee78d6dc47bc05d6 100644 (file)
@@ -375,7 +375,7 @@ fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
 }
 
 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 @@ pub fn read(&mut self, read: bool) -> &mut OpenOptions {
     /// This option, when true, will indicate that the file should be
     /// `write`-able if opened.
     ///
+    /// If the file already exists, any write calls on it will overwrite its
+    /// contents, without truncating it.
+    ///
     /// # Examples
     ///
     /// ```no_run
@@ -429,13 +433,30 @@ pub fn write(&mut self, write: bool) -> &mut 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 that 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 to the file in one operation. This
+    /// can be done by concatenating strings before passing them to `write()`,
+    /// or using a buffered writer (with a buffer of adequate size),
+    /// 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 @@ pub fn append(&mut self, append: bool) -> &mut 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 @@ pub fn truncate(&mut self, truncate: bool) -> &mut OpenOptions {
     /// This option indicates whether a new file will be created if the file
     /// does not yet already exist.
     ///
+    /// In order for the file to be created, `write` or `append` access must
+    /// be used.
+    ///
     /// # 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 @@ pub fn create(&mut self, create: bool) -> &mut 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 @@ fn c<T: Clone>(t: &T) -> T { t.clone() }
 
         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]