Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-41100: ctypes: check _dyld_shared_cache_contains_path in find_library #21241

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions Lib/ctypes/macholib/dyld.py
Expand Up @@ -7,6 +7,12 @@
from ctypes.macholib.dylib import dylib_info
from itertools import *

try:
from _ctypes import _dyld_shared_cache_contains_path
except ImportError:
def _dyld_shared_cache_contains_path(*args):
raise NotImplementedError

__all__ = [
'dyld_find', 'framework_find',
'framework_info', 'dylib_info',
Expand Down Expand Up @@ -124,6 +130,12 @@ def dyld_find(name, executable_path=None, env=None):
), env):
if os.path.isfile(path):
return path
try:
if _dyld_shared_cache_contains_path(path):
return path
except NotImplementedError:
pass

raise ValueError("dylib %s could not be found" % (name,))

def framework_find(fn, executable_path=None, env=None):
Expand Down
15 changes: 10 additions & 5 deletions Lib/ctypes/test/test_macholib.py
Expand Up @@ -46,18 +46,23 @@ class MachOTest(unittest.TestCase):
@unittest.skipUnless(sys.platform == "darwin", 'OSX-specific test')
def test_find(self):

self.assertEqual(find_lib('pthread'),
'/usr/lib/libSystem.B.dylib')
# On Mac OS 11, system dylibs are only present in the shared cache,
# so symlinks like libpthread.dylib -> libSystem.B.dylib will not
# be resolved by dyld_find

self.assertIn(find_lib('pthread'),
('/usr/lib/libSystem.B.dylib', '/usr/lib/libpthread.dylib'))

result = find_lib('z')
# Issue #21093: dyld default search path includes $HOME/lib and
# /usr/local/lib before /usr/lib, which caused test failures if
# a local copy of libz exists in one of them. Now ignore the head
# of the path.
self.assertRegex(result, r".*/lib/libz\..*.*\.dylib")
self.assertRegex(result, r".*/lib/libz.*\.dylib")

self.assertEqual(find_lib('IOKit'),
'/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit')
self.assertIn(find_lib('IOKit'),
('/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit',
'/System/Library/Frameworks/IOKit.framework/IOKit'))

if __name__ == "__main__":
unittest.main()
@@ -0,0 +1 @@
ctypes: Mac OS 11: check for system libraries in the shared cache
37 changes: 37 additions & 0 deletions Modules/_ctypes/callproc.c
Expand Up @@ -64,6 +64,10 @@
#include "ctypes_dlfcn.h"
#endif

#if __APPLE__ && HAVE_DYLD_SHARED_CACHE_CONTAINS_PATH
#include <mach-o/dyld.h>
#endif

#ifdef MS_WIN32
#include <malloc.h>
#endif
Expand Down Expand Up @@ -1398,6 +1402,36 @@ copy_com_pointer(PyObject *self, PyObject *args)
}
#else

#if __APPLE__ && HAVE_DYLD_SHARED_CACHE_CONTAINS_PATH
static PyObject *py_dyld_shared_cache_contains_path(PyObject *self, PyObject *args)
{
if (__builtin_available(macos 11, ios 14, watchos 7, tvos 14, *)) {
PyObject *name, *name2;
char *name_str;
if (!PyArg_ParseTuple(args, "O", &name))
return NULL;

if (name == Py_None)
Py_RETURN_FALSE;

if (PyUnicode_FSConverter(name, &name2) == 0)
return NULL;
if (PyBytes_Check(name2))
name_str = PyBytes_AS_STRING(name2);
else
name_str = PyByteArray_AS_STRING(name2);

if(_dyld_shared_cache_contains_path(name_str))
Py_RETURN_TRUE;
else
Py_RETURN_FALSE;
} else {
PyErr_SetString(PyExc_NotImplementedError, "_dyld_shared_cache_contains_path symbol is missing");
return NULL;
}
}
#endif

static PyObject *py_dl_open(PyObject *self, PyObject *args)
{
PyObject *name, *name2;
Expand Down Expand Up @@ -1908,6 +1942,9 @@ PyMethodDef _ctypes_module_methods[] = {
"dlopen(name, flag={RTLD_GLOBAL|RTLD_LOCAL}) open a shared library"},
{"dlclose", py_dl_close, METH_VARARGS, "dlclose a library"},
{"dlsym", py_dl_sym, METH_VARARGS, "find symbol in shared library"},
#endif
#if __APPLE__ && HAVE_DYLD_SHARED_CACHE_CONTAINS_PATH
{"_dyld_shared_cache_contains_path", py_dyld_shared_cache_contains_path, METH_VARARGS, "check if path is in the shared cache"},
#endif
{"alignment", align_func, METH_O, alignment_doc},
{"sizeof", sizeof_func, METH_O, sizeof_doc},
Expand Down
9 changes: 9 additions & 0 deletions configure
Expand Up @@ -11601,6 +11601,15 @@ fi
done


ac_fn_c_check_decl "$LINENO" "_dyld_shared_cache_contains_path" "ac_cv_have_decl__dyld_shared_cache_contains_path" "#include <mach-o/dyld.h>
"
if test "x$ac_cv_have_decl__dyld_shared_cache_contains_path" = xyes; then :

$as_echo "#define HAVE_DYLD_SHARED_CACHE_CONTAINS_PATH 1" >>confdefs.h

fi


# DYNLOADFILE specifies which dynload_*.o file we will use for dynamic
# loading of modules.

Expand Down
4 changes: 4 additions & 0 deletions configure.ac
Expand Up @@ -3603,6 +3603,10 @@ DLINCLDIR=.
# platforms, such as AIX, have dlopen(), but don't want to use it.
AC_CHECK_FUNCS(dlopen)

AC_CHECK_DECL(_dyld_shared_cache_contains_path,
AC_DEFINE(HAVE_DYLD_SHARED_CACHE_CONTAINS_PATH, 1, Define if you have the '_dyld_shared_cache_contains_path' function),
[], [#include <mach-o/dyld.h>])

# DYNLOADFILE specifies which dynload_*.o file we will use for dynamic
# loading of modules.
AC_SUBST(DYNLOADFILE)
Expand Down
3 changes: 3 additions & 0 deletions pyconfig.h.in
Expand Up @@ -284,6 +284,9 @@
/* Define to 1 if you have the `dup3' function. */
#undef HAVE_DUP3

/* Define if you have the '_dyld_shared_cache_contains_path' function */
#undef HAVE_DYLD_SHARED_CACHE_CONTAINS_PATH

/* Defined when any dynamic module loading is enabled. */
#undef HAVE_DYNAMIC_LOADING

Expand Down