Fix admin launching and locator

This commit is contained in:
Caelan Sayler
2025-05-02 19:00:17 +01:00
parent 3f0b00e6cd
commit 9573a511b5
13 changed files with 229 additions and 139 deletions

1
Cargo.lock generated
View File

@@ -2230,6 +2230,7 @@ dependencies = [
"ureq", "ureq",
"url", "url",
"uuid", "uuid",
"waitpid-any",
"windows", "windows",
"xml", "xml",
"zip", "zip",

View File

@@ -23,7 +23,7 @@ edition = "2021"
rust-version = "1.75" rust-version = "1.75"
[workspace.dependencies] [workspace.dependencies]
velopack = { path = "src/lib-rust", features = ["file-logging"] } velopack = { path = "src/lib-rust", features = ["file-logging", "public-utils"] }
log = "0.4" log = "0.4"
log-derive = "0.4.1" log-derive = "0.4.1"
ureq = "3.0" ureq = "3.0"

View File

@@ -2,4 +2,5 @@ max_width = 140
use_small_heuristics = "Max" use_small_heuristics = "Max"
indent_style = "Visual" indent_style = "Visual"
unstable_features = true unstable_features = true
format_strings = true format_strings = true
single_line_if_else_max_width = 40

View File

@@ -1,13 +1,12 @@
use crate::{ use crate::{
dialogs, dialogs,
shared::{self}, shared::{self},
// windows::locksmith, windows::{self, splash},
windows::splash,
}; };
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use std::sync::mpsc;
use std::{fs, path::PathBuf}; 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<()> { // fn ropycopy<P1: AsRef<Path>, P2: AsRef<Path>>(source: &P1, dest: &P2) -> Result<()> {
// let source = source.as_ref(); // 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> { 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 mut bundle = load_bundle_from_file(package)?;
let new_app_manifest = bundle.read_manifest()?; let new_app_manifest = bundle.read_manifest()?;
let new_locator = old_locator.clone_self_with_new_manifest(&new_app_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 old_version = old_locator.get_manifest_version();
let new_version = new_locator.get_manifest_version(); let new_version = new_locator.get_manifest_version();

View File

@@ -1,9 +1,11 @@
use crate::shared::{self, OperationWait}; use crate::shared::{self, OperationWait};
use anyhow::Result; use anyhow::Result;
use velopack::locator::LocationContext;
#[allow(unused_variables, unused_imports)] #[allow(unused_variables, unused_imports)]
pub fn start( pub fn start(
wait: OperationWait, wait: OperationWait,
context: LocationContext,
exe_name: Option<&String>, exe_name: Option<&String>,
exe_args: Option<Vec<&str>>, exe_args: Option<Vec<&str>>,
legacy_args: Option<&String>, legacy_args: Option<&String>,
@@ -18,12 +20,11 @@ pub fn start(
shared::operation_wait(wait); shared::operation_wait(wait);
#[cfg(target_os = "windows")] #[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"))] #[cfg(not(target_os = "windows"))]
{ {
use velopack::locator::{auto_locate_app_manifest, LocationContext}; let locator = velopack::locator::auto_locate_app_manifest(context)?;
let locator = auto_locate_app_manifest(LocationContext::IAmUpdateExe)?;
shared::start_package(&locator, exe_args, None)?; shared::start_package(&locator, exe_args, None)?;
} }

View File

@@ -61,8 +61,8 @@ impl LocatorResult {
} }
} }
fn legacy_locator() -> Result<LocatorResult> { fn legacy_locator(context: LocationContext) -> Result<LocatorResult> {
let locator = locator::auto_locate_app_manifest(LocationContext::IAmUpdateExe); let locator = locator::auto_locate_app_manifest(context);
match locator { match locator {
Ok(locator) => Ok(LocatorResult::Normal(locator)), Ok(locator) => Ok(LocatorResult::Normal(locator)),
Err(e) => { Err(e) => {
@@ -81,11 +81,12 @@ fn legacy_locator() -> Result<LocatorResult> {
} }
pub fn start_impl( pub fn start_impl(
context: LocationContext,
exe_name: Option<&String>, exe_name: Option<&String>,
exe_args: Option<Vec<&str>>, exe_args: Option<Vec<&str>>,
legacy_args: Option<&String>, legacy_args: Option<&String>,
) -> Result<()> { ) -> Result<()> {
let locator = legacy_locator()?; let locator = legacy_locator(context)?;
let root_dir = locator.get_root_dir(); let root_dir = locator.get_root_dir();
let manifest = locator.get_manifest(); let manifest = locator.get_manifest();
if shared::has_app_prefixed_folder(&root_dir) { if shared::has_app_prefixed_folder(&root_dir) {

View File

@@ -2,9 +2,8 @@ use crate::windows::strings;
use ::windows::{ use ::windows::{
core::PWSTR, core::PWSTR,
Win32::{ Win32::{
Foundation::CloseHandle,
System::ProcessStatus::EnumProcesses, 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}; use anyhow::{bail, Result};
@@ -33,27 +32,26 @@ fn get_pids() -> Result<Vec<u32>> {
Ok(pids.iter().map(|x| *x as u32).collect()) 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 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 mut full_path_vec = vec![0; i16::MAX as usize];
let full_path_ptr = PWSTR(full_path_vec.as_mut_ptr()); let full_path_ptr = PWSTR(full_path_vec.as_mut_ptr());
for pid in get_pids()? { 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() { if process.is_err() {
continue; continue;
} }
let process = process.unwrap(); let process = process.unwrap();
if process.is_invalid() { if process.handle().is_invalid() {
continue; continue;
} }
let mut full_path_len = full_path_vec.len() as u32; 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() { if QueryFullProcessImageNameW(process.handle(), PROCESS_NAME_WIN32, full_path_ptr, &mut full_path_len).is_err() {
let _ = CloseHandle(process);
continue; 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()); let full_path = PathBuf::from(full_path.unwrap());
if let Ok(is_subpath) = crate::windows::is_sub_path(&full_path, dir) { if let Ok(is_subpath) = crate::windows::is_sub_path(&full_path, dir) {
if is_subpath { 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()); info!("Checking for running processes in: {}", dir.display());
let processes = unsafe { 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, path, handle) in processes.iter() {
if *pid == my_pid { if *pid == my_pid {
warn!("Skipping killing self: {} ({})", exe.display(), pid); warn!("Skipping killing self: {} ({})", path.display(), pid);
continue; continue;
} }
warn!("Killing process: {} ({})", exe.display(), pid); warn!("Killing process: {} ({})", path.display(), pid);
process::kill_pid(*pid)?; process::kill_process(handle)?;
} }
Ok(()) Ok(())
} }
@@ -195,7 +193,7 @@ fn test_get_running_processes_finds_cargo() {
assert!(processes.len() > 0); assert!(processes.len() > 0);
let mut found = false; let mut found = false;
for (_pid, exe) in processes.iter() { for (_pid, exe, _handle) in processes.iter() {
if exe.ends_with("cargo.exe") { if exe.ends_with("cargo.exe") {
found = true; found = true;
} }

View File

@@ -41,6 +41,7 @@ fn root_command() -> Command {
) )
.arg(arg!(--verbose "Print debug messages to console / log").global(true)) .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!(-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))) .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 // 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. // 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"); let silent = get_flag_or_false(&matches, "silent");
dialogs::set_silent(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 verbose = get_flag_or_false(&matches, "verbose");
let log_file = matches.get_one("log"); 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); init_logging("update", Some(&desired_log_file), true, verbose, None);
// change working directory to the parent directory of the exe // change working directory to the parent directory of the exe
@@ -148,16 +156,17 @@ fn main() -> Result<()> {
info!(" Verbose: {}", verbose); info!(" Verbose: {}", verbose);
info!(" Silent: {}", silent); info!(" Silent: {}", silent);
info!(" Log File: {:?}", log_file); info!(" Log File: {:?}", log_file);
info!(" Context: {:?}", &location_context);
let (subcommand, subcommand_matches) = let (subcommand, subcommand_matches) =
matches.subcommand().ok_or_else(|| anyhow!("No known subcommand was used. Try `--help` for more information."))?; matches.subcommand().ok_or_else(|| anyhow!("No known subcommand was used. Try `--help` for more information."))?;
let result = match subcommand { let result = match subcommand {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
"uninstall" => uninstall(subcommand_matches).map_err(|e| anyhow!("Uninstall error: {}", e)), "uninstall" => uninstall(location_context, subcommand_matches).map_err(|e| anyhow!("Uninstall error: {}", e)),
"start" => start(subcommand_matches).map_err(|e| anyhow!("Start error: {}", e)), "start" => start(location_context, subcommand_matches).map_err(|e| anyhow!("Start error: {}", e)),
"apply" => apply(subcommand_matches).map_err(|e| anyhow!("Apply error: {}", e)), "apply" => apply(location_context, subcommand_matches).map_err(|e| anyhow!("Apply error: {}", e)),
"patch" => patch(subcommand_matches).map_err(|e| anyhow!("Patch error: {}", e)), "patch" => patch(location_context, subcommand_matches).map_err(|e| anyhow!("Patch error: {}", e)),
_ => bail!("Unknown subcommand '{subcommand}'. Try `--help` for more information."), _ => bail!("Unknown subcommand '{subcommand}'. Try `--help` for more information."),
}; };
@@ -169,7 +178,7 @@ fn main() -> Result<()> {
Ok(()) Ok(())
} }
fn patch(matches: &ArgMatches) -> Result<()> { fn patch(_context: LocationContext, matches: &ArgMatches) -> Result<()> {
let old_file = matches.get_one::<PathBuf>("old"); let old_file = matches.get_one::<PathBuf>("old");
let patch_file = matches.get_one::<PathBuf>("patch"); let patch_file = matches.get_one::<PathBuf>("patch");
let output_file = matches.get_one::<PathBuf>("output"); 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) (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); let (wait, restart, package, exe_args) = get_apply_args(matches);
info!("Command: Apply"); info!("Command: Apply");
info!(" Restart: {:?}", restart); info!(" Restart: {:?}", restart);
@@ -207,7 +216,7 @@ fn apply(matches: &ArgMatches) -> Result<()> {
info!(" Package: {:?}", package); info!(" Package: {:?}", package);
info!(" Exe Args: {:?}", exe_args); 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 _mutex = locator.try_get_exclusive_lock()?;
let _ = commands::apply(&locator, restart, wait, package, exe_args, true)?; let _ = commands::apply(&locator, restart, wait, package, exe_args, true)?;
Ok(()) Ok(())
@@ -221,7 +230,7 @@ fn get_start_args(matches: &ArgMatches) -> (OperationWait, Option<&String>, Opti
(wait, exe_name, legacy_args, exe_args) (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); let (wait, exe_name, legacy_args, exe_args) = get_start_args(matches);
info!("Command: Start"); 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."); 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")] #[cfg(target_os = "windows")]
fn uninstall(_matches: &ArgMatches) -> Result<()> { fn uninstall(context: LocationContext, _matches: &ArgMatches) -> Result<()> {
info!("Command: Uninstall"); info!("Command: Uninstall");
let locator = auto_locate_app_manifest(LocationContext::IAmUpdateExe)?; let locator = auto_locate_app_manifest(context)?;
commands::uninstall(&locator, true) commands::uninstall(&locator, true)
} }
@@ -256,6 +265,15 @@ fn test_cli_parse_handles_equals_spaces() {
assert_eq!(exe_args, None); 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")] #[cfg(target_os = "windows")]
#[test] #[test]
fn test_start_command_supports_legacy_commands() { fn test_start_command_supports_legacy_commands() {

View File

@@ -16,7 +16,7 @@ pub fn write_uninstall_entry(locator: &VelopackLocator) -> Result<()> {
let main_exe_path = locator.get_main_exe_path_as_string(); let main_exe_path = locator.get_main_exe_path_as_string();
let updater_path = locator.get_update_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 short_version = locator.get_manifest_version_short_string();
let now = DateTime::now(); let now = DateTime::now();

View File

@@ -19,7 +19,7 @@ pub fn run_hook(locator: &VelopackLocator, hook_name: &str, timeout_secs: u64) -
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_as_string(); let main_exe_path = locator.get_main_exe_path();
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;
@@ -40,7 +40,7 @@ pub fn run_hook(locator: &VelopackLocator, hook_name: &str, timeout_secs: u64) -
let cmd = cmd.unwrap(); 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) => { Ok(WaitResult::NoWaitRequired) => {
warn!("Was unable to wait for hook (it may have exited too quickly)."); 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) => { 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); error!("Process timed out after {}s and was killed.", timeout_secs);
} }
Err(e) => { Err(e) => {

View File

@@ -1,22 +1,11 @@
use std::path::PathBuf;
use semver::Version;
use uuid::Uuid;
use crate::{ use crate::{
bundle::{self, Manifest}, bundle::{self, Manifest},
util, Error, lockfile::LockFile,
lockfile::LockFile misc, Error,
}; };
use semver::Version;
/// Returns the default channel name for the current OS. use std::path::PathBuf;
pub fn default_channel_name() -> String { use uuid::Uuid;
#[cfg(target_os = "windows")]
return "win".to_owned();
#[cfg(target_os = "linux")]
return "linux".to_owned();
#[cfg(target_os = "macos")]
return "osx".to_owned();
}
bitflags::bitflags! { bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[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 { impl VelopackLocator {
/// Creates a new VelopackLocator from the given paths, trying to auto-detect the manifest. /// 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() { if !config.UpdateExePath.exists() {
return Err(Error::MissingUpdateExe); return Err(Error::MissingUpdateExe);
} }
@@ -122,10 +117,32 @@ impl VelopackLocator {
} }
let manifest = read_current_manifest(&config.ManifestPath)?; 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. /// 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 { pub fn new_with_manifest(paths: VelopackLocatorConfig, manifest: Manifest) -> Self {
Self { paths, manifest } 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. /// 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 { 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. /// 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. /// 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 pub fn clone_self_with_new_manifest(&self, manifest: &Manifest) -> VelopackLocator {
{ VelopackLocator { paths: self.paths.clone(), manifest: manifest.clone() }
VelopackLocator {
paths: self.paths.clone(),
manifest: manifest.clone(),
}
} }
/// Returns whether the app is portable or installed. /// Returns whether the app is portable or installed.
pub fn get_is_portable(&self) -> bool { pub fn get_is_portable(&self) -> bool {
self.paths.IsPortable self.paths.IsPortable
} }
/// Attemps to open / lock a file in the app's package directory for exclusive write access. /// Attemps to open / lock a file in the app's package directory for exclusive write access.
/// Fails immediately if the lock cannot be acquired. /// Fails immediately if the lock cannot be acquired.
pub fn try_get_exclusive_lock(&self) -> Result<LockFile, Error> { 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 /// 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. /// Generally, this should not be used except for installing the app for the first time.
#[cfg(target_os = "windows")] #[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(); let root_dir = root_dir.as_ref();
VelopackLocatorConfig { VelopackLocatorConfig {
RootAppDir: root_dir.to_path_buf(), 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. /// 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. /// Should not really be used, will try a few other enumerations to locate the app manifest.
Unknown, Unknown,
/// Locates the app manifest by assuming the current process is Update.exe. /// Locates the app manifest by assuming the current process is Update.exe.
@@ -352,8 +364,8 @@ pub enum LocationContext
FromSpecifiedAppExecutable(PathBuf), 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. /// 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> { pub fn auto_locate_app_manifest(context: LocationContext) -> Result<VelopackLocator, Error> {
info!("Auto-locating app manifest..."); info!("Auto-locating app manifest...");
match context { 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())) 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") { let appimage_path = match std::env::var("APPIMAGE") {
Ok(v) => { Ok(v) => {
if v.is_empty() || !PathBuf::from(&v).exists() { 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 { } else {
v v
} }
}, }
Err(_) => { Err(_) => {
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()));
} }
}; };
let app = read_current_manifest(&metadata_path)?; let app = read_current_manifest(&metadata_path)?;
let packages_dir = PathBuf::from("/var/tmp/velopack").join(&app.id).join("packages"); 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, CurrentBinaryDir: contents_dir,
IsPortable: true, IsPortable: true,
}; };
Ok(VelopackLocator::new_with_manifest(config, app)) Ok(VelopackLocator::new_with_manifest(config, app))
} }
fn read_current_manifest(nuspec_path: &PathBuf) -> Result<Manifest, Error> { fn read_current_manifest(nuspec_path: &PathBuf) -> Result<Manifest, Error> {
if nuspec_path.exists() { 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); 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 //Create new locator with paths to a test directory
let tmp_dir = tempfile::TempDir::new().unwrap(); let tmp_dir = tempfile::TempDir::new().unwrap();
let tmp_buf = tmp_dir.path().to_path_buf(); 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(); let mut paths = VelopackLocatorConfig::default();
paths.PackagesDir = test_dir; paths.PackagesDir = test_dir;
@@ -580,7 +594,7 @@ fn test_locator_staged_id_for_new_user() {
fn test_locator_staged_id_for_existing_user() { fn test_locator_staged_id_for_existing_user() {
let tmp_dir = tempfile::TempDir::new().unwrap(); let tmp_dir = tempfile::TempDir::new().unwrap();
let tmp_buf = tmp_dir.path().to_path_buf(); 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(); let mut paths = VelopackLocatorConfig::default();
paths.PackagesDir = test_dir; paths.PackagesDir = test_dir;

View File

@@ -506,12 +506,15 @@ impl UpdateManager {
if silent { if silent {
args.push("--silent".to_string()); args.push("--silent".to_string());
} }
if !restart { if !restart {
args.push("--norestart".to_string()); 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() { if !restart_args.is_empty() {
args.push("--".to_string()); args.push("--".to_string());
for arg in restart_args { for arg in restart_args {

View File

@@ -14,8 +14,9 @@ use windows::{
Security::{GetTokenInformation, TokenElevation, TOKEN_ELEVATION}, Security::{GetTokenInformation, TokenElevation, TOKEN_ELEVATION},
System::Threading::{ System::Threading::{
CreateProcessW, GetCurrentProcess, GetExitCodeProcess, GetProcessId, GetProcessTimes, OpenProcess, OpenProcessToken, CreateProcessW, GetCurrentProcess, GetExitCodeProcess, GetProcessId, GetProcessTimes, OpenProcess, OpenProcessToken,
TerminateProcess, WaitForSingleObject, CREATE_NO_WINDOW, PROCESS_ACCESS_RIGHTS, PROCESS_BASIC_INFORMATION, TerminateProcess, WaitForSingleObject, CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT, PROCESS_ACCESS_RIGHTS,
PROCESS_CREATION_FLAGS, PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_SYNCHRONIZE, PROCESS_TERMINATE, PROCESS_BASIC_INFORMATION, PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_SYNCHRONIZE, PROCESS_TERMINATE, STARTUPINFOW,
STARTUPINFOW_FLAGS,
}, },
UI::{ UI::{
Shell::{ShellExecuteExW, SEE_MASK_NOCLOSEPROCESS, SHELLEXECUTEINFOW}, 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<()> { fn append_arg(cmd: &mut Vec<u16>, arg: &Arg, force_quotes: bool) -> IoResult<()> {
let (arg, quote) = match arg { 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), Arg::Raw(arg) => (arg, Quote::Never),
}; };
@@ -206,26 +214,23 @@ impl SafeProcessHandle {
} }
} }
impl Into<u32> for SafeProcessHandle { impl AsRef<HANDLE> for SafeProcessHandle {
fn into(self) -> u32 { fn as_ref(&self) -> &HANDLE {
self.pid() &self.handle
} }
} }
impl Into<HANDLE> for SafeProcessHandle { // impl Into<u32> for SafeProcessHandle {
fn into(self) -> HANDLE { // fn into(self) -> u32 {
self.handle() // self.pid()
} // }
} // }
fn open_pid_safe( // impl Into<HANDLE> for SafeProcessHandle {
dwdesiredaccess: PROCESS_ACCESS_RIGHTS, // fn into(self) -> HANDLE {
binherithandle: bool, // self.handle()
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>)> { fn os_to_pcwstr<P: AsRef<OsStr>>(d: P) -> IoResult<(PCWSTR, Vec<u16>)> {
let d = d.as_ref(); 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 params = PCWSTR(params.as_ptr());
let work_dir = pathopt_to_pcwstr(work_dir.as_ref())?; let work_dir = pathopt_to_pcwstr(work_dir.as_ref())?;
let n_show = let n_show = if show_window {
if show_window { windows::Win32::UI::WindowsAndMessaging::SW_NORMAL.0 } else { windows::Win32::UI::WindowsAndMessaging::SW_HIDE.0 }; windows::Win32::UI::WindowsAndMessaging::SW_NORMAL.0
} else {
windows::Win32::UI::WindowsAndMessaging::SW_HIDE.0
};
let mut exe_info: SHELLEXECUTEINFOW = SHELLEXECUTEINFOW { let mut exe_info: SHELLEXECUTEINFOW = SHELLEXECUTEINFOW {
cbSize: std::mem::size_of::<SHELLEXECUTEINFOW>() as u32, cbSize: std::mem::size_of::<SHELLEXECUTEINFOW>() as u32,
@@ -290,7 +298,9 @@ pub fn run_process<P1: AsRef<Path>, P2: AsRef<Path>>(
) -> IoResult<SafeProcessHandle> { ) -> IoResult<SafeProcessHandle> {
let exe_path = exe_path.as_ref(); let exe_path = exe_path.as_ref();
let exe_path = OsString::from(exe_path); 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 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 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 mut pi = windows::Win32::System::Threading::PROCESS_INFORMATION::default();
// let si = STARTUPINFOW { let si = STARTUPINFOW {
// cb: std::mem::size_of::<STARTUPINFOW>() as u32, cb: std::mem::size_of::<STARTUPINFOW>() as u32,
// lpReserved: PWSTR::null(), lpReserved: PWSTR::null(),
// lpDesktop: PWSTR::null(), lpDesktop: PWSTR::null(),
// lpTitle: PWSTR::null(), lpTitle: PWSTR::null(),
// dwX: 0, dwX: 0,
// dwY: 0, dwY: 0,
// dwXSize: 0, dwXSize: 0,
// dwYSize: 0, dwYSize: 0,
// dwXCountChars: 0, dwXCountChars: 0,
// dwYCountChars: 0, dwYCountChars: 0,
// dwFillAttribute: 0, dwFillAttribute: 0,
// dwFlags: STARTUPINFOW_FLAGS(0), dwFlags: STARTUPINFOW_FLAGS(0),
// wShowWindow: 0, wShowWindow: 0,
// cbReserved2: 0, cbReserved2: 0,
// lpReserved2: std::ptr::null_mut(), lpReserved2: std::ptr::null_mut(),
// hStdInput: HANDLE(std::ptr::null_mut()), hStdInput: HANDLE(std::ptr::null_mut()),
// hStdOutput: HANDLE(std::ptr::null_mut()), hStdOutput: HANDLE(std::ptr::null_mut()),
// hStdError: HANDLE(std::ptr::null_mut()), hStdError: HANDLE(std::ptr::null_mut()),
// }; };
let envp = make_envp(set_env)?; 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 { unsafe {
info!("About to launch: '{:?}' in dir '{:?}' with arguments: {:?}", exe_name, dirp, args); info!("About to launch: '{:?}' in dir '{:?}' with arguments: {:?}", exe_path, work_dir, args);
CreateProcessW(exe_name, Option::Some(params), None, None, false, flags, envp.0, dirp.0, std::ptr::null(), &mut pi)?; CreateProcessW(exe_name_ptr.0, Option::Some(params), None, None, false, flags, envp.0, dirp.0, &si, &mut pi)?;
let _ = AllowSetForegroundWindow(pi.dwProcessId); let _ = AllowSetForegroundWindow(pi.dwProcessId);
let _ = CloseHandle(pi.hThread); 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<()> { pub fn kill_process<T: AsRef<HANDLE>>(process: T) -> IoResult<()> {
let process = process.into(); let process = process.as_ref();
unsafe { unsafe {
if process.is_invalid() { if process.is_invalid() {
return Ok(()); return Ok(());
} }
TerminateProcess(process, 1)?; TerminateProcess(*process, 1)?;
} }
Ok(()) 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<()> { 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)?; kill_process(handle)?;
Ok(()) Ok(())
} }
#[derive(Debug)]
pub enum WaitResult { pub enum WaitResult {
WaitTimeout, WaitTimeout,
ExitCode(u32), ExitCode(u32),
NoWaitRequired, NoWaitRequired,
} }
pub fn wait_for_process_to_exit_with_timeout<T: Into<HANDLE>>(process: T, dur: Duration) -> IoResult<WaitResult> { pub fn wait_for_process_to_exit_with_timeout<T: AsRef<HANDLE>>(process: T, dur: Duration) -> IoResult<WaitResult> {
let process = process.into(); let process = *process.as_ref();
if process.is_invalid() { if process.is_invalid() {
return Ok(WaitResult::NoWaitRequired); 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> { pub fn wait_for_pid_to_exit(pid: u32, dur: Duration) -> IoResult<WaitResult> {
info!("Waiting for process pid-{} to exit.", pid); 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) 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 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 parent_start_time = get_pid_start_time(parent_handle.handle())?;
let myself_start_time = get_pid_start_time(my_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); info!("Waiting for parent process ({}) to exit.", info.InheritedFromUniqueProcessId);
wait_for_process_to_exit_with_timeout(parent_handle, dur) 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");
}