Skip to content

Commit

Permalink
Merge pull request #211 from Berrysoft/fix/hidden-file-truncation
Browse files Browse the repository at this point in the history
fix(fs,windows): allow `File::create` on hidden files
  • Loading branch information
Berrysoft authored Feb 15, 2024
2 parents 5074b6c + 2bf6962 commit e5fa6a3
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 22 deletions.
39 changes: 25 additions & 14 deletions compio-driver/src/iocp/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand All @@ -163,28 +164,38 @@ 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 {
fn is_overlapped(&self) -> bool {
false
}

unsafe fn operate(self: Pin<&mut Self>, _optr: *mut OVERLAPPED) -> Poll<io::Result<usize>> {
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<io::Result<usize>> {
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 _))
}
}
}

Expand Down
36 changes: 28 additions & 8 deletions compio-fs/src/open_options/windows.rs
Original file line number Diff line number Diff line change
@@ -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,
},
};

Expand Down Expand Up @@ -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,
})
}
Expand All @@ -131,15 +134,32 @@ impl OpenOptions {

pub async fn open(&self, p: impl AsRef<Path>) -> io::Result<File> {
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::<FILE_ALLOCATION_INFO>() as _,
)
)?;
}
Ok(unsafe { File::from_raw_fd(fd) })
}
}
24 changes: 24 additions & 0 deletions compio-fs/tests/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down

0 comments on commit e5fa6a3

Please sign in to comment.