From 2fb5a9e220b536ac8fdbe9d3b565a91326a1d5ea Mon Sep 17 00:00:00 2001 From: Marek Blaha Date: Thu, 30 May 2024 09:25:50 +0200 Subject: [PATCH] Move offline from dnf5 to libdnf5 We will need to share the code also with dnf5daemon-server to support offline upgrades using D-Bus API. --- dnf5/commands/offline/offline.cpp | 33 +++---- dnf5/commands/offline/offline.hpp | 4 +- .../system-upgrade/system-upgrade.hpp | 4 +- dnf5/context.cpp | 16 ++-- dnf5/include/dnf5/offline.hpp | 66 +------------- dnf5/offline.cpp | 45 ---------- include/libdnf5/transaction/offline.hpp | 90 +++++++++++++++++++ libdnf5/transaction/offline.cpp | 69 ++++++++++++++ 8 files changed, 190 insertions(+), 137 deletions(-) create mode 100644 include/libdnf5/transaction/offline.hpp create mode 100644 libdnf5/transaction/offline.cpp diff --git a/dnf5/commands/offline/offline.cpp b/dnf5/commands/offline/offline.cpp index 12344e7e33..8d0272b074 100644 --- a/dnf5/commands/offline/offline.cpp +++ b/dnf5/commands/offline/offline.cpp @@ -29,6 +29,7 @@ along with libdnf. If not, see . #include #include #include +#include #include #include #include @@ -45,7 +46,7 @@ along with libdnf. If not, see . using namespace libdnf5::cli; -const std::string & ID_TO_IDENTIFY_BOOTS = dnf5::offline::OFFLINE_STARTED_ID; +const std::string & ID_TO_IDENTIFY_BOOTS = libdnf5::offline::OFFLINE_STARTED_ID; const std::string SYSTEMD_DESTINATION_NAME{"org.freedesktop.systemd1"}; const std::string SYSTEMD_OBJECT_PATH{"/org/freedesktop/systemd1"}; @@ -207,12 +208,12 @@ void OfflineSubcommand::configure() { magic_symlink = "/system-update"; const std::filesystem::path installroot = ctx.get_base().get_config().get_installroot_option().get_value(); - datadir = installroot / dnf5::offline::DEFAULT_DATADIR.relative_path(); + datadir = installroot / libdnf5::offline::DEFAULT_DATADIR.relative_path(); std::filesystem::create_directories(datadir); - state = std::make_optional(datadir / "offline-transaction-state.toml"); + state = std::make_optional(datadir / "offline-transaction-state.toml"); } -void check_state(const dnf5::offline::OfflineTransactionState & state) { +void check_state(const libdnf5::offline::OfflineTransactionState & state) { const auto & read_exception = state.get_read_exception(); if (read_exception != nullptr) { try { @@ -296,8 +297,8 @@ void OfflineRebootCommand::run() { check_state(*state); - if (state->get_data().status != dnf5::offline::STATUS_DOWNLOAD_COMPLETE && - state->get_data().status != dnf5::offline::STATUS_READY) { + if (state->get_data().status != libdnf5::offline::STATUS_DOWNLOAD_COMPLETE && + state->get_data().status != libdnf5::offline::STATUS_READY) { throw libdnf5::cli::CommandExitError(1, M_("System is not ready for offline transaction.")); } if (!std::filesystem::is_directory(get_datadir())) { @@ -350,14 +351,14 @@ void OfflineRebootCommand::run() { std::filesystem::create_symlink(get_datadir(), get_magic_symlink()); } - state->get_data().status = dnf5::offline::STATUS_READY; + state->get_data().status = libdnf5::offline::STATUS_READY; state->get_data().poweroff_after = poweroff_after->get_value(); state->write(); dnf5::offline::log_status( ctx, "Rebooting to perform offline transaction.", - dnf5::offline::REBOOT_REQUESTED_ID, + libdnf5::offline::REBOOT_REQUESTED_ID, system_releasever, target_releasever); @@ -427,7 +428,7 @@ void OfflineExecuteCommand::run() { dnf5::offline::log_status( ctx, "Starting offline transaction. This will take a while.", - dnf5::offline::OFFLINE_STARTED_ID, + libdnf5::offline::OFFLINE_STARTED_ID, system_releasever, target_releasever); @@ -438,15 +439,15 @@ void OfflineExecuteCommand::run() { std::filesystem::remove(get_magic_symlink()); - if (state->get_data().status != dnf5::offline::STATUS_READY) { + if (state->get_data().status != libdnf5::offline::STATUS_READY) { throw libdnf5::cli::CommandExitError(1, M_("Use `dnf5 offline reboot` to begin the transaction.")); } - state->get_data().status = dnf5::offline::STATUS_TRANSACTION_INCOMPLETE; + state->get_data().status = libdnf5::offline::STATUS_TRANSACTION_INCOMPLETE; state->write(); const auto & installroot = ctx.get_base().get_config().get_installroot_option().get_value(); - const auto & datadir = installroot / dnf5::offline::DEFAULT_DATADIR.relative_path(); + const auto & datadir = installroot / libdnf5::offline::DEFAULT_DATADIR.relative_path(); std::filesystem::create_directories(datadir); const auto & transaction_json_path = datadir / "transaction.json"; @@ -514,7 +515,7 @@ void OfflineExecuteCommand::run() { plymouth.message(_(transaction_complete_message.c_str())); dnf5::offline::log_status( - ctx, transaction_complete_message, dnf5::offline::OFFLINE_FINISHED_ID, system_releasever, target_releasever); + ctx, transaction_complete_message, libdnf5::offline::OFFLINE_FINISHED_ID, system_releasever, target_releasever); // If the transaction succeeded, remove downloaded data clean_datadir(ctx, get_datadir()); @@ -674,13 +675,13 @@ void OfflineStatusCommand::run() { check_state(*state); const auto & status = state->get_data().status; - if (status == offline::STATUS_DOWNLOAD_INCOMPLETE) { + if (status == libdnf5::offline::STATUS_DOWNLOAD_INCOMPLETE) { std::cout << no_transaction_message << std::endl; - } else if (status == offline::STATUS_DOWNLOAD_COMPLETE || status == offline::STATUS_READY) { + } else if (status == libdnf5::offline::STATUS_DOWNLOAD_COMPLETE || status == libdnf5::offline::STATUS_READY) { std::cout << _("An offline transaction was initiated by the following command:") << std::endl << "\t" << state->get_data().cmd_line << std::endl << _("Run `dnf5 offline reboot` to reboot and perform the offline transaction.") << std::endl; - } else if (status == offline::STATUS_TRANSACTION_INCOMPLETE) { + } else if (status == libdnf5::offline::STATUS_TRANSACTION_INCOMPLETE) { std::cout << _("An offline transaction was started, but it did not finish. Run `dnf5 offline log` for more " "information. The command that initiated the transaction was:") << std::endl diff --git a/dnf5/commands/offline/offline.hpp b/dnf5/commands/offline/offline.hpp index bd299e1ffb..b92bb6ba7a 100644 --- a/dnf5/commands/offline/offline.hpp +++ b/dnf5/commands/offline/offline.hpp @@ -21,10 +21,10 @@ along with libdnf. If not, see . #define DNF5_COMMANDS_OFFLINE_HPP #include -#include #include #include #include +#include const std::filesystem::path PATH_TO_PLYMOUTH{"/usr/bin/plymouth"}; const std::filesystem::path PATH_TO_JOURNALCTL{"/usr/bin/journalctl"}; @@ -48,7 +48,7 @@ class OfflineSubcommand : public Command { protected: std::filesystem::path get_magic_symlink() const { return magic_symlink; }; std::filesystem::path get_datadir() const { return datadir; }; - std::optional state; + std::optional state; private: std::filesystem::path magic_symlink; diff --git a/dnf5/commands/system-upgrade/system-upgrade.hpp b/dnf5/commands/system-upgrade/system-upgrade.hpp index 3b4bf60bcd..65f330cbd8 100644 --- a/dnf5/commands/system-upgrade/system-upgrade.hpp +++ b/dnf5/commands/system-upgrade/system-upgrade.hpp @@ -21,7 +21,7 @@ along with libdnf. If not, see . #define DNF5_COMMANDS_SYSTEM_UPGRADE_HPP #include -#include +#include namespace dnf5 { @@ -43,7 +43,7 @@ class SystemUpgradeDownloadCommand : public Command { private: libdnf5::OptionBool * no_downgrade{nullptr}; - std::filesystem::path datadir{dnf5::offline::DEFAULT_DATADIR}; + std::filesystem::path datadir{libdnf5::offline::DEFAULT_DATADIR}; std::string target_releasever; std::string system_releasever; }; diff --git a/dnf5/context.cpp b/dnf5/context.cpp index 3ebebb22fd..41227017dc 100644 --- a/dnf5/context.cpp +++ b/dnf5/context.cpp @@ -19,7 +19,6 @@ along with libdnf. If not, see . #include "dnf5/context.hpp" -#include "dnf5/offline.hpp" #include "download_callbacks.hpp" #include "plugins.hpp" #include "utils/string.hpp" @@ -37,6 +36,7 @@ along with libdnf. If not, see . #include #include #include +#include #include #include #include @@ -309,7 +309,7 @@ void Context::Impl::load_repos(bool load_system) { void Context::Impl::store_offline(libdnf5::base::Transaction & transaction) { const auto & installroot = base.get_config().get_installroot_option().get_value(); - const auto & offline_datadir = installroot / dnf5::offline::DEFAULT_DATADIR.relative_path(); + const auto & offline_datadir = installroot / libdnf5::offline::DEFAULT_DATADIR.relative_path(); std::filesystem::create_directories(offline_datadir); constexpr const char * packages_in_trans_dir{"./packages"}; @@ -317,10 +317,10 @@ void Context::Impl::store_offline(libdnf5::base::Transaction & transaction) { constexpr const char * comps_in_trans_dir{"./comps"}; const auto & comps_location = offline_datadir / comps_in_trans_dir; - const std::filesystem::path state_path{offline_datadir / dnf5::offline::TRANSACTION_STATE_FILENAME}; - dnf5::offline::OfflineTransactionState state{state_path}; + const std::filesystem::path state_path{offline_datadir / libdnf5::offline::TRANSACTION_STATE_FILENAME}; + libdnf5::offline::OfflineTransactionState state{state_path}; - if (state.get_data().status != dnf5::offline::STATUS_DOWNLOAD_INCOMPLETE) { + if (state.get_data().status != libdnf5::offline::STATUS_DOWNLOAD_INCOMPLETE) { std::cout << "There is already an offline transaction queued, initiated by the following command:" << std::endl << "\t" << state.get_data().cmd_line << std::endl << "Continuing will cancel the old offline transaction and replace it with this one." << std::endl; @@ -329,7 +329,7 @@ void Context::Impl::store_offline(libdnf5::base::Transaction & transaction) { } } - state.get_data().status = dnf5::offline::STATUS_DOWNLOAD_INCOMPLETE; + state.get_data().status = libdnf5::offline::STATUS_DOWNLOAD_INCOMPLETE; state.write(); // First, serialize the transaction @@ -369,7 +369,7 @@ void Context::Impl::store_offline(libdnf5::base::Transaction & transaction) { // Download and transaction test complete. Fill out entries in offline // transaction state file. - state.get_data().status = dnf5::offline::STATUS_DOWNLOAD_COMPLETE; + state.get_data().status = libdnf5::offline::STATUS_DOWNLOAD_COMPLETE; state.get_data().cachedir = base.get_config().get_cachedir_option().get_value(); std::vector command_vector; @@ -424,7 +424,7 @@ void Context::Impl::download_and_run(libdnf5::base::Transaction & transaction) { if (should_store_offline) { const auto & installroot = base.get_config().get_installroot_option().get_value(); - const auto & offline_datadir = installroot / dnf5::offline::DEFAULT_DATADIR.relative_path(); + const auto & offline_datadir = installroot / libdnf5::offline::DEFAULT_DATADIR.relative_path(); std::filesystem::create_directories(offline_datadir); base.get_config().get_destdir_option().set(offline_datadir / "packages"); diff --git a/dnf5/include/dnf5/offline.hpp b/dnf5/include/dnf5/offline.hpp index 9aca7c3c69..0c369c861a 100644 --- a/dnf5/include/dnf5/offline.hpp +++ b/dnf5/include/dnf5/offline.hpp @@ -20,61 +20,11 @@ along with libdnf. If not, see . #ifndef DNF5_OFFLINE_HPP #define DNF5_OFFLINE_HPP -#include "dnf5/version.hpp" - #include -#include -#include -#include -#include - -namespace dnf5::offline { - -// Unique identifiers used to mark and identify system-upgrade boots in -// journald logs. These are the same as they are in `dnf4 system-upgrade`, so -// `dnf5 offline log` will find offline transactions performed by DNF 4 and -// vice-versa. -const std::string REBOOT_REQUESTED_ID{"9348174c5cc74001a71ef26bd79d302e"}; -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"}; -const std::string STATUS_READY{"ready"}; -const std::string STATUS_TRANSACTION_INCOMPLETE{"transaction-incomplete"}; -const int STATE_VERSION = 1; -const std::string STATE_HEADER{"offline-transaction-state"}; +#include -const std::filesystem::path DEFAULT_DATADIR{std::filesystem::path(libdnf5::SYSTEM_STATE_DIR) / "offline"}; -const std::filesystem::path TRANSACTION_STATE_FILENAME{"offline-transaction-state.toml"}; - -struct OfflineTransactionStateData { - int state_version = STATE_VERSION; - std::string status = STATUS_DOWNLOAD_INCOMPLETE; - std::string cachedir; - std::string target_releasever; - std::string system_releasever; - std::string verb; - std::string cmd_line; - bool poweroff_after = false; - std::string module_platform_id; -}; - -class OfflineTransactionState { -public: - void write(); - OfflineTransactionState(std::filesystem::path path); - OfflineTransactionStateData & get_data(); - const std::exception_ptr & get_read_exception() const; - std::filesystem::path get_path() const; - -private: - void read(); - std::exception_ptr read_exception; - std::filesystem::path path; - OfflineTransactionStateData data; -}; +namespace dnf5::offline { void log_status( Context & context, @@ -85,16 +35,4 @@ void log_status( } // namespace dnf5::offline -TOML11_DEFINE_CONVERSION_NON_INTRUSIVE( - dnf5::offline::OfflineTransactionStateData, - state_version, - status, - cachedir, - target_releasever, - system_releasever, - verb, - cmd_line, - poweroff_after, - module_platform_id) - #endif // DNF5_OFFLINE_HPP diff --git a/dnf5/offline.cpp b/dnf5/offline.cpp index ea0cc66713..574693a13f 100644 --- a/dnf5/offline.cpp +++ b/dnf5/offline.cpp @@ -19,57 +19,12 @@ along with libdnf. If not, see . #include "dnf5/offline.hpp" -#include "libdnf5/common/exception.hpp" - -#include -#include - #ifdef WITH_SYSTEMD #include #endif namespace dnf5::offline { -OfflineTransactionState::OfflineTransactionState(std::filesystem::path path) : path(std::move(path)) { - read(); -} - -OfflineTransactionStateData & OfflineTransactionState::get_data() { - return data; -} - -const std::exception_ptr & OfflineTransactionState::get_read_exception() const { - return read_exception; -} - -std::filesystem::path OfflineTransactionState::get_path() const { - return path; -} - - -void OfflineTransactionState::read() { - try { - const std::ifstream file{path}; - if (!file.good()) { - throw libdnf5::FileSystemError(errno, path, M_("error reading offline state file")); - } - 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(); -} - void log_status( Context & context, const std::string & message, diff --git a/include/libdnf5/transaction/offline.hpp b/include/libdnf5/transaction/offline.hpp new file mode 100644 index 0000000000..3449732ef5 --- /dev/null +++ b/include/libdnf5/transaction/offline.hpp @@ -0,0 +1,90 @@ +/* +Copyright (C) 2024 Red Hat, Inc. + +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 Lesser 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with libdnf. If not, see . +*/ + +#ifndef LIBDNF5_TRANSACTION_OFFLINE_HPP +#define LIBDNF5_TRANSACTION_OFFLINE_HPP + +#include +#include + +#include + +namespace libdnf5::offline { + +// Unique identifiers used to mark and identify system-upgrade boots in +// journald logs. These are the same as they are in `dnf4 system-upgrade`, so +// `dnf5 offline log` will find offline transactions performed by DNF 4 and +// vice-versa. +const std::string REBOOT_REQUESTED_ID{"9348174c5cc74001a71ef26bd79d302e"}; +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"}; +const std::string STATUS_READY{"ready"}; +const std::string STATUS_TRANSACTION_INCOMPLETE{"transaction-incomplete"}; + +const int STATE_VERSION = 1; +const std::string STATE_HEADER{"offline-transaction-state"}; + +const std::filesystem::path DEFAULT_DATADIR{std::filesystem::path(libdnf5::SYSTEM_STATE_DIR) / "offline"}; +const std::filesystem::path TRANSACTION_STATE_FILENAME{"offline-transaction-state.toml"}; + +struct OfflineTransactionStateData { + int state_version = STATE_VERSION; + std::string status = STATUS_DOWNLOAD_INCOMPLETE; + std::string cachedir; + std::string target_releasever; + std::string system_releasever; + std::string verb; + std::string cmd_line; + bool poweroff_after = false; + std::string module_platform_id; +}; + +class OfflineTransactionState { +public: + void write(); + OfflineTransactionState(std::filesystem::path path); + OfflineTransactionStateData & get_data(); + const std::exception_ptr & get_read_exception() const; + std::filesystem::path get_path() const; + +private: + void read(); + std::exception_ptr read_exception; + std::filesystem::path path; + OfflineTransactionStateData data; +}; + +} // namespace libdnf5::offline + +TOML11_DEFINE_CONVERSION_NON_INTRUSIVE( + libdnf5::offline::OfflineTransactionStateData, + state_version, + status, + cachedir, + target_releasever, + system_releasever, + verb, + cmd_line, + poweroff_after, + module_platform_id) + +#endif // LIBDNF5_TRANSACTION_OFFLINE_HPP diff --git a/libdnf5/transaction/offline.cpp b/libdnf5/transaction/offline.cpp new file mode 100644 index 0000000000..5b852eabed --- /dev/null +++ b/libdnf5/transaction/offline.cpp @@ -0,0 +1,69 @@ +/* +Copyright (C) 2024 Red Hat, Inc. + +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 Lesser 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with libdnf. If not, see . +*/ + +#include "libdnf5/common/exception.hpp" + +#include +#include +#include + + +namespace libdnf5::offline { + +OfflineTransactionState::OfflineTransactionState(std::filesystem::path path) : path(std::move(path)) { + read(); +} + +OfflineTransactionStateData & OfflineTransactionState::get_data() { + return data; +} + +const std::exception_ptr & OfflineTransactionState::get_read_exception() const { + return read_exception; +} + +std::filesystem::path OfflineTransactionState::get_path() const { + return path; +} + + +void OfflineTransactionState::read() { + try { + const std::ifstream file{path}; + if (!file.good()) { + throw libdnf5::FileSystemError(errno, path, M_("error reading offline state file")); + } + 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 libdnf5::offline