1
Fork 0

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.
This commit is contained in:
Chris Denton 2023-10-05 03:15:50 +01:00
parent b0fedc07ce
commit c6f7aa0eea
No known key found for this signature in database
GPG key ID: 713472F2F45627DE
4 changed files with 64 additions and 7 deletions

View file

@ -22,7 +22,7 @@ use crate::os::unix::fs::symlink as symlink_file;
#[cfg(unix)] #[cfg(unix)]
use crate::os::unix::fs::symlink as symlink_junction; use crate::os::unix::fs::symlink as symlink_junction;
#[cfg(windows)] #[cfg(windows)]
use crate::os::windows::fs::{symlink_dir, symlink_file}; use crate::os::windows::fs::{symlink_dir, symlink_file, OpenOptionsExt};
#[cfg(windows)] #[cfg(windows)]
use crate::sys::fs::symlink_junction; use crate::sys::fs::symlink_junction;
#[cfg(target_os = "macos")] #[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.try_exists().unwrap(), true);
assert_eq!(socket_path.metadata().is_ok(), 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);
}

View file

@ -2224,6 +2224,7 @@ Windows.Win32.Storage.FileSystem.FILE_ACCESS_RIGHTS
Windows.Win32.Storage.FileSystem.FILE_ADD_FILE Windows.Win32.Storage.FileSystem.FILE_ADD_FILE
Windows.Win32.Storage.FileSystem.FILE_ADD_SUBDIRECTORY Windows.Win32.Storage.FileSystem.FILE_ADD_SUBDIRECTORY
Windows.Win32.Storage.FileSystem.FILE_ALL_ACCESS 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_APPEND_DATA
Windows.Win32.Storage.FileSystem.FILE_ATTRIBUTE_ARCHIVE Windows.Win32.Storage.FileSystem.FILE_ATTRIBUTE_ARCHIVE
Windows.Win32.Storage.FileSystem.FILE_ATTRIBUTE_COMPRESSED Windows.Win32.Storage.FileSystem.FILE_ATTRIBUTE_COMPRESSED

View file

@ -3107,6 +3107,16 @@ impl ::core::clone::Clone for FILETIME {
pub type FILE_ACCESS_RIGHTS = u32; pub type FILE_ACCESS_RIGHTS = u32;
pub const FILE_ADD_FILE: FILE_ACCESS_RIGHTS = 2u32; pub const FILE_ADD_FILE: FILE_ACCESS_RIGHTS = 2u32;
pub const FILE_ADD_SUBDIRECTORY: FILE_ACCESS_RIGHTS = 4u32; 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_ALL_ACCESS: FILE_ACCESS_RIGHTS = 2032127u32;
pub const FILE_APPEND_DATA: FILE_ACCESS_RIGHTS = 4u32; pub const FILE_APPEND_DATA: FILE_ACCESS_RIGHTS = 4u32;
pub const FILE_ATTRIBUTE_ARCHIVE: FILE_FLAGS_AND_ATTRIBUTES = 32u32; pub const FILE_ATTRIBUTE_ARCHIVE: FILE_FLAGS_AND_ATTRIBUTES = 32u32;

View file

@ -1,7 +1,7 @@
use crate::os::windows::prelude::*; use crate::os::windows::prelude::*;
use crate::borrow::Cow; use crate::borrow::Cow;
use crate::ffi::OsString; use crate::ffi::{c_void, OsString};
use crate::fmt; use crate::fmt;
use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom}; use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom};
use crate::mem::{self, MaybeUninit}; use crate::mem::{self, MaybeUninit};
@ -16,8 +16,6 @@ use crate::sys::{c, cvt, Align8};
use crate::sys_common::{AsInner, FromInner, IntoInner}; use crate::sys_common::{AsInner, FromInner, IntoInner};
use crate::thread; use crate::thread;
use core::ffi::c_void;
use super::path::maybe_verbatim; use super::path::maybe_verbatim;
use super::to_u16s; use super::to_u16s;
@ -273,7 +271,9 @@ impl OpenOptions {
(false, false, false) => c::OPEN_EXISTING, (false, false, false) => c::OPEN_EXISTING,
(true, false, false) => c::OPEN_ALWAYS, (true, false, false) => c::OPEN_ALWAYS,
(false, true, false) => c::TRUNCATE_EXISTING, (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, (_, _, true) => c::CREATE_NEW,
}) })
} }
@ -289,19 +289,40 @@ impl OpenOptions {
impl File { impl File {
pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> { pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> {
let path = maybe_verbatim(path)?; let path = maybe_verbatim(path)?;
let creation = opts.get_creation_mode()?;
let handle = unsafe { let handle = unsafe {
c::CreateFileW( c::CreateFileW(
path.as_ptr(), path.as_ptr(),
opts.get_access_mode()?, opts.get_access_mode()?,
opts.share_mode, opts.share_mode,
opts.security_attributes, opts.security_attributes,
opts.get_creation_mode()?, creation,
opts.get_flags_and_attributes(), opts.get_flags_and_attributes(),
ptr::null_mut(), ptr::null_mut(),
) )
}; };
let handle = unsafe { HandleOrInvalid::from_raw_handle(handle) }; 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) }) Ok(File { handle: Handle::from_inner(handle) })
} else { } else {
Err(Error::last_os_error()) Err(Error::last_os_error())