X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Flibstd%2Ffs.rs;h=e40a3d06f77537201336b442ee78d6dc47bc05d6;hb=4043c0247ebf5139f52a92b1501e4cdcbd5f1bd7;hp=635ed91f35da41c5335c36d4c5cd57e569de3d53;hpb=3246eaec90d3369347da28353b8aa23c9347d592;p=rust.git diff --git a/src/libstd/fs.rs b/src/libstd/fs.rs index 635ed91f35d..e40a3d06f77 100644 --- a/src/libstd/fs.rs +++ b/src/libstd/fs.rs @@ -375,7 +375,7 @@ fn seek(&mut self, pos: SeekFrom) -> io::Result { } 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: &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() {} + _assert_send_sync::(); } #[test]