Packages like ebtables have internal libraries that lack a DT_SONAME field in their ELF header. Consumers of these internal libraries have DT_RUNPATH entries that refer to the directory containing the internal libraries. For library dependencies that are satisfied by internal libraries like this, it is inappropriate for SonameDepsProcessor to include these depenedencies in the REQUIRES metadata, therefore fix SonameDepsProcessor to automatically detect this case and exclude these dependencies from the REQUIRES metadata. This solves incorrect reporting of broken soname dependencies like the following:
$ emerge -p --depclean --ignore-soname-deps=n Calculating dependencies... done! * Broken soname dependencies found: * * x86_64: libebt_redirect.so required by: * net-firewall/ebtables-2.0.10.4 * * x86_64: libebt_log.so required by: * net-firewall/ebtables-2.0.10.4 Bug: https://bugs.gentoo.org/646190 --- pym/portage/tests/util/dyn_libs/__init__.py | 0 pym/portage/tests/util/dyn_libs/__test__.py | 0 .../tests/util/dyn_libs/test_soname_deps.py | 34 +++++++++++++++++++++ pym/portage/util/_dyn_libs/soname_deps.py | 35 ++++++++++++++++++++-- 4 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 pym/portage/tests/util/dyn_libs/__init__.py create mode 100644 pym/portage/tests/util/dyn_libs/__test__.py create mode 100644 pym/portage/tests/util/dyn_libs/test_soname_deps.py diff --git a/pym/portage/tests/util/dyn_libs/__init__.py b/pym/portage/tests/util/dyn_libs/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pym/portage/tests/util/dyn_libs/__test__.py b/pym/portage/tests/util/dyn_libs/__test__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pym/portage/tests/util/dyn_libs/test_soname_deps.py b/pym/portage/tests/util/dyn_libs/test_soname_deps.py new file mode 100644 index 0000000000..823890c910 --- /dev/null +++ b/pym/portage/tests/util/dyn_libs/test_soname_deps.py @@ -0,0 +1,34 @@ +# Copyright 2018 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +from portage.tests import TestCase +from portage.util._dyn_libs.NeededEntry import NeededEntry +from portage.util._dyn_libs.soname_deps import SonameDepsProcessor + + +class SonameDepsProcessorTestCase(TestCase): + + def testInternalLibsWithoutSoname(self): + """ + Test handling of internal libraries that lack an soname, which are + resolved via DT_RUNPATH, see ebtables for example (bug 646190). + """ + needed_elf_2 = """ +X86_64;/sbin/ebtables;;/lib64/ebtables;libebt_802_3.so,libebtable_broute.so,libc.so.6;x86_64 +X86_64;/lib64/ebtables/libebtable_broute.so;;;libc.so.6;x86_64 +X86_64;/lib64/ebtables/libebt_802_3.so;;;libc.so.6;x86_64 +""" + soname_deps = SonameDepsProcessor('', '') + + for line in needed_elf_2.splitlines(): + if not line: + continue + entry = NeededEntry.parse(None, line) + soname_deps.add(entry) + + self.assertEqual(soname_deps.provides, None) + # Prior to the fix for bug 646190, REQUIRES contained references to + # the internal libebt* libraries which are resolved via a DT_RUNPATH + # entry referring to the /lib64/ebtables directory that contains the + # internal libraries. + self.assertEqual(soname_deps.requires, 'x86_64: libc.so.6\n') diff --git a/pym/portage/util/_dyn_libs/soname_deps.py b/pym/portage/util/_dyn_libs/soname_deps.py index a7d595429f..c6302afc25 100644 --- a/pym/portage/util/_dyn_libs/soname_deps.py +++ b/pym/portage/util/_dyn_libs/soname_deps.py @@ -9,6 +9,11 @@ import os import re from portage.util import shlex_split +from portage.util import ( + normalize_path, + varexpand, +) + class SonameDepsProcessor(object): """ @@ -31,6 +36,7 @@ class SonameDepsProcessor(object): self._requires_map = {} self._provides_map = {} self._provides_unfiltered = {} + self._basename_map = {} self._provides = None self._requires = None self._intersected = False @@ -62,15 +68,24 @@ class SonameDepsProcessor(object): raise AssertionError( "Missing multilib category data: %s" % entry.filename) + self._basename_map.setdefault( + os.path.basename(entry.filename), []).append(entry) + if entry.needed and ( self._requires_exclude is None or self._requires_exclude.match( entry.filename.lstrip(os.sep)) is None): + runpaths = frozenset() + if entry.runpaths is not None: + expand = {"ORIGIN": os.path.dirname(entry.filename)} + runpaths = frozenset(normalize_path(varexpand(x, expand, + error_leader=lambda: "%s: DT_RUNPATH: " % entry.filename)) + for x in entry.runpaths) for x in entry.needed: if (self._requires_exclude is None or self._requires_exclude.match(x) is None): self._requires_map.setdefault( - multilib_cat, set()).add(x) + multilib_cat, {}).setdefault(x, set()).add(runpaths) if entry.soname: self._provides_unfiltered.setdefault( @@ -93,9 +108,23 @@ class SonameDepsProcessor(object): requires_map.setdefault(multilib_cat, set()) provides_map.setdefault(multilib_cat, set()) provides_unfiltered.setdefault(multilib_cat, set()) - for soname in list(requires_map[multilib_cat]): + for soname, consumers in list(requires_map[multilib_cat].items()): if soname in provides_unfiltered[multilib_cat]: - requires_map[multilib_cat].remove(soname) + del requires_map[multilib_cat][soname] + elif soname in self._basename_map: + # Handle internal libraries that lack an soname, which + # are resolved via DT_RUNPATH, see ebtables for example + # (bug 646190). + for entry in self._basename_map[soname]: + if entry.multilib_category != multilib_cat: + continue + dirname = os.path.dirname(entry.filename) + for runpaths in list(consumers): + if dirname in runpaths: + consumers.remove(runpaths) + if not consumers: + del requires_map[multilib_cat][soname] + break provides_data = [] for multilib_cat in sorted(provides_map): -- 2.13.6