Skip to content

Commit e5fa6a3

Browse files
authored
Merge pull request #211 from Berrysoft/fix/hidden-file-truncation
fix(fs,windows): allow `File::create` on hidden files
2 parents 5074b6c + 2bf6962 commit e5fa6a3

File tree

3 files changed

+77
-22
lines changed

3 files changed

+77
-22
lines changed

compio-driver/src/iocp/op.rs

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use windows_sys::{
2222
Foundation::{
2323
CloseHandle, GetLastError, ERROR_ACCESS_DENIED, ERROR_HANDLE_EOF, ERROR_IO_INCOMPLETE,
2424
ERROR_IO_PENDING, ERROR_NOT_FOUND, ERROR_NO_DATA, ERROR_PIPE_CONNECTED,
25-
ERROR_SHARING_VIOLATION, FILETIME,
25+
ERROR_SHARING_VIOLATION, FILETIME, INVALID_HANDLE_VALUE,
2626
},
2727
Networking::WinSock::{
2828
closesocket, setsockopt, shutdown, socklen_t, WSAIoctl, WSARecv, WSARecvFrom, WSASend,
@@ -144,6 +144,7 @@ pub struct OpenFile {
144144
pub(crate) security_attributes: *const SECURITY_ATTRIBUTES,
145145
pub(crate) creation_mode: FILE_CREATION_DISPOSITION,
146146
pub(crate) flags_and_attributes: FILE_FLAGS_AND_ATTRIBUTES,
147+
pub(crate) error_code: u32,
147148
}
148149

149150
impl OpenFile {
@@ -163,28 +164,38 @@ impl OpenFile {
163164
security_attributes,
164165
creation_mode,
165166
flags_and_attributes,
167+
error_code: 0,
166168
}
167169
}
170+
171+
/// The result of [`GetLastError`]. It may not be 0 even if the operation is
172+
/// successful.
173+
pub fn last_os_error(&self) -> u32 {
174+
self.error_code
175+
}
168176
}
169177

170178
impl OpCode for OpenFile {
171179
fn is_overlapped(&self) -> bool {
172180
false
173181
}
174182

175-
unsafe fn operate(self: Pin<&mut Self>, _optr: *mut OVERLAPPED) -> Poll<io::Result<usize>> {
176-
Poll::Ready(Ok(syscall!(
177-
HANDLE,
178-
CreateFileW(
179-
self.path.as_ptr(),
180-
self.access_mode,
181-
self.share_mode,
182-
self.security_attributes,
183-
self.creation_mode,
184-
self.flags_and_attributes,
185-
0
186-
)
187-
)? as _))
183+
unsafe fn operate(mut self: Pin<&mut Self>, _optr: *mut OVERLAPPED) -> Poll<io::Result<usize>> {
184+
let handle = CreateFileW(
185+
self.path.as_ptr(),
186+
self.access_mode,
187+
self.share_mode,
188+
self.security_attributes,
189+
self.creation_mode,
190+
self.flags_and_attributes,
191+
0,
192+
);
193+
self.error_code = GetLastError();
194+
if handle == INVALID_HANDLE_VALUE {
195+
Poll::Ready(Err(io::Error::from_raw_os_error(self.error_code as _)))
196+
} else {
197+
Poll::Ready(Ok(handle as _))
198+
}
188199
}
189200
}
190201

compio-fs/src/open_options/windows.rs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
use std::{io, path::Path, ptr::null};
22

3-
use compio_driver::{op::OpenFile, FromRawFd, RawFd};
3+
use compio_buf::BufResult;
4+
use compio_driver::{op::OpenFile, syscall, FromRawFd, RawFd};
45
use compio_runtime::Runtime;
56
use windows_sys::Win32::{
6-
Foundation::{ERROR_INVALID_PARAMETER, GENERIC_READ, GENERIC_WRITE},
7+
Foundation::{ERROR_ALREADY_EXISTS, ERROR_INVALID_PARAMETER, GENERIC_READ, GENERIC_WRITE},
78
Security::SECURITY_ATTRIBUTES,
89
Storage::FileSystem::{
9-
CREATE_ALWAYS, CREATE_NEW, FILE_FLAGS_AND_ATTRIBUTES, FILE_FLAG_OPEN_REPARSE_POINT,
10-
FILE_FLAG_OVERLAPPED, FILE_SHARE_DELETE, FILE_SHARE_MODE, FILE_SHARE_READ,
11-
FILE_SHARE_WRITE, OPEN_ALWAYS, OPEN_EXISTING, SECURITY_SQOS_PRESENT, TRUNCATE_EXISTING,
10+
FileAllocationInfo, SetFileInformationByHandle, CREATE_NEW, FILE_ALLOCATION_INFO,
11+
FILE_FLAGS_AND_ATTRIBUTES, FILE_FLAG_OPEN_REPARSE_POINT, FILE_FLAG_OVERLAPPED,
12+
FILE_SHARE_DELETE, FILE_SHARE_MODE, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_ALWAYS,
13+
OPEN_EXISTING, SECURITY_SQOS_PRESENT, TRUNCATE_EXISTING,
1214
},
1315
};
1416

@@ -112,7 +114,8 @@ impl OpenOptions {
112114
(false, false, false) => OPEN_EXISTING,
113115
(true, false, false) => OPEN_ALWAYS,
114116
(false, true, false) => TRUNCATE_EXISTING,
115-
(true, true, false) => CREATE_ALWAYS,
117+
// https://github.com/rust-lang/rust/issues/115745
118+
(true, true, false) => OPEN_ALWAYS,
116119
(_, _, true) => CREATE_NEW,
117120
})
118121
}
@@ -131,15 +134,32 @@ impl OpenOptions {
131134

132135
pub async fn open(&self, p: impl AsRef<Path>) -> io::Result<File> {
133136
let p = path_string(p)?;
137+
let creation_mode = self.get_creation_mode()?;
134138
let op = OpenFile::new(
135139
p,
136140
self.get_access_mode()?,
137141
self.share_mode,
138142
self.security_attributes,
139-
self.get_creation_mode()?,
143+
creation_mode,
140144
self.get_flags_and_attributes(),
141145
);
142-
let fd = Runtime::current().submit(op).await.0? as RawFd;
146+
let BufResult(fd, op) = Runtime::current().submit(op).await;
147+
let fd = fd? as RawFd;
148+
if self.truncate
149+
&& creation_mode == OPEN_ALWAYS
150+
&& op.last_os_error() == ERROR_ALREADY_EXISTS
151+
{
152+
let alloc = FILE_ALLOCATION_INFO { AllocationSize: 0 };
153+
syscall!(
154+
BOOL,
155+
SetFileInformationByHandle(
156+
fd as _,
157+
FileAllocationInfo,
158+
std::ptr::addr_of!(alloc).cast(),
159+
std::mem::size_of::<FILE_ALLOCATION_INFO>() as _,
160+
)
161+
)?;
162+
}
143163
Ok(unsafe { File::from_raw_fd(fd) })
144164
}
145165
}

compio-fs/tests/file.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,30 @@ async fn drop_open() {
7878
assert_eq!(file, HELLO);
7979
}
8080

81+
#[cfg(windows)]
82+
#[compio_macros::test]
83+
async fn hidden_file_truncation() {
84+
let tmpdir = tempfile::tempdir().unwrap();
85+
let path = tmpdir.path().join("hidden_file.txt");
86+
87+
// Create a hidden file.
88+
const FILE_ATTRIBUTE_HIDDEN: u32 = 2;
89+
let mut file = compio_fs::OpenOptions::new()
90+
.write(true)
91+
.create_new(true)
92+
.attributes(FILE_ATTRIBUTE_HIDDEN)
93+
.open(&path)
94+
.await
95+
.unwrap();
96+
file.write_all_at("hidden world!", 0).await.unwrap();
97+
file.close().await.unwrap();
98+
99+
// Create a new file by truncating the existing one.
100+
let file = File::create(&path).await.unwrap();
101+
let metadata = file.metadata().await.unwrap();
102+
assert_eq!(metadata.len(), 0);
103+
}
104+
81105
fn tempfile() -> NamedTempFile {
82106
NamedTempFile::new().unwrap()
83107
}

0 commit comments

Comments
 (0)