diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py index 0c2510e1619c8e..b601c27eb80502 100644 --- a/Lib/ctypes/util.py +++ b/Lib/ctypes/util.py @@ -96,8 +96,11 @@ def find_library(name): def _is_elf(filename): "Return True if the given file is an ELF file" elf_header = b'\x7fELF' - with open(filename, 'br') as thefile: - return thefile.read(4) == elf_header + try: + with open(filename, 'br') as thefile: + return thefile.read(4) == elf_header + except OSError: + return False def _findLib_gcc(name): # Run GCC's linker with the -t (aka --trace) option and examine the @@ -324,10 +327,55 @@ def _findLib_ld(name): pass # result will be None return result + def _findLib_musl(name): + # fallback for musl libc, which does not have ldconfig -p + # See GH-65821 + + from _ctypes import get_interp + interp = get_interp() + if interp == None: + return None + + ldarch = re.sub(r'.*ld-musl-([^.]+)\..*', r'\1', interp) + if ldarch == interp: + # not musl + return None + + from glob import glob + if os.path.isabs(name): + return name + if name.startswith("lib") and name.endswith(".so"): + suffixes = [ '.[0-9]*' ] + libname = name + else: + suffixes = ['.so', '.so.[0-9]*', f'.musl-{ldarch}.so.[0-9]*'] + libname = 'lib'+name + # search LD_LIBRARY_PATH list and default musl libc locations + paths = os.environ.get('LD_LIBRARY_PATH', '').split(':') + try: + with open(f'/etc/ld-musl-{ldarch}.path') as f: + paths.extend(re.split(':|\n', f.read())) + except OSError: + paths.extend(['/lib', '/usr/local/lib', '/usr/lib']) + + for d in paths: + f = os.path.join(d, name) + if _is_elf(f): + return os.path.basename(f) + prefix = os.path.join(d, libname) + for suffix in suffixes: + for f in sorted(glob('{0}{1}'.format(prefix, suffix))): + if _is_elf(f): + return os.path.basename(f) + def find_library(name): # See issue #9998 - return _findSoname_ldconfig(name) or \ - _get_soname(_findLib_gcc(name)) or _get_soname(_findLib_ld(name)) + return ( + _findSoname_ldconfig(name) + or _get_soname(_findLib_gcc(name)) + or _get_soname(_findLib_ld(name)) + or _findLib_musl(name) + ) ################################################################ # test code @@ -344,6 +392,7 @@ def test(): print(find_library("m")) print(find_library("c")) print(find_library("bz2")) + print(find_library("libbz2.so")) # load if sys.platform == "darwin": @@ -368,8 +417,8 @@ def test(): print(f"crypto\t:: {find_library('crypto')}") print(f"crypto\t:: {cdll.LoadLibrary(find_library('crypto'))}") else: - print(cdll.LoadLibrary("libm.so")) - print(cdll.LoadLibrary("libcrypt.so")) + print(cdll.LoadLibrary(find_library("c"))) + print(cdll.LoadLibrary("libc.so")) print(find_library("crypt")) if __name__ == "__main__": diff --git a/Lib/test/test_ctypes/test_find.py b/Lib/test/test_ctypes/test_find.py index 1ff9d019b138a4..1c02769e9cf325 100644 --- a/Lib/test/test_ctypes/test_find.py +++ b/Lib/test/test_ctypes/test_find.py @@ -122,6 +122,15 @@ def test_find_library_with_ld(self): unittest.mock.patch("ctypes.util._findLib_gcc", lambda *args: None): self.assertNotEqual(find_library('c'), None) + def test_find_library_musl(self): + from _ctypes import get_interp + interp = get_interp() + if interp == None or interp.find("ld-musl-") == -1: + self.skipTest('ld-musl not detected') + + with unittest.mock.patch("ctypes.util._findSoname_ldconfig", lambda *args: None), \ + unittest.mock.patch("ctypes.util._get_soname", lambda *args: None): + self.assertNotEqual(find_library('c'), None) if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2021-02-10-16-05-03.bpo-21622.sNtCfz.rst b/Misc/NEWS.d/next/Library/2021-02-10-16-05-03.bpo-21622.sNtCfz.rst new file mode 100644 index 00000000000000..b5f671cab610c7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-02-10-16-05-03.bpo-21622.sNtCfz.rst @@ -0,0 +1 @@ +Fix :func:`ctypes.util.find_library` with musl libc. diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c index d2fe525dd4d396..347269774e3914 100644 --- a/Modules/_ctypes/callproc.c +++ b/Modules/_ctypes/callproc.c @@ -85,6 +85,13 @@ #include #endif +#ifdef HAVE_ELF_H +#include +#endif +#ifdef HAVE_LINK_H +#include +#endif + #ifdef _Py_MEMORY_SANITIZER #include #endif @@ -1992,7 +1999,52 @@ buffer_info(PyObject *self, PyObject *arg) return Py_BuildValue("siN", dict->format, dict->ndim, shape); } +#ifndef MS_WIN32 + +#ifdef HAVE_DL_ITERATE_PHDR +static int +interp_cb(struct dl_phdr_info *info, size_t size, void *data) +{ + const char **ps = data; + const char *base = (const char *)info->dlpi_addr; + const ElfW(Phdr) *ph = info->dlpi_phdr; + int phn = info->dlpi_phnum; + + for (int i=0; i < phn; i++) { + if (ph[i].p_type == PT_INTERP) { + *ps = base + ph[i].p_vaddr; + return 1; + } + } + return 0; +} + +static PyObject * +get_interp(PyObject *self, PyObject *arg) +{ + const char *s = NULL; + if (PySys_Audit("ctypes.get_interp", NULL) < 0) { + return NULL; + } + if (dl_iterate_phdr(interp_cb, &s) == 1) { + return PyUnicode_FromString(s); + } + + return NULL; +} + +#else + +static PyObject * +get_interp(PyObject *self, PyObject *arg) +{ + return Py_None; +} + +#endif + +#endif PyMethodDef _ctypes_module_methods[] = { {"get_errno", get_errno, METH_NOARGS}, @@ -2015,6 +2067,7 @@ 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"}, + {"get_interp", get_interp, METH_NOARGS}, #endif #ifdef __APPLE__ {"_dyld_shared_cache_contains_path", py_dyld_shared_cache_contains_path, METH_VARARGS, "check if path is in the shared cache"}, diff --git a/configure b/configure index b8fa9d66e735dd..a1cf54e3b8809d 100755 --- a/configure +++ b/configure @@ -9687,8 +9687,8 @@ $as_echo "#define STDC_HEADERS 1" >>confdefs.h # checks for header files for ac_header in \ - alloca.h asm/types.h bluetooth.h conio.h crypt.h direct.h dlfcn.h endian.h errno.h fcntl.h grp.h \ - ieeefp.h io.h langinfo.h libintl.h libutil.h linux/auxvec.h sys/auxv.h linux/fs.h linux/memfd.h \ + alloca.h asm/types.h bluetooth.h conio.h crypt.h direct.h dlfcn.h elf.h endian.h errno.h fcntl.h grp.h \ + ieeefp.h io.h langinfo.h libintl.h libutil.h link.h linux/auxvec.h sys/auxv.h linux/fs.h linux/memfd.h \ linux/random.h linux/soundcard.h \ linux/tipc.h linux/wait.h netdb.h net/ethernet.h netinet/in.h netpacket/packet.h poll.h process.h pthread.h pty.h \ sched.h setjmp.h shadow.h signal.h spawn.h stropts.h sys/audioio.h sys/bsdtty.h sys/devpoll.h \ @@ -16153,7 +16153,7 @@ fi # checks for library functions for ac_func in \ accept4 alarm bind_textdomain_codeset chmod chown clock close_range confstr \ - copy_file_range ctermid dup dup3 execv explicit_bzero explicit_memset \ + copy_file_range ctermid dl_iterate_phdr dup dup3 execv explicit_bzero explicit_memset \ faccessat fchmod fchmodat fchown fchownat fdopendir fdwalk fexecve \ fork fork1 fpathconf fstatat ftime ftruncate futimens futimes futimesat \ gai_strerror getegid getentropy geteuid getgid getgrgid getgrgid_r \ diff --git a/configure.ac b/configure.ac index 0940b93c25f7bf..22f2c82eae2f1b 100644 --- a/configure.ac +++ b/configure.ac @@ -2755,8 +2755,8 @@ AC_DEFINE(STDC_HEADERS, 1, [Define to 1 if you have the ANSI C header files.]) # checks for header files AC_CHECK_HEADERS([ \ - alloca.h asm/types.h bluetooth.h conio.h crypt.h direct.h dlfcn.h endian.h errno.h fcntl.h grp.h \ - ieeefp.h io.h langinfo.h libintl.h libutil.h linux/auxvec.h sys/auxv.h linux/fs.h linux/memfd.h \ + alloca.h asm/types.h bluetooth.h conio.h crypt.h direct.h dlfcn.h elf.h endian.h errno.h fcntl.h grp.h \ + ieeefp.h io.h langinfo.h libintl.h libutil.h link.h linux/auxvec.h sys/auxv.h linux/fs.h linux/memfd.h \ linux/random.h linux/soundcard.h \ linux/tipc.h linux/wait.h netdb.h net/ethernet.h netinet/in.h netpacket/packet.h poll.h process.h pthread.h pty.h \ sched.h setjmp.h shadow.h signal.h spawn.h stropts.h sys/audioio.h sys/bsdtty.h sys/devpoll.h \ @@ -4717,7 +4717,7 @@ fi # checks for library functions AC_CHECK_FUNCS([ \ accept4 alarm bind_textdomain_codeset chmod chown clock close_range confstr \ - copy_file_range ctermid dup dup3 execv explicit_bzero explicit_memset \ + copy_file_range ctermid dl_iterate_phdr dup dup3 execv explicit_bzero explicit_memset \ faccessat fchmod fchmodat fchown fchownat fdopendir fdwalk fexecve \ fork fork1 fpathconf fstatat ftime ftruncate futimens futimes futimesat \ gai_strerror getegid getentropy geteuid getgid getgrgid getgrgid_r \ diff --git a/pyconfig.h.in b/pyconfig.h.in index 2c22b27af65ea3..106ff4a045165e 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -290,6 +290,9 @@ /* Define to 1 if you have the `dlopen' function. */ #undef HAVE_DLOPEN +/* Define to 1 if you have the `dl_iterate_phdr' function. */ +#undef HAVE_DL_ITERATE_PHDR + /* Define to 1 if you have the `dup' function. */ #undef HAVE_DUP @@ -308,6 +311,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_EDITLINE_READLINE_H +/* Define to 1 if you have the header file. */ +#undef HAVE_ELF_H + /* Define to 1 if you have the header file. */ #undef HAVE_ENDIAN_H @@ -703,6 +709,9 @@ /* Define to 1 if you have the `linkat' function. */ #undef HAVE_LINKAT +/* Define to 1 if you have the header file. */ +#undef HAVE_LINK_H + /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_AUXVEC_H