From de57714174f14961974d952f548c7b4aa2e51f7d Mon Sep 17 00:00:00 2001 From: Marek Blaha Date: Mon, 3 Jun 2024 10:29:20 +0200 Subject: [PATCH 1/5] dnfdaemon: Support for running a transaction offline The patch adds a new supported option "offline" to the do_transaction method of the Goal interface. If set to "true", the transaction is not executed, but only prepared to run during the next reboot. --- .../dbus/interfaces/org.rpm.dnf.v0.Goal.xml | 2 + dnf5daemon-server/services/goal/goal.cpp | 63 +++++------- dnf5daemon-server/session.cpp | 97 +++++++++++++++++++ dnf5daemon-server/session.hpp | 4 + 4 files changed, 128 insertions(+), 38 deletions(-) diff --git a/dnf5daemon-server/dbus/interfaces/org.rpm.dnf.v0.Goal.xml b/dnf5daemon-server/dbus/interfaces/org.rpm.dnf.v0.Goal.xml index 06228ad8f..56014b7bb 100644 --- a/dnf5daemon-server/dbus/interfaces/org.rpm.dnf.v0.Goal.xml +++ b/dnf5daemon-server/dbus/interfaces/org.rpm.dnf.v0.Goal.xml @@ -85,6 +85,8 @@ along with libdnf. If not, see . Following @options are supported: - comment: string Adds a comment to a transaction. + - offline: boolean, default false + If true, the transaction will be prepared to run during the next reboot. Otherwise, it will run immediately. Unknown options are ignored. --> diff --git a/dnf5daemon-server/services/goal/goal.cpp b/dnf5daemon-server/services/goal/goal.cpp index 6c1b11bb1..dc6927cc2 100644 --- a/dnf5daemon-server/services/goal/goal.cpp +++ b/dnf5daemon-server/services/goal/goal.cpp @@ -28,7 +28,7 @@ along with libdnf. If not, see . #include "utils.hpp" #include -#include +#include #include #include #include @@ -265,24 +265,6 @@ sdbus::MethodReply Goal::get_transaction_problems(sdbus::MethodCall & call) { } -// TODO (mblaha) callbacks to report the status -void download_packages(Session & session, libdnf5::base::Transaction & transaction) { - libdnf5::repo::PackageDownloader downloader(session.get_base()->get_weak_ptr()); - - // container is owner of package callbacks user_data - std::vector> user_data; - for (auto & tspkg : transaction.get_transaction_packages()) { - if (transaction_item_action_is_inbound(tspkg.get_action()) && - tspkg.get_package().get_repo()->get_type() != libdnf5::repo::Repo::Type::COMMANDLINE) { - auto & data = user_data.emplace_back(std::make_unique()); - data->download_id = "package:" + std::to_string(tspkg.get_package().get_id().id); - downloader.add(tspkg.get_package(), data.get()); - } - } - - downloader.download(); -} - sdbus::MethodReply Goal::do_transaction(sdbus::MethodCall & call) { transaction_resolved_assert(); if (!session.check_authorization(dnfdaemon::POLKIT_EXECUTE_RPM_TRANSACTION, call.getSender())) { @@ -293,30 +275,35 @@ sdbus::MethodReply Goal::do_transaction(sdbus::MethodCall & call) { dnfdaemon::KeyValueMap options; call >> options; - auto * transaction = session.get_transaction(); + bool offline = dnfdaemon::key_value_map_get(options, "offline", false); - download_packages(session, *transaction); + if (offline) { + session.store_transaction_offline(); + // TODO(mblaha): signalize reboot? + } else { + session.download_transaction_packages(); - std::string comment; - if (options.find("comment") != options.end()) { - comment = dnfdaemon::key_value_map_get(options, "comment"); - } + std::string comment; + if (options.find("comment") != options.end()) { + comment = dnfdaemon::key_value_map_get(options, "comment"); + } - transaction->set_callbacks(std::make_unique(session)); - transaction->set_description("dnf5daemon-server"); - transaction->set_comment(comment); - - auto rpm_result = transaction->run(); - if (rpm_result != libdnf5::base::Transaction::TransactionRunResult::SUCCESS) { - throw sdbus::Error( - dnfdaemon::ERROR_TRANSACTION, - fmt::format( - "rpm transaction failed with code {}.", - static_cast>(rpm_result))); + auto * transaction = session.get_transaction(); + transaction->set_callbacks(std::make_unique(session)); + transaction->set_description("dnf5daemon-server"); + transaction->set_comment(comment); + + auto rpm_result = transaction->run(); + if (rpm_result != libdnf5::base::Transaction::TransactionRunResult::SUCCESS) { + throw sdbus::Error( + dnfdaemon::ERROR_TRANSACTION, + fmt::format( + "rpm transaction failed with code {}.", + static_cast>(rpm_result))); + } + // TODO(mblaha): clean up downloaded packages after successful transaction } - // TODO(mblaha): clean up downloaded packages after successful transaction - auto reply = call.createReply(); return reply; } diff --git a/dnf5daemon-server/session.cpp b/dnf5daemon-server/session.cpp index 846ed0a43..ea0c93ff0 100644 --- a/dnf5daemon-server/session.cpp +++ b/dnf5daemon-server/session.cpp @@ -30,9 +30,13 @@ along with libdnf. If not, see . #include "utils.hpp" #include +#include +#include +#include #include #include +#include #include #include #include @@ -283,3 +287,96 @@ bool Session::check_authorization( return res_is_authorized; } + +void Session::download_transaction_packages() { + libdnf5::repo::PackageDownloader downloader(base->get_weak_ptr()); + + // container is owner of package callbacks user_data + std::vector> user_data; + for (auto & tspkg : transaction->get_transaction_packages()) { + if (transaction_item_action_is_inbound(tspkg.get_action()) && + tspkg.get_package().get_repo()->get_type() != libdnf5::repo::Repo::Type::COMMANDLINE) { + auto & data = user_data.emplace_back(std::make_unique()); + data->download_id = "package:" + std::to_string(tspkg.get_package().get_id().id); + downloader.add(tspkg.get_package(), data.get()); + } + } + + downloader.download(); +} + +void Session::store_transaction_offline() { + const auto & installroot = base->get_config().get_installroot_option().get_value(); + const auto & dest_dir = installroot / libdnf5::offline::DEFAULT_DATADIR.relative_path() / "packages"; + std::filesystem::create_directories(dest_dir); + base->get_config().get_destdir_option().set(dest_dir); + download_transaction_packages(); + + 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"}; + 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 / libdnf5::offline::TRANSACTION_STATE_FILENAME}; + libdnf5::offline::OfflineTransactionState state{state_path}; + + auto & state_data = state.get_data(); + + state_data.set_status(libdnf5::offline::STATUS_DOWNLOAD_INCOMPLETE); + state.write(); + + // First, serialize the transaction + transaction->store_comps(comps_location); + + const auto transaction_json_path = offline_datadir / "transaction.json"; + libdnf5::utils::fs::File transaction_json_file{transaction_json_path, "w"}; + transaction_json_file.write(transaction->serialize(packages_in_trans_dir, comps_in_trans_dir)); + transaction_json_file.close(); + + // Then, test the serialized transaction + // TODO(mblaha): store transaction test/run problems in the session and add an API + // to retrieve it + const auto & test_goal = std::make_unique(*base); + test_goal->add_serialized_transaction(transaction_json_path); + auto test_transaction = test_goal->resolve(); + if (test_transaction.get_problems() != libdnf5::GoalProblem::NO_PROBLEM) { + throw sdbus::Error(dnfdaemon::ERROR_TRANSACTION, "failed to resolve serialized offline transaction."); + } + base->get_config().get_tsflags_option().set(libdnf5::Option::Priority::RUNTIME, "test"); + + auto result = test_transaction.run(); + if (result != libdnf5::base::Transaction::TransactionRunResult::SUCCESS) { + throw sdbus::Error( + dnfdaemon::ERROR_TRANSACTION, + fmt::format( + "offline rpm transaction test failed with code {}.", + static_cast>(result))); + } + + // Download and transaction test complete. Fill out entries in offline + // transaction state file. + state_data.set_cachedir(base->get_config().get_cachedir_option().get_value()); + + state_data.set_cmd_line("dnf5daemon-server"); + + const auto & detected_releasever = libdnf5::Vars::detect_release(base->get_weak_ptr(), installroot); + if (detected_releasever != nullptr) { + state_data.set_system_releasever(*detected_releasever); + } + state_data.set_target_releasever(base->get_vars()->get_value("releasever")); + + const auto module_platform_id = base->get_config().get_module_platform_id_option(); + if (!module_platform_id.empty()) { + state_data.set_module_platform_id(module_platform_id.get_value()); + } + + // create the magic symlink /system-update -> datadir + if (!std::filesystem::is_symlink(libdnf5::offline::MAGIC_SYMLINK)) { + std::filesystem::create_symlink(offline_datadir, libdnf5::offline::MAGIC_SYMLINK); + } + state_data.set_status(libdnf5::offline::STATUS_READY); + + state.write(); +} diff --git a/dnf5daemon-server/session.hpp b/dnf5daemon-server/session.hpp index 9150aa64f..c0672877c 100644 --- a/dnf5daemon-server/session.hpp +++ b/dnf5daemon-server/session.hpp @@ -88,6 +88,10 @@ class Session { std::optional session_locale; void confirm_key(const std::string & key_id, const bool confirmed); bool wait_for_key_confirmation(const std::string & key_id, sdbus::Signal & signal); + /// download packages for the current transaction + void download_transaction_packages(); + /// prepare the current transaction to run during the next reboot + void store_transaction_offline(); private: sdbus::IConnection & connection; From d21f55ed51f249ef0f74e10d15e3534a055ecd32 Mon Sep 17 00:00:00 2001 From: Marek Blaha Date: Fri, 31 May 2024 11:57:46 +0200 Subject: [PATCH 2/5] dnfdaemon-client: --offline option for transactions --- dnf5daemon-client/commands/command.cpp | 3 ++- dnf5daemon-client/commands/command.hpp | 2 +- .../commands/distro-sync/distro-sync.cpp | 5 ++++- .../commands/distro-sync/distro-sync.hpp | 3 ++- .../commands/downgrade/downgrade.cpp | 5 ++++- .../commands/downgrade/downgrade.hpp | 3 ++- .../commands/install/install.cpp | 5 ++++- .../commands/install/install.hpp | 4 ++-- .../commands/reinstall/reinstall.cpp | 5 ++++- .../commands/reinstall/reinstall.hpp | 3 ++- dnf5daemon-client/commands/remove/remove.cpp | 5 ++++- dnf5daemon-client/commands/remove/remove.hpp | 3 ++- dnf5daemon-client/commands/shared_options.cpp | 20 +++++++++++++++++++ dnf5daemon-client/commands/shared_options.hpp | 7 +++++++ .../commands/upgrade/upgrade.cpp | 6 ++++-- .../commands/upgrade/upgrade.hpp | 3 ++- 16 files changed, 66 insertions(+), 16 deletions(-) diff --git a/dnf5daemon-client/commands/command.cpp b/dnf5daemon-client/commands/command.cpp index 75acb8bd8..67a4e8ea2 100644 --- a/dnf5daemon-client/commands/command.cpp +++ b/dnf5daemon-client/commands/command.cpp @@ -37,7 +37,7 @@ along with libdnf. If not, see . namespace dnfdaemon::client { -void TransactionCommand::run_transaction() { +void TransactionCommand::run_transaction(bool offline) { auto & ctx = get_context(); dnfdaemon::KeyValueMap options = {}; @@ -80,6 +80,7 @@ void TransactionCommand::run_transaction() { // do the transaction options.clear(); + options["offline"] = offline; ctx.session_proxy->callMethod("do_transaction") .onInterface(dnfdaemon::INTERFACE_GOAL) .withTimeout(static_cast(-1)) diff --git a/dnf5daemon-client/commands/command.hpp b/dnf5daemon-client/commands/command.hpp index 1a32e21a8..2187d80bb 100644 --- a/dnf5daemon-client/commands/command.hpp +++ b/dnf5daemon-client/commands/command.hpp @@ -42,7 +42,7 @@ class DaemonCommand : public libdnf5::cli::session::Command { class TransactionCommand : public DaemonCommand { public: explicit TransactionCommand(Context & context, const std::string & name) : DaemonCommand(context, name){}; - void run_transaction(); + void run_transaction(bool offline = false); }; diff --git a/dnf5daemon-client/commands/distro-sync/distro-sync.cpp b/dnf5daemon-client/commands/distro-sync/distro-sync.cpp index 4b026425a..8b1d0741c 100644 --- a/dnf5daemon-client/commands/distro-sync/distro-sync.cpp +++ b/dnf5daemon-client/commands/distro-sync/distro-sync.cpp @@ -49,6 +49,9 @@ void DistroSyncCommand::set_argument_parser() { auto specs_arg = pkg_specs_argument(parser, libdnf5::cli::ArgumentParser::PositionalArg::UNLIMITED, pkg_specs); specs_arg->set_description("Patterns"); cmd.register_positional_arg(specs_arg); + + auto offline_arg = create_offline_option(parser, &offline_option); + cmd.register_named_arg(offline_arg); } void DistroSyncCommand::run() { @@ -65,7 +68,7 @@ void DistroSyncCommand::run() { .withTimeout(static_cast(-1)) .withArguments(pkg_specs, options); - run_transaction(); + run_transaction(offline_option.get_value()); } } // namespace dnfdaemon::client diff --git a/dnf5daemon-client/commands/distro-sync/distro-sync.hpp b/dnf5daemon-client/commands/distro-sync/distro-sync.hpp index 1ab69aae8..646d9a516 100644 --- a/dnf5daemon-client/commands/distro-sync/distro-sync.hpp +++ b/dnf5daemon-client/commands/distro-sync/distro-sync.hpp @@ -22,7 +22,7 @@ along with libdnf. If not, see . #include "commands/command.hpp" -#include +#include namespace dnfdaemon::client { @@ -35,6 +35,7 @@ class DistroSyncCommand : public TransactionCommand { private: std::vector pkg_specs; + libdnf5::OptionBool offline_option{false}; }; } // namespace dnfdaemon::client diff --git a/dnf5daemon-client/commands/downgrade/downgrade.cpp b/dnf5daemon-client/commands/downgrade/downgrade.cpp index e56047e49..0bcfaff13 100644 --- a/dnf5daemon-client/commands/downgrade/downgrade.cpp +++ b/dnf5daemon-client/commands/downgrade/downgrade.cpp @@ -49,6 +49,9 @@ void DowngradeCommand::set_argument_parser() { auto specs_arg = pkg_specs_argument(parser, libdnf5::cli::ArgumentParser::PositionalArg::AT_LEAST_ONE, pkg_specs); specs_arg->set_description("List of packages to downgrade"); cmd.register_positional_arg(specs_arg); + + auto offline_arg = create_offline_option(parser, &offline_option); + cmd.register_named_arg(offline_arg); } void DowngradeCommand::run() { @@ -65,7 +68,7 @@ void DowngradeCommand::run() { .withTimeout(static_cast(-1)) .withArguments(pkg_specs, options); - run_transaction(); + run_transaction(offline_option.get_value()); } } // namespace dnfdaemon::client diff --git a/dnf5daemon-client/commands/downgrade/downgrade.hpp b/dnf5daemon-client/commands/downgrade/downgrade.hpp index 21435edbb..e11de3eef 100644 --- a/dnf5daemon-client/commands/downgrade/downgrade.hpp +++ b/dnf5daemon-client/commands/downgrade/downgrade.hpp @@ -22,7 +22,7 @@ along with libdnf. If not, see . #include "commands/command.hpp" -#include +#include namespace dnfdaemon::client { @@ -35,6 +35,7 @@ class DowngradeCommand : public TransactionCommand { private: std::vector pkg_specs{}; + libdnf5::OptionBool offline_option{false}; }; } // namespace dnfdaemon::client diff --git a/dnf5daemon-client/commands/install/install.cpp b/dnf5daemon-client/commands/install/install.cpp index 9d2338905..4dba94f50 100644 --- a/dnf5daemon-client/commands/install/install.cpp +++ b/dnf5daemon-client/commands/install/install.cpp @@ -62,6 +62,9 @@ void InstallCommand::set_argument_parser() { skip_unavailable->link_value(&skip_unavailable_option); cmd.register_named_arg(skip_unavailable); + auto offline_arg = create_offline_option(parser, &offline_option); + cmd.register_named_arg(offline_arg); + auto specs_arg = pkg_specs_argument(parser, libdnf5::cli::ArgumentParser::PositionalArg::AT_LEAST_ONE, pkg_specs); specs_arg->set_description("List of packages to install"); cmd.register_positional_arg(specs_arg); @@ -88,7 +91,7 @@ void InstallCommand::run() { .withTimeout(static_cast(-1)) .withArguments(pkg_specs, options); - run_transaction(); + run_transaction(offline_option.get_value()); } } // namespace dnfdaemon::client diff --git a/dnf5daemon-client/commands/install/install.hpp b/dnf5daemon-client/commands/install/install.hpp index 18569bfc9..d567edb81 100644 --- a/dnf5daemon-client/commands/install/install.hpp +++ b/dnf5daemon-client/commands/install/install.hpp @@ -22,7 +22,6 @@ along with libdnf. If not, see . #include "commands/command.hpp" -#include #include namespace dnfdaemon::client { @@ -35,9 +34,10 @@ class InstallCommand : public TransactionCommand { void run() override; private: + std::vector pkg_specs{}; libdnf5::OptionBool skip_broken_option{false}; libdnf5::OptionBool skip_unavailable_option{false}; - std::vector pkg_specs{}; + libdnf5::OptionBool offline_option{false}; }; } // namespace dnfdaemon::client diff --git a/dnf5daemon-client/commands/reinstall/reinstall.cpp b/dnf5daemon-client/commands/reinstall/reinstall.cpp index e5fbf9d11..91624021c 100644 --- a/dnf5daemon-client/commands/reinstall/reinstall.cpp +++ b/dnf5daemon-client/commands/reinstall/reinstall.cpp @@ -49,6 +49,9 @@ void ReinstallCommand::set_argument_parser() { auto specs_arg = pkg_specs_argument(parser, libdnf5::cli::ArgumentParser::PositionalArg::AT_LEAST_ONE, pkg_specs); specs_arg->set_description("List of packages to reinstall"); cmd.register_positional_arg(specs_arg); + + auto offline_arg = create_offline_option(parser, &offline_option); + cmd.register_named_arg(offline_arg); } void ReinstallCommand::run() { @@ -65,7 +68,7 @@ void ReinstallCommand::run() { .withTimeout(static_cast(-1)) .withArguments(pkg_specs, options); - run_transaction(); + run_transaction(offline_option.get_value()); } } // namespace dnfdaemon::client diff --git a/dnf5daemon-client/commands/reinstall/reinstall.hpp b/dnf5daemon-client/commands/reinstall/reinstall.hpp index 395b9644d..689b957f0 100644 --- a/dnf5daemon-client/commands/reinstall/reinstall.hpp +++ b/dnf5daemon-client/commands/reinstall/reinstall.hpp @@ -22,7 +22,7 @@ along with libdnf. If not, see . #include "commands/command.hpp" -#include +#include namespace dnfdaemon::client { @@ -35,6 +35,7 @@ class ReinstallCommand : public TransactionCommand { private: std::vector pkg_specs{}; + libdnf5::OptionBool offline_option{false}; }; } // namespace dnfdaemon::client diff --git a/dnf5daemon-client/commands/remove/remove.cpp b/dnf5daemon-client/commands/remove/remove.cpp index 41c3826bb..6fcfa45db 100644 --- a/dnf5daemon-client/commands/remove/remove.cpp +++ b/dnf5daemon-client/commands/remove/remove.cpp @@ -51,6 +51,9 @@ void RemoveCommand::set_argument_parser() { specs_arg->set_description("List of packages to remove"); cmd.register_positional_arg(specs_arg); + auto offline_arg = create_offline_option(parser, &offline_option); + cmd.register_named_arg(offline_arg); + // run remove command always with allow_erasing on context.allow_erasing.set(libdnf5::Option::Priority::RUNTIME, true); } @@ -69,7 +72,7 @@ void RemoveCommand::run() { .withTimeout(static_cast(-1)) .withArguments(pkg_specs, options); - run_transaction(); + run_transaction(offline_option.get_value()); } } // namespace dnfdaemon::client diff --git a/dnf5daemon-client/commands/remove/remove.hpp b/dnf5daemon-client/commands/remove/remove.hpp index 77d52bf87..95229d440 100644 --- a/dnf5daemon-client/commands/remove/remove.hpp +++ b/dnf5daemon-client/commands/remove/remove.hpp @@ -22,7 +22,7 @@ along with libdnf. If not, see . #include "commands/command.hpp" -#include +#include namespace dnfdaemon::client { @@ -35,6 +35,7 @@ class RemoveCommand : public TransactionCommand { private: std::vector pkg_specs{}; + libdnf5::OptionBool offline_option{false}; }; } // namespace dnfdaemon::client diff --git a/dnf5daemon-client/commands/shared_options.cpp b/dnf5daemon-client/commands/shared_options.cpp index d40923523..409babe80 100644 --- a/dnf5daemon-client/commands/shared_options.cpp +++ b/dnf5daemon-client/commands/shared_options.cpp @@ -21,8 +21,12 @@ along with libdnf. If not, see . #include "utils/url.hpp" +#include + #include +namespace { + static void normalize_paths(int specs_count, const char * const specs[], std::vector & pkg_specs) { const std::string_view ext(".rpm"); std::set unique_items; @@ -39,6 +43,10 @@ static void normalize_paths(int specs_count, const char * const specs[], std::ve } } +} // anonymous namespace + +namespace dnfdaemon::client { + libdnf5::cli::ArgumentParser::PositionalArg * pkg_specs_argument( libdnf5::cli::ArgumentParser & parser, int nargs, std::vector & pkg_specs) { auto specs = parser.add_new_positional_arg("pkg_specs", nargs, nullptr, nullptr); @@ -49,3 +57,15 @@ libdnf5::cli::ArgumentParser::PositionalArg * pkg_specs_argument( }); return specs; } + +libdnf5::cli::ArgumentParser::NamedArg * create_offline_option( + libdnf5::cli::ArgumentParser & parser, libdnf5::Option * value) { + auto offline = parser.add_new_named_arg("offline"); + offline->set_long_name("offline"); + offline->set_description("Store the transaction to be performed offline"); + offline->set_const_value("true"); + offline->link_value(value); + return offline; +} + +} // namespace dnfdaemon::client diff --git a/dnf5daemon-client/commands/shared_options.hpp b/dnf5daemon-client/commands/shared_options.hpp index 29e932156..8712cb73d 100644 --- a/dnf5daemon-client/commands/shared_options.hpp +++ b/dnf5daemon-client/commands/shared_options.hpp @@ -22,7 +22,14 @@ along with libdnf. If not, see . #include +namespace dnfdaemon::client { + libdnf5::cli::ArgumentParser::PositionalArg * pkg_specs_argument( libdnf5::cli::ArgumentParser & parser, int nargs, std::vector & pkg_specs); +/// Create the `--offline` named arg bound to the given option +libdnf5::cli::ArgumentParser::NamedArg * create_offline_option( + libdnf5::cli::ArgumentParser & parser, libdnf5::Option * value); + +} // namespace dnfdaemon::client #endif diff --git a/dnf5daemon-client/commands/upgrade/upgrade.cpp b/dnf5daemon-client/commands/upgrade/upgrade.cpp index 047d6a7ea..884ca8658 100644 --- a/dnf5daemon-client/commands/upgrade/upgrade.cpp +++ b/dnf5daemon-client/commands/upgrade/upgrade.cpp @@ -49,6 +49,9 @@ void UpgradeCommand::set_argument_parser() { auto specs_arg = pkg_specs_argument(parser, libdnf5::cli::ArgumentParser::PositionalArg::UNLIMITED, pkg_specs); specs_arg->set_description("List of packages to upgrade"); cmd.register_positional_arg(specs_arg); + + auto offline_arg = create_offline_option(parser, &offline_option); + cmd.register_named_arg(offline_arg); } void UpgradeCommand::run() { @@ -59,13 +62,12 @@ void UpgradeCommand::run() { } dnfdaemon::KeyValueMap options = {}; - ctx.session_proxy->callMethod("upgrade") .onInterface(dnfdaemon::INTERFACE_RPM) .withTimeout(static_cast(-1)) .withArguments(pkg_specs, options); - run_transaction(); + run_transaction(offline_option.get_value()); } } // namespace dnfdaemon::client diff --git a/dnf5daemon-client/commands/upgrade/upgrade.hpp b/dnf5daemon-client/commands/upgrade/upgrade.hpp index 05f81557c..0a26070d5 100644 --- a/dnf5daemon-client/commands/upgrade/upgrade.hpp +++ b/dnf5daemon-client/commands/upgrade/upgrade.hpp @@ -22,7 +22,7 @@ along with libdnf. If not, see . #include "commands/command.hpp" -#include +#include namespace dnfdaemon::client { @@ -35,6 +35,7 @@ class UpgradeCommand : public TransactionCommand { private: std::vector pkg_specs{}; + libdnf5::OptionBool offline_option{false}; }; } // namespace dnfdaemon::client From e7db9ec1a4d7b8cd00a2922edbd224628be294bf Mon Sep 17 00:00:00 2001 From: Marek Blaha Date: Wed, 12 Jun 2024 18:45:15 +0200 Subject: [PATCH 3/5] dnfdaemon: Add new API for offline transactions Adds new `Offline` interface for interacting with offline transactions. The interface provides three methods: - check_pending() - check whether there is an offline update scheduled for the next reboot - cancel() - cancel the scheduled offline update - clean() - cancel the scheduled offline update and remove all stored offline transaction data. - set_finish_action() - to set whether after applying the offline transaction the system should be rebooted (default) or powered off. --- dnf5/commands/offline/offline.cpp | 3 +- dnf5daemon-server/dbus.hpp | 1 + .../interfaces/org.rpm.dnf.v0.Offline.xml | 80 ++++++++ dnf5daemon-server/services/goal/goal.cpp | 1 - .../services/offline/offline.cpp | 190 ++++++++++++++++++ .../services/offline/offline.hpp | 47 +++++ dnf5daemon-server/session.cpp | 2 + 7 files changed, 322 insertions(+), 2 deletions(-) create mode 100644 dnf5daemon-server/dbus/interfaces/org.rpm.dnf.v0.Offline.xml create mode 100644 dnf5daemon-server/services/offline/offline.cpp create mode 100644 dnf5daemon-server/services/offline/offline.hpp diff --git a/dnf5/commands/offline/offline.cpp b/dnf5/commands/offline/offline.cpp index 52f47e8cc..b38d720c2 100644 --- a/dnf5/commands/offline/offline.cpp +++ b/dnf5/commands/offline/offline.cpp @@ -206,7 +206,8 @@ void OfflineSubcommand::configure() { const std::filesystem::path installroot = ctx.get_base().get_config().get_installroot_option().get_value(); 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 / libdnf5::offline::TRANSACTION_STATE_FILENAME); } void check_state(const libdnf5::offline::OfflineTransactionState & state) { diff --git a/dnf5daemon-server/dbus.hpp b/dnf5daemon-server/dbus.hpp index 3c8981423..61482b90e 100644 --- a/dnf5daemon-server/dbus.hpp +++ b/dnf5daemon-server/dbus.hpp @@ -61,6 +61,7 @@ const char * const INTERFACE_RPM = "org.rpm.dnf.v0.rpm.Rpm"; const char * const INTERFACE_GOAL = "org.rpm.dnf.v0.Goal"; const char * const INTERFACE_GROUP = "org.rpm.dnf.v0.comps.Group"; const char * const INTERFACE_ADVISORY = "org.rpm.dnf.v0.Advisory"; +const char * const INTERFACE_OFFLINE = "org.rpm.dnf.v0.Offline"; const char * const INTERFACE_SESSION_MANAGER = "org.rpm.dnf.v0.SessionManager"; // signals diff --git a/dnf5daemon-server/dbus/interfaces/org.rpm.dnf.v0.Offline.xml b/dnf5daemon-server/dbus/interfaces/org.rpm.dnf.v0.Offline.xml new file mode 100644 index 000000000..6e060aa48 --- /dev/null +++ b/dnf5daemon-server/dbus/interfaces/org.rpm.dnf.v0.Offline.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dnf5daemon-server/services/goal/goal.cpp b/dnf5daemon-server/services/goal/goal.cpp index dc6927cc2..58797100c 100644 --- a/dnf5daemon-server/services/goal/goal.cpp +++ b/dnf5daemon-server/services/goal/goal.cpp @@ -28,7 +28,6 @@ along with libdnf. If not, see . #include "utils.hpp" #include -#include #include #include #include diff --git a/dnf5daemon-server/services/offline/offline.cpp b/dnf5daemon-server/services/offline/offline.cpp new file mode 100644 index 000000000..2735a92cf --- /dev/null +++ b/dnf5daemon-server/services/offline/offline.cpp @@ -0,0 +1,190 @@ +/* +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 "offline.hpp" + +#include "dbus.hpp" +#include "utils/string.hpp" + +#include +#include + +#include +#include + +const char * const ERR_ANOTHER_TOOL = "Offline transaction was initiated by another tool."; + +std::filesystem::path Offline::get_datadir() { + auto base = session.get_base(); + const auto & installroot = base->get_config().get_installroot_option().get_value(); + return installroot / libdnf5::offline::DEFAULT_DATADIR.relative_path(); +} + +Offline::Scheduled Offline::offline_transaction_scheduled() { + std::error_code ec; + // magic symlink exists + if (std::filesystem::exists(libdnf5::offline::MAGIC_SYMLINK, ec)) { + // and points to dnf5 location + if (std::filesystem::equivalent(libdnf5::offline::MAGIC_SYMLINK, get_datadir())) { + return Scheduled::SCHEDULED; + } else { + return Scheduled::ANOTHER_TOOL; + } + } + return Scheduled::NOT_SCHEDULED; +} + +void Offline::dbus_register() { + auto dbus_object = session.get_dbus_object(); + dbus_object->registerMethod( + dnfdaemon::INTERFACE_OFFLINE, + "cancel", + {}, + {}, + "bs", + {"success", "error_msg"}, + [this](sdbus::MethodCall call) -> void { + session.get_threads_manager().handle_method(*this, &Offline::cancel, call, session.session_locale); + }); + dbus_object->registerMethod( + dnfdaemon::INTERFACE_OFFLINE, + "check_pending", + {}, + {}, + "b", + {"pending"}, + [this](sdbus::MethodCall call) -> void { + session.get_threads_manager().handle_method(*this, &Offline::check_pending, call, session.session_locale); + }); + dbus_object->registerMethod( + dnfdaemon::INTERFACE_OFFLINE, + "clean", + {}, + {}, + "bs", + {"success", "error_msg"}, + [this](sdbus::MethodCall call) -> void { + session.get_threads_manager().handle_method(*this, &Offline::clean, call, session.session_locale); + }); + dbus_object->registerMethod( + dnfdaemon::INTERFACE_OFFLINE, + "set_finish_action", + "s", + {"action"}, + "bs", + {"success", "error_msg"}, + [this](sdbus::MethodCall call) -> void { + session.get_threads_manager().handle_method( + *this, &Offline::set_finish_action, call, session.session_locale); + }); +} + +sdbus::MethodReply Offline::check_pending(sdbus::MethodCall & call) { + auto reply = call.createReply(); + reply << (offline_transaction_scheduled() == Scheduled::SCHEDULED); + return reply; +} + +sdbus::MethodReply Offline::cancel(sdbus::MethodCall & call) { + if (!session.check_authorization(dnfdaemon::POLKIT_EXECUTE_RPM_TRANSACTION, call.getSender())) { + throw std::runtime_error("Not authorized"); + } + bool success = true; + std::string error_msg; + switch (offline_transaction_scheduled()) { + case Scheduled::SCHEDULED: { + std::error_code ec; + if (!std::filesystem::remove(libdnf5::offline::MAGIC_SYMLINK, ec) && ec) { + success = false; + error_msg = ec.message(); + } + } break; + case Scheduled::ANOTHER_TOOL: + success = false; + error_msg = ERR_ANOTHER_TOOL; + break; + case Scheduled::NOT_SCHEDULED: + break; + } + auto reply = call.createReply(); + reply << success; + reply << error_msg; + return reply; +} + +sdbus::MethodReply Offline::clean(sdbus::MethodCall & call) { + if (!session.check_authorization(dnfdaemon::POLKIT_EXECUTE_RPM_TRANSACTION, call.getSender())) { + throw std::runtime_error("Not authorized"); + } + std::vector error_msgs; + bool success = true; + if (offline_transaction_scheduled() == Scheduled::SCHEDULED) { + // remove the magic symlink if it was created by dnf5 + std::error_code ec; + if (!std::filesystem::remove(libdnf5::offline::MAGIC_SYMLINK, ec) && ec) { + success = false; + error_msgs.push_back(ec.message()); + } + } + // clean dnf5 offline transaction files + for (const auto & entry : std::filesystem::directory_iterator(get_datadir())) { + std::error_code ec; + std::filesystem::remove_all(entry.path(), ec); + if (ec) { + success = false; + error_msgs.push_back(ec.message()); + } + } + auto reply = call.createReply(); + reply << success; + reply << libdnf5::utils::string::join(error_msgs, ", "); + return reply; +} + +sdbus::MethodReply Offline::set_finish_action(sdbus::MethodCall & call) { + if (!session.check_authorization(dnfdaemon::POLKIT_EXECUTE_RPM_TRANSACTION, call.getSender())) { + throw std::runtime_error("Not authorized"); + } + bool success{false}; + std::string error_msg{}; + // try load the offline transaction state + const std::filesystem::path state_path{ + libdnf5::offline::MAGIC_SYMLINK / libdnf5::offline::TRANSACTION_STATE_FILENAME}; + libdnf5::offline::OfflineTransactionState state{state_path}; + const auto & read_exception = state.get_read_exception(); + if (read_exception == nullptr) { + // set the poweroff_after item accordingly + std::string finish_action; + call >> finish_action; + state.get_data().set_poweroff_after(finish_action == "poweroff"); + // write the new state + state.write(); + success = true; + } else { + try { + std::rethrow_exception(read_exception); + } catch (const std::exception & ex) { + error_msg = ex.what(); + } + } + auto reply = call.createReply(); + reply << success; + reply << error_msg; + return reply; +} diff --git a/dnf5daemon-server/services/offline/offline.hpp b/dnf5daemon-server/services/offline/offline.hpp new file mode 100644 index 000000000..e9f444932 --- /dev/null +++ b/dnf5daemon-server/services/offline/offline.hpp @@ -0,0 +1,47 @@ +/* +Copyright Contributors to the libdnf project. + +This file is part of libdnf: https://github.com/rpm-software-management/libdnf/ + +Libdnf is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +Libdnf is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with libdnf. If not, see . +*/ + +#ifndef DNF5DAEMON_SERVER_SERVICES_OFFLINE_OFFLINE_HPP +#define DNF5DAEMON_SERVER_SERVICES_OFFLINE_OFFLINE_HPP + +#include "session.hpp" + +#include + +#include + +class Offline : public IDbusSessionService { +public: + using IDbusSessionService::IDbusSessionService; + ~Offline() = default; + void dbus_register(); + void dbus_deregister(); + +private: + sdbus::MethodReply cancel(sdbus::MethodCall & call); + sdbus::MethodReply check_pending(sdbus::MethodCall & call); + sdbus::MethodReply clean(sdbus::MethodCall & call); + sdbus::MethodReply set_finish_action(sdbus::MethodCall & call); + + enum class Scheduled { NOT_SCHEDULED, ANOTHER_TOOL, SCHEDULED }; + Scheduled offline_transaction_scheduled(); + std::filesystem::path get_datadir(); +}; + +#endif diff --git a/dnf5daemon-server/session.cpp b/dnf5daemon-server/session.cpp index ea0c93ff0..1869dd492 100644 --- a/dnf5daemon-server/session.cpp +++ b/dnf5daemon-server/session.cpp @@ -25,6 +25,7 @@ along with libdnf. If not, see . #include "services/base/base.hpp" #include "services/comps/group.hpp" #include "services/goal/goal.hpp" +#include "services/offline/offline.hpp" #include "services/repo/repo.hpp" #include "services/rpm/rpm.hpp" #include "utils.hpp" @@ -135,6 +136,7 @@ Session::Session( services.emplace_back(std::make_unique(*this)); services.emplace_back(std::make_unique(*this)); services.emplace_back(std::make_unique(*this)); + services.emplace_back(std::make_unique(*this)); services.emplace_back(std::make_unique(*this)); services.emplace_back(std::make_unique(*this)); From 29e5323c20036a62ac4676b18d6d5c3ab2911cde Mon Sep 17 00:00:00 2001 From: Marek Blaha Date: Mon, 8 Jul 2024 15:54:56 +0200 Subject: [PATCH 4/5] dnfdaemon: Strict set_finish_action() value check Check that only one of "poweroff", or "reboot" value was passed to set_finish_action() call. --- .../interfaces/org.rpm.dnf.v0.Offline.xml | 2 +- .../services/offline/offline.cpp | 46 ++++++++++++------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/dnf5daemon-server/dbus/interfaces/org.rpm.dnf.v0.Offline.xml b/dnf5daemon-server/dbus/interfaces/org.rpm.dnf.v0.Offline.xml index 6e060aa48..d6986e866 100644 --- a/dnf5daemon-server/dbus/interfaces/org.rpm.dnf.v0.Offline.xml +++ b/dnf5daemon-server/dbus/interfaces/org.rpm.dnf.v0.Offline.xml @@ -62,7 +62,7 @@ along with libdnf. If not, see . - + + diff --git a/dnf5daemon-server/services/offline/offline.cpp b/dnf5daemon-server/services/offline/offline.cpp index b3848ac91..27e6d7931 100644 --- a/dnf5daemon-server/services/offline/offline.cpp +++ b/dnf5daemon-server/services/offline/offline.cpp @@ -64,13 +64,13 @@ void Offline::dbus_register() { }); dbus_object->registerMethod( dnfdaemon::INTERFACE_OFFLINE, - "check_pending", + "get_status", {}, {}, - "b", - {"pending"}, + "ba{sv}", + {"is_pending", "transaction_status"}, [this](sdbus::MethodCall call) -> void { - session.get_threads_manager().handle_method(*this, &Offline::check_pending, call, session.session_locale); + session.get_threads_manager().handle_method(*this, &Offline::get_status, call, session.session_locale); }); dbus_object->registerMethod( dnfdaemon::INTERFACE_OFFLINE, @@ -95,9 +95,27 @@ void Offline::dbus_register() { }); } -sdbus::MethodReply Offline::check_pending(sdbus::MethodCall & call) { +sdbus::MethodReply Offline::get_status(sdbus::MethodCall & call) { + dnfdaemon::KeyValueMap transaction_state; + + const std::filesystem::path state_path{get_datadir() / libdnf5::offline::TRANSACTION_STATE_FILENAME}; + // try load the offline transaction state + libdnf5::offline::OfflineTransactionState state{state_path}; + if (!state.get_read_exception()) { + const auto & state_data = state.get_data(); + transaction_state["status"] = state_data.get_status(); + transaction_state["cachedir"] = state_data.get_cachedir(); + transaction_state["target_releasever"] = state_data.get_target_releasever(); + transaction_state["system_releasever"] = state_data.get_system_releasever(); + transaction_state["verb"] = state_data.get_verb(); + transaction_state["cmd_line"] = state_data.get_cmd_line(); + transaction_state["poweroff_after"] = state_data.get_poweroff_after(); + transaction_state["module_platform_id"] = state_data.get_module_platform_id(); + } + auto reply = call.createReply(); reply << (offline_transaction_scheduled() == Scheduled::SCHEDULED); + reply << transaction_state; return reply; } diff --git a/dnf5daemon-server/services/offline/offline.hpp b/dnf5daemon-server/services/offline/offline.hpp index e9f444932..d25d081f7 100644 --- a/dnf5daemon-server/services/offline/offline.hpp +++ b/dnf5daemon-server/services/offline/offline.hpp @@ -35,8 +35,8 @@ class Offline : public IDbusSessionService { private: sdbus::MethodReply cancel(sdbus::MethodCall & call); - sdbus::MethodReply check_pending(sdbus::MethodCall & call); sdbus::MethodReply clean(sdbus::MethodCall & call); + sdbus::MethodReply get_status(sdbus::MethodCall & call); sdbus::MethodReply set_finish_action(sdbus::MethodCall & call); enum class Scheduled { NOT_SCHEDULED, ANOTHER_TOOL, SCHEDULED };