use std::path::Path;
use std::sync::{Arc, Barrier};
use crate::fsutil::{
MutationRoot, create_new_file_required, read_file_if_exists,
set_anchor_verification_barrier_for_test,
};
use crate::test_support::unique_temp_dir;
fn mutation_root(path: &Path) -> MutationRoot {
match MutationRoot::open(path) {
Ok(root) => root,
Err(error) => panic!("test mutation root failed: {error}"),
}
}
#[test]
fn identity_verification_does_not_disturb_ordinary_operation() {
let root_path = unique_temp_dir("windows-authority-positive-control");
let root = mutation_root(&root_path);
let relative = Path::new("state");
assert!(
create_new_file_required(&root, relative, b"unchanged anchor").is_ok(),
"a write through the same, unswapped anchor must still succeed"
);
assert_eq!(
read_file_if_exists(&root, relative).ok().flatten(),
Some(b"unchanged anchor".to_vec()),
"a read through the same, unswapped anchor must still see what was written"
);
let _ = std::fs::remove_dir_all(root_path);
}
#[test]
fn a_users_own_rename_of_the_anchor_succeeds_while_the_handle_is_retained() {
let root_path = unique_temp_dir("windows-authority-rename-not-blocked");
let root = mutation_root(&root_path);
let renamed_path = root_path.with_extension("renamed-by-user");
let _ = std::fs::remove_dir_all(&renamed_path);
let rename_result = std::fs::rename(&root_path, &renamed_path);
assert!(
rename_result.is_ok(),
"a user must be able to rename the repository root even while prikk retains an open \
handle to it -- FILE_SHARE_DELETE exists exactly for this: {rename_result:?}"
);
drop(root);
let _ = std::fs::remove_dir_all(&renamed_path);
}
#[test]
fn a_replacement_installed_between_path_re_derivation_and_open_is_refused()
-> prikk_error::Result<()> {
let root_path = unique_temp_dir("windows-authority-anchor-race");
let root = mutation_root(&root_path);
let aside_path = root_path.with_extension("raced-aside");
let _ = std::fs::remove_dir_all(&aside_path);
let barrier = Arc::new(Barrier::new(2));
let thread_root = root.clone();
let thread_barrier = Arc::clone(&barrier);
let handle = std::thread::spawn(move || {
set_anchor_verification_barrier_for_test(thread_barrier);
create_new_file_required(&thread_root, Path::new("state"), b"raced")
});
barrier.wait();
let rename_result = std::fs::rename(&root_path, &aside_path);
let create_result = std::fs::create_dir(&root_path);
barrier.wait();
let join_result = handle
.join()
.map_err(|_| prikk_error::PrikkError::Io("anchor race thread panicked".to_string()))?;
let _ = std::fs::remove_dir_all(&root_path);
let _ = std::fs::remove_dir_all(&aside_path);
rename_result.map_err(|error| {
prikk_error::PrikkError::Io(format!("renaming the anchor aside: {error}"))
})?;
create_result.map_err(|error| {
prikk_error::PrikkError::Io(format!("creating the replacement anchor: {error}"))
})?;
match join_result {
Ok(()) => panic!(
"an operation that opens a replacement installed at the anchor's own path must be \
refused, not succeed against the impostor"
),
Err(error) => {
let message = error.to_string();
assert!(
message.contains("Windows anchor replaced"),
"expected the specific anchor-replaced diagnostic, not merely any error: {message}"
);
}
}
Ok(())
}