use std::fs::File;
use std::io;
use std::os::windows::io::AsRawHandle;
use std::path::Path;
use std::sync::OnceLock;
use std::sync::atomic::{AtomicBool, Ordering};
use windows_sys::Win32::Foundation::{ERROR_LOCK_VIOLATION, HANDLE};
use windows_sys::Win32::Storage::FileSystem::{
BY_HANDLE_FILE_INFORMATION, GetFileInformationByHandle, LOCKFILE_EXCLUSIVE_LOCK,
LOCKFILE_FAIL_IMMEDIATELY, LockFileEx, MOVEFILE_WRITE_THROUGH, MoveFileExW,
};
use windows_sys::Win32::System::Console::{
CTRL_BREAK_EVENT, CTRL_C_EVENT, CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, CTRL_SHUTDOWN_EVENT,
SetConsoleCtrlHandler,
};
use windows_sys::Win32::System::IO::OVERLAPPED;
use windows_sys::core::BOOL;
use crate::{ProcessMemory, Termination};
const LOCK_OFFSET: u64 = i64::MAX as u64;
pub(crate) fn try_lock_exclusive(file: &File) -> io::Result<bool> {
let mut overlapped: OVERLAPPED = unsafe { std::mem::zeroed() };
overlapped.Anonymous.Anonymous.Offset = (LOCK_OFFSET & 0xFFFF_FFFF) as u32;
overlapped.Anonymous.Anonymous.OffsetHigh = (LOCK_OFFSET >> 32) as u32;
let locked = unsafe {
LockFileEx(
file.as_raw_handle() as HANDLE,
LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY,
0,
1,
0,
&raw mut overlapped,
)
};
if locked != 0 {
return Ok(true);
}
let error = io::Error::last_os_error();
if error.raw_os_error() == Some(ERROR_LOCK_VIOLATION.cast_signed()) {
Ok(false)
} else {
Err(error)
}
}
fn identity(file: &File) -> io::Result<(u32, u32, u32)> {
let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { std::mem::zeroed() };
if unsafe { GetFileInformationByHandle(file.as_raw_handle() as HANDLE, &raw mut info) } == 0 {
return Err(io::Error::last_os_error());
}
Ok((
info.dwVolumeSerialNumber,
info.nFileIndexHigh,
info.nFileIndexLow,
))
}
pub(crate) fn same_file(file: &File, path: &Path) -> io::Result<bool> {
let named = match File::open(path) {
Ok(named) => named,
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::PermissionDenied
) =>
{
return Ok(false);
}
Err(error) => return Err(error),
};
Ok(identity(file)? == identity(&named)?)
}
pub(crate) fn rename_no_replace(from: &Path, to: &Path) -> io::Result<()> {
use std::os::windows::ffi::OsStrExt;
let wide = |path: &Path| {
path.as_os_str()
.encode_wide()
.chain(std::iter::once(0))
.collect::<Vec<u16>>()
};
let (from, to) = (wide(from), wide(to));
if unsafe { MoveFileExW(from.as_ptr(), to.as_ptr(), MOVEFILE_WRITE_THROUGH) } == 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
static HANDLER: OnceLock<Box<dyn Fn(Termination) + Send + Sync>> = OnceLock::new();
static INTERRUPTED: AtomicBool = AtomicBool::new(false);
unsafe extern "system" fn on_console_event(event: u32) -> BOOL {
let (name, closing) = match event {
CTRL_C_EVENT => ("CTRL_C_EVENT", false),
CTRL_BREAK_EVENT => ("CTRL_BREAK_EVENT", false),
CTRL_CLOSE_EVENT => ("CTRL_CLOSE_EVENT", true),
CTRL_LOGOFF_EVENT => ("CTRL_LOGOFF_EVENT", true),
CTRL_SHUTDOWN_EVENT => ("CTRL_SHUTDOWN_EVENT", true),
_ => return 0,
};
if !closing && INTERRUPTED.swap(true, Ordering::AcqRel) {
return 0;
}
if let Some(handler) = HANDLER.get() {
handler(Termination {
name,
exit_code: if closing { 143 } else { 130 },
closing,
});
}
1
}
pub(crate) fn on_termination(handler: Box<dyn Fn(Termination) + Send + Sync>) -> io::Result<()> {
if HANDLER.set(handler).is_err() {
return Err(io::Error::new(
io::ErrorKind::AlreadyExists,
"a termination handler is already installed",
));
}
if unsafe { SetConsoleCtrlHandler(Some(on_console_event), 1) } == 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
pub(crate) fn process_memory() -> ProcessMemory {
use windows_sys::Win32::System::ProcessStatus::{
K32GetProcessMemoryInfo, PROCESS_MEMORY_COUNTERS,
};
use windows_sys::Win32::System::Threading::GetCurrentProcess;
let mut counters: PROCESS_MEMORY_COUNTERS = unsafe { std::mem::zeroed() };
let size = u32::try_from(std::mem::size_of::<PROCESS_MEMORY_COUNTERS>()).unwrap_or(u32::MAX);
let read =
unsafe { K32GetProcessMemoryInfo(GetCurrentProcess(), &raw mut counters, size) } != 0;
let bytes = |value: usize| read.then(|| u64::try_from(value).ok()).flatten();
ProcessMemory {
resident_bytes: bytes(counters.WorkingSetSize),
peak_resident_bytes: bytes(counters.PeakWorkingSetSize),
}
}
pub(crate) fn thread_cpu_work() -> Option<u64> {
use windows_sys::Win32::System::Threading::GetCurrentThread;
use windows_sys::Win32::System::WindowsProgramming::QueryThreadCycleTime;
let mut cycles = 0_u64;
let read = unsafe { QueryThreadCycleTime(GetCurrentThread(), &raw mut cycles) } != 0;
read.then_some(cycles)
}