diff --git a/dnf5-plugins/CMakeLists.txt b/dnf5-plugins/CMakeLists.txt
index 4c9571d68..90dc9fd79 100644
--- a/dnf5-plugins/CMakeLists.txt
+++ b/dnf5-plugins/CMakeLists.txt
@@ -7,4 +7,5 @@ include_directories("${PROJECT_SOURCE_DIR}/dnf5/include/")
add_subdirectory("builddep_plugin")
add_subdirectory("changelog_plugin")
add_subdirectory("copr_plugin")
+add_subdirectory("needs_restarting_plugin")
add_subdirectory("repoclosure_plugin")
diff --git a/dnf5-plugins/needs_restarting_plugin/CMakeLists.txt b/dnf5-plugins/needs_restarting_plugin/CMakeLists.txt
new file mode 100644
index 000000000..f3282f5f2
--- /dev/null
+++ b/dnf5-plugins/needs_restarting_plugin/CMakeLists.txt
@@ -0,0 +1,12 @@
+# set gettext domain for translations
+add_definitions(-DGETTEXT_DOMAIN=\"dnf5_cmd_needs_restarting\")
+
+add_library(needs_restarting_cmd_plugin MODULE needs_restarting.cpp needs_restarting_cmd_plugin.cpp)
+
+# disable the 'lib' prefix in order to create needs_restarting_cmd_plugin.so
+set_target_properties(needs_restarting_cmd_plugin PROPERTIES PREFIX "")
+
+target_link_libraries(needs_restarting_cmd_plugin PRIVATE libdnf5 libdnf5-cli)
+target_link_libraries(needs_restarting_cmd_plugin PRIVATE dnf5)
+
+install(TARGETS needs_restarting_cmd_plugin LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}/dnf5/plugins/)
diff --git a/dnf5-plugins/needs_restarting_plugin/needs_restarting.cpp b/dnf5-plugins/needs_restarting_plugin/needs_restarting.cpp
new file mode 100644
index 000000000..4d3713d82
--- /dev/null
+++ b/dnf5-plugins/needs_restarting_plugin/needs_restarting.cpp
@@ -0,0 +1,113 @@
+/*
+Copyright Contributors to the libdnf project.
+
+This file is part of libdnf: https://github.com/rpm-software-management/libdnf/
+
+Libdnf is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+Libdnf is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with libdnf. If not, see .
+*/
+
+#include "needs_restarting.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+namespace dnf5 {
+
+using namespace libdnf5::cli;
+
+void NeedsRestartingCommand::set_parent_command() {
+ auto * arg_parser_parent_cmd = get_session().get_argument_parser().get_root_command();
+ auto * arg_parser_this_cmd = get_argument_parser_command();
+ arg_parser_parent_cmd->register_command(arg_parser_this_cmd);
+}
+
+void NeedsRestartingCommand::set_argument_parser() {
+ auto & cmd = *get_argument_parser_command();
+ cmd.set_description("Determine whether system or systemd services need restarting");
+}
+
+void NeedsRestartingCommand::configure() {
+ auto & context = get_context();
+ context.set_load_system_repo(true);
+
+ context.set_load_available_repos(Context::LoadAvailableRepos::ENABLED);
+ context.base.get_config().get_optional_metadata_types_option().add(
+ libdnf5::Option::Priority::RUNTIME, libdnf5::OPTIONAL_METADATA_TYPES);
+
+ context.base.get_config().get_optional_metadata_types_option().add_item(
+ libdnf5::Option::Priority::RUNTIME, libdnf5::METADATA_TYPE_UPDATEINFO);
+}
+
+time_t get_boot_time() {
+ time_t proc_1_boot_time = 0;
+ struct stat proc_1_stat = {};
+ if (stat("/proc/1", &proc_1_stat) == 0) {
+ proc_1_boot_time = proc_1_stat.st_mtime;
+ }
+
+ time_t uptime_boot_time = 0;
+ std::ifstream uptime_stream{"/proc/uptime"};
+ if (uptime_stream.is_open()) {
+ double uptime = 0;
+ uptime_stream >> uptime;
+ if (uptime > 0) {
+ uptime_boot_time = std::time(nullptr) - static_cast(uptime);
+ }
+ }
+
+ return std::max(proc_1_boot_time, uptime_boot_time);
+}
+
+void NeedsRestartingCommand::run() {
+ auto & ctx = get_context();
+
+ const auto boot_time = get_boot_time();
+
+ libdnf5::rpm::PackageQuery base_query{ctx.base};
+
+ libdnf5::rpm::PackageQuery installed{base_query};
+ installed.filter_installed();
+
+ libdnf5::rpm::PackageQuery reboot_suggested{installed};
+ reboot_suggested.filter_reboot_suggested();
+
+ std::vector need_reboot = {};
+ for (const auto & pkg : reboot_suggested) {
+ if (pkg.get_install_time() > static_cast(boot_time)) {
+ need_reboot.push_back(pkg);
+ }
+ }
+
+ if (need_reboot.empty()) {
+ std::cout << "No core libraries or services have been updated since boot-up." << std::endl
+ << "Reboot should not be necessary." << std::endl;
+ } else {
+ std::cout << "Core libraries or services have been updated since boot-up:" << std::endl;
+ for (const auto & pkg : need_reboot) {
+ std::cout << "\t" << pkg.get_name() << std::endl;
+ }
+ }
+}
+
+} // namespace dnf5
diff --git a/dnf5-plugins/needs_restarting_plugin/needs_restarting.hpp b/dnf5-plugins/needs_restarting_plugin/needs_restarting.hpp
new file mode 100644
index 000000000..b76cdf8a4
--- /dev/null
+++ b/dnf5-plugins/needs_restarting_plugin/needs_restarting.hpp
@@ -0,0 +1,47 @@
+/*
+Copyright Contributors to the libdnf project.
+
+This file is part of libdnf: https://github.com/rpm-software-management/libdnf/
+
+Libdnf is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+Libdnf is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with libdnf. If not, see .
+*/
+
+#ifndef DNF5_COMMANDS_NEEDS_RESTARTING_HPP
+#define DNF5_COMMANDS_NEEDS_RESTARTING_HPP
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+namespace dnf5 {
+
+class NeedsRestartingCommand : public Command {
+public:
+ explicit NeedsRestartingCommand(Context & context) : Command(context, "needs-restarting") {}
+ void set_parent_command() override;
+ void set_argument_parser() override;
+ void configure() override;
+ void run() override;
+
+private:
+};
+
+} // namespace dnf5
+
+#endif // DNF5_COMMANDS_CHANGELOG_HPP
diff --git a/dnf5-plugins/needs_restarting_plugin/needs_restarting_cmd_plugin.cpp b/dnf5-plugins/needs_restarting_plugin/needs_restarting_cmd_plugin.cpp
new file mode 100644
index 000000000..07fd8e88b
--- /dev/null
+++ b/dnf5-plugins/needs_restarting_plugin/needs_restarting_cmd_plugin.cpp
@@ -0,0 +1,74 @@
+#include "needs_restarting.hpp"
+
+#include
+
+#include
+
+using namespace dnf5;
+
+namespace {
+
+constexpr const char * PLUGIN_NAME{"needs_restarting"};
+constexpr PluginVersion PLUGIN_VERSION{.major = 1, .minor = 0, .micro = 0};
+
+constexpr const char * attrs[]{"author.name", "author.email", "description", nullptr};
+constexpr const char * attrs_value[]{"Evan Goode", "egoode@redhat.com", "needs_restarting command."};
+
+class NeedsRestartingCmdPlugin : public IPlugin {
+public:
+ using IPlugin::IPlugin;
+
+ PluginAPIVersion get_api_version() const noexcept override { return PLUGIN_API_VERSION; }
+
+ const char * get_name() const noexcept override { return PLUGIN_NAME; }
+
+ PluginVersion get_version() const noexcept override { return PLUGIN_VERSION; }
+
+ const char * const * get_attributes() const noexcept override { return attrs; }
+
+ const char * get_attribute(const char * attribute) const noexcept override {
+ for (size_t i = 0; attrs[i]; ++i) {
+ if (std::strcmp(attribute, attrs[i]) == 0) {
+ return attrs_value[i];
+ }
+ }
+ return nullptr;
+ }
+
+ std::vector> create_commands() override;
+
+ void finish() noexcept override {}
+};
+
+
+std::vector> NeedsRestartingCmdPlugin::create_commands() {
+ std::vector> commands;
+ commands.push_back(std::make_unique(get_context()));
+ return commands;
+}
+
+
+} // namespace
+
+
+PluginAPIVersion dnf5_plugin_get_api_version(void) {
+ return PLUGIN_API_VERSION;
+}
+
+const char * dnf5_plugin_get_name(void) {
+ return PLUGIN_NAME;
+}
+
+PluginVersion dnf5_plugin_get_version(void) {
+ return PLUGIN_VERSION;
+}
+
+IPlugin * dnf5_plugin_new_instance([[maybe_unused]] ApplicationVersion application_version, Context & context) try {
+ return new NeedsRestartingCmdPlugin(context);
+} catch (...) {
+ return nullptr;
+}
+
+void dnf5_plugin_delete_instance(IPlugin * plugin_object) {
+ delete plugin_object;
+}
diff --git a/include/libdnf5/rpm/nevra.hpp b/include/libdnf5/rpm/nevra.hpp
index 996f9f450..ab9a17ac1 100644
--- a/include/libdnf5/rpm/nevra.hpp
+++ b/include/libdnf5/rpm/nevra.hpp
@@ -213,16 +213,16 @@ int evrcmp(const L & lhs, const R & rhs) {
return rpmvercmp(lhs.get_release().c_str(), rhs.get_release().c_str());
}
-
/// Compare two objects by their Name, Epoch:Version-Release and Arch.
/// @return `true` if `lhs` < `rhs`. Return `false` otherwise.
-template
-bool cmp_nevra(const T & lhs, const T & rhs) {
+template
+bool cmp_nevra(const L & lhs, const R & rhs) {
// compare by name
int r = lhs.get_name().compare(rhs.get_name());
if (r < 0) {
return true;
- } else if (r > 0) {
+ }
+ if (r > 0) {
return false;
}
@@ -230,28 +230,31 @@ bool cmp_nevra(const T & lhs, const T & rhs) {
r = evrcmp(lhs, rhs);
if (r < 0) {
return true;
- } else if (r > 0) {
+ }
+ if (r > 0) {
return false;
}
// names and evrs are equal, compare by arch
r = lhs.get_arch().compare(rhs.get_arch());
- if (r < 0) {
- return true;
- }
- return false;
+ return r < 0;
};
+template
+bool cmp_nevra(const T & lhs, const T & rhs) {
+ return cmp_nevra(lhs, rhs);
+}
/// Compare two objects by their Name, Arch and Epoch:Version-Release.
/// @return `true` if `lhs` < `rhs`. Return `false` otherwise.
-template
-bool cmp_naevr(const T & lhs, const T & rhs) {
+template
+bool cmp_naevr(const L & lhs, const R & rhs) {
// compare by name
int r = lhs.get_name().compare(rhs.get_name());
if (r < 0) {
return true;
- } else if (r > 0) {
+ }
+ if (r > 0) {
return false;
}
@@ -259,19 +262,21 @@ bool cmp_naevr(const T & lhs, const T & rhs) {
r = lhs.get_arch().compare(rhs.get_arch());
if (r < 0) {
return true;
- } else if (r > 0) {
+ }
+ if (r > 0) {
return false;
}
// names and arches are equal, compare by evr
r = evrcmp(lhs, rhs);
- if (r < 0) {
- return true;
- }
-
- return false;
+ return r < 0;
};
+template
+bool cmp_naevr(const T & lhs, const T rhs) {
+ return cmp_naevr(lhs, rhs);
+}
+
} // namespace libdnf5::rpm
#endif // LIBDNF5_RPM_NEVRA_HPP
diff --git a/include/libdnf5/rpm/package_query.hpp b/include/libdnf5/rpm/package_query.hpp
index 17bd246c2..956735665 100644
--- a/include/libdnf5/rpm/package_query.hpp
+++ b/include/libdnf5/rpm/package_query.hpp
@@ -31,6 +31,7 @@ along with libdnf. If not, see .
#include "libdnf5/common/sack/exclude_flags.hpp"
#include "libdnf5/common/sack/query_cmp.hpp"
+#include
#include
#include
@@ -680,6 +681,9 @@ class PackageQuery : public PackageSet {
/// in such cycles that are not required by any other installed package are also leaf.
void filter_leaves();
+ /// TODO reboot suggested
+ void filter_reboot_suggested();
+
/// Filter the leaf packages and return them grouped by their dependencies.
///
/// Leaf packages are installed packages that are not required as a dependency of another installed package.
diff --git a/libdnf5/rpm/package_query.cpp b/libdnf5/rpm/package_query.cpp
index 2e1cd7099..1f0e6ab09 100644
--- a/libdnf5/rpm/package_query.cpp
+++ b/libdnf5/rpm/package_query.cpp
@@ -2810,5 +2810,42 @@ void PackageQuery::filter_extras(const bool exact_evr) {
}
}
+static const std::unordered_set CORE_PACKAGE_NAMES = {
+ "kernel",
+ "kernel-rt",
+ "kernel-core",
+ "glibc",
+ "linux-firmware",
+ "systemd",
+ "dbus",
+ "dbus-broker",
+ "dbus-daemon",
+};
+
+void PackageQuery::filter_reboot_suggested() {
+ auto & pool = get_rpm_pool(p_impl->base);
+ libdnf5::solv::SolvMap filter_result{pool.get_nsolvables()};
+
+ for (const auto & pkg : *this) {
+ if (CORE_PACKAGE_NAMES.contains(pkg.get_name())) {
+ filter_result.add_unsafe(pkg.get_id().id);
+ }
+ }
+
+ libdnf5::advisory::AdvisoryQuery advisories{p_impl->base};
+ auto reboot_advisories =
+ advisories.get_advisory_packages_sorted_by_name_arch_evr() |
+ std::views::filter([](const auto & advisory_pkg) { return advisory_pkg.get_reboot_suggested(); });
+
+ const auto & cmp_naevr = libdnf5::rpm::cmp_naevr;
+ for (const auto & pkg : *this) {
+ auto lower = std::lower_bound(reboot_advisories.begin(), reboot_advisories.end(), pkg, cmp_naevr);
+ if (lower != reboot_advisories.end() && lower->get_nevra() == pkg.get_nevra()) {
+ filter_result.add_unsafe(pkg.get_id().id);
+ }
+ }
+
+ *p_impl &= filter_result;
+}
} // namespace libdnf5::rpm