mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Fix admin launching and locator
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2230,6 +2230,7 @@ dependencies = [
|
||||
"ureq",
|
||||
"url",
|
||||
"uuid",
|
||||
"waitpid-any",
|
||||
"windows",
|
||||
"xml",
|
||||
"zip",
|
||||
|
||||
@@ -23,7 +23,7 @@ edition = "2021"
|
||||
rust-version = "1.75"
|
||||
|
||||
[workspace.dependencies]
|
||||
velopack = { path = "src/lib-rust", features = ["file-logging"] }
|
||||
velopack = { path = "src/lib-rust", features = ["file-logging", "public-utils"] }
|
||||
log = "0.4"
|
||||
log-derive = "0.4.1"
|
||||
ureq = "3.0"
|
||||
|
||||
@@ -2,4 +2,5 @@ max_width = 140
|
||||
use_small_heuristics = "Max"
|
||||
indent_style = "Visual"
|
||||
unstable_features = true
|
||||
format_strings = true
|
||||
format_strings = true
|
||||
single_line_if_else_max_width = 40
|
||||
@@ -1,13 +1,12 @@
|
||||
use crate::{
|
||||
dialogs,
|
||||
shared::{self},
|
||||
// windows::locksmith,
|
||||
windows::splash,
|
||||
windows::{self, splash},
|
||||
};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use std::sync::mpsc;
|
||||
use std::{fs, path::PathBuf};
|
||||
use velopack::{bundle::load_bundle_from_file, constants, locator::VelopackLocator};
|
||||
use std::{sync::mpsc, time::Duration};
|
||||
use velopack::{bundle::load_bundle_from_file, constants, locator::VelopackLocator, process};
|
||||
|
||||
// fn ropycopy<P1: AsRef<Path>, P2: AsRef<Path>>(source: &P1, dest: &P2) -> Result<()> {
|
||||
// let source = source.as_ref();
|
||||
@@ -40,11 +39,31 @@ use velopack::{bundle::load_bundle_from_file, constants, locator::VelopackLocato
|
||||
// }
|
||||
|
||||
pub fn apply_package_impl(old_locator: &VelopackLocator, package: &PathBuf, run_hooks: bool) -> Result<VelopackLocator> {
|
||||
let root_path = old_locator.get_root_dir();
|
||||
|
||||
let mut bundle = load_bundle_from_file(package)?;
|
||||
let new_app_manifest = bundle.read_manifest()?;
|
||||
let new_locator = old_locator.clone_self_with_new_manifest(&new_app_manifest);
|
||||
|
||||
let root_path = old_locator.get_root_dir();
|
||||
if !windows::is_directory_writable(&root_path) {
|
||||
if process::is_current_process_elevated() {
|
||||
bail!("The root directory is not writable & process is already admin. The update cannot continue.");
|
||||
} else {
|
||||
info!("Re-launching as administrator to update in {:?}", root_path);
|
||||
|
||||
let package_string = package.to_string_lossy().to_string();
|
||||
let args = vec!["--norestart".to_string(), "--package".to_string(), package_string];
|
||||
let exe_path = std::env::current_exe()?;
|
||||
let work_dir: Option<String> = None; // same as this process
|
||||
let process_handle = process::run_process_as_admin(&exe_path, args, work_dir, false)?;
|
||||
|
||||
info!("Waiting (up to 10 minutes) for elevated process to exit...");
|
||||
let result = process::wait_for_process_to_exit_with_timeout(process_handle, Duration::from_secs(10 * 60))?;
|
||||
info!("Elevated process has exited ({:?}).", result);
|
||||
return Ok(new_locator);
|
||||
}
|
||||
}
|
||||
|
||||
let old_version = old_locator.get_manifest_version();
|
||||
let new_version = new_locator.get_manifest_version();
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use crate::shared::{self, OperationWait};
|
||||
use anyhow::Result;
|
||||
use velopack::locator::LocationContext;
|
||||
|
||||
#[allow(unused_variables, unused_imports)]
|
||||
pub fn start(
|
||||
wait: OperationWait,
|
||||
context: LocationContext,
|
||||
exe_name: Option<&String>,
|
||||
exe_args: Option<Vec<&str>>,
|
||||
legacy_args: Option<&String>,
|
||||
@@ -18,12 +20,11 @@ pub fn start(
|
||||
shared::operation_wait(wait);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
super::start_windows_impl::start_impl(exe_name, exe_args, legacy_args)?;
|
||||
super::start_windows_impl::start_impl(context, exe_name, exe_args, legacy_args)?;
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
use velopack::locator::{auto_locate_app_manifest, LocationContext};
|
||||
let locator = auto_locate_app_manifest(LocationContext::IAmUpdateExe)?;
|
||||
let locator = velopack::locator::auto_locate_app_manifest(context)?;
|
||||
shared::start_package(&locator, exe_args, None)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,8 +61,8 @@ impl LocatorResult {
|
||||
}
|
||||
}
|
||||
|
||||
fn legacy_locator() -> Result<LocatorResult> {
|
||||
let locator = locator::auto_locate_app_manifest(LocationContext::IAmUpdateExe);
|
||||
fn legacy_locator(context: LocationContext) -> Result<LocatorResult> {
|
||||
let locator = locator::auto_locate_app_manifest(context);
|
||||
match locator {
|
||||
Ok(locator) => Ok(LocatorResult::Normal(locator)),
|
||||
Err(e) => {
|
||||
@@ -81,11 +81,12 @@ fn legacy_locator() -> Result<LocatorResult> {
|
||||
}
|
||||
|
||||
pub fn start_impl(
|
||||
context: LocationContext,
|
||||
exe_name: Option<&String>,
|
||||
exe_args: Option<Vec<&str>>,
|
||||
legacy_args: Option<&String>,
|
||||
) -> Result<()> {
|
||||
let locator = legacy_locator()?;
|
||||
let locator = legacy_locator(context)?;
|
||||
let root_dir = locator.get_root_dir();
|
||||
let manifest = locator.get_manifest();
|
||||
if shared::has_app_prefixed_folder(&root_dir) {
|
||||
|
||||
@@ -2,9 +2,8 @@ use crate::windows::strings;
|
||||
use ::windows::{
|
||||
core::PWSTR,
|
||||
Win32::{
|
||||
Foundation::CloseHandle,
|
||||
System::ProcessStatus::EnumProcesses,
|
||||
System::Threading::{OpenProcess, QueryFullProcessImageNameW, PROCESS_NAME_WIN32, PROCESS_QUERY_LIMITED_INFORMATION},
|
||||
System::Threading::{QueryFullProcessImageNameW, PROCESS_NAME_WIN32, PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_TERMINATE},
|
||||
},
|
||||
};
|
||||
use anyhow::{bail, Result};
|
||||
@@ -33,27 +32,26 @@ fn get_pids() -> Result<Vec<u32>> {
|
||||
Ok(pids.iter().map(|x| *x as u32).collect())
|
||||
}
|
||||
|
||||
unsafe 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<Vec<(u32, PathBuf, process::SafeProcessHandle)>> {
|
||||
let dir = dir.as_ref();
|
||||
let mut oup = HashMap::new();
|
||||
let mut oup = Vec::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()? {
|
||||
let process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
|
||||
let process = process::open_process(PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE, false, pid);
|
||||
if process.is_err() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let process = process.unwrap();
|
||||
if process.is_invalid() {
|
||||
if process.handle().is_invalid() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut full_path_len = full_path_vec.len() as u32;
|
||||
if QueryFullProcessImageNameW(process, PROCESS_NAME_WIN32, full_path_ptr, &mut full_path_len).is_err() {
|
||||
let _ = CloseHandle(process);
|
||||
if QueryFullProcessImageNameW(process.handle(), PROCESS_NAME_WIN32, full_path_ptr, &mut full_path_len).is_err() {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -65,7 +63,7 @@ unsafe fn get_processes_running_in_directory<P: AsRef<Path>>(dir: P) -> Result<H
|
||||
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);
|
||||
oup.push((pid, full_path, process));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,13 +82,13 @@ fn _force_stop_package<P: AsRef<Path>>(root_dir: P) -> Result<()> {
|
||||
info!("Checking for running processes in: {}", dir.display());
|
||||
let processes = unsafe { get_processes_running_in_directory(dir)? };
|
||||
let my_pid = std::process::id();
|
||||
for (pid, exe) in processes.iter() {
|
||||
for (pid, path, handle) in processes.iter() {
|
||||
if *pid == my_pid {
|
||||
warn!("Skipping killing self: {} ({})", exe.display(), pid);
|
||||
warn!("Skipping killing self: {} ({})", path.display(), pid);
|
||||
continue;
|
||||
}
|
||||
warn!("Killing process: {} ({})", exe.display(), pid);
|
||||
process::kill_pid(*pid)?;
|
||||
warn!("Killing process: {} ({})", path.display(), pid);
|
||||
process::kill_process(handle)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -195,7 +193,7 @@ fn test_get_running_processes_finds_cargo() {
|
||||
assert!(processes.len() > 0);
|
||||
|
||||
let mut found = false;
|
||||
for (_pid, exe) in processes.iter() {
|
||||
for (_pid, exe, _handle) in processes.iter() {
|
||||
if exe.ends_with("cargo.exe") {
|
||||
found = true;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ fn root_command() -> Command {
|
||||
)
|
||||
.arg(arg!(--verbose "Print debug messages to console / log").global(true))
|
||||
.arg(arg!(-s --silent "Don't show any prompts / dialogs").global(true))
|
||||
.arg(arg!(--root <PATH> "Override the default locator root directory").global(true).hide(true).value_parser(value_parser!(PathBuf)))
|
||||
.arg(arg!(-l --log <PATH> "Override the default log file location").global(true).value_parser(value_parser!(PathBuf)))
|
||||
// Legacy arguments should not be fully removed if it's possible to keep them
|
||||
// Reason being is clap.ignore_errors(true) is not 100%, and sometimes old args can trip things up.
|
||||
@@ -131,9 +132,16 @@ fn main() -> Result<()> {
|
||||
let silent = get_flag_or_false(&matches, "silent");
|
||||
dialogs::set_silent(silent);
|
||||
|
||||
let root_dir = matches.get_one::<PathBuf>("root");
|
||||
let location_context = if let Some(root) = root_dir {
|
||||
LocationContext::FromSpecifiedRootDir(root.clone())
|
||||
} else {
|
||||
LocationContext::IAmUpdateExe
|
||||
};
|
||||
|
||||
let verbose = get_flag_or_false(&matches, "verbose");
|
||||
let log_file = matches.get_one("log");
|
||||
let desired_log_file = log_file.cloned().unwrap_or(default_logfile_path(LocationContext::IAmUpdateExe));
|
||||
let desired_log_file = log_file.cloned().unwrap_or(default_logfile_path(&location_context));
|
||||
init_logging("update", Some(&desired_log_file), true, verbose, None);
|
||||
|
||||
// change working directory to the parent directory of the exe
|
||||
@@ -148,16 +156,17 @@ fn main() -> Result<()> {
|
||||
info!(" Verbose: {}", verbose);
|
||||
info!(" Silent: {}", silent);
|
||||
info!(" Log File: {:?}", log_file);
|
||||
info!(" Context: {:?}", &location_context);
|
||||
|
||||
let (subcommand, subcommand_matches) =
|
||||
matches.subcommand().ok_or_else(|| anyhow!("No known subcommand was used. Try `--help` for more information."))?;
|
||||
|
||||
let result = match subcommand {
|
||||
#[cfg(target_os = "windows")]
|
||||
"uninstall" => uninstall(subcommand_matches).map_err(|e| anyhow!("Uninstall error: {}", e)),
|
||||
"start" => start(subcommand_matches).map_err(|e| anyhow!("Start error: {}", e)),
|
||||
"apply" => apply(subcommand_matches).map_err(|e| anyhow!("Apply error: {}", e)),
|
||||
"patch" => patch(subcommand_matches).map_err(|e| anyhow!("Patch error: {}", e)),
|
||||
"uninstall" => uninstall(location_context, subcommand_matches).map_err(|e| anyhow!("Uninstall error: {}", e)),
|
||||
"start" => start(location_context, subcommand_matches).map_err(|e| anyhow!("Start error: {}", e)),
|
||||
"apply" => apply(location_context, subcommand_matches).map_err(|e| anyhow!("Apply error: {}", e)),
|
||||
"patch" => patch(location_context, subcommand_matches).map_err(|e| anyhow!("Patch error: {}", e)),
|
||||
_ => bail!("Unknown subcommand '{subcommand}'. Try `--help` for more information."),
|
||||
};
|
||||
|
||||
@@ -169,7 +178,7 @@ fn main() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn patch(matches: &ArgMatches) -> Result<()> {
|
||||
fn patch(_context: LocationContext, matches: &ArgMatches) -> Result<()> {
|
||||
let old_file = matches.get_one::<PathBuf>("old");
|
||||
let patch_file = matches.get_one::<PathBuf>("patch");
|
||||
let output_file = matches.get_one::<PathBuf>("output");
|
||||
@@ -199,7 +208,7 @@ fn get_apply_args(matches: &ArgMatches) -> (OperationWait, bool, Option<&PathBuf
|
||||
(wait, restart, package, exe_args)
|
||||
}
|
||||
|
||||
fn apply(matches: &ArgMatches) -> Result<()> {
|
||||
fn apply(context: LocationContext, matches: &ArgMatches) -> Result<()> {
|
||||
let (wait, restart, package, exe_args) = get_apply_args(matches);
|
||||
info!("Command: Apply");
|
||||
info!(" Restart: {:?}", restart);
|
||||
@@ -207,7 +216,7 @@ fn apply(matches: &ArgMatches) -> Result<()> {
|
||||
info!(" Package: {:?}", package);
|
||||
info!(" Exe Args: {:?}", exe_args);
|
||||
|
||||
let locator = auto_locate_app_manifest(LocationContext::IAmUpdateExe)?;
|
||||
let locator = auto_locate_app_manifest(context)?;
|
||||
let _mutex = locator.try_get_exclusive_lock()?;
|
||||
let _ = commands::apply(&locator, restart, wait, package, exe_args, true)?;
|
||||
Ok(())
|
||||
@@ -221,7 +230,7 @@ fn get_start_args(matches: &ArgMatches) -> (OperationWait, Option<&String>, Opti
|
||||
(wait, exe_name, legacy_args, exe_args)
|
||||
}
|
||||
|
||||
fn start(matches: &ArgMatches) -> Result<()> {
|
||||
fn start(context: LocationContext, matches: &ArgMatches) -> Result<()> {
|
||||
let (wait, exe_name, legacy_args, exe_args) = get_start_args(matches);
|
||||
|
||||
info!("Command: Start");
|
||||
@@ -233,13 +242,13 @@ fn start(matches: &ArgMatches) -> Result<()> {
|
||||
warn!("Legacy args format is deprecated and will be removed in a future release. Please update your application to use the new format.");
|
||||
}
|
||||
|
||||
commands::start(wait, exe_name, exe_args, legacy_args)
|
||||
commands::start(wait, context, exe_name, exe_args, legacy_args)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn uninstall(_matches: &ArgMatches) -> Result<()> {
|
||||
fn uninstall(context: LocationContext, _matches: &ArgMatches) -> Result<()> {
|
||||
info!("Command: Uninstall");
|
||||
let locator = auto_locate_app_manifest(LocationContext::IAmUpdateExe)?;
|
||||
let locator = auto_locate_app_manifest(context)?;
|
||||
commands::uninstall(&locator, true)
|
||||
}
|
||||
|
||||
@@ -256,6 +265,15 @@ fn test_cli_parse_handles_equals_spaces() {
|
||||
assert_eq!(exe_args, None);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[test]
|
||||
fn test_cli_handles_root_at_end() {
|
||||
let command = vec!["C:\\Some Path\\With = Spaces\\Update.exe", "apply", "--package", "C:\\Package.zip", "--root", "C:\\Some Path"];
|
||||
let matches = try_parse_command_line_matches(command.iter().map(|s| s.to_string()).collect()).unwrap();
|
||||
let root = matches.get_one::<PathBuf>("root");
|
||||
assert_eq!(root, Some(&PathBuf::from("C:\\Some Path")));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[test]
|
||||
fn test_start_command_supports_legacy_commands() {
|
||||
|
||||
@@ -16,7 +16,7 @@ pub fn write_uninstall_entry(locator: &VelopackLocator) -> Result<()> {
|
||||
let main_exe_path = locator.get_main_exe_path_as_string();
|
||||
let updater_path = locator.get_update_path_as_string();
|
||||
|
||||
let folder_size = fs_extra::dir::get_size(locator.get_root_dir()).unwrap_or(0);
|
||||
let folder_size = fs_extra::dir::get_size(locator.get_current_bin_dir()).unwrap_or(0);
|
||||
let short_version = locator.get_manifest_version_short_string();
|
||||
|
||||
let now = DateTime::now();
|
||||
|
||||
@@ -19,7 +19,7 @@ pub fn run_hook(locator: &VelopackLocator, hook_name: &str, timeout_secs: u64) -
|
||||
let sw = simple_stopwatch::Stopwatch::start_new();
|
||||
let root_dir = locator.get_root_dir();
|
||||
let current_path = locator.get_current_bin_dir();
|
||||
let main_exe_path = locator.get_main_exe_path_as_string();
|
||||
let main_exe_path = locator.get_main_exe_path();
|
||||
let ver_string = locator.get_manifest_version_full_string();
|
||||
let args = vec![hook_name, &ver_string];
|
||||
let mut success = false;
|
||||
@@ -40,7 +40,7 @@ pub fn run_hook(locator: &VelopackLocator, hook_name: &str, timeout_secs: u64) -
|
||||
|
||||
let cmd = cmd.unwrap();
|
||||
|
||||
match process::wait_for_process_to_exit_with_timeout(cmd.handle(), Duration::from_secs(timeout_secs)) {
|
||||
match process::wait_for_process_to_exit_with_timeout(&cmd, Duration::from_secs(timeout_secs)) {
|
||||
Ok(WaitResult::NoWaitRequired) => {
|
||||
warn!("Was unable to wait for hook (it may have exited too quickly).");
|
||||
}
|
||||
@@ -53,7 +53,7 @@ pub fn run_hook(locator: &VelopackLocator, hook_name: &str, timeout_secs: u64) -
|
||||
}
|
||||
}
|
||||
Ok(WaitResult::WaitTimeout) => {
|
||||
let _ = process::kill_process(cmd.handle());
|
||||
let _ = process::kill_process(&cmd);
|
||||
error!("Process timed out after {}s and was killed.", timeout_secs);
|
||||
}
|
||||
Err(e) => {
|
||||
|
||||
@@ -1,22 +1,11 @@
|
||||
use std::path::PathBuf;
|
||||
use semver::Version;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
bundle::{self, Manifest},
|
||||
util, Error,
|
||||
lockfile::LockFile
|
||||
lockfile::LockFile,
|
||||
misc, Error,
|
||||
};
|
||||
|
||||
/// Returns the default channel name for the current OS.
|
||||
pub fn default_channel_name() -> String {
|
||||
#[cfg(target_os = "windows")]
|
||||
return "win".to_owned();
|
||||
#[cfg(target_os = "linux")]
|
||||
return "linux".to_owned();
|
||||
#[cfg(target_os = "macos")]
|
||||
return "osx".to_owned();
|
||||
}
|
||||
use semver::Version;
|
||||
use std::path::PathBuf;
|
||||
use uuid::Uuid;
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
@@ -110,10 +99,16 @@ impl TryFrom<LocationContext> for VelopackLocator {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&LocationContext> for VelopackLocator {
|
||||
type Error = Error;
|
||||
fn try_from(context: &LocationContext) -> Result<Self, Self::Error> {
|
||||
auto_locate_app_manifest(context.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl VelopackLocator {
|
||||
/// Creates a new VelopackLocator from the given paths, trying to auto-detect the manifest.
|
||||
pub fn new(config: &VelopackLocatorConfig) -> Result<VelopackLocator, Error>
|
||||
{
|
||||
pub fn new(config: &VelopackLocatorConfig) -> Result<VelopackLocator, Error> {
|
||||
if !config.UpdateExePath.exists() {
|
||||
return Err(Error::MissingUpdateExe);
|
||||
}
|
||||
@@ -122,10 +117,32 @@ impl VelopackLocator {
|
||||
}
|
||||
|
||||
let manifest = read_current_manifest(&config.ManifestPath)?;
|
||||
Ok(Self { paths: config.clone(), manifest })
|
||||
Ok(Self::new_with_manifest(config.clone(), manifest))
|
||||
}
|
||||
|
||||
|
||||
/// Creates a new VelopackLocator from the given paths and manifest.
|
||||
#[cfg(windows)]
|
||||
pub fn new_with_manifest(mut paths: VelopackLocatorConfig, manifest: Manifest) -> Self {
|
||||
let root = paths.RootAppDir.clone();
|
||||
if root.starts_with("C:\\Program Files") || !misc::is_directory_writable(&root) {
|
||||
let temp_root = std::env::temp_dir().join(format!("velopack_{}", manifest.id));
|
||||
let orig_update_path = paths.UpdateExePath.clone();
|
||||
paths.PackagesDir = temp_root.join("packages");
|
||||
if !paths.PackagesDir.exists() {
|
||||
std::fs::create_dir_all(&paths.PackagesDir).unwrap();
|
||||
}
|
||||
paths.UpdateExePath = temp_root.join("Update.exe");
|
||||
if !paths.UpdateExePath.exists() && orig_update_path.exists() {
|
||||
std::fs::copy(orig_update_path, &paths.UpdateExePath).unwrap();
|
||||
warn!("Application directory is not writable. Copying Update.exe to temp location: {:?}", paths.UpdateExePath);
|
||||
}
|
||||
}
|
||||
|
||||
Self { paths, manifest }
|
||||
}
|
||||
|
||||
/// Creates a new VelopackLocator from the given paths and manifest.
|
||||
#[cfg(not(windows))]
|
||||
pub fn new_with_manifest(paths: VelopackLocatorConfig, manifest: Manifest) -> Self {
|
||||
Self { paths, manifest }
|
||||
}
|
||||
@@ -159,7 +176,7 @@ impl VelopackLocator {
|
||||
|
||||
/// Get the name of a new temporary directory inside get_temp_dir_root() with a random 16-character suffix.
|
||||
pub fn get_temp_dir_rand16(&self) -> PathBuf {
|
||||
self.get_temp_dir_root().join("tmp_".to_string() + &util::random_string(16))
|
||||
self.get_temp_dir_root().join("tmp_".to_string() + &misc::random_string(16))
|
||||
}
|
||||
|
||||
/// Returns the path to the current app temporary directory as a string.
|
||||
@@ -274,19 +291,15 @@ impl VelopackLocator {
|
||||
}
|
||||
|
||||
/// Returns a copy of the current VelopackLocator with the manifest field set to the given manifest.
|
||||
pub fn clone_self_with_new_manifest(&self, manifest: &Manifest) -> VelopackLocator
|
||||
{
|
||||
VelopackLocator {
|
||||
paths: self.paths.clone(),
|
||||
manifest: manifest.clone(),
|
||||
}
|
||||
pub fn clone_self_with_new_manifest(&self, manifest: &Manifest) -> VelopackLocator {
|
||||
VelopackLocator { paths: self.paths.clone(), manifest: manifest.clone() }
|
||||
}
|
||||
|
||||
/// Returns whether the app is portable or installed.
|
||||
pub fn get_is_portable(&self) -> bool {
|
||||
self.paths.IsPortable
|
||||
}
|
||||
|
||||
|
||||
/// Attemps to open / lock a file in the app's package directory for exclusive write access.
|
||||
/// Fails immediately if the lock cannot be acquired.
|
||||
pub fn try_get_exclusive_lock(&self) -> Result<LockFile, Error> {
|
||||
@@ -324,8 +337,7 @@ impl VelopackLocator {
|
||||
/// Create a paths object containing default / ideal paths for a given root directory
|
||||
/// Generally, this should not be used except for installing the app for the first time.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn create_config_from_root_dir<P: AsRef<std::path::Path>>(root_dir: P) -> VelopackLocatorConfig
|
||||
{
|
||||
pub fn create_config_from_root_dir<P: AsRef<std::path::Path>>(root_dir: P) -> VelopackLocatorConfig {
|
||||
let root_dir = root_dir.as_ref();
|
||||
VelopackLocatorConfig {
|
||||
RootAppDir: root_dir.to_path_buf(),
|
||||
@@ -338,8 +350,8 @@ pub fn create_config_from_root_dir<P: AsRef<std::path::Path>>(root_dir: P) -> Ve
|
||||
}
|
||||
|
||||
/// LocationContext is an enumeration of possible contexts for locating the current app manifest.
|
||||
pub enum LocationContext
|
||||
{
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum LocationContext {
|
||||
/// Should not really be used, will try a few other enumerations to locate the app manifest.
|
||||
Unknown,
|
||||
/// Locates the app manifest by assuming the current process is Update.exe.
|
||||
@@ -352,8 +364,8 @@ pub enum LocationContext
|
||||
FromSpecifiedAppExecutable(PathBuf),
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
/// Automatically locates the current app's important paths. If the app is not installed, it will return an error.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn auto_locate_app_manifest(context: LocationContext) -> Result<VelopackLocator, Error> {
|
||||
info!("Auto-locating app manifest...");
|
||||
match context {
|
||||
@@ -409,7 +421,7 @@ pub fn auto_locate_app_manifest(context: LocationContext) -> Result<VelopackLoca
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Err(Error::NotInstalled("Could not auto-locate app manifest".to_owned()))
|
||||
}
|
||||
|
||||
@@ -441,16 +453,18 @@ pub fn auto_locate_app_manifest(context: LocationContext) -> Result<VelopackLoca
|
||||
let appimage_path = match std::env::var("APPIMAGE") {
|
||||
Ok(v) => {
|
||||
if v.is_empty() || !PathBuf::from(&v).exists() {
|
||||
return Err(Error::NotInstalled("The 'APPIMAGE' environment variable should point to the current AppImage path.".to_string()));
|
||||
return Err(Error::NotInstalled(
|
||||
"The 'APPIMAGE' environment variable should point to the current AppImage path.".to_string(),
|
||||
));
|
||||
} else {
|
||||
v
|
||||
}
|
||||
},
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(Error::NotInstalled("The 'APPIMAGE' environment variable should point to the current AppImage path.".to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let app = read_current_manifest(&metadata_path)?;
|
||||
let packages_dir = PathBuf::from("/var/tmp/velopack").join(&app.id).join("packages");
|
||||
|
||||
@@ -511,13 +525,13 @@ pub fn auto_locate_app_manifest(context: LocationContext) -> Result<VelopackLoca
|
||||
CurrentBinaryDir: contents_dir,
|
||||
IsPortable: true,
|
||||
};
|
||||
|
||||
|
||||
Ok(VelopackLocator::new_with_manifest(config, app))
|
||||
}
|
||||
|
||||
fn read_current_manifest(nuspec_path: &PathBuf) -> Result<Manifest, Error> {
|
||||
if nuspec_path.exists() {
|
||||
if let Ok(nuspec) = util::retry_io(|| std::fs::read_to_string(nuspec_path)) {
|
||||
if let Ok(nuspec) = misc::retry_io(|| std::fs::read_to_string(nuspec_path)) {
|
||||
return bundle::read_manifest_from_string(&nuspec);
|
||||
}
|
||||
}
|
||||
@@ -553,7 +567,7 @@ fn test_locator_staged_id_for_new_user() {
|
||||
//Create new locator with paths to a test directory
|
||||
let tmp_dir = tempfile::TempDir::new().unwrap();
|
||||
let tmp_buf = tmp_dir.path().to_path_buf();
|
||||
let test_dir = tmp_buf.join(format!("velopack_{}", util::random_string(8)));
|
||||
let test_dir = tmp_buf.join(format!("velopack_{}", misc::random_string(8)));
|
||||
|
||||
let mut paths = VelopackLocatorConfig::default();
|
||||
paths.PackagesDir = test_dir;
|
||||
@@ -580,7 +594,7 @@ fn test_locator_staged_id_for_new_user() {
|
||||
fn test_locator_staged_id_for_existing_user() {
|
||||
let tmp_dir = tempfile::TempDir::new().unwrap();
|
||||
let tmp_buf = tmp_dir.path().to_path_buf();
|
||||
let test_dir = tmp_buf.join(format!("velopack_{}", util::random_string(8)));
|
||||
let test_dir = tmp_buf.join(format!("velopack_{}", misc::random_string(8)));
|
||||
|
||||
let mut paths = VelopackLocatorConfig::default();
|
||||
paths.PackagesDir = test_dir;
|
||||
|
||||
@@ -506,12 +506,15 @@ impl UpdateManager {
|
||||
if silent {
|
||||
args.push("--silent".to_string());
|
||||
}
|
||||
|
||||
if !restart {
|
||||
args.push("--norestart".to_string());
|
||||
}
|
||||
|
||||
let restart_args: Vec<String> = restart_args.into_iter().map(|item| item.as_ref().to_string()).collect();
|
||||
args.push("--root".to_string());
|
||||
args.push(self.locator.get_root_dir_as_string());
|
||||
|
||||
let restart_args: Vec<String> = restart_args.into_iter().map(|item| item.as_ref().to_string()).collect();
|
||||
if !restart_args.is_empty() {
|
||||
args.push("--".to_string());
|
||||
for arg in restart_args {
|
||||
|
||||
@@ -14,8 +14,9 @@ use windows::{
|
||||
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,
|
||||
TerminateProcess, WaitForSingleObject, CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT, PROCESS_ACCESS_RIGHTS,
|
||||
PROCESS_BASIC_INFORMATION, PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_SYNCHRONIZE, PROCESS_TERMINATE, STARTUPINFOW,
|
||||
STARTUPINFOW_FLAGS,
|
||||
},
|
||||
UI::{
|
||||
Shell::{ShellExecuteExW, SEE_MASK_NOCLOSEPROCESS, SHELLEXECUTEINFOW},
|
||||
@@ -51,7 +52,14 @@ fn ensure_no_nuls<T: AsRef<OsStr>>(str: T) -> IoResult<T> {
|
||||
|
||||
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::Regular(arg) => (
|
||||
arg,
|
||||
if force_quotes {
|
||||
Quote::Always
|
||||
} else {
|
||||
Quote::Auto
|
||||
},
|
||||
),
|
||||
Arg::Raw(arg) => (arg, Quote::Never),
|
||||
};
|
||||
|
||||
@@ -206,26 +214,23 @@ impl SafeProcessHandle {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u32> for SafeProcessHandle {
|
||||
fn into(self) -> u32 {
|
||||
self.pid()
|
||||
impl AsRef<HANDLE> for SafeProcessHandle {
|
||||
fn as_ref(&self) -> &HANDLE {
|
||||
&self.handle
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<HANDLE> for SafeProcessHandle {
|
||||
fn into(self) -> HANDLE {
|
||||
self.handle()
|
||||
}
|
||||
}
|
||||
// impl Into<u32> for SafeProcessHandle {
|
||||
// fn into(self) -> u32 {
|
||||
// self.pid()
|
||||
// }
|
||||
// }
|
||||
|
||||
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 });
|
||||
}
|
||||
// impl Into<HANDLE> for SafeProcessHandle {
|
||||
// fn into(self) -> HANDLE {
|
||||
// self.handle()
|
||||
// }
|
||||
// }
|
||||
|
||||
fn os_to_pcwstr<P: AsRef<OsStr>>(d: P) -> IoResult<(PCWSTR, Vec<u16>)> {
|
||||
let d = d.as_ref();
|
||||
@@ -258,8 +263,11 @@ pub fn run_process_as_admin<P1: AsRef<Path>, P2: AsRef<Path>>(
|
||||
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 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,
|
||||
@@ -290,7 +298,9 @@ pub fn run_process<P1: AsRef<Path>, P2: AsRef<Path>>(
|
||||
) -> 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 exe_name_ptr = os_to_pcwstr(&exe_path)?;
|
||||
|
||||
let work_dir = work_dir.map(|d| d.as_ref().to_path_buf());
|
||||
|
||||
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)?;
|
||||
@@ -298,35 +308,39 @@ pub fn run_process<P1: AsRef<Path>, P2: AsRef<Path>>(
|
||||
|
||||
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 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 dirp = pathopt_to_pcwstr(work_dir.as_deref())?;
|
||||
|
||||
let flags = if show_window { PROCESS_CREATION_FLAGS(0) } else { CREATE_NO_WINDOW };
|
||||
let flags = if show_window {
|
||||
CREATE_UNICODE_ENVIRONMENT
|
||||
} else {
|
||||
CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT
|
||||
};
|
||||
|
||||
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)?;
|
||||
info!("About to launch: '{:?}' in dir '{:?}' with arguments: {:?}", exe_path, work_dir, args);
|
||||
CreateProcessW(exe_name_ptr.0, Option::Some(params), None, None, false, flags, envp.0, dirp.0, &si, &mut pi)?;
|
||||
let _ = AllowSetForegroundWindow(pi.dwProcessId);
|
||||
let _ = CloseHandle(pi.hThread);
|
||||
}
|
||||
@@ -347,31 +361,41 @@ fn duration_to_ms(dur: Duration) -> u32 {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kill_process<T: Into<HANDLE>>(process: T) -> IoResult<()> {
|
||||
let process = process.into();
|
||||
pub fn kill_process<T: AsRef<HANDLE>>(process: T) -> IoResult<()> {
|
||||
let process = process.as_ref();
|
||||
unsafe {
|
||||
if process.is_invalid() {
|
||||
return Ok(());
|
||||
}
|
||||
TerminateProcess(process, 1)?;
|
||||
TerminateProcess(*process, 1)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn open_process(
|
||||
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 });
|
||||
}
|
||||
|
||||
pub fn kill_pid(pid: u32) -> IoResult<()> {
|
||||
let handle = open_pid_safe(PROCESS_TERMINATE, false, pid)?;
|
||||
let handle = open_process(PROCESS_TERMINATE, false, pid)?;
|
||||
kill_process(handle)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
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();
|
||||
pub fn wait_for_process_to_exit_with_timeout<T: AsRef<HANDLE>>(process: T, dur: Duration) -> IoResult<WaitResult> {
|
||||
let process = *process.as_ref();
|
||||
if process.is_invalid() {
|
||||
return Ok(WaitResult::NoWaitRequired);
|
||||
}
|
||||
@@ -394,7 +418,7 @@ pub fn wait_for_process_to_exit_with_timeout<T: Into<HANDLE>>(process: T, dur: D
|
||||
|
||||
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)?;
|
||||
let handle = open_process(PROCESS_SYNCHRONIZE, false, pid)?;
|
||||
wait_for_process_to_exit_with_timeout(handle, dur)
|
||||
}
|
||||
|
||||
@@ -439,7 +463,7 @@ pub fn wait_for_parent_to_exit(dur: Duration) -> IoResult<WaitResult> {
|
||||
}
|
||||
|
||||
let permissions = PROCESS_SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION;
|
||||
let parent_handle = open_pid_safe(permissions, false, info.InheritedFromUniqueProcessId as u32)?;
|
||||
let parent_handle = open_process(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)?;
|
||||
|
||||
@@ -455,3 +479,13 @@ pub fn wait_for_parent_to_exit(dur: Duration) -> IoResult<WaitResult> {
|
||||
info!("Waiting for parent process ({}) to exit.", info.InheritedFromUniqueProcessId);
|
||||
wait_for_process_to_exit_with_timeout(parent_handle, dur)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kill_process() {
|
||||
let cmd =
|
||||
std::process::Command::new("cmd.exe").arg("/C").arg("ping").arg("8.8.8.8").arg("-t").spawn().expect("failed to start process");
|
||||
|
||||
let pid = cmd.id();
|
||||
|
||||
kill_pid(pid).expect("failed to kill process");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user