use std::path::PathBuf;
pub struct LockingProcess {
pub pid: u32,
pub name: String,
pub exe: Option<PathBuf>,
pub is_deno: bool,
}
#[cfg(not(windows))]
pub fn processes_locking_files(_paths: &[PathBuf]) -> Vec<LockingProcess> {
Vec::new()
}
#[cfg(windows)]
pub fn processes_locking_files(paths: &[PathBuf]) -> Vec<LockingProcess> {
use std::os::windows::ffi::OsStrExt;
use windows_sys::Win32::Foundation::ERROR_MORE_DATA;
use windows_sys::Win32::Foundation::ERROR_SUCCESS;
use windows_sys::Win32::System::RestartManager::CCH_RM_SESSION_KEY;
use windows_sys::Win32::System::RestartManager::RM_PROCESS_INFO;
use windows_sys::Win32::System::RestartManager::RmEndSession;
use windows_sys::Win32::System::RestartManager::RmGetList;
use windows_sys::Win32::System::RestartManager::RmRegisterResources;
use windows_sys::Win32::System::RestartManager::RmStartSession;
if paths.is_empty() {
return Vec::new();
}
unsafe {
let mut session: u32 = 0;
let mut session_key = [0u16; CCH_RM_SESSION_KEY as usize + 1];
if RmStartSession(&mut session, 0, session_key.as_mut_ptr())
!= ERROR_SUCCESS
{
return Vec::new();
}
let wide_paths: Vec<Vec<u16>> = paths
.iter()
.map(|path| {
path
.as_os_str()
.encode_wide()
.chain(std::iter::once(0))
.collect()
})
.collect();
let file_ptrs: Vec<*const u16> =
wide_paths.iter().map(|w| w.as_ptr()).collect();
let registered = RmRegisterResources(
session,
file_ptrs.len() as u32,
file_ptrs.as_ptr(),
0,
std::ptr::null(),
0,
std::ptr::null(),
);
if registered != ERROR_SUCCESS {
RmEndSession(session);
return Vec::new();
}
let mut needed: u32 = 0;
let mut have: u32 = 0;
let mut reboot_reasons: u32 = 0;
let status = RmGetList(
session,
&mut needed,
&mut have,
std::ptr::null_mut(),
&mut reboot_reasons,
);
if (status != ERROR_SUCCESS && status != ERROR_MORE_DATA) || needed == 0 {
RmEndSession(session);
return Vec::new();
}
let mut infos: Vec<RM_PROCESS_INFO> = Vec::with_capacity(needed as usize);
have = needed;
let status = RmGetList(
session,
&mut needed,
&mut have,
infos.as_mut_ptr(),
&mut reboot_reasons,
);
let mut result = Vec::new();
if status == ERROR_SUCCESS {
infos.set_len(have as usize);
for info in &infos {
let pid = info.Process.dwProcessId;
let name = wide_to_string(&info.strAppName);
let exe = process_image_path(pid);
let is_deno = exe.as_deref().is_some_and(is_deno_exe);
result.push(LockingProcess {
pid,
name,
exe,
is_deno,
});
}
}
RmEndSession(session);
result
}
}
#[cfg(windows)]
fn wide_to_string(buf: &[u16]) -> String {
let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len());
String::from_utf16_lossy(&buf[..len])
}
#[cfg(windows)]
fn is_deno_exe(path: &std::path::Path) -> bool {
path
.file_stem()
.and_then(|stem| stem.to_str())
.is_some_and(|stem| stem.eq_ignore_ascii_case("deno"))
}
#[cfg(windows)]
fn process_image_path(pid: u32) -> Option<PathBuf> {
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use windows_sys::Win32::Foundation::CloseHandle;
use windows_sys::Win32::System::Threading::OpenProcess;
use windows_sys::Win32::System::Threading::PROCESS_QUERY_LIMITED_INFORMATION;
use windows_sys::Win32::System::Threading::QueryFullProcessImageNameW;
unsafe {
let handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid);
if handle.is_null() {
return None;
}
let mut buf = [0u16; 1024];
let mut size = buf.len() as u32;
let ok = QueryFullProcessImageNameW(handle, 0, buf.as_mut_ptr(), &mut size);
CloseHandle(handle);
if ok == 0 {
return None;
}
Some(PathBuf::from(OsString::from_wide(&buf[..size as usize])))
}
}
pub fn ensure_stdio_open() {
#[cfg(windows)]
unsafe {
use std::mem::size_of;
use windows_sys::Win32::Foundation::ERROR_INVALID_HANDLE;
use windows_sys::Win32::Foundation::FALSE;
use windows_sys::Win32::Foundation::GetHandleInformation;
use windows_sys::Win32::Foundation::GetLastError;
use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
use windows_sys::Win32::Foundation::TRUE;
use windows_sys::Win32::Security::SECURITY_ATTRIBUTES;
use windows_sys::Win32::Storage::FileSystem::CreateFileA;
use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_NORMAL;
use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_READ;
use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_WRITE;
use windows_sys::Win32::Storage::FileSystem::FILE_READ_ATTRIBUTES;
use windows_sys::Win32::Storage::FileSystem::FILE_SHARE_READ;
use windows_sys::Win32::Storage::FileSystem::FILE_SHARE_WRITE;
use windows_sys::Win32::Storage::FileSystem::OPEN_EXISTING;
use windows_sys::Win32::System::Console::GetStdHandle;
use windows_sys::Win32::System::Console::STD_ERROR_HANDLE;
use windows_sys::Win32::System::Console::STD_INPUT_HANDLE;
use windows_sys::Win32::System::Console::STD_OUTPUT_HANDLE;
use windows_sys::Win32::System::Console::SetStdHandle;
for std_handle in [STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE] {
let handle = GetStdHandle(std_handle);
let is_valid = if handle.is_null() || handle == INVALID_HANDLE_VALUE {
false
} else {
let mut flags: u32 = 0;
match GetHandleInformation(handle, &mut flags) {
FALSE if GetLastError() == ERROR_INVALID_HANDLE => false,
FALSE => {
panic!("GetHandleInformation failed (error {})", GetLastError());
}
_ => true,
}
};
if !is_valid {
let desired_access = match std_handle {
STD_INPUT_HANDLE => FILE_GENERIC_READ,
_ => FILE_GENERIC_WRITE | FILE_READ_ATTRIBUTES,
};
let security_attributes = SECURITY_ATTRIBUTES {
nLength: size_of::<SECURITY_ATTRIBUTES>() as u32,
lpSecurityDescriptor: std::ptr::null_mut(),
bInheritHandle: TRUE,
};
let file_handle = CreateFileA(
c"\\\\?\\NUL".as_ptr() as *const u8,
desired_access,
FILE_SHARE_READ | FILE_SHARE_WRITE,
&security_attributes,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
std::ptr::null_mut(),
);
if file_handle == INVALID_HANDLE_VALUE {
panic!("Could not open NUL device (error {})", GetLastError());
}
if SetStdHandle(std_handle, file_handle) == FALSE {
panic!("SetStdHandle failed (error {})", GetLastError());
}
}
}
}
}