From dc8d0b95fda1b1830fb567f2c7ab649d42bf767d Mon Sep 17 00:00:00 2001 From: Evan Goode Date: Mon, 11 Dec 2023 22:17:14 +0000 Subject: [PATCH 1/7] system-upgrade: preliminary functionality --- dnf5-plugins/CMakeLists.txt | 1 + .../needs_restarting.hpp | 2 +- .../system_upgrade_plugin/CMakeLists.txt | 12 + .../system_upgrade_plugin/system_upgrade.cpp | 237 ++++++++++++++++++ .../system_upgrade_plugin/system_upgrade.hpp | 111 ++++++++ .../system_upgrade_cmd_plugin.cpp | 72 ++++++ 6 files changed, 434 insertions(+), 1 deletion(-) create mode 100644 dnf5-plugins/system_upgrade_plugin/CMakeLists.txt create mode 100644 dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp create mode 100644 dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp create mode 100644 dnf5-plugins/system_upgrade_plugin/system_upgrade_cmd_plugin.cpp diff --git a/dnf5-plugins/CMakeLists.txt b/dnf5-plugins/CMakeLists.txt index 433002674..ce8504276 100644 --- a/dnf5-plugins/CMakeLists.txt +++ b/dnf5-plugins/CMakeLists.txt @@ -13,3 +13,4 @@ add_subdirectory("config-manager_plugin") add_subdirectory("copr_plugin") add_subdirectory("needs_restarting_plugin") add_subdirectory("repoclosure_plugin") +add_subdirectory("system_upgrade_plugin") diff --git a/dnf5-plugins/needs_restarting_plugin/needs_restarting.hpp b/dnf5-plugins/needs_restarting_plugin/needs_restarting.hpp index 0ea4cbeb0..d9e806332 100644 --- a/dnf5-plugins/needs_restarting_plugin/needs_restarting.hpp +++ b/dnf5-plugins/needs_restarting_plugin/needs_restarting.hpp @@ -46,4 +46,4 @@ class NeedsRestartingCommand : public Command { } // namespace dnf5 -#endif // DNF5_COMMANDS_CHANGELOG_HPP +#endif // DNF5_COMMANDS_NEEDS_RESTARTING_HPP diff --git a/dnf5-plugins/system_upgrade_plugin/CMakeLists.txt b/dnf5-plugins/system_upgrade_plugin/CMakeLists.txt new file mode 100644 index 000000000..2b58babb1 --- /dev/null +++ b/dnf5-plugins/system_upgrade_plugin/CMakeLists.txt @@ -0,0 +1,12 @@ +# set gettext domain for translations +add_definitions(-DGETTEXT_DOMAIN=\"dnf5_cmd_system_upgrade\") + +file(GLOB SYSTEM_UPGRADE_SOURCES *.cpp) +add_library(system_upgrade_cmd_plugin MODULE ${SYSTEM_UPGRADE_SOURCES}) + +# disable the 'lib' prefix in order to create copr_cmd_plugin.so +set_target_properties(system_upgrade_cmd_plugin PROPERTIES PREFIX "") + +target_link_libraries(system_upgrade_cmd_plugin PRIVATE dnf5 libdnf5 libdnf5-cli) + +install(TARGETS system_upgrade_cmd_plugin LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}/dnf5/plugins/) diff --git a/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp b/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp new file mode 100644 index 000000000..3627bcc0f --- /dev/null +++ b/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp @@ -0,0 +1,237 @@ +/* +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 "system_upgrade.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +/* State::State(std::filesystem::path path) : path(std::move(path)) { */ +/* read(); */ +/* } */ +/* void State::read() { */ +/* auto toml_value = toml::parse(path); */ +/* } */ +/* void State::write() { */ + +/* } */ + +namespace dnf5 { + +using namespace libdnf5::cli; + +void SystemUpgradeCommand::pre_configure() { + throw_missing_command(); +} + +void SystemUpgradeCommand::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); + arg_parser_parent_cmd->get_group("subcommands").register_argument(arg_parser_this_cmd); +} + +void SystemUpgradeCommand::set_argument_parser() { + get_argument_parser_command()->set_description("Prepare system for upgrade to a new release"); +} + +void SystemUpgradeCommand::register_subcommands() { + register_subcommand(std::make_unique(get_context())); + register_subcommand(std::make_unique(get_context())); +} + +SystemUpgradeSubcommand::SystemUpgradeSubcommand(Context & context, const std::string & name) : Command(context, name) { + data_dir = std::filesystem::path(libdnf5::SYSTEM_STATE_DIR) / "system-upgrade"; +} + +void SystemUpgradeSubcommand::set_argument_parser() { + auto & ctx = get_context(); + auto & parser = ctx.get_argument_parser(); + auto & cmd = *get_argument_parser_command(); + + download_dir = + dynamic_cast(parser.add_init_value(std::make_unique(data_dir))); + + auto * download_dir_arg = parser.add_new_named_arg("downloaddir"); + download_dir_arg->set_long_name("downloaddir"); + download_dir_arg->set_description("Redirect download of packages to provided "); + download_dir_arg->link_value(download_dir); + cmd.register_named_arg(download_dir_arg); +} + +void SystemUpgradeSubcommand::configure() { + auto & ctx = get_context(); + const std::filesystem::path installroot{ctx.base.get_config().get_installroot_option().get_value()}; + magic_symlink = installroot / "system-update"; +} + +void SystemUpgradeDownloadCommand::set_argument_parser() { + SystemUpgradeSubcommand::set_argument_parser(); + + auto & ctx = get_context(); + auto & parser = ctx.get_argument_parser(); + auto & cmd = *get_argument_parser_command(); + + cmd.set_description("Downloads everything needed to upgrade to a new release"); + + no_downgrade = + dynamic_cast(parser.add_init_value(std::make_unique(true))); + + auto * no_downgrade_arg = parser.add_new_named_arg("no-downgrade"); + no_downgrade_arg->set_long_name("no-downgrade"); + no_downgrade_arg->set_description( + "Do not install packages from the new release if they are older than what is currently installed"); + no_downgrade_arg->link_value(no_downgrade); + + cmd.register_named_arg(no_downgrade_arg); +} + +void SystemUpgradeDownloadCommand::configure() { + SystemUpgradeSubcommand::configure(); + + auto & ctx = get_context(); + auto & conf = ctx.base.get_config(); + + // Check --releasever + const auto & installroot = conf.get_installroot_option().get_value(); + const auto current_release = *libdnf5::Vars::detect_release(ctx.base.get_weak_ptr(), installroot); + /* const auto & desired_release = ctx.base.get_vars()->get_value("releasever"); */ + + // TODO: uncomment this. Easier to test with it commented out. + /* if (desired_release == current_release) { */ + /* throw libdnf5::cli::CommandExitError(1, M_("Need a --releasever greater than the current system version.")); */ + /* } */ + + ctx.set_load_system_repo(true); + ctx.set_load_available_repos(Context::LoadAvailableRepos::ENABLED); + + ctx.base.get_config().get_cachedir_option().set( + libdnf5::Option::Priority::PLUGINDEFAULT, get_download_dir()->get_value()); +} + +void SystemUpgradeDownloadCommand::run() { + auto & ctx = get_context(); + + const auto & goal = std::make_unique(ctx.base); + + auto & conf = ctx.base.get_config(); + conf.opt_binds().at("tsflags").new_string(libdnf5::Option::Priority::RUNTIME, "test"); + + if (no_downgrade->get_value()) { + const auto & settings = libdnf5::GoalJobSettings(); + goal->add_rpm_upgrade(settings); + } else { + goal->add_rpm_distro_sync(); + } + + auto transaction = goal->resolve(); + if (transaction.get_problems() != libdnf5::GoalProblem::NO_PROBLEM) { + throw libdnf5::cli::GoalResolveError(transaction); + } + + ctx.download_and_run(transaction); + + const std::string json = transaction.serialize(); + if (json.empty()) { + throw libdnf5::cli::CommandExitError(1, M_("Transaction is empty.")); + } + + const auto & transaction_json_path = std::filesystem::path(get_data_dir()) / "system-upgrade-transaction.json"; + auto transaction_json_file = libdnf5::utils::fs::File(transaction_json_path, "w"); + + transaction_json_file.write(json); + transaction_json_file.close(); +} + +void reboot(Context & ctx, bool poweroff = false) { + const std::string systemd_destination_name{"org.freedesktop.systemd1"}; + const std::string systemd_object_path{"/org/freedesktop/systemd1"}; + const std::string systemd_manager_interface{"org.freedesktop.systemd1.Manager"}; + + if (std::getenv("DNF_SYSTEM_UPGRADE_NO_REBOOT")) { + ctx.base.get_logger()->info("Reboot turned off, not rebooting."); + return; + } + + std::unique_ptr connection; + try { + connection = sdbus::createSystemBusConnection(); + } catch (const sdbus::Error & ex) { + const std::string error_message{ex.what()}; + throw libdnf5::cli::CommandExitError(1, M_("Couldn't connect to D-Bus: {}"), error_message); + } + auto proxy = sdbus::createProxy(systemd_destination_name, systemd_object_path); + if (poweroff) { + proxy->callMethod("Poweroff").onInterface(systemd_manager_interface); + } else { + proxy->callMethod("Reboot").onInterface(systemd_manager_interface); + } +} + +void SystemUpgradeRebootCommand::run() { + std::filesystem::create_symlink(get_data_dir(), get_magic_symlink()); + reboot(get_context()); +} + +void SystemUpgradeUpgradeCommand::set_argument_parser() {} + +void SystemUpgradeUpgradeCommand::configure() { + auto & ctx = get_context(); + + ctx.set_load_system_repo(true); + ctx.set_load_available_repos(Context::LoadAvailableRepos::ENABLED); + + ctx.base.get_config().get_cacheonly_option().set(libdnf5::Option::Priority::PLUGINDEFAULT, "all"); + ctx.base.get_config().get_assumeno_option().set(libdnf5::Option::Priority::PLUGINDEFAULT, false); + ctx.base.get_config().get_assumeyes_option().set(libdnf5::Option::Priority::PLUGINDEFAULT, true); + ctx.base.get_config().get_clean_requirements_on_remove_option().set( + libdnf5::Option::Priority::PLUGINDEFAULT, false); + ctx.base.get_config().get_install_weak_deps_option().set(libdnf5::Option::Priority::PLUGINDEFAULT, false); +} + +void SystemUpgradeUpgradeCommand::run() { + auto & ctx = get_context(); + + const auto & transaction_json_path = std::filesystem::path(get_data_dir()) / "system-upgrade-transaction.json"; + auto transaction_json_file = libdnf5::utils::fs::File(transaction_json_path, "r"); + + const auto & json = transaction_json_file.read(); + transaction_json_file.close(); + + const auto & goal = std::make_unique(ctx.base); + + auto settings = libdnf5::GoalJobSettings(); + goal->add_serialized_transaction(json, settings); + + auto transaction = goal->resolve(); + if (transaction.get_problems() != libdnf5::GoalProblem::NO_PROBLEM) { + throw libdnf5::cli::GoalResolveError(transaction); + } + + transaction.run(); +} + +} // namespace dnf5 diff --git a/dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp b/dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp new file mode 100644 index 000000000..d936d9e26 --- /dev/null +++ b/dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp @@ -0,0 +1,111 @@ +/* +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_SYSTEM_UPGRADE_HPP +#define DNF5_COMMANDS_SYSTEM_UPGRADE_HPP + +#include +#include +#include + +/* #include */ +/* #include */ + +/* class State { */ +/* public: */ +/* State(std::filesystem::path path); */ +/* private: */ +/* void read(); */ +/* void write(); */ +/* std::map data; */ +/* std::filesystem::path path; */ +/* }; */ + +namespace dnf5 { + +class SystemUpgradeCommand : public Command { +public: + explicit SystemUpgradeCommand(Context & context) : Command(context, "system-upgrade") {} + void set_parent_command() override; + void set_argument_parser() override; + void register_subcommands() override; + void pre_configure() override; +}; + +class SystemUpgradeSubcommand : public Command { +public: + explicit SystemUpgradeSubcommand(Context & context, const std::string & name); + void set_argument_parser() override; + void configure() override; + +protected: + libdnf5::OptionPath * get_download_dir() { return download_dir; }; + std::filesystem::path get_data_dir() { return data_dir; }; + std::filesystem::path get_magic_symlink() { return magic_symlink; }; + +private: + libdnf5::OptionPath * download_dir{nullptr}; + std::filesystem::path data_dir; + std::filesystem::path magic_symlink; +}; + +class SystemUpgradeDownloadCommand : public SystemUpgradeSubcommand { +public: + explicit SystemUpgradeDownloadCommand(Context & context) : SystemUpgradeSubcommand(context, "download") {} + void set_argument_parser() override; + void configure() override; + void run() override; + +private: + libdnf5::OptionBool * no_downgrade{nullptr}; + libdnf5::OptionBool * distro_sync{nullptr}; +}; + +class SystemUpgradeRebootCommand : public SystemUpgradeSubcommand { +public: + explicit SystemUpgradeRebootCommand(Context & context) : SystemUpgradeSubcommand(context, "reboot") {} + void set_argument_parser() override; + void run() override; +}; + +class SystemUpgradeUpgradeCommand : public SystemUpgradeSubcommand { +public: + explicit SystemUpgradeUpgradeCommand(Context & context) : SystemUpgradeSubcommand(context, "upgrade") {} + void set_argument_parser() override; + void configure() override; + void run() override; +}; + +class SystemUpgradeCleanCommand : public SystemUpgradeSubcommand { +public: + explicit SystemUpgradeCleanCommand(Context & context) : SystemUpgradeSubcommand(context, "clean") {} + void set_argument_parser() override; + void run() override; +}; + +class SystemUpgradeLogCommand : public SystemUpgradeSubcommand { +public: + explicit SystemUpgradeLogCommand(Context & context) : SystemUpgradeSubcommand(context, "log") {} + void set_argument_parser() override; + void run() override; +}; + +} // namespace dnf5 + +#endif // DNF5_COMMANDS_CHANGELOG_HPP diff --git a/dnf5-plugins/system_upgrade_plugin/system_upgrade_cmd_plugin.cpp b/dnf5-plugins/system_upgrade_plugin/system_upgrade_cmd_plugin.cpp new file mode 100644 index 000000000..7c5970e50 --- /dev/null +++ b/dnf5-plugins/system_upgrade_plugin/system_upgrade_cmd_plugin.cpp @@ -0,0 +1,72 @@ +#include "system_upgrade.hpp" + +#include + +using namespace dnf5; + +namespace { + +constexpr const char * PLUGIN_NAME{"system_upgrade"}; +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", "system_upgrade command."}; + +class SystemUpgradeCmdPlugin : 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> SystemUpgradeCmdPlugin::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 SystemUpgradeCmdPlugin(context); +} catch (...) { + return nullptr; +} + +void dnf5_plugin_delete_instance(IPlugin * plugin_object) { + delete plugin_object; +} From db78666b65b174887a414b7a4e730b4abd8a5387 Mon Sep 17 00:00:00 2001 From: Evan Goode Date: Tue, 23 Jan 2024 15:08:46 +0000 Subject: [PATCH 2/7] system-upgrade: Download, reboot, upgrade working --- .../system_upgrade_plugin/CMakeLists.txt | 13 +- .../dnf5-system-upgrade.service | 20 +++ .../system_upgrade_plugin/system_upgrade.cpp | 150 +++++++++++++++--- .../system_upgrade_plugin/system_upgrade.hpp | 66 ++++++-- dnf5.spec | 3 + 5 files changed, 210 insertions(+), 42 deletions(-) create mode 100644 dnf5-plugins/system_upgrade_plugin/dnf5-system-upgrade.service diff --git a/dnf5-plugins/system_upgrade_plugin/CMakeLists.txt b/dnf5-plugins/system_upgrade_plugin/CMakeLists.txt index 2b58babb1..cf5beaab5 100644 --- a/dnf5-plugins/system_upgrade_plugin/CMakeLists.txt +++ b/dnf5-plugins/system_upgrade_plugin/CMakeLists.txt @@ -1,12 +1,21 @@ +set(SYSTEMD_UNIT_DIR /usr/lib/systemd/system) + # set gettext domain for translations add_definitions(-DGETTEXT_DOMAIN=\"dnf5_cmd_system_upgrade\") file(GLOB SYSTEM_UPGRADE_SOURCES *.cpp) add_library(system_upgrade_cmd_plugin MODULE ${SYSTEM_UPGRADE_SOURCES}) -# disable the 'lib' prefix in order to create copr_cmd_plugin.so +# disable the 'lib' prefix in order to create system_upgrade_cmd_plugin.so set_target_properties(system_upgrade_cmd_plugin PROPERTIES PREFIX "") -target_link_libraries(system_upgrade_cmd_plugin PRIVATE dnf5 libdnf5 libdnf5-cli) +pkg_check_modules(SDBUS_CPP REQUIRED sdbus-c++) + +target_link_libraries(system_upgrade_cmd_plugin PRIVATE dnf5 libdnf5 libdnf5-cli ${SDBUS_CPP_LIBRARIES}) +install(FILES "dnf5-system-upgrade.service" DESTINATION ${SYSTEMD_UNIT_DIR}) +install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink + ${SYSTEMD_UNIT_DIR}/dnf5-system-upgrade.service + ${SYSTEMD_UNIT_DIR}/system-update.target.wants/dnf5-system-upgrade.service)" +) install(TARGETS system_upgrade_cmd_plugin LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}/dnf5/plugins/) diff --git a/dnf5-plugins/system_upgrade_plugin/dnf5-system-upgrade.service b/dnf5-plugins/system_upgrade_plugin/dnf5-system-upgrade.service new file mode 100644 index 000000000..98a260781 --- /dev/null +++ b/dnf5-plugins/system_upgrade_plugin/dnf5-system-upgrade.service @@ -0,0 +1,20 @@ +[Unit] +Description=System Upgrade using DNF 5 +ConditionPathExists=/system-update +Documentation=https://www.freedesktop.org/wiki/Software/systemd/SystemUpdates + +DefaultDependencies=no +Requires=sysinit.target +After=sysinit.target systemd-journald.socket system-update-pre.target +Before=poweroff.target reboot.target shutdown.target system-update.target +OnFailure=dnf-system-upgrade-cleanup.service + +[Service] +# We are done when the script exits, not before +Type=oneshot +# Upgrade output goes to journal and on-screen. +StandardOutput=journal+console +ExecStart=/usr/bin/dnf5 system-upgrade upgrade + +[Install] +WantedBy=system-update.target diff --git a/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp b/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp index 3627bcc0f..bac7d4fa0 100644 --- a/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp +++ b/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp @@ -24,20 +24,30 @@ along with libdnf. If not, see . #include #include #include -#include -#include +#include #include -/* State::State(std::filesystem::path path) : path(std::move(path)) { */ -/* read(); */ -/* } */ -/* void State::read() { */ -/* auto toml_value = toml::parse(path); */ -/* } */ -/* void State::write() { */ - -/* } */ +OfflineTransactionState::OfflineTransactionState(std::filesystem::path path) : path(std::move(path)) { + read(); +} +void OfflineTransactionState::read() { + try { + const auto & value = toml::parse(path); + data = toml::find(value, STATE_HEADER); + if (data.state_version != STATE_VERSION) { + throw libdnf5::RuntimeError(M_("incompatible version of state data")); + } + } catch (const std::exception & ex) { + read_exception = std::current_exception(); + data = OfflineTransactionStateData{}; + } +} +void OfflineTransactionState::write() { + auto file = libdnf5::utils::fs::File(path, "w"); + file.write(toml::format(toml::value{{STATE_HEADER, data}})); + file.close(); +} namespace dnf5 { @@ -60,11 +70,12 @@ void SystemUpgradeCommand::set_argument_parser() { void SystemUpgradeCommand::register_subcommands() { register_subcommand(std::make_unique(get_context())); + register_subcommand(std::make_unique(get_context())); register_subcommand(std::make_unique(get_context())); } SystemUpgradeSubcommand::SystemUpgradeSubcommand(Context & context, const std::string & name) : Command(context, name) { - data_dir = std::filesystem::path(libdnf5::SYSTEM_STATE_DIR) / "system-upgrade"; + datadir = std::filesystem::path{libdnf5::SYSTEM_STATE_DIR} / "system-upgrade"; } void SystemUpgradeSubcommand::set_argument_parser() { @@ -72,13 +83,13 @@ void SystemUpgradeSubcommand::set_argument_parser() { auto & parser = ctx.get_argument_parser(); auto & cmd = *get_argument_parser_command(); - download_dir = - dynamic_cast(parser.add_init_value(std::make_unique(data_dir))); + cachedir = + dynamic_cast(parser.add_init_value(std::make_unique(datadir))); auto * download_dir_arg = parser.add_new_named_arg("downloaddir"); download_dir_arg->set_long_name("downloaddir"); download_dir_arg->set_description("Redirect download of packages to provided "); - download_dir_arg->link_value(download_dir); + download_dir_arg->link_value(cachedir); cmd.register_named_arg(download_dir_arg); } @@ -117,11 +128,11 @@ void SystemUpgradeDownloadCommand::configure() { // Check --releasever const auto & installroot = conf.get_installroot_option().get_value(); - const auto current_release = *libdnf5::Vars::detect_release(ctx.base.get_weak_ptr(), installroot); - /* const auto & desired_release = ctx.base.get_vars()->get_value("releasever"); */ + system_releasever = *libdnf5::Vars::detect_release(ctx.base.get_weak_ptr(), installroot); + target_releasever = ctx.base.get_vars()->get_value("releasever"); // TODO: uncomment this. Easier to test with it commented out. - /* if (desired_release == current_release) { */ + /* if (target_releasever == system_releasever) { */ /* throw libdnf5::cli::CommandExitError(1, M_("Need a --releasever greater than the current system version.")); */ /* } */ @@ -129,7 +140,7 @@ void SystemUpgradeDownloadCommand::configure() { ctx.set_load_available_repos(Context::LoadAvailableRepos::ENABLED); ctx.base.get_config().get_cachedir_option().set( - libdnf5::Option::Priority::PLUGINDEFAULT, get_download_dir()->get_value()); + libdnf5::Option::Priority::PLUGINDEFAULT, get_cachedir()->get_value()); } void SystemUpgradeDownloadCommand::run() { @@ -159,11 +170,32 @@ void SystemUpgradeDownloadCommand::run() { throw libdnf5::cli::CommandExitError(1, M_("Transaction is empty.")); } - const auto & transaction_json_path = std::filesystem::path(get_data_dir()) / "system-upgrade-transaction.json"; + const auto & transaction_json_path = std::filesystem::path(get_datadir()) / "system-upgrade-transaction.json"; auto transaction_json_file = libdnf5::utils::fs::File(transaction_json_path, "w"); transaction_json_file.write(json); transaction_json_file.close(); + + auto state = read_or_make_state(); + state.get_data().cachedir = get_cachedir()->get_value(); + state.get_data().system_releasever = system_releasever; + state.get_data().target_releasever = target_releasever; + state.get_data().status = STATUS_DOWNLOAD_COMPLETE; + state.write(); +} + +void check_state(const OfflineTransactionState & state) { + const auto & read_exception = state.get_read_exception(); + if (read_exception != nullptr) { + try { + std::rethrow_exception(read_exception); + } catch (const std::exception & ex) { + std::cout << "ex.what rethrown is " << ex.what() << std::endl; + const std::string message{ex.what()}; + throw libdnf5::cli::CommandExitError( + 1, M_("Error reading state: {}. Rerun `dnf5 system-upgrade download [OPTIONS]`."), message); + } + } } void reboot(Context & ctx, bool poweroff = false) { @@ -176,6 +208,7 @@ void reboot(Context & ctx, bool poweroff = false) { return; } + poweroff = !poweroff; std::unique_ptr connection; try { connection = sdbus::createSystemBusConnection(); @@ -191,14 +224,53 @@ void reboot(Context & ctx, bool poweroff = false) { } } +void SystemUpgradeRebootCommand::set_argument_parser() { + SystemUpgradeSubcommand::set_argument_parser(); + + auto & ctx = get_context(); + auto & parser = ctx.get_argument_parser(); + auto & cmd = *get_argument_parser_command(); + + cmd.set_description("Prepare the system to perform the upgrade and reboots to start the upgrade."); + + poweroff_after = + dynamic_cast(parser.add_init_value(std::make_unique(true))); + + auto * poweroff_after_arg = parser.add_new_named_arg("poweroff"); + poweroff_after_arg->set_long_name("poweroff"); + poweroff_after_arg->set_description("Power off the system after the operation is complete"); + poweroff_after_arg->link_value(poweroff_after); + + cmd.register_named_arg(poweroff_after_arg); +} + void SystemUpgradeRebootCommand::run() { - std::filesystem::create_symlink(get_data_dir(), get_magic_symlink()); - reboot(get_context()); + auto state = read_or_make_state(); + check_state(state); + if (state.get_data().status != STATUS_DOWNLOAD_COMPLETE) { + throw libdnf5::cli::CommandExitError(1, M_("system is not ready for upgrade")); + } + if (std::filesystem::is_symlink(get_magic_symlink())) { + throw libdnf5::cli::CommandExitError(1, M_("upgrade is already scheduled")); + } + + if (!std::filesystem::is_directory(get_datadir())) { + throw libdnf5::cli::CommandExitError(1, M_("data directory {} does not exist"), get_datadir().string()); + } + + std::filesystem::create_symlink(get_datadir(), get_magic_symlink()); + + state.get_data().status = STATUS_READY; + state.get_data().poweroff_after = poweroff_after->get_value(); + state.write(); + + reboot(get_context(), poweroff_after->get_value()); } void SystemUpgradeUpgradeCommand::set_argument_parser() {} void SystemUpgradeUpgradeCommand::configure() { + SystemUpgradeSubcommand::configure(); auto & ctx = get_context(); ctx.set_load_system_repo(true); @@ -215,7 +287,24 @@ void SystemUpgradeUpgradeCommand::configure() { void SystemUpgradeUpgradeCommand::run() { auto & ctx = get_context(); - const auto & transaction_json_path = std::filesystem::path(get_data_dir()) / "system-upgrade-transaction.json"; + if (!std::filesystem::is_symlink(get_magic_symlink())) { + throw libdnf5::cli::CommandExitError(0, M_("Trigger file does not exist. Exiting.")); + } + + const auto & symlinked_path = std::filesystem::read_symlink(get_magic_symlink()); + if (symlinked_path != get_datadir()) { + throw libdnf5::cli::CommandExitError(0, M_("Another upgrade tool is running. Exiting.")); + } + + std::filesystem::remove(get_magic_symlink()); + + auto state = read_or_make_state(); + check_state(state); + if (state.get_data().status != STATUS_READY) { + throw libdnf5::cli::CommandExitError(1, M_("Use `dnf5 system-upgrade reboot` to begin the upgrade.")); + } + + const auto & transaction_json_path = std::filesystem::path(get_datadir()) / "system-upgrade-transaction.json"; auto transaction_json_file = libdnf5::utils::fs::File(transaction_json_path, "r"); const auto & json = transaction_json_file.read(); @@ -231,7 +320,20 @@ void SystemUpgradeUpgradeCommand::run() { throw libdnf5::cli::GoalResolveError(transaction); } - transaction.run(); + const auto result = transaction.run(); + if (result != libdnf5::base::Transaction::TransactionRunResult::SUCCESS) { + std::cerr << "Transaction failed: " << libdnf5::base::Transaction::transaction_result_to_string(result) + << std::endl; + for (auto const & entry : transaction.get_gpg_signature_problems()) { + std::cerr << entry << std::endl; + } + for (auto & problem : transaction.get_transaction_problems()) { + std::cerr << " - " << problem << std::endl; + } + throw libdnf5::cli::SilentCommandExitError(1); + } } +void SystemUpgradeCleanCommand::run() {} + } // namespace dnf5 diff --git a/dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp b/dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp index d936d9e26..7480b94e8 100644 --- a/dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp +++ b/dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp @@ -21,21 +21,47 @@ along with libdnf. If not, see . #define DNF5_COMMANDS_SYSTEM_UPGRADE_HPP #include +#include #include #include +#include +#include + +const std::string STATUS_DOWNLOAD_INCOMPLETE{"download-incomplete"}; +const std::string STATUS_DOWNLOAD_COMPLETE{"download-complete"}; +const std::string STATUS_READY{"ready"}; +const std::string STATUS_UPGRADE_INCOMPLETE{"upgrade-incomplete"}; + +const int STATE_VERSION = 0; +const std::string STATE_HEADER{"offline-transaction-state"}; + +struct OfflineTransactionStateData { + int state_version = STATE_VERSION; + std::string status = STATUS_DOWNLOAD_INCOMPLETE; + std::string cachedir; + std::string target_releasever; + std::string system_releasever; + bool poweroff_after = false; +}; + +TOML11_DEFINE_CONVERSION_NON_INTRUSIVE( + OfflineTransactionStateData, state_version, status, cachedir, target_releasever, system_releasever, poweroff_after) + +class OfflineTransactionState { +public: + void write(); + OfflineTransactionState(std::filesystem::path path); + OfflineTransactionStateData & get_data() { return data; }; + const std::exception_ptr & get_read_exception() const { return read_exception; }; -/* #include */ -/* #include */ +private: + void read(); + std::exception_ptr read_exception; + std::filesystem::path path; + OfflineTransactionStateData data; +}; -/* class State { */ -/* public: */ -/* State(std::filesystem::path path); */ -/* private: */ -/* void read(); */ -/* void write(); */ -/* std::map data; */ -/* std::filesystem::path path; */ -/* }; */ +const std::filesystem::path DEFAULT_DATADIR{std::filesystem::path(libdnf5::SYSTEM_STATE_DIR) / "system-upgrade"}; namespace dnf5 { @@ -55,19 +81,21 @@ class SystemUpgradeSubcommand : public Command { void configure() override; protected: - libdnf5::OptionPath * get_download_dir() { return download_dir; }; - std::filesystem::path get_data_dir() { return data_dir; }; + libdnf5::OptionPath * get_cachedir() { return cachedir; }; + std::filesystem::path get_datadir() { return datadir; }; + void set_datadir(std::filesystem::path new_datadir) { datadir = std::move(new_datadir); }; std::filesystem::path get_magic_symlink() { return magic_symlink; }; + OfflineTransactionState read_or_make_state() { return OfflineTransactionState{datadir / "state.toml"}; }; private: - libdnf5::OptionPath * download_dir{nullptr}; - std::filesystem::path data_dir; + libdnf5::OptionPath * cachedir{nullptr}; + std::filesystem::path datadir{DEFAULT_DATADIR}; std::filesystem::path magic_symlink; }; class SystemUpgradeDownloadCommand : public SystemUpgradeSubcommand { public: - explicit SystemUpgradeDownloadCommand(Context & context) : SystemUpgradeSubcommand(context, "download") {} + explicit SystemUpgradeDownloadCommand(Context & context) : SystemUpgradeSubcommand{context, "download"} {} void set_argument_parser() override; void configure() override; void run() override; @@ -75,6 +103,9 @@ class SystemUpgradeDownloadCommand : public SystemUpgradeSubcommand { private: libdnf5::OptionBool * no_downgrade{nullptr}; libdnf5::OptionBool * distro_sync{nullptr}; + std::string target_releasever; + std::string system_releasever; + int state_version; }; class SystemUpgradeRebootCommand : public SystemUpgradeSubcommand { @@ -82,6 +113,9 @@ class SystemUpgradeRebootCommand : public SystemUpgradeSubcommand { explicit SystemUpgradeRebootCommand(Context & context) : SystemUpgradeSubcommand(context, "reboot") {} void set_argument_parser() override; void run() override; + +private: + libdnf5::OptionBool * poweroff_after{nullptr}; }; class SystemUpgradeUpgradeCommand : public SystemUpgradeSubcommand { diff --git a/dnf5.spec b/dnf5.spec index fd02ef56a..a27720f87 100644 --- a/dnf5.spec +++ b/dnf5.spec @@ -653,17 +653,20 @@ Provides: dnf5-command(config-manager) Provides: dnf5-command(copr) Provides: dnf5-command(needs-restarting) Provides: dnf5-command(repoclosure) +Provides: dnf5-command(system-upgrade) %description -n dnf5-plugins Core DNF5 plugins that enhance dnf5 with builddep, changelog, config-manager, copr, and repoclosure commands. %files -n dnf5-plugins -f dnf5-plugin-builddep.lang -f dnf5-plugin-changelog.lang -f dnf5-plugin-config-manager.lang -f dnf5-plugin-copr.lang -f dnf5-plugin-needs-restarting.lang -f dnf5-plugin-repoclosure.lang +%config %{_unitdir}/dnf5-system-upgrade.service %{_libdir}/dnf5/plugins/*.so %{_mandir}/man8/dnf5-builddep.8.* %{_mandir}/man8/dnf5-copr.8.* %{_mandir}/man8/dnf5-needs-restarting.8.* %{_mandir}/man8/dnf5-repoclosure.8.* +# %{_mandir}/man8/dnf5-system-upgrade.8.* %endif From 6ed8a908869201812b495268c6209dcbd88c49eb Mon Sep 17 00:00:00 2001 From: Evan Goode Date: Fri, 26 Jan 2024 19:31:57 +0000 Subject: [PATCH 3/7] system-upgrade: clean, store enabled/disabled repos --- .../system_upgrade_plugin/system_upgrade.cpp | 70 ++++++++++++++++--- .../system_upgrade_plugin/system_upgrade.hpp | 15 +++- 2 files changed, 73 insertions(+), 12 deletions(-) diff --git a/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp b/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp index bac7d4fa0..e777fccad 100644 --- a/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp +++ b/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp @@ -69,14 +69,16 @@ void SystemUpgradeCommand::set_argument_parser() { } void SystemUpgradeCommand::register_subcommands() { + register_subcommand(std::make_unique(get_context())); register_subcommand(std::make_unique(get_context())); register_subcommand(std::make_unique(get_context())); register_subcommand(std::make_unique(get_context())); } -SystemUpgradeSubcommand::SystemUpgradeSubcommand(Context & context, const std::string & name) : Command(context, name) { - datadir = std::filesystem::path{libdnf5::SYSTEM_STATE_DIR} / "system-upgrade"; -} +SystemUpgradeSubcommand::SystemUpgradeSubcommand(Context & context, const std::string & name) + : Command(context, name), + datadir(std::filesystem::path{libdnf5::SYSTEM_STATE_DIR} / "system-upgrade"), + state(datadir / "state.toml") {} void SystemUpgradeSubcommand::set_argument_parser() { auto & ctx = get_context(); @@ -176,11 +178,23 @@ void SystemUpgradeDownloadCommand::run() { transaction_json_file.write(json); transaction_json_file.close(); - auto state = read_or_make_state(); + auto state = get_state(); state.get_data().cachedir = get_cachedir()->get_value(); state.get_data().system_releasever = system_releasever; state.get_data().target_releasever = target_releasever; state.get_data().status = STATUS_DOWNLOAD_COMPLETE; + libdnf5::repo::RepoQuery enabled_repos(ctx.base); + enabled_repos.filter_enabled(true); + enabled_repos.filter_type(libdnf5::repo::Repo::Type::SYSTEM, libdnf5::sack::QueryCmp::NEQ); + for (const auto & repo : enabled_repos) { + state.get_data().enabled_repos.emplace_back(repo->get_id()); + } + libdnf5::repo::RepoQuery disabled_repos(ctx.base); + disabled_repos.filter_enabled(false); + disabled_repos.filter_type(libdnf5::repo::Repo::Type::SYSTEM, libdnf5::sack::QueryCmp::NEQ); + for (const auto & repo : disabled_repos) { + state.get_data().disabled_repos.emplace_back(repo->get_id()); + } state.write(); } @@ -190,7 +204,6 @@ void check_state(const OfflineTransactionState & state) { try { std::rethrow_exception(read_exception); } catch (const std::exception & ex) { - std::cout << "ex.what rethrown is " << ex.what() << std::endl; const std::string message{ex.what()}; throw libdnf5::cli::CommandExitError( 1, M_("Error reading state: {}. Rerun `dnf5 system-upgrade download [OPTIONS]`."), message); @@ -224,6 +237,14 @@ void reboot(Context & ctx, bool poweroff = false) { } } +void clean(Context & ctx, const std::filesystem::path & datadir) { + ctx.base.get_logger()->info("Cleaning up downloaded data..."); + + for (const auto & entry : std::filesystem::directory_iterator(datadir)) { + std::filesystem::remove_all(entry.path()); + } +} + void SystemUpgradeRebootCommand::set_argument_parser() { SystemUpgradeSubcommand::set_argument_parser(); @@ -245,7 +266,7 @@ void SystemUpgradeRebootCommand::set_argument_parser() { } void SystemUpgradeRebootCommand::run() { - auto state = read_or_make_state(); + auto state = get_state(); check_state(state); if (state.get_data().status != STATUS_DOWNLOAD_COMPLETE) { throw libdnf5::cli::CommandExitError(1, M_("system is not ready for upgrade")); @@ -276,9 +297,25 @@ void SystemUpgradeUpgradeCommand::configure() { ctx.set_load_system_repo(true); ctx.set_load_available_repos(Context::LoadAvailableRepos::ENABLED); + auto state = get_state(); + check_state(state); + + // Set same set of enabled/disabled repos used during `system-upgrade download` + for (const auto & repo_id : state.get_data().enabled_repos) { + ctx.setopts.emplace_back(repo_id + ".enabled", "1"); + } + for (const auto & repo_id : state.get_data().disabled_repos) { + ctx.setopts.emplace_back(repo_id + ".disabled", "1"); + } + + // Don't try to refresh metadata, we are offline ctx.base.get_config().get_cacheonly_option().set(libdnf5::Option::Priority::PLUGINDEFAULT, "all"); - ctx.base.get_config().get_assumeno_option().set(libdnf5::Option::Priority::PLUGINDEFAULT, false); + // Don't ask any questions ctx.base.get_config().get_assumeyes_option().set(libdnf5::Option::Priority::PLUGINDEFAULT, true); + // Override `assumeno` too since it takes priority over `assumeyes` + ctx.base.get_config().get_assumeno_option().set(libdnf5::Option::Priority::PLUGINDEFAULT, false); + // Upgrade operation already removes all element that must be removed. + // Additional removal could trigger unwanted changes in transaction. ctx.base.get_config().get_clean_requirements_on_remove_option().set( libdnf5::Option::Priority::PLUGINDEFAULT, false); ctx.base.get_config().get_install_weak_deps_option().set(libdnf5::Option::Priority::PLUGINDEFAULT, false); @@ -298,8 +335,7 @@ void SystemUpgradeUpgradeCommand::run() { std::filesystem::remove(get_magic_symlink()); - auto state = read_or_make_state(); - check_state(state); + auto state = get_state(); if (state.get_data().status != STATUS_READY) { throw libdnf5::cli::CommandExitError(1, M_("Use `dnf5 system-upgrade reboot` to begin the upgrade.")); } @@ -332,8 +368,22 @@ void SystemUpgradeUpgradeCommand::run() { } throw libdnf5::cli::SilentCommandExitError(1); } + + // If the upgrade succeeded, remove downloaded data + clean(ctx, get_datadir()); + + if (state.get_data().poweroff_after) { + reboot(ctx, true); + } } -void SystemUpgradeCleanCommand::run() {} +void SystemUpgradeCleanCommand::set_argument_parser() { + SystemUpgradeSubcommand::set_argument_parser(); +} + +void SystemUpgradeCleanCommand::run() { + auto & ctx = get_context(); + clean(ctx, get_datadir()); +} } // namespace dnf5 diff --git a/dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp b/dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp index 7480b94e8..ed9e1f230 100644 --- a/dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp +++ b/dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp @@ -42,10 +42,20 @@ struct OfflineTransactionStateData { std::string target_releasever; std::string system_releasever; bool poweroff_after = false; + std::vector enabled_repos; + std::vector disabled_repos; }; TOML11_DEFINE_CONVERSION_NON_INTRUSIVE( - OfflineTransactionStateData, state_version, status, cachedir, target_releasever, system_releasever, poweroff_after) + OfflineTransactionStateData, + state_version, + status, + cachedir, + target_releasever, + system_releasever, + poweroff_after, + enabled_repos, + disabled_repos) class OfflineTransactionState { public: @@ -85,12 +95,13 @@ class SystemUpgradeSubcommand : public Command { std::filesystem::path get_datadir() { return datadir; }; void set_datadir(std::filesystem::path new_datadir) { datadir = std::move(new_datadir); }; std::filesystem::path get_magic_symlink() { return magic_symlink; }; - OfflineTransactionState read_or_make_state() { return OfflineTransactionState{datadir / "state.toml"}; }; + OfflineTransactionState get_state() { return state; }; private: libdnf5::OptionPath * cachedir{nullptr}; std::filesystem::path datadir{DEFAULT_DATADIR}; std::filesystem::path magic_symlink; + OfflineTransactionState state; }; class SystemUpgradeDownloadCommand : public SystemUpgradeSubcommand { From 7ca9ef5c31449789f92bb68994ae6c61779aff45 Mon Sep 17 00:00:00 2001 From: Evan Goode Date: Sat, 27 Jan 2024 04:50:04 +0000 Subject: [PATCH 4/7] system-upgrade: Plymouth output Implementing transaction callbacks for Plymouth and keeping the progress bars on stdout meant extending RpmTransCB, meaning RpmTransCB had to move to the public API. --- .../system_upgrade_plugin/system_upgrade.cpp | 144 ++++++- .../system_upgrade_plugin/system_upgrade.hpp | 2 + dnf5/context.cpp | 359 +++++++++--------- dnf5/include/dnf5/context.hpp | 80 ++++ 4 files changed, 388 insertions(+), 197 deletions(-) diff --git a/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp b/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp index e777fccad..cb721b48d 100644 --- a/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp +++ b/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp @@ -24,10 +24,13 @@ along with libdnf. If not, see . #include #include #include +#include #include #include +using namespace libdnf5::cli; + OfflineTransactionState::OfflineTransactionState(std::filesystem::path path) : path(std::move(path)) { read(); } @@ -49,9 +52,122 @@ void OfflineTransactionState::write() { file.close(); } +int call(const std::string & command, const std::vector & args) { + std::vector c_args; + c_args.emplace_back(const_cast(command.c_str())); + for (const auto & arg : args) { + c_args.emplace_back(const_cast(arg.c_str())); + } + c_args.emplace_back(nullptr); + + const auto pid = fork(); + if (pid == -1) { + return -1; + } + if (pid == 0) { + int rc = execvp(command.c_str(), c_args.data()); + exit(rc == 0 ? 0 : -1); + } else { + int status; + int rc = waitpid(pid, &status, 0); + if (rc == -1) { + return -1; + } + if (WIFEXITED(status)) { + return WEXITSTATUS(status); + } + return -1; + } +} + namespace dnf5 { -using namespace libdnf5::cli; + +/// Helper for displaying messages with Plymouth +/// +/// Derived from DNF 4 system-upgrade PlymouthOutput implementation. Filters +/// duplicate calls, and stops calling the plymouth binary if we fail to +/// contact it. +class PlymouthOutput { +public: + bool ping() { return plymouth({"ping"}); } + bool set_mode() { return plymouth({"change-mode", "--system-upgrade"}); } + bool message(const std::string & message) { + if (last_message.has_value() && message == last_message) { + plymouth({"hide-message", "--text", last_message.value()}); + } + last_message = message; + return plymouth({"display-message", "--text", message}); + } + bool progress(const int percentage) { + return plymouth({"system-update", "--progress", std::to_string(percentage)}); + } + +private: + bool alive = true; + std::map> last_subcommand_args; + std::optional last_message; + bool plymouth(const std::vector & args) { + const auto & command = args.at(0); + const auto & last_args = last_subcommand_args.find(command); + + bool dupe_cmd = (last_args != last_subcommand_args.end() && args == last_args->second); + if ((alive && !dupe_cmd) || command == "--ping") { + alive = call(PATH_TO_PLYMOUTH, args) == 0; + last_subcommand_args[command] = args; + } + return alive; + } +}; + +/// Extend RpmTransCB to also display messages with Plymouth +class PlymouthTransCB : public RpmTransCB { +public: + void elem_progress( + [[maybe_unused]] const libdnf5::rpm::TransactionItem & item, + [[maybe_unused]] uint64_t amount, + [[maybe_unused]] uint64_t total) override { + RpmTransCB::elem_progress(item, amount, total); + + plymouth.progress(static_cast(100 * static_cast(amount) / static_cast(total))); + + std::string action; + switch (item.get_action()) { + case libdnf5::transaction::TransactionItemAction::UPGRADE: + action = "Upgrading"; + break; + case libdnf5::transaction::TransactionItemAction::DOWNGRADE: + action = "Downgrading"; + break; + case libdnf5::transaction::TransactionItemAction::REINSTALL: + action = "Reinstalling"; + break; + case libdnf5::transaction::TransactionItemAction::INSTALL: + action = "Installing"; + break; + case libdnf5::transaction::TransactionItemAction::REMOVE: + action = "Removing"; + break; + case libdnf5::transaction::TransactionItemAction::REPLACED: + action = "Replacing"; + break; + case libdnf5::transaction::TransactionItemAction::REASON_CHANGE: + case libdnf5::transaction::TransactionItemAction::ENABLE: + case libdnf5::transaction::TransactionItemAction::DISABLE: + case libdnf5::transaction::TransactionItemAction::RESET: + throw std::logic_error(fmt::format( + "Unexpected action in TransactionPackage: {}", + static_cast>( + item.get_action()))); + break; + } + const auto & message = fmt::format("[{}/{}] {} {}...", amount, total, action, item.get_package().get_name()); + plymouth.message(message); + } + +private: + PlymouthOutput plymouth; +}; void SystemUpgradeCommand::pre_configure() { throw_missing_command(); @@ -133,10 +249,9 @@ void SystemUpgradeDownloadCommand::configure() { system_releasever = *libdnf5::Vars::detect_release(ctx.base.get_weak_ptr(), installroot); target_releasever = ctx.base.get_vars()->get_value("releasever"); - // TODO: uncomment this. Easier to test with it commented out. - /* if (target_releasever == system_releasever) { */ - /* throw libdnf5::cli::CommandExitError(1, M_("Need a --releasever greater than the current system version.")); */ - /* } */ + if (target_releasever == system_releasever) { + throw libdnf5::cli::CommandExitError(1, M_("Need a --releasever greater than the current system version.")); + } ctx.set_load_system_repo(true); ctx.set_load_available_repos(Context::LoadAvailableRepos::ENABLED); @@ -167,11 +282,12 @@ void SystemUpgradeDownloadCommand::run() { ctx.download_and_run(transaction); - const std::string json = transaction.serialize(); - if (json.empty()) { - throw libdnf5::cli::CommandExitError(1, M_("Transaction is empty.")); + if (!transaction.get_transaction_packages_count()) { + throw libdnf5::cli::CommandExitError( + 1, M_("The system-upgrade transaction is empty; your system is already up-to-date.")); } + const std::string json = transaction.serialize(); const auto & transaction_json_path = std::filesystem::path(get_datadir()) / "system-upgrade-transaction.json"; auto transaction_json_file = libdnf5::utils::fs::File(transaction_json_path, "w"); @@ -196,6 +312,9 @@ void SystemUpgradeDownloadCommand::run() { state.get_data().disabled_repos.emplace_back(repo->get_id()); } state.write(); + + std::cout << "Download complete! Use `dnf5 system-upgrade reboot` to start the upgrade." << std::endl + << "To remove cached metadata and transaction, use `dnf5 system-upgrade clean`." << std::endl; } void check_state(const OfflineTransactionState & state) { @@ -356,7 +475,12 @@ void SystemUpgradeUpgradeCommand::run() { throw libdnf5::cli::GoalResolveError(transaction); } + auto callbacks = std::make_unique(); + /* callbacks->get_multi_progress_bar()->set_total_num_of_bars(num_of_actions); */ + transaction.set_callbacks(std::move(callbacks)); + const auto result = transaction.run(); + std::cout << std::endl; if (result != libdnf5::base::Transaction::TransactionRunResult::SUCCESS) { std::cerr << "Transaction failed: " << libdnf5::base::Transaction::transaction_result_to_string(result) << std::endl; @@ -369,6 +493,10 @@ void SystemUpgradeUpgradeCommand::run() { throw libdnf5::cli::SilentCommandExitError(1); } + for (auto const & entry : transaction.get_gpg_signature_problems()) { + std::cerr << entry << std::endl; + } + // If the upgrade succeeded, remove downloaded data clean(ctx, get_datadir()); diff --git a/dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp b/dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp index ed9e1f230..8502dbe6a 100644 --- a/dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp +++ b/dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp @@ -27,6 +27,8 @@ along with libdnf. If not, see . #include #include +const std::filesystem::path PATH_TO_PLYMOUTH{"/usr/bin/plymouth"}; + const std::string STATUS_DOWNLOAD_INCOMPLETE{"download-incomplete"}; const std::string STATUS_DOWNLOAD_COMPLETE{"download-complete"}; const std::string STATUS_READY{"ready"}; diff --git a/dnf5/context.cpp b/dnf5/context.cpp index 942e69a59..43ba888f6 100644 --- a/dnf5/context.cpp +++ b/dnf5/context.cpp @@ -184,230 +184,211 @@ void Context::load_repos(bool load_system) { print_info("Repositories loaded."); } -namespace { +RpmTransCB::RpmTransCB() { + multi_progress_bar.set_total_bar_visible_limit(libdnf5::cli::progressbar::MultiProgressBar::NEVER_VISIBLE_LIMIT); +} -class RpmTransCB : public libdnf5::rpm::TransactionCallbacks { -public: - RpmTransCB() { - multi_progress_bar.set_total_bar_visible_limit( - libdnf5::cli::progressbar::MultiProgressBar::NEVER_VISIBLE_LIMIT); +RpmTransCB::~RpmTransCB() { + if (active_progress_bar && active_progress_bar->get_state() != libdnf5::cli::progressbar::ProgressBarState::ERROR) { + active_progress_bar->set_state(libdnf5::cli::progressbar::ProgressBarState::SUCCESS); } - - ~RpmTransCB() { - if (active_progress_bar && - active_progress_bar->get_state() != libdnf5::cli::progressbar::ProgressBarState::ERROR) { - active_progress_bar->set_state(libdnf5::cli::progressbar::ProgressBarState::SUCCESS); - } - if (active_progress_bar) { - multi_progress_bar.print(); - } - } - - libdnf5::cli::progressbar::MultiProgressBar * get_multi_progress_bar() { return &multi_progress_bar; } - - void install_progress( - [[maybe_unused]] const libdnf5::rpm::TransactionItem & item, - uint64_t amount, - [[maybe_unused]] uint64_t total) override { - active_progress_bar->set_ticks(static_cast(amount)); - if (is_time_to_print()) { - multi_progress_bar.print(); - } + if (active_progress_bar) { + multi_progress_bar.print(); } +} - void install_start(const libdnf5::rpm::TransactionItem & item, uint64_t total) override { - const char * msg{nullptr}; - switch (item.get_action()) { - case libdnf5::transaction::TransactionItemAction::UPGRADE: - msg = "Upgrading "; - break; - case libdnf5::transaction::TransactionItemAction::DOWNGRADE: - msg = "Downgrading "; - break; - case libdnf5::transaction::TransactionItemAction::REINSTALL: - msg = "Reinstalling "; - break; - case libdnf5::transaction::TransactionItemAction::INSTALL: - case libdnf5::transaction::TransactionItemAction::REMOVE: - case libdnf5::transaction::TransactionItemAction::REPLACED: - break; - case libdnf5::transaction::TransactionItemAction::REASON_CHANGE: - case libdnf5::transaction::TransactionItemAction::ENABLE: - case libdnf5::transaction::TransactionItemAction::DISABLE: - case libdnf5::transaction::TransactionItemAction::RESET: - throw std::logic_error(fmt::format( - "Unexpected action in TransactionPackage: {}", - static_cast>( - item.get_action()))); - } - if (!msg) { - msg = "Installing "; - } - new_progress_bar(static_cast(total), msg + item.get_package().get_full_nevra()); - } +libdnf5::cli::progressbar::MultiProgressBar * RpmTransCB::get_multi_progress_bar() { + return &multi_progress_bar; +} - void install_stop( - [[maybe_unused]] const libdnf5::rpm::TransactionItem & item, - [[maybe_unused]] uint64_t amount, - [[maybe_unused]] uint64_t total) override { +void RpmTransCB::install_progress( + [[maybe_unused]] const libdnf5::rpm::TransactionItem & item, uint64_t amount, [[maybe_unused]] uint64_t total) { + active_progress_bar->set_ticks(static_cast(amount)); + if (is_time_to_print()) { multi_progress_bar.print(); } +} - void transaction_progress(uint64_t amount, [[maybe_unused]] uint64_t total) override { - active_progress_bar->set_ticks(static_cast(amount)); - if (is_time_to_print()) { - multi_progress_bar.print(); - } - } +void RpmTransCB::install_start(const libdnf5::rpm::TransactionItem & item, uint64_t total) { + const char * msg{nullptr}; + switch (item.get_action()) { + case libdnf5::transaction::TransactionItemAction::UPGRADE: + msg = "Upgrading "; + break; + case libdnf5::transaction::TransactionItemAction::DOWNGRADE: + msg = "Downgrading "; + break; + case libdnf5::transaction::TransactionItemAction::REINSTALL: + msg = "Reinstalling "; + break; + case libdnf5::transaction::TransactionItemAction::INSTALL: + case libdnf5::transaction::TransactionItemAction::REMOVE: + case libdnf5::transaction::TransactionItemAction::REPLACED: + break; + case libdnf5::transaction::TransactionItemAction::REASON_CHANGE: + case libdnf5::transaction::TransactionItemAction::ENABLE: + case libdnf5::transaction::TransactionItemAction::DISABLE: + case libdnf5::transaction::TransactionItemAction::RESET: + throw std::logic_error(fmt::format( + "Unexpected action in TransactionPackage: {}", + static_cast>( + item.get_action()))); + } + if (!msg) { + msg = "Installing "; + } + new_progress_bar(static_cast(total), msg + item.get_package().get_full_nevra()); +} - void transaction_start(uint64_t total) override { - new_progress_bar(static_cast(total), "Prepare transaction"); - } +void RpmTransCB::install_stop( + [[maybe_unused]] const libdnf5::rpm::TransactionItem & item, + [[maybe_unused]] uint64_t amount, + [[maybe_unused]] uint64_t total) { + multi_progress_bar.print(); +} - void transaction_stop([[maybe_unused]] uint64_t total) override { - active_progress_bar->set_ticks(static_cast(total)); +void RpmTransCB::transaction_progress(uint64_t amount, [[maybe_unused]] uint64_t total) { + active_progress_bar->set_ticks(static_cast(amount)); + if (is_time_to_print()) { multi_progress_bar.print(); } +} - void uninstall_progress( - [[maybe_unused]] const libdnf5::rpm::TransactionItem & item, - uint64_t amount, - [[maybe_unused]] uint64_t total) override { - active_progress_bar->set_ticks(static_cast(amount)); - if (is_time_to_print()) { - multi_progress_bar.print(); - } - } +void RpmTransCB::transaction_start(uint64_t total) { + new_progress_bar(static_cast(total), "Prepare transaction"); +} - void uninstall_start(const libdnf5::rpm::TransactionItem & item, uint64_t total) override { - const char * msg{nullptr}; - if (item.get_action() == libdnf5::transaction::TransactionItemAction::REMOVE || - item.get_action() == libdnf5::transaction::TransactionItemAction::REPLACED) { - msg = "Erasing "; - } - if (!msg) { - msg = "Cleanup "; - } - new_progress_bar(static_cast(total), msg + item.get_package().get_full_nevra()); - } +void RpmTransCB::transaction_stop([[maybe_unused]] uint64_t total) { + active_progress_bar->set_ticks(static_cast(total)); + multi_progress_bar.print(); +} - void uninstall_stop( - [[maybe_unused]] const libdnf5::rpm::TransactionItem & item, - [[maybe_unused]] uint64_t amount, - [[maybe_unused]] uint64_t total) override { +void RpmTransCB::uninstall_progress( + [[maybe_unused]] const libdnf5::rpm::TransactionItem & item, uint64_t amount, [[maybe_unused]] uint64_t total) { + active_progress_bar->set_ticks(static_cast(amount)); + if (is_time_to_print()) { multi_progress_bar.print(); } +} - - void unpack_error(const libdnf5::rpm::TransactionItem & item) override { - active_progress_bar->add_message( - libdnf5::cli::progressbar::MessageType::ERROR, "Unpack error: " + item.get_package().get_full_nevra()); - active_progress_bar->set_state(libdnf5::cli::progressbar::ProgressBarState::ERROR); - multi_progress_bar.print(); +void RpmTransCB::uninstall_start(const libdnf5::rpm::TransactionItem & item, uint64_t total) { + const char * msg{nullptr}; + if (item.get_action() == libdnf5::transaction::TransactionItemAction::REMOVE || + item.get_action() == libdnf5::transaction::TransactionItemAction::REPLACED) { + msg = "Erasing "; } - - void cpio_error(const libdnf5::rpm::TransactionItem & item) override { - active_progress_bar->add_message( - libdnf5::cli::progressbar::MessageType::ERROR, "Cpio error: " + item.get_package().get_full_nevra()); - active_progress_bar->set_state(libdnf5::cli::progressbar::ProgressBarState::ERROR); - multi_progress_bar.print(); + if (!msg) { + msg = "Cleanup "; } + new_progress_bar(static_cast(total), msg + item.get_package().get_full_nevra()); +} - void script_error( - [[maybe_unused]] const libdnf5::rpm::TransactionItem * item, - libdnf5::rpm::Nevra nevra, - libdnf5::rpm::TransactionCallbacks::ScriptType type, - uint64_t return_code) override { - active_progress_bar->add_message( - libdnf5::cli::progressbar::MessageType::ERROR, - fmt::format( - "Error in {} scriptlet: {} return code {}", - script_type_to_string(type), - to_full_nevra_string(nevra), - return_code)); - multi_progress_bar.print(); - } +void RpmTransCB::uninstall_stop( + [[maybe_unused]] const libdnf5::rpm::TransactionItem & item, + [[maybe_unused]] uint64_t amount, + [[maybe_unused]] uint64_t total) { + multi_progress_bar.print(); +} - void script_start( - [[maybe_unused]] const libdnf5::rpm::TransactionItem * item, - libdnf5::rpm::Nevra nevra, - libdnf5::rpm::TransactionCallbacks::ScriptType type) override { - active_progress_bar->add_message( - libdnf5::cli::progressbar::MessageType::INFO, - fmt::format("Running {} scriptlet: {}", script_type_to_string(type), to_full_nevra_string(nevra))); - multi_progress_bar.print(); - } - void script_stop( - [[maybe_unused]] const libdnf5::rpm::TransactionItem * item, - libdnf5::rpm::Nevra nevra, - libdnf5::rpm::TransactionCallbacks::ScriptType type, - [[maybe_unused]] uint64_t return_code) override { - active_progress_bar->add_message( - libdnf5::cli::progressbar::MessageType::INFO, - fmt::format("Stop {} scriptlet: {}", script_type_to_string(type), to_full_nevra_string(nevra))); - multi_progress_bar.print(); - } +void RpmTransCB::unpack_error(const libdnf5::rpm::TransactionItem & item) { + active_progress_bar->add_message( + libdnf5::cli::progressbar::MessageType::ERROR, "Unpack error: " + item.get_package().get_full_nevra()); + active_progress_bar->set_state(libdnf5::cli::progressbar::ProgressBarState::ERROR); + multi_progress_bar.print(); +} - void elem_progress( - [[maybe_unused]] const libdnf5::rpm::TransactionItem & item, - [[maybe_unused]] uint64_t amount, - [[maybe_unused]] uint64_t total) override { - //std::cout << "Element progress: " << header.get_full_nevra() << " " << amount << '/' << total << std::endl; - } +void RpmTransCB::cpio_error(const libdnf5::rpm::TransactionItem & item) { + active_progress_bar->add_message( + libdnf5::cli::progressbar::MessageType::ERROR, "Cpio error: " + item.get_package().get_full_nevra()); + active_progress_bar->set_state(libdnf5::cli::progressbar::ProgressBarState::ERROR); + multi_progress_bar.print(); +} - void verify_progress(uint64_t amount, [[maybe_unused]] uint64_t total) override { - active_progress_bar->set_ticks(static_cast(amount)); - if (is_time_to_print()) { - multi_progress_bar.print(); - } - } +void RpmTransCB::script_error( + [[maybe_unused]] const libdnf5::rpm::TransactionItem * item, + libdnf5::rpm::Nevra nevra, + libdnf5::rpm::TransactionCallbacks::ScriptType type, + uint64_t return_code) { + active_progress_bar->add_message( + libdnf5::cli::progressbar::MessageType::ERROR, + fmt::format( + "Error in {} scriptlet: {} return code {}", + script_type_to_string(type), + to_full_nevra_string(nevra), + return_code)); + multi_progress_bar.print(); +} - void verify_start([[maybe_unused]] uint64_t total) override { - new_progress_bar(static_cast(total), "Verify package files"); - } +void RpmTransCB::script_start( + [[maybe_unused]] const libdnf5::rpm::TransactionItem * item, + libdnf5::rpm::Nevra nevra, + libdnf5::rpm::TransactionCallbacks::ScriptType type) { + active_progress_bar->add_message( + libdnf5::cli::progressbar::MessageType::INFO, + fmt::format("Running {} scriptlet: {}", script_type_to_string(type), to_full_nevra_string(nevra))); + multi_progress_bar.print(); +} + +void RpmTransCB::script_stop( + [[maybe_unused]] const libdnf5::rpm::TransactionItem * item, + libdnf5::rpm::Nevra nevra, + libdnf5::rpm::TransactionCallbacks::ScriptType type, + [[maybe_unused]] uint64_t return_code) { + active_progress_bar->add_message( + libdnf5::cli::progressbar::MessageType::INFO, + fmt::format("Stop {} scriptlet: {}", script_type_to_string(type), to_full_nevra_string(nevra))); + multi_progress_bar.print(); +} + +void RpmTransCB::elem_progress( + [[maybe_unused]] const libdnf5::rpm::TransactionItem & item, + [[maybe_unused]] uint64_t amount, + [[maybe_unused]] uint64_t total) { + //std::cout << "Element progress: " << header.get_full_nevra() << " " << amount << '/' << total << std::endl; +} - void verify_stop([[maybe_unused]] uint64_t total) override { - active_progress_bar->set_ticks(static_cast(total)); +void RpmTransCB::verify_progress(uint64_t amount, [[maybe_unused]] uint64_t total) { + active_progress_bar->set_ticks(static_cast(amount)); + if (is_time_to_print()) { multi_progress_bar.print(); } +} -private: - void new_progress_bar(int64_t total, const std::string & descr) { - if (active_progress_bar && - active_progress_bar->get_state() != libdnf5::cli::progressbar::ProgressBarState::ERROR) { - active_progress_bar->set_state(libdnf5::cli::progressbar::ProgressBarState::SUCCESS); - } - auto progress_bar = - std::make_unique(static_cast(total), descr); - progress_bar->set_auto_finish(false); - progress_bar->start(); - active_progress_bar = progress_bar.get(); - multi_progress_bar.add_bar(std::move(progress_bar)); - } - - static bool is_time_to_print() { - auto now = std::chrono::steady_clock::now(); - auto delta = now - prev_print_time; - auto ms = std::chrono::duration_cast(delta).count(); - if (ms > 100) { - // 100ms equals to 10 FPS and that seems to be smooth enough - prev_print_time = now; - return true; - } - return false; - } +void RpmTransCB::verify_start([[maybe_unused]] uint64_t total) { + new_progress_bar(static_cast(total), "Verify package files"); +} - static std::chrono::time_point prev_print_time; +void RpmTransCB::verify_stop([[maybe_unused]] uint64_t total) { + active_progress_bar->set_ticks(static_cast(total)); + multi_progress_bar.print(); +} +void RpmTransCB::new_progress_bar(int64_t total, const std::string & descr) { + if (active_progress_bar && active_progress_bar->get_state() != libdnf5::cli::progressbar::ProgressBarState::ERROR) { + active_progress_bar->set_state(libdnf5::cli::progressbar::ProgressBarState::SUCCESS); + } + auto progress_bar = + std::make_unique(static_cast(total), descr); + progress_bar->set_auto_finish(false); + progress_bar->start(); + active_progress_bar = progress_bar.get(); + multi_progress_bar.add_bar(std::move(progress_bar)); +} - libdnf5::cli::progressbar::MultiProgressBar multi_progress_bar; - libdnf5::cli::progressbar::DownloadProgressBar * active_progress_bar{nullptr}; -}; +bool RpmTransCB::is_time_to_print() { + auto now = std::chrono::steady_clock::now(); + auto delta = now - prev_print_time; + auto ms = std::chrono::duration_cast(delta).count(); + if (ms > 100) { + // 100ms equals to 10 FPS and that seems to be smooth enough + prev_print_time = now; + return true; + } + return false; +} std::chrono::time_point RpmTransCB::prev_print_time = std::chrono::steady_clock::now(); -} // namespace - void Context::download_and_run(libdnf5::base::Transaction & transaction) { transaction.download(); diff --git a/dnf5/include/dnf5/context.hpp b/dnf5/include/dnf5/context.hpp index df430e4aa..f3021ba63 100644 --- a/dnf5/include/dnf5/context.hpp +++ b/dnf5/include/dnf5/context.hpp @@ -23,6 +23,7 @@ along with libdnf. If not, see . #include "version.hpp" #include +#include #include #include #include @@ -183,6 +184,85 @@ class RpmTransactionItem : public libdnf5::rpm::TransactionItem { Actions action; }; +class RpmTransCB : public libdnf5::rpm::TransactionCallbacks { +public: + RpmTransCB(); + ~RpmTransCB(); + libdnf5::cli::progressbar::MultiProgressBar * get_multi_progress_bar(); + + void install_progress( + [[maybe_unused]] const libdnf5::rpm::TransactionItem & item, + uint64_t amount, + [[maybe_unused]] uint64_t total) override; + + void install_start(const libdnf5::rpm::TransactionItem & item, uint64_t total) override; + void install_stop( + [[maybe_unused]] const libdnf5::rpm::TransactionItem & item, + [[maybe_unused]] uint64_t amount, + [[maybe_unused]] uint64_t total) override; + + void transaction_progress(uint64_t amount, [[maybe_unused]] uint64_t total) override; + + void transaction_start(uint64_t total) override; + + void transaction_stop([[maybe_unused]] uint64_t total) override; + + void uninstall_progress( + [[maybe_unused]] const libdnf5::rpm::TransactionItem & item, + uint64_t amount, + [[maybe_unused]] uint64_t total) override; + + void uninstall_start(const libdnf5::rpm::TransactionItem & item, uint64_t total) override; + + void uninstall_stop( + [[maybe_unused]] const libdnf5::rpm::TransactionItem & item, + [[maybe_unused]] uint64_t amount, + [[maybe_unused]] uint64_t total) override; + + + void unpack_error(const libdnf5::rpm::TransactionItem & item) override; + + void cpio_error(const libdnf5::rpm::TransactionItem & item) override; + + void script_error( + [[maybe_unused]] const libdnf5::rpm::TransactionItem * item, + libdnf5::rpm::Nevra nevra, + libdnf5::rpm::TransactionCallbacks::ScriptType type, + uint64_t return_code) override; + + void script_start( + [[maybe_unused]] const libdnf5::rpm::TransactionItem * item, + libdnf5::rpm::Nevra nevra, + libdnf5::rpm::TransactionCallbacks::ScriptType type) override; + + void script_stop( + [[maybe_unused]] const libdnf5::rpm::TransactionItem * item, + libdnf5::rpm::Nevra nevra, + libdnf5::rpm::TransactionCallbacks::ScriptType type, + [[maybe_unused]] uint64_t return_code) override; + + void elem_progress( + [[maybe_unused]] const libdnf5::rpm::TransactionItem & item, + [[maybe_unused]] uint64_t amount, + [[maybe_unused]] uint64_t total) override; + + void verify_progress(uint64_t amount, [[maybe_unused]] uint64_t total) override; + + void verify_start([[maybe_unused]] uint64_t total) override; + + void verify_stop([[maybe_unused]] uint64_t total) override; + +private: + void new_progress_bar(int64_t total, const std::string & descr); + + static bool is_time_to_print(); + + static std::chrono::time_point prev_print_time; + + libdnf5::cli::progressbar::MultiProgressBar multi_progress_bar; + libdnf5::cli::progressbar::DownloadProgressBar * active_progress_bar{nullptr}; +}; + void run_transaction(libdnf5::rpm::Transaction & transaction); /// Returns the names of matching packages and paths of matching package file names and directories. From 1013f8248b8e82c5ea5d17d749abb45f4ba7079c Mon Sep 17 00:00:00 2001 From: Evan Goode Date: Fri, 2 Feb 2024 19:31:39 +0000 Subject: [PATCH 5/7] system-upgrade: Minor changes to address feedback --- .../dnf5-system-upgrade.service | 1 - .../system_upgrade_plugin/system_upgrade.cpp | 22 ++++++++++++------- .../system_upgrade_plugin/system_upgrade.hpp | 1 - 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/dnf5-plugins/system_upgrade_plugin/dnf5-system-upgrade.service b/dnf5-plugins/system_upgrade_plugin/dnf5-system-upgrade.service index 98a260781..a846ad5bf 100644 --- a/dnf5-plugins/system_upgrade_plugin/dnf5-system-upgrade.service +++ b/dnf5-plugins/system_upgrade_plugin/dnf5-system-upgrade.service @@ -7,7 +7,6 @@ DefaultDependencies=no Requires=sysinit.target After=sysinit.target systemd-journald.socket system-update-pre.target Before=poweroff.target reboot.target shutdown.target system-update.target -OnFailure=dnf-system-upgrade-cleanup.service [Service] # We are done when the script exits, not before diff --git a/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp b/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp index cb721b48d..95d3549ea 100644 --- a/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp +++ b/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp @@ -19,6 +19,7 @@ along with libdnf. If not, see . #include "system_upgrade.hpp" +#include #include #include #include @@ -82,7 +83,6 @@ int call(const std::string & command, const std::vector & args) { namespace dnf5 { - /// Helper for displaying messages with Plymouth /// /// Derived from DNF 4 system-upgrade PlymouthOutput implementation. Filters @@ -266,11 +266,10 @@ void SystemUpgradeDownloadCommand::run() { const auto & goal = std::make_unique(ctx.base); auto & conf = ctx.base.get_config(); - conf.opt_binds().at("tsflags").new_string(libdnf5::Option::Priority::RUNTIME, "test"); + conf.get_tsflags_option().set(libdnf5::Option::Priority::RUNTIME, "test"); if (no_downgrade->get_value()) { - const auto & settings = libdnf5::GoalJobSettings(); - goal->add_rpm_upgrade(settings); + goal->add_rpm_upgrade(); } else { goal->add_rpm_distro_sync(); } @@ -314,7 +313,8 @@ void SystemUpgradeDownloadCommand::run() { state.write(); std::cout << "Download complete! Use `dnf5 system-upgrade reboot` to start the upgrade." << std::endl - << "To remove cached metadata and transaction, use `dnf5 system-upgrade clean`." << std::endl; + << "To cancel the upgrade and delete the downloaded upgrade files, use `dnf5 system-upgrade clean`." + << std::endl; } void check_state(const OfflineTransactionState & state) { @@ -385,6 +385,8 @@ void SystemUpgradeRebootCommand::set_argument_parser() { } void SystemUpgradeRebootCommand::run() { + auto & ctx = get_context(); + auto state = get_state(); check_state(state); if (state.get_data().status != STATUS_DOWNLOAD_COMPLETE) { @@ -398,6 +400,12 @@ void SystemUpgradeRebootCommand::run() { throw libdnf5::cli::CommandExitError(1, M_("data directory {} does not exist"), get_datadir().string()); } + std::cout << "The system will now reboot to upgrade to release version " << state.get_data().target_releasever + << ". Do you want to continue?" << std::endl; + if (!libdnf5::cli::utils::userconfirm::userconfirm(ctx.base.get_config())) { + return; + } + std::filesystem::create_symlink(get_datadir(), get_magic_symlink()); state.get_data().status = STATUS_READY; @@ -500,9 +508,7 @@ void SystemUpgradeUpgradeCommand::run() { // If the upgrade succeeded, remove downloaded data clean(ctx, get_datadir()); - if (state.get_data().poweroff_after) { - reboot(ctx, true); - } + reboot(ctx, state.get_data().poweroff_after); } void SystemUpgradeCleanCommand::set_argument_parser() { diff --git a/dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp b/dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp index 8502dbe6a..bd7667612 100644 --- a/dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp +++ b/dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp @@ -95,7 +95,6 @@ class SystemUpgradeSubcommand : public Command { protected: libdnf5::OptionPath * get_cachedir() { return cachedir; }; std::filesystem::path get_datadir() { return datadir; }; - void set_datadir(std::filesystem::path new_datadir) { datadir = std::move(new_datadir); }; std::filesystem::path get_magic_symlink() { return magic_symlink; }; OfflineTransactionState get_state() { return state; }; From 857c9d99203e04856666e1e503daf98d4cc23bbb Mon Sep 17 00:00:00 2001 From: Evan Goode Date: Fri, 2 Feb 2024 22:33:15 +0000 Subject: [PATCH 6/7] system-upgrade: implement `log` subcommand This commit brings in systemd-devel as a build dependency for dnf5-plugins so system-upgrade can log directly to journald and so the `log` subcommand can search the journal for upgrade logs. Perhaps in a subsequent patch, this dependency can be put behind a switch. --- .../system_upgrade_plugin/CMakeLists.txt | 3 +- .../system_upgrade_plugin/system_upgrade.cpp | 166 ++++++++++++++++-- .../system_upgrade_plugin/system_upgrade.hpp | 21 ++- dnf5.spec | 1 + 4 files changed, 174 insertions(+), 17 deletions(-) diff --git a/dnf5-plugins/system_upgrade_plugin/CMakeLists.txt b/dnf5-plugins/system_upgrade_plugin/CMakeLists.txt index cf5beaab5..bd6519526 100644 --- a/dnf5-plugins/system_upgrade_plugin/CMakeLists.txt +++ b/dnf5-plugins/system_upgrade_plugin/CMakeLists.txt @@ -10,8 +10,9 @@ add_library(system_upgrade_cmd_plugin MODULE ${SYSTEM_UPGRADE_SOURCES}) set_target_properties(system_upgrade_cmd_plugin PROPERTIES PREFIX "") pkg_check_modules(SDBUS_CPP REQUIRED sdbus-c++) +pkg_check_modules(LIBSYSTEMD REQUIRED libsystemd) -target_link_libraries(system_upgrade_cmd_plugin PRIVATE dnf5 libdnf5 libdnf5-cli ${SDBUS_CPP_LIBRARIES}) +target_link_libraries(system_upgrade_cmd_plugin PRIVATE dnf5 libdnf5 libdnf5-cli ${SDBUS_CPP_LIBRARIES} ${LIBSYSTEMD_LIBRARIES}) install(FILES "dnf5-system-upgrade.service" DESTINATION ${SYSTEMD_UNIT_DIR}) install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink diff --git a/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp b/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp index 95d3549ea..7db696ae3 100644 --- a/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp +++ b/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp @@ -19,6 +19,8 @@ along with libdnf. If not, see . #include "system_upgrade.hpp" +#include "utils/string.hpp" + #include #include #include @@ -26,12 +28,17 @@ along with libdnf. If not, see . #include #include #include +#include +#include #include #include +#include using namespace libdnf5::cli; +const std::string & ID_TO_IDENTIFY_BOOTS = OFFLINE_STARTED_ID; + OfflineTransactionState::OfflineTransactionState(std::filesystem::path path) : path(std::move(path)) { read(); } @@ -123,6 +130,7 @@ class PlymouthOutput { /// Extend RpmTransCB to also display messages with Plymouth class PlymouthTransCB : public RpmTransCB { public: + PlymouthTransCB(PlymouthOutput plymouth) : plymouth(std::move(plymouth)) {} void elem_progress( [[maybe_unused]] const libdnf5::rpm::TransactionItem & item, [[maybe_unused]] uint64_t amount, @@ -189,6 +197,7 @@ void SystemUpgradeCommand::register_subcommands() { register_subcommand(std::make_unique(get_context())); register_subcommand(std::make_unique(get_context())); register_subcommand(std::make_unique(get_context())); + register_subcommand(std::make_unique(get_context())); } SystemUpgradeSubcommand::SystemUpgradeSubcommand(Context & context, const std::string & name) @@ -215,6 +224,26 @@ void SystemUpgradeSubcommand::configure() { auto & ctx = get_context(); const std::filesystem::path installroot{ctx.base.get_config().get_installroot_option().get_value()}; magic_symlink = installroot / "system-update"; + + system_releasever = *libdnf5::Vars::detect_release(ctx.base.get_weak_ptr(), installroot); + target_releasever = ctx.base.get_vars()->get_value("releasever"); +} + +void SystemUpgradeSubcommand::log_status(const std::string & message, const std::string & message_id) const { + const auto & version = get_application_version(); + const std::string & version_string = fmt::format("{}.{}.{}", version.major, version.minor, version.micro); + sd_journal_send( + "MESSAGE=%s", + message.c_str(), + "MESSAGE_ID=%s", + message_id.c_str(), + "SYSTEM_RELEASEVER=%s", + get_system_releasever().c_str(), + "TARGET_RELEASEVER=%s", + get_target_releasever().c_str(), + "DNF_VERSION=%s", + version_string.c_str(), + NULL); } void SystemUpgradeDownloadCommand::set_argument_parser() { @@ -242,14 +271,9 @@ void SystemUpgradeDownloadCommand::configure() { SystemUpgradeSubcommand::configure(); auto & ctx = get_context(); - auto & conf = ctx.base.get_config(); // Check --releasever - const auto & installroot = conf.get_installroot_option().get_value(); - system_releasever = *libdnf5::Vars::detect_release(ctx.base.get_weak_ptr(), installroot); - target_releasever = ctx.base.get_vars()->get_value("releasever"); - - if (target_releasever == system_releasever) { + if (get_target_releasever() == get_system_releasever()) { throw libdnf5::cli::CommandExitError(1, M_("Need a --releasever greater than the current system version.")); } @@ -281,7 +305,7 @@ void SystemUpgradeDownloadCommand::run() { ctx.download_and_run(transaction); - if (!transaction.get_transaction_packages_count()) { + if (transaction.get_transaction_packages_count() == 0) { throw libdnf5::cli::CommandExitError( 1, M_("The system-upgrade transaction is empty; your system is already up-to-date.")); } @@ -295,8 +319,8 @@ void SystemUpgradeDownloadCommand::run() { auto state = get_state(); state.get_data().cachedir = get_cachedir()->get_value(); - state.get_data().system_releasever = system_releasever; - state.get_data().target_releasever = target_releasever; + state.get_data().system_releasever = get_system_releasever(); + state.get_data().target_releasever = get_target_releasever(); state.get_data().status = STATUS_DOWNLOAD_COMPLETE; libdnf5::repo::RepoQuery enabled_repos(ctx.base); enabled_repos.filter_enabled(true); @@ -451,6 +475,8 @@ void SystemUpgradeUpgradeCommand::configure() { void SystemUpgradeUpgradeCommand::run() { auto & ctx = get_context(); + log_status("Starting offline transaction. This will take a while.", OFFLINE_STARTED_ID); + if (!std::filesystem::is_symlink(get_magic_symlink())) { throw libdnf5::cli::CommandExitError(0, M_("Trigger file does not exist. Exiting.")); } @@ -483,7 +509,8 @@ void SystemUpgradeUpgradeCommand::run() { throw libdnf5::cli::GoalResolveError(transaction); } - auto callbacks = std::make_unique(); + PlymouthOutput plymouth; + auto callbacks = std::make_unique(plymouth); /* callbacks->get_multi_progress_bar()->set_total_num_of_bars(num_of_actions); */ transaction.set_callbacks(std::move(callbacks)); @@ -505,6 +532,15 @@ void SystemUpgradeUpgradeCommand::run() { std::cerr << entry << std::endl; } + std::string upgrade_complete_message; + if (state.get_data().poweroff_after) { + upgrade_complete_message = "Upgrade complete! Cleaning up and powering off..."; + } else { + upgrade_complete_message = "Upgrade complete! Cleaning up and rebooting..."; + } + plymouth.message(upgrade_complete_message); + log_status(upgrade_complete_message, OFFLINE_STARTED_ID); + // If the upgrade succeeded, remove downloaded data clean(ctx, get_datadir()); @@ -520,4 +556,114 @@ void SystemUpgradeCleanCommand::run() { clean(ctx, get_datadir()); } +struct BootEntry { + std::string boot_id; + std::string timestamp; + std::string system_releasever; + std::string target_releasever; +}; + +std::string get_journal_field(sd_journal * journal, const std::string & field) { + const char * data = nullptr; + size_t length = 0; + auto rc = sd_journal_get_data(journal, field.c_str(), reinterpret_cast(&data), &length); + if (rc < 0 || data == nullptr) { + return ""; + } + const auto prefix_length = field.length() + 1; + return std::string{data + prefix_length, length - prefix_length}; +} + +std::vector find_boots(const std::string & message_id) { + std::vector boots{}; + + sd_journal * journal = nullptr; + auto rc = sd_journal_open(&journal, SD_JOURNAL_LOCAL_ONLY); + if (rc < 0) { + throw libdnf5::RuntimeError(M_("Error reading journal: {}"), std::string{std::strerror(-rc)}); + } + + const auto & uid_filter_string = fmt::format("MESSAGE_ID={}", message_id); + rc = sd_journal_add_match(journal, uid_filter_string.c_str(), 0); + if (rc < 0) { + throw libdnf5::RuntimeError(M_("Add journal match failed: {}"), std::string{std::strerror(-rc)}); + } + SD_JOURNAL_FOREACH(journal) { + uint64_t usec = 0; + rc = sd_journal_get_realtime_usec(journal, &usec); + auto sec = usec / (1000 * 1000); + boots.emplace_back(BootEntry{ + get_journal_field(journal, "_BOOT_ID"), + libdnf5::utils::string::format_epoch(sec), + get_journal_field(journal, "SYSTEM_RELEASEVER"), + get_journal_field(journal, "TARGET_RELEASEVER"), + }); + } + + return boots; +} + +void list_logs() { + const auto & boot_entries = find_boots(ID_TO_IDENTIFY_BOOTS); + + std::cout << "The following boots appear to contain upgrade logs:" << std::endl; + for (size_t index = 0; index < boot_entries.size(); index += 1) { + const auto & entry = boot_entries[index]; + std::cout << fmt::format( + "{} / {}: {} {}→{}", + index + 1, + entry.boot_id, + entry.timestamp, + entry.system_releasever, + entry.target_releasever) + << std::endl; + } + if (boot_entries.empty()) { + std::cout << "No logs were found." << std::endl; + } +} + +void show_log(size_t boot_index) { + const auto & boot_entries = find_boots(ID_TO_IDENTIFY_BOOTS); + if (boot_index >= boot_entries.size()) { + throw libdnf5::cli::CommandExitError(1, M_("Cannot find logs with this index.")); + } + + const auto & boot_id = boot_entries[boot_index].boot_id; + const auto rc = call(PATH_TO_JOURNALCTL, {"--boot", boot_id}); + + if (rc != 0) { + throw libdnf5::cli::CommandExitError(1, M_("Unable to match systemd journal entry.")); + } +} + +void SystemUpgradeLogCommand::set_argument_parser() { + SystemUpgradeSubcommand::set_argument_parser(); + + auto & ctx = get_context(); + auto & parser = ctx.get_argument_parser(); + auto & cmd = *get_argument_parser_command(); + + cmd.set_description("Show logs from past upgrades"); + + number = dynamic_cast(parser.add_init_value(std::make_unique(""))); + + auto * number_arg = parser.add_new_named_arg("number"); + number_arg->set_long_name("number"); + number_arg->set_has_value(true); + + number_arg->set_description("Which log to show. Run without any arguments to get a list of available logs."); + number_arg->link_value(number); + cmd.register_named_arg(number_arg); +} + +void SystemUpgradeLogCommand::run() { + if (number->get_value().empty()) { + list_logs(); + } else { + std::string number_string{number->get_value()}; + show_log(std::stoul(number_string) - 1); + } +} + } // namespace dnf5 diff --git a/dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp b/dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp index bd7667612..c976b1bc9 100644 --- a/dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp +++ b/dnf5-plugins/system_upgrade_plugin/system_upgrade.hpp @@ -28,6 +28,9 @@ along with libdnf. If not, see . #include const std::filesystem::path PATH_TO_PLYMOUTH{"/usr/bin/plymouth"}; +const std::filesystem::path PATH_TO_JOURNALCTL{"/usr/bin/journalctl"}; +const std::string OFFLINE_STARTED_ID{"3e0a5636d16b4ca4bbe5321d06c6aa62"}; +const std::string OFFLINE_FINISHED_ID{"8cec00a1566f4d3594f116450395f06c"}; const std::string STATUS_DOWNLOAD_INCOMPLETE{"download-incomplete"}; const std::string STATUS_DOWNLOAD_COMPLETE{"download-complete"}; @@ -93,16 +96,21 @@ class SystemUpgradeSubcommand : public Command { void configure() override; protected: - libdnf5::OptionPath * get_cachedir() { return cachedir; }; - std::filesystem::path get_datadir() { return datadir; }; - std::filesystem::path get_magic_symlink() { return magic_symlink; }; - OfflineTransactionState get_state() { return state; }; + libdnf5::OptionPath * get_cachedir() const { return cachedir; }; + std::filesystem::path get_datadir() const { return datadir; }; + std::filesystem::path get_magic_symlink() const { return magic_symlink; }; + OfflineTransactionState get_state() const { return state; }; + std::string get_system_releasever() const { return system_releasever; }; + std::string get_target_releasever() const { return target_releasever; }; + void log_status(const std::string & message, const std::string & message_id) const; private: libdnf5::OptionPath * cachedir{nullptr}; std::filesystem::path datadir{DEFAULT_DATADIR}; std::filesystem::path magic_symlink; OfflineTransactionState state; + std::string target_releasever; + std::string system_releasever; }; class SystemUpgradeDownloadCommand : public SystemUpgradeSubcommand { @@ -115,8 +123,6 @@ class SystemUpgradeDownloadCommand : public SystemUpgradeSubcommand { private: libdnf5::OptionBool * no_downgrade{nullptr}; libdnf5::OptionBool * distro_sync{nullptr}; - std::string target_releasever; - std::string system_releasever; int state_version; }; @@ -150,6 +156,9 @@ class SystemUpgradeLogCommand : public SystemUpgradeSubcommand { explicit SystemUpgradeLogCommand(Context & context) : SystemUpgradeSubcommand(context, "log") {} void set_argument_parser() override; void run() override; + +private: + libdnf5::OptionString * number{nullptr}; }; } // namespace dnf5 diff --git a/dnf5.spec b/dnf5.spec index a27720f87..2b97eb810 100644 --- a/dnf5.spec +++ b/dnf5.spec @@ -174,6 +174,7 @@ BuildRequires: pkgconfig(smartcols) %if %{with dnf5_plugins} BuildRequires: libcurl-devel >= 7.62.0 +BuildRequires: systemd-devel %endif %if %{with dnf5daemon_server} From 1dd933df6001607861746970f4e115cd5e9068b0 Mon Sep 17 00:00:00 2001 From: Evan Goode Date: Fri, 2 Feb 2024 22:38:46 +0000 Subject: [PATCH 7/7] system-upgrade: fix journalctl exit code --- dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp b/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp index 7db696ae3..95c6bc62e 100644 --- a/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp +++ b/dnf5-plugins/system_upgrade_plugin/system_upgrade.cpp @@ -84,6 +84,9 @@ int call(const std::string & command, const std::vector & args) { if (WIFEXITED(status)) { return WEXITSTATUS(status); } + if (WIFSIGNALED(status)) { + return 128 + WTERMSIG(status); + } return -1; } } @@ -632,7 +635,7 @@ void show_log(size_t boot_index) { const auto & boot_id = boot_entries[boot_index].boot_id; const auto rc = call(PATH_TO_JOURNALCTL, {"--boot", boot_id}); - if (rc != 0) { + if (rc != 0 && rc != 141) { throw libdnf5::cli::CommandExitError(1, M_("Unable to match systemd journal entry.")); } }