From 2cd537d0d98428c564d29fff1017515bca37432b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= Date: Tue, 21 May 2024 13:28:02 +0200 Subject: [PATCH] Use `ignore_installed` option in transaction replay It adds checks for expected packages in transaction replay producing either a warning or an error if something is missing. This matches the dnf4 implementation. --- libdnf5/base/goal.cpp | 155 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 154 insertions(+), 1 deletion(-) diff --git a/libdnf5/base/goal.cpp b/libdnf5/base/goal.cpp index 20f62ab78..c3acd8cc8 100644 --- a/libdnf5/base/goal.cpp +++ b/libdnf5/base/goal.cpp @@ -653,6 +653,7 @@ GoalProblem Goal::Impl::add_replay_to_goal( const transaction::TransactionReplay & replay, GoalJobSettings settings, std::filesystem::path replay_location) { + auto ret = GoalProblem::NO_PROBLEM; bool skip_unavailable = settings.resolve_skip_unavailable(base->get_config()); std::unordered_set rpm_nevra_cache; @@ -661,6 +662,17 @@ GoalProblem Goal::Impl::add_replay_to_goal( rpm_nevra_cache.insert(package_replay.nevra); libdnf5::GoalJobSettings settings_per_package = settings; settings_per_package.set_clean_requirements_on_remove(libdnf5::GoalSetting::SET_FALSE); + const auto nevras = rpm::Nevra::parse(package_replay.nevra, {rpm::Nevra::Form::NEVRA}); + libdnf_assert( + nevras.size() > 0, "Cannot parse rpm nevra \"{}\" while replaying transaction.", package_replay.nevra); + + rpm::PackageQuery query_installed_na(base); + query_installed_na.filter_name(nevras[0].get_name()); + query_installed_na.filter_arch(nevras[0].get_arch()); + query_installed_na.filter_installed(); + auto query_installed = query_installed_na; + query_installed.filter_nevra(package_replay.nevra); + if (!package_replay.repo_id.empty()) { repo::RepoQuery enabled_repos(base); enabled_repos.filter_enabled(true); @@ -679,6 +691,54 @@ GoalProblem Goal::Impl::add_replay_to_goal( if (package_replay.action == transaction::TransactionItemAction::UPGRADE || package_replay.action == transaction::TransactionItemAction::INSTALL || package_replay.action == transaction::TransactionItemAction::DOWNGRADE) { + // In order to properly report an error when another version of a package with action INSTALL is already + // installed we have to verify several conditions. + // - There is another versions installed for this package (name-arch). + // - The package isn't installonly. + // - The transaction doesn't contain an outbound action for this name-arch. + // This could happend during transaction reverting because upgrade/downgrade/reinstall (and obsoleting) actions are reverted as a REMOVE. + // For example upgrade transaction: [a-2 Upgrade, a-1 Replaced] is reverted to [a-2 Remove, a-1 Install]. + // It is because we don't store the "replaces" relationship in history DB (there is a table `item_replaced_by`, but it is not populated + // and it doesn't seem worth it to populate it because of this use case) so we don't know which action to pick. We could try to guess + // based on the transaction packages but this approach seems easier. + if (package_replay.action == transaction::TransactionItemAction::INSTALL) { + bool has_counterpart_action = false; + for (const auto & installed_na : query_installed_na) { + has_counterpart_action |= + std::find_if(replay.packages.begin(), replay.packages.end(), [&installed_na](const auto & r) { + return r.nevra == installed_na.get_nevra() && transaction_item_action_is_outbound(r.action); + }) != replay.packages.end(); + if (has_counterpart_action) { + break; + } + } + if (!has_counterpart_action) { + auto is_installonly = query_installed_na; + is_installonly.filter_installonly(); + + // the package is not installonly + if (!query_installed_na.empty() && is_installonly.empty()) { + auto problem = query_installed.empty() ? GoalProblem::INSTALLED_IN_DIFFERENT_VERSION + : GoalProblem::ALREADY_INSTALLED; + auto log_level = libdnf5::Logger::Level::WARNING; + if (!settings.get_ignore_installed()) { + log_level = libdnf5::Logger::Level::ERROR; + ret = problem; + } + + transaction.p_impl->add_resolve_log( + GoalAction::REPLAY, + problem, + settings, + libdnf5::transaction::TransactionItemType::PACKAGE, + package_replay.nevra, + {transaction_item_action_to_string(package_replay.action)}, + log_level); + continue; + } + } + } + if (local_pkg) { add_rpm_ids(GoalAction::INSTALL, *local_pkg, settings_per_package); } else { @@ -693,6 +753,19 @@ GoalProblem Goal::Impl::add_replay_to_goal( } transaction.p_impl->rpm_reason_overrides[package_replay.nevra] = package_replay.reason; } else if (package_replay.action == transaction::TransactionItemAction::REMOVE) { + if (query_installed.empty()) { + auto problem = transaction.p_impl->report_not_found( + GoalAction::REPLAY, + package_replay.nevra, + settings, + settings.get_ignore_installed() ? libdnf5::Logger::Level::WARNING : libdnf5::Logger::Level::ERROR, + {transaction_item_action_to_string(package_replay.action)}); + if (!settings.get_ignore_installed()) { + ret |= problem; + } + continue; + } + if (local_pkg) { add_rpm_ids(GoalAction::REMOVE, *local_pkg, settings_per_package); } else { @@ -700,6 +773,18 @@ GoalProblem Goal::Impl::add_replay_to_goal( } transaction.p_impl->rpm_reason_overrides[package_replay.nevra] = package_replay.reason; } else if (package_replay.action == transaction::TransactionItemAction::REPLACED) { + if (query_installed.empty()) { + auto problem = transaction.p_impl->report_not_found( + GoalAction::REPLAY, + package_replay.nevra, + settings, + settings.get_ignore_installed() ? libdnf5::Logger::Level::WARNING : libdnf5::Logger::Level::ERROR, + {transaction_item_action_to_string(package_replay.action)}); + if (!settings.get_ignore_installed()) { + ret |= problem; + } + continue; + } // Removing the original versions (the reverse part of an action like e.g. Upgrade) is more robust, // but we can't do it if skip_unavailable is set because if the inbound action is skipped we would // simply remove the package. @@ -739,13 +824,47 @@ GoalProblem Goal::Impl::add_replay_to_goal( base->get_repo_sack()->add_stored_transaction_comps(replay_location / group_replay.group_path); } + comps::GroupQuery group_query_installed(base); + group_query_installed.filter_groupid(group_replay.group_id); + group_query_installed.filter_installed(true); + if (group_replay.action == transaction::TransactionItemAction::INSTALL) { group_specs.emplace_back( GoalAction::INSTALL, group_replay.reason, group_replay.group_id, settings_per_group); } else if (group_replay.action == transaction::TransactionItemAction::UPGRADE) { + if (group_query_installed.empty()) { + auto log_level = libdnf5::Logger::Level::WARNING; + if (!settings.get_ignore_installed()) { + log_level = libdnf5::Logger::Level::ERROR; + ret = GoalProblem::NOT_INSTALLED; + } + transaction.p_impl->add_resolve_log( + GoalAction::REPLAY, + GoalProblem::NOT_INSTALLED, + settings, + libdnf5::transaction::TransactionItemType::GROUP, + group_replay.group_id, + {transaction_item_action_to_string(group_replay.action)}, + log_level); + } group_specs.emplace_back( GoalAction::UPGRADE, group_replay.reason, group_replay.group_id, settings_per_group); } else if (group_replay.action == transaction::TransactionItemAction::REMOVE) { + if (group_query_installed.empty()) { + auto log_level = libdnf5::Logger::Level::WARNING; + if (!settings.get_ignore_installed()) { + log_level = libdnf5::Logger::Level::ERROR; + ret = GoalProblem::NOT_INSTALLED; + } + transaction.p_impl->add_resolve_log( + GoalAction::REPLAY, + GoalProblem::NOT_INSTALLED, + settings, + libdnf5::transaction::TransactionItemType::GROUP, + group_replay.group_id, + {transaction_item_action_to_string(group_replay.action)}, + log_level); + } group_specs.emplace_back( GoalAction::REMOVE, group_replay.reason, group_replay.group_id, settings_per_group); } else { @@ -769,6 +888,10 @@ GoalProblem Goal::Impl::add_replay_to_goal( } } + comps::EnvironmentQuery env_query_installed(base); + env_query_installed.filter_environmentid(env_replay.environment_id); + env_query_installed.filter_installed(true); + if (!env_replay.environment_path.empty()) { base->get_repo_sack()->add_stored_transaction_comps(replay_location / env_replay.environment_path); } @@ -779,12 +902,42 @@ GoalProblem Goal::Impl::add_replay_to_goal( env_replay.environment_id, settings_per_environment); } else if (env_replay.action == transaction::TransactionItemAction::UPGRADE) { + if (env_query_installed.empty()) { + auto log_level = libdnf5::Logger::Level::WARNING; + if (!settings.get_ignore_installed()) { + log_level = libdnf5::Logger::Level::ERROR; + ret = GoalProblem::NOT_INSTALLED; + } + transaction.p_impl->add_resolve_log( + GoalAction::REPLAY, + GoalProblem::NOT_INSTALLED, + settings, + libdnf5::transaction::TransactionItemType::ENVIRONMENT, + env_replay.environment_id, + {transaction_item_action_to_string(env_replay.action)}, + log_level); + } group_specs.emplace_back( GoalAction::UPGRADE, transaction::TransactionItemReason::USER, env_replay.environment_id, settings_per_environment); } else if (env_replay.action == transaction::TransactionItemAction::REMOVE) { + if (env_query_installed.empty()) { + auto log_level = libdnf5::Logger::Level::WARNING; + if (!settings.get_ignore_installed()) { + log_level = libdnf5::Logger::Level::ERROR; + ret = GoalProblem::NOT_INSTALLED; + } + transaction.p_impl->add_resolve_log( + GoalAction::REPLAY, + GoalProblem::NOT_INSTALLED, + settings, + libdnf5::transaction::TransactionItemType::ENVIRONMENT, + env_replay.environment_id, + {transaction_item_action_to_string(env_replay.action)}, + log_level); + } group_specs.emplace_back( GoalAction::REMOVE, transaction::TransactionItemReason::USER, @@ -796,7 +949,7 @@ GoalProblem Goal::Impl::add_replay_to_goal( } } - return GoalProblem::NO_PROBLEM; + return ret; } GoalProblem Goal::Impl::resolve_group_specs(std::vector & specs, base::Transaction & transaction) {