about summary refs log tree commit diff
diff options
context:
space:
mode:
authorChris Denton <chris@chrisdenton.dev>2023-10-05 03:15:50 +0100
committerChris Denton <chris@chrisdenton.dev>2023-10-15 10:51:20 +0100
commitc6f7aa0eea9af919ade663bb3a4bb4b6761c3f83 (patch)
tree985834a80c3f400598bb62dfd83bf3271cbf0799
parentb0fedc07ce52a7647272a34fadbf4c5585a79c0b (diff)
downloadrust-c6f7aa0eea9af919ade663bb3a4bb4b6761c3f83.tar.gz
rust-c6f7aa0eea9af919ade663bb3a4bb4b6761c3f83.zip
Make File::create work on Windows hidden files
Previously it failed on Windows if the file had the `FILE_ATTRIBUTE_HIDDEN` attribute set. This was inconsistent with `OpenOptions::new().write(true).truncate(true)` which can truncate an existing hidden file.
-rw-r--r--library/std/src/fs/tests.rs27
-rw-r--r--library/std/src/sys/windows/c/windows_sys.lst1
-rw-r--r--library/std/src/sys/windows/c/windows_sys.rs10
-rw-r--r--library/std/src/sys/windows/fs.rs33
4 files changed, 64 insertions, 7 deletions
diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs
index f0c4be2327c..6eb083515a4 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")]
@@ -1742,3 +1742,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/c/windows_sys.lst b/library/std/src/sys/windows/c/windows_sys.lst
index dad4a4223b2..31e5de4bce4 100644
--- a/library/std/src/sys/windows/c/windows_sys.lst
+++ b/library/std/src/sys/windows/c/windows_sys.lst
@@ -2224,6 +2224,7 @@ Windows.Win32.Storage.FileSystem.FILE_ACCESS_RIGHTS
 Windows.Win32.Storage.FileSystem.FILE_ADD_FILE
 Windows.Win32.Storage.FileSystem.FILE_ADD_SUBDIRECTORY
 Windows.Win32.Storage.FileSystem.FILE_ALL_ACCESS
+Windows.Win32.Storage.FileSystem.FILE_ALLOCATION_INFO
 Windows.Win32.Storage.FileSystem.FILE_APPEND_DATA
 Windows.Win32.Storage.FileSystem.FILE_ATTRIBUTE_ARCHIVE
 Windows.Win32.Storage.FileSystem.FILE_ATTRIBUTE_COMPRESSED
diff --git a/library/std/src/sys/windows/c/windows_sys.rs b/library/std/src/sys/windows/c/windows_sys.rs
index 20b44966a39..a3400eb3ac6 100644
--- a/library/std/src/sys/windows/c/windows_sys.rs
+++ b/library/std/src/sys/windows/c/windows_sys.rs
@@ -3107,6 +3107,16 @@ impl ::core::clone::Clone for FILETIME {
 pub type FILE_ACCESS_RIGHTS = u32;
 pub const FILE_ADD_FILE: FILE_ACCESS_RIGHTS = 2u32;
 pub const FILE_ADD_SUBDIRECTORY: FILE_ACCESS_RIGHTS = 4u32;
+#[repr(C)]
+pub struct FILE_ALLOCATION_INFO {
+    pub AllocationSize: i64,
+}
+impl ::core::marker::Copy for FILE_ALLOCATION_INFO {}
+impl ::core::clone::Clone for FILE_ALLOCATION_INFO {
+    fn clone(&self) -> Self {
+        *self
+    }
+}
 pub const FILE_ALL_ACCESS: FILE_ACCESS_RIGHTS = 2032127u32;
 pub const FILE_APPEND_DATA: FILE_ACCESS_RIGHTS = 4u32;
 pub const FILE_ATTRIBUTE_ARCHIVE: FILE_FLAGS_AND_ATTRIBUTES = 32u32;
diff --git a/library/std/src/sys/windows/fs.rs b/library/std/src/sys/windows/fs.rs
index 6ded683aade..75c7dc57c95 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::to_u16s;
 
@@ -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())