From 8e22c399df2948b0a736572b2eb5db1b5e701864 Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Thu, 13 Mar 2025 19:38:35 -0700 Subject: [PATCH] partially fix sys.getwindowsversion() (#5595) --- extra_tests/snippets/stdlib_sys.py | 6 +- vm/src/stdlib/sys.rs | 96 +++++++++++++++++++++++++++--- 2 files changed, 91 insertions(+), 11 deletions(-) diff --git a/extra_tests/snippets/stdlib_sys.py b/extra_tests/snippets/stdlib_sys.py index 5d8859ac8..d4a2d2cdc 100644 --- a/extra_tests/snippets/stdlib_sys.py +++ b/extra_tests/snippets/stdlib_sys.py @@ -81,7 +81,7 @@ if sys.platform.startswith("win"): 0x00000100 | 0x00000001 | 0x00000020 | 0x00002000 | 0x00000010 | 0x00008000 | 0x00020000 # We really can't test if the results are correct, so it just checks for meaningful value - assert winver.major > 0 + assert winver.major > 6 assert winver.minor >= 0 assert winver.build > 0 assert winver.platform == 2 @@ -91,8 +91,8 @@ if sys.platform.startswith("win"): # XXX if platform_version is implemented correctly, this'll break on compatiblity mode or a build without manifest # these fields can mismatch in CPython - # assert winver.major == winver.platform_version[0] - # assert winver.minor == winver.platform_version[1] + assert winver.major == winver.platform_version[0] + assert winver.minor == winver.platform_version[1] # assert winver.build == winver.platform_version[2] # test int_max_str_digits getter and setter diff --git a/vm/src/stdlib/sys.rs b/vm/src/stdlib/sys.rs index dfaab20f2..39c803a01 100644 --- a/vm/src/stdlib/sys.rs +++ b/vm/src/stdlib/sys.rs @@ -22,12 +22,23 @@ mod sys { vm::{Settings, VirtualMachine}, }; use num_traits::ToPrimitive; + #[cfg(windows)] + use std::os::windows::ffi::OsStrExt; use std::{ env::{self, VarError}, path, sync::atomic::Ordering, }; + #[cfg(windows)] + use windows_sys::Win32::{ + Foundation::MAX_PATH, + Storage::FileSystem::{ + GetFileVersionInfoSizeW, GetFileVersionInfoW, VS_FIXEDFILEINFO, VerQueryValueW, + }, + System::LibraryLoader::{GetModuleFileNameW, GetModuleHandleW}, + }; + // not the same as CPython (e.g. rust's x86_x64-unknown-linux-gnu is just x86_64-linux-gnu) // but hopefully that's just an implementation detail? TODO: copy CPython's multiarch exactly, // https://github.com/python/cpython/blob/3.8/configure.ac#L725 @@ -485,6 +496,78 @@ mod sys { vm.trace_func.borrow().clone() } + #[cfg(windows)] + fn get_kernel32_version() -> std::io::Result<(u32, u32, u32)> { + unsafe { + // Create a wide string for "kernel32.dll" + let module_name: Vec = std::ffi::OsStr::new("kernel32.dll") + .encode_wide() + .chain(Some(0)) + .collect(); + let h_kernel32 = GetModuleHandleW(module_name.as_ptr()); + if h_kernel32.is_null() { + return Err(std::io::Error::last_os_error()); + } + + // Prepare a buffer for the module file path + let mut kernel32_path = [0u16; MAX_PATH as usize]; + let len = GetModuleFileNameW( + h_kernel32, + kernel32_path.as_mut_ptr(), + kernel32_path.len() as u32, + ); + if len == 0 { + return Err(std::io::Error::last_os_error()); + } + + // Get the size of the version information block + let verblock_size = + GetFileVersionInfoSizeW(kernel32_path.as_ptr(), std::ptr::null_mut()); + if verblock_size == 0 { + return Err(std::io::Error::last_os_error()); + } + + // Allocate a buffer to hold the version information + let mut verblock = vec![0u8; verblock_size as usize]; + if GetFileVersionInfoW( + kernel32_path.as_ptr(), + 0, + verblock_size, + verblock.as_mut_ptr() as *mut _, + ) == 0 + { + return Err(std::io::Error::last_os_error()); + } + + // Prepare an empty sub-block string (L"") as required by VerQueryValueW + let sub_block: Vec = std::ffi::OsStr::new("") + .encode_wide() + .chain(Some(0)) + .collect(); + + let mut ffi_ptr: *mut VS_FIXEDFILEINFO = std::ptr::null_mut(); + let mut ffi_len: u32 = 0; + if VerQueryValueW( + verblock.as_ptr() as *const _, + sub_block.as_ptr(), + &mut ffi_ptr as *mut *mut VS_FIXEDFILEINFO as *mut *mut _, + &mut ffi_len as *mut u32, + ) == 0 + || ffi_ptr.is_null() + { + return Err(std::io::Error::last_os_error()); + } + + // Extract the version numbers from the VS_FIXEDFILEINFO structure. + let ffi = *ffi_ptr; + let real_major = (ffi.dwProductVersionMS >> 16) & 0xFFFF; + let real_minor = ffi.dwProductVersionMS & 0xFFFF; + let real_build = (ffi.dwProductVersionLS >> 16) & 0xFFFF; + + Ok((real_major, real_minor, real_build)) + } + } + #[cfg(windows)] #[pyfunction] fn getwindowsversion(vm: &VirtualMachine) -> PyResult { @@ -519,21 +602,18 @@ mod sys { sp.into_string() .map_err(|_| vm.new_os_error("service pack is not ASCII".to_owned()))? }; + let real_version = get_kernel32_version().map_err(|e| vm.new_os_error(e.to_string()))?; Ok(WindowsVersion { - major: version.dwMajorVersion, - minor: version.dwMinorVersion, - build: version.dwBuildNumber, + major: real_version.0, + minor: real_version.1, + build: real_version.2, platform: version.dwPlatformId, service_pack, service_pack_major: version.wServicePackMajor, service_pack_minor: version.wServicePackMinor, suite_mask: version.wSuiteMask, product_type: version.wProductType, - platform_version: ( - version.dwMajorVersion, - version.dwMinorVersion, - version.dwBuildNumber, - ), // TODO Provide accurate version, like CPython impl + platform_version: (real_version.0, real_version.1, real_version.2), // TODO Provide accurate version, like CPython impl } .into_struct_sequence(vm)) }