use std::fs::{File, OpenOptions};
use std::io::Write;
use std::process::Command;
fn main() {
let args: Vec<String> = std::env::args().collect();
match args.get(1).map(String::as_str) {
Some("child-ops") => child_ops(&args[2]),
Some("child-crash") => child_crash(&args[2]),
_ => parent(),
}
}
fn child_ops(path: &str) {
match std::fs::read(path) {
Ok(bytes) => println!(" child unlocked-read -> Ok({} bytes)", bytes.len()),
Err(e) => println!(" child unlocked-read -> Err({:?})", e.kind()),
}
match OpenOptions::new().append(true).open(path) {
Ok(mut f) => match f.write_all(b"x") {
Ok(()) => println!(" child unlocked-append -> Ok"),
Err(e) => println!(" child unlocked-append -> Err({:?})", e.kind()),
},
Err(e) => println!(" child open-for-append -> Err({:?})", e.kind()),
}
match OpenOptions::new().read(true).write(true).open(path) {
Ok(f) => {
println!(" child try_lock (exclusive) -> {:?}", f.try_lock().map(|_| ()));
println!(" child try_lock_shared -> {:?}", f.try_lock_shared().map(|_| ()));
}
Err(e) => println!(" child open-for-lock -> Err({:?})", e.kind()),
}
}
fn child_crash(path: &str) {
let f = OpenOptions::new().read(true).write(true).open(path).unwrap();
match f.try_lock() {
Ok(_) => {
println!(" crash-child took exclusive -> Ok (now exiting without unlock)");
std::process::exit(0);
}
Err(e) => {
println!(" crash-child took exclusive -> Err({:?})", e);
std::process::exit(1);
}
}
}
fn run_child(mode: &str, path: &str) {
let exe = std::env::current_exe().expect("current_exe");
let out = Command::new(exe)
.arg(mode)
.arg(path)
.output()
.expect("spawn child");
print!("{}", String::from_utf8_lossy(&out.stdout));
let err = String::from_utf8_lossy(&out.stderr);
if !err.trim().is_empty() {
print!(" child stderr: {}", err);
}
}
fn parent() {
println!("=== lock_probe report ===");
println!("os: {} / arch: {} / family: {}", std::env::consts::OS, std::env::consts::ARCH, std::env::consts::FAMILY);
let dir = std::env::temp_dir();
let path = dir.join(format!("htt_lock_probe_{}.bin", std::process::id()));
let path_str = path.to_string_lossy().to_string();
std::fs::write(&path, b"lock-probe-payload").unwrap();
{
let holder = File::options().read(true).write(true).open(&path).unwrap();
holder
.try_lock()
.expect("parent could not take the exclusive lock on a fresh file");
println!("\n[A] parent holds EXCLUSIVE:");
run_child("child-ops", &path_str);
holder.unlock().expect("unlock after phase A");
}
{
let holder = File::options().read(true).write(true).open(&path).unwrap();
holder
.try_lock_shared()
.expect("parent could not take the shared lock");
println!("\n[B] parent holds SHARED:");
run_child("child-ops", &path_str);
holder.unlock().expect("unlock after phase B");
}
{
println!("\n[C] crash release:");
run_child("child-crash", &path_str);
let f = File::options().read(true).write(true).open(&path).unwrap();
println!(
" parent try_lock after child death -> {:?} (Ok = kernel auto-released)",
f.try_lock().map(|_| ())
);
let _ = f.unlock();
}
{
println!("\n[D] same-host two-handle matrix:");
let a = File::options().read(true).write(true).open(&path).unwrap();
let b = File::options().read(true).write(true).open(&path).unwrap();
a.try_lock().unwrap();
println!(" a=EX, b try_lock -> {:?}", b.try_lock().map(|_| ()));
println!(" a=EX, b try_lock_shared -> {:?}", b.try_lock_shared().map(|_| ()));
a.unlock().unwrap();
a.try_lock_shared().unwrap();
println!(" a=SH, b try_lock_shared -> {:?}", b.try_lock_shared().map(|_| ()));
println!(" a=SH, b try_lock -> {:?}", b.try_lock().map(|_| ()));
}
let _ = std::fs::remove_file(&path);
println!("\n=== end of report — paste this whole output back ===");
}