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_ACCESS_DENIED, ERROR_INVALID_FUNCTION, ERROR_INVALID_PARAMETER, ERROR_LOCK_VIOLATION,
ERROR_NOT_SUPPORTED, ERROR_SHARING_VIOLATION, HANDLE,
};
use windows_sys::Win32::Storage::FileSystem::{
BY_HANDLE_FILE_INFORMATION, FILE_FLAG_BACKUP_SEMANTICS, FILE_ID_INFO, FileIdInfo,
GetFileInformationByHandle, GetFileInformationByHandleEx, 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)
}
}
pub(crate) fn identity(file: &File) -> io::Result<(u64, [u8; 16])> {
match file_id_info(file) {
Err(error)
if error.raw_os_error().is_some_and(|code| {
code == ERROR_INVALID_PARAMETER.cast_signed()
|| code == ERROR_INVALID_FUNCTION.cast_signed()
}) =>
{
file_index(file)
}
identity => identity,
}
}
pub(crate) fn file_id_info(file: &File) -> io::Result<(u64, [u8; 16])> {
let mut info: FILE_ID_INFO = unsafe { std::mem::zeroed() };
let size = u32::try_from(std::mem::size_of::<FILE_ID_INFO>()).unwrap_or(u32::MAX);
let read = unsafe {
GetFileInformationByHandleEx(
file.as_raw_handle() as HANDLE,
FileIdInfo,
(&raw mut info).cast(),
size,
)
};
if read == 0 {
return Err(io::Error::last_os_error());
}
Ok((info.VolumeSerialNumber, info.FileId.Identifier))
}
fn file_index(file: &File) -> io::Result<(u64, [u8; 16])> {
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());
}
let index = (u128::from(info.nFileIndexHigh) << 32) | u128::from(info.nFileIndexLow);
Ok((u64::from(info.dwVolumeSerialNumber), index.to_le_bytes()))
}
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) type FileIdentity = (u64, [u8; 16]);
pub(crate) fn file_identity(file: &File) -> io::Result<FileIdentity> {
identity(file)
}
pub(crate) fn path_identity(path: &Path) -> io::Result<Option<FileIdentity>> {
use std::os::windows::fs::OpenOptionsExt;
match std::fs::OpenOptions::new()
.access_mode(0)
.custom_flags(FILE_FLAG_BACKUP_SEMANTICS)
.open(path)
{
Ok(file) => Ok(Some(identity(&file)?)),
Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(None),
Err(error) => Err(error),
}
}
pub(crate) fn same_file_at(a: &Path, b: &Path) -> io::Result<bool> {
Ok(match (path_identity(a)?, path_identity(b)?) {
(Some(a), Some(b)) => a == b,
_ => false,
})
}
fn verbatim(path: &Path) -> io::Result<Vec<u16>> {
use std::os::windows::ffi::OsStrExt;
const VERBATIM: [u16; 4] = [b'\\' as u16, b'\\' as u16, b'?' as u16, b'\\' as u16];
const DEVICE: [u16; 4] = [b'\\' as u16, b'\\' as u16, b'.' as u16, b'\\' as u16];
const UNC: [u16; 2] = [b'\\' as u16, b'\\' as u16];
let given: Vec<u16> = path.as_os_str().encode_wide().collect();
let mut wide = if given.starts_with(&VERBATIM) {
given
} else {
let absolute: Vec<u16> = std::path::absolute(path)?
.as_os_str()
.encode_wide()
.collect();
if absolute.starts_with(&VERBATIM) || absolute.starts_with(&DEVICE) {
absolute
} else if let Some(share) = absolute.strip_prefix(&UNC) {
"\\\\?\\UNC\\"
.encode_utf16()
.chain(share.iter().copied())
.collect()
} else {
VERBATIM.iter().copied().chain(absolute).collect()
}
};
wide.push(0);
Ok(wide)
}
pub(crate) fn rename_exclusive(from: &Path, to: &Path) -> io::Result<()> {
let (from, to) = (verbatim(from)?, verbatim(to)?);
if unsafe { MoveFileExW(from.as_ptr(), to.as_ptr(), MOVEFILE_WRITE_THROUGH) } == 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
pub(crate) fn flag_refused(error: &io::Error) -> bool {
error.raw_os_error().is_some_and(|code| {
code == ERROR_NOT_SUPPORTED.cast_signed() || code == ERROR_INVALID_FUNCTION.cast_signed()
})
}
pub(crate) fn is_retryable_publish_error(error: &io::Error) -> bool {
error.raw_os_error().is_some_and(|code| {
code == ERROR_ACCESS_DENIED.cast_signed()
|| code == ERROR_SHARING_VIOLATION.cast_signed()
|| code == ERROR_LOCK_VIOLATION.cast_signed()
})
}
pub(crate) fn name_limit(_dir: &Path) -> crate::NameLimit {
crate::NameLimit {
max: 255,
unit: crate::NameUnit::Utf16,
}
}
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) {
crate::exit_without_handlers(130);
}
if let Some(handler) = HANDLER.get() {
handler(Termination {
name,
exit_code: if closing { 143 } else { 130 },
closing,
});
}
1
}
pub(crate) fn termination_handler() -> Option<&'static (dyn Fn(Termination) + Send + Sync)> {
HANDLER.get().map(|handler| &**handler)
}
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, PROCESS_MEMORY_COUNTERS_EX,
};
use windows_sys::Win32::System::Threading::GetCurrentProcess;
let mut counters: PROCESS_MEMORY_COUNTERS_EX = unsafe { std::mem::zeroed() };
let size = u32::try_from(std::mem::size_of::<PROCESS_MEMORY_COUNTERS_EX>()).unwrap_or(u32::MAX);
let read = unsafe {
K32GetProcessMemoryInfo(
GetCurrentProcess(),
std::ptr::addr_of_mut!(counters).cast::<PROCESS_MEMORY_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),
footprint_bytes: bytes(counters.PrivateUsage),
peak_footprint_bytes: bytes(counters.PeakPagefileUsage),
}
}
pub(crate) fn memory_capacity() -> crate::MemoryCapacity {
use windows_sys::Win32::System::JobObjects::{
JOB_OBJECT_LIMIT_JOB_MEMORY, JOB_OBJECT_LIMIT_PROCESS_MEMORY,
JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JobObjectExtendedLimitInformation,
QueryInformationJobObject,
};
use windows_sys::Win32::System::SystemInformation::{GlobalMemoryStatusEx, MEMORYSTATUSEX};
let mut status: MEMORYSTATUSEX = unsafe { std::mem::zeroed() };
status.dwLength = u32::try_from(std::mem::size_of::<MEMORYSTATUSEX>()).unwrap_or(u32::MAX);
let read = unsafe { GlobalMemoryStatusEx(&raw mut status) } != 0;
let physical = read.then_some(status.ullTotalPhys);
let mut job: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { std::mem::zeroed() };
let size = u32::try_from(std::mem::size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>())
.unwrap_or(u32::MAX);
let in_job = unsafe {
QueryInformationJobObject(
std::ptr::null_mut(),
JobObjectExtendedLimitInformation,
std::ptr::addr_of_mut!(job).cast(),
size,
std::ptr::null_mut(),
)
} != 0;
let flags = if in_job {
job.BasicLimitInformation.LimitFlags
} else {
0
};
let job_limit = [
(flags & JOB_OBJECT_LIMIT_PROCESS_MEMORY != 0).then_some(job.ProcessMemoryLimit),
(flags & JOB_OBJECT_LIMIT_JOB_MEMORY != 0).then_some(job.JobMemoryLimit),
]
.into_iter()
.flatten()
.filter_map(|limit| u64::try_from(limit).ok())
.min()
.filter(|limit| physical.is_none_or(|physical| *limit < physical));
crate::MemoryCapacity {
physical_bytes: physical,
available_bytes: read.then_some(status.ullAvailPhys),
limit_bytes: job_limit.or(physical),
limit_source: if job_limit.is_some() {
"job"
} else {
"physical"
},
used_in_limit_bytes: None,
}
}
pub(crate) fn prefer_fast_cores() {
use windows_sys::Win32::System::Threading::{
GetCurrentThread, SetThreadInformation, THREAD_POWER_THROTTLING_CURRENT_VERSION,
THREAD_POWER_THROTTLING_EXECUTION_SPEED, THREAD_POWER_THROTTLING_STATE,
ThreadPowerThrottling,
};
let state = THREAD_POWER_THROTTLING_STATE {
Version: THREAD_POWER_THROTTLING_CURRENT_VERSION,
ControlMask: THREAD_POWER_THROTTLING_EXECUTION_SPEED,
StateMask: 0,
};
let _ = unsafe {
SetThreadInformation(
GetCurrentThread(),
ThreadPowerThrottling,
(&raw const state).cast(),
u32::try_from(std::mem::size_of::<THREAD_POWER_THROTTLING_STATE>()).unwrap_or(0),
)
};
}
pub(crate) fn process_cpu_time() -> Option<std::time::Duration> {
use windows_sys::Win32::Foundation::FILETIME;
use windows_sys::Win32::System::Threading::{GetCurrentProcess, GetProcessTimes};
let zero = || FILETIME {
dwLowDateTime: 0,
dwHighDateTime: 0,
};
let (mut created, mut exited, mut kernel, mut user) = (zero(), zero(), zero(), zero());
let read = unsafe {
GetProcessTimes(
GetCurrentProcess(),
&raw mut created,
&raw mut exited,
&raw mut kernel,
&raw mut user,
)
} != 0;
let hundreds =
|time: FILETIME| u64::from(time.dwHighDateTime) << 32 | u64::from(time.dwLowDateTime);
read.then(|| {
std::time::Duration::from_nanos((hundreds(kernel) + hundreds(user)).saturating_mul(100))
})
}
pub(crate) fn unbiased_interrupt_time() -> u64 {
use windows_sys::Win32::System::WindowsProgramming::QueryUnbiasedInterruptTimePrecise;
let mut time = 0_u64;
unsafe { QueryUnbiasedInterruptTimePrecise(&raw mut time) };
time
}
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)
}
pub(crate) fn file_version(path: &Path) -> Option<String> {
use std::os::windows::ffi::OsStrExt;
use windows_sys::Win32::Storage::FileSystem::{
GetFileVersionInfoSizeW, GetFileVersionInfoW, VS_FIXEDFILEINFO, VerQueryValueW,
};
let wide: Vec<u16> = path.as_os_str().encode_wide().chain(Some(0)).collect();
let size = unsafe { GetFileVersionInfoSizeW(wide.as_ptr(), std::ptr::null_mut()) };
if size == 0 {
return None;
}
let mut data = vec![0_u8; usize::try_from(size).ok()?];
if unsafe { GetFileVersionInfoW(wide.as_ptr(), 0, size, data.as_mut_ptr().cast()) } == 0 {
return None;
}
let root: [u16; 2] = [u16::from(b'\\'), 0];
let mut info: *mut std::ffi::c_void = std::ptr::null_mut();
let mut length = 0_u32;
if unsafe {
VerQueryValueW(
data.as_ptr().cast(),
root.as_ptr(),
&raw mut info,
&raw mut length,
)
} == 0
|| info.is_null()
|| usize::try_from(length).ok()? < std::mem::size_of::<VS_FIXEDFILEINFO>()
{
return None;
}
let fixed = unsafe { info.cast::<VS_FIXEDFILEINFO>().read_unaligned() };
Some(format!(
"{}.{}.{}.{}",
fixed.dwFileVersionMS >> 16,
fixed.dwFileVersionMS & 0xffff,
fixed.dwFileVersionLS >> 16,
fixed.dwFileVersionLS & 0xffff
))
}