Zac Medico
2018-05-20 23:39:17 UTC
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):
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
2.13.6