diff --git a/packaging/_elffile.py b/packaging/_elffile.py index 9e47b5d4..9fb5984b 100644 --- a/packaging/_elffile.py +++ b/packaging/_elffile.py @@ -1,6 +1,7 @@ -"""ELF file parser. +""" +ELF file parser. -This provides a class ``ElfFile`` that parses an ELF executable in a similar +This provides a class ``ELFFile`` that parses an ELF executable in a similar interface to ``ZipFile``. Only the read interface is implemented. Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca @@ -13,7 +14,7 @@ from typing import IO, Optional, Tuple -class ElfInvalid(ValueError): +class ELFInvalid(ValueError): pass @@ -35,8 +36,10 @@ class EMachine(enum.IntEnum): AArc64 = 183 -class ElfFile: - """Representation of an ELF executable.""" +class ELFFile: + """ + Representation of an ELF executable. + """ def __init__(self, f: IO[bytes]) -> None: self._f = f @@ -44,9 +47,10 @@ def __init__(self, f: IO[bytes]) -> None: try: ident = self._read("16B") except struct.error: - raise ElfInvalid - if bytes(ident[:4]) != b"\x7fELF": # Invalid magic, not ELF. - raise ElfInvalid + 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 (endianess). @@ -62,7 +66,10 @@ def __init__(self, f: IO[bytes]) -> None: (2, 2): (">HHIQQQIHHH", ">IIQQQQQQ", (0, 2, 5)), # 64-bit MSB. }[(self.capacity, self.encoding)] except KeyError: - raise ElfInvalid + raise ELFInvalid( + f"unrecognized capacity ({self.capacity}) or " + f"encoding ({self.encoding})" + ) try: ( @@ -77,15 +84,17 @@ def __init__(self, f: IO[bytes]) -> None: self._e_phentsize, # Size of section. self._e_phnum, # Number of sections. ) = self._read(e_fmt) - except struct.error: - raise ElfInvalid + except struct.error as e: + raise ELFInvalid("unable to parse machine and section information") from e def _read(self, fmt: str) -> Tuple[int, ...]: return struct.unpack(fmt, self._f.read(struct.calcsize(fmt))) @property def interpreter(self) -> Optional[str]: - """Path recorded in the ``PT_INTERP`` section header.""" + """ + 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: diff --git a/packaging/_manylinux.py b/packaging/_manylinux.py index 249378f8..2f0cc743 100644 --- a/packaging/_manylinux.py +++ b/packaging/_manylinux.py @@ -7,7 +7,7 @@ import warnings from typing import Dict, Generator, Iterator, NamedTuple, Optional, Tuple -from ._elffile import EIClass, EIData, ElfFile, EMachine +from ._elffile import EIClass, EIData, ELFFile, EMachine EF_ARM_ABIMASK = 0xFF000000 EF_ARM_ABI_VER5 = 0x05000000 @@ -15,10 +15,10 @@ @contextlib.contextmanager -def _parse_elf(path: str) -> Generator[Optional[ElfFile], None, None]: +def _parse_elf(path: str) -> Generator[Optional[ELFFile], None, None]: try: with open(path, "rb") as f: - yield ElfFile(f) + yield ELFFile(f) except (OSError, TypeError, ValueError): yield None diff --git a/packaging/_musllinux.py b/packaging/_musllinux.py index 429ef8d7..706ba600 100644 --- a/packaging/_musllinux.py +++ b/packaging/_musllinux.py @@ -10,7 +10,7 @@ import sys from typing import Iterator, NamedTuple, Optional -from ._elffile import ElfFile +from ._elffile import ELFFile class _MuslVersion(NamedTuple): @@ -42,7 +42,7 @@ def _get_musl_version(executable: str) -> Optional[_MuslVersion]: """ try: with open(executable, "rb") as f: - ld = ElfFile(f).interpreter + ld = ELFFile(f).interpreter except (OSError, TypeError, ValueError): return None if ld is None or "musl" not in ld: diff --git a/tests/test_elffile.py b/tests/test_elffile.py index 486ba4e0..6b46ddc6 100644 --- a/tests/test_elffile.py +++ b/tests/test_elffile.py @@ -4,7 +4,7 @@ import pytest -from packaging._elffile import EIClass, EIData, ElfFile, ElfInvalid, EMachine +from packaging._elffile import EIClass, EIData, ELFFile, ELFInvalid, EMachine DIR_MANYLINUX = pathlib.Path(__file__, "..", "manylinux").resolve() DIR_MUSLLINUX = pathlib.Path(__file__, "..", "musllinux").resolve() @@ -25,7 +25,7 @@ def test_elffile_glibc(name, capacity, encoding, machine): path = DIR_MANYLINUX.joinpath(f"hello-world-{name}") with path.open("rb") as f: - ef = ElfFile(f) + ef = ELFFile(f) assert ef.capacity == capacity assert ef.encoding == encoding assert ef.machine == machine @@ -49,7 +49,7 @@ def test_elffile_glibc(name, capacity, encoding, machine): def test_elffile_musl(name, capacity, encoding, machine, interpreter): path = DIR_MUSLLINUX.joinpath(f"musl-{name}") with path.open("rb") as f: - ef = ElfFile(f) + ef = ELFFile(f) assert ef.capacity == capacity assert ef.encoding == encoding assert ef.machine == machine @@ -69,25 +69,25 @@ def test_elffile_musl(name, capacity, encoding, machine, interpreter): ids=["no-magic", "wrong-magic", "unknown-format"], ) def test_elffile_bad_ident(data): - with pytest.raises(ElfInvalid): - ElfFile(io.BytesIO(data)) + with pytest.raises(ELFInvalid): + ELFFile(io.BytesIO(data)) def test_elffile_no_section(): """Enough for magic, but not the section definitions.""" data = BIN_MUSL_X86_64[:25] - with pytest.raises(ElfInvalid): - ElfFile(io.BytesIO(data)) + with pytest.raises(ELFInvalid): + ELFFile(io.BytesIO(data)) def test_elffile_invalid_section(): """Enough for section definitions, but not the actual sections.""" data = BIN_MUSL_X86_64[:58] - assert ElfFile(io.BytesIO(data)).interpreter is None + assert ELFFile(io.BytesIO(data)).interpreter is None def test_elffle_no_interpreter_section(): - ef = ElfFile(io.BytesIO(BIN_MUSL_X86_64)) + ef = ELFFile(io.BytesIO(BIN_MUSL_X86_64)) # Change all sections to *not* PT_INTERP. data = BIN_MUSL_X86_64 @@ -97,4 +97,4 @@ def test_elffle_no_interpreter_section(): section = struct.unpack(ef._p_fmt, data[sb:se]) data = data[:sb] + struct.pack(ef._p_fmt, 0, *section[1:]) + data[se:] - assert ElfFile(io.BytesIO(data)).interpreter is None + assert ELFFile(io.BytesIO(data)).interpreter is None