Add x86 support

This commit is contained in:
Caelan Sayler
2024-01-20 11:10:15 +00:00
parent 286923d923
commit 8af0594f03
9 changed files with 871 additions and 88 deletions

View File

@@ -1,8 +1,10 @@
[alias] [alias]
bx = "build" bx = "build"
bw = "build --features windows"
tx = "test" tx = "test"
tw = "test --features windows" tw = "test --features windows"
bw = "build --features windows"
bw86 = "build --target i686-pc-windows-msvc --features windows"
[build] [build]
rustflags = ["-C", "target-feature=+crt-static"] rustflags = ["-C", "target-feature=+crt-static"]

104
src/Rust/Cargo.lock generated
View File

@@ -405,6 +405,12 @@ dependencies = [
"rpassword", "rpassword",
] ]
[[package]]
name = "diff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.9.0" version = "0.9.0"
@@ -1064,15 +1070,6 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "os_info"
version = "3.7.0"
source = "git+https://github.com/stanislav-tkach/os_info.git?branch=master#13df8d7858bf79e4f98b66bd33efac751240486f"
dependencies = [
"log",
"windows-sys 0.45.0",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.1" version = "2.3.1"
@@ -1119,6 +1116,16 @@ dependencies = [
"clap", "clap",
] ]
[[package]]
name = "pretty_assertions"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
dependencies = [
"diff",
"yansi",
]
[[package]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
version = "1.3.1" version = "1.3.1"
@@ -1671,8 +1678,8 @@ dependencies = [
"native-tls", "native-tls",
"normpath", "normpath",
"ntest", "ntest",
"os_info",
"pretty-bytes-rust", "pretty-bytes-rust",
"pretty_assertions",
"rand", "rand",
"regex", "regex",
"remove_dir_all", "remove_dir_all",
@@ -1885,15 +1892,6 @@ dependencies = [
"windows-targets 0.52.0", "windows-targets 0.52.0",
] ]
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.48.0" version = "0.48.0"
@@ -1912,21 +1910,6 @@ dependencies = [
"windows-targets 0.52.0", "windows-targets 0.52.0",
] ]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.48.5" version = "0.48.5"
@@ -1957,12 +1940,6 @@ dependencies = [
"windows_x86_64_msvc 0.52.0", "windows_x86_64_msvc 0.52.0",
] ]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.48.5" version = "0.48.5"
@@ -1975,12 +1952,6 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.48.5" version = "0.48.5"
@@ -1993,12 +1964,6 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.48.5" version = "0.48.5"
@@ -2011,12 +1976,6 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.48.5" version = "0.48.5"
@@ -2029,12 +1988,6 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.48.5" version = "0.48.5"
@@ -2047,12 +2000,6 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.48.5" version = "0.48.5"
@@ -2065,12 +2012,6 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.48.5" version = "0.48.5"
@@ -2103,8 +2044,9 @@ dependencies = [
[[package]] [[package]]
name = "winsafe" name = "winsafe"
version = "0.0.18" version = "0.0.19"
source = "git+https://github.com/caesay/winsafe.git?branch=cs/only-ipersistfile#e9aadd0b7d8337364865039bf098e73be40d11a1" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
[[package]] [[package]]
name = "xml" name = "xml"
@@ -2121,6 +2063,12 @@ version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a"
[[package]]
name = "yansi"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
[[package]] [[package]]
name = "zip" name = "zip"
version = "0.6.6" version = "0.6.6"

View File

@@ -46,7 +46,6 @@ rpath = false # disable rpath
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
pretty-bytes-rust = "0.3" pretty-bytes-rust = "0.3"
os_info = { git = "https://github.com/stanislav-tkach/os_info.git", branch = "master", default-features = false } # public releases don't yet have processor arch info
zip = { version = "0.6", default-features = false, features = ["deflate"] } zip = { version = "0.6", default-features = false, features = ["deflate"] }
regex = "1.10" regex = "1.10"
rand = "0.8" rand = "0.8"
@@ -85,14 +84,10 @@ libc = "0.2"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
fs_extra = "1.2" fs_extra = "1.2"
memmap2 = "0.9" memmap2 = "0.9"
winsafe = { git = "https://github.com/caesay/winsafe.git", branch = "cs/only-ipersistfile", features = [ winsafe = { version = "0.0.19", features = [
"kernel",
"version", "version",
"user", "user",
"shell",
"comctl",
"gui", "gui",
"ole",
] } ] }
image = { version = "0.24", default-features = false, features = [ image = { version = "0.24", default-features = false, features = [
"gif", "gif",
@@ -127,6 +122,13 @@ windows-sys = { version = "0.52", default-features = false, features = [
"Wdk", "Wdk",
"Wdk_System", "Wdk_System",
"Wdk_System_Threading", "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" normpath = "1.0.1"
codesign-verify = { git = "https://github.com/caesay/codesign-verify-rs.git" } codesign-verify = { git = "https://github.com/caesay/codesign-verify-rs.git" }
@@ -134,6 +136,7 @@ codesign-verify = { git = "https://github.com/caesay/codesign-verify-rs.git" }
[dev-dependencies] [dev-dependencies]
tempfile = "3.9" tempfile = "3.9"
ntest = "0.9.0" ntest = "0.9.0"
pretty_assertions = "1.4"
[build-dependencies] [build-dependencies]
semver = "1.0" semver = "1.0"

View File

@@ -1,6 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<!-- No UAC needed-->
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<!-- Indicate our support for newer versions so windows stops lying to us --> <!-- Indicate our support for newer versions so windows stops lying to us -->
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application> <application>

View File

@@ -11,7 +11,7 @@ use std::{
use winsafe::{self as w, co}; use winsafe::{self as w, co};
pub fn install(debug_pkg: Option<&PathBuf>, install_to: Option<&PathBuf>) -> Result<()> { pub fn install(debug_pkg: Option<&PathBuf>, install_to: Option<&PathBuf>) -> Result<()> {
let osinfo = os_info::get(); let osinfo = windows::os_info::get();
info!("OS: {}, Arch={}", osinfo, osinfo.architecture().unwrap_or("unknown")); info!("OS: {}, Arch={}", osinfo, osinfo.architecture().unwrap_or("unknown"));
if !w::IsWindowsVersionOrGreater(6, 1, 1)? { if !w::IsWindowsVersionOrGreater(6, 1, 1)? {

View File

@@ -2,6 +2,7 @@ pub mod download;
pub mod prerequisite; pub mod prerequisite;
pub mod runtimes; pub mod runtimes;
pub mod splash; pub mod splash;
pub mod os_info;
mod self_delete; mod self_delete;
mod shortcuts; mod shortcuts;

View File

@@ -0,0 +1,820 @@
// 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<String>,
/// Operating system codename.
pub(crate) codename: Option<String>,
/// 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<String>,
}
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 (<https://en.wikipedia.org/wiki/IBM_AIX>).
AIX,
/// Alpaquita Linux (<https://bell-sw.com/alpaquita-linux/>).
Alpaquita,
/// Alpine Linux (<https://en.wikipedia.org/wiki/Alpine_Linux>).
Alpine,
/// Amazon Linux AMI (<https://en.wikipedia.org/wiki/Amazon_Machine_Image#Amazon_Linux_AMI>).
Amazon,
/// Android (<https://en.wikipedia.org/wiki/Android_(operating_system)>).
Android,
/// Arch Linux (<https://en.wikipedia.org/wiki/Arch_Linux>).
Arch,
/// Artix Linux (<https://en.wikipedia.org/wiki/Artix_Linux>).
Artix,
/// CentOS (<https://en.wikipedia.org/wiki/CentOS>).
CentOS,
/// Debian (<https://en.wikipedia.org/wiki/Debian>).
Debian,
/// DragonFly BSD (<https://en.wikipedia.org/wiki/DragonFly_BSD>).
DragonFly,
/// Emscripten (<https://en.wikipedia.org/wiki/Emscripten>).
Emscripten,
/// EndeavourOS (<https://en.wikipedia.org/wiki/EndeavourOS>).
EndeavourOS,
/// Fedora (<https://en.wikipedia.org/wiki/Fedora_(operating_system)>).
Fedora,
/// FreeBSD (<https://en.wikipedia.org/wiki/FreeBSD>).
FreeBSD,
/// Garuda Linux (<https://en.wikipedia.org/wiki/Garuda_Linux>)
Garuda,
/// Gentoo Linux (<https://en.wikipedia.org/wiki/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 (<https://en.wikipedia.org/wiki/Linux>).
Linux,
/// Mabox (<https://maboxlinux.org/>).
Mabox,
/// Mac OS X/OS X/macOS (<https://en.wikipedia.org/wiki/MacOS>).
Macos,
/// Manjaro (<https://en.wikipedia.org/wiki/Manjaro>).
Manjaro,
/// Mariner (<https://en.wikipedia.org/wiki/CBL-Mariner>).
Mariner,
/// MidnightBSD (<https://en.wikipedia.org/wiki/MidnightBSD>).
MidnightBSD,
/// Mint (<https://en.wikipedia.org/wiki/Linux_Mint>).
Mint,
/// NetBSD (<https://en.wikipedia.org/wiki/NetBSD>).
NetBSD,
/// NixOS (<https://en.wikipedia.org/wiki/NixOS>).
NixOS,
/// OpenBSD (<https://en.wikipedia.org/wiki/OpenBSD>).
OpenBSD,
/// OpenCloudOS (<https://www.opencloudos.org>).
OpenCloudOS,
/// openEuler (<https://en.wikipedia.org/wiki/EulerOS>).
openEuler,
/// openSUSE (<https://en.wikipedia.org/wiki/OpenSUSE>).
openSUSE,
/// Oracle Linux (<https://en.wikipedia.org/wiki/Oracle_Linux>).
OracleLinux,
/// Pop!_OS (<https://en.wikipedia.org/wiki/Pop!_OS>)
Pop,
/// Raspberry Pi OS (<https://en.wikipedia.org/wiki/Raspberry_Pi_OS>).
Raspbian,
/// Red Hat Linux (<https://en.wikipedia.org/wiki/Red_Hat_Linux>).
Redhat,
/// Red Hat Enterprise Linux (<https://en.wikipedia.org/wiki/Red_Hat_Enterprise_Linux>).
RedHatEnterprise,
/// Redox (<https://en.wikipedia.org/wiki/Redox_(operating_system)>).
Redox,
/// Solus (<https://en.wikipedia.org/wiki/Solus_(operating_system)>).
Solus,
/// SUSE Linux Enterprise Server (<https://en.wikipedia.org/wiki/SUSE_Linux_Enterprise>).
SUSE,
/// Ubuntu (<https://en.wikipedia.org/wiki/Ubuntu_(operating_system)>).
Ubuntu,
/// Unknown operating system.
Unknown,
/// Windows (<https://en.wikipedia.org/wiki/Microsoft_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<String>),
/// 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<S: Into<String> + AsRef<str>>(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<String>) {
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/en-us/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<SYSTEM_INFO> = MaybeUninit::zeroed();
unsafe {
GetNativeSystemInfo(system_info.as_mut_ptr());
};
unsafe { system_info.assume_init() }
}
fn architecture(system_info: SYSTEM_INFO) -> Option<String> {
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/en-us/library/mt723418(v=vs.85).aspx
fn version_info() -> Option<OSVERSIONINFOEX> {
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::<OSVERSIONINFOEX>() as u32;
if unsafe { rtl_get_version(&mut info) } == STATUS_SUCCESS {
Some(info)
} else {
None
}
}
fn product_name(info: &OSVERSIONINFOEX) -> Option<String> {
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<u16> {
OsStr::new(value).encode_wide().chain(Some(0)).collect()
}
// Examines data in the OSVERSIONINFOEX structure to determine the Windows edition:
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724833(v=vs.85).aspx
fn edition(version_info: &OSVERSIONINFOEX) -> Option<String> {
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::<u32>::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<FARPROC> {
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<String>); 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);
}
}
}

View File

@@ -72,7 +72,7 @@ impl RuntimeArch {
} }
} }
pub fn from_current_system() -> Option<Self> { pub fn from_current_system() -> Option<Self> {
let info = os_info::get(); let info = super::os_info::get();
let machine = info.architecture(); let machine = info.architecture();
if machine.is_none() { if machine.is_none() {
return None; return None;

View File

@@ -305,7 +305,7 @@ where
} }
pub fn is_cpu_architecture_supported(architecture: &str) -> Result<bool> { pub fn is_cpu_architecture_supported(architecture: &str) -> Result<bool> {
let info = os_info::get(); let info = super::os_info::get();
let machine = info.architecture(); let machine = info.architecture();
if machine.is_none() { if machine.is_none() {
return Ok(true); // we can't detect current arch so try installing anyway. return Ok(true); // we can't detect current arch so try installing anyway.