use std::fs::File;
use std::os::windows::io::{AsHandle, AsRawHandle};
use windows_sys::Win32::Foundation::FALSE;
use windows_sys::Win32::Storage::FileSystem::{
BY_HANDLE_FILE_INFORMATION, ExtendedFileIdType, FILE_FLAG_BACKUP_SEMANTICS, FILE_GENERIC_READ,
FILE_LIST_DIRECTORY, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, FileIdType,
GetFileInformationByHandle, ObjectIdType,
};
use super::{FileIdentifier, OpenFileByIdentifier};
use crate::handle::tests::{FILE_CONTENTS, Fixture, handle_allocation};
use crate::{CapturedHandle, SecurityAttributes};
const AUDITED_SHARE: u32 = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
fn file_id_of(file: &File) -> u64 {
let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { std::mem::zeroed() };
let read = unsafe { GetFileInformationByHandle(file.as_raw_handle().cast(), &raw mut info) };
assert_ne!(read, FALSE, "read the fixture file's identity");
(u64::from(info.nFileIndexHigh) << 32) | u64::from(info.nFileIndexLow)
}
fn open_directory_for_hint(fixture: &Fixture) -> File {
fixture.open_directory()
}
#[test]
fn an_object_is_reopened_by_its_identifier() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("byid-reopen");
let file = fixture.open_file();
let id = file_id_of(&file);
let hint = open_directory_for_hint(&fixture);
let reopened = OpenFileByIdentifier::new(
CapturedHandle::capture(hint.as_handle()).expect("capture the volume hint"),
FileIdentifier::FileId(id),
)
.with_desired_access(FILE_GENERIC_READ)
.with_share_mode(AUDITED_SHARE)
.perform()
.expect("reopen the fixture file by id");
assert_eq!(
File::from(reopened)
.metadata()
.expect("read metadata")
.len(),
FILE_CONTENTS.len() as u64
);
}
#[test]
fn the_reopen_survives_the_source_handle_being_closed_first() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("byid-outlives");
let hint = open_directory_for_hint(&fixture);
let request = {
let file = fixture.open_file();
let id = file_id_of(&file);
let request = OpenFileByIdentifier::new(
CapturedHandle::capture(hint.as_handle()).expect("capture the volume hint"),
FileIdentifier::FileId(id),
)
.with_desired_access(FILE_GENERIC_READ)
.with_share_mode(AUDITED_SHARE);
drop(file);
request
};
drop(hint);
let reopened = request
.perform()
.expect("the request outlives both the source handle and the hint it was built from");
assert_eq!(
File::from(reopened)
.metadata()
.expect("read metadata")
.len(),
FILE_CONTENTS.len() as u64
);
}
#[test]
fn a_directory_is_reopened_in_the_audited_shape() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("byid-directory");
let directory = fixture.open_directory();
let id = file_id_of(&directory);
let reopened = OpenFileByIdentifier::new(
CapturedHandle::capture(directory.as_handle()).expect("capture the volume hint"),
FileIdentifier::FileId(id),
)
.with_desired_access(FILE_LIST_DIRECTORY)
.with_share_mode(AUDITED_SHARE)
.with_flags_and_attributes(FILE_FLAG_BACKUP_SEMANTICS)
.perform()
.expect("reopen the fixture directory by id");
assert!(
File::from(reopened)
.metadata()
.expect("read the directory metadata")
.is_dir()
);
}
#[test]
fn an_unknown_identifier_reports_the_raw_code() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("byid-unknown");
let hint = open_directory_for_hint(&fixture);
let outcome = OpenFileByIdentifier::new(
CapturedHandle::capture(hint.as_handle()).expect("capture the volume hint"),
FileIdentifier::FileId(u64::MAX - 1),
)
.with_desired_access(FILE_GENERIC_READ)
.with_share_mode(AUDITED_SHARE)
.perform();
assert!(
outcome.is_err(),
"an identifier naming nothing must fail, unaltered"
);
}
#[test]
fn every_identifier_kind_carries_its_own_win32_tag() {
assert_eq!(FileIdentifier::FileId(7).id_type(), FileIdType);
assert_eq!(FileIdentifier::ObjectId(0).id_type(), ObjectIdType);
assert_eq!(
FileIdentifier::ExtendedFileId([0; 16]).id_type(),
ExtendedFileIdType
);
}
#[test]
fn all_three_identifier_kinds_are_expressible() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("byid-kinds");
let hint = open_directory_for_hint(&fixture);
for identifier in [
FileIdentifier::FileId(1),
FileIdentifier::ObjectId(0x1234),
FileIdentifier::ExtendedFileId([9; 16]),
] {
let request = OpenFileByIdentifier::new(
CapturedHandle::capture(hint.as_handle()).expect("capture the volume hint"),
identifier,
)
.with_desired_access(FILE_GENERIC_READ)
.with_share_mode(AUDITED_SHARE);
assert_eq!(request.identifier(), identifier);
let _ = request.perform();
}
}
#[test]
fn a_new_request_defaults_nothing_and_has_no_creation_disposition() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("byid-defaults");
let hint = open_directory_for_hint(&fixture);
let request = OpenFileByIdentifier::new(
CapturedHandle::capture(hint.as_handle()).expect("capture the volume hint"),
FileIdentifier::FileId(1),
);
assert_eq!(request.desired_access(), 0);
assert_eq!(request.share_mode(), 0);
assert_eq!(request.flags_and_attributes(), 0);
assert!(request.security().is_none());
}
#[test]
fn a_copy_duplicates_the_volume_hint() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("byid-copy");
let hint = open_directory_for_hint(&fixture);
let request = OpenFileByIdentifier::new(
CapturedHandle::capture(hint.as_handle()).expect("capture the volume hint"),
FileIdentifier::FileId(1),
);
let copy = request.try_clone().expect("duplicate the volume hint");
assert_ne!(
copy.volume_hint().as_handle().as_raw_handle(),
request.volume_hint().as_handle().as_raw_handle(),
"each request owns its own duplicate"
);
assert_eq!(copy.identifier(), request.identifier());
}
#[test]
fn a_request_performs_the_same_way_on_another_thread() {
const fn assert_send<T: Send>() {}
const fn assert_sync<T: Sync>() {}
assert_send::<OpenFileByIdentifier>();
assert_sync::<OpenFileByIdentifier>();
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("byid-thread");
let file = fixture.open_file();
let id = file_id_of(&file);
let hint = open_directory_for_hint(&fixture);
let request = OpenFileByIdentifier::new(
CapturedHandle::capture(hint.as_handle()).expect("capture the volume hint"),
FileIdentifier::FileId(id),
)
.with_desired_access(FILE_GENERIC_READ)
.with_share_mode(AUDITED_SHARE);
let length = std::thread::spawn(move || {
let reopened = request.perform().expect("reopen on a worker thread");
File::from(reopened)
.metadata()
.expect("read metadata")
.len()
})
.join()
.expect("the worker did not panic");
assert_eq!(length, FILE_CONTENTS.len() as u64);
}
#[test]
fn every_configured_parameter_reads_back_through_its_own_accessor() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("byid-accessors");
let file = fixture.open_file();
let id = file_id_of(&file);
let hint = open_directory_for_hint(&fixture);
let request = OpenFileByIdentifier::new(
CapturedHandle::capture(hint.as_handle()).expect("capture the volume hint"),
FileIdentifier::FileId(id),
)
.with_desired_access(FILE_GENERIC_READ)
.with_share_mode(FILE_SHARE_READ)
.with_flags_and_attributes(FILE_FLAG_BACKUP_SEMANTICS);
assert_eq!(request.desired_access(), FILE_GENERIC_READ);
assert_eq!(request.share_mode(), FILE_SHARE_READ);
assert_eq!(request.flags_and_attributes(), FILE_FLAG_BACKUP_SEMANTICS);
assert!(
request.security().is_none(),
"nothing was supplied, so nothing must be reported"
);
let configured = [
FILE_GENERIC_READ,
FILE_SHARE_READ,
FILE_FLAG_BACKUP_SEMANTICS,
];
for (index, value) in configured.iter().enumerate() {
assert_ne!(*value, 0, "a zero is indistinguishable from the default");
for other in &configured[index + 1..] {
assert_ne!(
value, other,
"two parameters share a value, so this test cannot tell their \
accessors apart"
);
}
}
}
#[test]
fn supplied_security_attributes_read_back_rather_than_reporting_none() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("byid-security");
let file = fixture.open_file();
let id = file_id_of(&file);
let hint = open_directory_for_hint(&fixture);
let request = OpenFileByIdentifier::new(
CapturedHandle::capture(hint.as_handle()).expect("capture the volume hint"),
FileIdentifier::FileId(id),
)
.with_security(Some(SecurityAttributes::new(None, false)));
assert!(request.security().is_some());
}