use std::io;
use std::mem;
use std::path::{Path, PathBuf};
use std::ptr;
use tracing::{debug, trace};
use wincorda::prelude::*;
use windows_sys::Wdk::Storage::FileSystem::REPARSE_DATA_BUFFER;
use windows_sys::Win32::Foundation::{CloseHandle, GENERIC_READ, INVALID_HANDLE_VALUE};
use windows_sys::Win32::Storage::FileSystem::{
CreateFileW, FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_DELETE, FILE_SHARE_READ,
FILE_SHARE_WRITE, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, OPEN_EXISTING,
};
use windows_sys::Win32::System::IO::DeviceIoControl;
use windows_sys::Win32::System::Ioctl::FSCTL_GET_REPARSE_POINT;
use windows_sys::Win32::System::SystemServices::IO_REPARSE_TAG_APPEXECLINK;
#[derive(Debug, Clone)]
pub struct AppExecLink {
pub package_name: String,
pub package_entrypoint: String,
pub real_path: PathBuf,
}
pub fn resolve_appexec_link(path: &Path) -> Option<PathBuf> {
let path_w = NullTerminated::<WCHAR>::from(path.to_string_lossy().into_owned());
let handle = unsafe {
CreateFileW(
path_w.as_ptr(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
ptr::null(),
OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT,
ptr::null_mut(),
)
};
if handle == INVALID_HANDLE_VALUE {
debug!(
path = %path.display(),
error = %io::Error::last_os_error(),
"CreateFileW failed for appexec stub",
);
return None;
}
let mut buf: Vec<u8> = vec![0; MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize]; let mut returned: u32 = 0;
let ok = unsafe {
DeviceIoControl(
handle,
FSCTL_GET_REPARSE_POINT,
ptr::null(),
0,
buf.as_mut_ptr().cast(),
buf.len() as u32,
&raw mut returned,
ptr::null_mut(),
)
};
unsafe {
CloseHandle(handle);
}
if ok == 0 {
debug!(
path = %path.display(),
error = %io::Error::last_os_error(),
"FSCTL_GET_REPARSE_POINT failed",
);
return None;
}
let parsed = parse_appexec_buffer(&buf[..returned as usize]);
if parsed.is_none() {
debug!(
path = %path.display(),
"reparse point isn't an APPEXECLINK or buffer body was malformed",
);
}
let resolved = parsed?.real_path;
trace!(stub = %path.display(), real = %resolved.display(), "resolved appexec link");
Some(resolved)
}
fn parse_appexec_buffer(buffer: &[u8]) -> Option<AppExecLink> {
if buffer.len() < mem::size_of::<REPARSE_DATA_BUFFER>() {
return None;
}
let reparse = unsafe { &*buffer.as_ptr().cast::<REPARSE_DATA_BUFFER>() };
if reparse.ReparseTag != IO_REPARSE_TAG_APPEXECLINK {
return None;
}
let strings_ptr = unsafe {
reparse
.Anonymous
.GenericReparseBuffer
.DataBuffer
.as_ptr()
.add(mem::size_of::<u32>())
.cast::<WCHAR>()
};
let strings: Vec<String> = MultiBuffer::try_from(strings_ptr)
.ok()?
.into_iter()
.collect();
let [package_name, package_entrypoint, real_path, _app_type] =
<[String; 4]>::try_from(strings).ok()?;
Some(AppExecLink {
package_name,
package_entrypoint,
real_path: PathBuf::from(real_path),
})
}