use std::{ffi::OsStr, io, path::Path};
pub struct SymlinkMetadata {
pub is_symlink: bool,
pub is_dir: bool,
pub is_file: bool,
}
pub fn symlink_metadata(path: &Path) -> io::Result<SymlinkMetadata> {
use windows::{
Win32::Storage::FileSystem::{
FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_REPARSE_POINT, FILE_FLAGS_AND_ATTRIBUTES,
GetFileAttributesExW, GetFileExInfoStandard,
},
core::HSTRING,
};
let verbatim_path = maybe_verbatim(path)?;
let lpfilename = HSTRING::from_wide(&verbatim_path);
let finfolevelid = GetFileExInfoStandard;
let mut file_info = std::mem::MaybeUninit::<
windows::Win32::Storage::FileSystem::WIN32_FILE_ATTRIBUTE_DATA,
>::uninit();
unsafe { GetFileAttributesExW(&lpfilename, finfolevelid, (&raw mut file_info).cast()) }?;
let file_info = unsafe { file_info.assume_init() };
let file_attrs = FILE_FLAGS_AND_ATTRIBUTES(file_info.dwFileAttributes);
let is_directory = file_attrs.contains(FILE_ATTRIBUTE_DIRECTORY);
let is_symlink = file_attrs.contains(FILE_ATTRIBUTE_REPARSE_POINT);
Ok(SymlinkMetadata {
is_dir: !is_symlink && is_directory,
is_file: !is_symlink && !is_directory,
is_symlink,
})
}
fn maybe_verbatim(path: &Path) -> io::Result<Vec<u16>> {
let path = to_u16s(path)?;
get_long_path(path)
}
fn get_long_path(mut path: Vec<u16>) -> io::Result<Vec<u16>> {
use windows::Win32::Storage::FileSystem::GetFullPathNameW;
use windows::core::HSTRING;
const LEGACY_MAX_PATH: usize = 248;
const SEP: u16 = b'\\' as _;
const ALT_SEP: u16 = b'/' as _;
const QUERY: u16 = b'?' as _;
const COLON: u16 = b':' as _;
const DOT: u16 = b'.' as _;
const U: u16 = b'U' as _;
const N: u16 = b'N' as _;
const C: u16 = b'C' as _;
const VERBATIM_PREFIX: &[u16] = &[SEP, SEP, QUERY, SEP];
const NT_PREFIX: &[u16] = &[SEP, QUERY, QUERY, SEP];
const UNC_PREFIX: &[u16] = &[SEP, SEP, QUERY, SEP, U, N, C, SEP];
if path.starts_with(VERBATIM_PREFIX) || path.starts_with(NT_PREFIX) || path == [0] {
return Ok(path);
} else if path.len() < LEGACY_MAX_PATH {
match path.as_slice() {
[drive, COLON, 0] | [drive, COLON, SEP | ALT_SEP, ..]
if *drive != SEP && *drive != ALT_SEP =>
{
return Ok(path);
}
[SEP | ALT_SEP, SEP | ALT_SEP, ..] => return Ok(path),
_ => {}
}
}
let lpfilename = HSTRING::from_wide(&path);
let mut buffer = vec![0u16; LEGACY_MAX_PATH * 2];
loop {
let res = unsafe { GetFullPathNameW(&lpfilename, Some(buffer.as_mut_slice()), None) };
match res as usize {
0 => return Err(io::Error::last_os_error()),
len if len <= buffer.len() => {
let mut buffer = &buffer[..len];
let prefix = match buffer {
[_, COLON, SEP, ..] => VERBATIM_PREFIX,
[SEP, SEP, DOT, SEP, ..] => {
buffer = &buffer[4..];
VERBATIM_PREFIX
}
[SEP, SEP | QUERY, QUERY, SEP, ..] => &[],
[SEP, SEP, ..] => {
buffer = &buffer[2..];
UNC_PREFIX
}
_ => &[],
};
path.clear();
path.reserve_exact(prefix.len() + buffer.len() + 1);
path.extend_from_slice(prefix);
path.extend_from_slice(buffer);
path.push(0);
return Ok(path);
}
new_len => buffer.resize(new_len, 0),
}
}
}
fn to_u16s<S: AsRef<OsStr>>(s: S) -> io::Result<Vec<u16>> {
fn inner(s: &OsStr) -> io::Result<Vec<u16>> {
use std::os::windows::ffi::OsStrExt;
let mut maybe_result = Vec::with_capacity(s.len() + 1);
maybe_result.extend(s.encode_wide());
if unrolled_find_u16s(0, &maybe_result).is_some() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"strings passed to WinAPI cannot contain NULs",
));
}
maybe_result.push(0);
Ok(maybe_result)
}
inner(s.as_ref())
}
fn unrolled_find_u16s(needle: u16, haystack: &[u16]) -> Option<usize> {
let ptr = haystack.as_ptr();
let mut start = haystack;
while start.len() >= 8 {
macro_rules! if_return {
($($n:literal,)+) => {
$(
if start[$n] == needle {
return Some(((&raw const start[$n]).addr() - ptr.addr()) / 2);
}
)+
}
}
if_return!(0, 1, 2, 3, 4, 5, 6, 7,);
start = &start[8..];
}
for c in start {
if *c == needle {
return Some((std::ptr::from_ref::<u16>(c).addr() - ptr.addr()) / 2);
}
}
None
}