diff options
| author | bors <bors@rust-lang.org> | 2023-12-13 02:27:12 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2023-12-13 02:27:12 +0000 |
| commit | 77d16997566dfb2384bcbc504e2579c7ae973666 (patch) | |
| tree | e4350dc1e78ad58b62a310734ee680c326a4e1b5 | |
| parent | 3340d49d22b1aba425779767278c40781826c2f5 (diff) | |
| parent | c6f7aa0eea9af919ade663bb3a4bb4b6761c3f83 (diff) | |
| download | rust-77d16997566dfb2384bcbc504e2579c7ae973666.tar.gz rust-77d16997566dfb2384bcbc504e2579c7ae973666.zip | |
Auto merge of #116438 - ChrisDenton:truncate, r=thomcc
Windows: Allow `File::create` to work on hidden files This makes `OpenOptions::new().write(true).create(true).truncate(true).open(&path)` work if the path exists and is a hidden file. Previously it would fail with access denied. This makes it consistent with `OpenOptions::new().write(true).truncate(true).open(&path)` (note the lack of `create`) which does not have this restriction. It's also more consistent with other platforms. Fixes #115745 (see that issue for more details).
| -rw-r--r-- | library/std/src/fs/tests.rs | 27 | ||||
| -rw-r--r-- | library/std/src/sys/windows/fs.rs | 33 |
2 files changed, 53 insertions, 7 deletions
diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 3b6fbf95faf..12afdef2669 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -22,7 +22,7 @@ use crate::os::unix::fs::symlink as symlink_file; #[cfg(unix)] use crate::os::unix::fs::symlink as symlink_junction; #[cfg(windows)] -use crate::os::windows::fs::{symlink_dir, symlink_file}; +use crate::os::windows::fs::{symlink_dir, symlink_file, OpenOptionsExt}; #[cfg(windows)] use crate::sys::fs::symlink_junction; #[cfg(target_os = "macos")] @@ -1793,3 +1793,28 @@ fn windows_unix_socket_exists() { assert_eq!(socket_path.try_exists().unwrap(), true); assert_eq!(socket_path.metadata().is_ok(), true); } + +#[cfg(windows)] +#[test] +fn test_hidden_file_truncation() { + // Make sure that File::create works on an existing hidden file. See #115745. + let tmpdir = tmpdir(); + let path = tmpdir.join("hidden_file.txt"); + + // Create a hidden file. + const FILE_ATTRIBUTE_HIDDEN: u32 = 2; + let mut file = OpenOptions::new() + .write(true) + .create_new(true) + .attributes(FILE_ATTRIBUTE_HIDDEN) + .open(&path) + .unwrap(); + file.write("hidden world!".as_bytes()).unwrap(); + file.flush().unwrap(); + drop(file); + + // Create a new file by truncating the existing one. + let file = File::create(&path).unwrap(); + let metadata = file.metadata().unwrap(); + assert_eq!(metadata.len(), 0); +} diff --git a/library/std/src/sys/windows/fs.rs b/library/std/src/sys/windows/fs.rs index f57034cc8d5..09dfb0caeec 100644 --- a/library/std/src/sys/windows/fs.rs +++ b/library/std/src/sys/windows/fs.rs @@ -1,7 +1,7 @@ use crate::os::windows::prelude::*; use crate::borrow::Cow; -use crate::ffi::OsString; +use crate::ffi::{c_void, OsString}; use crate::fmt; use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom}; use crate::mem::{self, MaybeUninit}; @@ -16,8 +16,6 @@ use crate::sys::{c, cvt, Align8}; use crate::sys_common::{AsInner, FromInner, IntoInner}; use crate::thread; -use core::ffi::c_void; - use super::path::maybe_verbatim; use super::{api, to_u16s, IoResult}; @@ -273,7 +271,9 @@ impl OpenOptions { (false, false, false) => c::OPEN_EXISTING, (true, false, false) => c::OPEN_ALWAYS, (false, true, false) => c::TRUNCATE_EXISTING, - (true, true, false) => c::CREATE_ALWAYS, + // `CREATE_ALWAYS` has weird semantics so we emulate it using + // `OPEN_ALWAYS` and a manual truncation step. See #115745. + (true, true, false) => c::OPEN_ALWAYS, (_, _, true) => c::CREATE_NEW, }) } @@ -289,19 +289,40 @@ impl OpenOptions { impl File { pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> { let path = maybe_verbatim(path)?; + let creation = opts.get_creation_mode()?; let handle = unsafe { c::CreateFileW( path.as_ptr(), opts.get_access_mode()?, opts.share_mode, opts.security_attributes, - opts.get_creation_mode()?, + creation, opts.get_flags_and_attributes(), ptr::null_mut(), ) }; let handle = unsafe { HandleOrInvalid::from_raw_handle(handle) }; - if let Ok(handle) = handle.try_into() { + if let Ok(handle) = OwnedHandle::try_from(handle) { + // Manual truncation. See #115745. + if opts.truncate + && creation == c::OPEN_ALWAYS + && unsafe { c::GetLastError() } == c::ERROR_ALREADY_EXISTS + { + unsafe { + // Setting the allocation size to zero also sets the + // EOF position to zero. + let alloc = c::FILE_ALLOCATION_INFO { AllocationSize: 0 }; + let result = c::SetFileInformationByHandle( + handle.as_raw_handle(), + c::FileAllocationInfo, + ptr::addr_of!(alloc).cast::<c_void>(), + mem::size_of::<c::FILE_ALLOCATION_INFO>() as u32, + ); + if result == 0 { + return Err(io::Error::last_os_error()); + } + } + } Ok(File { handle: Handle::from_inner(handle) }) } else { Err(Error::last_os_error()) |
