use std::path::Path; #[cfg(windows)]mod locations { use std::{ ffi::{OsStr, OsString, c_void}, io::ErrorKind, os::windows::ffi::OsStringExt, path::{Path, PathBuf}, }; use windows::{ Win32::{ System::{ Com::CoTaskMemFree, Threading::{GetCurrentProcess, IsWow64Process}, }, UI::Shell::{ FOLDERID_LocalAppData, FOLDERID_ProgramFiles, FOLDERID_ProgramFilesX86, FOLDERID_UserProgramFiles, KF_FLAG_DEFAULT, KF_FLAG_DONT_VERIFY, KNOWN_FOLDER_FLAG, SHGetKnownFolderPath, }, }, core::{BOOL, GUID, PWSTR, Result as WindowsResult}, }; use winreg::{ RegKey, enums::{HKEY_LOCAL_MACHINE, KEY_QUERY_VALUE}, }; macro_rules! var_os_stub { { $($name:expr => $value:expr),* $(,)? } => { |key| { match key { $( $name => Some(OsString::from($value)), )* _ => None, } } } } macro_rules! locations_from { ($($name:expr => $value:expr),* $(,)?) => { super::super::locations_under_program_files(var_os_stub! { $( $name => $value, )* }) } } macro_rules! pathbuf_vec { [$($path:expr),* $(,)?] => { vec![$( PathBuf::from($path), )*] } } #[test] fn locations_under_program_files_no_vars() { assert_eq!(locations_from!(), Vec::<PathBuf>::new()); } #[test] fn locations_under_program_files_global_only_ordinary_values_current_var_only() { assert_eq!( locations_from!( "ProgramFiles" => r"C:\Program Files", ), if cfg!(target_pointer_width = "64") { pathbuf_vec![ r"C:\Program Files\Git\clangarm64\bin", r"C:\Program Files\Git\mingw64\bin", ] } else { pathbuf_vec![r"C:\Program Files\Git\mingw32\bin"] }, ); } #[test] fn locations_under_program_files_global_only_ordinary_values_all_vars() { assert_eq!( locations_from!( "ProgramFiles" => { if cfg!(target_pointer_width = "64") { r"C:\Program Files" } else { r"C:\Program Files (x86)" } }, "ProgramFiles(x86)" => r"C:\Program Files (x86)", "ProgramW6432" => r"C:\Program Files", ), pathbuf_vec![ r"C:\Program Files\Git\clangarm64\bin", r"C:\Program Files\Git\mingw64\bin", r"C:\Program Files (x86)\Git\mingw32\bin", ], ); } #[test] fn locations_under_program_files_global_only_strange_values_all_vars_distinct() { assert_eq!( locations_from!( "ProgramFiles" => r"X:\cur\rent", "ProgramFiles(x86)" => r"Y:\nar\row", "ProgramW6432" => r"Z:\wi\de", ), if cfg!(target_pointer_width = "64") { pathbuf_vec![ r"Z:\wi\de\Git\clangarm64\bin", r"Z:\wi\de\Git\mingw64\bin", r"Y:\nar\row\Git\mingw32\bin", r"X:\cur\rent\Git\clangarm64\bin", r"X:\cur\rent\Git\mingw64\bin", ] } else { pathbuf_vec![ r"Z:\wi\de\Git\clangarm64\bin", r"Z:\wi\de\Git\mingw64\bin", r"Y:\nar\row\Git\mingw32\bin", r"X:\cur\rent\Git\mingw32\bin", ] }, ); } #[test] fn locations_under_program_files_global_only_strange_values_64bit_var_only() { assert_eq!( locations_from!( "ProgramW6432" => r"Z:\wi\de", ), pathbuf_vec![r"Z:\wi\de\Git\clangarm64\bin", r"Z:\wi\de\Git\mingw64\bin"], ); } #[test] fn locations_under_program_files_global_only_strange_values_all_vars_path_cruft() { assert_eq!( locations_from!( "ProgramFiles" => r"Z:/wi//de/", "ProgramFiles(x86)" => r"Y:/\nar\/row", "ProgramW6432" => r"Z:\wi\.\de", ), if cfg!(target_pointer_width = "64") { pathbuf_vec![ r"Z:\wi\de\Git\clangarm64\bin", r"Z:\wi\de\Git\mingw64\bin", r"Y:\nar\row\Git\mingw32\bin", ] } else { pathbuf_vec![ r"Z:\wi\de\Git\clangarm64\bin", r"Z:\wi\de\Git\mingw64\bin", r"Y:\nar\row\Git\mingw32\bin", r"Z:\wi\de\Git\mingw32\bin", ] }, ); } #[test] fn locations_under_program_files_global_only_strange_values_some_relative() { assert_eq!( locations_from!( "ProgramFiles" => r"foo\bar", "ProgramFiles(x86)" => r"\\host\share\subdir", "ProgramW6432" => r"", ), pathbuf_vec![r"\\host\share\subdir\Git\mingw32\bin"], ); } #[test] fn locations_under_program_files_local_only_ordinary_value() { assert_eq!( locations_from!( "LocalAppData" => r"C:\Users\alice\AppData\Local", ), pathbuf_vec![ r"C:\Users\alice\AppData\Local\Programs\Git\clangarm64\bin", r"C:\Users\alice\AppData\Local\Programs\Git\mingw64\bin", r"C:\Users\alice\AppData\Local\Programs\Git\mingw32\bin", ], ); } #[test] fn locations_under_program_files_local_only_strange_value_path_cruft() { assert_eq!( locations_from!( "LocalAppData" => r"\\.\Q:\Documents and Settings/bob\/weird\.\sub//dir", ), pathbuf_vec![ r"\\.\Q:\Documents and Settings\bob\weird\sub\dir\Programs\Git\clangarm64\bin", r"\\.\Q:\Documents and Settings\bob\weird\sub\dir\Programs\Git\mingw64\bin", r"\\.\Q:\Documents and Settings\bob\weird\sub\dir\Programs\Git\mingw32\bin", ], ); } #[test] fn locations_under_program_files_local_only_strange_value_empty() { assert_eq!( locations_from!( "LocalAppData" => "", ), Vec::<PathBuf>::new(), ); } #[test] fn locations_under_program_files_local_only_strange_value_relative_nonempty() { assert_eq!( locations_from!( "LocalAppData" => r"AppData\Local", ), Vec::<PathBuf>::new(), ); } #[test] fn locations_under_program_files_local_and_global_ordinary_values_limited_vars() { assert_eq!( locations_from!( "LocalAppData" => r"C:\Users\alice\AppData\Local", "ProgramFiles" => r"C:\Program Files", ), if cfg!(target_pointer_width = "64") { pathbuf_vec![ r"C:\Users\alice\AppData\Local\Programs\Git\clangarm64\bin", r"C:\Users\alice\AppData\Local\Programs\Git\mingw64\bin", r"C:\Users\alice\AppData\Local\Programs\Git\mingw32\bin", r"C:\Program Files\Git\clangarm64\bin", r"C:\Program Files\Git\mingw64\bin", ] } else { pathbuf_vec![ r"C:\Users\alice\AppData\Local\Programs\Git\clangarm64\bin", r"C:\Users\alice\AppData\Local\Programs\Git\mingw64\bin", r"C:\Users\alice\AppData\Local\Programs\Git\mingw32\bin", r"C:\Program Files\Git\mingw32\bin", ] }, ); } #[test] fn locations_under_program_files_local_and_global_ordinary_values_all_vars() { assert_eq!( locations_from!( "LocalAppData" => r"C:\Users\bob\AppData\Local", "ProgramFiles" => { if cfg!(target_pointer_width = "64") { r"C:\Program Files" } else { r"C:\Program Files (x86)" } }, "ProgramFiles(x86)" => r"C:\Program Files (x86)", "ProgramW6432" => r"C:\Program Files", ), pathbuf_vec![ r"C:\Users\bob\AppData\Local\Programs\Git\clangarm64\bin", r"C:\Users\bob\AppData\Local\Programs\Git\mingw64\bin", r"C:\Users\bob\AppData\Local\Programs\Git\mingw32\bin", r"C:\Program Files\Git\clangarm64\bin", r"C:\Program Files\Git\mingw64\bin", r"C:\Program Files (x86)\Git\mingw32\bin", ], ); } #[test] fn locations_under_program_files_local_and_global_strange_values_all_vars_distinct() { assert_eq!( locations_from!( "LocalAppData" => r"W:\us\er", "ProgramFiles" => r"X:\cur\rent", "ProgramFiles(x86)" => r"Y:\nar\row", "ProgramW6432" => r"Z:\wi\de", ), if cfg!(target_pointer_width = "64") { pathbuf_vec![ r"W:\us\er\Programs\Git\clangarm64\bin", r"W:\us\er\Programs\Git\mingw64\bin", r"W:\us\er\Programs\Git\mingw32\bin", r"Z:\wi\de\Git\clangarm64\bin", r"Z:\wi\de\Git\mingw64\bin", r"Y:\nar\row\Git\mingw32\bin", r"X:\cur\rent\Git\clangarm64\bin", r"X:\cur\rent\Git\mingw64\bin", ] } else { pathbuf_vec![ r"W:\us\er\Programs\Git\clangarm64\bin", r"W:\us\er\Programs\Git\mingw64\bin", r"W:\us\er\Programs\Git\mingw32\bin", r"Z:\wi\de\Git\clangarm64\bin", r"Z:\wi\de\Git\mingw64\bin", r"Y:\nar\row\Git\mingw32\bin", r"X:\cur\rent\Git\mingw32\bin", ] }, ); } #[test] fn locations_under_program_files_local_and_global_strange_values_limited_64bit_var() { assert_eq!( locations_from!( "LocalAppData" => r"W:\us\er", "ProgramW6432" => r"Z:\wi\de", ), pathbuf_vec![ r"W:\us\er\Programs\Git\clangarm64\bin", r"W:\us\er\Programs\Git\mingw64\bin", r"W:\us\er\Programs\Git\mingw32\bin", r"Z:\wi\de\Git\clangarm64\bin", r"Z:\wi\de\Git\mingw64\bin", ], ); } #[test] fn locations_under_program_files_local_and_global_strange_values_crufty_cross_clash() { assert_eq!( locations_from!( "LocalAppData" => r"Y:\nar\row", "ProgramFiles" => r"Z:/wi//de/", "ProgramFiles(x86)" => r"Y:/\nar/row\Programs", "ProgramW6432" => r"Z:\wi\.\de", ), if cfg!(target_pointer_width = "64") { pathbuf_vec![ r"Y:\nar\row\Programs\Git\clangarm64\bin", r"Y:\nar\row\Programs\Git\mingw64\bin", r"Y:\nar\row\Programs\Git\mingw32\bin", r"Z:\wi\de\Git\clangarm64\bin", r"Z:\wi\de\Git\mingw64\bin", ] } else { pathbuf_vec![ r"Y:\nar\row\Programs\Git\clangarm64\bin", r"Y:\nar\row\Programs\Git\mingw64\bin", r"Y:\nar\row\Programs\Git\mingw32\bin", r"Z:\wi\de\Git\clangarm64\bin", r"Z:\wi\de\Git\mingw64\bin", r"Z:\wi\de\Git\mingw32\bin", ] }, ); } #[test] fn locations_under_program_files_local_and_global_strange_values_some_relative() { assert_eq!( locations_from!( "LocalAppData" => "dir", "ProgramFiles" => r"foo\bar", "ProgramFiles(x86)" => r"\\host\share\subdir", "ProgramW6432" => r"", ), pathbuf_vec![r"\\host\share\subdir\Git\mingw32\bin"], ); } /// Owner of a null-terminated `PWSTR` that must be freed with `CoTaskMemFree`. struct CoStr { pwstr: PWSTR, } impl CoStr { /// SAFETY: The caller must ensure `pwstr` is a non-null pointer to the beginning of a /// null-terminated (zero-codepoint-terminated) wide string releasable by `CoTaskMemFree`. unsafe fn new(pwstr: PWSTR) -> Self { Self { pwstr } } fn to_os_string(&self) -> OsString { // SAFETY: We know `pwstr` is derefrenceable and the string is null-terminated. let wide = unsafe { self.pwstr.as_wide() }; OsString::from_wide(wide) } } impl Drop for CoStr { fn drop(&mut self) { // SAFETY: `pwstr` is allowed to be passed to `CoTaskMemFree`. (We happen to know it's // non-null, but `CoTaskMemFree` permits null as well, so the cast is doubly safe.) unsafe { CoTaskMemFree(Some(self.pwstr.as_ptr().cast::<c_void>())) }; } } fn get_known_folder_path_with_flag(id: GUID, flag: KNOWN_FOLDER_FLAG) -> WindowsResult<PathBuf> { // SAFETY: `SHGetKnownFolderPath` in the `windows` crate wraps the API function and returns // a non-null pointer to a null-terminated wide string, or an error, not a null pointer. // As in the wrapped API function, the pointer it returns can be passed to `CoTaskMemFree`. let costr = unsafe { CoStr::new(SHGetKnownFolderPath(&id, flag, None)?) }; Ok(PathBuf::from(costr.to_os_string())) } #[derive(Clone, Copy, Debug)] enum PlatformBitness { Is32on32, Is32on64, Is64on64, } impl PlatformBitness { fn current() -> WindowsResult<Self> { // Ordinarily, we would check the target pointer width first to avoid doing extra work, // because if this is a 64-bit executable then the operating system is 64-bit. But this // is for the test suite, and doing it this way allows problems to be caught earlier if // a change made on a 64-bit development machine breaks the IsWow64Process() call. let mut wow64process = BOOL::default(); unsafe { // SAFETY: `GetCurrentProcess` always succeeds, and the handle it returns is a // valid process handle to pass to `IsWow64Process`. IsWow64Process(GetCurrentProcess(), &mut wow64process)?; } let platform_bitness = if wow64process.as_bool() { Self::Is32on64 } else if cfg!(target_pointer_width = "32") { Self::Is32on32 } else { assert!(cfg!(target_pointer_width = "64")); Self::Is64on64 }; Ok(platform_bitness) } } fn ends_with_case_insensitive(full_text: &OsStr, literal_pattern: &str) -> Option<bool> { let folded_text = full_text.to_str()?.to_lowercase(); let folded_pattern = literal_pattern.to_lowercase(); Some(folded_text.ends_with(&folded_pattern)) } /// The most common program files paths, as they are available in this environment. /// /// This omits the global 32-bit ARM program files directory, because Git for Windows is never /// installed there. #[derive(Clone, Debug)] struct ProgramFilesPaths { /// The program files directory used for whatever architecture this program was built for. current: PathBuf, /// The 32-bit x86 program files directory regardless of the architecture of the program. /// /// If Rust gains Windows targets like ARMv7 where this is unavailable, this could fail. x86: PathBuf, /// The 64-bit program files directory if there is one. /// /// This is present on x64 (AMD64) and also ARM64 systems. On an ARM64 system, ARM64 and /// AMD64 programs use the same program files directory while 32-bit x86 and 32-bit ARM /// programs use two others. Only a 32-bit system has no 64-bit program files directory. maybe_64bit: Option<PathBuf>, /// The per-user `Programs` subdirectory of the user's local application data directory. user: PathBuf, } impl ProgramFilesPaths { /// Get the four most common kinds of program files paths without environment variables. /// /// The idea here is to obtain this information, which the `alternative_locations()` unit /// test uses to learn the expected alternative locations, without duplicating *any* of the /// approach used for `ALTERNATIVE_LOCATIONS`, so it can be used to test that. The approach /// here is also more reliable than using environment variables, but it is a bit more /// complex, and it requires either additional dependencies or the use of unsafe code. /// /// This gets `pf_user`, `pf_current`, and `pf_x86` by the [known folders][known-folders] /// system. But it gets `maybe_pf_64bit` from the registry, as the corresponding known /// folder is not available to 32-bit processes. See the [`KNOWNFOLDDERID`][knownfolderid] /// documentation. /// /// If in the future the implementation of `ALTERNATIVE_LOCATIONS` uses these techniques, /// then this function can be changed to use environment variables and renamed accordingly. /// /// [known-folders]: https://learn.microsoft.com/en-us/windows/win32/shell/known-folders /// [knownfolderid]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#remarks fn obtain_envlessly() -> Self { let pf_current = get_known_folder_path_with_flag(FOLDERID_ProgramFiles, KF_FLAG_DEFAULT) .expect("The process architecture specific program files folder is always available"); let pf_x86 = get_known_folder_path_with_flag(FOLDERID_ProgramFilesX86, KF_FLAG_DEFAULT) .expect("The x86 program files folder will in practice always be available"); let maybe_pf_64bit = RegKey::predef(HKEY_LOCAL_MACHINE) .open_subkey_with_flags(r"SOFTWARE\Microsoft\Windows\CurrentVersion", KEY_QUERY_VALUE) .expect("The `CurrentVersion` registry key exists and allows reading") .get_value::<OsString, _>("ProgramW6432Dir")Lines 1–500 of 1033. Load more
use std::path::Path; #[cfg(windows)] mod locations { use std::{ ffi::{OsStr, OsString, c_void}, io::ErrorKind, os::windows::ffi::OsStringExt, path::{Path, PathBuf}, }; use windows::{ Win32::{ System::{ Com::CoTaskMemFree, Threading::{GetCurrentProcess, IsWow64Process}, }, UI::Shell::{ FOLDERID_LocalAppData, FOLDERID_ProgramFiles, FOLDERID_ProgramFilesX86, FOLDERID_UserProgramFiles, KF_FLAG_DEFAULT, KF_FLAG_DONT_VERIFY, KNOWN_FOLDER_FLAG, SHGetKnownFolderPath, }, }, core::{BOOL, GUID, PWSTR, Result as WindowsResult}, }; use winreg::{ RegKey, enums::{HKEY_LOCAL_MACHINE, KEY_QUERY_VALUE}, }; macro_rules! var_os_stub { { $($name:expr => $value:expr),* $(,)? } => { |key| { match key { $( $name => Some(OsString::from($value)), )* _ => None, } } } } macro_rules! locations_from { ($($name:expr => $value:expr),* $(,)?) => { super::super::locations_under_program_files(var_os_stub! { $( $name => $value, )* }) } } macro_rules! pathbuf_vec { [$($path:expr),* $(,)?] => { vec![$( PathBuf::from($path), )*] } } #[test] fn locations_under_program_files_no_vars() { assert_eq!(locations_from!(), Vec::<PathBuf>::new()); } #[test] fn locations_under_program_files_global_only_ordinary_values_current_var_only() { assert_eq!( locations_from!( "ProgramFiles" => r"C:\Program Files", ), if cfg!(target_pointer_width = "64") { pathbuf_vec![ r"C:\Program Files\Git\clangarm64\bin", r"C:\Program Files\Git\mingw64\bin", ] } else { pathbuf_vec![r"C:\Program Files\Git\mingw32\bin"] }, ); } #[test] fn locations_under_program_files_global_only_ordinary_values_all_vars() { assert_eq!( locations_from!( "ProgramFiles" => { if cfg!(target_pointer_width = "64") { r"C:\Program Files" } else { r"C:\Program Files (x86)" } }, "ProgramFiles(x86)" => r"C:\Program Files (x86)", "ProgramW6432" => r"C:\Program Files", ), pathbuf_vec![ r"C:\Program Files\Git\clangarm64\bin", r"C:\Program Files\Git\mingw64\bin", r"C:\Program Files (x86)\Git\mingw32\bin", ], ); } #[test] fn locations_under_program_files_global_only_strange_values_all_vars_distinct() { assert_eq!( locations_from!( "ProgramFiles" => r"X:\cur\rent", "ProgramFiles(x86)" => r"Y:\nar\row", "ProgramW6432" => r"Z:\wi\de", ), if cfg!(target_pointer_width = "64") { pathbuf_vec![ r"Z:\wi\de\Git\clangarm64\bin", r"Z:\wi\de\Git\mingw64\bin", r"Y:\nar\row\Git\mingw32\bin", r"X:\cur\rent\Git\clangarm64\bin", r"X:\cur\rent\Git\mingw64\bin", ] } else { pathbuf_vec![ r"Z:\wi\de\Git\clangarm64\bin", r"Z:\wi\de\Git\mingw64\bin", r"Y:\nar\row\Git\mingw32\bin", r"X:\cur\rent\Git\mingw32\bin", ] }, ); } #[test] fn locations_under_program_files_global_only_strange_values_64bit_var_only() { assert_eq!( locations_from!( "ProgramW6432" => r"Z:\wi\de", ), pathbuf_vec![r"Z:\wi\de\Git\clangarm64\bin", r"Z:\wi\de\Git\mingw64\bin"], ); } #[test] fn locations_under_program_files_global_only_strange_values_all_vars_path_cruft() { assert_eq!( locations_from!( "ProgramFiles" => r"Z:/wi//de/", "ProgramFiles(x86)" => r"Y:/\nar\/row", "ProgramW6432" => r"Z:\wi\.\de", ), if cfg!(target_pointer_width = "64") { pathbuf_vec![ r"Z:\wi\de\Git\clangarm64\bin", r"Z:\wi\de\Git\mingw64\bin", r"Y:\nar\row\Git\mingw32\bin", ] } else { pathbuf_vec![ r"Z:\wi\de\Git\clangarm64\bin", r"Z:\wi\de\Git\mingw64\bin", r"Y:\nar\row\Git\mingw32\bin", r"Z:\wi\de\Git\mingw32\bin", ] }, ); } #[test] fn locations_under_program_files_global_only_strange_values_some_relative() { assert_eq!( locations_from!( "ProgramFiles" => r"foo\bar", "ProgramFiles(x86)" => r"\\host\share\subdir", "ProgramW6432" => r"", ), pathbuf_vec![r"\\host\share\subdir\Git\mingw32\bin"], ); } #[test] fn locations_under_program_files_local_only_ordinary_value() { assert_eq!( locations_from!( "LocalAppData" => r"C:\Users\alice\AppData\Local", ), pathbuf_vec![ r"C:\Users\alice\AppData\Local\Programs\Git\clangarm64\bin", r"C:\Users\alice\AppData\Local\Programs\Git\mingw64\bin", r"C:\Users\alice\AppData\Local\Programs\Git\mingw32\bin", ], ); } #[test] fn locations_under_program_files_local_only_strange_value_path_cruft() { assert_eq!( locations_from!( "LocalAppData" => r"\\.\Q:\Documents and Settings/bob\/weird\.\sub//dir", ), pathbuf_vec![ r"\\.\Q:\Documents and Settings\bob\weird\sub\dir\Programs\Git\clangarm64\bin", r"\\.\Q:\Documents and Settings\bob\weird\sub\dir\Programs\Git\mingw64\bin", r"\\.\Q:\Documents and Settings\bob\weird\sub\dir\Programs\Git\mingw32\bin", ], ); } #[test] fn locations_under_program_files_local_only_strange_value_empty() { assert_eq!( locations_from!( "LocalAppData" => "", ), Vec::<PathBuf>::new(), ); } #[test] fn locations_under_program_files_local_only_strange_value_relative_nonempty() { assert_eq!( locations_from!( "LocalAppData" => r"AppData\Local", ), Vec::<PathBuf>::new(), ); } #[test] fn locations_under_program_files_local_and_global_ordinary_values_limited_vars() { assert_eq!( locations_from!( "LocalAppData" => r"C:\Users\alice\AppData\Local", "ProgramFiles" => r"C:\Program Files", ), if cfg!(target_pointer_width = "64") { pathbuf_vec![ r"C:\Users\alice\AppData\Local\Programs\Git\clangarm64\bin", r"C:\Users\alice\AppData\Local\Programs\Git\mingw64\bin", r"C:\Users\alice\AppData\Local\Programs\Git\mingw32\bin", r"C:\Program Files\Git\clangarm64\bin", r"C:\Program Files\Git\mingw64\bin", ] } else { pathbuf_vec![ r"C:\Users\alice\AppData\Local\Programs\Git\clangarm64\bin", r"C:\Users\alice\AppData\Local\Programs\Git\mingw64\bin", r"C:\Users\alice\AppData\Local\Programs\Git\mingw32\bin", r"C:\Program Files\Git\mingw32\bin", ] }, ); } #[test] fn locations_under_program_files_local_and_global_ordinary_values_all_vars() { assert_eq!( locations_from!( "LocalAppData" => r"C:\Users\bob\AppData\Local", "ProgramFiles" => { if cfg!(target_pointer_width = "64") { r"C:\Program Files" } else { r"C:\Program Files (x86)" } }, "ProgramFiles(x86)" => r"C:\Program Files (x86)", "ProgramW6432" => r"C:\Program Files", ), pathbuf_vec![ r"C:\Users\bob\AppData\Local\Programs\Git\clangarm64\bin", r"C:\Users\bob\AppData\Local\Programs\Git\mingw64\bin", r"C:\Users\bob\AppData\Local\Programs\Git\mingw32\bin", r"C:\Program Files\Git\clangarm64\bin", r"C:\Program Files\Git\mingw64\bin", r"C:\Program Files (x86)\Git\mingw32\bin", ], ); } #[test] fn locations_under_program_files_local_and_global_strange_values_all_vars_distinct() { assert_eq!( locations_from!( "LocalAppData" => r"W:\us\er", "ProgramFiles" => r"X:\cur\rent", "ProgramFiles(x86)" => r"Y:\nar\row", "ProgramW6432" => r"Z:\wi\de", ), if cfg!(target_pointer_width = "64") { pathbuf_vec![ r"W:\us\er\Programs\Git\clangarm64\bin", r"W:\us\er\Programs\Git\mingw64\bin", r"W:\us\er\Programs\Git\mingw32\bin", r"Z:\wi\de\Git\clangarm64\bin", r"Z:\wi\de\Git\mingw64\bin", r"Y:\nar\row\Git\mingw32\bin", r"X:\cur\rent\Git\clangarm64\bin", r"X:\cur\rent\Git\mingw64\bin", ] } else { pathbuf_vec![ r"W:\us\er\Programs\Git\clangarm64\bin", r"W:\us\er\Programs\Git\mingw64\bin", r"W:\us\er\Programs\Git\mingw32\bin", r"Z:\wi\de\Git\clangarm64\bin", r"Z:\wi\de\Git\mingw64\bin", r"Y:\nar\row\Git\mingw32\bin", r"X:\cur\rent\Git\mingw32\bin", ] }, ); } #[test] fn locations_under_program_files_local_and_global_strange_values_limited_64bit_var() { assert_eq!( locations_from!( "LocalAppData" => r"W:\us\er", "ProgramW6432" => r"Z:\wi\de", ), pathbuf_vec![ r"W:\us\er\Programs\Git\clangarm64\bin", r"W:\us\er\Programs\Git\mingw64\bin", r"W:\us\er\Programs\Git\mingw32\bin", r"Z:\wi\de\Git\clangarm64\bin", r"Z:\wi\de\Git\mingw64\bin", ], ); } #[test] fn locations_under_program_files_local_and_global_strange_values_crufty_cross_clash() { assert_eq!( locations_from!( "LocalAppData" => r"Y:\nar\row", "ProgramFiles" => r"Z:/wi//de/", "ProgramFiles(x86)" => r"Y:/\nar/row\Programs", "ProgramW6432" => r"Z:\wi\.\de", ), if cfg!(target_pointer_width = "64") { pathbuf_vec![ r"Y:\nar\row\Programs\Git\clangarm64\bin", r"Y:\nar\row\Programs\Git\mingw64\bin", r"Y:\nar\row\Programs\Git\mingw32\bin", r"Z:\wi\de\Git\clangarm64\bin", r"Z:\wi\de\Git\mingw64\bin", ] } else { pathbuf_vec![ r"Y:\nar\row\Programs\Git\clangarm64\bin", r"Y:\nar\row\Programs\Git\mingw64\bin", r"Y:\nar\row\Programs\Git\mingw32\bin", r"Z:\wi\de\Git\clangarm64\bin", r"Z:\wi\de\Git\mingw64\bin", r"Z:\wi\de\Git\mingw32\bin", ] }, ); } #[test] fn locations_under_program_files_local_and_global_strange_values_some_relative() { assert_eq!( locations_from!( "LocalAppData" => "dir", "ProgramFiles" => r"foo\bar", "ProgramFiles(x86)" => r"\\host\share\subdir", "ProgramW6432" => r"", ), pathbuf_vec![r"\\host\share\subdir\Git\mingw32\bin"], ); } /// Owner of a null-terminated `PWSTR` that must be freed with `CoTaskMemFree`. struct CoStr { pwstr: PWSTR, } impl CoStr { /// SAFETY: The caller must ensure `pwstr` is a non-null pointer to the beginning of a /// null-terminated (zero-codepoint-terminated) wide string releasable by `CoTaskMemFree`. unsafe fn new(pwstr: PWSTR) -> Self { Self { pwstr } } fn to_os_string(&self) -> OsString { // SAFETY: We know `pwstr` is derefrenceable and the string is null-terminated. let wide = unsafe { self.pwstr.as_wide() }; OsString::from_wide(wide) } } impl Drop for CoStr { fn drop(&mut self) { // SAFETY: `pwstr` is allowed to be passed to `CoTaskMemFree`. (We happen to know it's // non-null, but `CoTaskMemFree` permits null as well, so the cast is doubly safe.) unsafe { CoTaskMemFree(Some(self.pwstr.as_ptr().cast::<c_void>())) }; } } fn get_known_folder_path_with_flag(id: GUID, flag: KNOWN_FOLDER_FLAG) -> WindowsResult<PathBuf> { // SAFETY: `SHGetKnownFolderPath` in the `windows` crate wraps the API function and returns // a non-null pointer to a null-terminated wide string, or an error, not a null pointer. // As in the wrapped API function, the pointer it returns can be passed to `CoTaskMemFree`. let costr = unsafe { CoStr::new(SHGetKnownFolderPath(&id, flag, None)?) }; Ok(PathBuf::from(costr.to_os_string())) } #[derive(Clone, Copy, Debug)] enum PlatformBitness { Is32on32, Is32on64, Is64on64, } impl PlatformBitness { fn current() -> WindowsResult<Self> { // Ordinarily, we would check the target pointer width first to avoid doing extra work, // because if this is a 64-bit executable then the operating system is 64-bit. But this // is for the test suite, and doing it this way allows problems to be caught earlier if // a change made on a 64-bit development machine breaks the IsWow64Process() call. let mut wow64process = BOOL::default(); unsafe { // SAFETY: `GetCurrentProcess` always succeeds, and the handle it returns is a // valid process handle to pass to `IsWow64Process`. IsWow64Process(GetCurrentProcess(), &mut wow64process)?; } let platform_bitness = if wow64process.as_bool() { Self::Is32on64 } else if cfg!(target_pointer_width = "32") { Self::Is32on32 } else { assert!(cfg!(target_pointer_width = "64")); Self::Is64on64 }; Ok(platform_bitness) } } fn ends_with_case_insensitive(full_text: &OsStr, literal_pattern: &str) -> Option<bool> { let folded_text = full_text.to_str()?.to_lowercase(); let folded_pattern = literal_pattern.to_lowercase(); Some(folded_text.ends_with(&folded_pattern)) } /// The most common program files paths, as they are available in this environment. /// /// This omits the global 32-bit ARM program files directory, because Git for Windows is never /// installed there. #[derive(Clone, Debug)] struct ProgramFilesPaths { /// The program files directory used for whatever architecture this program was built for. current: PathBuf, /// The 32-bit x86 program files directory regardless of the architecture of the program. /// /// If Rust gains Windows targets like ARMv7 where this is unavailable, this could fail. x86: PathBuf, /// The 64-bit program files directory if there is one. /// /// This is present on x64 (AMD64) and also ARM64 systems. On an ARM64 system, ARM64 and /// AMD64 programs use the same program files directory while 32-bit x86 and 32-bit ARM /// programs use two others. Only a 32-bit system has no 64-bit program files directory. maybe_64bit: Option<PathBuf>, /// The per-user `Programs` subdirectory of the user's local application data directory. user: PathBuf, } impl ProgramFilesPaths { /// Get the four most common kinds of program files paths without environment variables. /// /// The idea here is to obtain this information, which the `alternative_locations()` unit /// test uses to learn the expected alternative locations, without duplicating *any* of the /// approach used for `ALTERNATIVE_LOCATIONS`, so it can be used to test that. The approach /// here is also more reliable than using environment variables, but it is a bit more /// complex, and it requires either additional dependencies or the use of unsafe code. /// /// This gets `pf_user`, `pf_current`, and `pf_x86` by the [known folders][known-folders] /// system. But it gets `maybe_pf_64bit` from the registry, as the corresponding known /// folder is not available to 32-bit processes. See the [`KNOWNFOLDDERID`][knownfolderid] /// documentation. /// /// If in the future the implementation of `ALTERNATIVE_LOCATIONS` uses these techniques, /// then this function can be changed to use environment variables and renamed accordingly. /// /// [known-folders]: https://learn.microsoft.com/en-us/windows/win32/shell/known-folders /// [knownfolderid]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#remarks fn obtain_envlessly() -> Self { let pf_current = get_known_folder_path_with_flag(FOLDERID_ProgramFiles, KF_FLAG_DEFAULT) .expect("The process architecture specific program files folder is always available"); let pf_x86 = get_known_folder_path_with_flag(FOLDERID_ProgramFilesX86, KF_FLAG_DEFAULT) .expect("The x86 program files folder will in practice always be available"); let maybe_pf_64bit = RegKey::predef(HKEY_LOCAL_MACHINE) .open_subkey_with_flags(r"SOFTWARE\Microsoft\Windows\CurrentVersion", KEY_QUERY_VALUE) .expect("The `CurrentVersion` registry key exists and allows reading") .get_value::<OsString, _>("ProgramW6432Dir")