diff --git a/src/Rust/Cargo.lock b/src/Rust/Cargo.lock index 1cc09313..bc63b784 100644 --- a/src/Rust/Cargo.lock +++ b/src/Rust/Cargo.lock @@ -994,6 +994,17 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "os_info" +version = "3.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" +dependencies = [ + "log", + "serde", + "windows-sys 0.52.0", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1586,6 +1597,7 @@ dependencies = [ "native-tls", "normpath", "ntest", + "os_info", "pretty-bytes-rust", "pretty_assertions", "rand", diff --git a/src/Rust/Cargo.toml b/src/Rust/Cargo.toml index 99e41402..43ff648b 100644 --- a/src/Rust/Cargo.toml +++ b/src/Rust/Cargo.toml @@ -78,6 +78,7 @@ url = "2.5" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" time = "0.3" +os_info = "3.8" [target.'cfg(unix)'.dependencies] native-dialog = "0.7" @@ -101,6 +102,7 @@ windows = { version = "0.52", default-features = false, features = [ "Win32_UI", "Win32_UI_Shell", "Win32_System_Threading", + "Win32_System_SystemInformation", "Win32_System_Variant", "Win32_Storage_EnhancedStorage", "Win32_Storage_FileSystem", @@ -123,13 +125,6 @@ windows-sys = { version = "0.52", default-features = false, features = [ "Wdk", "Wdk_System", "Wdk_System_Threading", - # below are just for os_info - "Win32_UI_WindowsAndMessaging", - "Win32_System_Diagnostics_Debug", - "Win32_System_LibraryLoader", - "Win32_System_SystemInformation", - "Win32_System_Registry", - "Win32_System_SystemServices", ] } normpath = "1.0.1" webview2-com = "0.28.0" diff --git a/src/Rust/src/commands/install.rs b/src/Rust/src/commands/install.rs index 5dc9798b..cbc5594d 100644 --- a/src/Rust/src/commands/install.rs +++ b/src/Rust/src/commands/install.rs @@ -1,4 +1,8 @@ -use crate::{dialogs, shared, shared::bundle, windows}; +use crate::{ + dialogs, + shared::{self, bundle, runtime_arch::RuntimeArch}, + windows, +}; use anyhow::{anyhow, bail, Result}; use memmap2::Mmap; use pretty_bytes_rust::pretty_bytes; @@ -11,8 +15,9 @@ use std::{ use winsafe::{self as w, co}; pub fn install(debug_pkg: Option<&PathBuf>, install_to: Option<&PathBuf>) -> Result<()> { - let osinfo = windows::os_info::get(); - info!("OS: {}, Arch={}", osinfo, osinfo.architecture().unwrap_or("unknown")); + let osinfo = os_info::get(); + let osarch = RuntimeArch::from_current_system(); + info!("OS: {osinfo}, Arch={osarch:#?}"); if !w::IsWindows7OrGreater()? { bail!("This installer requires Windows 7 or later and cannot run."); @@ -121,7 +126,7 @@ pub fn install(debug_pkg: Option<&PathBuf>, install_to: Option<&PathBuf>) -> Res let splash_bytes = pkg.get_splash_bytes(); windows::splash::show_splash_dialog(app.title.to_owned(), splash_bytes) }; - + let install_result = install_impl(&pkg, &root_path, &tx); let _ = tx.send(windows::splash::MSG_CLOSE); diff --git a/src/Rust/src/shared/mod.rs b/src/Rust/src/shared/mod.rs index c66d02d9..e128d975 100644 --- a/src/Rust/src/shared/mod.rs +++ b/src/Rust/src/shared/mod.rs @@ -1,6 +1,7 @@ pub mod bundle; pub mod download; pub mod macho; +pub mod runtime_arch; mod dialogs_const; mod dialogs_common; diff --git a/src/Rust/src/shared/runtime_arch.rs b/src/Rust/src/shared/runtime_arch.rs new file mode 100644 index 00000000..c82120d8 --- /dev/null +++ b/src/Rust/src/shared/runtime_arch.rs @@ -0,0 +1,102 @@ +#[derive(PartialEq, Debug, Clone, strum::IntoStaticStr)] +pub enum RuntimeArch { + X86, + X64, + Arm64, +} + +impl RuntimeArch { + #[cfg(target_os = "windows")] + fn from_u32(value: u32) -> Option { + match value { + 0x014c => Some(RuntimeArch::X86), + 0x8664 => Some(RuntimeArch::X64), + 0xAA64 => Some(RuntimeArch::Arm64), + _ => None, + } + } + + pub fn from_str(arch_str: &str) -> Option { + match arch_str.to_lowercase().as_str() { + "x86" => Some(RuntimeArch::X86), + "i386" => Some(RuntimeArch::X86), + "x64" => Some(RuntimeArch::X64), + "x86_64" => Some(RuntimeArch::X64), + "arm64" => Some(RuntimeArch::Arm64), + "aarch64" => Some(RuntimeArch::Arm64), + _ => None, + } + } + + #[cfg(target_os = "windows")] + pub fn from_current_system() -> Option { + return check_arch_windows(); + } + + #[cfg(not(target_os = "windows"))] + pub fn from_current_system() -> Option { + use os_info; + let info = os_info::get(); + let machine = info.architecture(); + if machine.is_none() { + return None; + } + let machine = machine.unwrap(); + if machine.is_empty() { + return None; + } + Self::from_str(machine) + } +} + +#[cfg(target_os = "windows")] +fn check_arch_windows() -> Option { + use windows::Win32::Foundation::{FALSE, TRUE}; + use windows::Win32::System::SystemInformation::IMAGE_FILE_MACHINE; + use windows::Win32::System::Threading::{GetCurrentProcess, IsWow64Process, IsWow64Process2}; + + unsafe { + let handle = GetCurrentProcess(); + + let mut process_machine: IMAGE_FILE_MACHINE = Default::default(); + let mut native_machine: IMAGE_FILE_MACHINE = Default::default(); + if let Ok(()) = IsWow64Process2(handle, &mut process_machine, Some(&mut native_machine)) { + return RuntimeArch::from_u32(native_machine.0.into()); + } + + let mut iswow64 = FALSE; + if let Ok(()) = IsWow64Process(handle, &mut iswow64) { + if iswow64 == TRUE { + return Some(RuntimeArch::X64); + } else { + return Some(RuntimeArch::X86); + } + } + + #[cfg(target_arch = "x86_64")] + return Some(RuntimeArch::X64); + + #[cfg(not(target_arch = "x86_64"))] + return Some(RuntimeArch::X86); + } +} + +#[test] +#[cfg(target_os = "windows")] +fn test_current_architecture() { + let arch = check_arch_windows(); + assert!(arch.is_some()); + let arch = arch.unwrap(); + assert!(arch == RuntimeArch::X64); +} + +#[test] +fn test_cpu_arch_from_str() { + assert_eq!(RuntimeArch::from_str("x86"), Some(RuntimeArch::X86)); + assert_eq!(RuntimeArch::from_str("x64"), Some(RuntimeArch::X64)); + assert_eq!(RuntimeArch::from_str("arm64"), Some(RuntimeArch::Arm64)); + assert_eq!(RuntimeArch::from_str("foo"), None); + assert_eq!(RuntimeArch::from_str("X86"), Some(RuntimeArch::X86)); + assert_eq!(RuntimeArch::from_str("X64"), Some(RuntimeArch::X64)); + assert_eq!(RuntimeArch::from_str("ARM64"), Some(RuntimeArch::Arm64)); +} diff --git a/src/Rust/src/windows/mod.rs b/src/Rust/src/windows/mod.rs index a34b313b..8b9b5974 100644 --- a/src/Rust/src/windows/mod.rs +++ b/src/Rust/src/windows/mod.rs @@ -1,6 +1,5 @@ pub mod locksmith; pub mod mitigate; -pub mod os_info; pub mod prerequisite; pub mod runtimes; pub mod splash; diff --git a/src/Rust/src/windows/os_info.rs b/src/Rust/src/windows/os_info.rs deleted file mode 100644 index d0f425d4..00000000 --- a/src/Rust/src/windows/os_info.rs +++ /dev/null @@ -1,820 +0,0 @@ -// spell-checker:ignore dword, minwindef, ntdef, ntdll, ntstatus, osversioninfoex, osversioninfoexa -// spell-checker:ignore osversioninfoexw, serverr, sysinfoapi, winnt, winuser, pbool, libloaderapi -// spell-checker:ignore lpcstr, processthreadsapi, farproc, lstatus, wchar, lpbyte, hkey, winerror -// spell-checker:ignore osstr, winreg - -#![allow(unsafe_code)] - -use std::fmt::{self, Display, Formatter}; -use std::{ - ffi::{OsStr, OsString}, - mem::{self, MaybeUninit}, - os::windows::ffi::{OsStrExt, OsStringExt}, - ptr, -}; - -use windows_sys::Win32::{ - Foundation::{ERROR_SUCCESS, FARPROC, NTSTATUS, STATUS_SUCCESS}, - System::{ - SystemInformation::{ - PROCESSOR_ARCHITECTURE_AMD64, PROCESSOR_ARCHITECTURE_ARM, PROCESSOR_ARCHITECTURE_IA64, - PROCESSOR_ARCHITECTURE_INTEL, - }, - LibraryLoader::{GetModuleHandleA, GetProcAddress}, - Registry::{RegOpenKeyExW, RegQueryValueExW, HKEY_LOCAL_MACHINE, KEY_READ, REG_SZ}, - SystemInformation::{GetNativeSystemInfo, GetSystemInfo, SYSTEM_INFO}, - SystemServices::{VER_NT_WORKSTATION, VER_SUITE_WH_SERVER}, - }, - UI::WindowsAndMessaging::{GetSystemMetrics, SM_SERVERR2}, -}; - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Info { - /// Operating system type. See `Type` for details. - pub(crate) os_type: Type, - /// Operating system version. See `Version` for details. - pub(crate) version: Version, - /// Operating system edition. - pub(crate) edition: Option, - /// Operating system codename. - pub(crate) codename: Option, - /// Operating system architecture in terms of how many bits compose the basic values it can deal - /// with. See `Bitness` for details. - pub(crate) bitness: Bitness, - /// Processor architecture. - pub(crate) architecture: Option, -} - -impl Info { - /// Constructs a new `Info` instance with unknown type, version and bitness. - /// - /// # Examples - /// - /// ``` - /// use os_info::{Info, Type, Version, Bitness}; - /// - /// let info = Info::unknown(); - /// assert_eq!(Type::Unknown, info.os_type()); - /// assert_eq!(&Version::Unknown, info.version()); - /// assert_eq!(None, info.edition()); - /// assert_eq!(None, info.codename()); - /// assert_eq!(Bitness::Unknown, info.bitness()); - /// assert_eq!(None, info.architecture()); - /// ``` - pub fn unknown() -> Self { - Self { - os_type: Type::Unknown, - version: Version::Unknown, - edition: None, - codename: None, - bitness: Bitness::Unknown, - architecture: None, - } - } - - /// Constructs a new `Info` instance with the specified operating system type. - /// - /// # Examples - /// - /// ``` - /// use os_info::{Info, Type, Version, Bitness}; - /// - /// let os_type = Type::Linux; - /// let info = Info::with_type(os_type); - /// assert_eq!(os_type, info.os_type()); - /// assert_eq!(&Version::Unknown, info.version()); - /// assert_eq!(None, info.edition()); - /// assert_eq!(None, info.codename()); - /// assert_eq!(Bitness::Unknown, info.bitness()); - /// assert_eq!(None, info.architecture()); - /// ``` - pub fn with_type(os_type: Type) -> Self { - Self { - os_type, - ..Default::default() - } - } - - /// Returns operating system type. See `Type` for details. - /// - /// # Examples - /// - /// ``` - /// use os_info::{Info, Type}; - /// - /// let info = Info::unknown(); - /// assert_eq!(Type::Unknown, info.os_type()); - /// ``` - pub fn os_type(&self) -> Type { - self.os_type - } - - /// Returns operating system version. See `Version` for details. - /// - /// # Examples - /// - /// ``` - /// use os_info::{Info, Version}; - /// - /// let info = Info::unknown(); - /// assert_eq!(&Version::Unknown, info.version()); - /// ``` - pub fn version(&self) -> &Version { - &self.version - } - - /// Returns optional operation system edition. - /// - /// # Examples - /// - /// ``` - /// use os_info::Info; - /// - /// let info = Info::unknown(); - /// assert_eq!(None, info.edition()); - pub fn edition(&self) -> Option<&str> { - self.edition.as_ref().map(String::as_ref) - } - - /// Returns optional operation system 'codename'. - /// - /// # Examples - /// - /// ``` - /// use os_info::Info; - /// - /// let info = Info::unknown(); - /// assert_eq!(None, info.codename()); - pub fn codename(&self) -> Option<&str> { - self.codename.as_ref().map(String::as_ref) - } - - /// Returns operating system bitness. See `Bitness` for details. - /// - /// # Examples - /// - /// ``` - /// use os_info::{Info, Bitness}; - /// - /// let info = Info::unknown(); - /// assert_eq!(Bitness::Unknown, info.bitness()); - /// ``` - pub fn bitness(&self) -> Bitness { - self.bitness - } - - /// Returns operating system architecture. - /// - /// # Examples - /// - /// ``` - /// use os_info::Info; - /// - /// let info = Info::unknown(); - /// assert_eq!(None, info.architecture()); - pub fn architecture(&self) -> Option<&str> { - self.architecture.as_ref().map(String::as_ref) - } -} - -impl Default for Info { - fn default() -> Self { - Self::unknown() - } -} - -impl Display for Info { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}", self.os_type)?; - if self.version != Version::Unknown { - write!(f, " {}", self.version)?; - } - if let Some(ref edition) = self.edition { - write!(f, " ({edition})")?; - } - if let Some(ref codename) = self.codename { - write!(f, " ({codename})")?; - } - write!(f, " [{}]", self.bitness) - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[allow(non_camel_case_types, clippy::upper_case_acronyms)] -#[non_exhaustive] -pub enum Type { - /// IBM AIX (). - AIX, - /// Alpaquita Linux (). - Alpaquita, - /// Alpine Linux (). - Alpine, - /// Amazon Linux AMI (). - Amazon, - /// Android (). - Android, - /// Arch Linux (). - Arch, - /// Artix Linux (). - Artix, - /// CentOS (). - CentOS, - /// Debian (). - Debian, - /// DragonFly BSD (). - DragonFly, - /// Emscripten (). - Emscripten, - /// EndeavourOS (). - EndeavourOS, - /// Fedora (). - Fedora, - /// FreeBSD (). - FreeBSD, - /// Garuda Linux () - Garuda, - /// Gentoo Linux (). - Gentoo, - /// HardenedBSD (https://hardenedbsd.org/). - HardenedBSD, - /// Illumos (https://en.wikipedia.org/wiki/Illumos). - Illumos, - /// Kali Linux (https://en.wikipedia.org/wiki/Kali_Linux). - Kali, - /// Linux based operating system (). - Linux, - /// Mabox (). - Mabox, - /// Mac OS X/OS X/macOS (). - Macos, - /// Manjaro (). - Manjaro, - /// Mariner (). - Mariner, - /// MidnightBSD (). - MidnightBSD, - /// Mint (). - Mint, - /// NetBSD (). - NetBSD, - /// NixOS (). - NixOS, - /// OpenBSD (). - OpenBSD, - /// OpenCloudOS (). - OpenCloudOS, - /// openEuler (). - openEuler, - /// openSUSE (). - openSUSE, - /// Oracle Linux (). - OracleLinux, - /// Pop!_OS () - Pop, - /// Raspberry Pi OS (). - Raspbian, - /// Red Hat Linux (). - Redhat, - /// Red Hat Enterprise Linux (). - RedHatEnterprise, - /// Redox (). - Redox, - /// Solus (). - Solus, - /// SUSE Linux Enterprise Server (). - SUSE, - /// Ubuntu (). - Ubuntu, - /// Unknown operating system. - Unknown, - /// Windows (). - Windows, -} - -impl Default for Type { - fn default() -> Self { - Type::Unknown - } -} - -impl Display for Type { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match *self { - Type::Alpaquita => write!(f, "Alpaquita Linux"), - Type::Alpine => write!(f, "Alpine Linux"), - Type::Amazon => write!(f, "Amazon Linux AMI"), - Type::Arch => write!(f, "Arch Linux"), - Type::Artix => write!(f, "Artix Linux"), - Type::DragonFly => write!(f, "DragonFly BSD"), - Type::Garuda => write!(f, "Garuda Linux"), - Type::Gentoo => write!(f, "Gentoo Linux"), - Type::Illumos => write!(f, "illumos"), - Type::Kali => write!(f, "Kali Linux"), - Type::Macos => write!(f, "Mac OS"), - Type::MidnightBSD => write!(f, "Midnight BSD"), - Type::Mint => write!(f, "Linux Mint"), - Type::Pop => write!(f, "Pop!_OS"), - Type::Raspbian => write!(f, "Raspberry Pi OS"), - Type::Redhat => write!(f, "Red Hat Linux"), - Type::RedHatEnterprise => write!(f, "Red Hat Enterprise Linux"), - Type::SUSE => write!(f, "SUSE Linux Enterprise Server"), - _ => write!(f, "{self:?}"), - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Bitness { - /// Unknown bitness (unable to determine). - Unknown, - /// 32-bit. - X32, - /// 64-bit. - X64, -} - -impl Display for Bitness { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match *self { - Bitness::Unknown => write!(f, "unknown bitness"), - Bitness::X32 => write!(f, "32-bit"), - Bitness::X64 => write!(f, "64-bit"), - } - } -} - -/// Operating system version. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum Version { - /// Unknown version. - Unknown, - /// Semantic version (major.minor.patch). - Semantic(u64, u64, u64), - /// Rolling version. Optionally contains the release date in the string format. - Rolling(Option), - /// Custom version format. - Custom(String), -} - -impl Version { - /// Constructs `VersionType` from the given string. - /// - /// Returns `VersionType::Unknown` if the string is empty. If it can be parsed as a semantic - /// version, then `VersionType::Semantic`, otherwise `VersionType::Custom`. - /// - /// # Examples - /// - /// ``` - /// use os_info::Version; - /// - /// let v = Version::from_string("custom"); - /// assert_eq!(Version::Custom("custom".to_owned()), v); - /// - /// let v = Version::from_string("1.2.3"); - /// assert_eq!(Version::Semantic(1, 2, 3), v); - /// ``` - pub fn from_string + AsRef>(s: S) -> Self { - if s.as_ref().is_empty() { - Self::Unknown - } else if let Some((major, minor, patch)) = parse_version(s.as_ref()) { - Self::Semantic(major, minor, patch) - } else { - Self::Custom(s.into()) - } - } -} - -impl Default for Version { - fn default() -> Self { - Version::Unknown - } -} - -impl Display for Version { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match *self { - Self::Unknown => f.write_str("Unknown"), - Self::Semantic(major, minor, patch) => write!(f, "{major}.{minor}.{patch}"), - Self::Rolling(ref date) => { - let date = match date { - Some(date) => format!(" ({date})"), - None => "".to_owned(), - }; - write!(f, "Rolling Release{date}") - } - Self::Custom(ref version) => write!(f, "{version}"), - } - } -} - -fn parse_version(s: &str) -> Option<(u64, u64, u64)> { - let mut iter = s.trim().split_terminator('.').fuse(); - - let major = iter.next().and_then(|s| s.parse().ok())?; - let minor = iter.next().unwrap_or("0").parse().ok()?; - let patch = iter.next().unwrap_or("0").parse().ok()?; - - if iter.next().is_some() { - return None; - } - - Some((major, minor, patch)) -} - -#[cfg(target_arch = "x86")] -type OSVERSIONINFOEX = windows_sys::Win32::System::SystemInformation::OSVERSIONINFOEXA; - -#[cfg(not(target_arch = "x86"))] -type OSVERSIONINFOEX = windows_sys::Win32::System::SystemInformation::OSVERSIONINFOEXW; - -pub fn get() -> Info { - let (version, edition) = version(); - let native_system_info = native_system_info(); - - Info { - os_type: Type::Windows, - version, - edition, - bitness: bitness(), - architecture: architecture(native_system_info), - ..Default::default() - } -} - -fn version() -> (Version, Option) { - match version_info() { - None => (Version::Unknown, None), - Some(v) => ( - Version::Semantic( - v.dwMajorVersion as u64, - v.dwMinorVersion as u64, - v.dwBuildNumber as u64, - ), - product_name(&v).or_else(|| edition(&v)), - ), - } -} - -// According to https://learn.microsoft.com/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info -// there is a variant for AMD64 CPUs, but it's not defined in generated bindings. -const PROCESSOR_ARCHITECTURE_ARM64: u16 = 12; - -fn native_system_info() -> SYSTEM_INFO { - let mut system_info: MaybeUninit = MaybeUninit::zeroed(); - unsafe { - GetNativeSystemInfo(system_info.as_mut_ptr()); - }; - - unsafe { system_info.assume_init() } -} - -fn architecture(system_info: SYSTEM_INFO) -> Option { - let cpu_architecture = unsafe { system_info.Anonymous.Anonymous.wProcessorArchitecture }; - - match cpu_architecture { - PROCESSOR_ARCHITECTURE_AMD64 => Some("x86_64"), - PROCESSOR_ARCHITECTURE_IA64 => Some("ia64"), - PROCESSOR_ARCHITECTURE_ARM => Some("arm"), - PROCESSOR_ARCHITECTURE_ARM64 => Some("aarch64"), - PROCESSOR_ARCHITECTURE_INTEL => Some("i386"), - _ => None, - } - .map(str::to_string) -} - -#[cfg(target_pointer_width = "64")] -fn bitness() -> Bitness { - // x64 program can only run on x64 Windows. - Bitness::X64 -} - -#[cfg(target_pointer_width = "32")] -fn bitness() -> Bitness { - use windows_sys::Win32::Foundation::{BOOL, FALSE, HANDLE}; - use windows_sys::Win32::System::Threading::GetCurrentProcess; - - // IsWow64Process is not available on all supported versions of Windows. Use GetModuleHandle to - // get a handle to the DLL that contains the function and GetProcAddress to get a pointer to the - // function if available. - let is_wow_64 = match get_proc_address(b"kernel32\0", b"IsWow64Process\0") { - None => return Bitness::Unknown, - Some(val) => val, - }; - - type IsWow64 = unsafe extern "system" fn(HANDLE, *mut BOOL) -> BOOL; - let is_wow_64: IsWow64 = unsafe { mem::transmute(is_wow_64) }; - - let mut result = FALSE; - if unsafe { is_wow_64(GetCurrentProcess(), &mut result) } == 0 { - log::error!("IsWow64Process failed"); - return Bitness::Unknown; - } - - if result == FALSE { - Bitness::X32 - } else { - Bitness::X64 - } -} - -// Calls the Win32 API function RtlGetVersion to get the OS version information: -// https://msdn.microsoft.com/library/mt723418(v=vs.85).aspx -fn version_info() -> Option { - let rtl_get_version = match get_proc_address(b"ntdll\0", b"RtlGetVersion\0") { - None => return None, - Some(val) => val, - }; - - type RtlGetVersion = unsafe extern "system" fn(&mut OSVERSIONINFOEX) -> NTSTATUS; - let rtl_get_version: RtlGetVersion = unsafe { mem::transmute(rtl_get_version) }; - - let mut info: OSVERSIONINFOEX = unsafe { mem::zeroed() }; - info.dwOSVersionInfoSize = mem::size_of::() as u32; - - if unsafe { rtl_get_version(&mut info) } == STATUS_SUCCESS { - Some(info) - } else { - None - } -} - -fn product_name(info: &OSVERSIONINFOEX) -> Option { - let sub_key = to_wide("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"); - let mut key = Default::default(); - if unsafe { RegOpenKeyExW(HKEY_LOCAL_MACHINE, sub_key.as_ptr(), 0, KEY_READ, &mut key) } - != ERROR_SUCCESS - || key == 0 - { - log::error!("RegOpenKeyExW(HKEY_LOCAL_MACHINE, ...) failed"); - return None; - } - - let is_win_11 = info.dwMajorVersion == 10 && info.dwBuildNumber >= 22000; - - // Get size of the data. - let name = to_wide(if is_win_11 { - "EditionID" - } else { - "ProductName" - }); - let mut data_type = 0; - let mut data_size = 0; - if unsafe { - RegQueryValueExW( - key, - name.as_ptr(), - ptr::null_mut(), - &mut data_type, - ptr::null_mut(), - &mut data_size, - ) - } != ERROR_SUCCESS - || data_type != REG_SZ - || data_size == 0 - || data_size % 2 != 0 - { - log::error!("RegQueryValueExW failed"); - return None; - } - - // Get the data. - let mut data = vec![0u16; data_size as usize / 2]; - if unsafe { - RegQueryValueExW( - key, - name.as_ptr(), - ptr::null_mut(), - ptr::null_mut(), - data.as_mut_ptr().cast(), - &mut data_size, - ) - } != ERROR_SUCCESS - || data_size as usize != data.len() * 2 - { - return None; - } - - // If the data has the REG_SZ, REG_MULTI_SZ or REG_EXPAND_SZ type, the string may not have been - // stored with the proper terminating null characters. - match data.last() { - Some(0) => { - data.pop(); - } - _ => {} - } - - let value = OsString::from_wide(data.as_slice()) - .to_string_lossy() - .into_owned(); - - if is_win_11 { - Some(format!("Windows 11 {}", value)) - } else { - Some(value) - } -} - -fn to_wide(value: &str) -> Vec { - OsStr::new(value).encode_wide().chain(Some(0)).collect() -} - -// Examines data in the OSVERSIONINFOEX structure to determine the Windows edition: -// https://msdn.microsoft.com/library/windows/desktop/ms724833(v=vs.85).aspx -fn edition(version_info: &OSVERSIONINFOEX) -> Option { - match ( - version_info.dwMajorVersion, - version_info.dwMinorVersion, - version_info.wProductType as u32, - ) { - // Windows 10. - (10, 0, VER_NT_WORKSTATION) => { - if version_info.dwBuildNumber >= 22000 { - Some("Windows 11") - } else { - Some("Windows 10") - } - } - (10, 0, _) => Some("Windows Server 2016"), - // Windows Vista, 7, 8 and 8.1. - (6, 3, VER_NT_WORKSTATION) => Some("Windows 8.1"), - (6, 3, _) => Some("Windows Server 2012 R2"), - (6, 2, VER_NT_WORKSTATION) => Some("Windows 8"), - (6, 2, _) => Some("Windows Server 2012"), - (6, 1, VER_NT_WORKSTATION) => Some("Windows 7"), - (6, 1, _) => Some("Windows Server 2008 R2"), - (6, 0, VER_NT_WORKSTATION) => Some("Windows Vista"), - (6, 0, _) => Some("Windows Server 2008"), - // Windows 2000, Home Server, 2003 Server, 2003 R2 Server, XP and XP Professional x64. - (5, 1, _) => Some("Windows XP"), - (5, 0, _) => Some("Windows 2000"), - (5, 2, _) if unsafe { GetSystemMetrics(SM_SERVERR2) } == 0 => { - let mut info: SYSTEM_INFO = unsafe { mem::zeroed() }; - unsafe { GetSystemInfo(&mut info) }; - - if Into::::into(version_info.wSuiteMask) & VER_SUITE_WH_SERVER - == VER_SUITE_WH_SERVER - { - Some("Windows Home Server") - } else if version_info.wProductType == VER_NT_WORKSTATION as u8 - && unsafe { info.Anonymous.Anonymous.wProcessorArchitecture } - == PROCESSOR_ARCHITECTURE_AMD64 - { - Some("Windows XP Professional x64 Edition") - } else { - Some("Windows Server 2003") - } - } - _ => None, - } - .map(str::to_string) -} - -fn get_proc_address(module: &[u8], proc: &[u8]) -> Option { - assert!( - *module.last().expect("Empty module name") == 0, - "Module name should be zero-terminated" - ); - assert!( - *proc.last().expect("Empty procedure name") == 0, - "Procedure name should be zero-terminated" - ); - - let handle = unsafe { GetModuleHandleA(module.as_ptr()) }; - if handle == 0 { - log::error!( - "GetModuleHandleA({}) failed", - String::from_utf8_lossy(module) - ); - return None; - } - - unsafe { Some(GetProcAddress(handle, proc.as_ptr())) } -} - -#[cfg(test)] -mod tests { - use super::*; - use pretty_assertions::{assert_eq, assert_ne}; - - #[test] - fn version() { - let info = get(); - assert_eq!(Type::Windows, info.os_type()); - } - - #[test] - fn get_version_info() { - let version = version_info(); - assert!(version.is_some()); - } - - #[test] - fn get_edition() { - let test_data = [ - (10, 0, 0, "Windows Server 2016"), - (6, 3, VER_NT_WORKSTATION, "Windows 8.1"), - (6, 3, 0, "Windows Server 2012 R2"), - (6, 2, VER_NT_WORKSTATION, "Windows 8"), - (6, 2, 0, "Windows Server 2012"), - (6, 1, VER_NT_WORKSTATION, "Windows 7"), - (6, 1, 0, "Windows Server 2008 R2"), - (6, 0, VER_NT_WORKSTATION, "Windows Vista"), - (6, 0, 0, "Windows Server 2008"), - (5, 1, 0, "Windows XP"), - (5, 1, 1, "Windows XP"), - (5, 1, 100, "Windows XP"), - (5, 0, 0, "Windows 2000"), - (5, 0, 1, "Windows 2000"), - (5, 0, 100, "Windows 2000"), - ]; - - let mut info = version_info().unwrap(); - - for &(major, minor, product_type, expected_edition) in &test_data { - info.dwMajorVersion = major; - info.dwMinorVersion = minor; - info.wProductType = product_type as u8; - - let edition = edition(&info).unwrap(); - assert_eq!(edition, expected_edition); - } - } - - #[test] - fn get_bitness() { - let b = bitness(); - assert_ne!(b, Bitness::Unknown); - } - - #[test] - #[should_panic(expected = "Empty module name")] - fn empty_module_name() { - get_proc_address(b"", b"RtlGetVersion\0"); - } - - #[test] - #[should_panic(expected = "Module name should be zero-terminated")] - fn non_zero_terminated_module_name() { - get_proc_address(b"ntdll", b"RtlGetVersion\0"); - } - - #[test] - #[should_panic(expected = "Empty procedure name")] - fn empty_proc_name() { - get_proc_address(b"ntdll\0", b""); - } - - #[test] - #[should_panic(expected = "Procedure name should be zero-terminated")] - fn non_zero_terminated_proc_name() { - get_proc_address(b"ntdll\0", b"RtlGetVersion"); - } - - #[test] - fn proc_address() { - let address = get_proc_address(b"ntdll\0", b"RtlGetVersion\0"); - assert!(address.is_some()); - } - - #[test] - fn get_architecture() { - let cpu_types: [(u16, Option); 6] = [ - (PROCESSOR_ARCHITECTURE_AMD64, Some("x86_64".to_owned())), - (PROCESSOR_ARCHITECTURE_ARM, Some("arm".to_owned())), - (PROCESSOR_ARCHITECTURE_ARM64, Some("aarch64".to_owned())), - (PROCESSOR_ARCHITECTURE_IA64, Some("ia64".to_owned())), - (PROCESSOR_ARCHITECTURE_INTEL, Some("i386".to_owned())), - (0xffff, None), - ]; - - let mut native_info = native_system_info(); - - for cpu_type in cpu_types { - native_info.Anonymous.Anonymous.wProcessorArchitecture = cpu_type.0; - assert_eq!(architecture(native_info), cpu_type.1); - } - } - - #[test] - fn get_product_name() { - let version = version_info().expect("version_info() failed"); - let edition = product_name(&version).expect("edition() failed"); - assert!(!edition.is_empty()); - } - - #[test] - fn to_wide_str() { - let data = [ - ("", [0x0000].as_ref()), - ("U", &[0x0055, 0x0000]), - ("你好", &[0x4F60, 0x597D, 0x0000]), - ]; - - for (s, expected) in &data { - let wide = to_wide(s); - assert_eq!(&wide, expected); - } - } -} \ No newline at end of file diff --git a/src/Rust/src/windows/runtimes.rs b/src/Rust/src/windows/runtimes.rs index 88d8cab7..e516613b 100644 --- a/src/Rust/src/windows/runtimes.rs +++ b/src/Rust/src/windows/runtimes.rs @@ -1,5 +1,6 @@ use crate::shared as util; use crate::shared::download; +use crate::shared::runtime_arch::RuntimeArch; use anyhow::{anyhow, bail, Result}; use regex::Regex; use std::process::Command as Process; @@ -56,54 +57,6 @@ lazy_static! { }; } -#[derive(PartialEq, Debug, Clone, strum::IntoStaticStr)] -pub enum RuntimeArch { - X86, - X64, - Arm64, -} - -impl RuntimeArch { - pub fn from_str(arch_str: &str) -> Option { - match arch_str.to_lowercase().as_str() { - "x86" => Some(RuntimeArch::X86), - "x64" => Some(RuntimeArch::X64), - "arm64" => Some(RuntimeArch::Arm64), - _ => None, - } - } - pub fn from_current_system() -> Option { - let info = super::os_info::get(); - let machine = info.architecture(); - if machine.is_none() { - return None; - } - let mut machine = machine.unwrap(); - if machine.is_empty() { - return None; - } - if machine == "x86_64" { - machine = "x64"; - } else if machine == "i386" { - machine = "x86"; - } else if machine == "aarch64" { - machine = "arm64"; - } - Self::from_str(machine) - } -} - -#[test] -fn test_cpu_arch_from_str() { - assert_eq!(RuntimeArch::from_str("x86"), Some(RuntimeArch::X86)); - assert_eq!(RuntimeArch::from_str("x64"), Some(RuntimeArch::X64)); - assert_eq!(RuntimeArch::from_str("arm64"), Some(RuntimeArch::Arm64)); - assert_eq!(RuntimeArch::from_str("foo"), None); - assert_eq!(RuntimeArch::from_str("X86"), Some(RuntimeArch::X86)); - assert_eq!(RuntimeArch::from_str("X64"), Some(RuntimeArch::X64)); - assert_eq!(RuntimeArch::from_str("ARM64"), Some(RuntimeArch::Arm64)); -} - #[derive(Debug, PartialEq, Clone, strum::IntoStaticStr)] pub enum RuntimeInstallResult { InstallSuccess, diff --git a/src/Rust/src/windows/util.rs b/src/Rust/src/windows/util.rs index 5dbe4f57..3c597067 100644 --- a/src/Rust/src/windows/util.rs +++ b/src/Rust/src/windows/util.rs @@ -1,4 +1,4 @@ -use crate::shared; +use crate::shared::{self, runtime_arch::RuntimeArch}; use anyhow::{anyhow, bail, Result}; use normpath::PathExt; use std::{ @@ -301,37 +301,31 @@ where } pub fn is_cpu_architecture_supported(architecture: &str) -> Result { - let info = super::os_info::get(); - let machine = info.architecture(); + let machine = RuntimeArch::from_current_system(); if machine.is_none() { - return Ok(true); // we can't detect current arch so try installing anyway. - } - - let mut machine = machine.unwrap(); - let is_win_11 = is_os_version_or_greater("11")?; - - if machine.is_empty() || architecture.is_empty() { + // we can't detect current os arch so try installing anyway return Ok(true); } - // https://github.com/stanislav-tkach/os_info/blob/master/os_info/src/windows/winapi.rs#L82 - if machine == "x86_64" { - machine = "x64"; - } else if machine == "i386" { - machine = "x86"; - } else if machine == "aarch64" { - machine = "arm64"; + let architecture = RuntimeArch::from_str(architecture); + if architecture.is_none() { + // no arch specified in this package, so install on any arch + return Ok(true); } - if machine == "x86" { + let machine = machine.unwrap(); + let architecture = architecture.unwrap(); + let is_win_11 = is_os_version_or_greater("11")?; + + if machine == RuntimeArch::X86 { // windows x86 only supports x86 - Ok(architecture == "x86") - } else if machine == "x64" { + Ok(architecture == RuntimeArch::X86) + } else if machine == RuntimeArch::X64 { // windows x64 only supports x86 and x64 - Ok(architecture == "x86" || architecture == "x64") - } else if machine == "arm64" { + Ok(architecture == RuntimeArch::X86 || architecture == RuntimeArch::X64) + } else if machine == RuntimeArch::Arm64 { // windows arm64 supports x86, and arm64, and only on windows 11 does it support x64 - Ok(architecture == "x86" || (architecture == "x64" && is_win_11) || architecture == "arm64") + Ok(architecture == RuntimeArch::X86 || (architecture == RuntimeArch::X64 && is_win_11) || architecture == RuntimeArch::Arm64) } else { // we don't know what this is, so try installing anyway Ok(true) @@ -341,7 +335,7 @@ pub fn is_cpu_architecture_supported(architecture: &str) -> Result { #[test] pub fn test_x64_and_x86_is_supported_but_not_arm64_or_invalid() { assert!(!is_cpu_architecture_supported("arm64").unwrap()); - assert!(!is_cpu_architecture_supported("invalid").unwrap()); + assert!(is_cpu_architecture_supported("invalid").unwrap()); assert!(is_cpu_architecture_supported("x64").unwrap()); assert!(is_cpu_architecture_supported("x86").unwrap()); }