#![warn(unused_results)]
use std::convert::TryFrom;
use std::ffi::{OsStr, OsString};
use std::fmt;
use std::fmt::{Debug, Formatter};
use std::io;
use std::os::windows::ffi::OsStringExt;
use windows_sys::Win32::Foundation::FALSE;
use windows_sys::Win32::System::SystemInformation::{
ComputerNamePhysicalDnsHostname, PROCESSOR_ARCHITECTURE_ALPHA, PROCESSOR_ARCHITECTURE_ALPHA64,
PROCESSOR_ARCHITECTURE_AMD64, PROCESSOR_ARCHITECTURE_ARM, PROCESSOR_ARCHITECTURE_ARM64,
PROCESSOR_ARCHITECTURE_IA64, PROCESSOR_ARCHITECTURE_INTEL, PROCESSOR_ARCHITECTURE_MIPS,
PROCESSOR_ARCHITECTURE_PPC, PROCESSOR_ARCHITECTURE_SHX, SYSTEM_INFO, VER_PRODUCT_TYPE,
VER_SUITENAME,
};
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
pub(crate) type BYTE = u8;
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
pub(crate) type WORD = u16;
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
pub(crate) type DWORD = u32;
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
pub(crate) type UINT = u32;
const VER_EQUAL: BYTE = 1;
const VER_NT_WORKSTATION: BYTE = 1;
const VER_SUITE_WH_SERVER: DWORD = 32768;
#[cfg(test)]
const VER_NT_SERVER: BYTE = 3;
#[cfg(test)]
const VER_SUITE_PERSONAL: DWORD = 0x00000200;
#[cfg(test)]
const VER_SUITE_SMALLBUSINESS: DWORD = 0x00000001;
use crate::{PlatformInfoAPI, PlatformInfoError, UNameAPI};
use super::PathStr;
use super::PathString;
type WinOSError = crate::lib_impl::BoxedThreadSafeStdError;
mod windows_safe;
use windows_safe::*;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PlatformInfo {
pub computer_name: OsString,
pub system_info: WinApiSystemInfo,
pub version_info: WinOsVersionInfo,
sysname: OsString,
nodename: OsString,
release: OsString,
version: OsString,
machine: OsString,
processor: OsString,
osname: OsString,
}
impl PlatformInfoAPI for PlatformInfo {
fn new() -> Result<Self, PlatformInfoError> {
let computer_name = WinOsGetComputerName()?;
let system_info = WinApiSystemInfo(WinAPI_GetNativeSystemInfo());
let version_info = os_version_info()?;
let sysname = determine_sysname();
let nodename = computer_name.clone();
let release = version_info.release.clone();
let version = version_info.version.clone();
let machine = determine_machine(&system_info);
let processor = OsString::from(crate::lib_impl::map_processor(&machine.to_string_lossy()));
let osname = determine_osname(&version_info);
Ok(Self {
computer_name,
system_info,
version_info,
sysname,
nodename,
release,
version,
machine,
processor,
osname,
})
}
}
impl UNameAPI for PlatformInfo {
fn sysname(&self) -> &OsStr {
&self.sysname
}
fn nodename(&self) -> &OsStr {
&self.nodename
}
fn release(&self) -> &OsStr {
&self.release
}
fn version(&self) -> &OsStr {
&self.version
}
fn machine(&self) -> &OsStr {
&self.machine
}
fn processor(&self) -> &OsStr {
&self.processor
}
fn osname(&self) -> &OsStr {
&self.osname
}
}
#[derive(Clone, Copy /* , Debug, PartialEq, Eq *//* note: implemented elsewhere */)]
pub struct WinApiSystemInfo(
SYSTEM_INFO,
);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WinOsVersionInfo {
pub os_name: OsString,
pub release: OsString,
pub version: OsString,
}
pub mod util {
use std::ffi::CString;
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
pub type WCHAR = u16;
#[allow(clippy::upper_case_acronyms)]
pub type WSTR = Vec<WCHAR>;
#[allow(clippy::upper_case_acronyms)]
pub type CWSTR = Vec<WCHAR>;
pub fn to_c_string<S: AsRef<OsStr>>(os_str: S) -> CString {
let nul = '\0';
let s = os_str.as_ref().to_string_lossy();
let leading_s = s.split(nul).next().unwrap_or("");
let maybe_c_string = CString::new(leading_s);
assert!(maybe_c_string.is_ok()); maybe_c_string.unwrap()
}
pub fn to_c_wstring<S: AsRef<OsStr>>(os_str: S) -> CWSTR {
let nul: WCHAR = 0;
let mut wstring: WSTR = os_str.as_ref().encode_wide().collect();
wstring.push(nul);
let maybe_index_first_nul = wstring.iter().position(|&i| i == nul);
assert!(maybe_index_first_nul.is_some()); let index_first_nul = maybe_index_first_nul.unwrap();
assert!(index_first_nul < wstring.len()); CWSTR::from(&wstring[..(index_first_nul + 1)])
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct MmbrVersion {
major: DWORD,
minor: DWORD,
build: DWORD,
release: DWORD,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WinApiFileVersionInfo {
data: Vec<BYTE>,
}
impl Debug for WinApiSystemInfo {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("WinApiSystemInfo")
.field("wProcessorArchitecture", &self.wProcessorArchitecture())
.field("dwPageSize", &self.0.dwPageSize)
.field(
"lpMinimumApplicationAddress",
&self.0.lpMinimumApplicationAddress,
)
.field(
"lpMaximumApplicationAddress",
&self.0.lpMaximumApplicationAddress,
)
.field("dwActiveProcessorMask", &self.0.dwActiveProcessorMask)
.field("dwNumberOfProcessors", &self.0.dwNumberOfProcessors)
.field("dwProcessorType", &self.0.dwProcessorType)
.field("dwAllocationGranularity", &self.0.dwAllocationGranularity)
.field("wAllocationGranularity", &self.0.wProcessorLevel)
.field("wAllocationRevision", &self.0.wProcessorRevision)
.finish()
}
}
impl PartialEq for WinApiSystemInfo {
fn eq(&self, other: &Self) -> bool {
(
self.wProcessorArchitecture(),
self.0.dwPageSize,
self.0.lpMinimumApplicationAddress,
self.0.lpMaximumApplicationAddress,
self.0.dwActiveProcessorMask,
self.0.dwNumberOfProcessors,
self.0.dwProcessorType,
self.0.dwAllocationGranularity,
self.0.wProcessorLevel,
self.0.wProcessorRevision,
) == (
other.wProcessorArchitecture(),
other.0.dwPageSize,
other.0.lpMinimumApplicationAddress,
other.0.lpMaximumApplicationAddress,
other.0.dwActiveProcessorMask,
other.0.dwNumberOfProcessors,
other.0.dwProcessorType,
other.0.dwAllocationGranularity,
other.0.wProcessorLevel,
other.0.wProcessorRevision,
)
}
}
impl Eq for WinApiSystemInfo {}
#[allow(non_snake_case)]
fn WinOsGetComputerName() -> Result<OsString, WinOSError> {
let name_type = ComputerNamePhysicalDnsHostname;
let mut size: DWORD = 0;
let _ = WinAPI_GetComputerNameExW(name_type, None, &mut size);
let mut data = vec![0; usize::try_from(size)?];
let result = WinAPI_GetComputerNameExW(name_type, &mut data, &mut size);
if result == FALSE {
return Err(Box::new(io::Error::last_os_error()));
}
Ok(OsString::from_wide(&data[..usize::try_from(size)?]))
}
#[allow(non_snake_case)]
fn WinOsGetFileVersionInfo<P: AsRef<PathStr>>(
file_path: P,
) -> Result<WinApiFileVersionInfo, WinOSError> {
let file_version_size = WinAPI_GetFileVersionInfoSizeW(&file_path);
if file_version_size == 0 {
return Err(Box::new(io::Error::last_os_error()));
}
let mut data: Vec<BYTE> = vec![0; usize::try_from(file_version_size)?];
let result = WinAPI_GetFileVersionInfoW(&file_path, &mut data);
if result == FALSE {
return Err(Box::new(io::Error::last_os_error()));
}
Ok(WinApiFileVersionInfo { data })
}
#[allow(non_snake_case)]
fn WinOsGetSystemDirectory() -> Result<PathString, WinOSError> {
let required_capacity: UINT = WinAPI_GetSystemDirectoryW(None);
let mut data = vec![0; usize::try_from(required_capacity)?];
let result = WinAPI_GetSystemDirectoryW(&mut data);
if result == 0 {
return Err(Box::new(io::Error::last_os_error()));
}
let path = PathString::from(OsString::from_wide(&data[..usize::try_from(result)?]));
Ok(path)
}
fn os_version_info() -> Result<WinOsVersionInfo, WinOSError> {
match os_version_info_from_dll() {
Ok(os_info) => Ok(os_info),
Err(_) => {
version_info_from_file("" )
}
}
}
fn os_version_info_from_dll() -> Result<WinOsVersionInfo, WinOSError> {
let os_info = NTDLL_RtlGetVersion()?;
Ok(WinOsVersionInfo {
os_name: winos_name(
os_info.dwMajorVersion,
os_info.dwMinorVersion,
os_info.dwBuildNumber,
os_info.wProductType,
os_info.wSuiteMask.into(),
)
.into(),
release: format!("{}.{}", os_info.dwMajorVersion, os_info.dwMinorVersion).into(),
version: format!("{}", os_info.dwBuildNumber).into(),
})
}
fn version_info_from_file<I, P>(file_path: I) -> Result<WinOsVersionInfo, WinOSError>
where
I: Into<Option<P>>,
P: AsRef<PathStr>,
{
let file_path: PathString = match file_path.into() {
Some(ref p) if !p.as_ref().as_os_str().is_empty() => p.as_ref().into(),
_ => WinOsGetSystemDirectory()?.join("kernel32.dll"),
};
let file_info = WinOsGetFileVersionInfo(file_path)?;
let v = mmbr_from_file_version(file_info)?;
let mut info = create_OSVERSIONINFOEXW()?;
info.wSuiteMask = WORD::try_from(VER_SUITE_WH_SERVER)?;
info.wProductType = VER_NT_WORKSTATION;
let mask = WinAPI_VerSetConditionMask(0, VER_SUITENAME, VER_EQUAL);
let suite_mask = if WinAPI_VerifyVersionInfoW(&info, VER_SUITENAME, mask) != FALSE {
VER_SUITE_WH_SERVER
} else {
0
};
let mask = WinAPI_VerSetConditionMask(0, VER_PRODUCT_TYPE, VER_EQUAL);
let product_type = if WinAPI_VerifyVersionInfoW(&info, VER_PRODUCT_TYPE, mask) != FALSE {
VER_NT_WORKSTATION
} else {
0
};
Ok(WinOsVersionInfo {
os_name: winos_name(v.major, v.minor, v.build, product_type, suite_mask).into(),
release: format!("{}.{}", v.major, v.minor).into(),
version: format!("{}", v.build).into(),
})
}
fn mmbr_from_file_version(
file_version_info: WinApiFileVersionInfo,
) -> Result<MmbrVersion, WinOSError> {
let info = WinOsFileVersionInfoQuery_root(&file_version_info)?;
Ok(MmbrVersion {
major: info.dwProductVersionMS >> 16,
minor: info.dwProductVersionMS & 0xffff,
build: info.dwProductVersionLS >> 16,
release: info.dwProductVersionLS & 0xffff,
})
}
fn winos_name(
major: DWORD,
minor: DWORD,
build: DWORD,
product_type: BYTE,
suite_mask: DWORD,
) -> String {
let default_name = if product_type == VER_NT_WORKSTATION {
format!("{} {}.{}", "Windows", major, minor)
} else {
format!("{} {}.{}", "Windows Server", major, minor)
};
let name = match major {
5 => match minor {
0 => "Windows 2000",
1 => "Windows XP",
2 if product_type == VER_NT_WORKSTATION => "Windows XP Professional x64 Edition",
2 if suite_mask == VER_SUITE_WH_SERVER => "Windows Home Server",
2 => "Windows Server 2003",
_ => &default_name,
},
6 => match minor {
0 if product_type == VER_NT_WORKSTATION => "Windows Vista",
0 => "Windows Server 2008",
1 if product_type != VER_NT_WORKSTATION => "Windows Server 2008 R2",
1 => "Windows 7",
2 if product_type != VER_NT_WORKSTATION => "Windows Server 2012",
2 => "Windows 8",
3 if product_type != VER_NT_WORKSTATION => "Windows Server 2012 R2",
3 => "Windows 8.1",
_ => &default_name,
},
10 => match minor {
0 if product_type == VER_NT_WORKSTATION && (build >= 22000) => "Windows 11",
0 if product_type != VER_NT_WORKSTATION && (14000..17000).contains(&build) => {
"Windows Server 2016"
}
0 if product_type != VER_NT_WORKSTATION && (17000..19000).contains(&build) => {
"Windows Server 2019"
}
0 if product_type != VER_NT_WORKSTATION && (build >= 20000) => "Windows Server 2022",
_ => "Windows 10",
},
_ => &default_name,
};
name.to_string()
}
fn determine_machine(system_info: &WinApiSystemInfo) -> OsString {
let arch = system_info.wProcessorArchitecture();
let arch_str = match arch {
PROCESSOR_ARCHITECTURE_AMD64 => "x86_64",
PROCESSOR_ARCHITECTURE_INTEL => match system_info.0.wProcessorLevel {
4 => "i486",
5 => "i586",
6 => "i686",
_ => "i386",
},
PROCESSOR_ARCHITECTURE_IA64 => "ia64",
PROCESSOR_ARCHITECTURE_ARM => "arm", PROCESSOR_ARCHITECTURE_ARM64 => "aarch64", PROCESSOR_ARCHITECTURE_MIPS => "mips",
PROCESSOR_ARCHITECTURE_PPC => "powerpc",
PROCESSOR_ARCHITECTURE_ALPHA | PROCESSOR_ARCHITECTURE_ALPHA64 => "alpha",
PROCESSOR_ARCHITECTURE_SHX => "superh", _ => "unknown",
};
OsString::from(arch_str)
}
fn determine_osname(version_info: &WinOsVersionInfo) -> OsString {
let mut osname = OsString::from(crate::lib_impl::HOST_OS_NAME);
osname.extend([
OsString::from(" ("),
version_info.os_name.clone(),
OsString::from(")"),
]);
osname
}
fn determine_sysname() -> OsString {
OsString::from("Windows_NT") }
#[test]
fn test_sysname() {
let info = PlatformInfo::new().unwrap();
let sysname = info.sysname().to_os_string();
let expected = std::env::var_os("OS").unwrap_or_else(|| OsString::from("Windows_NT"));
println!("sysname=[{}]'{:#?}'", sysname.len(), sysname);
assert_eq!(sysname, expected);
}
#[test]
#[allow(non_snake_case)]
fn test_nodename_no_trailing_NUL() {
let info = PlatformInfo::new().unwrap();
let nodename = info.nodename().to_string_lossy();
let trimmed = nodename.trim().trim_end_matches('\0');
println!("nodename=[{}]'{}'", nodename.len(), nodename);
assert_eq!(nodename, trimmed);
}
#[test]
fn test_machine() {
let is_wow64 = KERNEL32_IsWow64Process(WinAPI_GetCurrentProcess()).unwrap_or_else(|err| {
println!("ERR: IsWow64Process(): {:#?}", err);
false
});
let target = if cfg!(target_arch = "x86_64") || (cfg!(target_arch = "x86") && is_wow64) {
vec!["x86_64"]
} else if cfg!(target_arch = "x86") {
vec!["i386", "i486", "i586", "i686"]
} else if cfg!(target_arch = "arm") {
vec!["arm"]
} else if cfg!(target_arch = "aarch64") {
vec!["arm64", "aarch64"]
} else if cfg!(target_arch = "powerpc") {
vec!["powerpc"]
} else if cfg!(target_arch = "mips") {
vec!["mips"]
} else {
vec!["unknown"]
};
println!("target={:#?}", target);
let info = PlatformInfo::new().unwrap();
let machine = info.machine().to_string_lossy();
println!("machine=[{}]'{}'", machine.len(), machine);
assert!(target.contains(&&machine[..]));
}
#[test]
fn test_osname() {
let info = PlatformInfo::new().unwrap();
let osname = info.osname().to_string_lossy();
println!("osname=[{}]'{}'", osname.len(), osname);
assert!(osname.starts_with(crate::lib_impl::HOST_OS_NAME));
}
#[test]
fn test_version_vs_version() {
let version_via_dll = os_version_info_from_dll().unwrap();
let version_via_file = version_info_from_file::<_, &str>(None).unwrap();
assert!(version_via_file == version_info_from_file("").unwrap());
println!("version (via dll) = '{:#?}'", version_via_dll);
println!("version (via known file) = '{:#?}'", version_via_file);
assert_eq!(version_via_dll.os_name, version_via_file.os_name);
assert_eq!(version_via_dll.release, version_via_file.release);
let version_via_dll_n = version_via_dll
.version
.to_string_lossy()
.parse::<u32>()
.unwrap();
let version_via_file_n = version_via_file
.version
.to_string_lossy()
.parse::<u32>()
.unwrap();
assert!(version_via_dll_n.checked_sub(version_via_file_n) < Some(1000));
}
#[test]
fn test_known_winos_names() {
assert_eq!(
winos_name(3, 1, 528, VER_NT_WORKSTATION, VER_SUITE_PERSONAL),
"Windows 3.1"
);
assert_eq!(
winos_name(3, 5, 807, VER_NT_WORKSTATION, VER_SUITE_PERSONAL),
"Windows 3.5"
);
assert_eq!(
winos_name(3, 51, 1057, VER_NT_WORKSTATION, VER_SUITE_PERSONAL),
"Windows 3.51"
);
assert_eq!(
winos_name(4, 0, 1381, VER_NT_WORKSTATION, VER_SUITE_PERSONAL),
"Windows 4.0"
);
assert_eq!(
winos_name(5, 0, 2195, VER_NT_WORKSTATION, VER_SUITE_PERSONAL),
"Windows 2000"
);
assert_eq!(
winos_name(5, 1, 2600, VER_NT_WORKSTATION, VER_SUITE_PERSONAL),
"Windows XP"
);
assert_eq!(
winos_name(5, 2, 3790, VER_NT_WORKSTATION, VER_SUITE_PERSONAL),
"Windows XP Professional x64 Edition"
);
assert_eq!(
winos_name(5, 2, 3790, VER_NT_SERVER, VER_SUITE_WH_SERVER),
"Windows Home Server"
);
assert_eq!(
winos_name(5, 2, 3790, VER_NT_SERVER, VER_SUITE_SMALLBUSINESS),
"Windows Server 2003"
);
assert_eq!(
winos_name(5, 2, 3790, VER_NT_SERVER, VER_SUITE_SMALLBUSINESS),
"Windows Server 2003"
);
assert_eq!(
winos_name(6, 0, 6000, VER_NT_WORKSTATION, VER_SUITE_PERSONAL),
"Windows Vista"
);
assert_eq!(
winos_name(6, 0, 6001, VER_NT_SERVER, VER_SUITE_SMALLBUSINESS),
"Windows Server 2008"
);
assert_eq!(
winos_name(6, 1, 7600, VER_NT_WORKSTATION, VER_SUITE_PERSONAL),
"Windows 7"
);
assert_eq!(
winos_name(6, 1, 7600, VER_NT_SERVER, VER_SUITE_SMALLBUSINESS),
"Windows Server 2008 R2"
);
assert_eq!(
winos_name(6, 2, 9200, VER_NT_SERVER, VER_SUITE_SMALLBUSINESS),
"Windows Server 2012"
);
assert_eq!(
winos_name(6, 2, 9200, VER_NT_WORKSTATION, VER_SUITE_PERSONAL),
"Windows 8"
);
assert_eq!(
winos_name(6, 3, 9600, VER_NT_WORKSTATION, VER_SUITE_PERSONAL),
"Windows 8.1"
);
assert_eq!(
winos_name(6, 3, 9600, VER_NT_SERVER, VER_SUITE_SMALLBUSINESS),
"Windows Server 2012 R2"
);
assert_eq!(
winos_name(10, 0, 10240, VER_NT_WORKSTATION, VER_SUITE_PERSONAL),
"Windows 10"
);
assert_eq!(
winos_name(10, 0, 17134, VER_NT_WORKSTATION, VER_SUITE_PERSONAL),
"Windows 10"
);
assert_eq!(
winos_name(10, 0, 19141, VER_NT_WORKSTATION, VER_SUITE_PERSONAL),
"Windows 10"
);
assert_eq!(
winos_name(10, 0, 19145, VER_NT_WORKSTATION, VER_SUITE_PERSONAL),
"Windows 10"
);
assert_eq!(
winos_name(10, 0, 14393, VER_NT_SERVER, VER_SUITE_SMALLBUSINESS),
"Windows Server 2016"
);
assert_eq!(
winos_name(10, 0, 17763, VER_NT_SERVER, VER_SUITE_SMALLBUSINESS),
"Windows Server 2019"
);
assert_eq!(
winos_name(10, 0, 20348, VER_NT_SERVER, VER_SUITE_SMALLBUSINESS),
"Windows Server 2022"
);
assert_eq!(
winos_name(10, 0, 22000, VER_NT_WORKSTATION, VER_SUITE_PERSONAL),
"Windows 11"
);
assert_eq!(
winos_name(10, 0, 22621, VER_NT_WORKSTATION, VER_SUITE_PERSONAL),
"Windows 11"
);
assert_eq!(
winos_name(5, 9, 3790, VER_NT_WORKSTATION, VER_SUITE_PERSONAL),
"Windows 5.9"
);
assert_eq!(
winos_name(5, 9, 3790, VER_NT_SERVER, VER_SUITE_SMALLBUSINESS),
"Windows Server 5.9"
);
assert_eq!(
winos_name(6, 9, 9600, VER_NT_WORKSTATION, VER_SUITE_PERSONAL),
"Windows 6.9"
);
assert_eq!(
winos_name(6, 9, 9600, VER_NT_SERVER, VER_SUITE_SMALLBUSINESS),
"Windows Server 6.9"
);
}
#[test]
fn test_processor() {
let info = PlatformInfo::new().unwrap();
let processor = info.processor().to_string_lossy();
assert!(!processor.is_empty());
#[cfg(target_arch = "x86_64")]
assert_eq!(processor, "x86_64", "Windows x86_64 should pass through");
#[cfg(target_arch = "x86")]
assert_eq!(processor, "i686", "Windows x86 should normalize to i686");
#[cfg(target_arch = "aarch64")]
assert_eq!(
processor, "aarch64",
"Windows ARM64 should pass through as aarch64"
);
println!("Windows processor=[{}]'{}'", processor.len(), processor);
}
#[test]
fn structure_clone() {
let info = PlatformInfo::new().unwrap();
println!("{:?}", info);
#[allow(clippy::redundant_clone)] let info_copy = info.clone();
assert_eq!(info_copy, info);
let mmbr = MmbrVersion {
major: 1,
minor: 2,
build: 3,
release: 4,
};
println!("{:?}", mmbr);
#[allow(clippy::redundant_clone)] let mmbr_copy = mmbr.clone();
assert_eq!(mmbr_copy, mmbr);
let fvi = WinApiFileVersionInfo {
data: vec![1, 2, 3, 4],
};
println!("{:?}", fvi);
#[allow(clippy::redundant_clone)] let fvi_copy = fvi.clone();
assert_eq!(fvi_copy, fvi);
}