diff --git a/compio-driver/src/iocp/op.rs b/compio-driver/src/iocp/op.rs index 658c49b1..b123b5b9 100644 --- a/compio-driver/src/iocp/op.rs +++ b/compio-driver/src/iocp/op.rs @@ -22,7 +22,7 @@ use windows_sys::{ Foundation::{ CloseHandle, GetLastError, ERROR_ACCESS_DENIED, ERROR_HANDLE_EOF, ERROR_IO_INCOMPLETE, ERROR_IO_PENDING, ERROR_NOT_FOUND, ERROR_NO_DATA, ERROR_PIPE_CONNECTED, - ERROR_SHARING_VIOLATION, FILETIME, + ERROR_SHARING_VIOLATION, FILETIME, INVALID_HANDLE_VALUE, }, Networking::WinSock::{ closesocket, setsockopt, shutdown, socklen_t, WSAIoctl, WSARecv, WSARecvFrom, WSASend, @@ -144,6 +144,7 @@ pub struct OpenFile { pub(crate) security_attributes: *const SECURITY_ATTRIBUTES, pub(crate) creation_mode: FILE_CREATION_DISPOSITION, pub(crate) flags_and_attributes: FILE_FLAGS_AND_ATTRIBUTES, + pub(crate) error_code: u32, } impl OpenFile { @@ -163,8 +164,15 @@ impl OpenFile { security_attributes, creation_mode, flags_and_attributes, + error_code: 0, } } + + /// The result of [`GetLastError`]. It may not be 0 even if the operation is + /// successful. + pub fn last_os_error(&self) -> u32 { + self.error_code + } } impl OpCode for OpenFile { @@ -172,19 +180,22 @@ impl OpCode for OpenFile { false } - unsafe fn operate(self: Pin<&mut Self>, _optr: *mut OVERLAPPED) -> Poll> { - Poll::Ready(Ok(syscall!( - HANDLE, - CreateFileW( - self.path.as_ptr(), - self.access_mode, - self.share_mode, - self.security_attributes, - self.creation_mode, - self.flags_and_attributes, - 0 - ) - )? as _)) + unsafe fn operate(mut self: Pin<&mut Self>, _optr: *mut OVERLAPPED) -> Poll> { + let handle = CreateFileW( + self.path.as_ptr(), + self.access_mode, + self.share_mode, + self.security_attributes, + self.creation_mode, + self.flags_and_attributes, + 0, + ); + self.error_code = GetLastError(); + if handle == INVALID_HANDLE_VALUE { + Poll::Ready(Err(io::Error::from_raw_os_error(self.error_code as _))) + } else { + Poll::Ready(Ok(handle as _)) + } } } diff --git a/compio-fs/src/open_options/windows.rs b/compio-fs/src/open_options/windows.rs index ac130535..004314a6 100644 --- a/compio-fs/src/open_options/windows.rs +++ b/compio-fs/src/open_options/windows.rs @@ -1,14 +1,16 @@ use std::{io, path::Path, ptr::null}; -use compio_driver::{op::OpenFile, FromRawFd, RawFd}; +use compio_buf::BufResult; +use compio_driver::{op::OpenFile, syscall, FromRawFd, RawFd}; use compio_runtime::Runtime; use windows_sys::Win32::{ - Foundation::{ERROR_INVALID_PARAMETER, GENERIC_READ, GENERIC_WRITE}, + Foundation::{ERROR_ALREADY_EXISTS, ERROR_INVALID_PARAMETER, GENERIC_READ, GENERIC_WRITE}, Security::SECURITY_ATTRIBUTES, Storage::FileSystem::{ - CREATE_ALWAYS, CREATE_NEW, FILE_FLAGS_AND_ATTRIBUTES, FILE_FLAG_OPEN_REPARSE_POINT, - FILE_FLAG_OVERLAPPED, FILE_SHARE_DELETE, FILE_SHARE_MODE, FILE_SHARE_READ, - FILE_SHARE_WRITE, OPEN_ALWAYS, OPEN_EXISTING, SECURITY_SQOS_PRESENT, TRUNCATE_EXISTING, + FileAllocationInfo, SetFileInformationByHandle, CREATE_NEW, FILE_ALLOCATION_INFO, + FILE_FLAGS_AND_ATTRIBUTES, FILE_FLAG_OPEN_REPARSE_POINT, FILE_FLAG_OVERLAPPED, + FILE_SHARE_DELETE, FILE_SHARE_MODE, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_ALWAYS, + OPEN_EXISTING, SECURITY_SQOS_PRESENT, TRUNCATE_EXISTING, }, }; @@ -112,7 +114,8 @@ impl OpenOptions { (false, false, false) => OPEN_EXISTING, (true, false, false) => OPEN_ALWAYS, (false, true, false) => TRUNCATE_EXISTING, - (true, true, false) => CREATE_ALWAYS, + // https://github.com/rust-lang/rust/issues/115745 + (true, true, false) => OPEN_ALWAYS, (_, _, true) => CREATE_NEW, }) } @@ -131,15 +134,32 @@ impl OpenOptions { pub async fn open(&self, p: impl AsRef) -> io::Result { let p = path_string(p)?; + let creation_mode = self.get_creation_mode()?; let op = OpenFile::new( p, self.get_access_mode()?, self.share_mode, self.security_attributes, - self.get_creation_mode()?, + creation_mode, self.get_flags_and_attributes(), ); - let fd = Runtime::current().submit(op).await.0? as RawFd; + let BufResult(fd, op) = Runtime::current().submit(op).await; + let fd = fd? as RawFd; + if self.truncate + && creation_mode == OPEN_ALWAYS + && op.last_os_error() == ERROR_ALREADY_EXISTS + { + let alloc = FILE_ALLOCATION_INFO { AllocationSize: 0 }; + syscall!( + BOOL, + SetFileInformationByHandle( + fd as _, + FileAllocationInfo, + std::ptr::addr_of!(alloc).cast(), + std::mem::size_of::() as _, + ) + )?; + } Ok(unsafe { File::from_raw_fd(fd) }) } } diff --git a/compio-fs/tests/file.rs b/compio-fs/tests/file.rs index dff8fb6b..96f1b602 100644 --- a/compio-fs/tests/file.rs +++ b/compio-fs/tests/file.rs @@ -78,6 +78,30 @@ async fn drop_open() { assert_eq!(file, HELLO); } +#[cfg(windows)] +#[compio_macros::test] +async fn hidden_file_truncation() { + let tmpdir = tempfile::tempdir().unwrap(); + let path = tmpdir.path().join("hidden_file.txt"); + + // Create a hidden file. + const FILE_ATTRIBUTE_HIDDEN: u32 = 2; + let mut file = compio_fs::OpenOptions::new() + .write(true) + .create_new(true) + .attributes(FILE_ATTRIBUTE_HIDDEN) + .open(&path) + .await + .unwrap(); + file.write_all_at("hidden world!", 0).await.unwrap(); + file.close().await.unwrap(); + + // Create a new file by truncating the existing one. + let file = File::create(&path).await.unwrap(); + let metadata = file.metadata().await.unwrap(); + assert_eq!(metadata.len(), 0); +} + fn tempfile() -> NamedTempFile { NamedTempFile::new().unwrap() }