use std::fmt;
use windows_sys::Win32::Storage::FileSystem::{
FILE_INFO_BY_HANDLE_CLASS, FileBasicInfo, FileCaseSensitiveInfo, FileFullDirectoryInfo,
FileFullDirectoryRestartInfo, FileIdBothDirectoryInfo, FileIdBothDirectoryRestartInfo,
FileIdExtdDirectoryInfo, FileIdExtdDirectoryRestartInfo, FileIdInfo, FileStreamInfo,
GetFileInformationByHandleEx,
};
use io_buffers::AlignedBuf;
use win_shared_os_owned_handle::SharedHandle;
use crate::Win32Error;
use crate::handle::SharedRaw;
use crate::outcome::{Outcome, out_of_memory, perform_bool};
const BATCH_ALIGNMENT: usize = align_of::<u64>();
const _: () = assert!(BATCH_ALIGNMENT.is_power_of_two());
const REQUIRED_ALIGNMENT: [(FILE_INFO_BY_HANDLE_CLASS, &str, usize); 5] = [
(FileStreamInfo, "FILE_STREAM_INFO", 8),
(FileIdBothDirectoryInfo, "FILE_ID_BOTH_DIR_INFO", 8),
(FileIdBothDirectoryRestartInfo, "FILE_ID_BOTH_DIR_INFO", 8),
(FileFullDirectoryInfo, "FILE_FULL_DIR_INFO", 8),
(FileFullDirectoryRestartInfo, "FILE_FULL_DIR_INFO", 8),
];
const _: () = {
let mut i = 0;
while i < REQUIRED_ALIGNMENT.len() {
assert!(REQUIRED_ALIGNMENT[i].2 <= BATCH_ALIGNMENT);
i += 1;
}
};
fn required_alignment(class: FileInformationClass) -> Option<(&'static str, usize)> {
REQUIRED_ALIGNMENT
.iter()
.find(|(raw, _, _)| *raw == class.as_raw())
.map(|&(_, structure, required)| (structure, required))
}
fn check_alignment(class: FileInformationClass, address: usize) -> Result<(), PerformIntoError> {
match required_alignment(class) {
Some((structure, required)) if !address.is_multiple_of(required) => {
Err(PerformIntoError::Misaligned {
class,
structure,
required,
address,
})
}
_ => Ok(()),
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum PerformIntoError {
Win32(Win32Error),
Misaligned {
class: FileInformationClass,
structure: &'static str,
required: usize,
address: usize,
},
}
impl PerformIntoError {
#[must_use]
pub fn code(&self) -> Option<u32> {
match self {
Self::Win32(error) => Some(error.code()),
Self::Misaligned { .. } => None,
}
}
}
impl fmt::Display for PerformIntoError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Win32(error) => write!(f, "GetFileInformationByHandleEx: {error}"),
Self::Misaligned {
structure,
required,
address,
..
} => write!(
f,
"GetFileInformationByHandleEx not called: {structure} must be aligned on a \
{required}-byte boundary, and the buffer is at {address:#x}"
),
}
}
}
impl std::error::Error for PerformIntoError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Win32(error) => Some(error),
Self::Misaligned { .. } => None,
}
}
}
impl From<Win32Error> for PerformIntoError {
fn from(error: Win32Error) -> Self {
Self::Win32(error)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct FileInformationClass(FILE_INFO_BY_HANDLE_CLASS);
impl FileInformationClass {
pub const BASIC: Self = Self(FileBasicInfo);
pub const ID: Self = Self(FileIdInfo);
pub const CASE_SENSITIVE: Self = Self(FileCaseSensitiveInfo);
pub const ID_EXTD_DIRECTORY: Self = Self(FileIdExtdDirectoryInfo);
pub const ID_EXTD_DIRECTORY_RESTART: Self = Self(FileIdExtdDirectoryRestartInfo);
#[must_use]
pub const fn from_raw(class: FILE_INFO_BY_HANDLE_CLASS) -> Self {
Self(class)
}
#[must_use]
pub const fn as_raw(self) -> FILE_INFO_BY_HANDLE_CLASS {
self.0
}
#[must_use]
pub const fn moves_enumeration_cursor(self) -> bool {
self.0 == FileIdExtdDirectoryInfo || self.0 == FileIdExtdDirectoryRestartInfo
}
}
#[derive(Clone, Debug)]
#[must_use = "an unperformed request queries nothing"]
pub struct QueryFileInformation {
handle: SharedHandle,
class: FileInformationClass,
capacity: usize,
}
impl QueryFileInformation {
pub const DEFAULT_CAPACITY: usize = 64 * 1024;
pub fn new(handle: impl Into<SharedHandle>, class: FileInformationClass) -> Self {
Self {
handle: handle.into(),
class,
capacity: Self::DEFAULT_CAPACITY,
}
}
pub fn with_capacity(mut self, capacity: usize) -> Self {
self.capacity = capacity;
self
}
pub fn handle(&self) -> &SharedHandle {
&self.handle
}
#[must_use]
pub fn class(&self) -> FileInformationClass {
self.class
}
#[must_use]
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn perform(&self) -> Outcome<AlignedBuf> {
let mut buffer =
AlignedBuf::zeroed(self.capacity, BATCH_ALIGNMENT).map_err(|_| out_of_memory())?;
self.call_into(&mut buffer)?;
Ok(buffer)
}
pub fn perform_into(&self, buffer: &mut AlignedBuf) -> Result<(), PerformIntoError> {
check_alignment(self.class, buffer.as_ptr().addr())?;
Ok(self.call_into(buffer)?)
}
fn call_into(&self, buffer: &mut AlignedBuf) -> Outcome<()> {
let size = u32::try_from(buffer.len()).unwrap_or(u32::MAX);
perform_bool(|| {
unsafe {
GetFileInformationByHandleEx(
self.handle.raw(),
self.class.as_raw(),
buffer.as_mut_ptr().cast(),
size,
)
}
})
}
}
impl crate::request::Request for QueryFileInformation {
type Error = crate::Win32Error;
type Output = AlignedBuf;
fn perform(&self) -> Outcome<AlignedBuf> {
Self::perform(self)
}
}
#[cfg(test)]
mod alignment_tests;
#[cfg(test)]
mod tests;