Skip to content

Commit 64a3387

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

File tree

2 files changed

+123
-168
lines changed

2 files changed

+123
-168
lines changed

Lib/platform.py

Lines changed: 120 additions & 168 deletions
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
@@ -455,189 +457,139 @@ def _syscmd_ver(system='', release='', version='',
455457
version = _norm_version(version)
456458
return system, release, version
457459

458-
def _win32_getvalue(key, name, default=''):
460+
_WIN32_CLIENT_RELEASES = {
461+
(5, 0): "2000",
462+
(5, 1): "XP",
463+
# Strictly, 5.2 client is XP 64-bit, but platform.py historically
464+
# has always called it 2003 Server
465+
(5, 2): "2003Server",
466+
(5, None): "post2003",
467+
468+
(6, 0): "Vista",
469+
(6, 1): "7",
470+
(6, 2): "8",
471+
(6, 3): "8.1",
472+
(6, None): "post8.1",
473+
474+
(10, 0): "10",
475+
(10, None): "post10",
476+
}
459477

460-
""" Read a value for name from the registry key.
478+
# Server release name lookup will default to client names if necessary
479+
_WIN32_SERVER_RELEASES = {
480+
(5, 2): "2003Server",
461481

462-
In case this fails, default is returned.
482+
(6, 0): "2008Server",
483+
(6, 1): "2008ServerR2",
484+
(6, 2): "2012Server",
485+
(6, 3): "2012ServerR2",
486+
(6, None): "post2012ServerR2",
487+
}
463488

464-
"""
465-
try:
466-
# Use win32api if available
467-
from win32api import RegQueryValueEx
468-
except ImportError:
469-
# On Python 2.0 and later, emulate using winreg
470-
import winreg
471-
RegQueryValueEx = winreg.QueryValueEx
472-
try:
473-
return RegQueryValueEx(key, name)
474-
except:
475-
return default
489+
def _get_real_winver(maj, min, build):
490+
if maj < 6 or (maj == 6 and min < 2):
491+
return maj, min, build
492+
493+
from ctypes import (c_buffer, POINTER, byref, create_unicode_buffer,
494+
Structure, WinDLL)
495+
from ctypes.wintypes import DWORD, HANDLE
496+
497+
class VS_FIXEDFILEINFO(Structure):
498+
_fields_ = [
499+
("dwSignature", DWORD),
500+
("dwStrucVersion", DWORD),
501+
("dwFileVersionMS", DWORD),
502+
("dwFileVersionLS", DWORD),
503+
("dwProductVersionMS", DWORD),
504+
("dwProductVersionLS", DWORD),
505+
("dwFileFlagsMask", DWORD),
506+
("dwFileFlags", DWORD),
507+
("dwFileOS", DWORD),
508+
("dwFileType", DWORD),
509+
("dwFileSubtype", DWORD),
510+
("dwFileDateMS", DWORD),
511+
("dwFileDateLS", DWORD),
512+
]
513+
514+
kernel32 = WinDLL('kernel32')
515+
version = WinDLL('version')
516+
517+
# We will immediately double the length up to MAX_PATH, but the
518+
# path may be longer, so we retry until the returned string is
519+
# shorter than our buffer.
520+
name_len = actual_len = 130
521+
while actual_len == name_len:
522+
name_len *= 2
523+
name = create_unicode_buffer(name_len)
524+
actual_len = kernel32.GetModuleFileNameW(HANDLE(kernel32._handle),
525+
name, len(name))
526+
if not actual_len:
527+
return maj, min, build
528+
529+
size = version.GetFileVersionInfoSizeW(name, None)
530+
if not size:
531+
return maj, min, build
532+
533+
ver_block = c_buffer(size)
534+
if (not version.GetFileVersionInfoW(name, None, size, ver_block) or
535+
not ver_block):
536+
return maj, min, build
537+
538+
pvi = POINTER(VS_FIXEDFILEINFO)()
539+
if not version.VerQueryValueW(ver_block, "", byref(pvi), byref(DWORD())):
540+
return maj, min, build
541+
542+
maj = pvi.contents.dwProductVersionMS >> 16
543+
min = pvi.contents.dwProductVersionMS & 0xFFFF
544+
build = pvi.contents.dwProductVersionLS >> 16
545+
546+
return maj, min, build
476547

477548
def win32_ver(release='', version='', csd='', ptype=''):
549+
from sys import getwindowsversion
550+
try:
551+
from winreg import OpenKeyEx, QueryValueEx, CloseKey, HKEY_LOCAL_MACHINE
552+
except ImportError:
553+
from _winreg import OpenKeyEx, QueryValueEx, CloseKey, HKEY_LOCAL_MACHINE
478554

479-
""" Get additional version information from the Windows Registry
480-
and return a tuple (version, csd, ptype) referring to version
481-
number, CSD level (service pack), and OS type (multi/single
482-
processor).
483-
484-
As a hint: ptype returns 'Uniprocessor Free' on single
485-
processor NT machines and 'Multiprocessor Free' on multi
486-
processor machines. The 'Free' refers to the OS version being
487-
free of debugging code. It could also state 'Checked' which
488-
means the OS version uses debugging code, i.e. code that
489-
checks arguments, ranges, etc. (Thomas Heller).
555+
winver = getwindowsversion()
556+
maj, min, build = _get_real_winver(*winver[:3])
557+
version = '{0}.{1}.{2}'.format(maj, min, build)
490558

491-
Note: this function works best with Mark Hammond's win32
492-
package installed, but also on Python 2.3 and later. It
493-
obviously only runs on Win32 compatible platforms.
559+
release = (_WIN32_CLIENT_RELEASES.get((maj, min)) or
560+
_WIN32_CLIENT_RELEASES.get((maj, None)) or
561+
release)
494562

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

607-
else:
608-
if not release:
609-
# E.g. Win3.1 with win32s
610-
release = '%i.%i' % (maj, min)
611-
return release, version, csd, ptype
573+
# VER_NT_SERVER = 3
574+
if getattr(winver, 'product_type', None) == 3:
575+
release = (_WIN32_SERVER_RELEASES.get((maj, min)) or
576+
_WIN32_SERVER_RELEASES.get((maj, None)) or
577+
release)
612578

613-
# Open the registry key
579+
key = None
614580
try:
615-
keyCurVer = RegOpenKeyEx(HKEY_LOCAL_MACHINE, regkey)
616-
# Get a value to make sure the key exists...
617-
RegQueryValueEx(keyCurVer, 'SystemRoot')
581+
key = OpenKeyEx(HKEY_LOCAL_MACHINE,
582+
r'SOFTWARE\Microsoft\Windows NT\CurrentVersion')
583+
ptype = QueryValueEx(key, 'CurrentType')[0]
618584
except:
619-
return release, version, csd, ptype
620-
621-
# Parse values
622-
#subversion = _win32_getvalue(keyCurVer,
623-
# 'SubVersionNumber',
624-
# ('',1))[0]
625-
#if subversion:
626-
# release = release + subversion # 95a, 95b, etc.
627-
build = _win32_getvalue(keyCurVer,
628-
'CurrentBuildNumber',
629-
('', 1))[0]
630-
ptype = _win32_getvalue(keyCurVer,
631-
'CurrentType',
632-
(ptype, 1))[0]
633-
634-
# Normalize version
635-
version = _norm_version(version, build)
636-
637-
# Close key
638-
RegCloseKey(keyCurVer)
585+
pass
586+
finally:
587+
if key:
588+
CloseKey(key)
589+
639590
return release, version, csd, ptype
640591

592+
641593
def _mac_ver_xml():
642594
fn = '/System/Library/CoreServices/SystemVersion.plist'
643595
if not os.path.exists(fn):

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ Core and Builtins
8181
Library
8282
-------
8383

84+
- Issue #19143: platform module now reads Windows version from kernel32.dll to
85+
avoid compatibility shims.
86+
8487
- Issue #23517: Fix rounding in fromtimestamp() and utcfromtimestamp() methods
8588
of datetime.datetime: microseconds are now rounded to nearest with ties
8689
going to nearest even integer (ROUND_HALF_EVEN), instead of being rounding

0 commit comments

Comments
 (0)