diff --git a/0035-Fix-dnf_keyring_add_public_keys-reset-GError-to-NULL.patch b/0035-Fix-dnf_keyring_add_public_keys-reset-GError-to-NULL.patch new file mode 100644 index 0000000..d96b68b --- /dev/null +++ b/0035-Fix-dnf_keyring_add_public_keys-reset-GError-to-NULL.patch @@ -0,0 +1,36 @@ +From 96df64bf5f4e374adac4b1ea423cb29ae73b9f49 Mon Sep 17 00:00:00 2001 +From: Jaroslav Rohel +Date: Mon, 7 Nov 2022 12:15:19 +0100 +Subject: [PATCH] Fix: "dnf_keyring_add_public_keys": reset GError to NULL + (RhBug:2121222) + +Fixes problem "PackageKit crashes if parsing multiple key files fails" +packagekitd[1397]: GError set over the top of a previous GError or + uninitialized memory. This indicates a bug in someone's code. You must + ensure an error is NULL before it's set. The overwriting error message + was: failed to parse public key for + /etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-14-secondary + += changelog = +msg: "dnf_keyring_add_public_keys": reset localError to NULL after free +type: bugfix +resolves: https://bugzilla.redhat.com/show_bug.cgi?id=2121222 +--- + libdnf/dnf-keyring.cpp | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/libdnf/dnf-keyring.cpp b/libdnf/dnf-keyring.cpp +index 62a6248c..550d5ce2 100644 +--- a/libdnf/dnf-keyring.cpp ++++ b/libdnf/dnf-keyring.cpp +@@ -213,6 +213,7 @@ dnf_keyring_add_public_keys(rpmKeyring keyring, GError **error) try + if (!ret) { + g_warning("%s", localError->message); + g_error_free(localError); ++ localError = NULL; + } + } while (true); + return TRUE; +-- +2.52.0 + diff --git a/0036-Mark-all-protected-packages-as-user-installed-for-al.patch b/0036-Mark-all-protected-packages-as-user-installed-for-al.patch new file mode 100644 index 0000000..351fe76 --- /dev/null +++ b/0036-Mark-all-protected-packages-as-user-installed-for-al.patch @@ -0,0 +1,34 @@ +From 5298228fc2ac50f2172f17ff3f821f7a1a35393d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= +Date: Fri, 9 Jan 2026 07:26:34 +0100 +Subject: [PATCH 1/5] Mark all protected packages as user installed for all + transactions + +Closes: https://github.com/rpm-software-management/dnf/issues/2192 +--- + libdnf/goal/Goal.cpp | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/libdnf/goal/Goal.cpp b/libdnf/goal/Goal.cpp +index ba938e10..3543b3bd 100644 +--- a/libdnf/goal/Goal.cpp ++++ b/libdnf/goal/Goal.cpp +@@ -987,6 +987,15 @@ Goal::jobLength() + bool + Goal::run(DnfGoalActions flags) + { ++ // Automatically mark all protected packages as user installed. ++ // When a protected package is installed as a dependency it can block ++ // removal of the last package that depends on it (because the protected ++ // package cannot be removed, not even as an unused dependency). ++ // To prevent this and still correctly resolve dependencies of the protected ++ // packages mark them all as user installed. ++ if (pImpl->protectedPkgs) { ++ userInstalled(*pImpl->protectedPkgs); ++ } + auto job = pImpl->constructJob(flags); + pImpl->actions = static_cast(pImpl->actions | flags); + int ret = pImpl->solve(job->getQueue(), flags); +-- +2.53.0 + diff --git a/0037-Add-filterUnneededExtraUserinstalled-and-Python-vers.patch b/0037-Add-filterUnneededExtraUserinstalled-and-Python-vers.patch new file mode 100644 index 0000000..8040a47 --- /dev/null +++ b/0037-Add-filterUnneededExtraUserinstalled-and-Python-vers.patch @@ -0,0 +1,149 @@ +From 71c3d69bf56c1fa0726b7b5a2fad20acbf8f1ba9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= +Date: Mon, 26 Jan 2026 14:07:37 +0100 +Subject: [PATCH 2/5] Add `filterUnneededExtraUserinstalled` and Python version + to the API + +This can be used to get unneeded packages with for example protected +packages extra marked as userinstalled. +--- + libdnf/sack/query.cpp | 18 +++++++++++---- + libdnf/sack/query.hpp | 1 + + python/hawkey/query-py.cpp | 47 ++++++++++++++++++++++++++++++++++++++ + 3 files changed, 62 insertions(+), 4 deletions(-) + +diff --git a/libdnf/sack/query.cpp b/libdnf/sack/query.cpp +index 6eecfa50..8dce7082 100644 +--- a/libdnf/sack/query.cpp ++++ b/libdnf/sack/query.cpp +@@ -830,7 +830,7 @@ private: + void filterUpdownByPriority(const Filter & f, Map *m); + void filterUpdownAble(const Filter &f, Map *m); + void filterDataiterator(const Filter & f, Map *m); +- int filterUnneededOrSafeToRemove(const Swdb &swdb, bool debug_solver, bool safeToRemove); ++ int filterUnneededOrSafeToRemove(const Swdb &swdb, bool debug_solver, bool safeToRemove, PackageSet *extra_userinstalled); + void obsoletesByPriority(Pool * pool, Solvable * candidate, Map * m, const Map * target, int obsprovides); + + bool isGlob(const std::vector &matches) const; +@@ -2245,7 +2245,7 @@ Query::Impl::filterDataiterator(const Filter & f, Map *m) + } + + int +-Query::Impl::filterUnneededOrSafeToRemove(const Swdb &swdb, bool debug_solver, bool safeToRemove) ++Query::Impl::filterUnneededOrSafeToRemove(const Swdb &swdb, bool debug_solver, bool safeToRemove, PackageSet *extra_userinstalled) + { + apply(); + Goal goal(sack); +@@ -2260,6 +2260,10 @@ Query::Impl::filterUnneededOrSafeToRemove(const Swdb &swdb, bool debug_solver, b + } + goal.userInstalled(*userInstalled); + ++ if (extra_userinstalled != NULL) { ++ goal.userInstalled(*extra_userinstalled); ++ } ++ + int ret1 = goal.run(DNF_NONE); + if (ret1) + return -1; +@@ -2575,13 +2579,19 @@ Query::filterDuplicated() + int + Query::filterUnneeded(const Swdb &swdb, bool debug_solver) + { +- return pImpl->filterUnneededOrSafeToRemove(swdb, debug_solver, false); ++ return pImpl->filterUnneededOrSafeToRemove(swdb, debug_solver, false, NULL); + } + + int + Query::filterSafeToRemove(const Swdb &swdb, bool debug_solver) + { +- return pImpl->filterUnneededOrSafeToRemove(swdb, debug_solver, true); ++ return pImpl->filterUnneededOrSafeToRemove(swdb, debug_solver, true, NULL); ++} ++ ++int ++Query::filterUnneededExtraUserinstalled(const Swdb &swdb, PackageSet &extra_userinstalled, bool debug_solver) ++{ ++ return pImpl->filterUnneededOrSafeToRemove(swdb, debug_solver, false, &extra_userinstalled); + } + + void +diff --git a/libdnf/sack/query.hpp b/libdnf/sack/query.hpp +index 306b24e3..a5ed7745 100644 +--- a/libdnf/sack/query.hpp ++++ b/libdnf/sack/query.hpp +@@ -177,6 +177,7 @@ public: + void filterRecent(const long unsigned int recent_limit); + void filterDuplicated(); + int filterUnneeded(const Swdb &swdb, bool debug_solver); ++ int filterUnneededExtraUserinstalled(const Swdb &swdb, PackageSet &extra_userinstalled, bool debug_solver); + int filterSafeToRemove(const Swdb &swdb, bool debug_solver); + void getAdvisoryPkgs(int cmpType, std::vector & advisoryPkgs); + void filterUserInstalled(const Swdb &swdb); +diff --git a/python/hawkey/query-py.cpp b/python/hawkey/query-py.cpp +index 99f71bd5..1290a07a 100644 +--- a/python/hawkey/query-py.cpp ++++ b/python/hawkey/query-py.cpp +@@ -809,6 +809,52 @@ filter_unneeded(PyObject *self, PyObject *args, PyObject *kwds) try + return filter_unneeded_or_safe_to_remove(self, args, kwds, false); + } CATCH_TO_PYTHON + ++static PyObject * ++filter_unneeded_extra_userinstalled(PyObject *self, PyObject *args, PyObject *kwds) try ++{ ++ const char *kwlist[] = {"swdb", "extra_userinstalled", "debug_solver", NULL}; ++ PyObject *pySwdb; ++ PyObject *extra_userinstalled; ++ PyObject *debug_solver = NULL; ++ ++ if (!PyArg_ParseTupleAndKeywords( ++ args, kwds, "OO|O!", (char **)kwlist, &pySwdb, &extra_userinstalled, &PyBool_Type, &debug_solver)) { ++ return NULL; ++ } ++ ++ UniquePtrPyObject thisPySwdb(PyObject_GetAttrString(pySwdb, "this")); ++ auto swigSwdb = reinterpret_cast< SwdbSwigPyObject * >(thisPySwdb.get()); ++ if (swigSwdb == nullptr) { ++ PyErr_SetString(PyExc_SystemError, "Unable to parse SwigPyObject"); ++ return NULL; ++ } ++ libdnf::Swdb *swdb = swigSwdb->ptr; ++ if (swdb == NULL) { ++ PyErr_SetString(PyExc_SystemError, "Unable to parse swig object"); ++ return NULL; ++ } ++ ++ HyQuery query = ((_QueryObject *) self)->query; ++ auto extra_userinstalled_pset = pyseq_to_packageset(extra_userinstalled, query->getSack()); ++ if (!extra_userinstalled_pset) { ++ PyErr_SetString(PyExc_SystemError, "Unable to parse SwigPyObject: extra_userinstalled PackageSet"); ++ return NULL; ++ } ++ ++ std::unique_ptr self_query_copy(new libdnf::Query(*query)); ++ gboolean c_debug_solver = debug_solver != NULL && PyObject_IsTrue(debug_solver); ++ ++ int ret = self_query_copy->filterUnneededExtraUserinstalled(*swdb, *extra_userinstalled_pset, c_debug_solver); ++ if (ret == -1) { ++ PyErr_SetString(PyExc_SystemError, "Unable to provide query with unneded filter"); ++ return NULL; ++ } ++ ++ PyObject *final_query = queryToPyObject(self_query_copy.release(), ((_QueryObject *) self)->sack, ++ Py_TYPE(self)); ++ return final_query; ++} CATCH_TO_PYTHON ++ + static PyObject * + q_add(_QueryObject *self, PyObject *list) try + { +@@ -1071,6 +1117,7 @@ static struct PyMethodDef query_methods[] = { + {"_nevra", (PyCFunction)add_nevra_or_other_filter, METH_VARARGS, NULL}, + {"_recent", (PyCFunction)add_filter_recent, METH_VARARGS, NULL}, + {"_unneeded", (PyCFunction)filter_unneeded, METH_KEYWORDS|METH_VARARGS, NULL}, ++ {"_unneeded_extra_userinstalled", (PyCFunction)filter_unneeded_extra_userinstalled, METH_KEYWORDS|METH_VARARGS, NULL}, + {"_safe_to_remove", (PyCFunction)filter_safe_to_remove, METH_KEYWORDS|METH_VARARGS, NULL}, + {NULL} /* sentinel */ + }; +-- +2.53.0 + diff --git a/0038-Describe-all-problems-even-when-there-are-protected-.patch b/0038-Describe-all-problems-even-when-there-are-protected-.patch new file mode 100644 index 0000000..c084282 --- /dev/null +++ b/0038-Describe-all-problems-even-when-there-are-protected-.patch @@ -0,0 +1,31 @@ +From 61066041c2d4e816bc5f4443876af7d9b15d03ec Mon Sep 17 00:00:00 2001 +From: Evan Goode +Date: Tue, 27 Jan 2026 13:00:50 -0500 +Subject: [PATCH 4/5] Describe all problems even when there are protected + removals + +Previously, if a transaction involved removal (or dependency break) of a +protected package, only the protected package problem would be described +by `describeProblemRules`. Information about excluded or versionlocked +packages would be missing. + +For: RHEL-115194 +--- + libdnf/goal/Goal.cpp | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/libdnf/goal/Goal.cpp b/libdnf/goal/Goal.cpp +index 3543b3bd..fb7bd343 100644 +--- a/libdnf/goal/Goal.cpp ++++ b/libdnf/goal/Goal.cpp +@@ -1108,7 +1108,6 @@ Goal::describeProblemRules(unsigned i, bool pkgs) + auto problem = pImpl->describeProtectedRemoval(); + if (!problem.empty()) { + output.push_back(std::move(problem)); +- return output; + } + auto solv = pImpl->solv; + +-- +2.53.0 + diff --git a/0039-Clearer-error-for-protected-package-broken-dependenc.patch b/0039-Clearer-error-for-protected-package-broken-dependenc.patch new file mode 100644 index 0000000..6695a7a --- /dev/null +++ b/0039-Clearer-error-for-protected-package-broken-dependenc.patch @@ -0,0 +1,60 @@ +From 5f5dbd01e93d1ec830d5d071051d1a98d2357227 Mon Sep 17 00:00:00 2001 +From: Evan Goode +Date: Tue, 27 Jan 2026 13:19:14 -0500 +Subject: [PATCH 5/5] Clearer error for protected package broken dependencies + +The error message now distinguishes removal of a protected package from +breaking dependencies of protected packages. +--- + libdnf/goal/Goal.cpp | 17 +++++++++++------ + 1 file changed, 11 insertions(+), 6 deletions(-) + +diff --git a/libdnf/goal/Goal.cpp b/libdnf/goal/Goal.cpp +index fb7bd343..5ae86db5 100644 +--- a/libdnf/goal/Goal.cpp ++++ b/libdnf/goal/Goal.cpp +@@ -1697,11 +1697,11 @@ Goal::Impl::protectedInRemovals() + std::string + Goal::Impl::describeProtectedRemoval() + { +- std::string message(_("The operation would result in removing" +- " the following protected packages: ")); + Pool * pool = solv->pool; + + if (removalOfProtected && removalOfProtected->size()) { ++ const std::string removal_message(_("The operation would result in removing " ++ "the following protected packages: ")); + Id id = -1; + std::vector names; + while((id = removalOfProtected->next(id)) != -1) { +@@ -1711,9 +1711,13 @@ Goal::Impl::describeProtectedRemoval() + if (names.empty()) { + return {}; + } +- return message + std::accumulate(std::next(names.begin()), names.end(), +- std::string(names[0]), [](std::string a, std::string b) { return a + ", " + b; }); ++ return removal_message + ++ std::accumulate(std::next(names.begin()), names.end(), std::string(names[0]), ++ [](std::string a, std::string b) { return a + ", " + b; }); + } ++ ++ const std::string broken_dependency_message(_("The operation would result in broken " ++ "dependencies for the following protected packages: ")); + auto pset = brokenDependencyAllPkgs(DNF_PACKAGE_STATE_INSTALLED); + Id id = -1; + Id protected_kernel = protectedRunningKernel(); +@@ -1726,8 +1730,9 @@ Goal::Impl::describeProtectedRemoval() + } + if (names.empty()) + return {}; +- return message + std::accumulate(std::next(names.begin()), names.end(), std::string(names[0]), +- [](std::string a, std::string b) { return a + ", " + b; }); ++ return broken_dependency_message + ++ std::accumulate(std::next(names.begin()), names.end(), std::string(names[0]), ++ [](std::string a, std::string b) { return a + ", " + b; }); + } + + } +-- +2.53.0 + diff --git a/0040-Goal-set-protected-as-userinstalled-only-for-the-tem.patch b/0040-Goal-set-protected-as-userinstalled-only-for-the-tem.patch new file mode 100644 index 0000000..7754302 --- /dev/null +++ b/0040-Goal-set-protected-as-userinstalled-only-for-the-tem.patch @@ -0,0 +1,57 @@ +From ac1121bc58f779838713c0b0a39b6cc929d141c3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= +Date: Tue, 10 Feb 2026 13:04:42 +0100 +Subject: [PATCH] Goal: set protected as userinstalled only for the temporary + job + +This way the goal's `staging` is not affected. +--- + libdnf/goal/Goal.cpp | 25 ++++++++++++++++--------- + 1 file changed, 16 insertions(+), 9 deletions(-) + +diff --git a/libdnf/goal/Goal.cpp b/libdnf/goal/Goal.cpp +index 5ae86db5..7766b976 100644 +--- a/libdnf/goal/Goal.cpp ++++ b/libdnf/goal/Goal.cpp +@@ -987,15 +987,6 @@ Goal::jobLength() + bool + Goal::run(DnfGoalActions flags) + { +- // Automatically mark all protected packages as user installed. +- // When a protected package is installed as a dependency it can block +- // removal of the last package that depends on it (because the protected +- // package cannot be removed, not even as an unused dependency). +- // To prevent this and still correctly resolve dependencies of the protected +- // packages mark them all as user installed. +- if (pImpl->protectedPkgs) { +- userInstalled(*pImpl->protectedPkgs); +- } + auto job = pImpl->constructJob(flags); + pImpl->actions = static_cast(pImpl->actions | flags); + int ret = pImpl->solve(job->getQueue(), flags); +@@ -1401,6 +1392,22 @@ Goal::Impl::constructJob(DnfGoalActions flags) + + allowUninstallAllButProtected(job->getQueue(), flags); + ++ // Automatically mark all protected packages as user installed. ++ // When a protected package is installed as a dependency it can block ++ // removal of the last package that depends on it (because the protected ++ // package cannot be removed, not even as an unused dependency). ++ // To prevent this and still correctly resolve dependencies of the protected ++ // packages mark them all as user installed. ++ if (protectedPkgs) { ++ Id id = -1; ++ while (true) { ++ id = protectedPkgs->next(id); ++ if (id == -1) ++ break; ++ queue_push2(job->getQueue(), SOLVER_SOLVABLE|SOLVER_USERINSTALLED, id); ++ } ++ } ++ + if (flags & DNF_VERIFY) + job->pushBack(SOLVER_VERIFY|SOLVER_SOLVABLE_ALL, 0); + +-- +2.53.0 + diff --git a/libdnf.spec b/libdnf.spec index 815bc34..107ff8a 100644 --- a/libdnf.spec +++ b/libdnf.spec @@ -58,7 +58,7 @@ Name: libdnf Version: %{libdnf_major_version}.%{libdnf_minor_version}.%{libdnf_micro_version} -Release: 17%{?dist} +Release: 18%{?dist} Summary: Library providing simplified C and Python API to libsolv License: LGPLv2+ URL: https://github.com/rpm-software-management/libdnf @@ -97,6 +97,12 @@ Patch31: 0031-C-API-Use-releasever_-major-minor-from-context-inste.patch Patch32: 0032-C-API-support-shell-style-variable-substitution.patch Patch33: 0033-C-API-test-shell-style-variable-expressions.patch Patch34: 0034-fix-compare-RPMItem-in-transaction-with-rpmvercmp.patch +Patch35: 0035-Fix-dnf_keyring_add_public_keys-reset-GError-to-NULL.patch +Patch36: 0036-Mark-all-protected-packages-as-user-installed-for-al.patch +Patch37: 0037-Add-filterUnneededExtraUserinstalled-and-Python-vers.patch +Patch38: 0038-Describe-all-problems-even-when-there-are-protected-.patch +Patch39: 0039-Clearer-error-for-protected-package-broken-dependenc.patch +Patch40: 0040-Goal-set-protected-as-userinstalled-only-for-the-tem.patch BuildRequires: cmake @@ -346,6 +352,13 @@ popd %endif %changelog +* Mon Feb 09 2026 Ales Matej - 0.69.0-18 +- Fix a crash when parsing multiple key files fails (RHEL-145618) +- Mark all protected packages as user installed for all transactions (RHEL-76112) +- Add `filterUnneededExtraUserinstalled` and Python version to the API +- Describe all problems even when there are protected removals (RHEL-115194) +- Clearer error for protected package broken dependencies + * Tue Feb 03 2026 Matej Focko - 0.69.0-17 - Fix comparison of RPM items in the transaction (RHEL-81779)