diff --git a/src/libstd/fs.rs b/src/libstd/fs.rs index c2d3d2fb0c848..edb6bee4cf1c6 100644 --- a/src/libstd/fs.rs +++ b/src/libstd/fs.rs @@ -359,7 +359,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`. /// @@ -368,7 +368,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,13 +414,15 @@ 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)`. /// /// # 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 { @@ -431,6 +434,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 @@ -448,12 +453,15 @@ impl OpenOptions { /// This option indicates whether a new file will be created if the file /// does not yet already exist. /// + /// The access mode (e.g. read, write and/or append) does not influence whether + /// a new file is created, or with which permissions it is created. + /// /// # 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 { @@ -1960,61 +1968,111 @@ 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); + 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); + // Test opening a non-existing file 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"))); + // Test various combinations of access modes with open options. + // Tested combinations: + // - .create(true).open() when file does not exist + // - .create(true).open() when file exists + // - .open() when file exists + // - .truncate.open() when file exists + check!(c(&r).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"))); + check!(c(&r).create(true).open(&tmpdir.join("b"))); + check!(r.open(&tmpdir.join("b"))); + error!(c(&r).truncate(true).open(&tmpdir.join("b")), invalid_options); - check!(c(&rw).create(true).open(&tmpdir.join("c"))); + check!(c(&w).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).create(true).open(&tmpdir.join("c"))); + check!(w.open(&tmpdir.join("c"))); + check!(c(&w).truncate(true).open(&tmpdir.join("c"))); - check!(c(&w).append(true).create(true).open(&tmpdir.join("d"))); + check!(c(&rw).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).create(true).open(&tmpdir.join("d"))); + check!(rw.open(&tmpdir.join("d"))); + check!(c(&rw).truncate(true).open(&tmpdir.join("d"))); - check!(c(&rw).append(true).create(true).open(&tmpdir.join("e"))); + check!(c(&a).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(&a).create(true).open(&tmpdir.join("e"))); + check!(a.open(&tmpdir.join("e"))); + error!(c(&a).truncate(true).open(&tmpdir.join("e")), invalid_options); - check!(c(&w).truncate(true).create(true).open(&tmpdir.join("f"))); + check!(c(&ra).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"))); + check!(c(&ra).create(true).open(&tmpdir.join("f"))); + check!(ra.open(&tmpdir.join("f"))); + error!(c(&ra).truncate(true).open(&tmpdir.join("f")), invalid_options); + + // Test combination of .create(true) and .truncate(true) + // Tested combinations: + // - .truncate.open() when file does not exist + // - .create(true).truncate.open() when file does not exist + // - .create(true).truncate.open() when file exists + assert!(c(&w).truncate(true).open(&tmpdir.join("g")).is_err()); + check!(c(&w).create(true).truncate(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!(c(&w).create(true).truncate(true).open(&tmpdir.join("g"))); - check!(check!(File::create(&tmpdir.join("h"))).write("foo".as_bytes())); + // Test open options have effect + // 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(), 9); + + // Test opening a file without setting an access mode + let mut blank = OO::new(); + if cfg!(unix) { + error!(blank.create(true).open(&tmpdir.join("i")), invalid_options); + } + if cfg!(windows) { + let mut f = check!(blank.create(true).open(&tmpdir.join("i"))); + let mut b = vec![0; 6]; + assert!(f.read(&mut b).is_err()); } - assert_eq!(check!(fs::metadata(&tmpdir.join("h"))).len(), 3); } #[test] diff --git a/src/libstd/sys/unix/fs.rs b/src/libstd/sys/unix/fs.rs index 128284834ab01..db1b1e21034c0 100644 --- a/src/libstd/sys/unix/fs.rs +++ b/src/libstd/sys/unix/fs.rs @@ -213,9 +213,13 @@ impl DirEntry { impl OpenOptions { pub fn new() -> OpenOptions { OpenOptions { - flags: 0, + flags: libc::O_CLOEXEC, read: false, write: false, + append: false, + truncate: false, + create: false, + create_new: false, mode: 0o666, } } @@ -229,15 +233,19 @@ impl OpenOptions { } pub fn append(&mut self, append: bool) { - self.flag(libc::O_APPEND, append); + self.append = append; } pub fn truncate(&mut self, truncate: bool) { - self.flag(libc::O_TRUNC, truncate); + self.truncate = truncate; } pub fn create(&mut self, create: bool) { - self.flag(libc::O_CREAT, create); + self.create = create; + } + + pub fn create_new(&mut self, create_new: bool) { + self.create_new = create_new; } pub fn mode(&mut self, mode: raw::mode_t) { @@ -251,6 +259,37 @@ impl OpenOptions { self.flags &= !bit; } } + + fn get_flags(&self) -> io::Result { + flags = self.flags; + + // Access mode + match (self.read, self.write | self.append) { + (true, true) => flags |= libc::O_RDWR, + (false, true) => flags |= libc::O_WRONLY, + (true, false) => flags |= libc::O_RDONLY, + (false, false) => return Err(Error::from_raw_os_error(libc::EINVAL)), + } + if self.append {flags |= libc::O_APPEND}; + + // Flags + if self.create_new { + flags |= libc::O_CREAT | libc::O_EXCL; + } else { + if self.truncate { + if !self.write || self.append { + // The result of using O_TRUNC with O_RDONLY or O_APPEND is undefined. + // Not allowed, for consistency with Windows. + return Err(Error::from_raw_os_error(libc::EINVAL)) + } else { + flags |= libc::O_TRUNC; + } + } + if self.create {flags |= libc::O_CREAT}; + } + + Ok(flags) + } } impl File { @@ -260,12 +299,7 @@ impl File { } pub fn open_c(path: &CStr, opts: &OpenOptions) -> io::Result { - 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 = try!(opts.get_flags()); let fd = try!(cvt_r(|| unsafe { libc::open(path.as_ptr(), flags, opts.mode) })); diff --git a/src/libstd/sys/windows/fs.rs b/src/libstd/sys/windows/fs.rs index 36fabe72aa0c1..9b5c866bab3a6 100644 --- a/src/libstd/sys/windows/fs.rs +++ b/src/libstd/sys/windows/fs.rs @@ -176,12 +176,14 @@ impl OpenOptions { fn get_desired_access(&self) -> libc::DWORD { self.desired_access.unwrap_or({ - let mut base = if self.read {libc::FILE_GENERIC_READ} else {0} | - if self.write {libc::FILE_GENERIC_WRITE} else {0}; - if self.append { - base &= !libc::FILE_WRITE_DATA; - base |= libc::FILE_APPEND_DATA; - } + let base = if self.read {libc::GENERIC_READ} else {0} | + if self.append { + /* append has the same bits set as those that are implied + for write, but without FILE_WRITE_DATA */ + libc::FILE_GENERIC_WRITE & !libc::FILE_WRITE_DATA + } else if self.write { + libc::GENERIC_WRITE + } else {0}; base }) } @@ -201,13 +203,7 @@ impl OpenOptions { (true, true) => libc::CREATE_ALWAYS, (true, false) => libc::OPEN_ALWAYS, (false, false) => libc::OPEN_EXISTING, - (false, true) => { - if self.write && !self.append { - libc::CREATE_ALWAYS - } else { - libc::TRUNCATE_EXISTING - } - } + (false, true) => libc::TRUNCATE_EXISTING, } }) }