Issue #19143: platform module now reads Windows version from kernel32.dll to avoid compatibility shims.

This commit is contained in:
Steve Dower 2015-09-22 17:24:01 -07:00
commit a0c3c19665
2 changed files with 123 additions and 168 deletions

View File

@ -26,12 +26,14 @@
# Betancourt, Randall Hopper, Karl Putland, John Farrell, Greg # Betancourt, Randall Hopper, Karl Putland, John Farrell, Greg
# Andruk, Just van Rossum, Thomas Heller, Mark R. Levinson, Mark # Andruk, Just van Rossum, Thomas Heller, Mark R. Levinson, Mark
# Hammond, Bill Tutt, Hans Nowak, Uwe Zessin (OpenVMS support), # 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: # History:
# #
# <see CVS and SVN checkin messages for 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.7 - added DEV_NULL
# 1.0.6 - added linux_distribution() # 1.0.6 - added linux_distribution()
# 1.0.5 - fixed Java support to allow running the module on Jython # 1.0.5 - fixed Java support to allow running the module on Jython
@ -469,188 +471,138 @@ def _syscmd_ver(system='', release='', version='',
version = _norm_version(version) version = _norm_version(version)
return system, release, 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",
}
""" # Server release name lookup will default to client names if necessary
try: _WIN32_SERVER_RELEASES = {
# Use win32api if available (5, 2): "2003Server",
from win32api import RegQueryValueEx
except ImportError: (6, 0): "2008Server",
# On Python 2.0 and later, emulate using winreg (6, 1): "2008ServerR2",
import winreg (6, 2): "2012Server",
RegQueryValueEx = winreg.QueryValueEx (6, 3): "2012ServerR2",
try: (6, None): "post2012ServerR2",
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=''): def win32_ver(release='', version='', csd='', ptype=''):
from sys import getwindowsversion
""" 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).
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.
"""
# 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: try:
from win32api import RegQueryValueEx, RegOpenKeyEx, \ from winreg import OpenKeyEx, QueryValueEx, CloseKey, HKEY_LOCAL_MACHINE
RegCloseKey, GetVersionEx
from win32con import HKEY_LOCAL_MACHINE, VER_PLATFORM_WIN32_NT, \
VER_PLATFORM_WIN32_WINDOWS, VER_NT_WORKSTATION
except ImportError: except ImportError:
# Emulate the win32api module using Python APIs from _winreg import OpenKeyEx, QueryValueEx, CloseKey, HKEY_LOCAL_MACHINE
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
# Find out the registry key and some general version infos winver = getwindowsversion()
winver = GetVersionEx() maj, min, build = _get_real_winver(*winver[:3])
maj, min, buildno, plat, csd = winver version = '{0}.{1}.{2}'.format(maj, min, build)
version = '%i.%i.%i' % (maj, min, buildno & 0xFFFF)
if hasattr(winver, "service_pack"): release = (_WIN32_CLIENT_RELEASES.get((maj, min)) or
if winver.service_pack != "": _WIN32_CLIENT_RELEASES.get((maj, None)) or
csd = 'SP%s' % winver.service_pack_major release)
else:
# 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 ': if csd[:13] == 'Service Pack ':
csd = 'SP' + csd[13:] csd = 'SP' + csd[13:]
if plat == VER_PLATFORM_WIN32_WINDOWS: # VER_NT_SERVER = 3
regkey = 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion' if getattr(winver, 'product_type', None) == 3:
# Try to guess the release name release = (_WIN32_SERVER_RELEASES.get((maj, min)) or
if maj == 4: _WIN32_SERVER_RELEASES.get((maj, None)) or
if min == 0: release)
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: key = None
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: try:
key = RegOpenKeyEx(HKEY_LOCAL_MACHINE, regkey) key = OpenKeyEx(HKEY_LOCAL_MACHINE,
name, type = RegQueryValueEx(key, "ProductName") r'SOFTWARE\Microsoft\Windows NT\CurrentVersion')
# Discard any type that isn't REG_SZ ptype = QueryValueEx(key, 'CurrentType')[0]
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: except:
pass
finally:
if key:
CloseKey(key)
return release, version, csd, ptype 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(): def _mac_ver_xml():
fn = '/System/Library/CoreServices/SystemVersion.plist' fn = '/System/Library/CoreServices/SystemVersion.plist'

View File

@ -138,6 +138,9 @@ Core and Builtins
Library 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 - Issue #25092: Fix datetime.strftime() failure when errno was already set to
EINVAL. EINVAL.