commit:     1364cd44e7a6232bf425c4573b5bd3d6738d49a4
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Sun May 20 06:40:19 2018 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Thu May 24 20:31:02 2018 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=1364cd44

SonameDepsProcessor: handle internal libs without DT_SONAME (bug 646190)

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(-)

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 000000000..e69de29bb

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 000000000..e69de29bb

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 000000000..823890c91
--- /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 a7d595429..c6302afc2 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):

Reply via email to