use std::fmt;
use windows_sys::Win32::Storage::FileSystem::{
FILE_NAME_NORMALIZED, FILE_NAME_OPENED, GETFINALPATHNAMEBYHANDLE_FLAGS,
GetFinalPathNameByHandleW, VOLUME_NAME_DOS, VOLUME_NAME_GUID, VOLUME_NAME_NONE, VOLUME_NAME_NT,
};
use wtf_string::Wtf16String;
use crate::handle::{CapturedHandle, HandleCaptureError};
use crate::outcome::{Win32Error, perform_nonzero};
const MAX_ATTEMPTS: usize = 8;
const FIRST_ATTEMPT_CHARS: usize = 260;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct FinalPathFlags(GETFINALPATHNAMEBYHANDLE_FLAGS);
impl FinalPathFlags {
pub const VOLUME_NAME_DOS: Self = Self(VOLUME_NAME_DOS);
pub const VOLUME_NAME_GUID: Self = Self(VOLUME_NAME_GUID);
pub const VOLUME_NAME_NT: Self = Self(VOLUME_NAME_NT);
pub const VOLUME_NAME_NONE: Self = Self(VOLUME_NAME_NONE);
pub const NAME_NORMALIZED: Self = Self(FILE_NAME_NORMALIZED);
pub const NAME_OPENED: Self = Self(FILE_NAME_OPENED);
pub const DEFAULT: Self = Self(VOLUME_NAME_DOS | FILE_NAME_NORMALIZED);
#[must_use]
pub const fn from_bits(bits: GETFINALPATHNAMEBYHANDLE_FLAGS) -> Self {
Self(bits)
}
#[must_use]
pub const fn bits(self) -> GETFINALPATHNAMEBYHANDLE_FLAGS {
self.0
}
}
impl std::ops::BitOr for FinalPathFlags {
type Output = Self;
fn bitor(self, other: Self) -> Self {
Self(self.0 | other.0)
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum FinalPathError {
Win32(Win32Error),
Unstable {
attempts: usize,
},
}
impl fmt::Display for FinalPathError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Win32(error) => write!(f, "GetFinalPathNameByHandleW: {error}"),
Self::Unstable { attempts } => write!(
f,
"GetFinalPathNameByHandleW: the required size changed on each of {attempts} attempts"
),
}
}
}
impl std::error::Error for FinalPathError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Win32(error) => Some(error),
Self::Unstable { .. } => None,
}
}
}
impl From<Win32Error> for FinalPathError {
fn from(error: Win32Error) -> Self {
Self::Win32(error)
}
}
#[derive(Debug)]
#[must_use = "an unperformed request resolves nothing"]
pub struct QueryFinalPath {
handle: CapturedHandle,
flags: FinalPathFlags,
}
impl QueryFinalPath {
pub fn new(handle: CapturedHandle) -> Self {
Self {
handle,
flags: FinalPathFlags::DEFAULT,
}
}
pub fn with_flags(mut self, flags: FinalPathFlags) -> Self {
self.flags = flags;
self
}
pub fn handle(&self) -> &CapturedHandle {
&self.handle
}
#[must_use]
pub fn flags(&self) -> FinalPathFlags {
self.flags
}
pub fn try_clone(&self) -> Result<Self, HandleCaptureError> {
Ok(Self {
handle: self.handle.try_clone()?,
flags: self.flags,
})
}
pub fn perform(&self) -> Result<Wtf16String, FinalPathError> {
let mut capacity = FIRST_ATTEMPT_CHARS;
for _ in 0..MAX_ATTEMPTS {
let mut buffer = Wtf16String::with_capacity(capacity);
let requested = u32::try_from(capacity).unwrap_or(u32::MAX);
let written = perform_nonzero(|| {
unsafe {
GetFinalPathNameByHandleW(
self.handle.raw(),
buffer.as_mut_ptr(),
requested,
self.flags.bits(),
)
}
})?;
let written = written as usize;
if written < capacity {
unsafe { buffer.set_len_from_ffi(written) };
return Ok(buffer);
}
capacity = written;
}
Err(FinalPathError::Unstable {
attempts: MAX_ATTEMPTS,
})
}
}
impl crate::request::Request for QueryFinalPath {
type Error = FinalPathError;
type Output = Wtf16String;
fn perform(&self) -> Result<Wtf16String, FinalPathError> {
Self::perform(self)
}
}
#[cfg(test)]
mod tests;