Skip to content

Commit 9b69a09

Browse files
committed
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.
1 parent b0fedc0 commit 9b69a09

File tree

4 files changed

+64
-5
lines changed

4 files changed

+64
-5
lines changed

library/std/src/fs/tests.rs

+26-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::os::unix::fs::symlink as symlink_file;
2222
#[cfg(unix)]
2323
use crate::os::unix::fs::symlink as symlink_junction;
2424
#[cfg(windows)]
25-
use crate::os::windows::fs::{symlink_dir, symlink_file};
25+
use crate::os::windows::fs::{symlink_dir, symlink_file, OpenOptionsExt};
2626
#[cfg(windows)]
2727
use crate::sys::fs::symlink_junction;
2828
#[cfg(target_os = "macos")]
@@ -1742,3 +1742,28 @@ fn windows_unix_socket_exists() {
17421742
assert_eq!(socket_path.try_exists().unwrap(), true);
17431743
assert_eq!(socket_path.metadata().is_ok(), true);
17441744
}
1745+
1746+
#[cfg(windows)]
1747+
#[test]
1748+
fn test_hidden_file_truncation() {
1749+
// Make sure that File::create works on an existing hidden file. See #115745.
1750+
let tmpdir = tmpdir();
1751+
let path = tmpdir.join("hidden_file.txt");
1752+
1753+
// Create a hidden file.
1754+
const FILE_ATTRIBUTE_HIDDEN: u32 = 2;
1755+
let mut file = OpenOptions::new()
1756+
.write(true)
1757+
.create_new(true)
1758+
.attributes(FILE_ATTRIBUTE_HIDDEN)
1759+
.open(&path)
1760+
.unwrap();
1761+
file.write("hidden world!".as_bytes()).unwrap();
1762+
file.flush().unwrap();
1763+
drop(file);
1764+
1765+
// Create a new file by truncating the existing one.
1766+
let file = File::create(&path).unwrap();
1767+
let metadata = file.metadata().unwrap();
1768+
assert_eq!(metadata.len(), 0);
1769+
}

library/std/src/sys/windows/c/windows_sys.lst

+1
Original file line numberDiff line numberDiff line change
@@ -2224,6 +2224,7 @@ Windows.Win32.Storage.FileSystem.FILE_ACCESS_RIGHTS
22242224
Windows.Win32.Storage.FileSystem.FILE_ADD_FILE
22252225
Windows.Win32.Storage.FileSystem.FILE_ADD_SUBDIRECTORY
22262226
Windows.Win32.Storage.FileSystem.FILE_ALL_ACCESS
2227+
Windows.Win32.Storage.FileSystem.FILE_ALLOCATION_INFO
22272228
Windows.Win32.Storage.FileSystem.FILE_APPEND_DATA
22282229
Windows.Win32.Storage.FileSystem.FILE_ATTRIBUTE_ARCHIVE
22292230
Windows.Win32.Storage.FileSystem.FILE_ATTRIBUTE_COMPRESSED

library/std/src/sys/windows/c/windows_sys.rs

+10
Original file line numberDiff line numberDiff line change
@@ -3107,6 +3107,16 @@ impl ::core::clone::Clone for FILETIME {
31073107
pub type FILE_ACCESS_RIGHTS = u32;
31083108
pub const FILE_ADD_FILE: FILE_ACCESS_RIGHTS = 2u32;
31093109
pub const FILE_ADD_SUBDIRECTORY: FILE_ACCESS_RIGHTS = 4u32;
3110+
#[repr(C)]
3111+
pub struct FILE_ALLOCATION_INFO {
3112+
pub AllocationSize: i64,
3113+
}
3114+
impl ::core::marker::Copy for FILE_ALLOCATION_INFO {}
3115+
impl ::core::clone::Clone for FILE_ALLOCATION_INFO {
3116+
fn clone(&self) -> Self {
3117+
*self
3118+
}
3119+
}
31103120
pub const FILE_ALL_ACCESS: FILE_ACCESS_RIGHTS = 2032127u32;
31113121
pub const FILE_APPEND_DATA: FILE_ACCESS_RIGHTS = 4u32;
31123122
pub const FILE_ATTRIBUTE_ARCHIVE: FILE_FLAGS_AND_ATTRIBUTES = 32u32;

library/std/src/sys/windows/fs.rs

+27-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::os::windows::prelude::*;
22

33
use crate::borrow::Cow;
4-
use crate::ffi::OsString;
4+
use crate::ffi::{c_void, OsString};
55
use crate::fmt;
66
use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom};
77
use crate::mem::{self, MaybeUninit};
@@ -273,7 +273,9 @@ impl OpenOptions {
273273
(false, false, false) => c::OPEN_EXISTING,
274274
(true, false, false) => c::OPEN_ALWAYS,
275275
(false, true, false) => c::TRUNCATE_EXISTING,
276-
(true, true, false) => c::CREATE_ALWAYS,
276+
// `CREATE_ALWAYS` has weird semantics so we emulate it using
277+
// `OPEN_ALWAYS` and a manual truncation step. See #115745.
278+
(true, true, false) => c::OPEN_ALWAYS,
277279
(_, _, true) => c::CREATE_NEW,
278280
})
279281
}
@@ -289,19 +291,40 @@ impl OpenOptions {
289291
impl File {
290292
pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> {
291293
let path = maybe_verbatim(path)?;
294+
let creation = opts.get_creation_mode()?;
292295
let handle = unsafe {
293296
c::CreateFileW(
294297
path.as_ptr(),
295298
opts.get_access_mode()?,
296299
opts.share_mode,
297300
opts.security_attributes,
298-
opts.get_creation_mode()?,
301+
creation,
299302
opts.get_flags_and_attributes(),
300303
ptr::null_mut(),
301304
)
302305
};
303306
let handle = unsafe { HandleOrInvalid::from_raw_handle(handle) };
304-
if let Ok(handle) = handle.try_into() {
307+
if let Ok(handle) = OwnedHandle::try_from(handle) {
308+
// Manual truncation. See #115745.
309+
if opts.truncate
310+
&& creation == c::OPEN_ALWAYS
311+
&& unsafe { c::GetLastError() } == c::ERROR_ALREADY_EXISTS
312+
{
313+
unsafe {
314+
// Setting the allocation size to zero also sets the
315+
// EOF position to zero.
316+
let alloc = c::FILE_ALLOCATION_INFO { AllocationSize: 0 };
317+
let result = c::SetFileInformationByHandle(
318+
handle.as_raw_handle(),
319+
c::FileAllocationInfo,
320+
ptr::addr_of!(alloc).cast::<c_void>(),
321+
mem::size_of::<c::FILE_ALLOCATION_INFO>() as u32,
322+
);
323+
if result == 0 {
324+
return Err(io::Error::last_os_error());
325+
}
326+
}
327+
}
305328
Ok(File { handle: Handle::from_inner(handle) })
306329
} else {
307330
Err(Error::last_os_error())

0 commit comments

Comments
 (0)