file_source_common/
metadata_ext.rs

1//! FIXME: A workaround to fix <https://github.com/vectordotdev/vector/issues/1480> resulting from <https://github.com/rust-lang/rust/issues/63010>
2//! Most of code is cribbed directly from the Rust stdlib and ported to work with winapi.
3//!
4//! In stdlib imported code, warnings are allowed.
5#![allow(async_fn_in_trait)]
6
7#[cfg(unix)]
8use std::fs::Metadata;
9#[cfg(unix)]
10use std::os::unix::fs::MetadataExt;
11#[cfg(windows)]
12use std::{mem::zeroed, ptr};
13use tokio::fs::File;
14
15#[cfg(windows)]
16use winapi::shared::minwindef::DWORD;
17#[cfg(windows)]
18use winapi::um::{
19    fileapi::BY_HANDLE_FILE_INFORMATION, fileapi::GetFileInformationByHandle,
20    ioapiset::DeviceIoControl, winioctl::FSCTL_GET_REPARSE_POINT,
21    winnt::FILE_ATTRIBUTE_REPARSE_POINT, winnt::MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
22};
23
24pub trait PortableFileExt {
25    fn portable_dev(&self) -> u64;
26    fn portable_ino(&self) -> u64;
27}
28
29#[cfg(unix)]
30pub trait AsyncFileInfo {
31    async fn file_info(&self) -> std::io::Result<Metadata>;
32}
33
34#[cfg(windows)]
35pub trait AsyncFileInfo: std::os::windows::io::AsRawHandle {
36    // This code is from the Rust stdlib https://github.com/rust-lang/rust/blob/30ddb5a8c1e85916da0acdc665d6a16535a12dd6/src/libstd/sys/windows/fs.rs#L458-L478
37    #[allow(unused_assignments, unused_variables)]
38    fn reparse_point<'a>(
39        &self,
40        space: &'a mut [u8; MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize],
41    ) -> std::io::Result<(DWORD, &'a REPARSE_DATA_BUFFER)> {
42        unsafe {
43            let mut bytes = 0;
44            cvt({
45                DeviceIoControl(
46                    self.as_raw_handle(),
47                    FSCTL_GET_REPARSE_POINT,
48                    ptr::null_mut(),
49                    0,
50                    space.as_mut_ptr() as *mut _,
51                    space.len() as DWORD,
52                    &mut bytes,
53                    ptr::null_mut(),
54                )
55            })?;
56            Ok((bytes, &*(space.as_ptr() as *const REPARSE_DATA_BUFFER)))
57        }
58    }
59    // This code is from the Rust stdlib https://github.com/rust-lang/rust/blob/30ddb5a8c1e85916da0acdc665d6a16535a12dd6/src/libstd/sys/windows/fs.rs#L326-L351
60    #[allow(unused_assignments, unused_variables)]
61    fn file_info_inner(&self) -> std::io::Result<BY_HANDLE_FILE_INFORMATION> {
62        unsafe {
63            let mut info: BY_HANDLE_FILE_INFORMATION = zeroed();
64            cvt(GetFileInformationByHandle(self.as_raw_handle(), &mut info))?;
65            let mut reparse_tag = 0;
66            if info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
67                let mut b = [0; MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize];
68                if let Ok((_, buf)) = self.reparse_point(&mut b) {
69                    reparse_tag = buf.ReparseTag;
70                }
71            }
72            Ok(info)
73        }
74    }
75    async fn file_info(&self) -> std::io::Result<BY_HANDLE_FILE_INFORMATION>;
76}
77
78#[cfg(unix)]
79impl AsyncFileInfo for File {
80    async fn file_info(&self) -> std::io::Result<Metadata> {
81        self.metadata().await
82    }
83}
84
85#[cfg(unix)]
86impl PortableFileExt for Metadata {
87    fn portable_dev(&self) -> u64 {
88        self.dev()
89    }
90    fn portable_ino(&self) -> u64 {
91        self.ino()
92    }
93}
94
95#[cfg(windows)]
96impl AsyncFileInfo for File {
97    async fn file_info(&self) -> std::io::Result<BY_HANDLE_FILE_INFORMATION> {
98        let file = self.try_clone().await?;
99        tokio::task::spawn_blocking(move || file.file_info_inner())
100            .await
101            .map_err(std::io::Error::other)?
102    }
103}
104
105#[cfg(windows)]
106impl PortableFileExt for BY_HANDLE_FILE_INFORMATION {
107    fn portable_dev(&self) -> u64 {
108        self.dwVolumeSerialNumber.into()
109    }
110    // This is not exactly inode, but it's close. See https://docs.microsoft.com/en-us/windows/win32/api/fileapi/ns-fileapi-by_handle_file_information
111    fn portable_ino(&self) -> u64 {
112        // https://github.com/rust-lang/rust/blob/30ddb5a8c1e85916da0acdc665d6a16535a12dd6/src/libstd/sys/windows/fs.rs#L347
113        (self.nFileIndexLow as u64) | ((self.nFileIndexHigh as u64) << 32)
114    }
115}
116
117// This code is from the Rust stdlib https://github.com/rust-lang/rust/blob/a916ac22b9f7f1f0f7aba0a41a789b3ecd765018/src/libstd/sys/windows/c.rs#L380-L386
118#[cfg(windows)]
119#[allow(dead_code, non_snake_case, non_camel_case_types)]
120pub struct REPARSE_DATA_BUFFER {
121    pub ReparseTag: libc::c_uint,
122    pub ReparseDataLength: libc::c_ushort,
123    pub Reserved: libc::c_ushort,
124    pub rest: (),
125}
126
127// This code is from the Rust stdlib  https://github.com/rust-lang/rust/blob/30ddb5a8c1e85916da0acdc665d6a16535a12dd6/src/libstd/sys/hermit/mod.rs#L141-L143
128#[cfg(windows)]
129pub fn cvt(result: i32) -> std::io::Result<usize> {
130    if result < 0 {
131        Err(std::io::Error::from_raw_os_error(-result))
132    } else {
133        Ok(result as usize)
134    }
135}