use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle};
use windows_impersonation_token_sys::ImpersonationToken;
use windows_sys::Win32::Foundation::{
ERROR_BAD_LENGTH, ERROR_DIRECTORY, ERROR_FILE_NOT_FOUND, ERROR_INSUFFICIENT_BUFFER,
ERROR_INVALID_FUNCTION, ERROR_INVALID_PARAMETER, ERROR_MORE_DATA, ERROR_NO_MORE_FILES,
ERROR_NOT_SUPPORTED, HANDLE, INVALID_HANDLE_VALUE,
};
use windows_sys::Win32::Storage::FileSystem::{
CreateFileW, FILE_ATTRIBUTE_DIRECTORY, FILE_BASIC_INFO, FILE_FLAG_BACKUP_SEMANTICS,
FILE_ID_INFO, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, FileBasicInfo,
FileIdExtdDirectoryInfo, FileIdExtdDirectoryRestartInfo, FileIdInfo,
GetFileInformationByHandleEx, OPEN_EXISTING,
};
use wtf_string::Wtf16Str;
use crate::buffer::NativeBuffer;
use crate::error::{EnumerationError, Win32Error};
use crate::request::{MINIMUM_BUFFER_CAPACITY, RECORD_ALIGNMENT};
const FILE_LIST_DIRECTORY: u32 = 0x0000_0001;
pub(crate) fn open_directory(
path: &Wtf16Str,
token: &ImpersonationToken,
) -> Result<OwnedHandle, EnumerationError> {
let path = wtf_string::Wtf16String::from_units(path.as_units());
let opened = token
.with_impersonation(|| {
let handle = unsafe {
CreateFileW(
path.as_terminated_ptr(),
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
core::ptr::null(),
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
core::ptr::null_mut(),
)
};
if handle == INVALID_HANDLE_VALUE {
Err(Win32Error::last())
} else {
Ok(handle)
}
})
.map_err(EnumerationError::Impersonation)?;
let handle = match opened {
Ok(handle) => unsafe { OwnedHandle::from_raw_handle(handle as _) },
Err(code) => return Err(EnumerationError::DirectoryOpen(code)),
};
if !is_directory(&handle)? {
return Err(EnumerationError::DirectoryOpen(Win32Error::from_code(
ERROR_DIRECTORY,
)));
}
Ok(handle)
}
fn is_directory(handle: &OwnedHandle) -> Result<bool, EnumerationError> {
let mut info = FILE_BASIC_INFO {
CreationTime: 0,
LastAccessTime: 0,
LastWriteTime: 0,
ChangeTime: 0,
FileAttributes: 0,
};
let ok = unsafe {
GetFileInformationByHandleEx(
handle.as_raw_handle() as HANDLE,
FileBasicInfo,
(&raw mut info).cast(),
size_of::<FILE_BASIC_INFO>() as u32,
)
};
if ok == 0 {
return Err(EnumerationError::DirectoryOpen(Win32Error::last()));
}
Ok(info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0)
}
pub(crate) fn volume_serial(directory: &OwnedHandle) -> Result<u64, Win32Error> {
let mut info = FILE_ID_INFO {
VolumeSerialNumber: 0,
FileId: windows_sys::Win32::Storage::FileSystem::FILE_ID_128 {
Identifier: [0; 16],
},
};
let ok = unsafe {
GetFileInformationByHandleEx(
directory.as_raw_handle() as HANDLE,
FileIdInfo,
(&raw mut info).cast(),
size_of::<FILE_ID_INFO>() as u32,
)
};
if ok == 0 {
return Err(Win32Error::last());
}
Ok(info.VolumeSerialNumber)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Refill {
First,
Next,
}
#[derive(Debug)]
pub(crate) enum RefillOutcome {
Batch,
Exhausted,
Failed(EnumerationError),
}
pub(crate) fn refill(
directory: &OwnedHandle,
buffer: &mut NativeBuffer,
which: Refill,
) -> RefillOutcome {
let class = match which {
Refill::First => FileIdExtdDirectoryRestartInfo,
Refill::Next => FileIdExtdDirectoryInfo,
};
let capacity = buffer.capacity();
let handle = directory.as_raw_handle() as HANDLE;
debug_assert!(
!handle.is_null() && handle != INVALID_HANDLE_VALUE,
"the directory handle must be a live handle this crate opened"
);
debug_assert!(
class == FileIdExtdDirectoryRestartInfo || class == FileIdExtdDirectoryInfo,
"the information class must be one this crate's refill actually requests"
);
let base = buffer.as_mut_ptr();
debug_assert!(!base.is_null(), "the buffer base must not be null");
debug_assert_eq!(
(base as usize) % RECORD_ALIGNMENT,
0,
"the buffer base must be 8-byte aligned"
);
debug_assert!(
capacity as usize >= MINIMUM_BUFFER_CAPACITY,
"the effective capacity must be at least the minimum buffer capacity"
);
debug_assert_eq!(
capacity as usize % RECORD_ALIGNMENT,
0,
"the effective capacity must be an 8-byte multiple"
);
let ok = unsafe { GetFileInformationByHandleEx(handle, class, base, capacity) };
if ok != 0 {
return RefillOutcome::Batch;
}
let code = Win32Error::last();
match classify_refill_failure(code, which, capacity as usize) {
Some(error) => RefillOutcome::Failed(error),
None => RefillOutcome::Exhausted,
}
}
fn classify_refill_failure(
code: Win32Error,
which: Refill,
capacity: usize,
) -> Option<EnumerationError> {
match code.code() {
ERROR_NO_MORE_FILES => None,
ERROR_FILE_NOT_FOUND if which == Refill::First => None,
ERROR_INVALID_FUNCTION | ERROR_NOT_SUPPORTED | ERROR_INVALID_PARAMETER => {
Some(EnumerationError::UnsupportedExtendedDirectoryInfo(code))
}
ERROR_MORE_DATA | ERROR_INSUFFICIENT_BUFFER | ERROR_BAD_LENGTH => {
Some(EnumerationError::RecordTooLarge {
buffer_capacity: capacity,
code,
})
}
_ => Some(EnumerationError::DirectoryQuery(code)),
}
}
#[cfg(test)]
mod tests;