]> git.proxmox.com Git - cargo.git/blob - vendor/os_info/src/windows/winapi.rs
New upstream version 0.63.1
[cargo.git] / vendor / os_info / src / windows / winapi.rs
1 // spell-checker:ignore dword, minwindef, ntdef, ntdll, ntstatus, osversioninfoex, osversioninfoexa
2 // spell-checker:ignore osversioninfoexw, serverr, sysinfoapi, winnt, winuser, pbool, libloaderapi
3 // spell-checker:ignore lpcstr, processthreadsapi, farproc, lstatus, wchar, lpbyte, hkey, winerror
4 // spell-checker:ignore osstr, winreg
5
6 #![allow(unsafe_code)]
7
8 use std::{
9 ffi::{OsStr, OsString},
10 mem,
11 os::windows::ffi::{OsStrExt, OsStringExt},
12 ptr,
13 };
14
15 use winapi::{
16 shared::{
17 minwindef::{DWORD, FARPROC, LPBYTE},
18 ntdef::{LPCSTR, NTSTATUS},
19 ntstatus::STATUS_SUCCESS,
20 winerror::ERROR_SUCCESS,
21 },
22 um::{
23 libloaderapi::{GetModuleHandleA, GetProcAddress},
24 sysinfoapi::{GetSystemInfo, SYSTEM_INFO},
25 winnt::{
26 KEY_READ, PROCESSOR_ARCHITECTURE_AMD64, REG_SZ, VER_NT_WORKSTATION,
27 VER_SUITE_WH_SERVER, WCHAR,
28 },
29 winreg::{RegOpenKeyExW, RegQueryValueExW, HKEY_LOCAL_MACHINE, LSTATUS},
30 winuser::{GetSystemMetrics, SM_SERVERR2},
31 },
32 };
33
34 use crate::{Bitness, Info, Type, Version};
35
36 #[cfg(target_arch = "x86")]
37 type OSVERSIONINFOEX = winapi::um::winnt::OSVERSIONINFOEXA;
38
39 #[cfg(not(target_arch = "x86"))]
40 type OSVERSIONINFOEX = winapi::um::winnt::OSVERSIONINFOEXW;
41
42 pub fn get() -> Info {
43 let (version, edition) = version();
44 Info {
45 os_type: Type::Windows,
46 version,
47 edition,
48 bitness: bitness(),
49 ..Default::default()
50 }
51 }
52
53 fn version() -> (Version, Option<String>) {
54 match version_info() {
55 None => (Version::Unknown, None),
56 Some(v) => (
57 Version::Semantic(
58 v.dwMajorVersion as u64,
59 v.dwMinorVersion as u64,
60 v.dwBuildNumber as u64,
61 ),
62 product_name(&v).or_else(|| edition(&v)),
63 ),
64 }
65 }
66
67 #[cfg(target_pointer_width = "64")]
68 fn bitness() -> Bitness {
69 // x64 program can only run on x64 Windows.
70 Bitness::X64
71 }
72
73 #[cfg(target_pointer_width = "32")]
74 fn bitness() -> Bitness {
75 use winapi::{
76 shared::{
77 minwindef::{BOOL, FALSE, PBOOL},
78 ntdef::HANDLE,
79 },
80 um::processthreadsapi::GetCurrentProcess,
81 };
82
83 // IsWow64Process is not available on all supported versions of Windows. Use GetModuleHandle to
84 // get a handle to the DLL that contains the function and GetProcAddress to get a pointer to the
85 // function if available.
86 let is_wow_64 = match get_proc_address(b"kernel32\0", b"IsWow64Process\0") {
87 None => return Bitness::Unknown,
88 Some(val) => val,
89 };
90
91 type IsWow64 = unsafe extern "system" fn(HANDLE, PBOOL) -> BOOL;
92 let is_wow_64: IsWow64 = unsafe { mem::transmute(is_wow_64) };
93
94 let mut result = FALSE;
95 if unsafe { is_wow_64(GetCurrentProcess(), &mut result) } == 0 {
96 log::error!("IsWow64Process failed");
97 return Bitness::Unknown;
98 }
99
100 if result == FALSE {
101 Bitness::X32
102 } else {
103 Bitness::X64
104 }
105 }
106
107 // Calls the Win32 API function RtlGetVersion to get the OS version information:
108 // https://msdn.microsoft.com/en-us/library/mt723418(v=vs.85).aspx
109 fn version_info() -> Option<OSVERSIONINFOEX> {
110 let rtl_get_version = match get_proc_address(b"ntdll\0", b"RtlGetVersion\0") {
111 None => return None,
112 Some(val) => val,
113 };
114
115 type RtlGetVersion = unsafe extern "system" fn(&mut OSVERSIONINFOEX) -> NTSTATUS;
116 let rtl_get_version: RtlGetVersion = unsafe { mem::transmute(rtl_get_version) };
117
118 let mut info: OSVERSIONINFOEX = unsafe { mem::zeroed() };
119 info.dwOSVersionInfoSize = mem::size_of::<OSVERSIONINFOEX>() as DWORD;
120
121 if unsafe { rtl_get_version(&mut info) } == STATUS_SUCCESS {
122 Some(info)
123 } else {
124 None
125 }
126 }
127
128 fn product_name(info: &OSVERSIONINFOEX) -> Option<String> {
129 const REG_SUCCESS: LSTATUS = ERROR_SUCCESS as LSTATUS;
130
131 let sub_key = to_wide("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion");
132 let mut key = ptr::null_mut();
133 if unsafe { RegOpenKeyExW(HKEY_LOCAL_MACHINE, sub_key.as_ptr(), 0, KEY_READ, &mut key) }
134 != REG_SUCCESS
135 || key.is_null()
136 {
137 log::error!("RegOpenKeyExW(HKEY_LOCAL_MACHINE, ...) failed");
138 return None;
139 }
140
141 let is_win_11 = info.dwMajorVersion == 10 && info.dwBuildNumber >= 22000;
142
143 // Get size of the data.
144 let name = to_wide(if is_win_11 {
145 "EditionID"
146 } else {
147 "ProductName"
148 });
149 let mut data_type: DWORD = 0;
150 let mut data_size: DWORD = 0;
151 if unsafe {
152 RegQueryValueExW(
153 key,
154 name.as_ptr(),
155 ptr::null_mut(),
156 &mut data_type,
157 ptr::null_mut(),
158 &mut data_size,
159 )
160 } != REG_SUCCESS
161 || data_type != REG_SZ
162 || data_size == 0
163 || data_size % 2 != 0
164 {
165 log::error!("RegQueryValueExW failed");
166 return None;
167 }
168
169 // Get the data.
170 let mut data = vec![0u16; data_size as usize / 2];
171 if unsafe {
172 RegQueryValueExW(
173 key,
174 name.as_ptr(),
175 ptr::null_mut(),
176 ptr::null_mut(),
177 data.as_mut_ptr() as LPBYTE,
178 &mut data_size,
179 )
180 } != REG_SUCCESS
181 || data_size as usize != data.len() * 2
182 {
183 return None;
184 }
185
186 // If the data has the REG_SZ, REG_MULTI_SZ or REG_EXPAND_SZ type, the string may not have been
187 // stored with the proper terminating null characters.
188 match data.last() {
189 Some(0) => {
190 data.pop();
191 }
192 _ => {}
193 }
194
195 let value = OsString::from_wide(data.as_slice())
196 .to_string_lossy()
197 .into_owned();
198
199 if is_win_11 {
200 Some(format!("Windows 11 {}", value))
201 } else {
202 Some(value)
203 }
204 }
205
206 fn to_wide(value: &str) -> Vec<WCHAR> {
207 OsStr::new(value).encode_wide().chain(Some(0)).collect()
208 }
209
210 // Examines data in the OSVERSIONINFOEX structure to determine the Windows edition:
211 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724833(v=vs.85).aspx
212 fn edition(version_info: &OSVERSIONINFOEX) -> Option<String> {
213 match (
214 version_info.dwMajorVersion,
215 version_info.dwMinorVersion,
216 version_info.wProductType,
217 ) {
218 // Windows 10.
219 (10, 0, VER_NT_WORKSTATION) => {
220 if version_info.dwBuildNumber >= 22000 {
221 Some("Windows 11")
222 } else {
223 Some("Windows 10")
224 }
225 }
226 (10, 0, _) => Some("Windows Server 2016"),
227 // Windows Vista, 7, 8 and 8.1.
228 (6, 3, VER_NT_WORKSTATION) => Some("Windows 8.1"),
229 (6, 3, _) => Some("Windows Server 2012 R2"),
230 (6, 2, VER_NT_WORKSTATION) => Some("Windows 8"),
231 (6, 2, _) => Some("Windows Server 2012"),
232 (6, 1, VER_NT_WORKSTATION) => Some("Windows 7"),
233 (6, 1, _) => Some("Windows Server 2008 R2"),
234 (6, 0, VER_NT_WORKSTATION) => Some("Windows Vista"),
235 (6, 0, _) => Some("Windows Server 2008"),
236 // Windows 2000, Home Server, 2003 Server, 2003 R2 Server, XP and XP Professional x64.
237 (5, 1, _) => Some("Windows XP"),
238 (5, 0, _) => Some("Windows 2000"),
239 (5, 2, _) if unsafe { GetSystemMetrics(SM_SERVERR2) } == 0 => {
240 let mut info: SYSTEM_INFO = unsafe { mem::zeroed() };
241 unsafe { GetSystemInfo(&mut info) };
242
243 if Into::<DWORD>::into(version_info.wSuiteMask) & VER_SUITE_WH_SERVER
244 == VER_SUITE_WH_SERVER
245 {
246 Some("Windows Home Server")
247 } else if version_info.wProductType == VER_NT_WORKSTATION
248 && unsafe { info.u.s().wProcessorArchitecture } == PROCESSOR_ARCHITECTURE_AMD64
249 {
250 Some("Windows XP Professional x64 Edition")
251 } else {
252 Some("Windows Server 2003")
253 }
254 }
255 _ => None,
256 }
257 .map(str::to_string)
258 }
259
260 fn get_proc_address(module: &[u8], proc: &[u8]) -> Option<FARPROC> {
261 assert!(
262 *module.last().expect("Empty module name") == 0,
263 "Module name should be zero-terminated"
264 );
265 assert!(
266 *proc.last().expect("Empty procedure name") == 0,
267 "Procedure name should be zero-terminated"
268 );
269
270 let handle = unsafe { GetModuleHandleA(module.as_ptr() as LPCSTR) };
271 if handle.is_null() {
272 log::error!(
273 "GetModuleHandleA({}) failed",
274 String::from_utf8_lossy(module)
275 );
276 return None;
277 }
278
279 unsafe { Some(GetProcAddress(handle, proc.as_ptr() as LPCSTR)) }
280 }
281
282 #[cfg(test)]
283 mod tests {
284 use super::*;
285 use pretty_assertions::{assert_eq, assert_ne};
286
287 #[test]
288 fn version() {
289 let info = get();
290 assert_eq!(Type::Windows, info.os_type());
291 }
292
293 #[test]
294 fn get_version_info() {
295 let version = version_info();
296 assert!(version.is_some());
297 }
298
299 #[test]
300 fn get_edition() {
301 let test_data = [
302 (10, 0, 0, "Windows Server 2016"),
303 (6, 3, VER_NT_WORKSTATION, "Windows 8.1"),
304 (6, 3, 0, "Windows Server 2012 R2"),
305 (6, 2, VER_NT_WORKSTATION, "Windows 8"),
306 (6, 2, 0, "Windows Server 2012"),
307 (6, 1, VER_NT_WORKSTATION, "Windows 7"),
308 (6, 1, 0, "Windows Server 2008 R2"),
309 (6, 0, VER_NT_WORKSTATION, "Windows Vista"),
310 (6, 0, 0, "Windows Server 2008"),
311 (5, 1, 0, "Windows XP"),
312 (5, 1, 1, "Windows XP"),
313 (5, 1, 100, "Windows XP"),
314 (5, 0, 0, "Windows 2000"),
315 (5, 0, 1, "Windows 2000"),
316 (5, 0, 100, "Windows 2000"),
317 ];
318
319 let mut info = version_info().unwrap();
320
321 for &(major, minor, product_type, expected_edition) in &test_data {
322 info.dwMajorVersion = major;
323 info.dwMinorVersion = minor;
324 info.wProductType = product_type;
325
326 let edition = edition(&info).unwrap();
327 assert_eq!(edition, expected_edition);
328 }
329 }
330
331 #[test]
332 fn get_bitness() {
333 let b = bitness();
334 assert_ne!(b, Bitness::Unknown);
335 }
336
337 #[test]
338 #[should_panic(expected = "Empty module name")]
339 fn empty_module_name() {
340 get_proc_address(b"", b"RtlGetVersion\0");
341 }
342
343 #[test]
344 #[should_panic(expected = "Module name should be zero-terminated")]
345 fn non_zero_terminated_module_name() {
346 get_proc_address(b"ntdll", b"RtlGetVersion\0");
347 }
348
349 #[test]
350 #[should_panic(expected = "Empty procedure name")]
351 fn empty_proc_name() {
352 get_proc_address(b"ntdll\0", b"");
353 }
354
355 #[test]
356 #[should_panic(expected = "Procedure name should be zero-terminated")]
357 fn non_zero_terminated_proc_name() {
358 get_proc_address(b"ntdll\0", b"RtlGetVersion");
359 }
360
361 #[test]
362 fn proc_address() {
363 let address = get_proc_address(b"ntdll\0", b"RtlGetVersion\0");
364 assert!(address.is_some());
365 }
366
367 #[test]
368 fn get_product_name() {
369 let version = version_info().expect("version_info() failed");
370 let edition = product_name(&version).expect("edition() failed");
371 assert!(!edition.is_empty());
372 }
373
374 #[test]
375 fn to_wide_str() {
376 let data = [
377 ("", [0x0000].as_ref()),
378 ("U", &[0x0055, 0x0000]),
379 ("你好", &[0x4F60, 0x597D, 0x0000]),
380 ];
381
382 for (s, expected) in &data {
383 let wide = to_wide(s);
384 assert_eq!(&wide, expected);
385 }
386 }
387 }