From fa024407e2f29d7491d0c607e79fb08faa61b690 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 25 Jun 2026 03:50:27 +0200 Subject: [PATCH 1/2] gh-151929: Get machine ID and uptime on Windows in pythoninfo * Replace "linux." prefix with "system." in pythoninfo. * Add _winapi.GetTickCount64() function. --- Lib/test/pythoninfo.py | 100 +++++++++++++++++++++++++++---------- Modules/_winapi.c | 16 ++++++ Modules/clinic/_winapi.c.h | 20 +++++++- 3 files changed, 109 insertions(+), 27 deletions(-) diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 067e218f797364c..c7de1f43bfb0cbb 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -8,6 +8,9 @@ import warnings +MS_WINDOWS = (sys.platform == "win32") + + def normalize_text(text): if text is None: return None @@ -906,8 +909,29 @@ def collect_subprocess(info_add): copy_attributes(info_add, subprocess, 'subprocess.%s', ('_USE_POSIX_SPAWN',)) +def winreg_query(path): + try: + import winreg + except ImportError: + return None + + key, path = path.split('\\', 1) + sub_key, value = path.rsplit('\\', 1) + if key == "HKEY_LOCAL_MACHINE": + key = winreg.HKEY_LOCAL_MACHINE + else: + raise ValueError(f"unknown key {key!r}") + + try: + with winreg.OpenKey(key, sub_key) as key_handle: + result, _ = winreg.QueryValueEx(key_handle, value) + return result + except OSError: + return None + + def collect_windows(info_add): - if sys.platform != "win32": + if not MS_WINDOWS: # Code specific to Windows return @@ -999,19 +1023,10 @@ def collect_windows(info_add): info_add('windows.ver', line) # windows.developer_mode: get AllowDevelopmentWithoutDevLicense registry - import winreg - try: - key = winreg.OpenKey( - winreg.HKEY_LOCAL_MACHINE, - r"SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock") - subkey = "AllowDevelopmentWithoutDevLicense" - try: - value, value_type = winreg.QueryValueEx(key, subkey) - finally: - winreg.CloseKey(key) - except OSError: - pass - else: + value = winreg_query(r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows" + r"\CurrentVersion\AppModelUnlock" + r"\AllowDevelopmentWithoutDevLicense") + if value is not None: info_add('windows.developer_mode', "enabled" if value else "disabled") @@ -1044,20 +1059,22 @@ def collect_libregrtest_utils(info_add): info_add('libregrtests.build_info', ' '.join(utils.get_build_info())) -def linux_get_uptime(): - # Use CLOCK_BOOTTIME if available +def uptime_boottime(): + # Use CLOCK_BOOTTIME import time try: return time.clock_gettime(time.CLOCK_BOOTTIME) except (AttributeError, OSError): - pass + return None + - # Otherwise, parse the first member of /proc/uptime - uptime = read_first_line("/proc/uptime") - if not uptime: +def uptime_linux(): + # Parse the first member of /proc/uptime + line = read_first_line("/proc/uptime") + if not line: return try: - parts = uptime.split() + parts = line.split() if not parts: return return float(parts[0]) @@ -1065,17 +1082,48 @@ def linux_get_uptime(): return +def uptime_windows(): + try: + import _winapi + except ImportError: + return None + else: + return _winapi.GetTickCount64() / 1000. + + +def get_uptime(): + for func in (uptime_boottime, uptime_linux, uptime_windows): + uptime = func() + if uptime is not None: + return uptime + return None + + +def get_machine_id(): + if MS_WINDOWS: + machine_guid = winreg_query(r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft" + r"\Cryptography\MachineGuid") + if machine_guid: + return machine_guid + + machine_id = read_first_line("/etc/machine-id") + if machine_id: + return machine_id + + return None + + def collect_linux(info_add): boot_id = read_first_line("/proc/sys/kernel/random/boot_id") if boot_id: - info_add('linux.boot_id', boot_id) + info_add('system.boot_id', boot_id) # https://www.freedesktop.org/software/systemd/man/latest/machine-id.html - machine_id = read_first_line("/etc/machine-id") + machine_id = get_machine_id() if machine_id: - info_add('linux.machine_id', machine_id) + info_add('system.machine_id', machine_id) - uptime = linux_get_uptime() + uptime = get_uptime() if uptime is not None: # truncate microseconds uptime = int(uptime) @@ -1084,7 +1132,7 @@ def collect_linux(info_add): uptime = str(datetime.timedelta(seconds=uptime)) except ImportError: uptime = f'{uptime} sec' - info_add('linux.uptime', uptime) + info_add('system.uptime', uptime) def collect_info(info): diff --git a/Modules/_winapi.c b/Modules/_winapi.c index 369a7400eb63b90..13f88cdf2f11d98 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -3145,6 +3145,21 @@ _winapi_GetProcessMemoryInfo_impl(PyObject *module, HANDLE handle) } +/*[clinic input] +_winapi.GetTickCount64 + +Number of milliseconds that have elapsed since the system was started. +[clinic start generated code]*/ + +static PyObject * +_winapi_GetTickCount64_impl(PyObject *module) +/*[clinic end generated code: output=cb33c0568f0b3ed1 input=77ed6539ac7d6590]*/ +{ + ULONGLONG ticks = GetTickCount64(); + return PyLong_FromUnsignedLongLong(ticks); +} + + static PyMethodDef winapi_functions[] = { _WINAPI_CLOSEHANDLE_METHODDEF _WINAPI_CONNECTNAMEDPIPE_METHODDEF @@ -3196,6 +3211,7 @@ static PyMethodDef winapi_functions[] = { _WINAPI_NEEDCURRENTDIRECTORYFOREXEPATH_METHODDEF _WINAPI_COPYFILE2_METHODDEF _WINAPI_GETPROCESSMEMORYINFO_METHODDEF + _WINAPI_GETTICKCOUNT64_METHODDEF {NULL, NULL} }; diff --git a/Modules/clinic/_winapi.c.h b/Modules/clinic/_winapi.c.h index dd9dbffaa9ac23c..031a0783aef60bb 100644 --- a/Modules/clinic/_winapi.c.h +++ b/Modules/clinic/_winapi.c.h @@ -2358,7 +2358,25 @@ _winapi_GetProcessMemoryInfo(PyObject *module, PyObject *arg) return return_value; } +PyDoc_STRVAR(_winapi_GetTickCount64__doc__, +"GetTickCount64($module, /)\n" +"--\n" +"\n" +"Number of milliseconds that have elapsed since the system was started."); + +#define _WINAPI_GETTICKCOUNT64_METHODDEF \ + {"GetTickCount64", (PyCFunction)_winapi_GetTickCount64, METH_NOARGS, _winapi_GetTickCount64__doc__}, + +static PyObject * +_winapi_GetTickCount64_impl(PyObject *module); + +static PyObject * +_winapi_GetTickCount64(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _winapi_GetTickCount64_impl(module); +} + #ifndef _WINAPI_GETSHORTPATHNAME_METHODDEF #define _WINAPI_GETSHORTPATHNAME_METHODDEF #endif /* !defined(_WINAPI_GETSHORTPATHNAME_METHODDEF) */ -/*[clinic end generated code: output=07dfd4bbacaed4a8 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=713a8ce97185b017 input=a9049054013a1b77]*/ From b5fbfc04928ace5a0f39186e1caeaf9949421d3f Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 25 Jun 2026 05:16:44 +0200 Subject: [PATCH 2/2] Use winreg.KEY_WOW64_64KEY --- Lib/test/pythoninfo.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index c7de1f43bfb0cbb..7f3d566e988d804 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -923,7 +923,8 @@ def winreg_query(path): raise ValueError(f"unknown key {key!r}") try: - with winreg.OpenKey(key, sub_key) as key_handle: + access = winreg.KEY_READ | winreg.KEY_WOW64_64KEY + with winreg.OpenKey(key, sub_key, access=access) as key_handle: result, _ = winreg.QueryValueEx(key_handle, value) return result except OSError: