From c34020e5bfc2c3533f87d3c130e50bc6392b61f8 Mon Sep 17 00:00:00 2001 From: Gregory Szorc Date: Sat, 11 Apr 2020 09:31:24 -0700 Subject: [PATCH] Handle failure to call dlopen(NULL) See the inline comment for the explanation. I have been running into this bug on PyOxidizer when using Python distributions built against musl libc. For reference: $ ldd python/install/bin/python3.7m not a dynamic executable $ python/install/bin/python3.7m Python 3.7.7 (default, Apr 5 2020, 06:02:52) [Clang 9.0.1 ] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import ctypes >>> ctypes.CDLL(None) Traceback (most recent call last): File "", line 1, in File "/home/gps/src/pyoxidizer.git/build/python_distributions/python.c1ffa330c730/python/install/lib/python3.7/ctypes/__init__.py", line 364, in __init__ self._handle = _dlopen(self._name, mode) OSError: Dynamic loading not supported Interestingly, OSError is incomplete (possibly a CPython ctypes bug?): >>> try: ... ctypes.CDLL(None) ... except OSError as e: ... err = e ... >>> err OSError('Dynamic loading not supported') >>> err.errno None >>> err.strerror None >>> err.args ('Dynamic loading not supported',) --- CHANGELOG.rst | 2 ++ packaging/tags.py | 16 ++++++++++++++-- tests/test_tags.py | 7 +++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3ce556a3..71afc47c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,8 @@ Changelog *unreleased* ~~~~~~~~~~~~ +* Handle ``OSError`` on non-dynamic executables when attempting to resolve + the glibc version string. * Canonicalize version before comparing specifiers. (:issue:`282`) * Change type hint for ``canonicalize_name`` to return ``packaging.utils.NormalizedName``. diff --git a/packaging/tags.py b/packaging/tags.py index 9064910b..e35bbdad 100644 --- a/packaging/tags.py +++ b/packaging/tags.py @@ -474,8 +474,20 @@ def _glibc_version_string_ctypes(): # main program". This way we can let the linker do the work to figure out # which libc our process is actually using. # - # Note: typeshed is wrong here so we are ignoring this line. - process_namespace = ctypes.CDLL(None) # type: ignore + # We must also handle the special case where the executable is not a + # dynamically linked executable. This can occur when using musl libc, + # for example. In this situation, dlopen() will error, leading to an + # OSError. Interestingly, at least in the case of musl, there is no + # errno set on the OSError. The single string argument used to construct + # OSError comes from libc itself and is therefore not portable to + # hard code here. In any case, failure to call dlopen() means we + # can proceed, so we bail on our attempt. + try: + # Note: typeshed is wrong here so we are ignoring this line. + process_namespace = ctypes.CDLL(None) # type: ignore + except OSError: + return None + try: gnu_get_libc_version = process_namespace.gnu_get_libc_version except AttributeError: diff --git a/tests/test_tags.py b/tests/test_tags.py index 4840e196..0e445f2b 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -389,6 +389,13 @@ def test_glibc_version_string_ctypes_missing(self, monkeypatch): monkeypatch.setitem(sys.modules, "ctypes", None) assert tags._glibc_version_string_ctypes() is None + def test_glibc_version_string_ctypes_raise_oserror(self, monkeypatch): + def patched_cdll(name): + raise OSError("Dynamic loading not supported") + + monkeypatch.setattr(ctypes, "CDLL", patched_cdll) + assert tags._glibc_version_string_ctypes() is None + def test_get_config_var_does_not_log(self, monkeypatch): debug = pretend.call_recorder(lambda *a: None) monkeypatch.setattr(tags.logger, "debug", debug)