|
| 1 | +// most of this code is taken wholesale from this: |
| 2 | +// https://github.com/rust-lang/rustup/blob/master/src/cli/self_update/windows.rs |
1 | 3 | use crate::{Installer, InstallerError};
|
2 | 4 |
|
| 5 | +use std::io; |
| 6 | + |
| 7 | +/// Adds the downloaded binary in Installer to a Windows PATH |
3 | 8 | pub fn add_binary_to_path(installer: &Installer) -> Result<(), InstallerError> {
|
4 |
| - unimplemented!() |
| 9 | + let windows_path = get_windows_path_var()?; |
| 10 | + let bin_path = installer |
| 11 | + .get_bin_dir_path()? |
| 12 | + .to_str() |
| 13 | + .ok_or(InstallerError::PathNotUnicode)? |
| 14 | + .to_string(); |
| 15 | + if let Some(old_path) = windows_path { |
| 16 | + if let Some(new_path) = add_to_path(&old_path, &bin_path) { |
| 17 | + apply_new_path(&new_path)?; |
| 18 | + } |
| 19 | + } |
| 20 | + |
| 21 | + Ok(()) |
| 22 | +} |
| 23 | + |
| 24 | +// --------------------------------------------- |
| 25 | +// https://en.wikipedia.org/wiki/Here_be_dragons |
| 26 | +// --------------------------------------------- |
| 27 | +// nothing is sacred beyond this point. |
| 28 | + |
| 29 | +// Get the windows PATH variable out of the registry as a String. If |
| 30 | +// this returns None then the PATH variable is not unicode and we |
| 31 | +// should not mess with it. |
| 32 | +fn get_windows_path_var() -> Result<Option<String>, InstallerError> { |
| 33 | + use winreg::enums::{HKEY_CURRENT_USER, KEY_READ, KEY_WRITE}; |
| 34 | + use winreg::RegKey; |
| 35 | + |
| 36 | + let root = RegKey::predef(HKEY_CURRENT_USER); |
| 37 | + let environment = root.open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE)?; |
| 38 | + |
| 39 | + let reg_value = environment.get_raw_value("PATH"); |
| 40 | + match reg_value { |
| 41 | + Ok(val) => { |
| 42 | + if let Some(s) = string_from_winreg_value(&val) { |
| 43 | + Ok(Some(s)) |
| 44 | + } else { |
| 45 | + tracing::warn!("the registry key HKEY_CURRENT_USER\\Environment\\PATH does not contain valid Unicode. \ |
| 46 | + Not modifying the PATH variable"); |
| 47 | + Ok(None) |
| 48 | + } |
| 49 | + } |
| 50 | + Err(ref e) if e.kind() == io::ErrorKind::NotFound => Ok(Some(String::new())), |
| 51 | + Err(e) => Err(e)?, |
| 52 | + } |
| 53 | +} |
| 54 | + |
| 55 | +// This is used to decode the value of HKCU\Environment\PATH. If that |
| 56 | +// key is not unicode (or not REG_SZ | REG_EXPAND_SZ) then this |
| 57 | +// returns None. The winreg library itself does a lossy unicode |
| 58 | +// conversion. |
| 59 | +fn string_from_winreg_value(val: &winreg::RegValue) -> Option<String> { |
| 60 | + use std::slice; |
| 61 | + use winreg::enums::RegType; |
| 62 | + |
| 63 | + match val.vtype { |
| 64 | + RegType::REG_SZ | RegType::REG_EXPAND_SZ => { |
| 65 | + // Copied from winreg |
| 66 | + let words = unsafe { |
| 67 | + #[allow(clippy::cast_ptr_alignment)] |
| 68 | + slice::from_raw_parts(val.bytes.as_ptr().cast::<u16>(), val.bytes.len() / 2) |
| 69 | + }; |
| 70 | + String::from_utf16(words).ok().map(|mut s| { |
| 71 | + while s.ends_with('\u{0}') { |
| 72 | + s.pop(); |
| 73 | + } |
| 74 | + s |
| 75 | + }) |
| 76 | + } |
| 77 | + _ => None, |
| 78 | + } |
| 79 | +} |
| 80 | + |
| 81 | +// Returns None if the existing old_path does not need changing, otherwise |
| 82 | +// prepends the path_str to old_path, handling empty old_path appropriately. |
| 83 | +fn add_to_path(old_path: &str, path_str: &str) -> Option<String> { |
| 84 | + if old_path.is_empty() { |
| 85 | + Some(path_str.to_string()) |
| 86 | + } else if old_path.contains(path_str) { |
| 87 | + None |
| 88 | + } else { |
| 89 | + let mut new_path = path_str.to_string(); |
| 90 | + new_path.push_str(";"); |
| 91 | + new_path.push_str(&old_path); |
| 92 | + Some(new_path) |
| 93 | + } |
| 94 | +} |
| 95 | + |
| 96 | +fn apply_new_path(new_path: &str) -> Result<(), InstallerError> { |
| 97 | + use std::ptr; |
| 98 | + use winapi::shared::minwindef::*; |
| 99 | + use winapi::um::winuser::{ |
| 100 | + SendMessageTimeoutA, HWND_BROADCAST, SMTO_ABORTIFHUNG, WM_SETTINGCHANGE, |
| 101 | + }; |
| 102 | + use winreg::enums::{RegType, HKEY_CURRENT_USER, KEY_READ, KEY_WRITE}; |
| 103 | + use winreg::{RegKey, RegValue}; |
| 104 | + |
| 105 | + let root = RegKey::predef(HKEY_CURRENT_USER); |
| 106 | + let environment = root.open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE)?; |
| 107 | + |
| 108 | + if new_path.is_empty() { |
| 109 | + environment.delete_value("PATH")?; |
| 110 | + } else { |
| 111 | + let reg_value = RegValue { |
| 112 | + bytes: string_to_winreg_bytes(new_path), |
| 113 | + vtype: RegType::REG_EXPAND_SZ, |
| 114 | + }; |
| 115 | + environment.set_raw_value("PATH", ®_value)?; |
| 116 | + } |
| 117 | + |
| 118 | + // Tell other processes to update their environment |
| 119 | + unsafe { |
| 120 | + SendMessageTimeoutA( |
| 121 | + HWND_BROADCAST, |
| 122 | + WM_SETTINGCHANGE, |
| 123 | + 0 as WPARAM, |
| 124 | + "Environment\0".as_ptr() as LPARAM, |
| 125 | + SMTO_ABORTIFHUNG, |
| 126 | + 5000, |
| 127 | + ptr::null_mut(), |
| 128 | + ); |
| 129 | + } |
| 130 | + |
| 131 | + Ok(()) |
| 132 | +} |
| 133 | + |
| 134 | +fn string_to_winreg_bytes(s: &str) -> Vec<u8> { |
| 135 | + use std::ffi::OsStr; |
| 136 | + use std::os::windows::ffi::OsStrExt; |
| 137 | + let v: Vec<u16> = OsStr::new(s).encode_wide().chain(Some(0)).collect(); |
| 138 | + unsafe { std::slice::from_raw_parts(v.as_ptr().cast::<u8>(), v.len() * 2).to_vec() } |
| 139 | +} |
| 140 | + |
| 141 | +#[cfg(test)] |
| 142 | +mod tests { |
| 143 | + #[test] |
| 144 | + fn windows_install_does_not_add_path_twice() { |
| 145 | + assert_eq!( |
| 146 | + None, |
| 147 | + super::add_to_path( |
| 148 | + r"c:\users\example\.mybinary\bin;foo", |
| 149 | + r"c:\users\example\.mybinary\bin" |
| 150 | + ) |
| 151 | + ); |
| 152 | + } |
| 153 | + |
| 154 | + #[test] |
| 155 | + fn windows_install_does_add_path() { |
| 156 | + assert_eq!( |
| 157 | + Some(r"c:\users\example\.mybinary\bin;foo".to_string()), |
| 158 | + super::add_to_path("foo", r"c:\users\example\.mybinary\bin") |
| 159 | + ); |
| 160 | + } |
| 161 | + |
| 162 | + #[test] |
| 163 | + fn windows_install_does_add_path_no_double_semicolon() { |
| 164 | + assert_eq!( |
| 165 | + Some(r"c:\users\example\.mybinary\bin;foo;bar;".to_string()), |
| 166 | + super::add_to_path("foo;bar;", r"c:\users\example\.mybinary\bin") |
| 167 | + ); |
| 168 | + } |
5 | 169 | }
|
0 commit comments