From 868d3f959df1ace7b5d0a5007e60c2a662f10086 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 5 Jun 2026 11:36:41 +0100 Subject: [PATCH] dlopen-notes: make output stable regardless of order of inputs When multiple files are passed to dlopen-notes --sonames the output depends on the order of inputs in case the same soname is used with different priorities across different binaries: $ ./dlopen-notes.py --sonames systemd-sbsign systemd-repart libblkid.so.1 required libcrypto.so.3 recommended libcryptsetup.so.12 recommended libfdisk.so.1 required libmount.so.1 required $ ./dlopen-notes.py --sonames systemd-repart systemd-sbsign libblkid.so.1 required libcrypto.so.3 required libcryptsetup.so.12 recommended libfdisk.so.1 required libmount.so.1 required sbsign requires libcrypto, and repart recommends it, so the output changes between required and recommended depending on which is passed last. Fix it so that the highest priority for any given soname is always returned, regardless of the number of input binaries. --- dlopen-notes.py | 11 ++++++++--- test/test.py | 31 ++++++++++++++++++++++++------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/dlopen-notes.py b/dlopen-notes.py index 2127405..b7e24be 100755 --- a/dlopen-notes.py +++ b/dlopen-notes.py @@ -69,13 +69,15 @@ def notes(self): yield from j -@dictify def group_by_soname(elffiles): + sonames = {} for elffile in elffiles: for element in elffile.notes(): - priority = element.get('priority', 'recommended') + priority = Priority[element.get('priority', 'recommended')] for soname in element['soname']: - yield soname, priority + sonames[soname] = max(sonames.get(soname, priority), priority) + + return sonames class Priority(enum.Enum): suggested = 1 @@ -85,6 +87,9 @@ class Priority(enum.Enum): def __lt__(self, other): return self.value < other.value + def __str__(self): + return self.name + def rpm_name(self): if self == self.__class__.suggested: return 'Suggests' diff --git a/test/test.py b/test/test.py index 7da5fa5..fcb1c7b 100644 --- a/test/test.py +++ b/test/test.py @@ -1,15 +1,15 @@ # SPDX-License-Identifier: CC0-1.0 -from _notes import ELFFileReader, group_by_soname, generate_rpm +from _notes import ELFFileReader, group_by_soname, generate_rpm, Priority def test_sonames(): expected = { - 'libfido2.so.1': 'required', - 'liblz4.so.1': 'recommended', - 'libpcre2-8.so.0': 'suggested', - 'libpcre2-8.so.1': 'suggested', - 'libtss2-esys.so.0': 'recommended', - 'libtss2-mu.so.0': 'recommended', + 'libfido2.so.1': Priority.required, + 'liblz4.so.1': Priority.recommended, + 'libpcre2-8.so.0': Priority.suggested, + 'libpcre2-8.so.1': Priority.suggested, + 'libtss2-esys.so.0': Priority.recommended, + 'libtss2-mu.so.0': Priority.recommended, } notes = ELFFileReader('notes') assert group_by_soname([notes]) == expected @@ -33,3 +33,20 @@ def test_requires(): lines = generate_rpm([notes], 'Suggests', ('pcre2', 'tpm')) expect = expected[notes.elffile.elfclass] assert sorted(lines) == sorted(expect) + +class FakeReader: + def __init__(self, notes): + self._notes = notes + + def notes(self): + return self._notes + +def test_sonames_highest_priority_stable(): + a = FakeReader([{'soname': ['libcrypto.so.3'], 'priority': 'required'}]) + b = FakeReader([{'soname': ['libcrypto.so.3'], 'priority': 'recommended'}]) + c = FakeReader([{'soname': ['libcrypto.so.3'], 'priority': 'suggested'}]) + + expected = {'libcrypto.so.3': Priority.required} + assert group_by_soname([a, b, c]) == expected + assert group_by_soname([c, b, a]) == expected + assert group_by_soname([b, a, c]) == expected