use std::fs::File;
use std::os::windows::io::{AsHandle, AsRawHandle};
use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_DIRECTORY;
use super::QueryFileInformationByHandle;
use crate::CapturedHandle;
use crate::handle::tests::{FILE_CONTENTS, Fixture, handle_allocation};
use crate::query::{FileInformationClass, QueryFileInformation};
const SMALL_BATCH: usize = 320;
const ENOUGH_TO_SPAN_BATCHES: usize = 24;
fn request_for(file: &File) -> QueryFileInformationByHandle {
QueryFileInformationByHandle::new(
CapturedHandle::capture(file.as_handle()).expect("capture the handle"),
)
}
fn file_index(
information: &windows_sys::Win32::Storage::FileSystem::BY_HANDLE_FILE_INFORMATION,
) -> u64 {
(u64::from(information.nFileIndexHigh) << 32) | u64::from(information.nFileIndexLow)
}
#[test]
fn a_file_reports_its_size_and_identity() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("fileinfo-file");
let file = fixture.open_file();
let information = request_for(&file).perform().expect("query the file");
assert_eq!(information.nFileSizeLow, FILE_CONTENTS.len() as u32);
assert_eq!(information.nFileSizeHigh, 0);
assert_ne!(file_index(&information), 0);
assert_ne!(information.dwVolumeSerialNumber, 0);
}
#[test]
fn a_file_reports_a_link_count() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("fileinfo-links");
let file = fixture.open_file();
let information = request_for(&file).perform().expect("query the file");
assert!(
information.nNumberOfLinks >= 1,
"an open file has at least one link"
);
}
#[test]
fn a_directory_is_reported_as_a_directory() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("fileinfo-directory");
let directory = fixture.open_directory();
let information = request_for(&directory)
.perform()
.expect("query the directory");
assert_ne!(
information.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY,
0,
"the directory attribute must be reported"
);
}
#[test]
fn two_handles_to_one_object_report_the_same_identity() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("fileinfo-identity");
let first = fixture.open_file();
let second = fixture.open_file();
let one = request_for(&first)
.perform()
.expect("query the first handle");
let two = request_for(&second)
.perform()
.expect("query the second handle");
assert_eq!(file_index(&one), file_index(&two));
assert_eq!(one.dwVolumeSerialNumber, two.dwVolumeSerialNumber);
}
#[test]
fn the_query_does_not_disturb_an_enumeration_in_progress() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::with_extra_files("fileinfo-interleave", ENOUGH_TO_SPAN_BATCHES);
let directory = fixture.open_directory();
let restart = QueryFileInformation::new(
CapturedHandle::capture(directory.as_handle()).expect("capture the handle"),
FileInformationClass::ID_EXTD_DIRECTORY_RESTART,
)
.with_capacity(SMALL_BATCH);
let first = restart.perform().expect("the first batch");
request_for(&directory)
.perform()
.expect("an interleaved non-Ex query");
let next = QueryFileInformation::new(
CapturedHandle::capture(directory.as_handle()).expect("capture the handle"),
FileInformationClass::ID_EXTD_DIRECTORY,
)
.with_capacity(SMALL_BATCH);
let continued = next.perform().expect("the enumeration continues");
assert_ne!(
first.as_slice(),
continued.as_slice(),
"the cursor must have advanced rather than restarted"
);
}
#[test]
fn a_stale_handle_reports_the_raw_code() {
let _allocating = handle_allocation()
.write()
.expect("the lock is not poisoned");
let fixture = Fixture::new("fileinfo-stale");
let file = fixture.open_file();
let request = request_for(&file);
drop(file);
request
.perform()
.expect("the duplicate outlives its source handle");
}
#[test]
fn a_copy_duplicates_the_handle() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("fileinfo-copy");
let file = fixture.open_file();
let request = request_for(&file);
let copy = request.try_clone().expect("duplicate the handle");
assert_ne!(
copy.handle().as_handle().as_raw_handle(),
request.handle().as_handle().as_raw_handle()
);
copy.perform().expect("the copy queries the same object");
}
#[test]
fn a_query_performs_the_same_way_on_another_thread() {
const fn assert_send<T: Send>() {}
const fn assert_sync<T: Sync>() {}
assert_send::<QueryFileInformationByHandle>();
assert_sync::<QueryFileInformationByHandle>();
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("fileinfo-thread");
let file = fixture.open_file();
let request = request_for(&file);
let size = std::thread::spawn(move || {
request
.perform()
.expect("query on a worker that never saw the file")
.nFileSizeLow
})
.join()
.expect("the worker did not panic");
assert_eq!(size, FILE_CONTENTS.len() as u32);
}