use std::fs::File;
use std::os::windows::io::{AsHandle, AsRawHandle};
use windows_sys::Win32::Foundation::{ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND};
use windows_sys::Win32::Storage::FileSystem::{
FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OVERLAPPED, FILE_GENERIC_READ, FILE_LIST_DIRECTORY,
FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING,
};
use wtf_string::Wtf16String;
use super::OpenFile;
use crate::handle::tests::{FILE_CONTENTS, Fixture, handle_allocation};
use crate::security::tests::Absolute;
use crate::{CapturedHandle, SecurityAttributes, SecurityDescriptor, prepare};
const AUDITED_SHARE: u32 = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
fn request_for(path: &std::path::Path) -> OpenFile {
let text = path.to_str().expect("the fixture path is valid UTF-8");
OpenFile::new(prepare(&Wtf16String::from(text)).expect("prepare the fixture path"))
}
fn unassociated_directory_request(path: &std::path::Path) -> OpenFile {
request_for(path)
.with_desired_access(FILE_LIST_DIRECTORY)
.with_share_mode(AUDITED_SHARE)
.with_creation_disposition(OPEN_EXISTING)
.with_flags_and_attributes(FILE_FLAG_BACKUP_SEMANTICS)
}
fn overlapped_directory_request(path: &std::path::Path) -> OpenFile {
unassociated_directory_request(path)
.with_flags_and_attributes(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED)
}
#[test]
fn the_unassociated_directory_shape_opens() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("open-unassociated");
let opened = unassociated_directory_request(fixture.directory())
.perform()
.expect("open the fixture directory");
assert!(
File::from(opened)
.metadata()
.expect("read the directory metadata")
.is_dir()
);
}
#[test]
fn the_overlapped_directory_shape_opens() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("open-overlapped");
let opened = overlapped_directory_request(fixture.directory())
.perform()
.expect("open the fixture directory for overlapped use");
assert!(!opened.as_raw_handle().is_null());
}
#[test]
fn the_ordinary_file_shape_opens() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("open-file");
let opened = request_for(&fixture.file())
.with_desired_access(FILE_GENERIC_READ)
.with_share_mode(FILE_SHARE_READ)
.with_creation_disposition(OPEN_EXISTING)
.perform()
.expect("open the fixture file");
assert_eq!(
File::from(opened).metadata().expect("read metadata").len(),
FILE_CONTENTS.len() as u64
);
}
#[test]
fn the_overlapped_flag_is_carried_rather_than_decided() {
let fixture = Fixture::new("open-flag-split");
let plain = unassociated_directory_request(fixture.directory());
let overlapped = overlapped_directory_request(fixture.directory());
assert_eq!(plain.flags_and_attributes(), FILE_FLAG_BACKUP_SEMANTICS);
assert_eq!(
overlapped.flags_and_attributes(),
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED
);
assert_eq!(
plain.flags_and_attributes() | FILE_FLAG_OVERLAPPED,
overlapped.flags_and_attributes(),
"the two shapes differ by exactly the one flag and nothing else"
);
}
#[test]
fn a_new_request_defaults_nothing_on_the_callers_behalf() {
let fixture = Fixture::new("open-defaults");
let request = request_for(&fixture.file());
assert_eq!(request.desired_access(), 0);
assert_eq!(request.share_mode(), 0);
assert_eq!(request.creation_disposition(), 0);
assert_eq!(request.flags_and_attributes(), 0);
assert!(request.security().is_none());
assert!(request.template().is_none());
}
#[test]
fn backup_semantics_is_not_implied_for_a_directory() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("open-no-backup");
let outcome = request_for(fixture.directory())
.with_desired_access(FILE_LIST_DIRECTORY)
.with_share_mode(AUDITED_SHARE)
.with_creation_disposition(OPEN_EXISTING)
.perform();
assert!(
outcome.is_err(),
"omitting FILE_FLAG_BACKUP_SEMANTICS must reach Windows unaltered"
);
}
#[test]
fn a_missing_path_reports_the_raw_code_unaltered() {
let fixture = Fixture::new("open-missing");
let absent = fixture.directory().join("no-such-file.t");
let error = request_for(&absent)
.with_desired_access(FILE_GENERIC_READ)
.with_share_mode(FILE_SHARE_READ)
.with_creation_disposition(OPEN_EXISTING)
.perform()
.expect_err("a missing file cannot be opened");
assert_eq!(
error.code(),
ERROR_FILE_NOT_FOUND,
"the code is passed through, not reclassified"
);
}
#[test]
fn a_missing_directory_is_distinguishable_from_a_missing_file() {
let fixture = Fixture::new("open-missing-dir");
let absent = fixture.directory().join("no-such-dir").join("file.t");
let error = request_for(&absent)
.with_desired_access(FILE_GENERIC_READ)
.with_share_mode(FILE_SHARE_READ)
.with_creation_disposition(OPEN_EXISTING)
.perform()
.expect_err("a missing directory cannot be traversed");
assert_eq!(error.code(), ERROR_PATH_NOT_FOUND);
}
#[test]
fn security_attributes_are_expressible_though_no_consumer_uses_them() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("open-security");
let absolute = Absolute::with_populated_dacl();
let descriptor =
unsafe { SecurityDescriptor::capture(absolute.as_ptr()) }.expect("capture a descriptor");
let request = request_for(&fixture.file())
.with_desired_access(FILE_GENERIC_READ)
.with_share_mode(FILE_SHARE_READ)
.with_creation_disposition(OPEN_EXISTING)
.with_security(Some(SecurityAttributes::new(Some(descriptor), false)));
assert!(request.security().is_some());
request.perform().expect("open with security attributes");
}
#[test]
fn a_template_handle_is_expressible_though_no_consumer_uses_one() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("open-template");
let template_source = fixture.open_file();
let template =
CapturedHandle::capture(template_source.as_handle()).expect("capture the template handle");
let request = request_for(&fixture.file())
.with_desired_access(FILE_GENERIC_READ)
.with_share_mode(FILE_SHARE_READ)
.with_creation_disposition(OPEN_EXISTING)
.with_template(Some(template));
assert!(request.template().is_some());
request.perform().expect("open with a template handle");
}
#[test]
fn a_request_survives_every_input_it_was_built_from() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let (request, expected_len) = {
let fixture = Fixture::new("open-outlives");
let template_source = fixture.open_file();
let absolute = Absolute::with_populated_dacl();
let descriptor = unsafe { SecurityDescriptor::capture(absolute.as_ptr()) }
.expect("capture a descriptor");
let request = request_for(&fixture.file())
.with_desired_access(FILE_GENERIC_READ)
.with_share_mode(FILE_SHARE_READ)
.with_creation_disposition(OPEN_EXISTING)
.with_security(Some(SecurityAttributes::new(Some(descriptor), false)))
.with_template(Some(
CapturedHandle::capture(template_source.as_handle())
.expect("capture the template handle"),
));
std::mem::forget(fixture);
(request, FILE_CONTENTS.len() as u64)
};
let opened = request
.perform()
.expect("open after every input is dropped");
assert_eq!(
File::from(opened).metadata().expect("read metadata").len(),
expected_len
);
}
#[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::<OpenFile>();
assert_sync::<OpenFile>();
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("open-thread");
let request = unassociated_directory_request(fixture.directory());
let opened = std::thread::spawn(move || request.perform())
.join()
.expect("the worker did not panic")
.expect("open on a worker thread");
assert!(
File::from(opened)
.metadata()
.expect("read the directory metadata")
.is_dir()
);
}
#[test]
fn a_request_can_be_performed_more_than_once() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("open-repeat");
let request = unassociated_directory_request(fixture.directory());
let first = request.perform().expect("first open");
let second = request.perform().expect("second open");
assert_ne!(
first.as_raw_handle(),
second.as_raw_handle(),
"two opens are two handles"
);
}
#[test]
fn a_copy_is_fallible_because_a_request_may_own_a_handle() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("open-copy");
let request = unassociated_directory_request(fixture.directory());
let copy = request.try_clone().expect("a request with no template");
drop(request);
assert!(
File::from(copy.perform().expect("the copy opens"))
.metadata()
.expect("read the directory metadata")
.is_dir()
);
}
#[test]
fn a_copy_duplicates_the_template_rather_than_sharing_the_owner() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("open-copy-template");
let template_source = fixture.open_file();
let request = request_for(&fixture.file())
.with_desired_access(FILE_GENERIC_READ)
.with_share_mode(FILE_SHARE_READ)
.with_creation_disposition(OPEN_EXISTING)
.with_template(Some(
CapturedHandle::capture(template_source.as_handle())
.expect("capture the template handle"),
));
let copy = request.try_clone().expect("duplicate the template");
assert_ne!(
copy.template()
.expect("the copy kept a template")
.as_handle()
.as_raw_handle(),
request
.template()
.expect("the original kept a template")
.as_handle()
.as_raw_handle(),
"each request owns its own duplicate"
);
drop(request);
copy.perform()
.expect("the copy's template outlived the original");
}