use std::fs::File;
use std::os::windows::io::AsHandle;
use windows_sys::Win32::Storage::FileSystem::{
FILE_FULL_DIR_INFO, FILE_INFO_BY_HANDLE_CLASS, FileBasicInfo, FileFullDirectoryInfo,
FileFullDirectoryRestartInfo, FileIdBothDirectoryInfo, FileIdBothDirectoryRestartInfo,
FileIdExtdDirectoryInfo, FileIdExtdDirectoryRestartInfo, FileIdInfo, FileNameInfo,
FileStandardInfo, FileStreamInfo,
};
use super::{
BATCH_ALIGNMENT, FileInformationClass, PerformIntoError, QueryFileInformation, check_alignment,
required_alignment,
};
use crate::handle::tests::{Fixture, handle_allocation};
use io_buffers::AlignedBuf;
use win_shared_os_owned_handle::SharedHandle;
const REQUIRING: [(FILE_INFO_BY_HANDLE_CLASS, &str); 5] = [
(FileStreamInfo, "FILE_STREAM_INFO"),
(FileIdBothDirectoryInfo, "FILE_ID_BOTH_DIR_INFO"),
(FileIdBothDirectoryRestartInfo, "FILE_ID_BOTH_DIR_INFO"),
(FileFullDirectoryInfo, "FILE_FULL_DIR_INFO"),
(FileFullDirectoryRestartInfo, "FILE_FULL_DIR_INFO"),
];
fn class(raw: FILE_INFO_BY_HANDLE_CLASS) -> FileInformationClass {
FileInformationClass::from_raw(raw)
}
fn query(file: &File, class: FileInformationClass) -> QueryFileInformation {
QueryFileInformation::new(
SharedHandle::from(
file.as_handle()
.try_clone_to_owned()
.expect("own the handle"),
),
class,
)
}
#[test]
fn the_five_classes_require_eight_bytes_for_the_structure_their_page_names() {
for (raw, structure) in REQUIRING {
assert_eq!(
required_alignment(class(raw)),
Some((structure, 8)),
"class {raw}"
);
}
}
#[test]
fn no_other_class_requires_an_alignment() {
let requiring: Vec<_> = REQUIRING.iter().map(|(raw, _)| *raw).collect();
for raw in (0..64).chain([i32::MAX, i32::MIN, -1]) {
if !requiring.contains(&raw) {
assert_eq!(required_alignment(class(raw)), None, "class {raw}");
}
}
}
#[test]
fn the_classes_this_crate_names_require_none() {
for named in [
FileInformationClass::BASIC,
FileInformationClass::ID,
FileInformationClass::CASE_SENSITIVE,
FileInformationClass::ID_EXTD_DIRECTORY,
FileInformationClass::ID_EXTD_DIRECTORY_RESTART,
] {
assert_eq!(required_alignment(named), None, "{named:?}");
}
}
#[test]
fn every_required_alignment_divides_the_allocating_forms() {
for (raw, _) in REQUIRING {
let (_, required) = required_alignment(class(raw)).expect("required");
assert_eq!(BATCH_ALIGNMENT % required, 0, "class {raw}");
}
}
#[test]
fn an_address_off_the_boundary_is_refused_for_each_requiring_class() {
for (raw, structure) in REQUIRING {
for address in [1usize, 2, 3, 4, 5, 6, 7, 9, 12, 0x1004, usize::MAX] {
match check_alignment(class(raw), address) {
Err(PerformIntoError::Misaligned {
class: refused,
structure: named,
required,
address: at,
}) => {
assert_eq!(refused, class(raw));
assert_eq!(named, structure);
assert_eq!(required, 8);
assert_eq!(at, address);
}
other => panic!("class {raw} at {address:#x}: {other:?}"),
}
}
}
}
#[test]
fn an_address_on_the_boundary_is_accepted_for_each_requiring_class() {
for (raw, _) in REQUIRING {
for address in [0usize, 8, 16, 64, 0x1000, 0x1008, usize::MAX - 7] {
assert!(
check_alignment(class(raw), address).is_ok(),
"class {raw} at {address:#x}"
);
}
}
}
#[test]
fn any_address_is_accepted_for_a_class_that_requires_none() {
for raw in [
FileBasicInfo,
FileStandardInfo,
FileNameInfo,
FileIdInfo,
FileIdExtdDirectoryInfo,
FileIdExtdDirectoryRestartInfo,
0x7fff,
] {
for address in [1usize, 2, 3, 4, 7, 8, 0x1001] {
assert!(
check_alignment(class(raw), address).is_ok(),
"class {raw} at {address:#x}"
);
}
}
}
#[test]
fn a_refusal_carries_no_code_and_names_the_structure() {
let error = check_alignment(class(FileFullDirectoryInfo), 4).expect_err("refused");
assert_eq!(error.code(), None);
assert!(std::error::Error::source(&error).is_none());
let text = error.to_string();
assert!(text.contains("FILE_FULL_DIR_INFO"), "{text}");
assert!(text.contains("8-byte"), "{text}");
assert!(text.contains("0x4"), "{text}");
}
#[test]
fn a_calls_error_carries_its_code_unaltered() {
let error = PerformIntoError::from(crate::Win32Error::from_code(0xC03A_0015));
assert_eq!(error.code(), Some(0xC03A_0015));
assert!(std::error::Error::source(&error).is_some());
}
#[test]
fn a_misaligned_buffer_is_refused_before_the_call_for_each_requiring_class() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::with_extra_files("query-misaligned", 3);
let directory = fixture.open_directory();
for (raw, structure) in REQUIRING {
for align in [1usize, 2, 4] {
let mut buffer = AlignedBuf::zeroed(0, align).expect("an empty buffer");
assert_eq!(
buffer.as_ptr().addr(),
align,
"an empty buffer lies at its alignment"
);
let error = query(&directory, class(raw))
.perform_into(&mut buffer)
.expect_err("misaligned");
assert!(
matches!(error, PerformIntoError::Misaligned { structure: s, .. } if s == structure),
"class {raw}, align {align}: {error:?}"
);
assert_eq!(error.code(), None, "the call was not made");
}
}
}
#[test]
fn an_aligned_empty_buffer_reaches_the_call_for_each_requiring_class() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::with_extra_files("query-aligned-empty", 3);
let directory = fixture.open_directory();
for (raw, _) in REQUIRING {
let mut buffer = AlignedBuf::zeroed(0, 8).expect("an empty buffer");
let error = query(&directory, class(raw))
.perform_into(&mut buffer)
.expect_err("no room");
assert!(
matches!(error, PerformIntoError::Win32(_)),
"class {raw}: {error:?}"
);
}
}
#[test]
fn a_full_directory_class_enumerates_into_an_aligned_buffer() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::with_extra_files("query-full-dir", 3);
let directory = fixture.open_directory();
let mut buffer = AlignedBuf::zeroed(64 * 1024, 8).expect("a buffer");
query(&directory, class(FileFullDirectoryRestartInfo))
.perform_into(&mut buffer)
.expect("the first batch");
let at = std::mem::offset_of!(FILE_FULL_DIR_INFO, FileNameLength);
let name_length = u32::from_ne_bytes(buffer[at..at + 4].try_into().expect("four bytes"));
assert_ne!(name_length, 0);
}
#[test]
fn a_misaligned_buffer_reaches_the_call_for_a_class_that_requires_none() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::with_extra_files("query-misaligned-extd", 3);
let directory = fixture.open_directory();
let mut buffer = AlignedBuf::zeroed(0, 1).expect("an empty buffer");
let error = query(&directory, FileInformationClass::ID_EXTD_DIRECTORY_RESTART)
.perform_into(&mut buffer)
.expect_err("no room");
assert!(matches!(error, PerformIntoError::Win32(_)), "{error:?}");
}