Skip to content

Commit d6b727e

Browse files
authored
bpo-40501: Replace ctypes code in uuid with native module (GH-19948)
1 parent f453221 commit d6b727e

File tree

9 files changed

+248
-186
lines changed

9 files changed

+248
-186
lines changed

Lib/test/test_uuid.py

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -852,17 +852,6 @@ def test_netstat_getnode(self):
852852
node = self.uuid._netstat_getnode()
853853
self.check_node(node, 'netstat')
854854

855-
@unittest.skipUnless(os.name == 'nt', 'requires Windows')
856-
def test_ipconfig_getnode(self):
857-
node = self.uuid._ipconfig_getnode()
858-
self.check_node(node, 'ipconfig')
859-
860-
@unittest.skipUnless(importable('win32wnet'), 'requires win32wnet')
861-
@unittest.skipUnless(importable('netbios'), 'requires netbios')
862-
def test_netbios_getnode(self):
863-
node = self.uuid._netbios_getnode()
864-
self.check_node(node)
865-
866855
def test_random_getnode(self):
867856
node = self.uuid._random_getnode()
868857
# The multicast bit, i.e. the least significant bit of first octet,
@@ -874,6 +863,13 @@ def test_random_getnode(self):
874863
node2 = self.uuid._random_getnode()
875864
self.assertNotEqual(node2, node, '%012x' % node)
876865

866+
class TestInternalsWithoutExtModule(BaseTestInternals, unittest.TestCase):
867+
uuid = py_uuid
868+
869+
@unittest.skipUnless(c_uuid, 'requires the C _uuid module')
870+
class TestInternalsWithExtModule(BaseTestInternals, unittest.TestCase):
871+
uuid = c_uuid
872+
877873
@unittest.skipUnless(os.name == 'posix', 'requires Posix')
878874
def test_unix_getnode(self):
879875
if not importable('_uuid') and not importable('ctypes'):
@@ -885,19 +881,10 @@ def test_unix_getnode(self):
885881
self.check_node(node, 'unix')
886882

887883
@unittest.skipUnless(os.name == 'nt', 'requires Windows')
888-
@unittest.skipUnless(importable('ctypes'), 'requires ctypes')
889884
def test_windll_getnode(self):
890885
node = self.uuid._windll_getnode()
891886
self.check_node(node)
892887

893888

894-
class TestInternalsWithoutExtModule(BaseTestInternals, unittest.TestCase):
895-
uuid = py_uuid
896-
897-
@unittest.skipUnless(c_uuid, 'requires the C _uuid module')
898-
class TestInternalsWithExtModule(BaseTestInternals, unittest.TestCase):
899-
uuid = c_uuid
900-
901-
902889
if __name__ == '__main__':
903890
unittest.main()

Lib/uuid.py

Lines changed: 23 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -555,178 +555,44 @@ def _netstat_getnode():
555555
return _find_mac_under_heading('netstat', '-ian', b'Address')
556556

557557
def _ipconfig_getnode():
558-
"""Get the hardware address on Windows by running ipconfig.exe."""
559-
import os, re, subprocess
560-
first_local_mac = None
561-
dirs = ['', r'c:\windows\system32', r'c:\winnt\system32']
562-
try:
563-
import ctypes
564-
buffer = ctypes.create_string_buffer(300)
565-
ctypes.windll.kernel32.GetSystemDirectoryA(buffer, 300)
566-
dirs.insert(0, buffer.value.decode('mbcs'))
567-
except:
568-
pass
569-
for dir in dirs:
570-
try:
571-
proc = subprocess.Popen([os.path.join(dir, 'ipconfig'), '/all'],
572-
stdout=subprocess.PIPE,
573-
encoding="oem")
574-
except OSError:
575-
continue
576-
with proc:
577-
for line in proc.stdout:
578-
value = line.split(':')[-1].strip().lower()
579-
if re.fullmatch('(?:[0-9a-f][0-9a-f]-){5}[0-9a-f][0-9a-f]', value):
580-
mac = int(value.replace('-', ''), 16)
581-
if _is_universal(mac):
582-
return mac
583-
first_local_mac = first_local_mac or mac
584-
return first_local_mac or None
558+
"""[DEPRECATED] Get the hardware address on Windows."""
559+
# bpo-40501: UuidCreateSequential() is now the only supported approach
560+
return _windll_getnode()
585561

586562
def _netbios_getnode():
587-
"""Get the hardware address on Windows using NetBIOS calls.
588-
See http://support.microsoft.com/kb/118623 for details."""
589-
import win32wnet, netbios
590-
first_local_mac = None
591-
ncb = netbios.NCB()
592-
ncb.Command = netbios.NCBENUM
593-
ncb.Buffer = adapters = netbios.LANA_ENUM()
594-
adapters._pack()
595-
if win32wnet.Netbios(ncb) != 0:
596-
return None
597-
adapters._unpack()
598-
for i in range(adapters.length):
599-
ncb.Reset()
600-
ncb.Command = netbios.NCBRESET
601-
ncb.Lana_num = ord(adapters.lana[i])
602-
if win32wnet.Netbios(ncb) != 0:
603-
continue
604-
ncb.Reset()
605-
ncb.Command = netbios.NCBASTAT
606-
ncb.Lana_num = ord(adapters.lana[i])
607-
ncb.Callname = '*'.ljust(16)
608-
ncb.Buffer = status = netbios.ADAPTER_STATUS()
609-
if win32wnet.Netbios(ncb) != 0:
610-
continue
611-
status._unpack()
612-
bytes = status.adapter_address[:6]
613-
if len(bytes) != 6:
614-
continue
615-
mac = int.from_bytes(bytes, 'big')
616-
if _is_universal(mac):
617-
return mac
618-
first_local_mac = first_local_mac or mac
619-
return first_local_mac or None
563+
"""[DEPRECATED] Get the hardware address on Windows."""
564+
# bpo-40501: UuidCreateSequential() is now the only supported approach
565+
return _windll_getnode()
620566

621567

622-
_generate_time_safe = _UuidCreate = None
623-
_has_uuid_generate_time_safe = None
624-
625568
# Import optional C extension at toplevel, to help disabling it when testing
626569
try:
627570
import _uuid
571+
_generate_time_safe = getattr(_uuid, "generate_time_safe", None)
572+
_UuidCreate = getattr(_uuid, "UuidCreate", None)
573+
_has_uuid_generate_time_safe = _uuid.has_uuid_generate_time_safe
628574
except ImportError:
629575
_uuid = None
576+
_generate_time_safe = None
577+
_UuidCreate = None
578+
_has_uuid_generate_time_safe = None
630579

631580

632581
def _load_system_functions():
633-
"""
634-
Try to load platform-specific functions for generating uuids.
635-
"""
636-
global _generate_time_safe, _UuidCreate, _has_uuid_generate_time_safe
637-
638-
if _has_uuid_generate_time_safe is not None:
639-
return
640-
641-
_has_uuid_generate_time_safe = False
642-
643-
if sys.platform == "darwin" and int(os.uname().release.split('.')[0]) < 9:
644-
# The uuid_generate_* functions are broken on MacOS X 10.5, as noted
645-
# in issue #8621 the function generates the same sequence of values
646-
# in the parent process and all children created using fork (unless
647-
# those children use exec as well).
648-
#
649-
# Assume that the uuid_generate functions are broken from 10.5 onward,
650-
# the test can be adjusted when a later version is fixed.
651-
pass
652-
elif _uuid is not None:
653-
_generate_time_safe = _uuid.generate_time_safe
654-
_has_uuid_generate_time_safe = _uuid.has_uuid_generate_time_safe
655-
return
656-
657-
try:
658-
# If we couldn't find an extension module, try ctypes to find
659-
# system routines for UUID generation.
660-
# Thanks to Thomas Heller for ctypes and for his help with its use here.
661-
import ctypes
662-
import ctypes.util
663-
664-
# The uuid_generate_* routines are provided by libuuid on at least
665-
# Linux and FreeBSD, and provided by libc on Mac OS X.
666-
_libnames = ['uuid']
667-
if not sys.platform.startswith('win'):
668-
_libnames.append('c')
669-
for libname in _libnames:
670-
try:
671-
lib = ctypes.CDLL(ctypes.util.find_library(libname))
672-
except Exception: # pragma: nocover
673-
continue
674-
# Try to find the safe variety first.
675-
if hasattr(lib, 'uuid_generate_time_safe'):
676-
_uuid_generate_time_safe = lib.uuid_generate_time_safe
677-
# int uuid_generate_time_safe(uuid_t out);
678-
def _generate_time_safe():
679-
_buffer = ctypes.create_string_buffer(16)
680-
res = _uuid_generate_time_safe(_buffer)
681-
return bytes(_buffer.raw), res
682-
_has_uuid_generate_time_safe = True
683-
break
684-
685-
elif hasattr(lib, 'uuid_generate_time'): # pragma: nocover
686-
_uuid_generate_time = lib.uuid_generate_time
687-
# void uuid_generate_time(uuid_t out);
688-
_uuid_generate_time.restype = None
689-
def _generate_time_safe():
690-
_buffer = ctypes.create_string_buffer(16)
691-
_uuid_generate_time(_buffer)
692-
return bytes(_buffer.raw), None
693-
break
694-
695-
# On Windows prior to 2000, UuidCreate gives a UUID containing the
696-
# hardware address. On Windows 2000 and later, UuidCreate makes a
697-
# random UUID and UuidCreateSequential gives a UUID containing the
698-
# hardware address. These routines are provided by the RPC runtime.
699-
# NOTE: at least on Tim's WinXP Pro SP2 desktop box, while the last
700-
# 6 bytes returned by UuidCreateSequential are fixed, they don't appear
701-
# to bear any relationship to the MAC address of any network device
702-
# on the box.
703-
try:
704-
lib = ctypes.windll.rpcrt4
705-
except:
706-
lib = None
707-
_UuidCreate = getattr(lib, 'UuidCreateSequential',
708-
getattr(lib, 'UuidCreate', None))
709-
710-
except Exception as exc:
711-
import warnings
712-
warnings.warn(f"Could not find fallback ctypes uuid functions: {exc}",
713-
ImportWarning)
582+
"""[DEPRECATED] Platform-specific functions loaded at import time"""
714583

715584

716585
def _unix_getnode():
717-
"""Get the hardware address on Unix using the _uuid extension module
718-
or ctypes."""
719-
_load_system_functions()
720-
uuid_time, _ = _generate_time_safe()
721-
return UUID(bytes=uuid_time).node
586+
"""Get the hardware address on Unix using the _uuid extension module."""
587+
if _generate_time_safe:
588+
uuid_time, _ = _generate_time_safe()
589+
return UUID(bytes=uuid_time).node
722590

723591
def _windll_getnode():
724-
"""Get the hardware address on Windows using ctypes."""
725-
import ctypes
726-
_load_system_functions()
727-
_buffer = ctypes.create_string_buffer(16)
728-
if _UuidCreate(_buffer) == 0:
729-
return UUID(bytes=bytes_(_buffer.raw)).node
592+
"""Get the hardware address on Windows using the _uuid extension module."""
593+
if _UuidCreate:
594+
uuid_bytes = _UuidCreate()
595+
return UUID(bytes_le=uuid_bytes).node
730596

731597
def _random_getnode():
732598
"""Get a random node ID."""
@@ -755,7 +621,8 @@ def _random_getnode():
755621
elif _DARWIN:
756622
_OS_GETTERS = [_ifconfig_getnode, _arp_getnode, _netstat_getnode]
757623
elif _WINDOWS:
758-
_OS_GETTERS = [_netbios_getnode, _ipconfig_getnode]
624+
# bpo-40201: _windll_getnode will always succeed, so these are not needed
625+
_OS_GETTERS = []
759626
elif _AIX:
760627
_OS_GETTERS = [_netstat_getnode]
761628
else:
@@ -802,7 +669,6 @@ def uuid1(node=None, clock_seq=None):
802669

803670
# When the system provides a version-1 UUID generator, use it (but don't
804671
# use UuidCreate here because its UUIDs don't conform to RFC 4122).
805-
_load_system_functions()
806672
if _generate_time_safe is not None and node is clock_seq is None:
807673
uuid_time, safely_generated = _generate_time_safe()
808674
try:
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:mod:`uuid` no longer uses :mod:`ctypes` to load :file:`libuuid` or
2+
:file:`rpcrt4.dll` at runtime.

Modules/_uuidmodule.c

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Python UUID module that wraps libuuid -
2+
* Python UUID module that wraps libuuid or Windows rpcrt4.dll.
33
* DCE compatible Universally Unique Identifier library.
44
*/
55

@@ -12,6 +12,12 @@
1212
#include <uuid.h>
1313
#endif
1414

15+
#ifdef MS_WINDOWS
16+
#include <rpc.h>
17+
#endif
18+
19+
#ifndef MS_WINDOWS
20+
1521
static PyObject *
1622
py_uuid_generate_time_safe(PyObject *Py_UNUSED(context),
1723
PyObject *Py_UNUSED(ignored))
@@ -31,17 +37,50 @@ py_uuid_generate_time_safe(PyObject *Py_UNUSED(context),
3137
return Py_BuildValue("y#i", buf, sizeof(uuid), (int) status);
3238
# else
3339
return Py_BuildValue("y#i", (const char *) &uuid, sizeof(uuid), (int) status);
34-
# endif
35-
#else
40+
# endif /* HAVE_UUID_CREATE */
41+
#else /* HAVE_UUID_GENERATE_TIME_SAFE */
3642
uuid_generate_time(uuid);
3743
return Py_BuildValue("y#O", (const char *) uuid, sizeof(uuid), Py_None);
38-
#endif
44+
#endif /* HAVE_UUID_GENERATE_TIME_SAFE */
3945
}
4046

47+
#else /* MS_WINDOWS */
48+
49+
static PyObject *
50+
py_UuidCreate(PyObject *Py_UNUSED(context),
51+
PyObject *Py_UNUSED(ignored))
52+
{
53+
UUID uuid;
54+
RPC_STATUS res;
55+
56+
Py_BEGIN_ALLOW_THREADS
57+
res = UuidCreateSequential(&uuid);
58+
Py_END_ALLOW_THREADS
59+
60+
switch (res) {
61+
case RPC_S_OK:
62+
case RPC_S_UUID_LOCAL_ONLY:
63+
case RPC_S_UUID_NO_ADDRESS:
64+
/*
65+
All success codes, but the latter two indicate that the UUID is random
66+
rather than based on the MAC address. If the OS can't figure this out,
67+
neither can we, so we'll take it anyway.
68+
*/
69+
return Py_BuildValue("y#", (const char *)&uuid, sizeof(uuid));
70+
}
71+
PyErr_SetFromWindowsErr(res);
72+
return NULL;
73+
}
74+
75+
#endif /* MS_WINDOWS */
76+
77+
4178
static int
4279
uuid_exec(PyObject *module) {
4380
assert(sizeof(uuid_t) == 16);
44-
#ifdef HAVE_UUID_GENERATE_TIME_SAFE
81+
#if defined(MS_WINDOWS)
82+
int has_uuid_generate_time_safe = 0;
83+
#elif defined(HAVE_UUID_GENERATE_TIME_SAFE)
4584
int has_uuid_generate_time_safe = 1;
4685
#else
4786
int has_uuid_generate_time_safe = 0;
@@ -54,7 +93,12 @@ uuid_exec(PyObject *module) {
5493
}
5594

5695
static PyMethodDef uuid_methods[] = {
96+
#if defined(HAVE_UUID_UUID_H) || defined(HAVE_UUID_H)
5797
{"generate_time_safe", py_uuid_generate_time_safe, METH_NOARGS, NULL},
98+
#endif
99+
#if defined(MS_WINDOWS)
100+
{"UuidCreate", py_UuidCreate, METH_NOARGS, NULL},
101+
#endif
58102
{NULL, NULL, 0, NULL} /* sentinel */
59103
};
60104

0 commit comments

Comments
 (0)