1033 lines · 38.7 KB · Rust
RawDownload
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.