From b9f4feab1b9c9ffa8ea29af3d82bc536f9f3005a Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Tue, 22 Sep 2015 17:23:39 -0700 Subject: [PATCH] Issue #19143: platform module now reads Windows version from kernel32.dll to avoid compatibility shims. --- Lib/platform.py | 288 ++++++++++++++++++++---------------------------- Misc/NEWS | 3 + 2 files changed, 123 insertions(+), 168 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py index 9096696a7e..ecc9fb3a21 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -26,12 +26,14 @@ # Betancourt, Randall Hopper, Karl Putland, John Farrell, Greg # Andruk, Just van Rossum, Thomas Heller, Mark R. Levinson, Mark # Hammond, Bill Tutt, Hans Nowak, Uwe Zessin (OpenVMS support), -# Colin Kong, Trent Mick, Guido van Rossum, Anthony Baxter +# Colin Kong, Trent Mick, Guido van Rossum, Anthony Baxter, Steve +# Dower # # History: # # # +# 1.0.8 - changed Windows support to read version from kernel32.dll # 1.0.7 - added DEV_NULL # 1.0.6 - added linux_distribution() # 1.0.5 - fixed Java support to allow running the module on Jython @@ -469,189 +471,139 @@ def _syscmd_ver(system='', release='', version='', version = _norm_version(version) return system, release, version -def _win32_getvalue(key, name, default=''): +_WIN32_CLIENT_RELEASES = { + (5, 0): "2000", + (5, 1): "XP", + # Strictly, 5.2 client is XP 64-bit, but platform.py historically + # has always called it 2003 Server + (5, 2): "2003Server", + (5, None): "post2003", + + (6, 0): "Vista", + (6, 1): "7", + (6, 2): "8", + (6, 3): "8.1", + (6, None): "post8.1", + + (10, 0): "10", + (10, None): "post10", +} - """ Read a value for name from the registry key. +# Server release name lookup will default to client names if necessary +_WIN32_SERVER_RELEASES = { + (5, 2): "2003Server", - In case this fails, default is returned. + (6, 0): "2008Server", + (6, 1): "2008ServerR2", + (6, 2): "2012Server", + (6, 3): "2012ServerR2", + (6, None): "post2012ServerR2", +} - """ - try: - # Use win32api if available - from win32api import RegQueryValueEx - except ImportError: - # On Python 2.0 and later, emulate using winreg - import winreg - RegQueryValueEx = winreg.QueryValueEx - try: - return RegQueryValueEx(key, name) - except: - return default +def _get_real_winver(maj, min, build): + if maj < 6 or (maj == 6 and min < 2): + return maj, min, build + + from ctypes import (c_buffer, POINTER, byref, create_unicode_buffer, + Structure, WinDLL) + from ctypes.wintypes import DWORD, HANDLE + + class VS_FIXEDFILEINFO(Structure): + _fields_ = [ + ("dwSignature", DWORD), + ("dwStrucVersion", DWORD), + ("dwFileVersionMS", DWORD), + ("dwFileVersionLS", DWORD), + ("dwProductVersionMS", DWORD), + ("dwProductVersionLS", DWORD), + ("dwFileFlagsMask", DWORD), + ("dwFileFlags", DWORD), + ("dwFileOS", DWORD), + ("dwFileType", DWORD), + ("dwFileSubtype", DWORD), + ("dwFileDateMS", DWORD), + ("dwFileDateLS", DWORD), + ] + + kernel32 = WinDLL('kernel32') + version = WinDLL('version') + + # We will immediately double the length up to MAX_PATH, but the + # path may be longer, so we retry until the returned string is + # shorter than our buffer. + name_len = actual_len = 130 + while actual_len == name_len: + name_len *= 2 + name = create_unicode_buffer(name_len) + actual_len = kernel32.GetModuleFileNameW(HANDLE(kernel32._handle), + name, len(name)) + if not actual_len: + return maj, min, build + + size = version.GetFileVersionInfoSizeW(name, None) + if not size: + return maj, min, build + + ver_block = c_buffer(size) + if (not version.GetFileVersionInfoW(name, None, size, ver_block) or + not ver_block): + return maj, min, build + + pvi = POINTER(VS_FIXEDFILEINFO)() + if not version.VerQueryValueW(ver_block, "", byref(pvi), byref(DWORD())): + return maj, min, build + + maj = pvi.contents.dwProductVersionMS >> 16 + min = pvi.contents.dwProductVersionMS & 0xFFFF + build = pvi.contents.dwProductVersionLS >> 16 + + return maj, min, build def win32_ver(release='', version='', csd='', ptype=''): + from sys import getwindowsversion + try: + from winreg import OpenKeyEx, QueryValueEx, CloseKey, HKEY_LOCAL_MACHINE + except ImportError: + from _winreg import OpenKeyEx, QueryValueEx, CloseKey, HKEY_LOCAL_MACHINE - """ Get additional version information from the Windows Registry - and return a tuple (version, csd, ptype) referring to version - number, CSD level (service pack), and OS type (multi/single - processor). - - As a hint: ptype returns 'Uniprocessor Free' on single - processor NT machines and 'Multiprocessor Free' on multi - processor machines. The 'Free' refers to the OS version being - free of debugging code. It could also state 'Checked' which - means the OS version uses debugging code, i.e. code that - checks arguments, ranges, etc. (Thomas Heller). + winver = getwindowsversion() + maj, min, build = _get_real_winver(*winver[:3]) + version = '{0}.{1}.{2}'.format(maj, min, build) - Note: this function works best with Mark Hammond's win32 - package installed, but also on Python 2.3 and later. It - obviously only runs on Win32 compatible platforms. + release = (_WIN32_CLIENT_RELEASES.get((maj, min)) or + _WIN32_CLIENT_RELEASES.get((maj, None)) or + release) - """ - # XXX Is there any way to find out the processor type on WinXX ? - # XXX Is win32 available on Windows CE ? - # - # Adapted from code posted by Karl Putland to comp.lang.python. - # - # The mappings between reg. values and release names can be found - # here: http://msdn.microsoft.com/library/en-us/sysinfo/base/osversioninfo_str.asp - - # Import the needed APIs - try: - from win32api import RegQueryValueEx, RegOpenKeyEx, \ - RegCloseKey, GetVersionEx - from win32con import HKEY_LOCAL_MACHINE, VER_PLATFORM_WIN32_NT, \ - VER_PLATFORM_WIN32_WINDOWS, VER_NT_WORKSTATION - except ImportError: - # Emulate the win32api module using Python APIs + # getwindowsversion() reflect the compatibility mode Python is + # running under, and so the service pack value is only going to be + # valid if the versions match. + if winver[:2] == (maj, min): try: - sys.getwindowsversion + csd = 'SP{}'.format(winver.service_pack_major) except AttributeError: - # No emulation possible, so return the defaults... - return release, version, csd, ptype - else: - # Emulation using winreg (added in Python 2.0) and - # sys.getwindowsversion() (added in Python 2.3) - import winreg - GetVersionEx = sys.getwindowsversion - RegQueryValueEx = winreg.QueryValueEx - RegOpenKeyEx = winreg.OpenKeyEx - RegCloseKey = winreg.CloseKey - HKEY_LOCAL_MACHINE = winreg.HKEY_LOCAL_MACHINE - VER_PLATFORM_WIN32_WINDOWS = 1 - VER_PLATFORM_WIN32_NT = 2 - VER_NT_WORKSTATION = 1 - VER_NT_SERVER = 3 - REG_SZ = 1 - - # Find out the registry key and some general version infos - winver = GetVersionEx() - maj, min, buildno, plat, csd = winver - version = '%i.%i.%i' % (maj, min, buildno & 0xFFFF) - if hasattr(winver, "service_pack"): - if winver.service_pack != "": - csd = 'SP%s' % winver.service_pack_major - else: - if csd[:13] == 'Service Pack ': - csd = 'SP' + csd[13:] - - if plat == VER_PLATFORM_WIN32_WINDOWS: - regkey = 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion' - # Try to guess the release name - if maj == 4: - if min == 0: - release = '95' - elif min == 10: - release = '98' - elif min == 90: - release = 'Me' - else: - release = 'postMe' - elif maj == 5: - release = '2000' - - elif plat == VER_PLATFORM_WIN32_NT: - regkey = 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion' - if maj <= 4: - release = 'NT' - elif maj == 5: - if min == 0: - release = '2000' - elif min == 1: - release = 'XP' - elif min == 2: - release = '2003Server' - else: - release = 'post2003' - elif maj == 6: - if hasattr(winver, "product_type"): - product_type = winver.product_type - else: - product_type = VER_NT_WORKSTATION - # Without an OSVERSIONINFOEX capable sys.getwindowsversion(), - # or help from the registry, we cannot properly identify - # non-workstation versions. - try: - key = RegOpenKeyEx(HKEY_LOCAL_MACHINE, regkey) - name, type = RegQueryValueEx(key, "ProductName") - # Discard any type that isn't REG_SZ - if type == REG_SZ and name.find("Server") != -1: - product_type = VER_NT_SERVER - except OSError: - # Use default of VER_NT_WORKSTATION - pass - - if min == 0: - if product_type == VER_NT_WORKSTATION: - release = 'Vista' - else: - release = '2008Server' - elif min == 1: - if product_type == VER_NT_WORKSTATION: - release = '7' - else: - release = '2008ServerR2' - elif min == 2: - if product_type == VER_NT_WORKSTATION: - release = '8' - else: - release = '2012Server' - else: - release = 'post2012Server' + if csd[:13] == 'Service Pack ': + csd = 'SP' + csd[13:] - else: - if not release: - # E.g. Win3.1 with win32s - release = '%i.%i' % (maj, min) - return release, version, csd, ptype + # VER_NT_SERVER = 3 + if getattr(winver, 'product_type', None) == 3: + release = (_WIN32_SERVER_RELEASES.get((maj, min)) or + _WIN32_SERVER_RELEASES.get((maj, None)) or + release) - # Open the registry key + key = None try: - keyCurVer = RegOpenKeyEx(HKEY_LOCAL_MACHINE, regkey) - # Get a value to make sure the key exists... - RegQueryValueEx(keyCurVer, 'SystemRoot') + key = OpenKeyEx(HKEY_LOCAL_MACHINE, + r'SOFTWARE\Microsoft\Windows NT\CurrentVersion') + ptype = QueryValueEx(key, 'CurrentType')[0] except: - return release, version, csd, ptype - - # Parse values - #subversion = _win32_getvalue(keyCurVer, - # 'SubVersionNumber', - # ('',1))[0] - #if subversion: - # release = release + subversion # 95a, 95b, etc. - build = _win32_getvalue(keyCurVer, - 'CurrentBuildNumber', - ('', 1))[0] - ptype = _win32_getvalue(keyCurVer, - 'CurrentType', - (ptype, 1))[0] - - # Normalize version - version = _norm_version(version, build) - - # Close key - RegCloseKey(keyCurVer) + pass + finally: + if key: + CloseKey(key) + return release, version, csd, ptype + def _mac_ver_xml(): fn = '/System/Library/CoreServices/SystemVersion.plist' if not os.path.exists(fn): diff --git a/Misc/NEWS b/Misc/NEWS index 36cb6e4db3..a2902122a1 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -18,6 +18,9 @@ Core and Builtins Library ------- +- Issue #19143: platform module now reads Windows version from kernel32.dll to + avoid compatibility shims. + - Issue #25092: Fix datetime.strftime() failure when errno was already set to EINVAL. -- 2.40.0