-
-
Notifications
You must be signed in to change notification settings - Fork 32.2k
gh-65821: Fix ctypes.util.find_library with musl #18380
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
base: main
Are you sure you want to change the base?
Changes from all commits
01fcc84
7d73c59
d8fefa9
266d243
1d1832b
068de75
b02efa3
f0ca603
0b423ee
8a32a5b
4c0889d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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']) | ||
Comment on lines
+358
to
+359
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What are the possible issues here? Would it make sense to be more specific with the exception? Would it make sense to let the user know that we are defaulting to Should There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIRC the order was copied from musl libc: http://git.musl-libc.org/cgit/musl/tree/ldso/dynlink.c?id=f5f55d6589940fd2c2188d76686efe3a530e64e0#n1160 |
||
|
||
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")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aren’t these two lines doing the same thing now? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They should be. IIRC I added those tests to verify that they actually are the same thing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe the existing lines should be kept for general linux, and a new block added for musl-based? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think that would be necessary. The updated test should be good enough for both. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’m unsure about this change. Maybe the author really wanted to see if libm and libcrypt could be found, not just libc which should always be there. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What would be the purpose of testing that? Those libs does not not exist on musl libc systems. I thought that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let me put it this way: should the test fail if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My point was about not changing this test for glibc-based systems – new test for musl can use the two new lines |
||
print(find_library("crypt")) | ||
|
||
if __name__ == "__main__": | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Fix :func:`ctypes.util.find_library` with musl libc. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This does not mention the introduced API. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -85,6 +85,13 @@ | |
#include <alloca.h> | ||
#endif | ||
|
||
#ifdef HAVE_ELF_H | ||
#include <elf.h> | ||
#endif | ||
#ifdef HAVE_LINK_H | ||
#include <link.h> | ||
#endif | ||
|
||
#ifdef _Py_MEMORY_SANITIZER | ||
#include <sanitizer/msan_interface.h> | ||
#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; | ||
erlend-aasland marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Documentation is missing for this new API. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, if you introduce a new API, please use Argument Clinic. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there some way to add it as an internal function? So we don't expose it as a public API There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, you could prefix it with an underscore. |
||
#endif | ||
#ifdef __APPLE__ | ||
{"_dyld_shared_cache_contains_path", py_dyld_shared_cache_contains_path, METH_VARARGS, "check if path is in the shared cache"}, | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the motivation behind this? Saying the file is not an elf if we get any kind of
OSError
does not seem like a great idea to me 😅There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this means to catch file does not exist and file is not readable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's what'd expect to, but I want to make sure returning false on all possible
OSError
is actually the behavior we want, as it might introduce subtle bugs. For eg. I would expect aBrokenPipeError
orTimeoutError
to propagate, instead of not considering the file a valid library.Looking at the usages of this function, I'd guess we only need
PermissionError
, and I think we only need it on the musl specific code path, so IMO it'd be better off there.Anyway, I think I am being overly careful here, but would like to hear from the author if they have time. This is not blocker.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The thinking here was: return True if we know it is an elf, return False if we know it is not or we don't know.
will current
find_library
implementation for GNU libc or *BSD raise an error in those cases?