Skip to content

Commit 6b8f7ed

Browse files
authored
Merge pull request #170 from Berrysoft/dev/statx
feat(fs,driver): add async metadata
2 parents 320e6dc + dccfb42 commit 6b8f7ed

File tree

15 files changed

+1316
-34
lines changed

15 files changed

+1316
-34
lines changed

compio-driver/src/fusion/op.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::ffi::CString;
2+
13
use compio_buf::{IntoInner, IoBuf, IoBufMut, IoVectoredBuf, IoVectoredBufMut};
24
use socket2::SockAddr;
35

@@ -93,3 +95,5 @@ op!(<T: IoBufMut> RecvFrom(fd: RawFd, buffer: T));
9395
op!(<T: IoBuf> SendTo(fd: RawFd, buffer: T, addr: SockAddr));
9496
op!(<T: IoVectoredBufMut> RecvFromVectored(fd: RawFd, buffer: T));
9597
op!(<T: IoVectoredBuf> SendToVectored(fd: RawFd, buffer: T, addr: SockAddr));
98+
op!(<> FileStat(fd: RawFd));
99+
op!(<> PathStat(path: CString, follow_symlink: bool));

compio-driver/src/iocp/op.rs

+226-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::sync::OnceLock;
33
use std::{
44
io,
55
net::Shutdown,
6+
os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle},
67
pin::Pin,
78
ptr::{null, null_mut},
89
task::Poll,
@@ -18,8 +19,9 @@ use windows_sys::{
1819
core::GUID,
1920
Win32::{
2021
Foundation::{
21-
CloseHandle, GetLastError, ERROR_HANDLE_EOF, ERROR_IO_INCOMPLETE, ERROR_IO_PENDING,
22-
ERROR_NOT_FOUND, ERROR_NO_DATA, ERROR_PIPE_CONNECTED,
22+
CloseHandle, GetLastError, ERROR_ACCESS_DENIED, ERROR_HANDLE_EOF, ERROR_IO_INCOMPLETE,
23+
ERROR_IO_PENDING, ERROR_NOT_FOUND, ERROR_NO_DATA, ERROR_PIPE_CONNECTED,
24+
ERROR_SHARING_VIOLATION, FILETIME,
2325
},
2426
Networking::WinSock::{
2527
closesocket, setsockopt, shutdown, socklen_t, WSAIoctl, WSARecv, WSARecvFrom, WSASend,
@@ -30,8 +32,12 @@ use windows_sys::{
3032
},
3133
Security::SECURITY_ATTRIBUTES,
3234
Storage::FileSystem::{
33-
CreateFileW, FlushFileBuffers, ReadFile, WriteFile, FILE_CREATION_DISPOSITION,
34-
FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_MODE,
35+
CreateFileW, FileAttributeTagInfo, FindClose, FindFirstFileW, FlushFileBuffers,
36+
GetFileInformationByHandle, GetFileInformationByHandleEx, ReadFile, WriteFile,
37+
BY_HANDLE_FILE_INFORMATION, FILE_ATTRIBUTE_REPARSE_POINT, FILE_ATTRIBUTE_TAG_INFO,
38+
FILE_CREATION_DISPOSITION, FILE_FLAGS_AND_ATTRIBUTES, FILE_FLAG_BACKUP_SEMANTICS,
39+
FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_DELETE, FILE_SHARE_MODE, FILE_SHARE_READ,
40+
FILE_SHARE_WRITE, OPEN_EXISTING, WIN32_FIND_DATAW,
3541
},
3642
System::{
3743
Pipes::ConnectNamedPipe,
@@ -201,6 +207,222 @@ impl OpCode for CloseFile {
201207
}
202208
}
203209

210+
/// A mixture of [`BY_HANDLE_FILE_INFORMATION`], [`FILE_ATTRIBUTE_TAG_INFO`] and
211+
/// [`WIN32_FIND_DATAW`]. The field names follows Hungarian case, to make it
212+
/// look like Windows API.
213+
#[derive(Default, Clone)]
214+
#[allow(non_snake_case, missing_docs)]
215+
pub struct FileMetadata {
216+
pub dwFileAttributes: u32,
217+
pub ftCreationTime: u64,
218+
pub ftLastAccessTime: u64,
219+
pub ftLastWriteTime: u64,
220+
pub nFileSize: u64,
221+
pub dwReparseTag: u32,
222+
pub dwVolumeSerialNumber: Option<u32>,
223+
pub nNumberOfLinks: Option<u32>,
224+
pub nFileIndex: Option<u64>,
225+
}
226+
227+
impl FileMetadata {
228+
fn is_reparse_point(&self) -> bool {
229+
self.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0
230+
}
231+
}
232+
233+
const fn create_u64(high: u32, low: u32) -> u64 {
234+
((high as u64) << 32) | (low as u64)
235+
}
236+
237+
const fn filetime_u64(t: FILETIME) -> u64 {
238+
create_u64(t.dwHighDateTime, t.dwLowDateTime)
239+
}
240+
241+
impl From<BY_HANDLE_FILE_INFORMATION> for FileMetadata {
242+
fn from(value: BY_HANDLE_FILE_INFORMATION) -> Self {
243+
Self {
244+
dwFileAttributes: value.dwFileAttributes,
245+
ftCreationTime: filetime_u64(value.ftCreationTime),
246+
ftLastAccessTime: filetime_u64(value.ftLastAccessTime),
247+
ftLastWriteTime: filetime_u64(value.ftLastWriteTime),
248+
nFileSize: create_u64(value.nFileSizeHigh, value.nFileSizeLow),
249+
dwReparseTag: 0,
250+
dwVolumeSerialNumber: Some(value.dwVolumeSerialNumber),
251+
nNumberOfLinks: Some(value.nNumberOfLinks),
252+
nFileIndex: Some(create_u64(value.nFileIndexHigh, value.nFileIndexLow)),
253+
}
254+
}
255+
}
256+
257+
impl From<WIN32_FIND_DATAW> for FileMetadata {
258+
fn from(value: WIN32_FIND_DATAW) -> Self {
259+
let mut this = Self {
260+
dwFileAttributes: value.dwFileAttributes,
261+
ftCreationTime: filetime_u64(value.ftCreationTime),
262+
ftLastAccessTime: filetime_u64(value.ftLastAccessTime),
263+
ftLastWriteTime: filetime_u64(value.ftLastWriteTime),
264+
nFileSize: create_u64(value.nFileSizeHigh, value.nFileSizeLow),
265+
dwReparseTag: 0,
266+
dwVolumeSerialNumber: None,
267+
nNumberOfLinks: None,
268+
nFileIndex: None,
269+
};
270+
if this.is_reparse_point() {
271+
this.dwReparseTag = value.dwReserved0;
272+
}
273+
this
274+
}
275+
}
276+
277+
/// Get metadata of an opened file.
278+
pub struct FileStat {
279+
pub(crate) fd: RawFd,
280+
pub(crate) stat: FileMetadata,
281+
}
282+
283+
impl FileStat {
284+
/// Create [`FileStat`].
285+
pub fn new(fd: RawFd) -> Self {
286+
Self {
287+
fd,
288+
stat: Default::default(),
289+
}
290+
}
291+
}
292+
293+
impl OpCode for FileStat {
294+
fn is_overlapped(&self) -> bool {
295+
false
296+
}
297+
298+
unsafe fn operate(mut self: Pin<&mut Self>, _optr: *mut OVERLAPPED) -> Poll<io::Result<usize>> {
299+
let mut stat = unsafe { std::mem::zeroed() };
300+
syscall!(BOOL, GetFileInformationByHandle(self.fd as _, &mut stat))?;
301+
self.stat = stat.into();
302+
if self.stat.is_reparse_point() {
303+
let mut tag: FILE_ATTRIBUTE_TAG_INFO = std::mem::zeroed();
304+
syscall!(
305+
BOOL,
306+
GetFileInformationByHandleEx(
307+
self.fd as _,
308+
FileAttributeTagInfo,
309+
&mut tag as *mut _ as _,
310+
std::mem::size_of::<FILE_ATTRIBUTE_TAG_INFO>() as _
311+
)
312+
)?;
313+
debug_assert_eq!(self.stat.dwFileAttributes, tag.FileAttributes);
314+
self.stat.dwReparseTag = tag.ReparseTag;
315+
}
316+
Poll::Ready(Ok(0))
317+
}
318+
319+
unsafe fn cancel(self: Pin<&mut Self>, _optr: *mut OVERLAPPED) -> io::Result<()> {
320+
Ok(())
321+
}
322+
}
323+
324+
impl IntoInner for FileStat {
325+
type Inner = FileMetadata;
326+
327+
fn into_inner(self) -> Self::Inner {
328+
self.stat
329+
}
330+
}
331+
332+
/// Get metadata from path.
333+
pub struct PathStat {
334+
pub(crate) path: U16CString,
335+
pub(crate) follow_symlink: bool,
336+
pub(crate) stat: FileMetadata,
337+
}
338+
339+
impl PathStat {
340+
/// Create [`PathStat`].
341+
pub fn new(path: U16CString, follow_symlink: bool) -> Self {
342+
Self {
343+
path,
344+
follow_symlink,
345+
stat: Default::default(),
346+
}
347+
}
348+
349+
unsafe fn open_and_stat(&self, optr: *mut OVERLAPPED) -> io::Result<FileMetadata> {
350+
let mut flags = FILE_FLAG_BACKUP_SEMANTICS;
351+
if !self.follow_symlink {
352+
flags |= FILE_FLAG_OPEN_REPARSE_POINT;
353+
}
354+
let handle = syscall!(
355+
HANDLE,
356+
CreateFileW(
357+
self.path.as_ptr(),
358+
0,
359+
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
360+
null(),
361+
OPEN_EXISTING,
362+
flags,
363+
0
364+
)
365+
)?;
366+
let handle = OwnedHandle::from_raw_handle(handle as _);
367+
let mut op = FileStat::new(handle.as_raw_handle());
368+
let op_pin = std::pin::Pin::new(&mut op);
369+
let res = op_pin.operate(optr);
370+
if let Poll::Ready(res) = res {
371+
res.map(|_| op.into_inner())
372+
} else {
373+
unreachable!("FileStat could not return Poll::Pending")
374+
}
375+
}
376+
}
377+
378+
impl OpCode for PathStat {
379+
fn is_overlapped(&self) -> bool {
380+
false
381+
}
382+
383+
unsafe fn operate(mut self: Pin<&mut Self>, optr: *mut OVERLAPPED) -> Poll<io::Result<usize>> {
384+
let res = match self.open_and_stat(optr) {
385+
Ok(stat) => {
386+
self.stat = stat;
387+
Ok(0)
388+
}
389+
Err(e)
390+
if [
391+
Some(ERROR_SHARING_VIOLATION as _),
392+
Some(ERROR_ACCESS_DENIED as _),
393+
]
394+
.contains(&e.raw_os_error()) =>
395+
{
396+
let mut wfd: WIN32_FIND_DATAW = std::mem::zeroed();
397+
let handle = syscall!(HANDLE, FindFirstFileW(self.path.as_ptr(), &mut wfd))?;
398+
FindClose(handle);
399+
self.stat = wfd.into();
400+
let is_reparse = self.stat.is_reparse_point();
401+
let surrogate = self.stat.dwReparseTag & 0x20000000 != 0;
402+
if self.follow_symlink && is_reparse && surrogate {
403+
Err(e)
404+
} else {
405+
Ok(0)
406+
}
407+
}
408+
Err(e) => Err(e),
409+
};
410+
Poll::Ready(res)
411+
}
412+
413+
unsafe fn cancel(self: Pin<&mut Self>, _optr: *mut OVERLAPPED) -> io::Result<()> {
414+
Ok(())
415+
}
416+
}
417+
418+
impl IntoInner for PathStat {
419+
type Inner = FileMetadata;
420+
421+
fn into_inner(self) -> Self::Inner {
422+
self.stat
423+
}
424+
}
425+
204426
impl<T: IoBufMut> OpCode for ReadAt<T> {
205427
unsafe fn operate(mut self: Pin<&mut Self>, optr: *mut OVERLAPPED) -> Poll<io::Result<usize>> {
206428
if let Some(overlapped) = optr.as_mut() {

compio-driver/src/iour/op.rs

+82-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{os::fd::RawFd, pin::Pin};
1+
use std::{ffi::CString, os::fd::RawFd, pin::Pin};
22

33
use compio_buf::{
44
BufResult, IntoInner, IoBuf, IoBufMut, IoSlice, IoSliceMut, IoVectoredBuf, IoVectoredBufMut,
@@ -50,6 +50,87 @@ impl OpCode for CloseFile {
5050
}
5151
}
5252

53+
/// Get metadata of an opened file.
54+
pub struct FileStat {
55+
pub(crate) fd: RawFd,
56+
pub(crate) stat: libc::statx,
57+
}
58+
59+
impl FileStat {
60+
/// Create [`FileStat`].
61+
pub fn new(fd: RawFd) -> Self {
62+
Self {
63+
fd,
64+
stat: unsafe { std::mem::zeroed() },
65+
}
66+
}
67+
}
68+
69+
impl OpCode for FileStat {
70+
fn create_entry(mut self: Pin<&mut Self>) -> OpEntry {
71+
static EMPTY_NAME: &[u8] = b"\0";
72+
opcode::Statx::new(
73+
Fd(self.fd),
74+
EMPTY_NAME.as_ptr().cast(),
75+
std::ptr::addr_of_mut!(self.stat).cast(),
76+
)
77+
.flags(libc::AT_EMPTY_PATH)
78+
.build()
79+
.into()
80+
}
81+
}
82+
83+
impl IntoInner for FileStat {
84+
type Inner = libc::stat;
85+
86+
fn into_inner(self) -> Self::Inner {
87+
statx_to_stat(self.stat)
88+
}
89+
}
90+
91+
/// Get metadata from path.
92+
pub struct PathStat {
93+
pub(crate) path: CString,
94+
pub(crate) stat: libc::statx,
95+
pub(crate) follow_symlink: bool,
96+
}
97+
98+
impl PathStat {
99+
/// Create [`PathStat`].
100+
pub fn new(path: CString, follow_symlink: bool) -> Self {
101+
Self {
102+
path,
103+
stat: unsafe { std::mem::zeroed() },
104+
follow_symlink,
105+
}
106+
}
107+
}
108+
109+
impl OpCode for PathStat {
110+
fn create_entry(mut self: Pin<&mut Self>) -> OpEntry {
111+
let mut flags = libc::AT_EMPTY_PATH;
112+
if !self.follow_symlink {
113+
flags |= libc::AT_SYMLINK_NOFOLLOW;
114+
}
115+
opcode::Statx::new(
116+
Fd(libc::AT_FDCWD),
117+
self.path.as_ptr(),
118+
std::ptr::addr_of_mut!(self.stat).cast(),
119+
)
120+
.flags(flags)
121+
.build()
122+
.into()
123+
}
124+
}
125+
126+
impl IntoInner for PathStat {
127+
type Inner = libc::stat;
128+
129+
fn into_inner(self) -> Self::Inner {
130+
statx_to_stat(self.stat)
131+
}
132+
}
133+
53134
impl<T: IoBufMut> OpCode for ReadAt<T> {
54135
fn create_entry(mut self: Pin<&mut Self>) -> OpEntry {
55136
let fd = Fd(self.fd);

compio-driver/src/op.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ use std::net::Shutdown;
88
use compio_buf::{BufResult, IntoInner, IoBuf, IoBufMut, SetBufInit};
99
use socket2::SockAddr;
1010

11-
#[cfg(windows)]
12-
pub use crate::sys::op::ConnectNamedPipe;
1311
pub use crate::sys::op::{
14-
Accept, OpenFile, Recv, RecvFrom, RecvFromVectored, RecvVectored, Send, SendTo, SendToVectored,
15-
SendVectored,
12+
Accept, FileStat, OpenFile, PathStat, Recv, RecvFrom, RecvFromVectored, RecvVectored, Send,
13+
SendTo, SendToVectored, SendVectored,
1614
};
15+
#[cfg(windows)]
16+
pub use crate::sys::op::{ConnectNamedPipe, FileMetadata};
1717
#[cfg(unix)]
1818
pub use crate::sys::op::{ReadVectoredAt, WriteVectoredAt};
1919
use crate::sys::{sockaddr_storage, socklen_t, RawFd};

0 commit comments

Comments
 (0)