From 56ee44e81b582b6730d6bee22e41fccb7b9c091c Mon Sep 17 00:00:00 2001 From: Evan Goode Date: Tue, 12 Sep 2023 21:32:15 +0000 Subject: [PATCH] Initial implementation of needs-restarting For https://github.com/rpm-software-management/dnf5/issues/743 Resolves https://github.com/rpm-software-management/dnf5/issues/587 --- dnf5-plugins/CMakeLists.txt | 1 + .../needs_restarting_plugin/CMakeLists.txt | 12 ++ .../needs_restarting.cpp | 112 ++++++++++++++++++ .../needs_restarting.hpp | 47 ++++++++ .../needs_restarting_cmd_plugin.cpp | 74 ++++++++++++ include/libdnf5/rpm/nevra.hpp | 41 ++++--- include/libdnf5/rpm/package_query.hpp | 4 + libdnf5/rpm/package_query.cpp | 38 ++++++ 8 files changed, 311 insertions(+), 18 deletions(-) create mode 100644 dnf5-plugins/needs_restarting_plugin/CMakeLists.txt create mode 100644 dnf5-plugins/needs_restarting_plugin/needs_restarting.cpp create mode 100644 dnf5-plugins/needs_restarting_plugin/needs_restarting.hpp create mode 100644 dnf5-plugins/needs_restarting_plugin/needs_restarting_cmd_plugin.cpp 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..0371ee079 --- /dev/null +++ b/dnf5-plugins/needs_restarting_plugin/needs_restarting.cpp @@ -0,0 +1,112 @@ +/* +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_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; + } + throw libdnf5::cli::SilentCommandExitError(1); + } +} + +} // 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 560f32063..13c94b9a8 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 e8cf7a9d1..6f926f018 100644 --- a/libdnf5/rpm/package_query.cpp +++ b/libdnf5/rpm/package_query.cpp @@ -2828,4 +2828,42 @@ void PackageQuery::filter_installonly() { filter_provides(installonly_packages, libdnf5::sack::QueryCmp::GLOB); } +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