Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 34k
gh-87414: add musl support to platform.libc_ver#103784
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
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
051e8f2625330a65d0bd8c6306331a10966ee37ed7e945a5d8a3f51518420f976550e705ed7a24216858File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -118,6 +118,7 @@ | ||
| import sys | ||
| import functools | ||
| import itertools | ||
| import struct | ||
| try: | ||
| import _wmi | ||
| except ImportError: | ||
| @@ -204,6 +205,13 @@ def libc_ver(executable=None, lib='', version='', chunksize=16384): | ||
| binary = f.read(chunksize) | ||
| pos = 0 | ||
| while pos < len(binary): | ||
| # We first check 'musl' in the binary, because the next | ||
| # condition of looking for 'libc' in binary will be | ||
| # true for the case of musl. | ||
| if b'musl' in binary: | ||
| mv = _get_musl_version(executable) | ||
| return "musl", mv | ||
| if b'libc' in binary or b'GLIBC' in binary: | ||
| m = libc_search.search(binary, pos) | ||
| else: | ||
| @@ -1355,6 +1363,121 @@ def freedesktop_os_release(): | ||
| return _os_release_cache.copy() | ||
| ### musl libc version support | ||
| # These functions were copied and adapted from the packaging module: | ||
| # https://github.com/pypa/packaging/blob/4d8534061364e3cbfee582192ab81a095ec2db51/src/packaging/_musllinux.py | ||
| # https://github.com/pypa/packaging/blob/4d8534061364e3cbfee582192ab81a095ec2db51/src/packaging/_elffile.py | ||
malemburg marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| class ELFInvalid(ValueError): | ||
| pass | ||
| class ELFFile: | ||
| """ | ||
| Representation of an ELF executable. | ||
| """ | ||
| def __init__(self, f): | ||
| self._f = f | ||
| try: | ||
| ident = self._read("16B") | ||
| except struct.error: | ||
| raise ELFInvalid("unable to parse identification") | ||
| magic = bytes(ident[:4]) | ||
| if magic != b"\x7fELF": | ||
| raise ELFInvalid(f"invalid magic:{magic!r}") | ||
| self.capacity = ident[4] # Format for program header (bitness). | ||
| self.encoding = ident[5] # Data structure encoding (endianness). | ||
| try: | ||
| # e_fmt: Format for program header. | ||
| # p_fmt: Format for section header. | ||
| # p_idx: Indexes to find p_type, p_offset, and p_filesz. | ||
| e_fmt, self._p_fmt, self._p_idx ={ | ||
| (1, 1): ("<HHIIIIIHHH", "<IIIIIIII", (0, 1, 4)), # 32-bit LSB. | ||
| (1, 2): (">HHIIIIIHHH", ">IIIIIIII", (0, 1, 4)), # 32-bit MSB. | ||
| (2, 1): ("<HHIQQQIHHH", "<IIQQQQQQ", (0, 2, 5)), # 64-bit LSB. | ||
| (2, 2): (">HHIQQQIHHH", ">IIQQQQQQ", (0, 2, 5)), # 64-bit MSB. | ||
| }[(self.capacity, self.encoding)] | ||
| except KeyError: | ||
| raise ELFInvalid( | ||
| f"unrecognized capacity ({self.capacity}) or " | ||
| f"encoding ({self.encoding})" | ||
| ) | ||
| try: | ||
| ( | ||
| _, | ||
| self.machine, # Architecture type. | ||
| _, | ||
| _, | ||
| self._e_phoff, # Offset of program header. | ||
| _, | ||
| self.flags, # Processor-specific flags. | ||
| _, | ||
| self._e_phentsize, # Size of section. | ||
| self._e_phnum, # Number of sections. | ||
| ) = self._read(e_fmt) | ||
| except struct.error as e: | ||
| raise ELFInvalid("unable to parse machine and section information") from e | ||
| def _read(self, fmt): | ||
| return struct.unpack(fmt, self._f.read(struct.calcsize(fmt))) | ||
| @property | ||
| def interpreter(self): | ||
| """ | ||
| The path recorded in the ``PT_INTERP`` section header. | ||
| """ | ||
| for index in range(self._e_phnum): | ||
| self._f.seek(self._e_phoff + self._e_phentsize * index) | ||
| try: | ||
| data = self._read(self._p_fmt) | ||
| except struct.error: | ||
| continue | ||
| if data[self._p_idx[0]] != 3: # Not PT_INTERP. | ||
| continue | ||
| self._f.seek(data[self._p_idx[1]]) | ||
| return os.fsdecode(self._f.read(data[self._p_idx[2]])).strip("\0") | ||
| return None | ||
| def _parse_musl_version(output): | ||
| lines = [n for n in (n.strip() for n in output.splitlines()) if n] | ||
| if len(lines) < 2 or lines[0][:4] != "musl": | ||
| return None | ||
| m = re.match(r"Version (\d+)\.(\d+)", lines[1]) | ||
| if not m: | ||
| return None | ||
| return f"{m.group(1)}.{m.group(2)}" | ||
| @functools.lru_cache() | ||
| def _get_musl_version(executable): | ||
| """Detect currently-running musl runtime version. | ||
| This is done by checking the specified executable's dynamic linking | ||
| information, and invoking the loader to parse its output for a version | ||
| string. If the loader is musl, the output would be something like:: | ||
| musl libc (x86_64) | ||
| Version 1.2.2 | ||
| Dynamic Program Loader | ||
| """ | ||
| import subprocess | ||
| try: | ||
| with open(executable, "rb") as f: | ||
| ld = ELFFile(f).interpreter | ||
| except (OSError, TypeError, ValueError): | ||
| return None | ||
| if ld is None or "musl" not in ld: | ||
| return None | ||
| proc = subprocess.run([ld], stderr=subprocess.PIPE, universal_newlines=True) | ||
| return _parse_musl_version(proc.stderr) | ||
| ### Command line interface | ||
| if __name__ == '__main__': | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| import io | ||
| import os | ||
| import copy | ||
| import pickle | ||
| @@ -68,6 +69,9 @@ | ||
| """ | ||
| ELFFILE_HEADER = b"\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00" | ||
| class PlatformTest(unittest.TestCase): | ||
| def clear_caches(self): | ||
| platform._platform_cache.clear() | ||
| @@ -538,6 +542,49 @@ def test_parse_os_release(self): | ||
| self.assertEqual(info, expected) | ||
| self.assertEqual(len(info["SPECIALS"]), 5) | ||
| def test_parse_musl_version(self): | ||
| output = """\ | ||
| musl libc (x86_64) | ||
| Version 1.2.3 | ||
| Dynamic Program Loader | ||
| Usage: /lib/ld-musl-x86_64.so.1 [options] [--] pathname [args] | ||
| """ | ||
| self.assertEqual(platform._parse_musl_version(output), "1.2") | ||
| @support.requires_subprocess() | ||
| @support.requires_musl() | ||
| def test_libc_ver_musl(self): | ||
| self.assertEqual(platform.libc_ver(), ("musl", "1.2")) | ||
Member 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. Will this always be 1.2? | ||
| @support.requires_musl() | ||
| class ELFFileTest(unittest.TestCase): | ||
| def test_get_interpreter(self): | ||
| with open(sys.executable, "rb") as f: | ||
| elffile = platform.ELFFile(f) | ||
| self.assertEqual(elffile.interpreter, "/lib/ld-musl-x86_64.so.1") | ||
| def test_init_invalid_magic(self): | ||
| BAD_ELFFILE_HEADER = b"\x7fBAD\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00" | ||
| f = io.BytesIO(BAD_ELFFILE_HEADER) | ||
| self.assertRaisesRegex( | ||
| platform.ELFInvalid, | ||
| "invalid magic:", | ||
| platform.ELFFile, | ||
| f, | ||
| ) | ||
| def test_init_parse_error(self): | ||
| EMPTY_ELF_HEADER = b"\x00" | ||
| f = io.BytesIO(EMPTY_ELF_HEADER) | ||
| self.assertRaisesRegex( | ||
| platform.ELFInvalid, | ||
| "unable to parse identification", | ||
| platform.ELFFile, | ||
| f, | ||
| ) | ||
| if __name__ == '__main__': | ||
| unittest.main() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Add musl support to ``platform.libc_ver`` function. |
Uh oh!
There was an error while loading. Please reload this page.