Gentoo Archives: gentoo-portage-dev

From: Zac Medico <zmedico@g.o>
To: gentoo-portage-dev@l.g.o
Cc: Zac Medico <zmedico@g.o>
Subject: [gentoo-portage-dev] [PATCH] SonameDepsProcessor: handle internal libs without DT_SONAME (bug 646190)
Date: Sun, 20 May 2018 23:43:17
Message-Id: 20180520233917.2415-1-zmedico@gentoo.org
1 Packages like ebtables have internal libraries that lack a DT_SONAME
2 field in their ELF header. Consumers of these internal libraries have
3 DT_RUNPATH entries that refer to the directory containing the internal
4 libraries. For library dependencies that are satisfied by internal
5 libraries like this, it is inappropriate for SonameDepsProcessor to
6 include these depenedencies in the REQUIRES metadata, therefore fix
7 SonameDepsProcessor to automatically detect this case and exclude
8 these dependencies from the REQUIRES metadata. This solves incorrect
9 reporting of broken soname dependencies like the following:
10
11 $ emerge -p --depclean --ignore-soname-deps=n
12
13 Calculating dependencies... done!
14 * Broken soname dependencies found:
15 *
16 * x86_64: libebt_redirect.so required by:
17 * net-firewall/ebtables-2.0.10.4
18 *
19 * x86_64: libebt_log.so required by:
20 * net-firewall/ebtables-2.0.10.4
21
22 Bug: https://bugs.gentoo.org/646190
23 ---
24 pym/portage/tests/util/dyn_libs/__init__.py | 0
25 pym/portage/tests/util/dyn_libs/__test__.py | 0
26 .../tests/util/dyn_libs/test_soname_deps.py | 34 +++++++++++++++++++++
27 pym/portage/util/_dyn_libs/soname_deps.py | 35 ++++++++++++++++++++--
28 4 files changed, 66 insertions(+), 3 deletions(-)
29 create mode 100644 pym/portage/tests/util/dyn_libs/__init__.py
30 create mode 100644 pym/portage/tests/util/dyn_libs/__test__.py
31 create mode 100644 pym/portage/tests/util/dyn_libs/test_soname_deps.py
32
33 diff --git a/pym/portage/tests/util/dyn_libs/__init__.py b/pym/portage/tests/util/dyn_libs/__init__.py
34 new file mode 100644
35 index 0000000000..e69de29bb2
36 diff --git a/pym/portage/tests/util/dyn_libs/__test__.py b/pym/portage/tests/util/dyn_libs/__test__.py
37 new file mode 100644
38 index 0000000000..e69de29bb2
39 diff --git a/pym/portage/tests/util/dyn_libs/test_soname_deps.py b/pym/portage/tests/util/dyn_libs/test_soname_deps.py
40 new file mode 100644
41 index 0000000000..823890c910
42 --- /dev/null
43 +++ b/pym/portage/tests/util/dyn_libs/test_soname_deps.py
44 @@ -0,0 +1,34 @@
45 +# Copyright 2018 Gentoo Foundation
46 +# Distributed under the terms of the GNU General Public License v2
47 +
48 +from portage.tests import TestCase
49 +from portage.util._dyn_libs.NeededEntry import NeededEntry
50 +from portage.util._dyn_libs.soname_deps import SonameDepsProcessor
51 +
52 +
53 +class SonameDepsProcessorTestCase(TestCase):
54 +
55 + def testInternalLibsWithoutSoname(self):
56 + """
57 + Test handling of internal libraries that lack an soname, which are
58 + resolved via DT_RUNPATH, see ebtables for example (bug 646190).
59 + """
60 + needed_elf_2 = """
61 +X86_64;/sbin/ebtables;;/lib64/ebtables;libebt_802_3.so,libebtable_broute.so,libc.so.6;x86_64
62 +X86_64;/lib64/ebtables/libebtable_broute.so;;;libc.so.6;x86_64
63 +X86_64;/lib64/ebtables/libebt_802_3.so;;;libc.so.6;x86_64
64 +"""
65 + soname_deps = SonameDepsProcessor('', '')
66 +
67 + for line in needed_elf_2.splitlines():
68 + if not line:
69 + continue
70 + entry = NeededEntry.parse(None, line)
71 + soname_deps.add(entry)
72 +
73 + self.assertEqual(soname_deps.provides, None)
74 + # Prior to the fix for bug 646190, REQUIRES contained references to
75 + # the internal libebt* libraries which are resolved via a DT_RUNPATH
76 + # entry referring to the /lib64/ebtables directory that contains the
77 + # internal libraries.
78 + self.assertEqual(soname_deps.requires, 'x86_64: libc.so.6\n')
79 diff --git a/pym/portage/util/_dyn_libs/soname_deps.py b/pym/portage/util/_dyn_libs/soname_deps.py
80 index a7d595429f..c6302afc25 100644
81 --- a/pym/portage/util/_dyn_libs/soname_deps.py
82 +++ b/pym/portage/util/_dyn_libs/soname_deps.py
83 @@ -9,6 +9,11 @@ import os
84 import re
85
86 from portage.util import shlex_split
87 +from portage.util import (
88 + normalize_path,
89 + varexpand,
90 +)
91 +
92
93 class SonameDepsProcessor(object):
94 """
95 @@ -31,6 +36,7 @@ class SonameDepsProcessor(object):
96 self._requires_map = {}
97 self._provides_map = {}
98 self._provides_unfiltered = {}
99 + self._basename_map = {}
100 self._provides = None
101 self._requires = None
102 self._intersected = False
103 @@ -62,15 +68,24 @@ class SonameDepsProcessor(object):
104 raise AssertionError(
105 "Missing multilib category data: %s" % entry.filename)
106
107 + self._basename_map.setdefault(
108 + os.path.basename(entry.filename), []).append(entry)
109 +
110 if entry.needed and (
111 self._requires_exclude is None or
112 self._requires_exclude.match(
113 entry.filename.lstrip(os.sep)) is None):
114 + runpaths = frozenset()
115 + if entry.runpaths is not None:
116 + expand = {"ORIGIN": os.path.dirname(entry.filename)}
117 + runpaths = frozenset(normalize_path(varexpand(x, expand,
118 + error_leader=lambda: "%s: DT_RUNPATH: " % entry.filename))
119 + for x in entry.runpaths)
120 for x in entry.needed:
121 if (self._requires_exclude is None or
122 self._requires_exclude.match(x) is None):
123 self._requires_map.setdefault(
124 - multilib_cat, set()).add(x)
125 + multilib_cat, {}).setdefault(x, set()).add(runpaths)
126
127 if entry.soname:
128 self._provides_unfiltered.setdefault(
129 @@ -93,9 +108,23 @@ class SonameDepsProcessor(object):
130 requires_map.setdefault(multilib_cat, set())
131 provides_map.setdefault(multilib_cat, set())
132 provides_unfiltered.setdefault(multilib_cat, set())
133 - for soname in list(requires_map[multilib_cat]):
134 + for soname, consumers in list(requires_map[multilib_cat].items()):
135 if soname in provides_unfiltered[multilib_cat]:
136 - requires_map[multilib_cat].remove(soname)
137 + del requires_map[multilib_cat][soname]
138 + elif soname in self._basename_map:
139 + # Handle internal libraries that lack an soname, which
140 + # are resolved via DT_RUNPATH, see ebtables for example
141 + # (bug 646190).
142 + for entry in self._basename_map[soname]:
143 + if entry.multilib_category != multilib_cat:
144 + continue
145 + dirname = os.path.dirname(entry.filename)
146 + for runpaths in list(consumers):
147 + if dirname in runpaths:
148 + consumers.remove(runpaths)
149 + if not consumers:
150 + del requires_map[multilib_cat][soname]
151 + break
152
153 provides_data = []
154 for multilib_cat in sorted(provides_map):
155 --
156 2.13.6