mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Refactor process module into the core rust lib
This commit is contained in:
@@ -2,6 +2,7 @@ use anyhow::{anyhow, Result};
|
|||||||
use rand::distr::{Alphanumeric, SampleString};
|
use rand::distr::{Alphanumeric, SampleString};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::{path::Path, thread, time::Duration};
|
use std::{path::Path, thread, time::Duration};
|
||||||
|
use velopack::process;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum OperationWait {
|
pub enum OperationWait {
|
||||||
@@ -12,11 +13,11 @@ pub enum OperationWait {
|
|||||||
|
|
||||||
pub fn operation_wait(wait: OperationWait) {
|
pub fn operation_wait(wait: OperationWait) {
|
||||||
if let OperationWait::WaitPid(pid) = wait {
|
if let OperationWait::WaitPid(pid) = wait {
|
||||||
if let Err(e) = super::wait_for_pid_to_exit(pid, 60_000) {
|
if let Err(e) = process::wait_for_pid_to_exit(pid, Duration::from_secs(60)) {
|
||||||
warn!("Failed to wait for process ({}) to exit ({}). Continuing...", pid, e);
|
warn!("Failed to wait for process ({}) to exit ({}). Continuing...", pid, e);
|
||||||
}
|
}
|
||||||
} else if let OperationWait::WaitParent = wait {
|
} else if let OperationWait::WaitParent = wait {
|
||||||
if let Err(e) = super::wait_for_parent_to_exit(60_000) {
|
if let Err(e) = process::wait_for_parent_to_exit(Duration::from_secs(60)) {
|
||||||
warn!("Failed to wait for parent process to exit ({}). Continuing...", e);
|
warn!("Failed to wait for parent process to exit ({}). Continuing...", e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,89 +1,21 @@
|
|||||||
use ::windows::Win32::System::ProcessStatus::EnumProcesses;
|
use crate::windows::strings;
|
||||||
use ::windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
|
use ::windows::{
|
||||||
use anyhow::{anyhow, bail, Result};
|
core::PWSTR,
|
||||||
|
Win32::{
|
||||||
|
Foundation::CloseHandle,
|
||||||
|
System::ProcessStatus::EnumProcesses,
|
||||||
|
System::Threading::{OpenProcess, QueryFullProcessImageNameW, PROCESS_NAME_WIN32, PROCESS_QUERY_LIMITED_INFORMATION},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use anyhow::{bail, Result};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fs,
|
fs,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::Command as Process,
|
|
||||||
};
|
};
|
||||||
use windows::Wdk::System::Threading::{NtQueryInformationProcess, ProcessBasicInformation};
|
use velopack::{locator::VelopackLocator, process};
|
||||||
use windows::Win32::System::Threading::{GetCurrentProcess, PROCESS_BASIC_INFORMATION};
|
|
||||||
use winsafe::{self as w, co, prelude::*};
|
|
||||||
|
|
||||||
use velopack::locator::VelopackLocator;
|
|
||||||
|
|
||||||
pub fn wait_for_pid_to_exit(pid: u32, ms_to_wait: u32) -> Result<()> {
|
|
||||||
info!("Waiting {}ms for process ({}) to exit.", ms_to_wait, pid);
|
|
||||||
let handle = w::HPROCESS::OpenProcess(co::PROCESS::SYNCHRONIZE, false, pid)?;
|
|
||||||
match handle.WaitForSingleObject(Some(ms_to_wait)) {
|
|
||||||
Ok(co::WAIT::OBJECT_0) => Ok(()),
|
|
||||||
// Ok(co::WAIT::TIMEOUT) => Ok(()),
|
|
||||||
_ => Err(anyhow!("WaitForSingleObject Failed.")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wait_for_parent_to_exit(ms_to_wait: u32) -> Result<()> {
|
|
||||||
info!("Reading parent process information.");
|
|
||||||
let basic_info = ProcessBasicInformation;
|
|
||||||
let handle = unsafe { GetCurrentProcess() };
|
|
||||||
let mut return_length: u32 = 0;
|
|
||||||
let return_length_ptr: *mut u32 = &mut return_length as *mut u32;
|
|
||||||
|
|
||||||
let mut info = PROCESS_BASIC_INFORMATION {
|
|
||||||
AffinityMask: 0,
|
|
||||||
BasePriority: 0,
|
|
||||||
ExitStatus: Default::default(),
|
|
||||||
InheritedFromUniqueProcessId: 0,
|
|
||||||
PebBaseAddress: std::ptr::null_mut(),
|
|
||||||
UniqueProcessId: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let info_ptr: *mut ::core::ffi::c_void = &mut info as *mut _ as *mut ::core::ffi::c_void;
|
|
||||||
let info_size = std::mem::size_of::<PROCESS_BASIC_INFORMATION>() as u32;
|
|
||||||
let hres = unsafe { NtQueryInformationProcess(handle, basic_info, info_ptr, info_size, return_length_ptr) };
|
|
||||||
if hres.is_err() {
|
|
||||||
return Err(anyhow!("Failed to query process information: {:?}", hres));
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.InheritedFromUniqueProcessId <= 1 {
|
|
||||||
// the parent process has exited
|
|
||||||
info!("The parent process ({}) has already exited", info.InheritedFromUniqueProcessId);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_pid_start_time(process: w::HPROCESS) -> Result<u64> {
|
|
||||||
let mut creation = w::FILETIME::default();
|
|
||||||
let mut exit = w::FILETIME::default();
|
|
||||||
let mut kernel = w::FILETIME::default();
|
|
||||||
let mut user = w::FILETIME::default();
|
|
||||||
process.GetProcessTimes(&mut creation, &mut exit, &mut kernel, &mut user)?;
|
|
||||||
Ok(((creation.dwHighDateTime as u64) << 32) | creation.dwLowDateTime as u64)
|
|
||||||
}
|
|
||||||
|
|
||||||
let permissions = co::PROCESS::QUERY_LIMITED_INFORMATION | co::PROCESS::SYNCHRONIZE;
|
|
||||||
let parent_handle = w::HPROCESS::OpenProcess(permissions, false, info.InheritedFromUniqueProcessId as u32)?;
|
|
||||||
let parent_start_time = get_pid_start_time(unsafe { parent_handle.raw_copy() })?;
|
|
||||||
let myself_start_time = get_pid_start_time(w::HPROCESS::GetCurrentProcess())?;
|
|
||||||
|
|
||||||
if parent_start_time > myself_start_time {
|
|
||||||
// the parent process has exited and the id has been re-used
|
|
||||||
info!(
|
|
||||||
"The parent process ({}) has already exited. parent_start={}, my_start={}",
|
|
||||||
info.InheritedFromUniqueProcessId, parent_start_time, myself_start_time
|
|
||||||
);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Waiting {}ms for parent process ({}) to exit.", ms_to_wait, info.InheritedFromUniqueProcessId);
|
|
||||||
match parent_handle.WaitForSingleObject(Some(ms_to_wait)) {
|
|
||||||
Ok(co::WAIT::OBJECT_0) => Ok(()),
|
|
||||||
// Ok(co::WAIT::TIMEOUT) => Ok(()),
|
|
||||||
_ => Err(anyhow!("WaitForSingleObject Failed.")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/nushell/nushell/blob/4458aae3d41517d74ce1507ad3e8cd94021feb16/crates/nu-system/src/windows.rs#L593
|
// https://github.com/nushell/nushell/blob/4458aae3d41517d74ce1507ad3e8cd94021feb16/crates/nu-system/src/windows.rs#L593
|
||||||
fn get_pids() -> Result<Vec<u32>> {
|
fn get_pids() -> Result<Vec<u32>> {
|
||||||
@@ -101,52 +33,46 @@ fn get_pids() -> Result<Vec<u32>> {
|
|||||||
Ok(pids.iter().map(|x| *x as u32).collect())
|
Ok(pids.iter().map(|x| *x as u32).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_processes_running_in_directory<P: AsRef<Path>>(dir: P) -> Result<HashMap<u32, PathBuf>> {
|
unsafe fn get_processes_running_in_directory<P: AsRef<Path>>(dir: P) -> Result<HashMap<u32, PathBuf>> {
|
||||||
let dir = dir.as_ref();
|
let dir = dir.as_ref();
|
||||||
let mut oup = HashMap::new();
|
let mut oup = HashMap::new();
|
||||||
|
|
||||||
|
let mut full_path_vec = vec![0; i16::MAX as usize];
|
||||||
|
let full_path_ptr = PWSTR(full_path_vec.as_mut_ptr());
|
||||||
|
|
||||||
for pid in get_pids()? {
|
for pid in get_pids()? {
|
||||||
// I don't like using catch_unwind, but QueryFullProcessImageName seems to panic
|
let process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
|
||||||
// when it reaches a mingw64 process. This is a workaround.
|
if process.is_err() {
|
||||||
let process_path = std::panic::catch_unwind(|| {
|
continue;
|
||||||
let process = w::HPROCESS::OpenProcess(co::PROCESS::QUERY_LIMITED_INFORMATION, false, pid);
|
}
|
||||||
if let Err(_) = process {
|
|
||||||
// trace!("Failed to open process: {} ({})", pid, e);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let process = process.unwrap();
|
let process = process.unwrap();
|
||||||
let full_path = process.QueryFullProcessImageName(co::PROCESS_NAME::WIN32);
|
if process.is_invalid() {
|
||||||
if let Err(_) = full_path {
|
continue;
|
||||||
// trace!("Failed to query process path: {} ({})", pid, e);
|
}
|
||||||
return None;
|
|
||||||
}
|
|
||||||
return Some(full_path.unwrap());
|
|
||||||
});
|
|
||||||
|
|
||||||
match process_path {
|
let mut full_path_len = full_path_vec.len() as u32;
|
||||||
Ok(Some(full_path)) => {
|
if QueryFullProcessImageNameW(process, PROCESS_NAME_WIN32, full_path_ptr, &mut full_path_len).is_err() {
|
||||||
let full_path = Path::new(&full_path);
|
let _ = CloseHandle(process);
|
||||||
if let Ok(is_subpath) = crate::windows::is_sub_path(full_path, dir) {
|
continue;
|
||||||
if is_subpath {
|
}
|
||||||
oup.insert(pid, full_path.to_path_buf());
|
|
||||||
}
|
let full_path = strings::u16_to_string(&full_path_vec);
|
||||||
}
|
if let Err(_) = full_path {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let full_path = PathBuf::from(full_path.unwrap());
|
||||||
|
if let Ok(is_subpath) = crate::windows::is_sub_path(&full_path, dir) {
|
||||||
|
if is_subpath {
|
||||||
|
oup.insert(pid, full_path);
|
||||||
}
|
}
|
||||||
Ok(None) => {}
|
|
||||||
Err(e) => error!("Fatal panic checking process: {} ({:?})", pid, e),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(oup)
|
Ok(oup)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn kill_pid(pid: u32) -> Result<()> {
|
|
||||||
let process = w::HPROCESS::OpenProcess(co::PROCESS::TERMINATE, false, pid)?;
|
|
||||||
process.TerminateProcess(1)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn force_stop_package<P: AsRef<Path>>(root_dir: P) -> Result<()> {
|
pub fn force_stop_package<P: AsRef<Path>>(root_dir: P) -> Result<()> {
|
||||||
let root_dir = root_dir.as_ref();
|
let root_dir = root_dir.as_ref();
|
||||||
super::retry_io(|| _force_stop_package(root_dir))?;
|
super::retry_io(|| _force_stop_package(root_dir))?;
|
||||||
@@ -156,7 +82,7 @@ pub fn force_stop_package<P: AsRef<Path>>(root_dir: P) -> Result<()> {
|
|||||||
fn _force_stop_package<P: AsRef<Path>>(root_dir: P) -> Result<()> {
|
fn _force_stop_package<P: AsRef<Path>>(root_dir: P) -> Result<()> {
|
||||||
let dir = root_dir.as_ref();
|
let dir = root_dir.as_ref();
|
||||||
info!("Checking for running processes in: {}", dir.display());
|
info!("Checking for running processes in: {}", dir.display());
|
||||||
let processes = get_processes_running_in_directory(dir)?;
|
let processes = unsafe { get_processes_running_in_directory(dir)? };
|
||||||
let my_pid = std::process::id();
|
let my_pid = std::process::id();
|
||||||
for (pid, exe) in processes.iter() {
|
for (pid, exe) in processes.iter() {
|
||||||
if *pid == my_pid {
|
if *pid == my_pid {
|
||||||
@@ -164,7 +90,7 @@ fn _force_stop_package<P: AsRef<Path>>(root_dir: P) -> Result<()> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
warn!("Killing process: {} ({})", exe.display(), pid);
|
warn!("Killing process: {} ({})", exe.display(), pid);
|
||||||
kill_pid(*pid)?;
|
process::kill_pid(*pid)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -177,21 +103,13 @@ pub fn start_package(locator: &VelopackLocator, exe_args: Option<Vec<&str>>, set
|
|||||||
bail!("Unable to find executable to start: '{}'", exe_to_execute.to_string_lossy());
|
bail!("Unable to find executable to start: '{}'", exe_to_execute.to_string_lossy());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut psi = Process::new(&exe_to_execute);
|
let args: Vec<String> = exe_args.unwrap_or_default().iter().map(|s| s.to_string()).collect();
|
||||||
psi.current_dir(¤t);
|
let mut environment = HashMap::new();
|
||||||
if let Some(args) = exe_args {
|
if let Some(env_var) = set_env {
|
||||||
psi.args(args);
|
debug!("Setting environment variable: {}={}", env_var, "true");
|
||||||
|
environment.insert(env_var.to_string(), "true".to_string());
|
||||||
}
|
}
|
||||||
if let Some(env) = set_env {
|
process::run_process(exe_to_execute, args, Some(current), true, Some(environment))?;
|
||||||
debug!("Setting environment variable: {}={}", env, "true");
|
|
||||||
psi.env(env, "true");
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("About to launch: '{:?}' in dir '{:?}'", exe_to_execute, current);
|
|
||||||
info!("Args: {:?}", psi.get_args());
|
|
||||||
let child = psi.spawn().map_err(|z| anyhow!("Failed to start application ({}).", z))?;
|
|
||||||
let _ = unsafe { AllowSetForegroundWindow(child.id()) };
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +191,7 @@ fn test_get_running_processes_finds_cargo() {
|
|||||||
let path = Path::new(&profile);
|
let path = Path::new(&profile);
|
||||||
let rustup = path.join(".rustup");
|
let rustup = path.join(".rustup");
|
||||||
|
|
||||||
let processes = get_processes_running_in_directory(&rustup).unwrap();
|
let processes = unsafe { get_processes_running_in_directory(&rustup).unwrap() };
|
||||||
assert!(processes.len() > 0);
|
assert!(processes.len() > 0);
|
||||||
|
|
||||||
let mut found = false;
|
let mut found = false;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ fn get_known_folder(rfid: *const GUID) -> Result<String> {
|
|||||||
unsafe {
|
unsafe {
|
||||||
let flag = windows::Win32::UI::Shell::KNOWN_FOLDER_FLAG(0);
|
let flag = windows::Win32::UI::Shell::KNOWN_FOLDER_FLAG(0);
|
||||||
let result = SHGetKnownFolderPath(rfid, flag, None)?;
|
let result = SHGetKnownFolderPath(rfid, flag, None)?;
|
||||||
super::strings::pwstr_to_string(result)
|
super::strings::u16_to_string(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,94 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use windows::core::{HSTRING, PCWSTR, PWSTR};
|
use windows::core::{PCWSTR, PWSTR};
|
||||||
|
|
||||||
pub fn string_to_u16<P: AsRef<str>>(input: P) -> Vec<u16> {
|
pub fn string_to_u16<P: AsRef<str>>(input: P) -> Vec<u16> {
|
||||||
let input = input.as_ref();
|
let input = input.as_ref();
|
||||||
input.encode_utf16().chain(Some(0)).collect::<Vec<u16>>()
|
input.encode_utf16().chain(Some(0)).collect::<Vec<u16>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pwstr_to_string(input: PWSTR) -> Result<String> {
|
pub trait WideString {
|
||||||
unsafe {
|
fn to_wide_slice(&self) -> &[u16];
|
||||||
let hstring = input.to_hstring();
|
}
|
||||||
let string = hstring.to_string_lossy();
|
|
||||||
Ok(string.trim_end_matches('\0').to_string())
|
impl WideString for PWSTR {
|
||||||
|
fn to_wide_slice(&self) -> &[u16] {
|
||||||
|
unsafe { self.as_wide() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pcwstr_to_string(input: PCWSTR) -> Result<String> {
|
impl WideString for PCWSTR {
|
||||||
unsafe {
|
fn to_wide_slice(&self) -> &[u16] {
|
||||||
let hstring = input.to_hstring();
|
unsafe { self.as_wide() }
|
||||||
let string = hstring.to_string_lossy();
|
|
||||||
Ok(string.trim_end_matches('\0').to_string())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn u16_to_string<T: AsRef<[u16]>>(input: T) -> Result<String> {
|
impl WideString for Vec<u16> {
|
||||||
let input = input.as_ref();
|
fn to_wide_slice(&self) -> &[u16] {
|
||||||
let hstring = HSTRING::from_wide(input);
|
self.as_ref()
|
||||||
let string = hstring.to_string_lossy();
|
}
|
||||||
Ok(string.trim_end_matches('\0').to_string())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl WideString for &Vec<u16> {
|
||||||
|
fn to_wide_slice(&self) -> &[u16] {
|
||||||
|
self.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// impl WideString for [u16] {
|
||||||
|
// fn to_wide_slice(&self) -> &[u16] {
|
||||||
|
// self.as_ref()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
impl<const N: usize> WideString for [u16; N] {
|
||||||
|
fn to_wide_slice(&self) -> &[u16] {
|
||||||
|
self.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn u16_to_string_lossy<T: WideString>(input: T) -> String {
|
||||||
|
let slice = input.to_wide_slice();
|
||||||
|
let null_pos = slice.iter().position(|&x| x == 0).unwrap_or(slice.len());
|
||||||
|
let trimmed_slice = &slice[..null_pos];
|
||||||
|
String::from_utf16_lossy(trimmed_slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn u16_to_string<T: WideString>(input: T) -> Result<String> {
|
||||||
|
let slice = input.to_wide_slice();
|
||||||
|
let null_pos = slice.iter().position(|&x| x == 0).unwrap_or(slice.len());
|
||||||
|
let trimmed_slice = &slice[..null_pos];
|
||||||
|
Ok(String::from_utf16(trimmed_slice)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn pwstr_to_string(input: PWSTR) -> Result<String> {
|
||||||
|
// unsafe {
|
||||||
|
// let hstring = input.to_hstring();
|
||||||
|
// let string = hstring.to_string_lossy();
|
||||||
|
// Ok(string.trim_end_matches('\0').to_string())
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub fn pcwstr_to_string(input: PCWSTR) -> Result<String> {
|
||||||
|
// unsafe {
|
||||||
|
// let hstring = input.to_hstring();
|
||||||
|
// let string = hstring.to_string_lossy();
|
||||||
|
// Ok(string.trim_end_matches('\0').to_string())
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub fn u16_to_string<T: AsRef<[u16]>>(input: T) -> Result<String> {
|
||||||
|
// let input = input.as_ref();
|
||||||
|
// let hstring = HSTRING::from_wide(input);
|
||||||
|
// let string = hstring.to_string_lossy();
|
||||||
|
// Ok(string.trim_end_matches('\0').to_string())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub fn u16_to_string<T: AsRef<[u16]>>(input: T) -> Result<String> {
|
||||||
|
// let input = input.as_ref();
|
||||||
|
// // Find position of first null byte (0)
|
||||||
|
// let null_pos = input.iter().position(|&x| x == 0).unwrap_or(input.len());
|
||||||
|
// // Take only up to the first null byte
|
||||||
|
// let trimmed_input = &input[..null_pos];
|
||||||
|
// let hstring = HSTRING::from_wide(trimmed_input);
|
||||||
|
// Ok(hstring.to_string_lossy())
|
||||||
|
// }
|
||||||
|
|||||||
@@ -1,57 +1,60 @@
|
|||||||
use std::{
|
use crate::{
|
||||||
os::windows::process::CommandExt,
|
shared::{self, runtime_arch::RuntimeArch},
|
||||||
path::{Path, PathBuf},
|
windows::strings::{string_to_u16, u16_to_string},
|
||||||
process::Command as Process,
|
|
||||||
time::Duration,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use velopack::locator::VelopackLocator;
|
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use normpath::PathExt;
|
use normpath::PathExt;
|
||||||
use wait_timeout::ChildExt;
|
use std::{
|
||||||
use windows::core::PCWSTR;
|
path::{Path, PathBuf},
|
||||||
use windows::Win32::Storage::FileSystem::GetLongPathNameW;
|
time::Duration,
|
||||||
use windows::Win32::System::SystemInformation::{VerSetConditionMask, VerifyVersionInfoW, OSVERSIONINFOEXW, VER_FLAGS};
|
};
|
||||||
use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
|
use velopack::{locator::VelopackLocator, process, process::WaitResult};
|
||||||
use windows::Win32::Foundation;
|
use windows::{
|
||||||
|
core::PCWSTR,
|
||||||
use crate::shared::{self, runtime_arch::RuntimeArch};
|
Win32::Storage::FileSystem::GetLongPathNameW,
|
||||||
use crate::windows::strings::{string_to_u16, u16_to_string};
|
Win32::System::SystemInformation::{VerSetConditionMask, VerifyVersionInfoW, OSVERSIONINFOEXW, VER_FLAGS},
|
||||||
|
};
|
||||||
|
|
||||||
pub fn run_hook(locator: &VelopackLocator, hook_name: &str, timeout_secs: u64) -> bool {
|
pub fn run_hook(locator: &VelopackLocator, hook_name: &str, timeout_secs: u64) -> bool {
|
||||||
let sw = simple_stopwatch::Stopwatch::start_new();
|
let sw = simple_stopwatch::Stopwatch::start_new();
|
||||||
let root_dir = locator.get_root_dir();
|
let root_dir = locator.get_root_dir();
|
||||||
let current_path = locator.get_current_bin_dir();
|
let current_path = locator.get_current_bin_dir();
|
||||||
let main_exe_path = locator.get_main_exe_path();
|
let main_exe_path = locator.get_main_exe_path_as_string();
|
||||||
let ver_string = locator.get_manifest_version_full_string();
|
let ver_string = locator.get_manifest_version_full_string();
|
||||||
let args = vec![hook_name, &ver_string];
|
let args = vec![hook_name, &ver_string];
|
||||||
let mut success = false;
|
let mut success = false;
|
||||||
|
|
||||||
info!("Running {} hook...", hook_name);
|
info!("Running {} hook...", hook_name);
|
||||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
let cmd = process::run_process(
|
||||||
let cmd = Process::new(&main_exe_path).args(args).current_dir(¤t_path).creation_flags(CREATE_NO_WINDOW).spawn();
|
main_exe_path,
|
||||||
|
args.iter().map(|f| f.to_string()).collect(),
|
||||||
|
Some(current_path.to_string_lossy().to_string()),
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
if let Err(e) = cmd {
|
if let Err(e) = cmd {
|
||||||
warn!("Failed to start hook {}: {}", hook_name, e);
|
warn!("Failed to start hook {}: {}", hook_name, e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut cmd = cmd.unwrap();
|
let cmd = cmd.unwrap();
|
||||||
let _ = unsafe { AllowSetForegroundWindow(cmd.id()) };
|
|
||||||
|
|
||||||
match cmd.wait_timeout(Duration::from_secs(timeout_secs)) {
|
match process::wait_for_process_to_exit_with_timeout(cmd.handle(), Duration::from_secs(timeout_secs)) {
|
||||||
Ok(Some(status)) => {
|
Ok(WaitResult::NoWaitRequired) => {
|
||||||
if status.success() {
|
warn!("Was unable to wait for hook (it may have exited too quickly).");
|
||||||
|
}
|
||||||
|
Ok(WaitResult::ExitCode(code)) => {
|
||||||
|
if code == 0 {
|
||||||
info!("Hook executed successfully (took {}ms)", sw.ms());
|
info!("Hook executed successfully (took {}ms)", sw.ms());
|
||||||
success = true;
|
success = true;
|
||||||
} else {
|
} else {
|
||||||
warn!("Hook exited with non-zero exit code: {}", status.code().unwrap_or(0));
|
warn!("Hook exited with non-zero exit code: {}", code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(WaitResult::WaitTimeout) => {
|
||||||
let _ = cmd.kill();
|
let _ = process::kill_process(cmd.handle());
|
||||||
error!("Process timed out after {}s", timeout_secs);
|
error!("Process timed out after {}s and was killed.", timeout_secs);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error waiting for process to finish: {}", e);
|
error!("Error waiting for process to finish: {}", e);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ delta = ["zstd"]
|
|||||||
async = ["async-std"]
|
async = ["async-std"]
|
||||||
typescript = ["ts-rs"]
|
typescript = ["ts-rs"]
|
||||||
file-logging = ["log-panics", "simplelog", "file-rotate", "time"]
|
file-logging = ["log-panics", "simplelog", "file-rotate", "time"]
|
||||||
|
public-utils = []
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["async", "delta"]
|
features = ["async", "delta"]
|
||||||
@@ -67,7 +68,22 @@ file-rotate = { workspace = true, optional = true }
|
|||||||
time = { workspace = true, optional = true }
|
time = { workspace = true, optional = true }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
windows = { workspace = true, features = ["Win32_Foundation", "Win32_Storage", "Win32_Storage_FileSystem", "Win32_System_IO", "Win32_UI_Shell"] }
|
windows = { workspace = true, features = [
|
||||||
|
"Win32_Foundation",
|
||||||
|
"Win32_Storage",
|
||||||
|
"Win32_Storage_FileSystem",
|
||||||
|
"Win32_Security",
|
||||||
|
"Win32_System_IO",
|
||||||
|
"Win32_System_Threading",
|
||||||
|
"Win32_UI_WindowsAndMessaging",
|
||||||
|
"Win32_UI_Shell",
|
||||||
|
"Win32_System_Kernel",
|
||||||
|
"Win32_System_Registry",
|
||||||
|
"Wdk",
|
||||||
|
"Wdk_System",
|
||||||
|
"Wdk_System_Threading",
|
||||||
|
] }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
libc.workspace = true
|
libc.workspace = true
|
||||||
|
waitpid-any.workspace = true
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use xml::EventReader;
|
|||||||
use xml::reader::XmlEvent;
|
use xml::reader::XmlEvent;
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
|
|
||||||
use crate::{Error, util};
|
use crate::{Error, misc};
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
@@ -39,7 +39,7 @@ pub struct BundleZip<'a> {
|
|||||||
pub fn load_bundle_from_file<'a, P: AsRef<Path>>(file_name: P) -> Result<BundleZip<'a>, Error> {
|
pub fn load_bundle_from_file<'a, P: AsRef<Path>>(file_name: P) -> Result<BundleZip<'a>, Error> {
|
||||||
let file_name = file_name.as_ref();
|
let file_name = file_name.as_ref();
|
||||||
debug!("Loading bundle from file '{}'...", file_name.to_string_lossy());
|
debug!("Loading bundle from file '{}'...", file_name.to_string_lossy());
|
||||||
let file = util::retry_io(|| File::open(&file_name))?;
|
let file = misc::retry_io(|| File::open(&file_name))?;
|
||||||
let cursor: Box<dyn ReadSeek> = Box::new(file);
|
let cursor: Box<dyn ReadSeek> = Box::new(file);
|
||||||
let zip = ZipArchive::new(cursor)?;
|
let zip = ZipArchive::new(cursor)?;
|
||||||
Ok(BundleZip {
|
Ok(BundleZip {
|
||||||
@@ -69,9 +69,9 @@ impl BundleZip<'_> {
|
|||||||
pub fn copy_bundle_to_file<T: AsRef<Path>>(&self, output_file_path: T) -> Result<(), Error> {
|
pub fn copy_bundle_to_file<T: AsRef<Path>>(&self, output_file_path: T) -> Result<(), Error> {
|
||||||
let nupkg_path = output_file_path.as_ref();
|
let nupkg_path = output_file_path.as_ref();
|
||||||
if self.zip_from_file {
|
if self.zip_from_file {
|
||||||
util::retry_io(|| fs::copy(self.file_path.clone().unwrap(), nupkg_path))?;
|
misc::retry_io(|| fs::copy(self.file_path.clone().unwrap(), nupkg_path))?;
|
||||||
} else {
|
} else {
|
||||||
util::retry_io(|| fs::write(nupkg_path, self.zip_range.unwrap()))?;
|
misc::retry_io(|| fs::write(nupkg_path, self.zip_range.unwrap()))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -146,12 +146,12 @@ impl BundleZip<'_> {
|
|||||||
|
|
||||||
if !parent.exists() {
|
if !parent.exists() {
|
||||||
debug!("Creating parent directory: {:?}", parent);
|
debug!("Creating parent directory: {:?}", parent);
|
||||||
util::retry_io(|| fs::create_dir_all(parent))?;
|
misc::retry_io(|| fs::create_dir_all(parent))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut archive = self.zip.borrow_mut();
|
let mut archive = self.zip.borrow_mut();
|
||||||
let mut file = archive.by_index(index)?;
|
let mut file = archive.by_index(index)?;
|
||||||
let mut outfile = util::retry_io(|| File::create(path))?;
|
let mut outfile = misc::retry_io(|| File::create(path))?;
|
||||||
let mut buffer = [0; 64000]; // Use a 64KB buffer; good balance for large/small files.
|
let mut buffer = [0; 64000]; // Use a 64KB buffer; good balance for large/small files.
|
||||||
|
|
||||||
debug!("Writing file to disk with 64k buffer: {:?}", path);
|
debug!("Writing file to disk with 64k buffer: {:?}", path);
|
||||||
@@ -326,9 +326,9 @@ impl BundleZip<'_> {
|
|||||||
let parent = link_path.parent().unwrap();
|
let parent = link_path.parent().unwrap();
|
||||||
if !parent.exists() {
|
if !parent.exists() {
|
||||||
debug!("Creating parent directory: {:?}", parent);
|
debug!("Creating parent directory: {:?}", parent);
|
||||||
util::retry_io(|| fs::create_dir_all(parent))?;
|
misc::retry_io(|| fs::create_dir_all(parent))?;
|
||||||
}
|
}
|
||||||
util::retry_io(|| Self::create_symlink(&link_path, &contents))?;
|
misc::retry_io(|| Self::create_symlink(&link_path, &contents))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -6,4 +6,13 @@ pub const HOOK_ENV_RESTART: &str = "VELOPACK_RESTART";
|
|||||||
pub const HOOK_CLI_INSTALL: &str = "--veloapp-install";
|
pub const HOOK_CLI_INSTALL: &str = "--veloapp-install";
|
||||||
pub const HOOK_CLI_UPDATED: &str = "--veloapp-updated";
|
pub const HOOK_CLI_UPDATED: &str = "--veloapp-updated";
|
||||||
pub const HOOK_CLI_OBSOLETE: &str = "--veloapp-obsolete";
|
pub const HOOK_CLI_OBSOLETE: &str = "--veloapp-obsolete";
|
||||||
pub const HOOK_CLI_UNINSTALL: &str = "--veloapp-uninstall";
|
pub const HOOK_CLI_UNINSTALL: &str = "--veloapp-uninstall";
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
pub const DEFAULT_CHANNEL_NAME: &str = "win";
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub const DEFAULT_CHANNEL_NAME: &str = "linux";
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
pub const DEFAULT_CHANNEL_NAME: &str = "osx";
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
use crate::{util, Error};
|
use crate::{misc, Error};
|
||||||
|
|
||||||
/// Downloads a file from a URL and writes it to a file while reporting progress from 0-100.
|
/// Downloads a file from a URL and writes it to a file while reporting progress from 0-100.
|
||||||
pub fn download_url_to_file<A>(url: &str, file_path: &str, mut progress: A) -> Result<(), Error>
|
pub fn download_url_to_file<A>(url: &str, file_path: &str, mut progress: A) -> Result<(), Error>
|
||||||
@@ -12,7 +12,7 @@ where
|
|||||||
let (head, body) = agent.get(url).call()?.into_parts();
|
let (head, body) = agent.get(url).call()?.into_parts();
|
||||||
|
|
||||||
let total_size = head.headers.get("Content-Length").and_then(|s| s.to_str().ok()).and_then(|s| s.parse::<u64>().ok());
|
let total_size = head.headers.get("Content-Length").and_then(|s| s.to_str().ok()).and_then(|s| s.parse::<u64>().ok());
|
||||||
let mut file = util::retry_io(|| File::create(file_path))?;
|
let mut file = misc::retry_io(|| File::create(file_path))?;
|
||||||
|
|
||||||
const CHUNK_SIZE: usize = 2 * 1024 * 1024; // 2MB
|
const CHUNK_SIZE: usize = 2 * 1024 * 1024; // 2MB
|
||||||
let mut downloaded: u64 = 0;
|
let mut downloaded: u64 = 0;
|
||||||
|
|||||||
@@ -78,44 +78,68 @@
|
|||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
macro_rules! maybe_pub {
|
||||||
|
($($mod:ident),*) => {
|
||||||
|
$(
|
||||||
|
#[cfg(feature = "public-utils")]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub mod $mod;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "public-utils"))]
|
||||||
|
#[allow(unused)]
|
||||||
|
mod $mod;
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! maybe_pub_os {
|
||||||
|
($mod:ident, $win_path:expr, $unix_path:expr) => {
|
||||||
|
#[cfg(all(windows, feature = "public-utils"))]
|
||||||
|
#[path = $win_path]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub mod $mod;
|
||||||
|
|
||||||
|
#[cfg(all(windows, not(feature = "public-utils")))]
|
||||||
|
#[path = $win_path]
|
||||||
|
#[allow(unused)]
|
||||||
|
mod $mod;
|
||||||
|
|
||||||
|
#[cfg(all(not(windows), feature = "public-utils"))]
|
||||||
|
#[path = $unix_path]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub mod $mod;
|
||||||
|
|
||||||
|
#[cfg(all(not(windows), not(feature = "public-utils")))]
|
||||||
|
#[path = $unix_path]
|
||||||
|
#[allow(unused)]
|
||||||
|
mod $mod;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
|
pub use app::*;
|
||||||
|
|
||||||
mod manager;
|
mod manager;
|
||||||
mod util;
|
pub use manager::*;
|
||||||
|
|
||||||
/// Utility functions for loading and working with Velopack bundles and manifests.
|
/// Locator provides support for locating the current app important paths (eg. path to packages, update binary, and so forth).
|
||||||
pub mod bundle;
|
|
||||||
|
|
||||||
/// Utility function for downloading files with progress reporting.
|
|
||||||
pub mod download;
|
|
||||||
|
|
||||||
/// Constant strings used internally by Velopack.
|
|
||||||
pub mod constants;
|
|
||||||
|
|
||||||
/// Locator provides some utility functions for locating the current app important paths (eg. path to packages, update binary, and so forth).
|
|
||||||
pub mod locator;
|
pub mod locator;
|
||||||
|
|
||||||
/// Sources contains abstractions for custom update sources (eg. url, local file, github releases, etc).
|
/// Sources are abstractions for custom update sources (eg. url, local file, github releases, etc).
|
||||||
pub mod sources;
|
pub mod sources;
|
||||||
|
|
||||||
/// Functions to patch files and reconstruct Velopack delta packages.
|
maybe_pub!(download, bundle, delta, constants, lockfile, logging, misc);
|
||||||
pub mod delta;
|
maybe_pub_os!(process, "process_win.rs", "process_unix.rs");
|
||||||
|
|
||||||
/// Acquire and manage file-system based lock files.
|
|
||||||
pub mod lockfile;
|
|
||||||
|
|
||||||
/// Logging utilities and setup.
|
|
||||||
pub mod logging;
|
|
||||||
|
|
||||||
pub use app::*;
|
|
||||||
pub use manager::*;
|
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
#[allow(missing_docs, clippy::large_enum_variant)]
|
#[allow(missing_docs, clippy::large_enum_variant)]
|
||||||
pub enum NetworkError
|
pub enum NetworkError {
|
||||||
{
|
|
||||||
#[error("Http error: {0}")]
|
#[error("Http error: {0}")]
|
||||||
Http(#[from] ureq::Error),
|
Http(#[from] ureq::Error),
|
||||||
#[error("Url error: {0}")]
|
#[error("Url error: {0}")]
|
||||||
@@ -124,8 +148,7 @@ pub enum NetworkError
|
|||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
pub enum Error
|
pub enum Error {
|
||||||
{
|
|
||||||
#[error("File does not exist: {0}")]
|
#[error("File does not exist: {0}")]
|
||||||
FileNotFound(String),
|
FileNotFound(String),
|
||||||
#[error("IO error: {0}")]
|
#[error("IO error: {0}")]
|
||||||
@@ -148,6 +171,7 @@ pub enum Error
|
|||||||
NotInstalled(String),
|
NotInstalled(String),
|
||||||
#[error("Generic error: {0}")]
|
#[error("Generic error: {0}")]
|
||||||
Generic(String),
|
Generic(String),
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
#[error("Win32 error: {0}")]
|
#[error("Win32 error: {0}")]
|
||||||
Win32(#[from] windows::core::Error),
|
Win32(#[from] windows::core::Error),
|
||||||
}
|
}
|
||||||
@@ -162,4 +186,4 @@ impl From<ureq::Error> for Error {
|
|||||||
fn from(err: ureq::Error) -> Self {
|
fn from(err: ureq::Error) -> Self {
|
||||||
Error::Network(Box::new(NetworkError::Http(err)))
|
Error::Network(Box::new(NetworkError::Http(err)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ impl LockFile {
|
|||||||
pub fn try_acquire_lock<P: Into<PathBuf>>(path: P) -> Result<Self> {
|
pub fn try_acquire_lock<P: Into<PathBuf>>(path: P) -> Result<Self> {
|
||||||
let path: PathBuf = path.into();
|
let path: PathBuf = path.into();
|
||||||
|
|
||||||
crate::util::retry_io(|| {
|
crate::misc::retry_io(|| {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
let file = Self::windows_exclusive_lock(&path)?;
|
let file = Self::windows_exclusive_lock(&path)?;
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
|
use std::process::exit;
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
use std::os::windows::process::CommandExt;
|
use std::{fs, sync::mpsc::Sender};
|
||||||
use std::{
|
|
||||||
fs,
|
|
||||||
process::{exit, Command as Process},
|
|
||||||
sync::mpsc::Sender,
|
|
||||||
};
|
|
||||||
|
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -15,9 +11,11 @@ use async_std::channel::Sender as AsyncSender;
|
|||||||
use async_std::task::JoinHandle;
|
use async_std::task::JoinHandle;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
constants,
|
||||||
locator::{self, LocationContext, VelopackLocator, VelopackLocatorConfig},
|
locator::{self, LocationContext, VelopackLocator, VelopackLocatorConfig},
|
||||||
|
misc, process,
|
||||||
sources::UpdateSource,
|
sources::UpdateSource,
|
||||||
util, Error,
|
Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Configure how the update process should wait before applying updates.
|
/// Configure how the update process should wait before applying updates.
|
||||||
@@ -185,8 +183,8 @@ impl UpdateManager {
|
|||||||
let app_channel = self.locator.get_manifest_channel();
|
let app_channel = self.locator.get_manifest_channel();
|
||||||
let mut channel = options_channel.unwrap_or(&app_channel).to_string();
|
let mut channel = options_channel.unwrap_or(&app_channel).to_string();
|
||||||
if channel.is_empty() {
|
if channel.is_empty() {
|
||||||
warn!("Channel is empty, picking default.");
|
warn!("Channel is empty, using default.");
|
||||||
channel = locator::default_channel_name();
|
channel = constants::DEFAULT_CHANNEL_NAME.to_owned();
|
||||||
}
|
}
|
||||||
info!("Chosen channel for updates: {:?} (explicit={:?}, memorized={:?})", channel, options_channel, app_channel);
|
info!("Chosen channel for updates: {:?} (explicit={:?}, memorized={:?})", channel, options_channel, app_channel);
|
||||||
channel
|
channel
|
||||||
@@ -220,13 +218,14 @@ impl UpdateManager {
|
|||||||
let packages_dir = self.locator.get_packages_dir();
|
let packages_dir = self.locator.get_packages_dir();
|
||||||
if let Some((path, manifest)) = locator::find_latest_full_package(&packages_dir) {
|
if let Some((path, manifest)) = locator::find_latest_full_package(&packages_dir) {
|
||||||
if manifest.version > self.locator.get_manifest_version() {
|
if manifest.version > self.locator.get_manifest_version() {
|
||||||
|
let (sha1, sha256) = misc::calculate_sha1_sha256(&path).unwrap_or_default();
|
||||||
return Some(VelopackAsset {
|
return Some(VelopackAsset {
|
||||||
PackageId: manifest.id,
|
PackageId: manifest.id,
|
||||||
Version: manifest.version.to_string(),
|
Version: manifest.version.to_string(),
|
||||||
Type: "Full".to_string(),
|
Type: "Full".to_string(),
|
||||||
FileName: path.file_name().unwrap().to_string_lossy().to_string(),
|
FileName: path.file_name().unwrap().to_string_lossy().to_string(),
|
||||||
SHA1: util::calculate_file_sha1(&path).unwrap_or_default(),
|
SHA1: sha1,
|
||||||
SHA256: util::calculate_file_sha256(&path).unwrap_or_default(),
|
SHA256: sha256,
|
||||||
Size: path.metadata().map(|m| m.len()).unwrap_or(0),
|
Size: path.metadata().map(|m| m.len()).unwrap_or(0),
|
||||||
NotesMarkdown: manifest.release_notes,
|
NotesMarkdown: manifest.release_notes,
|
||||||
NotesHtml: manifest.release_notes_html,
|
NotesHtml: manifest.release_notes_html,
|
||||||
@@ -462,7 +461,7 @@ impl UpdateManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// This will launch the Velopack updater and optionally wait for a program to exit gracefully.
|
/// This will launch the Velopack updater and optionally wait for a program to exit gracefully.
|
||||||
/// This method is unsafe because it does not necessarily wait for any / the correct process to exit
|
/// This method is unsafe because it does not necessarily wait for any / the correct process to exit
|
||||||
/// before applying updates. The `wait_exit_then_apply_updates` method is recommended for most use cases.
|
/// before applying updates. The `wait_exit_then_apply_updates` method is recommended for most use cases.
|
||||||
pub fn unsafe_apply_updates<A, C, S>(
|
pub fn unsafe_apply_updates<A, C, S>(
|
||||||
&self,
|
&self,
|
||||||
@@ -520,21 +519,8 @@ impl UpdateManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut p = Process::new(&self.locator.get_update_path());
|
let update_path = self.locator.get_update_path();
|
||||||
p.args(&args);
|
process::run_process(&update_path, args, update_path.parent(), false, None)?;
|
||||||
|
|
||||||
if let Some(update_exe_parent) = self.locator.get_update_path().parent() {
|
|
||||||
p.current_dir(update_exe_parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
{
|
|
||||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
|
||||||
p.creation_flags(CREATE_NO_WINDOW);
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("About to run Update.exe: {} {:?}", self.locator.get_update_path_as_string(), args);
|
|
||||||
p.spawn()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use crate::Error;
|
|||||||
use rand::distr::{Alphanumeric, SampleString};
|
use rand::distr::{Alphanumeric, SampleString};
|
||||||
use sha2::Digest;
|
use sha2::Digest;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
use std::io::{BufReader, Read};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -42,20 +43,28 @@ pub fn random_string(len: usize) -> String {
|
|||||||
Alphanumeric.sample_string(&mut rand::rng(), len)
|
Alphanumeric.sample_string(&mut rand::rng(), len)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate_file_sha256<P: AsRef<Path>>(file: P) -> Result<String, Error> {
|
pub fn calculate_sha1_sha256<P: AsRef<Path>>(file: P) -> Result<(String, String), Error> {
|
||||||
let mut file = File::open(file)?;
|
let file = File::open(file)?;
|
||||||
let mut sha256 = sha2::Sha256::new();
|
let mut reader = BufReader::new(file);
|
||||||
std::io::copy(&mut file, &mut sha256)?;
|
|
||||||
let hash = sha256.finalize();
|
|
||||||
Ok(format!("{:x}", hash))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn calculate_file_sha1<P: AsRef<Path>>(file: P) -> Result<String, Error> {
|
let mut sha256 = sha2::Sha256::new();
|
||||||
let mut file = File::open(file)?;
|
let mut sha1 = sha1::Sha1::new();
|
||||||
let mut sha1o = sha1::Sha1::new();
|
|
||||||
std::io::copy(&mut file, &mut sha1o)?;
|
let mut buffer = [0u8; 1024 * 1024]; // 1MB buffer
|
||||||
let hash = sha1o.finalize();
|
loop {
|
||||||
Ok(format!("{:x}", hash))
|
let bytes_read = reader.read(&mut buffer)?;
|
||||||
|
if bytes_read == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sha256.update(&buffer[..bytes_read]);
|
||||||
|
sha1.update(&buffer[..bytes_read]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sha256_hash = format!("{:x}", sha256.finalize());
|
||||||
|
let sha1_hash = format!("{:x}", sha1.finalize());
|
||||||
|
|
||||||
|
Ok((sha1_hash, sha256_hash))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_directory_writable<P1: AsRef<Path>>(path: P1) -> bool {
|
pub fn is_directory_writable<P1: AsRef<Path>>(path: P1) -> bool {
|
||||||
87
src/lib-rust/src/process_unix.rs
Normal file
87
src/lib-rust/src/process_unix.rs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
ffi::{OsStr, OsString},
|
||||||
|
io::{Error as IoError, ErrorKind as IoErrorKind, Result as IoResult},
|
||||||
|
os::{raw::c_void, windows::ffi::OsStrExt},
|
||||||
|
process::{Child, Command},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::process;
|
||||||
|
|
||||||
|
pub fn is_current_process_elevated() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_to_u16<P: AsRef<str>>(input: P) -> Vec<u16> {
|
||||||
|
let input = input.as_ref();
|
||||||
|
input.encode_utf16().chain(Some(0)).collect::<Vec<u16>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_process_as_admin(
|
||||||
|
exe_path: String,
|
||||||
|
args: Vec<String>,
|
||||||
|
work_dir: Option<String>,
|
||||||
|
show_window: bool,
|
||||||
|
) -> IoResult<SafeProcessHandle> {
|
||||||
|
|
||||||
|
// let mut cmd = Command::new(exe_path).args(args);
|
||||||
|
|
||||||
|
// if let Some(dir) = work_dir {
|
||||||
|
// cmd.current_dir(dir);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_process(
|
||||||
|
exe_path: String,
|
||||||
|
args: Vec<String>,
|
||||||
|
work_dir: Option<String>,
|
||||||
|
_show_window: bool,
|
||||||
|
set_env: Option<HashMap<String, String>>,
|
||||||
|
) -> IoResult<Child> {
|
||||||
|
let mut cmd = Command::new(exe_path);
|
||||||
|
cmd.args(args);
|
||||||
|
if let Some(dir) = work_dir {
|
||||||
|
cmd.current_dir(dir);
|
||||||
|
}
|
||||||
|
if let Some(env) = set_env {
|
||||||
|
for (key, value) in env {
|
||||||
|
cmd.env(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd.spawn()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wait_for_process_exit_with_timeout(process: Child, dur: Duration) -> IoResult<Option<u32>> {
|
||||||
|
|
||||||
|
let mut status = process.wait_timeout(dur)?;
|
||||||
|
if status.is_none() {
|
||||||
|
return Err(IoError::new(IoErrorKind::TimedOut, "Process timed out"));
|
||||||
|
}
|
||||||
|
Ok(status.unwrap().code())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wait_for_pid_to_exit(pid: u32, dur: Duration) -> IoResult<()> {
|
||||||
|
info!("Waiting {}ms for process ({}) to exit.", ms_to_wait, pid);
|
||||||
|
let mut handle = waitpid_any::WaitHandle::open(pid.try_into()?)?;
|
||||||
|
let result = handle.wait_timeout(Duration::from_millis(ms_to_wait as u64))?;
|
||||||
|
if result.is_some() {
|
||||||
|
info!("Parent process exited.");
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
bail!("Parent process timed out.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wait_for_parent_to_exit(dur: Duration) -> IoResult<()> {
|
||||||
|
let id = std::os::unix::process::parent_id();
|
||||||
|
info!("Attempting to wait for parent process ({}) to exit.", id);
|
||||||
|
if id > 1 {
|
||||||
|
wait_for_pid_to_exit(id, ms_to_wait)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kill_process(mut process: Child) -> IoResult<()> {
|
||||||
|
process.kill()
|
||||||
|
}
|
||||||
457
src/lib-rust/src/process_win.rs
Normal file
457
src/lib-rust/src/process_win.rs
Normal file
@@ -0,0 +1,457 @@
|
|||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
ffi::{OsStr, OsString},
|
||||||
|
io::{Error as IoError, ErrorKind as IoErrorKind, Result as IoResult},
|
||||||
|
os::{raw::c_void, windows::ffi::OsStrExt},
|
||||||
|
path::Path,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
use windows::{
|
||||||
|
core::{PCWSTR, PWSTR},
|
||||||
|
Wdk::System::Threading::{NtQueryInformationProcess, ProcessBasicInformation},
|
||||||
|
Win32::{
|
||||||
|
Foundation::{CloseHandle, FILETIME, HANDLE, WAIT_OBJECT_0, WAIT_TIMEOUT},
|
||||||
|
Security::{GetTokenInformation, TokenElevation, TOKEN_ELEVATION},
|
||||||
|
System::Threading::{
|
||||||
|
CreateProcessW, GetCurrentProcess, GetExitCodeProcess, GetProcessId, GetProcessTimes, OpenProcess, OpenProcessToken,
|
||||||
|
TerminateProcess, WaitForSingleObject, CREATE_NO_WINDOW, PROCESS_ACCESS_RIGHTS, PROCESS_BASIC_INFORMATION,
|
||||||
|
PROCESS_CREATION_FLAGS, PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_SYNCHRONIZE, PROCESS_TERMINATE,
|
||||||
|
},
|
||||||
|
UI::{
|
||||||
|
Shell::{ShellExecuteExW, SEE_MASK_NOCLOSEPROCESS, SHELLEXECUTEINFOW},
|
||||||
|
WindowsAndMessaging::AllowSetForegroundWindow,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Arg {
|
||||||
|
/// Add quotes (if needed)
|
||||||
|
Regular(OsString),
|
||||||
|
// Append raw string without quoting
|
||||||
|
#[allow(unused)]
|
||||||
|
Raw(OsString),
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Quote {
|
||||||
|
// Every arg is quoted
|
||||||
|
Always,
|
||||||
|
// Whitespace and empty args are quoted
|
||||||
|
Auto,
|
||||||
|
// Arg appended without any changes (#29494)
|
||||||
|
Never,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_no_nuls<T: AsRef<OsStr>>(str: T) -> IoResult<T> {
|
||||||
|
if str.as_ref().encode_wide().any(|b| b == 0) {
|
||||||
|
Err(IoError::new(IoErrorKind::InvalidInput, "nul byte found in provided data"))
|
||||||
|
} else {
|
||||||
|
Ok(str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_arg(cmd: &mut Vec<u16>, arg: &Arg, force_quotes: bool) -> IoResult<()> {
|
||||||
|
let (arg, quote) = match arg {
|
||||||
|
Arg::Regular(arg) => (arg, if force_quotes { Quote::Always } else { Quote::Auto }),
|
||||||
|
Arg::Raw(arg) => (arg, Quote::Never),
|
||||||
|
};
|
||||||
|
|
||||||
|
// If an argument has 0 characters then we need to quote it to ensure
|
||||||
|
// that it actually gets passed through on the command line or otherwise
|
||||||
|
// it will be dropped entirely when parsed on the other end.
|
||||||
|
ensure_no_nuls(arg)?;
|
||||||
|
let arg_bytes = arg.as_encoded_bytes();
|
||||||
|
let (quote, escape) = match quote {
|
||||||
|
Quote::Always => (true, true),
|
||||||
|
Quote::Auto => (arg_bytes.iter().any(|c| *c == b' ' || *c == b'\t') || arg_bytes.is_empty(), true),
|
||||||
|
Quote::Never => (false, false),
|
||||||
|
};
|
||||||
|
if quote {
|
||||||
|
cmd.push('"' as u16);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut backslashes: usize = 0;
|
||||||
|
for x in arg.encode_wide() {
|
||||||
|
if escape {
|
||||||
|
if x == '\\' as u16 {
|
||||||
|
backslashes += 1;
|
||||||
|
} else {
|
||||||
|
if x == '"' as u16 {
|
||||||
|
// Add n+1 backslashes to total 2n+1 before internal '"'.
|
||||||
|
cmd.extend((0..=backslashes).map(|_| '\\' as u16));
|
||||||
|
}
|
||||||
|
backslashes = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd.push(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
if quote {
|
||||||
|
// Add n backslashes to total 2n before ending '"'.
|
||||||
|
cmd.extend((0..backslashes).map(|_| '\\' as u16));
|
||||||
|
cmd.push('"' as u16);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_command_line(argv0: Option<&OsStr>, args: &[Arg], force_quotes: bool) -> IoResult<Vec<u16>> {
|
||||||
|
// Encode the command and arguments in a command line string such
|
||||||
|
// that the spawned process may recover them using CommandLineToArgvW.
|
||||||
|
let mut cmd: Vec<u16> = Vec::new();
|
||||||
|
|
||||||
|
// Always quote the program name so CreateProcess to avoid ambiguity when
|
||||||
|
// the child process parses its arguments.
|
||||||
|
// Note that quotes aren't escaped here because they can't be used in arg0.
|
||||||
|
// But that's ok because file paths can't contain quotes.
|
||||||
|
if let Some(argv0) = argv0 {
|
||||||
|
cmd.push(b'"' as u16);
|
||||||
|
cmd.extend(argv0.encode_wide());
|
||||||
|
cmd.push(b'"' as u16);
|
||||||
|
cmd.push(' ' as u16);
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg in args {
|
||||||
|
append_arg(&mut cmd, arg, force_quotes)?;
|
||||||
|
cmd.push(' ' as u16);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.push(0);
|
||||||
|
Ok(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_envp(maybe_env: Option<HashMap<String, String>>) -> IoResult<(Option<*const c_void>, Vec<u16>)> {
|
||||||
|
// On Windows we pass an "environment block" which is not a char**, but
|
||||||
|
// rather a concatenation of null-terminated k=v\0 sequences, with a final
|
||||||
|
// \0 to terminate.
|
||||||
|
if let Some(env) = maybe_env {
|
||||||
|
let mut blk = Vec::new();
|
||||||
|
|
||||||
|
// If there are no environment variables to set then signal this by
|
||||||
|
// pushing a null.
|
||||||
|
if env.is_empty() {
|
||||||
|
blk.push(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (k, v) in env {
|
||||||
|
let os_key = OsString::from(k);
|
||||||
|
let os_value = OsString::from(v);
|
||||||
|
blk.extend(ensure_no_nuls(os_key)?.encode_wide());
|
||||||
|
blk.push('=' as u16);
|
||||||
|
blk.extend(ensure_no_nuls(os_value)?.encode_wide());
|
||||||
|
blk.push(0);
|
||||||
|
}
|
||||||
|
blk.push(0);
|
||||||
|
Ok((Some(blk.as_ptr() as *mut c_void), blk))
|
||||||
|
} else {
|
||||||
|
Ok((None, Vec::new()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_current_process_elevated() -> bool {
|
||||||
|
// Get the current process handle
|
||||||
|
let process = unsafe { GetCurrentProcess() };
|
||||||
|
|
||||||
|
// Variable to hold the process token
|
||||||
|
let mut token: HANDLE = HANDLE::default();
|
||||||
|
|
||||||
|
// Open the process token with the TOKEN_QUERY access rights
|
||||||
|
unsafe {
|
||||||
|
if OpenProcessToken(process, windows::Win32::Security::TOKEN_QUERY, &mut token).is_ok() {
|
||||||
|
// Allocate a buffer for the TOKEN_ELEVATION structure
|
||||||
|
let mut elevation = TOKEN_ELEVATION::default();
|
||||||
|
let mut size: u32 = 0;
|
||||||
|
|
||||||
|
let elevation_ptr: *mut core::ffi::c_void = &mut elevation as *mut _ as *mut _;
|
||||||
|
|
||||||
|
// Query the token information to check if it is elevated
|
||||||
|
if GetTokenInformation(token, TokenElevation, Some(elevation_ptr), std::mem::size_of::<TOKEN_ELEVATION>() as u32, &mut size)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
// Return whether the token is elevated
|
||||||
|
let _ = CloseHandle(token);
|
||||||
|
return elevation.TokenIsElevated != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the token handle
|
||||||
|
if !token.is_invalid() {
|
||||||
|
unsafe {
|
||||||
|
let _ = CloseHandle(token);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SafeProcessHandle {
|
||||||
|
handle: HANDLE,
|
||||||
|
pid: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for SafeProcessHandle {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if !self.handle.is_invalid() {
|
||||||
|
let _ = unsafe { CloseHandle(self.handle) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SafeProcessHandle {
|
||||||
|
pub fn handle(&self) -> HANDLE {
|
||||||
|
self.handle
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pid(&self) -> u32 {
|
||||||
|
self.pid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<u32> for SafeProcessHandle {
|
||||||
|
fn into(self) -> u32 {
|
||||||
|
self.pid()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<HANDLE> for SafeProcessHandle {
|
||||||
|
fn into(self) -> HANDLE {
|
||||||
|
self.handle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_pid_safe(
|
||||||
|
dwdesiredaccess: PROCESS_ACCESS_RIGHTS,
|
||||||
|
binherithandle: bool,
|
||||||
|
dwprocessid: u32,
|
||||||
|
) -> windows::core::Result<SafeProcessHandle> {
|
||||||
|
let handle = unsafe { OpenProcess(dwdesiredaccess, binherithandle, dwprocessid)? };
|
||||||
|
return Ok(SafeProcessHandle { handle, pid: dwprocessid });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn os_to_pcwstr<P: AsRef<OsStr>>(d: P) -> IoResult<(PCWSTR, Vec<u16>)> {
|
||||||
|
let d = d.as_ref();
|
||||||
|
let d = OsString::from(d);
|
||||||
|
let mut d_str: Vec<u16> = ensure_no_nuls(d)?.encode_wide().collect();
|
||||||
|
d_str.push(0);
|
||||||
|
Ok((PCWSTR(d_str.as_ptr()), d_str))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pathopt_to_pcwstr<P: AsRef<Path>>(d: Option<P>) -> IoResult<(PCWSTR, Vec<u16>)> {
|
||||||
|
match d {
|
||||||
|
Some(dir) => {
|
||||||
|
let dir = dir.as_ref();
|
||||||
|
os_to_pcwstr(dir)
|
||||||
|
}
|
||||||
|
None => Ok((PCWSTR::null(), Vec::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_process_as_admin<P1: AsRef<Path>, P2: AsRef<Path>>(
|
||||||
|
exe_path: P1,
|
||||||
|
args: Vec<String>,
|
||||||
|
work_dir: Option<P2>,
|
||||||
|
show_window: bool,
|
||||||
|
) -> IoResult<SafeProcessHandle> {
|
||||||
|
let verb = os_to_pcwstr("runas")?;
|
||||||
|
let exe = os_to_pcwstr(exe_path.as_ref())?;
|
||||||
|
let wrapped_args: Vec<Arg> = args.iter().map(|a| Arg::Regular(a.into())).collect();
|
||||||
|
let params = make_command_line(None, &wrapped_args, false)?;
|
||||||
|
let params = PCWSTR(params.as_ptr());
|
||||||
|
let work_dir = pathopt_to_pcwstr(work_dir.as_ref())?;
|
||||||
|
|
||||||
|
let n_show =
|
||||||
|
if show_window { windows::Win32::UI::WindowsAndMessaging::SW_NORMAL.0 } else { windows::Win32::UI::WindowsAndMessaging::SW_HIDE.0 };
|
||||||
|
|
||||||
|
let mut exe_info: SHELLEXECUTEINFOW = SHELLEXECUTEINFOW {
|
||||||
|
cbSize: std::mem::size_of::<SHELLEXECUTEINFOW>() as u32,
|
||||||
|
fMask: SEE_MASK_NOCLOSEPROCESS,
|
||||||
|
lpVerb: verb.0,
|
||||||
|
lpFile: exe.0,
|
||||||
|
lpParameters: params,
|
||||||
|
lpDirectory: work_dir.0,
|
||||||
|
nShow: n_show,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
info!("About to launch [AS ADMIN]: '{:?}' in dir '{:?}' with arguments: {:?}", exe, work_dir, args);
|
||||||
|
ShellExecuteExW(&mut exe_info as *mut SHELLEXECUTEINFOW)?;
|
||||||
|
let process_id = GetProcessId(exe_info.hProcess);
|
||||||
|
let _ = AllowSetForegroundWindow(process_id);
|
||||||
|
Ok(SafeProcessHandle { handle: exe_info.hProcess, pid: process_id })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_process<P1: AsRef<Path>, P2: AsRef<Path>>(
|
||||||
|
exe_path: P1,
|
||||||
|
args: Vec<String>,
|
||||||
|
work_dir: Option<P2>,
|
||||||
|
show_window: bool,
|
||||||
|
set_env: Option<HashMap<String, String>>,
|
||||||
|
) -> IoResult<SafeProcessHandle> {
|
||||||
|
let exe_path = exe_path.as_ref();
|
||||||
|
let exe_path = OsString::from(exe_path);
|
||||||
|
let exe_name = PCWSTR(exe_path.encode_wide().chain(Some(0)).collect::<Vec<_>>().as_mut_ptr());
|
||||||
|
|
||||||
|
let wrapped_args: Vec<Arg> = args.iter().map(|a| Arg::Regular(a.into())).collect();
|
||||||
|
let mut params = make_command_line(Some(&exe_path), &wrapped_args, false)?;
|
||||||
|
let params = PWSTR(params.as_mut_ptr());
|
||||||
|
|
||||||
|
let mut pi = windows::Win32::System::Threading::PROCESS_INFORMATION::default();
|
||||||
|
|
||||||
|
// let si = STARTUPINFOW {
|
||||||
|
// cb: std::mem::size_of::<STARTUPINFOW>() as u32,
|
||||||
|
// lpReserved: PWSTR::null(),
|
||||||
|
// lpDesktop: PWSTR::null(),
|
||||||
|
// lpTitle: PWSTR::null(),
|
||||||
|
// dwX: 0,
|
||||||
|
// dwY: 0,
|
||||||
|
// dwXSize: 0,
|
||||||
|
// dwYSize: 0,
|
||||||
|
// dwXCountChars: 0,
|
||||||
|
// dwYCountChars: 0,
|
||||||
|
// dwFillAttribute: 0,
|
||||||
|
// dwFlags: STARTUPINFOW_FLAGS(0),
|
||||||
|
// wShowWindow: 0,
|
||||||
|
// cbReserved2: 0,
|
||||||
|
// lpReserved2: std::ptr::null_mut(),
|
||||||
|
// hStdInput: HANDLE(std::ptr::null_mut()),
|
||||||
|
// hStdOutput: HANDLE(std::ptr::null_mut()),
|
||||||
|
// hStdError: HANDLE(std::ptr::null_mut()),
|
||||||
|
// };
|
||||||
|
|
||||||
|
let envp = make_envp(set_env)?;
|
||||||
|
let dirp = pathopt_to_pcwstr(work_dir)?;
|
||||||
|
|
||||||
|
let flags = if show_window { PROCESS_CREATION_FLAGS(0) } else { CREATE_NO_WINDOW };
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
info!("About to launch: '{:?}' in dir '{:?}' with arguments: {:?}", exe_name, dirp, args);
|
||||||
|
CreateProcessW(exe_name, Option::Some(params), None, None, false, flags, envp.0, dirp.0, std::ptr::null(), &mut pi)?;
|
||||||
|
let _ = AllowSetForegroundWindow(pi.dwProcessId);
|
||||||
|
let _ = CloseHandle(pi.hThread);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(SafeProcessHandle { handle: pi.hProcess, pid: pi.dwProcessId })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn duration_to_ms(dur: Duration) -> u32 {
|
||||||
|
let ms = dur
|
||||||
|
.as_secs()
|
||||||
|
.checked_mul(1000)
|
||||||
|
.and_then(|amt| amt.checked_add((dur.subsec_nanos() / 1_000_000) as u64))
|
||||||
|
.expect("failed to convert duration to milliseconds");
|
||||||
|
if ms > (u32::max_value() as u64) {
|
||||||
|
u32::max_value()
|
||||||
|
} else {
|
||||||
|
ms as u32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kill_process<T: Into<HANDLE>>(process: T) -> IoResult<()> {
|
||||||
|
let process = process.into();
|
||||||
|
unsafe {
|
||||||
|
if process.is_invalid() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
TerminateProcess(process, 1)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kill_pid(pid: u32) -> IoResult<()> {
|
||||||
|
let handle = open_pid_safe(PROCESS_TERMINATE, false, pid)?;
|
||||||
|
kill_process(handle)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum WaitResult {
|
||||||
|
WaitTimeout,
|
||||||
|
ExitCode(u32),
|
||||||
|
NoWaitRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wait_for_process_to_exit_with_timeout<T: Into<HANDLE>>(process: T, dur: Duration) -> IoResult<WaitResult> {
|
||||||
|
let process = process.into();
|
||||||
|
if process.is_invalid() {
|
||||||
|
return Ok(WaitResult::NoWaitRequired);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ms = duration_to_ms(dur);
|
||||||
|
info!("Waiting {}ms for process handle to exit.", ms);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
match WaitForSingleObject(process, ms) {
|
||||||
|
WAIT_OBJECT_0 => {}
|
||||||
|
WAIT_TIMEOUT => return Ok(WaitResult::WaitTimeout),
|
||||||
|
_ => return Err(IoError::last_os_error()),
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut exit_code = 0;
|
||||||
|
GetExitCodeProcess(process, &mut exit_code)?;
|
||||||
|
Ok(WaitResult::ExitCode(exit_code))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wait_for_pid_to_exit(pid: u32, dur: Duration) -> IoResult<WaitResult> {
|
||||||
|
info!("Waiting for process pid-{} to exit.", pid);
|
||||||
|
let handle = open_pid_safe(PROCESS_SYNCHRONIZE, false, pid)?;
|
||||||
|
wait_for_process_to_exit_with_timeout(handle, dur)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wait_for_parent_to_exit(dur: Duration) -> IoResult<WaitResult> {
|
||||||
|
info!("Reading parent process information.");
|
||||||
|
let basic_info = ProcessBasicInformation;
|
||||||
|
let my_handle = unsafe { GetCurrentProcess() };
|
||||||
|
let mut return_length: u32 = 0;
|
||||||
|
let return_length_ptr: *mut u32 = &mut return_length as *mut u32;
|
||||||
|
|
||||||
|
let mut info = PROCESS_BASIC_INFORMATION {
|
||||||
|
AffinityMask: 0,
|
||||||
|
BasePriority: 0,
|
||||||
|
ExitStatus: Default::default(),
|
||||||
|
InheritedFromUniqueProcessId: 0,
|
||||||
|
PebBaseAddress: std::ptr::null_mut(),
|
||||||
|
UniqueProcessId: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let info_ptr: *mut ::core::ffi::c_void = &mut info as *mut _ as *mut ::core::ffi::c_void;
|
||||||
|
let info_size = std::mem::size_of::<PROCESS_BASIC_INFORMATION>() as u32;
|
||||||
|
let hres = unsafe { NtQueryInformationProcess(my_handle, basic_info, info_ptr, info_size, return_length_ptr) };
|
||||||
|
if hres.is_err() {
|
||||||
|
return Err(IoError::new(IoErrorKind::Other, format!("NtQueryInformationProcess failed: {:?}", hres)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.InheritedFromUniqueProcessId <= 1 {
|
||||||
|
// the parent process has exited
|
||||||
|
info!("The parent process ({}) has already exited", info.InheritedFromUniqueProcessId);
|
||||||
|
return Ok(WaitResult::NoWaitRequired);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_pid_start_time(process: HANDLE) -> IoResult<u64> {
|
||||||
|
let mut creation = FILETIME::default();
|
||||||
|
let mut exit = FILETIME::default();
|
||||||
|
let mut kernel = FILETIME::default();
|
||||||
|
let mut user = FILETIME::default();
|
||||||
|
unsafe {
|
||||||
|
GetProcessTimes(process, &mut creation, &mut exit, &mut kernel, &mut user)?;
|
||||||
|
}
|
||||||
|
Ok(((creation.dwHighDateTime as u64) << 32) | creation.dwLowDateTime as u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
let permissions = PROCESS_SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION;
|
||||||
|
let parent_handle = open_pid_safe(permissions, false, info.InheritedFromUniqueProcessId as u32)?;
|
||||||
|
let parent_start_time = get_pid_start_time(parent_handle.handle())?;
|
||||||
|
let myself_start_time = get_pid_start_time(my_handle)?;
|
||||||
|
|
||||||
|
if parent_start_time > myself_start_time {
|
||||||
|
// the parent process has exited and the id has been re-used
|
||||||
|
info!(
|
||||||
|
"The parent process ({}) has already exited. parent_start={}, my_start={}",
|
||||||
|
info.InheritedFromUniqueProcessId, parent_start_time, myself_start_time
|
||||||
|
);
|
||||||
|
return Ok(WaitResult::NoWaitRequired);
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Waiting for parent process ({}) to exit.", info.InheritedFromUniqueProcessId);
|
||||||
|
wait_for_process_to_exit_with_timeout(parent_handle, dur)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user