[Python-checkins] cpython (merge 3.5 -> default): Issue #19143: platform module now reads Windows version from kernel32.dll to

steve.dower python-checkins at python.org
Wed Sep 23 02:27:07 CEST 2015


https://hg.python.org/cpython/rev/2f55d73e5ad6
changeset:   98183:2f55d73e5ad6
parent:      98180:ff1c6e9eeed4
parent:      98182:fa869ccf9368
user:        Steve Dower <steve.dower at microsoft.com>
date:        Tue Sep 22 17:24:01 2015 -0700
summary:
  Issue #19143: platform module now reads Windows version from kernel32.dll to avoid compatibility shims.

files:
  Lib/platform.py |  292 +++++++++++++++--------------------
  Misc/NEWS       |    3 +
  2 files changed, 125 insertions(+), 170 deletions(-)


diff --git a/Lib/platform.py b/Lib/platform.py
--- 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:
 #
 #    <see CVS and SVN checkin messages for 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,188 +471,138 @@
         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",
 
-    """ Read a value for name from the registry key.
+    (6, 0): "Vista",
+    (6, 1): "7",
+    (6, 2): "8",
+    (6, 3): "8.1",
+    (6, None): "post8.1",
 
-        In case this fails, default is returned.
+    (10, 0): "10",
+    (10, None): "post10",
+}
 
-    """
-    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
+# Server release name lookup will default to client names if necessary
+_WIN32_SERVER_RELEASES = {
+    (5, 2): "2003Server",
+
+    (6, 0): "2008Server",
+    (6, 1): "2008ServerR2",
+    (6, 2): "2012Server",
+    (6, 3): "2012ServerR2",
+    (6, None): "post2012ServerR2",
+}
+
+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).
+    winver = getwindowsversion()
+    maj, min, build = _get_real_winver(*winver[:3])
+    version = '{0}.{1}.{2}'.format(maj, min, build)
 
-        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).
+    release = (_WIN32_CLIENT_RELEASES.get((maj, min)) or
+               _WIN32_CLIENT_RELEASES.get((maj, None)) or
+               release)
 
-        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.
+    # 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:
+            csd = 'SP{}'.format(winver.service_pack_major)
+        except AttributeError:
+            if csd[:13] == 'Service Pack ':
+                csd = 'SP' + csd[13:]
 
-    """
-    # 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
+    # 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)
 
-    # Import the needed APIs
+    key = None
     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
-        try:
-            sys.getwindowsversion
-        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
+        key = OpenKeyEx(HKEY_LOCAL_MACHINE,
+                        r'SOFTWARE\Microsoft\Windows NT\CurrentVersion')
+        ptype = QueryValueEx(key, 'CurrentType')[0]
+    except:
+        pass
+    finally:
+        if key:
+            CloseKey(key)
 
-    # 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:]
+    return release, version, csd, ptype
 
-    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'
-
-    else:
-        if not release:
-            # E.g. Win3.1 with win32s
-            release = '%i.%i' % (maj, min)
-        return release, version, csd, ptype
-
-    # Open the registry key
-    try:
-        keyCurVer = RegOpenKeyEx(HKEY_LOCAL_MACHINE, regkey)
-        # Get a value to make sure the key exists...
-        RegQueryValueEx(keyCurVer, 'SystemRoot')
-    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)
-    return release, version, csd, ptype
 
 def _mac_ver_xml():
     fn = '/System/Library/CoreServices/SystemVersion.plist'
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -138,6 +138,9 @@
 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.
 

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list