Skip to content

Commit b9f4fea

Browse files
committed
Issue #19143: platform module now reads Windows version from kernel32.dll to avoid compatibility shims.
1 parent a2ea0e4 commit b9f4fea

File tree

2 files changed

+123
-168
lines changed

2 files changed

+123
-168
lines changed

Lib/platform.py

+120-168
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@
2626
# Betancourt, Randall Hopper, Karl Putland, John Farrell, Greg
2727
# Andruk, Just van Rossum, Thomas Heller, Mark R. Levinson, Mark
2828
# Hammond, Bill Tutt, Hans Nowak, Uwe Zessin (OpenVMS support),
29-
# Colin Kong, Trent Mick, Guido van Rossum, Anthony Baxter
29+
# Colin Kong, Trent Mick, Guido van Rossum, Anthony Baxter, Steve
30+
# Dower
3031
#
3132
# History:
3233
#
3334
# <see CVS and SVN checkin messages for history>
3435
#
36+
# 1.0.8 - changed Windows support to read version from kernel32.dll
3537
# 1.0.7 - added DEV_NULL
3638
# 1.0.6 - added linux_distribution()
3739
# 1.0.5 - fixed Java support to allow running the module on Jython
@@ -469,189 +471,139 @@ def _syscmd_ver(system='', release='', version='',
469471
version = _norm_version(version)
470472
return system, release, version
471473

472-
def _win32_getvalue(key, name, default=''):
474+
_WIN32_CLIENT_RELEASES = {
475+
(5, 0): "2000",
476+
(5, 1): "XP",
477+
# Strictly, 5.2 client is XP 64-bit, but platform.py historically
478+
# has always called it 2003 Server
479+
(5, 2): "2003Server",
480+
(5, None): "post2003",
481+
482+
(6, 0): "Vista",
483+
(6, 1): "7",
484+
(6, 2): "8",
485+
(6, 3): "8.1",
486+
(6, None): "post8.1",
487+
488+
(10, 0): "10",
489+
(10, None): "post10",
490+
}
473491

474-
""" Read a value for name from the registry key.
492+
# Server release name lookup will default to client names if necessary
493+
_WIN32_SERVER_RELEASES = {
494+
(5, 2): "2003Server",
475495

476-
In case this fails, default is returned.
496+
(6, 0): "2008Server",
497+
(6, 1): "2008ServerR2",
498+
(6, 2): "2012Server",
499+
(6, 3): "2012ServerR2",
500+
(6, None): "post2012ServerR2",
501+
}
477502

478-
"""
479-
try:
480-
# Use win32api if available
481-
from win32api import RegQueryValueEx
482-
except ImportError:
483-
# On Python 2.0 and later, emulate using winreg
484-
import winreg
485-
RegQueryValueEx = winreg.QueryValueEx
486-
try:
487-
return RegQueryValueEx(key, name)
488-
except:
489-
return default
503+
def _get_real_winver(maj, min, build):
504+
if maj < 6 or (maj == 6 and min < 2):
505+
return maj, min, build
506+
507+
from ctypes import (c_buffer, POINTER, byref, create_unicode_buffer,
508+
Structure, WinDLL)
509+
from ctypes.wintypes import DWORD, HANDLE
510+
511+
class VS_FIXEDFILEINFO(Structure):
512+
_fields_ = [
513+
("dwSignature", DWORD),
514+
("dwStrucVersion", DWORD),
515+
("dwFileVersionMS", DWORD),
516+
("dwFileVersionLS", DWORD),
517+
("dwProductVersionMS", DWORD),
518+
("dwProductVersionLS", DWORD),
519+
("dwFileFlagsMask", DWORD),
520+
("dwFileFlags", DWORD),
521+
("dwFileOS", DWORD),
522+
("dwFileType", DWORD),
523+
("dwFileSubtype", DWORD),
524+
("dwFileDateMS", DWORD),
525+
("dwFileDateLS", DWORD),
526+
]
527+
528+
kernel32 = WinDLL('kernel32')
529+
version = WinDLL('version')
530+
531+
# We will immediately double the length up to MAX_PATH, but the
532+
# path may be longer, so we retry until the returned string is
533+
# shorter than our buffer.
534+
name_len = actual_len = 130
535+
while actual_len == name_len:
536+
name_len *= 2
537+
name = create_unicode_buffer(name_len)
538+
actual_len = kernel32.GetModuleFileNameW(HANDLE(kernel32._handle),
539+
name, len(name))
540+
if not actual_len:
541+
return maj, min, build
542+
543+
size = version.GetFileVersionInfoSizeW(name, None)
544+
if not size:
545+
return maj, min, build
546+
547+
ver_block = c_buffer(size)
548+
if (not version.GetFileVersionInfoW(name, None, size, ver_block) or
549+
not ver_block):
550+
return maj, min, build
551+
552+
pvi = POINTER(VS_FIXEDFILEINFO)()
553+
if not version.VerQueryValueW(ver_block, "", byref(pvi), byref(DWORD())):
554+
return maj, min, build
555+
556+
maj = pvi.contents.dwProductVersionMS >> 16
557+
min = pvi.contents.dwProductVersionMS & 0xFFFF
558+
build = pvi.contents.dwProductVersionLS >> 16
559+
560+
return maj, min, build
490561

491562
def win32_ver(release='', version='', csd='', ptype=''):
563+
from sys import getwindowsversion
564+
try:
565+
from winreg import OpenKeyEx, QueryValueEx, CloseKey, HKEY_LOCAL_MACHINE
566+
except ImportError:
567+
from _winreg import OpenKeyEx, QueryValueEx, CloseKey, HKEY_LOCAL_MACHINE
492568

493-
""" Get additional version information from the Windows Registry
494-
and return a tuple (version, csd, ptype) referring to version
495-
number, CSD level (service pack), and OS type (multi/single
496-
processor).
497-
498-
As a hint: ptype returns 'Uniprocessor Free' on single
499-
processor NT machines and 'Multiprocessor Free' on multi
500-
processor machines. The 'Free' refers to the OS version being
501-
free of debugging code. It could also state 'Checked' which
502-
means the OS version uses debugging code, i.e. code that
503-
checks arguments, ranges, etc. (Thomas Heller).
569+
winver = getwindowsversion()
570+
maj, min, build = _get_real_winver(*winver[:3])
571+
version = '{0}.{1}.{2}'.format(maj, min, build)
504572

505-
Note: this function works best with Mark Hammond's win32
506-
package installed, but also on Python 2.3 and later. It
507-
obviously only runs on Win32 compatible platforms.
573+
release = (_WIN32_CLIENT_RELEASES.get((maj, min)) or
574+
_WIN32_CLIENT_RELEASES.get((maj, None)) or
575+
release)
508576

509-
"""
510-
# XXX Is there any way to find out the processor type on WinXX ?
511-
# XXX Is win32 available on Windows CE ?
512-
#
513-
# Adapted from code posted by Karl Putland to comp.lang.python.
514-
#
515-
# The mappings between reg. values and release names can be found
516-
# here: http://msdn.microsoft.com/library/en-us/sysinfo/base/osversioninfo_str.asp
517-
518-
# Import the needed APIs
519-
try:
520-
from win32api import RegQueryValueEx, RegOpenKeyEx, \
521-
RegCloseKey, GetVersionEx
522-
from win32con import HKEY_LOCAL_MACHINE, VER_PLATFORM_WIN32_NT, \
523-
VER_PLATFORM_WIN32_WINDOWS, VER_NT_WORKSTATION
524-
except ImportError:
525-
# Emulate the win32api module using Python APIs
577+
# getwindowsversion() reflect the compatibility mode Python is
578+
# running under, and so the service pack value is only going to be
579+
# valid if the versions match.
580+
if winver[:2] == (maj, min):
526581
try:
527-
sys.getwindowsversion
582+
csd = 'SP{}'.format(winver.service_pack_major)
528583
except AttributeError:
529-
# No emulation possible, so return the defaults...
530-
return release, version, csd, ptype
531-
else:
532-
# Emulation using winreg (added in Python 2.0) and
533-
# sys.getwindowsversion() (added in Python 2.3)
534-
import winreg
535-
GetVersionEx = sys.getwindowsversion
536-
RegQueryValueEx = winreg.QueryValueEx
537-
RegOpenKeyEx = winreg.OpenKeyEx
538-
RegCloseKey = winreg.CloseKey
539-
HKEY_LOCAL_MACHINE = winreg.HKEY_LOCAL_MACHINE
540-
VER_PLATFORM_WIN32_WINDOWS = 1
541-
VER_PLATFORM_WIN32_NT = 2
542-
VER_NT_WORKSTATION = 1
543-
VER_NT_SERVER = 3
544-
REG_SZ = 1
545-
546-
# Find out the registry key and some general version infos
547-
winver = GetVersionEx()
548-
maj, min, buildno, plat, csd = winver
549-
version = '%i.%i.%i' % (maj, min, buildno & 0xFFFF)
550-
if hasattr(winver, "service_pack"):
551-
if winver.service_pack != "":
552-
csd = 'SP%s' % winver.service_pack_major
553-
else:
554-
if csd[:13] == 'Service Pack ':
555-
csd = 'SP' + csd[13:]
556-
557-
if plat == VER_PLATFORM_WIN32_WINDOWS:
558-
regkey = 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion'
559-
# Try to guess the release name
560-
if maj == 4:
561-
if min == 0:
562-
release = '95'
563-
elif min == 10:
564-
release = '98'
565-
elif min == 90:
566-
release = 'Me'
567-
else:
568-
release = 'postMe'
569-
elif maj == 5:
570-
release = '2000'
571-
572-
elif plat == VER_PLATFORM_WIN32_NT:
573-
regkey = 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion'
574-
if maj <= 4:
575-
release = 'NT'
576-
elif maj == 5:
577-
if min == 0:
578-
release = '2000'
579-
elif min == 1:
580-
release = 'XP'
581-
elif min == 2:
582-
release = '2003Server'
583-
else:
584-
release = 'post2003'
585-
elif maj == 6:
586-
if hasattr(winver, "product_type"):
587-
product_type = winver.product_type
588-
else:
589-
product_type = VER_NT_WORKSTATION
590-
# Without an OSVERSIONINFOEX capable sys.getwindowsversion(),
591-
# or help from the registry, we cannot properly identify
592-
# non-workstation versions.
593-
try:
594-
key = RegOpenKeyEx(HKEY_LOCAL_MACHINE, regkey)
595-
name, type = RegQueryValueEx(key, "ProductName")
596-
# Discard any type that isn't REG_SZ
597-
if type == REG_SZ and name.find("Server") != -1:
598-
product_type = VER_NT_SERVER
599-
except OSError:
600-
# Use default of VER_NT_WORKSTATION
601-
pass
602-
603-
if min == 0:
604-
if product_type == VER_NT_WORKSTATION:
605-
release = 'Vista'
606-
else:
607-
release = '2008Server'
608-
elif min == 1:
609-
if product_type == VER_NT_WORKSTATION:
610-
release = '7'
611-
else:
612-
release = '2008ServerR2'
613-
elif min == 2:
614-
if product_type == VER_NT_WORKSTATION:
615-
release = '8'
616-
else:
617-
release = '2012Server'
618-
else:
619-
release = 'post2012Server'
584+
if csd[:13] == 'Service Pack ':
585+
csd = 'SP' + csd[13:]
620586

621-
else:
622-
if not release:
623-
# E.g. Win3.1 with win32s
624-
release = '%i.%i' % (maj, min)
625-
return release, version, csd, ptype
587+
# VER_NT_SERVER = 3
588+
if getattr(winver, 'product_type', None) == 3:
589+
release = (_WIN32_SERVER_RELEASES.get((maj, min)) or
590+
_WIN32_SERVER_RELEASES.get((maj, None)) or
591+
release)
626592

627-
# Open the registry key
593+
key = None
628594
try:
629-
keyCurVer = RegOpenKeyEx(HKEY_LOCAL_MACHINE, regkey)
630-
# Get a value to make sure the key exists...
631-
RegQueryValueEx(keyCurVer, 'SystemRoot')
595+
key = OpenKeyEx(HKEY_LOCAL_MACHINE,
596+
r'SOFTWARE\Microsoft\Windows NT\CurrentVersion')
597+
ptype = QueryValueEx(key, 'CurrentType')[0]
632598
except:
633-
return release, version, csd, ptype
634-
635-
# Parse values
636-
#subversion = _win32_getvalue(keyCurVer,
637-
# 'SubVersionNumber',
638-
# ('',1))[0]
639-
#if subversion:
640-
# release = release + subversion # 95a, 95b, etc.
641-
build = _win32_getvalue(keyCurVer,
642-
'CurrentBuildNumber',
643-
('', 1))[0]
644-
ptype = _win32_getvalue(keyCurVer,
645-
'CurrentType',
646-
(ptype, 1))[0]
647-
648-
# Normalize version
649-
version = _norm_version(version, build)
650-
651-
# Close key
652-
RegCloseKey(keyCurVer)
599+
pass
600+
finally:
601+
if key:
602+
CloseKey(key)
603+
653604
return release, version, csd, ptype
654605

606+
655607
def _mac_ver_xml():
656608
fn = '/System/Library/CoreServices/SystemVersion.plist'
657609
if not os.path.exists(fn):

Misc/NEWS

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ Core and Builtins
1818
Library
1919
-------
2020

21+
- Issue #19143: platform module now reads Windows version from kernel32.dll to
22+
avoid compatibility shims.
23+
2124
- Issue #25092: Fix datetime.strftime() failure when errno was already set to
2225
EINVAL.
2326

0 commit comments

Comments
 (0)