From 9573a511b5174c1daa2fd7f7e81c1d4be3871756 Mon Sep 17 00:00:00 2001 From: Caelan Sayler Date: Fri, 2 May 2025 19:00:17 +0100 Subject: [PATCH] Fix admin launching and locator --- Cargo.lock | 1 + Cargo.toml | 2 +- rustfmt.toml | 3 +- src/bins/src/commands/apply_windows_impl.rs | 29 +++- src/bins/src/commands/start.rs | 7 +- src/bins/src/commands/start_windows_impl.rs | 7 +- src/bins/src/shared/util_windows.rs | 26 ++-- src/bins/src/update.rs | 42 ++++-- src/bins/src/windows/registry.rs | 2 +- src/bins/src/windows/util.rs | 6 +- src/lib-rust/src/locator.rs | 96 +++++++------ src/lib-rust/src/manager.rs | 5 +- src/lib-rust/src/process_win.rs | 142 ++++++++++++-------- 13 files changed, 229 insertions(+), 139 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 35b4d73f..994f6921 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2230,6 +2230,7 @@ dependencies = [ "ureq", "url", "uuid", + "waitpid-any", "windows", "xml", "zip", diff --git a/Cargo.toml b/Cargo.toml index 6af625bb..1f31f610 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/rustfmt.toml b/rustfmt.toml index 3d5f7ad6..f8c0fac8 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -2,4 +2,5 @@ max_width = 140 use_small_heuristics = "Max" indent_style = "Visual" unstable_features = true -format_strings = true \ No newline at end of file +format_strings = true +single_line_if_else_max_width = 40 \ No newline at end of file diff --git a/src/bins/src/commands/apply_windows_impl.rs b/src/bins/src/commands/apply_windows_impl.rs index 637ebf1e..57f711f8 100644 --- a/src/bins/src/commands/apply_windows_impl.rs +++ b/src/bins/src/commands/apply_windows_impl.rs @@ -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, P2: AsRef>(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 { + 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 = 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(); diff --git a/src/bins/src/commands/start.rs b/src/bins/src/commands/start.rs index 1b04cbd5..f55e0b68 100644 --- a/src/bins/src/commands/start.rs +++ b/src/bins/src/commands/start.rs @@ -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>, 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)?; } diff --git a/src/bins/src/commands/start_windows_impl.rs b/src/bins/src/commands/start_windows_impl.rs index aad60dad..0faf45c7 100644 --- a/src/bins/src/commands/start_windows_impl.rs +++ b/src/bins/src/commands/start_windows_impl.rs @@ -61,8 +61,8 @@ impl LocatorResult { } } -fn legacy_locator() -> Result { - let locator = locator::auto_locate_app_manifest(LocationContext::IAmUpdateExe); +fn legacy_locator(context: LocationContext) -> Result { + 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 { } pub fn start_impl( + context: LocationContext, exe_name: Option<&String>, exe_args: Option>, 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) { diff --git a/src/bins/src/shared/util_windows.rs b/src/bins/src/shared/util_windows.rs index d5cd27e8..f6e78d7f 100644 --- a/src/bins/src/shared/util_windows.rs +++ b/src/bins/src/shared/util_windows.rs @@ -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> { Ok(pids.iter().map(|x| *x as u32).collect()) } -unsafe fn get_processes_running_in_directory>(dir: P) -> Result> { +unsafe fn get_processes_running_in_directory>(dir: P) -> Result> { 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>(dir: P) -> Result>(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; } diff --git a/src/bins/src/update.rs b/src/bins/src/update.rs index 414131e8..aac8bc67 100644 --- a/src/bins/src/update.rs +++ b/src/bins/src/update.rs @@ -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 "Override the default locator root directory").global(true).hide(true).value_parser(value_parser!(PathBuf))) .arg(arg!(-l --log "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::("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::("old"); let patch_file = matches.get_one::("patch"); let output_file = matches.get_one::("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::("root"); + assert_eq!(root, Some(&PathBuf::from("C:\\Some Path"))); +} + #[cfg(target_os = "windows")] #[test] fn test_start_command_supports_legacy_commands() { diff --git a/src/bins/src/windows/registry.rs b/src/bins/src/windows/registry.rs index 90fe8036..5bc504ec 100644 --- a/src/bins/src/windows/registry.rs +++ b/src/bins/src/windows/registry.rs @@ -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(); diff --git a/src/bins/src/windows/util.rs b/src/bins/src/windows/util.rs index e1fb24e8..87a8aaa1 100644 --- a/src/bins/src/windows/util.rs +++ b/src/bins/src/windows/util.rs @@ -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) => { diff --git a/src/lib-rust/src/locator.rs b/src/lib-rust/src/locator.rs index 58a9a8d5..965ba925 100644 --- a/src/lib-rust/src/locator.rs +++ b/src/lib-rust/src/locator.rs @@ -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 for VelopackLocator { } } +impl TryFrom<&LocationContext> for VelopackLocator { + type Error = Error; + fn try_from(context: &LocationContext) -> Result { + 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 - { + pub fn new(config: &VelopackLocatorConfig) -> Result { 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 { @@ -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>(root_dir: P) -> VelopackLocatorConfig -{ +pub fn create_config_from_root_dir>(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>(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 { info!("Auto-locating app manifest..."); match context { @@ -409,7 +421,7 @@ pub fn auto_locate_app_manifest(context: LocationContext) -> Result Result { 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 Result { 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; diff --git a/src/lib-rust/src/manager.rs b/src/lib-rust/src/manager.rs index 8622c338..8644f2f5 100644 --- a/src/lib-rust/src/manager.rs +++ b/src/lib-rust/src/manager.rs @@ -506,12 +506,15 @@ impl UpdateManager { if silent { args.push("--silent".to_string()); } + if !restart { args.push("--norestart".to_string()); } - let restart_args: Vec = 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 = 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 { diff --git a/src/lib-rust/src/process_win.rs b/src/lib-rust/src/process_win.rs index 98c73f83..fca094d0 100644 --- a/src/lib-rust/src/process_win.rs +++ b/src/lib-rust/src/process_win.rs @@ -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>(str: T) -> IoResult { fn append_arg(cmd: &mut Vec, 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 for SafeProcessHandle { - fn into(self) -> u32 { - self.pid() +impl AsRef for SafeProcessHandle { + fn as_ref(&self) -> &HANDLE { + &self.handle } } -impl Into for SafeProcessHandle { - fn into(self) -> HANDLE { - self.handle() - } -} +// impl Into for SafeProcessHandle { +// fn into(self) -> u32 { +// self.pid() +// } +// } -fn open_pid_safe( - dwdesiredaccess: PROCESS_ACCESS_RIGHTS, - binherithandle: bool, - dwprocessid: u32, -) -> windows::core::Result { - let handle = unsafe { OpenProcess(dwdesiredaccess, binherithandle, dwprocessid)? }; - return Ok(SafeProcessHandle { handle, pid: dwprocessid }); -} +// impl Into for SafeProcessHandle { +// fn into(self) -> HANDLE { +// self.handle() +// } +// } fn os_to_pcwstr>(d: P) -> IoResult<(PCWSTR, Vec)> { let d = d.as_ref(); @@ -258,8 +263,11 @@ pub fn run_process_as_admin, P2: AsRef>( 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::() as u32, @@ -290,7 +298,9 @@ pub fn run_process, P2: AsRef>( ) -> IoResult { 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::>().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 = 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, P2: AsRef>( let mut pi = windows::Win32::System::Threading::PROCESS_INFORMATION::default(); - // let si = STARTUPINFOW { - // cb: std::mem::size_of::() 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::() 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>(process: T) -> IoResult<()> { - let process = process.into(); +pub fn kill_process>(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 { + 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>(process: T, dur: Duration) -> IoResult { - let process = process.into(); +pub fn wait_for_process_to_exit_with_timeout>(process: T, dur: Duration) -> IoResult { + 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>(process: T, dur: D pub fn wait_for_pid_to_exit(pid: u32, dur: Duration) -> IoResult { 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 { } 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 { 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"); +}