winbrew_windows\fs/
mod.rs

1use std::fs::{self, OpenOptions};
2use std::io;
3use std::path::Path;
4
5use std::os::windows::fs::OpenOptionsExt;
6
7use windows_sys::Win32::Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE};
8
9use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OPEN_REPARSE_POINT;
10
11/// Metadata snapshot returned by [`inspect_path`].
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct PathInfo {
14    /// `true` when the path points to a directory.
15    pub is_directory: bool,
16    /// `true` when the path is marked as a reparse point.
17    pub is_reparse_point: bool,
18    /// Number of hard links attached to the path entry.
19    pub hard_link_count: u32,
20}
21
22/// Inspect a Windows filesystem path without following reparse points.
23///
24/// The helper opens the target with the Windows handle APIs, reads handle
25/// information, and returns the small metadata set WinBrew needs for extraction
26/// and cleanup decisions.
27///
28/// # Example
29///
30/// ```no_run
31/// use std::path::Path;
32/// use winbrew_windows::fs::inspect_path;
33///
34/// let info = inspect_path(Path::new(r"C:\Temp\payload.msix")).unwrap();
35/// println!("dir={} reparse={} links={}", info.is_directory, info.is_reparse_point, info.hard_link_count);
36/// ```
37pub fn inspect_path(path: &Path) -> io::Result<PathInfo> {
38    use std::mem::MaybeUninit;
39    use std::os::windows::ffi::OsStrExt;
40    use windows_sys::Win32::Storage::FileSystem::{
41        BY_HANDLE_FILE_INFORMATION, CreateFileW, FILE_ATTRIBUTE_DIRECTORY,
42        FILE_ATTRIBUTE_REPARSE_POINT, FILE_FLAG_BACKUP_SEMANTICS, FILE_READ_ATTRIBUTES,
43        FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, GetFileInformationByHandle,
44        OPEN_EXISTING,
45    };
46
47    let mut wide_path: Vec<u16> = path.as_os_str().encode_wide().collect();
48    wide_path.push(0);
49
50    unsafe {
51        let handle = CreateFileW(
52            wide_path.as_ptr(),
53            FILE_READ_ATTRIBUTES,
54            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
55            std::ptr::null(),
56            OPEN_EXISTING,
57            FILE_FLAG_BACKUP_SEMANTICS,
58            std::ptr::null_mut(),
59        );
60
61        if handle == INVALID_HANDLE_VALUE {
62            return Err(io::Error::last_os_error());
63        }
64
65        let _handle_guard = HandleGuard(handle);
66
67        let mut info = MaybeUninit::<BY_HANDLE_FILE_INFORMATION>::uninit();
68        if GetFileInformationByHandle(handle, info.as_mut_ptr()) == 0 {
69            return Err(io::Error::last_os_error());
70        }
71
72        let info = info.assume_init();
73
74        Ok(PathInfo {
75            is_directory: info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0,
76            is_reparse_point: info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0,
77            hard_link_count: info.nNumberOfLinks,
78        })
79    }
80}
81
82/// Create a new file for extraction, failing if the target already exists.
83///
84/// This helper is used by archive and package extraction code when the output
85/// path must be brand new. It keeps the file creation rules in one place and
86/// applies the Windows flag WinBrew expects for reparse-point-aware staging.
87///
88/// # Example
89///
90/// ```no_run
91/// use std::path::Path;
92/// use winbrew_windows::fs::create_extraction_target_file;
93///
94/// let _file = create_extraction_target_file(Path::new(r"C:\Temp\extract\tool.exe")).unwrap();
95/// ```
96pub fn create_extraction_target_file(path: &Path) -> io::Result<fs::File> {
97    OpenOptions::new()
98        .write(true)
99        .create_new(true)
100        .custom_flags(FILE_FLAG_OPEN_REPARSE_POINT)
101        .open(path)
102}
103
104#[deprecated(note = "use create_extraction_target_file")]
105#[doc(hidden)]
106pub fn create_extracted_file(path: &Path) -> io::Result<fs::File> {
107    create_extraction_target_file(path)
108}
109
110struct HandleGuard(HANDLE);
111
112impl Drop for HandleGuard {
113    fn drop(&mut self) {
114        unsafe {
115            let _ = CloseHandle(self.0);
116        }
117    }
118}