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


Reply via email to