diff --git a/SOURCES/0026-context-Substitute-all-repository-config-options-RhB.patch b/SOURCES/0026-context-Substitute-all-repository-config-options-RhB.patch new file mode 100644 index 0000000..340d2c5 --- /dev/null +++ b/SOURCES/0026-context-Substitute-all-repository-config-options-RhB.patch @@ -0,0 +1,107 @@ +From 7d8f9cfcdf7725fef2c99ecb2dedcdff1e9506d7 Mon Sep 17 00:00:00 2001 +From: Jaroslav Rohel +Date: Wed, 13 Apr 2022 12:26:10 +0200 +Subject: [PATCH 26/34] context: Substitute all repository config options + (RhBug:2076853) + +It also solves the problem: Substitution of variables in `baseurl` +does not work in microdnf and PackageKit unless `metalink` or `mirrorlist` +is set at the same time. +--- + libdnf/dnf-repo.cpp | 34 +++++++++++++++++++++++++--------- + 1 file changed, 25 insertions(+), 9 deletions(-) + +diff --git a/libdnf/dnf-repo.cpp b/libdnf/dnf-repo.cpp +index 710045fb..9d42e3e3 100644 +--- a/libdnf/dnf-repo.cpp ++++ b/libdnf/dnf-repo.cpp +@@ -83,6 +83,7 @@ typedef struct + LrHandle *repo_handle; + LrResult *repo_result; + LrUrlVars *urlvars; ++ bool unit_test_mode; /* ugly hack for unit tests */ + } DnfRepoPrivate; + + G_DEFINE_TYPE_WITH_PRIVATE(DnfRepo, dnf_repo, G_TYPE_OBJECT) +@@ -847,8 +848,11 @@ dnf_repo_conf_reset(libdnf::ConfigRepo &config) + + /* Loads repository configuration from GKeyFile */ + static void +-dnf_repo_conf_from_gkeyfile(libdnf::ConfigRepo &config, const char *repoId, GKeyFile *gkeyFile) ++dnf_repo_conf_from_gkeyfile(DnfRepo *repo, const char *repoId, GKeyFile *gkeyFile) + { ++ DnfRepoPrivate *priv = GET_PRIVATE(repo); ++ auto & config = *priv->repo->getConfig(); ++ + // Reset to the initial state before reloading the configuration. + dnf_repo_conf_reset(config); + +@@ -883,20 +887,31 @@ dnf_repo_conf_from_gkeyfile(libdnf::ConfigRepo &config, const char *repoId, GKey + // list can be ['value1', 'value2, value3'] therefore we first join + // to have 'value1, value2, value3' + g_autofree gchar * tmp_strval = g_strjoinv(",", list); ++ ++ // Substitute vars. ++ g_autofree gchar *subst_value = dnf_repo_substitute(repo, tmp_strval); ++ ++ if (strcmp(key, "baseurl") == 0 && strstr(tmp_strval, "file://$testdatadir") != NULL) { ++ priv->unit_test_mode = true; ++ } ++ + try { +- optionItem.newString(libdnf::Option::Priority::REPOCONFIG, tmp_strval); ++ optionItem.newString(libdnf::Option::Priority::REPOCONFIG, subst_value); + } catch (const std::exception & ex) { +- g_debug("Invalid configuration value: %s = %s in %s; %s", key, value.c_str(), repoId, ex.what()); ++ g_debug("Invalid configuration value: %s = %s in %s; %s", key, subst_value, repoId, ex.what()); + } + } + + } else { +- + // process other (non list) options ++ ++ // Substitute vars. ++ g_autofree gchar *subst_value = dnf_repo_substitute(repo, value.c_str()); ++ + try { +- optionItem.newString(libdnf::Option::Priority::REPOCONFIG, value); ++ optionItem.newString(libdnf::Option::Priority::REPOCONFIG, subst_value); + } catch (const std::exception & ex) { +- g_debug("Invalid configuration value: %s = %s in %s; %s", key, value.c_str(), repoId, ex.what()); ++ g_debug("Invalid configuration value: %s = %s in %s; %s", key, subst_value, repoId, ex.what()); + } + + } +@@ -950,7 +965,7 @@ dnf_repo_set_keyfile_data(DnfRepo *repo, gboolean reloadFromGKeyFile, GError **e + + // Reload repository configuration from keyfile. + if (reloadFromGKeyFile) { +- dnf_repo_conf_from_gkeyfile(*conf, repoId, priv->keyfile); ++ dnf_repo_conf_from_gkeyfile(repo, repoId, priv->keyfile); + dnf_repo_apply_setopts(*conf, repoId); + } + +@@ -996,8 +1011,9 @@ dnf_repo_set_keyfile_data(DnfRepo *repo, gboolean reloadFromGKeyFile, GError **e + g_autofree gchar *url = NULL; + url = lr_prepend_url_protocol(baseurls[0]); + if (url != NULL && strncasecmp(url, "file://", 7) == 0) { +- if (g_strstr_len(url, -1, "$testdatadir") == NULL) ++ if (!priv->unit_test_mode) { + priv->kind = DNF_REPO_KIND_LOCAL; ++ } + g_free(priv->location); + g_free(priv->keyring); + priv->location = dnf_repo_substitute(repo, url + 7); +@@ -1224,7 +1240,7 @@ dnf_repo_setup(DnfRepo *repo, GError **error) try + auto repoId = priv->repo->getId().c_str(); + + auto conf = priv->repo->getConfig(); +- dnf_repo_conf_from_gkeyfile(*conf, repoId, priv->keyfile); ++ dnf_repo_conf_from_gkeyfile(repo, repoId, priv->keyfile); + dnf_repo_apply_setopts(*conf, repoId); + + auto sslverify = conf->sslverify().getValue(); +-- +2.31.1 + diff --git a/SOURCES/0027-Use-environment-variable-in-unittest-instead-of-ugly.patch b/SOURCES/0027-Use-environment-variable-in-unittest-instead-of-ugly.patch new file mode 100644 index 0000000..31fb712 --- /dev/null +++ b/SOURCES/0027-Use-environment-variable-in-unittest-instead-of-ugly.patch @@ -0,0 +1,50 @@ +From 074ca4cf643c79b8ec3db89a7fd5580ba387eb4d Mon Sep 17 00:00:00 2001 +From: Jaroslav Rohel +Date: Wed, 20 Apr 2022 08:22:30 +0200 +Subject: [PATCH 27/34] Use environment variable in unittest instead of ugly + hack in libdnf + +Libdnf contains hacks for unit tests. This removes one hack. +--- + libdnf/dnf-repo.cpp | 3 --- + tests/libdnf/dnf-self-test.c | 3 +++ + 2 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/libdnf/dnf-repo.cpp b/libdnf/dnf-repo.cpp +index 9d42e3e3..c015d7fd 100644 +--- a/libdnf/dnf-repo.cpp ++++ b/libdnf/dnf-repo.cpp +@@ -1191,7 +1191,6 @@ dnf_repo_setup(DnfRepo *repo, GError **error) try + DnfRepoEnabled enabled = DNF_REPO_ENABLED_NONE; + g_autofree gchar *basearch = NULL; + g_autofree gchar *release = NULL; +- g_autofree gchar *testdatadir = NULL; + + basearch = g_key_file_get_string(priv->keyfile, "general", "arch", NULL); + if (basearch == NULL) +@@ -1230,8 +1229,6 @@ dnf_repo_setup(DnfRepo *repo, GError **error) try + for (const auto & item : libdnf::dnf_context_get_vars(priv->context)) + priv->urlvars = lr_urlvars_set(priv->urlvars, item.first.c_str(), item.second.c_str()); + +- testdatadir = dnf_realpath(TESTDATADIR); +- priv->urlvars = lr_urlvars_set(priv->urlvars, "testdatadir", testdatadir); + if (!lr_handle_setopt(priv->repo_handle, error, LRO_VARSUB, priv->urlvars)) + return FALSE; + if (!lr_handle_setopt(priv->repo_handle, error, LRO_GNUPGHOMEDIR, priv->keyring)) +diff --git a/tests/libdnf/dnf-self-test.c b/tests/libdnf/dnf-self-test.c +index 52958371..906f0e21 100644 +--- a/tests/libdnf/dnf-self-test.c ++++ b/tests/libdnf/dnf-self-test.c +@@ -1225,6 +1225,9 @@ main(int argc, char **argv) + g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); + g_log_set_always_fatal (G_LOG_FATAL_MASK); + ++ /* Sets a variable to replace in repository configurations. */ ++ g_setenv("DNF_VAR_testdatadir", TESTDATADIR, TRUE); ++ + /* tests go here */ + g_test_add_func("/libdnf/repo_loader{gpg-asc}", dnf_repo_loader_gpg_asc_func); + g_test_add_func("/libdnf/repo_loader{gpg-wrong-asc}", dnf_repo_loader_gpg_wrong_asc_func); +-- +2.31.1 + diff --git a/SOURCES/0028-Add-private-API-for-filling-reading-and-verifying-ne.patch b/SOURCES/0028-Add-private-API-for-filling-reading-and-verifying-ne.patch new file mode 100644 index 0000000..dfbfcbd --- /dev/null +++ b/SOURCES/0028-Add-private-API-for-filling-reading-and-verifying-ne.patch @@ -0,0 +1,169 @@ +From 983aeea57d75494fd4ea2ff2903f966136278c15 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= +Date: Wed, 9 Feb 2022 13:17:00 +0100 +Subject: [PATCH 28/34] Add private API for filling, reading and verifying new + dnf solv userdata + +--- + libdnf/hy-iutil-private.hpp | 24 +++++++++ + libdnf/hy-iutil.cpp | 102 ++++++++++++++++++++++++++++++++++++ + 2 files changed, 126 insertions(+) + +diff --git a/libdnf/hy-iutil-private.hpp b/libdnf/hy-iutil-private.hpp +index e07b1b51..d498c032 100644 +--- a/libdnf/hy-iutil-private.hpp ++++ b/libdnf/hy-iutil-private.hpp +@@ -24,6 +24,30 @@ + #include "hy-iutil.h" + #include "hy-types.h" + #include "sack/packageset.hpp" ++#include ++#include ++ ++// Use 8 bytes for libsolv version (API: solv_toolversion) ++// to be future proof even though it currently is "1.2" ++static constexpr const size_t solv_userdata_solv_toolversion_size{8}; ++static constexpr const std::array solv_userdata_magic{'\0', 'd', 'n', 'f'}; ++static constexpr const std::array solv_userdata_dnf_version{'\0', '1', '.', '0'}; ++ ++static constexpr const int solv_userdata_size = solv_userdata_solv_toolversion_size + \ ++ solv_userdata_magic.size() + \ ++ solv_userdata_dnf_version.size() + \ ++ CHKSUM_BYTES; ++ ++struct SolvUserdata { ++ char dnf_magic[solv_userdata_magic.size()]; ++ char dnf_version[solv_userdata_dnf_version.size()]; ++ char libsolv_version[solv_userdata_solv_toolversion_size]; ++ unsigned char checksum[CHKSUM_BYTES]; ++}__attribute__((packed)); ; ++ ++int solv_userdata_fill(SolvUserdata *solv_userdata, const unsigned char *checksum, GError** error); ++std::unique_ptr solv_userdata_read(FILE *fp); ++int solv_userdata_verify(const SolvUserdata *solv_userdata, const unsigned char *checksum); + + /* crypto utils */ + int checksum_cmp(const unsigned char *cs1, const unsigned char *cs2); +diff --git a/libdnf/hy-iutil.cpp b/libdnf/hy-iutil.cpp +index 2af13197..f81ca52f 100644 +--- a/libdnf/hy-iutil.cpp ++++ b/libdnf/hy-iutil.cpp +@@ -43,6 +43,7 @@ extern "C" { + #include + #include + #include ++#include + #include + #include + } +@@ -182,6 +183,107 @@ int checksum_write(const unsigned char *cs, FILE *fp) + return 0; + } + ++static std::array ++get_padded_solv_toolversion() ++{ ++ std::array padded_solv_toolversion{}; ++ std::string solv_ver_str{solv_toolversion}; ++ std::copy(solv_ver_str.rbegin(), solv_ver_str.rend(), padded_solv_toolversion.rbegin()); ++ ++ return padded_solv_toolversion; ++} ++ ++int ++solv_userdata_fill(SolvUserdata *solv_userdata, const unsigned char *checksum, GError** error) ++{ ++ if (strlen(solv_toolversion) > solv_userdata_solv_toolversion_size) { ++ g_set_error(error, DNF_ERROR, DNF_ERROR_INTERNAL_ERROR, ++ _("Libsolv's solv_toolversion is: %zu long but we expect max of: %zu"), ++ strlen(solv_toolversion), solv_userdata_solv_toolversion_size); ++ return 1; ++ } ++ ++ // copy dnf solv file magic ++ memcpy(solv_userdata->dnf_magic, solv_userdata_magic.data(), solv_userdata_magic.size()); ++ ++ // copy dnf solv file version ++ memcpy(solv_userdata->dnf_version, solv_userdata_dnf_version.data(), solv_userdata_dnf_version.size()); ++ ++ // copy libsolv solv file version ++ memcpy(solv_userdata->libsolv_version, get_padded_solv_toolversion().data(), solv_userdata_solv_toolversion_size); ++ ++ // copy checksum ++ memcpy(solv_userdata->checksum, checksum, CHKSUM_BYTES); ++ ++ return 0; ++} ++ ++ ++std::unique_ptr ++solv_userdata_read(FILE *fp) ++{ ++ unsigned char *dnf_solvfile_userdata_read = NULL; ++ int dnf_solvfile_userdata_len_read; ++ if (!fp) { ++ return nullptr; ++ } ++ ++ int ret_code = solv_read_userdata(fp, &dnf_solvfile_userdata_read, &dnf_solvfile_userdata_len_read); ++ // The userdata layout has to match our struct exactly so we can just cast the memory ++ // allocated by libsolv ++ std::unique_ptr uniq_userdata(reinterpret_cast(dnf_solvfile_userdata_read)); ++ if(ret_code) { ++ g_warning("Failed to read solv userdata: solv_read_userdata returned: %i", ret_code); ++ return nullptr; ++ } ++ ++ if (dnf_solvfile_userdata_len_read != solv_userdata_size) { ++ g_warning("Solv userdata length mismatch, read: %i vs expected: %i", ++ dnf_solvfile_userdata_len_read, solv_userdata_size); ++ return nullptr; ++ } ++ ++ return uniq_userdata; ++} ++ ++gboolean ++solv_userdata_verify(const SolvUserdata *solv_userdata, const unsigned char *checksum) ++{ ++ // check dnf solvfile magic bytes ++ if (memcmp(solv_userdata->dnf_magic, solv_userdata_magic.data(), solv_userdata_magic.size()) != 0) { ++ // This is not dnf header do not read after it ++ g_warning("magic bytes don't match, read: %s vs. dnf solvfile magic: %s", ++ solv_userdata->dnf_magic, solv_userdata_magic.data()); ++ return FALSE; ++ } ++ ++ // check dnf solvfile version ++ if (memcmp(solv_userdata->dnf_version, solv_userdata_dnf_version.data(), solv_userdata_dnf_version.size()) != 0) { ++ // Mismatching dnf solvfile version -> we need to regenerate ++ g_warning("dnf solvfile version doesn't match, read: %s vs. dnf solvfile version: %s", ++ solv_userdata->dnf_version, solv_userdata_dnf_version.data()); ++ return FALSE; ++ } ++ ++ // check libsolv solvfile version ++ if (memcmp(solv_userdata->libsolv_version, get_padded_solv_toolversion().data(), solv_userdata_solv_toolversion_size) != 0) { ++ // Mismatching libsolv solvfile version -> we need to regenerate ++ g_warning("libsolv solvfile version doesn't match, read: %s vs. libsolv version: %s", ++ solv_userdata->libsolv_version, solv_toolversion); ++ return FALSE; ++ } ++ ++ // check solvfile checksum ++ if (checksum_cmp(solv_userdata->checksum, checksum)) { ++ // Mismatching solvfile checksum -> we need to regenerate ++ g_debug("solvfile checksum doesn't match, read: %s vs. repomd checksum: %s", ++ solv_userdata->checksum, checksum); ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ + int + checksum_type2length(int type) + { +-- +2.31.1 + diff --git a/SOURCES/0029-Use-dnf-solv-userdata-to-check-versions-and-checksum.patch b/SOURCES/0029-Use-dnf-solv-userdata-to-check-versions-and-checksum.patch new file mode 100644 index 0000000..fd7feff --- /dev/null +++ b/SOURCES/0029-Use-dnf-solv-userdata-to-check-versions-and-checksum.patch @@ -0,0 +1,417 @@ +From 465a6a59279bd7fa2680c626ca0f10c059276668 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= +Date: Wed, 9 Feb 2022 13:18:41 +0100 +Subject: [PATCH 29/34] Use dnf solv userdata to check versions and checksum + (RhBug:2027445) + +Remove unused functions for checksums + += changelog = +msg: Write and check versions and checksums for solvfile cache by using new dnf solvfile userdata (RhBug:2027445) + It is not possible to use old cache files, therefore cache regeneration is triggered automatically. +type: bugfix +resolves: https://bugzilla.redhat.com/show_bug.cgi?id=2027445 +--- + libdnf/dnf-sack.cpp | 254 ++++++++++++++++++++++-------------- + libdnf/hy-iutil-private.hpp | 2 - + libdnf/hy-iutil.cpp | 20 --- + 3 files changed, 156 insertions(+), 120 deletions(-) + +diff --git a/libdnf/dnf-sack.cpp b/libdnf/dnf-sack.cpp +index b9baeaef..61f4807c 100644 +--- a/libdnf/dnf-sack.cpp ++++ b/libdnf/dnf-sack.cpp +@@ -225,17 +225,39 @@ dnf_sack_new(void) + return DNF_SACK(g_object_new(DNF_TYPE_SACK, NULL)); + } + +-static int +-can_use_repomd_cache(FILE *fp_solv, unsigned char cs_repomd[CHKSUM_BYTES]) +-{ +- unsigned char cs_cache[CHKSUM_BYTES]; +- +- if (fp_solv && +- !checksum_read(cs_cache, fp_solv) && +- !checksum_cmp(cs_cache, cs_repomd)) +- return 1; ++// Try to load cached solv file into repo otherwise return FALSE ++static gboolean ++try_to_use_cached_solvfile(const char *path, Repo *repo, int flags, const unsigned char *checksum, GError **err){ ++ FILE *fp_cache = fopen(path, "r"); ++ if (!fp_cache) { ++ // Missing cache files (ENOENT) are not an error and can even be expected in some cases ++ // (such as when repo doesn't have updateinfo/prestodelta metadata). ++ // Use g_debug in order not to pollute the log by default with such entries. ++ if (errno == ENOENT) { ++ g_debug("Failed to open solvfile cache: %s: %s", path, strerror(errno)); ++ } else { ++ g_warning("Failed to open solvfile cache: %s: %s", path, strerror(errno)); ++ } ++ return FALSE; ++ } ++ std::unique_ptr solv_userdata = solv_userdata_read(fp_cache); ++ gboolean ret = TRUE; ++ if (solv_userdata && solv_userdata_verify(solv_userdata.get(), checksum)) { ++ // after reading the header rewind to the begining ++ fseek(fp_cache, 0, SEEK_SET); ++ if (repo_add_solv(repo, fp_cache, flags)) { ++ g_set_error (err, ++ DNF_ERROR, ++ DNF_ERROR_INTERNAL_ERROR, ++ _("repo_add_solv() has failed.")); ++ ret = FALSE; ++ } ++ } else { ++ ret = FALSE; ++ } + +- return 0; ++ fclose(fp_cache); ++ return ret; + } + + void +@@ -375,33 +397,27 @@ load_ext(DnfSack *sack, HyRepo hrepo, _hy_repo_repodata which_repodata, + gboolean done = FALSE; + + char *fn_cache = dnf_sack_give_cache_fn(sack, name, suffix); +- fp = fopen(fn_cache, "r"); + assert(libdnf::repoGetImpl(hrepo)->checksum); +- if (can_use_repomd_cache(fp, libdnf::repoGetImpl(hrepo)->checksum)) { +- int flags = 0; +- /* the updateinfo is not a real extension */ +- if (which_repodata != _HY_REPODATA_UPDATEINFO) +- flags |= REPO_EXTEND_SOLVABLES; +- /* do not pollute the main pool with directory component ids */ +- if (which_repodata == _HY_REPODATA_FILENAMES || which_repodata == _HY_REPODATA_OTHER) +- flags |= REPO_LOCALPOOL; +- done = TRUE; ++ ++ int flags = 0; ++ /* the updateinfo is not a real extension */ ++ if (which_repodata != _HY_REPODATA_UPDATEINFO) ++ flags |= REPO_EXTEND_SOLVABLES; ++ /* do not pollute the main pool with directory component ids */ ++ if (which_repodata == _HY_REPODATA_FILENAMES || which_repodata == _HY_REPODATA_OTHER) ++ flags |= REPO_LOCALPOOL; ++ if (try_to_use_cached_solvfile(fn_cache, repo, flags, libdnf::repoGetImpl(hrepo)->checksum, error)) { + g_debug("%s: using cache file: %s", __func__, fn_cache); +- ret = repo_add_solv(repo, fp, flags); +- if (ret) { +- g_set_error_literal (error, +- DNF_ERROR, +- DNF_ERROR_INTERNAL_ERROR, +- _("failed to add solv")); +- return FALSE; +- } else { +- repo_update_state(hrepo, which_repodata, _HY_LOADED_CACHE); +- repo_set_repodata(hrepo, which_repodata, repo->nrepodata - 1); +- } ++ done = TRUE; ++ repo_update_state(hrepo, which_repodata, _HY_LOADED_CACHE); ++ repo_set_repodata(hrepo, which_repodata, repo->nrepodata - 1); + } ++ if (error && *error) { ++ g_prefix_error(error, _("Loading extension cache %s (%d) failed: "), fn_cache, which_repodata); ++ return FALSE; ++ } ++ + g_free(fn_cache); +- if (fp) +- fclose(fp); + if (done) + return TRUE; + +@@ -514,35 +530,53 @@ write_main(DnfSack *sack, HyRepo hrepo, int switchtosolv, GError **error) + strerror(errno)); + goto done; + } +- rc = repo_write(repo, fp); +- rc |= checksum_write(repoImpl->checksum, fp); +- rc |= fclose(fp); ++ ++ SolvUserdata solv_userdata; ++ if (solv_userdata_fill(&solv_userdata, repoImpl->checksum, error)) { ++ ret = FALSE; ++ fclose(fp); ++ goto done; ++ } ++ ++ Repowriter *writer = repowriter_create(repo); ++ repowriter_set_userdata(writer, &solv_userdata, solv_userdata_size); ++ rc = repowriter_write(writer, fp); ++ repowriter_free(writer); + if (rc) { ++ ret = FALSE; ++ fclose(fp); ++ g_set_error(error, ++ DNF_ERROR, ++ DNF_ERROR_INTERNAL_ERROR, ++ _("While writing primary cache %s repowriter write failed: %i, error: %s"), ++ tmp_fn_templ, rc, pool_errstr(repo->pool)); ++ goto done; ++ } ++ ++ if (fclose(fp)) { + ret = FALSE; + g_set_error (error, + DNF_ERROR, + DNF_ERROR_FILE_INVALID, +- _("write_main() failed writing data: %i"), rc); ++ _("Failed closing tmp file %s: %s"), ++ tmp_fn_templ, strerror(errno)); + goto done; + } + } + if (switchtosolv && repo_is_one_piece(repo)) { ++ repo_empty(repo, 1); + /* switch over to written solv file activate paging */ +- FILE *fp = fopen(tmp_fn_templ, "r"); +- if (fp) { +- repo_empty(repo, 1); +- rc = repo_add_solv(repo, fp, 0); +- fclose(fp); +- if (rc) { +- /* this is pretty fatal */ +- ret = FALSE; +- g_set_error_literal (error, +- DNF_ERROR, +- DNF_ERROR_FILE_INVALID, +- _("write_main() failed to re-load " +- "written solv file")); +- goto done; +- } ++ gboolean loaded = try_to_use_cached_solvfile(tmp_fn_templ, repo, 0, repoImpl->checksum, error); ++ if (error && *error) { ++ g_prefix_error(error, _("Failed to use newly written primary cache: %s: "), tmp_fn_templ); ++ ret = FALSE; ++ goto done; ++ } ++ if (!loaded) { ++ g_set_error(error, DNF_ERROR, DNF_ERROR_INTERNAL_ERROR, ++ _("Failed to use newly written primary cache: %s"), tmp_fn_templ); ++ ret = FALSE; ++ goto done; + } + } + +@@ -569,20 +603,6 @@ write_ext_updateinfo_filter(Repo *repo, Repokey *key, void *kfdata) + return repo_write_stdkeyfilter(repo, key, 0); + } + +-static int +-write_ext_updateinfo(HyRepo hrepo, Repodata *data, FILE *fp) +-{ +- auto repoImpl = libdnf::repoGetImpl(hrepo); +- Repo *repo = repoImpl->libsolvRepo; +- int oldstart = repo->start; +- repo->start = repoImpl->main_end; +- repo->nsolvables -= repoImpl->main_nsolvables; +- int res = repo_write_filtered(repo, fp, write_ext_updateinfo_filter, data, 0); +- repo->start = oldstart; +- repo->nsolvables += repoImpl->main_nsolvables; +- return res; +-} +- + static gboolean + write_ext(DnfSack *sack, HyRepo hrepo, _hy_repo_repodata which_repodata, + const char *suffix, GError **error) +@@ -611,37 +631,78 @@ write_ext(DnfSack *sack, HyRepo hrepo, _hy_repo_repodata which_repodata, + FILE *fp = fdopen(tmp_fd, "w+"); + + g_debug("%s: storing %s to: %s", __func__, repo->name, tmp_fn_templ); +- if (which_repodata != _HY_REPODATA_UPDATEINFO) +- ret |= repodata_write(data, fp); +- else +- ret |= write_ext_updateinfo(hrepo, data, fp); +- ret |= checksum_write(repoImpl->checksum, fp); +- ret |= fclose(fp); ++ ++ SolvUserdata solv_userdata; ++ if (solv_userdata_fill(&solv_userdata, repoImpl->checksum, error)) { ++ fclose(fp); ++ success = FALSE; ++ goto done; ++ } ++ ++ Repowriter *writer = repowriter_create(repo); ++ repowriter_set_userdata(writer, &solv_userdata, solv_userdata_size); ++ if (which_repodata != _HY_REPODATA_UPDATEINFO) { ++ repowriter_set_repodatarange(writer, data->repodataid, data->repodataid + 1); ++ repowriter_set_flags(writer, REPOWRITER_NO_STORAGE_SOLVABLE); ++ ret = repowriter_write(writer, fp); ++ } else { ++ // write only updateinfo repodata ++ int oldstart = repo->start; ++ repo->start = repoImpl->main_end; ++ repo->nsolvables -= repoImpl->main_nsolvables; ++ repowriter_set_flags(writer, REPOWRITER_LEGACY); ++ repowriter_set_keyfilter(writer, write_ext_updateinfo_filter, data); ++ repowriter_set_keyqueue(writer, 0); ++ ret = repowriter_write(writer, fp); ++ repo->start = oldstart; ++ repo->nsolvables += repoImpl->main_nsolvables; ++ } ++ repowriter_free(writer); + if (ret) { ++ success = FALSE; ++ fclose(fp); ++ g_set_error (error, ++ DNF_ERROR, ++ DNF_ERROR_INTERNAL_ERROR, ++ _("While writing extension cache %s (%d): repowriter write failed: %i, error: %s"), ++ tmp_fn_templ, which_repodata, ret, pool_errstr(repo->pool)); ++ goto done; ++ } ++ ++ if (fclose(fp)) { + success = FALSE; + g_set_error (error, + DNF_ERROR, +- DNF_ERROR_FAILED, +- _("write_ext(%1$d) has failed: %2$d"), +- which_repodata, ret); ++ DNF_ERROR_FILE_INVALID, ++ _("While writing extension cache (%d): cannot close temporary file: %s"), ++ which_repodata, tmp_fn_templ); + goto done; + } + } + + if (repo_is_one_piece(repo) && which_repodata != _HY_REPODATA_UPDATEINFO) { + /* switch over to written solv file activate paging */ +- FILE *fp = fopen(tmp_fn_templ, "r"); +- if (fp) { +- int flags = REPO_USE_LOADING | REPO_EXTEND_SOLVABLES; +- /* do not pollute the main pool with directory component ids */ +- if (which_repodata == _HY_REPODATA_FILENAMES || which_repodata == _HY_REPODATA_OTHER) +- flags |= REPO_LOCALPOOL; +- repodata_extend_block(data, repo->start, repo->end - repo->start); +- data->state = REPODATA_LOADING; +- repo_add_solv(repo, fp, flags); +- data->state = REPODATA_AVAILABLE; +- fclose(fp); ++ int flags = REPO_USE_LOADING | REPO_EXTEND_SOLVABLES; ++ /* do not pollute the main pool with directory component ids */ ++ if (which_repodata == _HY_REPODATA_FILENAMES || which_repodata == _HY_REPODATA_OTHER) ++ flags |= REPO_LOCALPOOL; ++ repodata_extend_block(data, repo->start, repo->end - repo->start); ++ data->state = REPODATA_LOADING; ++ int loaded = try_to_use_cached_solvfile(tmp_fn_templ, repo, flags, repoImpl->checksum, error); ++ if (error && *error) { ++ g_prefix_error(error, _("Failed to use newly written extension cache: %s (%d): "), ++ tmp_fn_templ, which_repodata); ++ success = FALSE; ++ goto done; ++ } ++ if (!loaded) { ++ g_set_error(error, DNF_ERROR, DNF_ERROR_INTERNAL_ERROR, ++ _("Failed to use newly written extension cache: %s (%d)"), tmp_fn_templ, which_repodata); ++ success = FALSE; ++ goto done; + } ++ ++ data->state = REPODATA_AVAILABLE; + } + + if (!mv(tmp_fn_templ, fn, error)) { +@@ -672,7 +733,7 @@ load_yum_repo(DnfSack *sack, HyRepo hrepo, GError **error) + + FILE *fp_primary = NULL; + FILE *fp_repomd = NULL; +- FILE *fp_cache = fopen(fn_cache, "r"); ++ + if (!fn_repomd) { + g_set_error (error, + DNF_ERROR, +@@ -693,18 +754,17 @@ load_yum_repo(DnfSack *sack, HyRepo hrepo, GError **error) + } + checksum_fp(repoImpl->checksum, fp_repomd); + +- if (can_use_repomd_cache(fp_cache, repoImpl->checksum)) { ++ if (try_to_use_cached_solvfile(fn_cache, repo, 0, repoImpl->checksum, error)) { + const char *chksum = pool_checksum_str(pool, repoImpl->checksum); + g_debug("using cached %s (0x%s)", name, chksum); +- if (repo_add_solv(repo, fp_cache, 0)) { +- g_set_error (error, +- DNF_ERROR, +- DNF_ERROR_INTERNAL_ERROR, +- _("repo_add_solv() has failed.")); +- retval = FALSE; +- goto out; +- } + repoImpl->state_main = _HY_LOADED_CACHE; ++ goto out; ++ } ++ ++ if (error && *error) { ++ g_prefix_error(error, _("While loading repository failed to use %s: "), fn_cache); ++ retval = FALSE; ++ goto out; + } else { + auto primary = hrepo->getMetadataPath(MD_TYPE_PRIMARY); + if (primary.empty()) { +@@ -733,8 +793,6 @@ load_yum_repo(DnfSack *sack, HyRepo hrepo, GError **error) + repoImpl->state_main = _HY_LOADED_FETCH; + } + out: +- if (fp_cache) +- fclose(fp_cache); + if (fp_repomd) + fclose(fp_repomd); + if (fp_primary) +diff --git a/libdnf/hy-iutil-private.hpp b/libdnf/hy-iutil-private.hpp +index d498c032..efc91c63 100644 +--- a/libdnf/hy-iutil-private.hpp ++++ b/libdnf/hy-iutil-private.hpp +@@ -52,9 +52,7 @@ int solv_userdata_verify(const SolvUserdata *solv_userdata, const unsigned char + /* crypto utils */ + int checksum_cmp(const unsigned char *cs1, const unsigned char *cs2); + int checksum_fp(unsigned char *out, FILE *fp); +-int checksum_read(unsigned char *csout, FILE *fp); + int checksum_stat(unsigned char *out, FILE *fp); +-int checksum_write(const unsigned char *cs, FILE *fp); + int checksumt_l2h(int type); + const char *pool_checksum_str(Pool *pool, const unsigned char *chksum); + +diff --git a/libdnf/hy-iutil.cpp b/libdnf/hy-iutil.cpp +index f81ca52f..c409a10a 100644 +--- a/libdnf/hy-iutil.cpp ++++ b/libdnf/hy-iutil.cpp +@@ -142,17 +142,6 @@ checksum_fp(unsigned char *out, FILE *fp) + return 0; + } + +-/* calls rewind(fp) before returning */ +-int +-checksum_read(unsigned char *csout, FILE *fp) +-{ +- if (fseek(fp, -32, SEEK_END) || +- fread(csout, CHKSUM_BYTES, 1, fp) != 1) +- return 1; +- rewind(fp); +- return 0; +-} +- + /* does not move the fp position */ + int + checksum_stat(unsigned char *out, FILE *fp) +@@ -174,15 +163,6 @@ checksum_stat(unsigned char *out, FILE *fp) + return 0; + } + +-/* moves fp to the end of file */ +-int checksum_write(const unsigned char *cs, FILE *fp) +-{ +- if (fseek(fp, 0, SEEK_END) || +- fwrite(cs, CHKSUM_BYTES, 1, fp) != 1) +- return 1; +- return 0; +-} +- + static std::array + get_padded_solv_toolversion() + { +-- +2.31.1 + diff --git a/SOURCES/0030-Update-unittest-to-test-the-new-private-dnf-solvfile.patch b/SOURCES/0030-Update-unittest-to-test-the-new-private-dnf-solvfile.patch new file mode 100644 index 0000000..56c96c5 --- /dev/null +++ b/SOURCES/0030-Update-unittest-to-test-the-new-private-dnf-solvfile.patch @@ -0,0 +1,83 @@ +From 1e0f8f66f6ff30e177c41be7d72330d5eccf2ff8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= +Date: Wed, 9 Feb 2022 13:24:06 +0100 +Subject: [PATCH 30/34] Update unittest to test the new private dnf solvfile + API + +--- + tests/hawkey/test_iutil.cpp | 34 ++++++++++++++++++++++------------ + 1 file changed, 22 insertions(+), 12 deletions(-) + +diff --git a/tests/hawkey/test_iutil.cpp b/tests/hawkey/test_iutil.cpp +index 8d00cc94..f3c04782 100644 +--- a/tests/hawkey/test_iutil.cpp ++++ b/tests/hawkey/test_iutil.cpp +@@ -24,6 +24,8 @@ + + + #include ++#include ++#include + + + #include "libdnf/hy-util.h" +@@ -97,28 +99,36 @@ START_TEST(test_checksum) + } + END_TEST + +-START_TEST(test_checksum_write_read) ++START_TEST(test_dnf_solvfile_userdata) + { + char *new_file = solv_dupjoin(test_globals.tmpdir, +- "/test_checksum_write_read", NULL); ++ "/test_dnf_solvfile_userdata", NULL); + build_test_file(new_file); + + unsigned char cs_computed[CHKSUM_BYTES]; +- unsigned char cs_read[CHKSUM_BYTES]; +- FILE *fp = fopen(new_file, "r"); ++ FILE *fp = fopen(new_file, "r+"); + checksum_fp(cs_computed, fp); +- // fails, file opened read-only: +- fail_unless(checksum_write(cs_computed, fp) == 1); +- fclose(fp); +- fp = fopen(new_file, "r+"); +- fail_if(checksum_write(cs_computed, fp)); ++ ++ SolvUserdata solv_userdata; ++ fail_if(solv_userdata_fill(&solv_userdata, cs_computed, NULL)); ++ ++ Pool *pool = pool_create(); ++ Repo *repo = repo_create(pool, "test_repo"); ++ Repowriter *writer = repowriter_create(repo); ++ repowriter_set_userdata(writer, &solv_userdata, solv_userdata_size); ++ fail_if(repowriter_write(writer, fp)); ++ repowriter_free(writer); + fclose(fp); ++ + fp = fopen(new_file, "r"); +- fail_if(checksum_read(cs_read, fp)); +- fail_if(checksum_cmp(cs_computed, cs_read)); ++ std::unique_ptr dnf_solvfile = solv_userdata_read(fp); ++ fail_unless(dnf_solvfile); ++ fail_unless(solv_userdata_verify(dnf_solvfile.get(), cs_computed)); + fclose(fp); + + g_free(new_file); ++ repo_free(repo, 0); ++ pool_free(pool); + } + END_TEST + +@@ -181,7 +191,7 @@ iutil_suite(void) + TCase *tc = tcase_create("Main"); + tcase_add_test(tc, test_abspath); + tcase_add_test(tc, test_checksum); +- tcase_add_test(tc, test_checksum_write_read); ++ tcase_add_test(tc, test_dnf_solvfile_userdata); + tcase_add_test(tc, test_mkcachedir); + tcase_add_test(tc, test_version_split); + suite_add_tcase(s, tc); +-- +2.31.1 + diff --git a/SOURCES/0031-Increase-required-libsolv-version-for-cache-versioni.patch b/SOURCES/0031-Increase-required-libsolv-version-for-cache-versioni.patch new file mode 100644 index 0000000..37b9497 --- /dev/null +++ b/SOURCES/0031-Increase-required-libsolv-version-for-cache-versioni.patch @@ -0,0 +1,38 @@ +From 893eb087e56588d62e81e91e5d283003bd80552a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= +Date: Tue, 8 Mar 2022 11:43:38 +0100 +Subject: [PATCH 31/34] Increase required libsolv version for cache versioning + +--- + CMakeLists.txt | 2 +- + libdnf.spec | 4 ++-- + 2 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 60cf1b8c..d895b2bf 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -51,7 +51,7 @@ endif() + + # build dependencies + find_package(Gpgme REQUIRED) +-find_package(LibSolv 0.6.30 REQUIRED COMPONENTS ext) ++find_package(LibSolv 0.7.20 REQUIRED COMPONENTS ext) + find_package(OpenSSL REQUIRED) + + +diff --git a/libdnf.spec b/libdnf.spec +index a849cdea..aa51dd28 100644 +--- a/libdnf.spec ++++ b/libdnf.spec +@@ -1,5 +1,5 @@ +-%global libsolv_version 0.7.17 +-%global libmodulemd_version 2.11.2-2 ++%global libsolv_version 0.7.21 ++%global libmodulemd_version 2.13.0 + %global librepo_version 1.13.1 + %global dnf_conflict 4.3.0 + %global swig_version 3.0.12 +-- +2.31.1 + diff --git a/SOURCES/0032-Add-more-specific-error-handling-for-loading-repomd-.patch b/SOURCES/0032-Add-more-specific-error-handling-for-loading-repomd-.patch new file mode 100644 index 0000000..1f0303c --- /dev/null +++ b/SOURCES/0032-Add-more-specific-error-handling-for-loading-repomd-.patch @@ -0,0 +1,46 @@ +From b636af779fcdab326eef7bbb74912254c2fa2b0c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= +Date: Thu, 17 Mar 2022 10:34:24 +0100 +Subject: [PATCH 32/34] Add more specific error handling for loading repomd and + primary + +--- + libdnf/dnf-sack.cpp | 19 +++++++++++++++---- + 1 file changed, 15 insertions(+), 4 deletions(-) + +diff --git a/libdnf/dnf-sack.cpp b/libdnf/dnf-sack.cpp +index 61f4807c..8e11b8f8 100644 +--- a/libdnf/dnf-sack.cpp ++++ b/libdnf/dnf-sack.cpp +@@ -780,13 +780,24 @@ load_yum_repo(DnfSack *sack, HyRepo hrepo, GError **error) + fp_primary = solv_xfopen(primary.c_str(), "r"); + assert(fp_primary); + +- g_debug("fetching %s", name); +- if (repo_add_repomdxml(repo, fp_repomd, 0) || \ +- repo_add_rpmmd(repo, fp_primary, 0, 0)) { ++ g_debug("Loading repomd: %s", fn_repomd); ++ if (repo_add_repomdxml(repo, fp_repomd, 0)) { + g_set_error (error, + DNF_ERROR, + DNF_ERROR_INTERNAL_ERROR, +- _("repo_add_repomdxml/rpmmd() has failed.")); ++ _("Loading repomd has failed: %s"), ++ pool_errstr(repo->pool)); ++ retval = FALSE; ++ goto out; ++ } ++ ++ g_debug("Loading primary: %s", primary.c_str()); ++ if (repo_add_rpmmd(repo, fp_primary, 0, 0)) { ++ g_set_error (error, ++ DNF_ERROR, ++ DNF_ERROR_INTERNAL_ERROR, ++ _("Loading primary has failed: %s"), ++ pool_errstr(repo->pool)); + retval = FALSE; + goto out; + } +-- +2.31.1 + diff --git a/SOURCES/0033-libdnf-transaction-RPMItem-Fix-handling-transaction-.patch b/SOURCES/0033-libdnf-transaction-RPMItem-Fix-handling-transaction-.patch new file mode 100644 index 0000000..7398fc8 --- /dev/null +++ b/SOURCES/0033-libdnf-transaction-RPMItem-Fix-handling-transaction-.patch @@ -0,0 +1,74 @@ +From c5919efe898294420ec8e91e4eed5b9081e681c5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hr=C3=A1zk=C3=BD?= +Date: Thu, 17 Feb 2022 18:18:16 +0100 +Subject: [PATCH 33/34] libdnf/transaction/RPMItem: Fix handling transaction id + in resolveTransactionItemReason + +The maxTransactionId argument was ignored, the method was always returning the +reason from the last transaction. This is the correct result for +maxTransactionId = -1. In a couple of places the method is called with +maxTransactionId = -2. Fixing this would mean nontrivial changes to the +logic which could potentially break something else, so I'm leaving this +behavior unchanged. + +For non-negative values of maxTransactionId (with which it's not being called +anywhere in dnf codebase), the commit adds a condition to SELECT only +transaction ids less than or equal to maxTransactionId. + += changelog = +msg: Fix handling transaction id in resolveTransactionItemReason +type: bugfix +resolves: https://bugzilla.redhat.com/show_bug.cgi?id=2053014 +resolves: https://bugzilla.redhat.com/show_bug.cgi?id=2010259 +--- + libdnf/transaction/RPMItem.cpp | 21 ++++++++++++++++++--- + 1 file changed, 18 insertions(+), 3 deletions(-) + +diff --git a/libdnf/transaction/RPMItem.cpp b/libdnf/transaction/RPMItem.cpp +index 5f667ab9..ecce789d 100644 +--- a/libdnf/transaction/RPMItem.cpp ++++ b/libdnf/transaction/RPMItem.cpp +@@ -255,7 +255,11 @@ RPMItem::resolveTransactionItemReason(SQLite3Ptr conn, + const std::string &arch, + int64_t maxTransactionId) + { +- const char *sql = R"**( ++ // NOTE: All negative maxTransactionId values are treated the same. The ++ // method is called with maxTransactionId = -2 in a couple of places, the ++ // semantics here have been the same as with -1 for a long time. If it ++ // ain't broke... ++ std::string sql = R"**( + SELECT + ti.action as action, + ti.reason as reason +@@ -271,14 +275,25 @@ RPMItem::resolveTransactionItemReason(SQLite3Ptr conn, + AND ti.action not in (3, 5, 7, 10) + AND i.name = ? + AND i.arch = ? ++ )**"; ++ ++ if (maxTransactionId >= 0) { ++ sql.append(" AND ti.trans_id <= ?"); ++ } ++ ++ sql.append(R"**( + ORDER BY + ti.trans_id DESC + LIMIT 1 +- )**"; ++ )**"); + + if (arch != "") { + SQLite3::Query query(*conn, sql); +- query.bindv(name, arch); ++ if (maxTransactionId >= 0) { ++ query.bindv(name, arch, maxTransactionId); ++ } else { ++ query.bindv(name, arch); ++ } + + if (query.step() == SQLite3::Statement::StepResult::ROW) { + auto action = static_cast< TransactionItemAction >(query.get< int64_t >("action")); +-- +2.31.1 + diff --git a/SOURCES/0034-libdnf-transaction-TransactionItem-Set-short-action-.patch b/SOURCES/0034-libdnf-transaction-TransactionItem-Set-short-action-.patch new file mode 100644 index 0000000..c7290c1 --- /dev/null +++ b/SOURCES/0034-libdnf-transaction-TransactionItem-Set-short-action-.patch @@ -0,0 +1,33 @@ +From c303b7c3723f3e9fbc43963a62237ea17516fc6b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hr=C3=A1zk=C3=BD?= +Date: Thu, 17 Feb 2022 18:30:14 +0100 +Subject: [PATCH 34/34] libdnf/transaction/TransactionItem: Set short action + for Reason Change + +Sets the "short" (one letter) representation of the Reason Change action +to "C". + +This was likely not ever used before as the only way to create a +transaction with a reason change and something else is rolling back +multiple transactions, which was broken. +--- + libdnf/transaction/TransactionItem.cpp | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/libdnf/transaction/TransactionItem.cpp b/libdnf/transaction/TransactionItem.cpp +index 3b43d1f1..4358038e 100644 +--- a/libdnf/transaction/TransactionItem.cpp ++++ b/libdnf/transaction/TransactionItem.cpp +@@ -51,8 +51,7 @@ static const std::map< TransactionItemAction, std::string > transactionItemActio + {TransactionItemAction::REMOVE, "E"}, + {TransactionItemAction::REINSTALL, "R"}, + {TransactionItemAction::REINSTALLED, "R"}, +- // TODO: replace "?" with something better +- {TransactionItemAction::REASON_CHANGE, "?"}, ++ {TransactionItemAction::REASON_CHANGE, "C"}, + }; + + /* +-- +2.31.1 + diff --git a/SOURCES/0035-Do-not-print-errors-on-failovermethod-repo-option-Rh.patch b/SOURCES/0035-Do-not-print-errors-on-failovermethod-repo-option-Rh.patch new file mode 100644 index 0000000..b9ff0c3 --- /dev/null +++ b/SOURCES/0035-Do-not-print-errors-on-failovermethod-repo-option-Rh.patch @@ -0,0 +1,45 @@ +From c4ee580c73375060b6eb5b3414636688e3d601c3 Mon Sep 17 00:00:00 2001 +From: Marek Blaha +Date: Fri, 10 Jun 2022 15:29:56 +0200 +Subject: [PATCH] Do not print errors on failovermethod repo option + (RhBug:2039906) + += changelog = +msg: Do not print errors if repository config contains failovermethod option +type: bugfix +resolves: https://bugzilla.redhat.com/show_bug.cgi?id=2039906 +--- + libdnf/conf/ConfigRepo.cpp | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/libdnf/conf/ConfigRepo.cpp b/libdnf/conf/ConfigRepo.cpp +index e98ac0af..0cb52f58 100644 +--- a/libdnf/conf/ConfigRepo.cpp ++++ b/libdnf/conf/ConfigRepo.cpp +@@ -22,6 +22,8 @@ + #include "Const.hpp" + #include "Config-private.hpp" + ++#include "bgettext/bgettext-lib.h" ++ + namespace libdnf { + + class ConfigRepo::Impl { +@@ -174,6 +176,14 @@ ConfigRepo::Impl::Impl(Config & owner, ConfigMain & mainConfig) + owner.optBinds().add("enabled_metadata", enabled_metadata); + owner.optBinds().add("user_agent", user_agent); + owner.optBinds().add("countme", countme); ++ owner.optBinds().add("failovermethod", failovermethod, ++ [&](Option::Priority priority, const std::string & value){ ++ if (value != "priority") { ++ throw Option::InvalidValue(_("only the value 'priority' is supported.")); ++ } ++ failovermethod.set(priority, value); ++ }, nullptr, false ++ ); + owner.optBinds().add("sslverifystatus", sslverifystatus); + } + +-- +2.36.1 + diff --git a/SOURCES/0036-sack-query.hpp-Add-a-missing-include.patch b/SOURCES/0036-sack-query.hpp-Add-a-missing-include.patch new file mode 100644 index 0000000..1e3c5c0 --- /dev/null +++ b/SOURCES/0036-sack-query.hpp-Add-a-missing-include.patch @@ -0,0 +1,24 @@ +From 9dbd5f8f0ac3d6d3fab9147a3208623cba698682 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hr=C3=A1zk=C3=BD?= +Date: Tue, 14 Jun 2022 17:26:44 +0200 +Subject: [PATCH] sack/query.hpp: Add a missing include + +--- + libdnf/sack/query.hpp | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/libdnf/sack/query.hpp b/libdnf/sack/query.hpp +index 9e49761c..306b24e3 100644 +--- a/libdnf/sack/query.hpp ++++ b/libdnf/sack/query.hpp +@@ -26,6 +26,7 @@ + #include "../hy-types.h" + #include "../hy-query.h" + #include "../hy-subject.h" ++#include "../nevra.hpp" + #include "../repo/solvable/Dependency.hpp" + #include "../repo/solvable/DependencyContainer.hpp" + #include "../transaction/Swdb.hpp" +-- +2.36.1 + diff --git a/SOURCES/0037-context-dnf_context_remove-accepts-package-spec-as-d.patch b/SOURCES/0037-context-dnf_context_remove-accepts-package-spec-as-d.patch new file mode 100644 index 0000000..5c67e98 --- /dev/null +++ b/SOURCES/0037-context-dnf_context_remove-accepts-package-spec-as-d.patch @@ -0,0 +1,128 @@ +From 876393d5d0cd5f806415dcdc90168e58e66da916 Mon Sep 17 00:00:00 2001 +From: Jaroslav Rohel +Date: Mon, 28 Mar 2022 07:29:48 +0200 +Subject: [PATCH] context: dnf_context_remove accepts `` as dnf, + unify code + +Prior to change, the `dnf_context_remove` function only accepted +the package name (without globs). It was not possible to enter more detailed +specifications and thus, for example, select a specific version of the package +to uninstall - for example, which kernel we want to uninstall. +This patch adds full `` support as in dnf, including support +for globs (wildcards) and searching against 'provides' and 'file provides'. + +Better error handling for `hy_goal_upgrade_selector` in` dnf_context_update`. + +Unification of the function code `dnf_context_install`, `dnf_context_remove`, +`dnf_context_update`. + += changelog = +msg: context: Support (NEVRA forms, provides, file provides) including globs in the dnf_context_remove func +type: enhancement +resolves: https://bugzilla.redhat.com/show_bug.cgi?id=2084602 +--- + libdnf/dnf-context.cpp | 46 ++++++++++++++++++++++++------------------ + 1 file changed, 26 insertions(+), 20 deletions(-) + +diff --git a/libdnf/dnf-context.cpp b/libdnf/dnf-context.cpp +index 6cb0011b..4b055f03 100644 +--- a/libdnf/dnf-context.cpp ++++ b/libdnf/dnf-context.cpp +@@ -2391,10 +2391,9 @@ dnf_context_run(DnfContext *context, GCancellable *cancellable, GError **error) + * Since: 0.1.0 + **/ + gboolean +-dnf_context_install (DnfContext *context, const gchar *name, GError **error) try ++dnf_context_install(DnfContext *context, const gchar *name, GError **error) try + { + DnfContextPrivate *priv = GET_PRIVATE (context); +- g_autoptr(GPtrArray) selector_matches = NULL; + + /* create sack and add sources */ + if (priv->sack == NULL) { +@@ -2405,7 +2404,7 @@ dnf_context_install (DnfContext *context, const gchar *name, GError **error) try + + g_auto(HySubject) subject = hy_subject_create(name); + g_auto(HySelector) selector = hy_subject_get_best_selector(subject, priv->sack, NULL, FALSE, NULL); +- selector_matches = hy_selector_matches(selector); ++ g_autoptr(GPtrArray) selector_matches = hy_selector_matches(selector); + if (selector_matches->len == 0) { + g_set_error(error, + DNF_ERROR, +@@ -2438,31 +2437,33 @@ gboolean + dnf_context_remove(DnfContext *context, const gchar *name, GError **error) try + { + DnfContextPrivate *priv = GET_PRIVATE(context); +- GPtrArray *pkglist; +- hy_autoquery HyQuery query = NULL; +- gboolean ret = TRUE; +- guint i; + + /* create sack and add repos */ + if (priv->sack == NULL) { + dnf_state_reset(priv->state); +- ret = dnf_context_setup_sack(context, priv->state, error); +- if (!ret) ++ if (!dnf_context_setup_sack(context, priv->state, error)) + return FALSE; + } + +- /* find installed packages to remove */ +- query = hy_query_create(priv->sack); +- query->installed(); +- hy_query_filter(query, HY_PKG_NAME, HY_EQ, name); +- pkglist = hy_query_run(query); ++ libdnf::Query query(priv->sack, libdnf::Query::ExcludeFlags::APPLY_EXCLUDES); ++ query.installed(); ++ auto ret = query.filterSubject(name, nullptr, false, true, true, true); ++ if (!ret.first) { ++ g_set_error(error, ++ DNF_ERROR, ++ DNF_ERROR_PACKAGE_NOT_FOUND, ++ "No installed package matches '%s'", name); ++ return FALSE; ++ } ++ ++ g_autoptr(GPtrArray) packages = query.run(); + + /* add each package */ +- for (i = 0; i < pkglist->len; i++) { +- auto pkg = static_cast(g_ptr_array_index(pkglist, i)); ++ for (guint i = 0; i < packages->len; i++) { ++ auto pkg = static_cast(g_ptr_array_index(packages, i)); + hy_goal_erase(priv->goal, pkg); + } +- g_ptr_array_unref(pkglist); ++ + return TRUE; + } CATCH_TO_GERROR(FALSE) + +@@ -2493,8 +2494,7 @@ dnf_context_update(DnfContext *context, const gchar *name, GError **error) try + } + + g_auto(HySubject) subject = hy_subject_create(name); +- g_auto(HySelector) selector = hy_subject_get_best_selector(subject, priv->sack, NULL, FALSE, +- NULL); ++ g_auto(HySelector) selector = hy_subject_get_best_selector(subject, priv->sack, NULL, FALSE, NULL); + g_autoptr(GPtrArray) selector_matches = hy_selector_matches(selector); + if (selector_matches->len == 0) { + g_set_error(error, +@@ -2504,8 +2504,14 @@ dnf_context_update(DnfContext *context, const gchar *name, GError **error) try + return FALSE; + } + +- if (hy_goal_upgrade_selector(priv->goal, selector)) ++ int ret = hy_goal_upgrade_selector(priv->goal, selector); ++ if (ret != 0) { ++ g_set_error(error, ++ DNF_ERROR, ++ ret, ++ "Ill-formed Selector '%s'", name); + return FALSE; ++ } + + return TRUE; + } CATCH_TO_GERROR(FALSE) +-- +2.36.1 + diff --git a/SOURCES/0038-context-Fix-doc-dnf_context_install-remove-update-di.patch b/SOURCES/0038-context-Fix-doc-dnf_context_install-remove-update-di.patch new file mode 100644 index 0000000..379a770 --- /dev/null +++ b/SOURCES/0038-context-Fix-doc-dnf_context_install-remove-update-di.patch @@ -0,0 +1,62 @@ +From 44d75a36d7c8a933119e5b63f180a8c23715ec51 Mon Sep 17 00:00:00 2001 +From: Jaroslav Rohel +Date: Mon, 28 Mar 2022 07:51:45 +0200 +Subject: [PATCH] context: Fix doc dnf_context_install/remove/update/distrosync + +Functions do not support groups - only packages are supported. + +The `dnf_context_remove` function marks all matching packages for removal +- not just the oldest one. +--- + libdnf/dnf-context.cpp | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +diff --git a/libdnf/dnf-context.cpp b/libdnf/dnf-context.cpp +index 4b055f03..fe005430 100644 +--- a/libdnf/dnf-context.cpp ++++ b/libdnf/dnf-context.cpp +@@ -2379,7 +2379,7 @@ dnf_context_run(DnfContext *context, GCancellable *cancellable, GError **error) + /** + * dnf_context_install: + * @context: a #DnfContext instance. +- * @name: A package or group name, e.g. "firefox" or "@gnome-desktop" ++ * @name: A package specification (NEVRA forms, provide, file provide, globs supported) e.g. "firefox" + * @error: A #GError or %NULL + * + * Finds a remote package and marks it to be installed. +@@ -2422,12 +2422,12 @@ dnf_context_install(DnfContext *context, const gchar *name, GError **error) try + /** + * dnf_context_remove: + * @context: a #DnfContext instance. +- * @name: A package or group name, e.g. "firefox" or "@gnome-desktop" ++ * @name: A package specification (NEVRA forms, provide, file provide, globs supported) e.g. "firefox" + * @error: A #GError or %NULL + * + * Finds an installed package and marks it to be removed. + * +- * If multiple packages are available then only the oldest package is removed. ++ * If multiple packages are available, all of them will be removed. + * + * Returns: %TRUE for success, %FALSE otherwise + * +@@ -2470,7 +2470,7 @@ dnf_context_remove(DnfContext *context, const gchar *name, GError **error) try + /** + * dnf_context_update: + * @context: a #DnfContext instance. +- * @name: A package or group name, e.g. "firefox" or "@gnome-desktop" ++ * @name: A package specification (NEVRA forms, provide, file provide, globs supported) e.g. "firefox" + * @error: A #GError or %NULL + * + * Finds an installed and remote package and marks it to be updated. +@@ -2548,7 +2548,7 @@ dnf_context_update_all (DnfContext *context, + /** + * dnf_context_distrosync: + * @context: a #DnfContext instance. +- * @name: A package or group name, e.g. "firefox" or "@gnome-desktop" ++ * @name: A package specification (NEVRA forms, provide, file provide, globs supported) e.g. "firefox" + * @error: A #GError or %NULL + * + * Finds an installed and remote package and marks it to be synchronized with remote version. +-- +2.36.1 + diff --git a/SOURCES/0039-advisory-upgrade-filter-out-advPkgs-with-different-a.patch b/SOURCES/0039-advisory-upgrade-filter-out-advPkgs-with-different-a.patch new file mode 100644 index 0000000..ad93526 --- /dev/null +++ b/SOURCES/0039-advisory-upgrade-filter-out-advPkgs-with-different-a.patch @@ -0,0 +1,100 @@ +From cf4893a0128c567ed1fdd1b02c9cf2b43bfb02f7 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= +Date: Mon, 30 May 2022 08:59:41 +0200 +Subject: [PATCH] advisory upgrade: filter out advPkgs with different arch + +This prevents a situation in security upgrades where libsolv cannot +upgrade dependent pkgs because we ask for an upgrade of different arch: + +We can get the following testcase if libdnf has filtered out +json-c-2-2.el8.x86_64@rhel-8-for-x86_64-baseos-rpms +(because there is an advisory for already installed json-c-1-1.el8.x86_64) but +json-c-2-2.el8.i686@rhel-8-for-x86_64-baseos-rpms is not filtered out because +it has different architecture. The resulting transaction doesn't work. + +``` +repo @System -99.-1000 testtags +#>=Pkg: bind-libs-lite 1 1.el8 x86_64 +#>=Pkg: json-c 1 1.el8 x86_64 + +repo rhel-8-for-x86_64-baseos-rpms -99.-1000 testtags +#>=Pkg: json-c 2 2.el8 x86_64 +#>=Prv: libjson-c.so.4()(64bit) +#> +#>=Pkg: json-c 2 2.el8 i686 +#>=Prv: libjson-c.so.4() +#> +#>=Pkg: bind-libs-lite 2 2.el8 x86_64 +#>=Req: libjson-c.so.4()(64bit) +system x86_64 rpm @System +job update oneof json-c-1-1.el8.x86_64@@System json-c-2-2.el8.i686@rhel-8-for-x86_64-baseos-rpms bind-libs-lite-2-2.el8.x86_64@rhel-8-for-x86_64-baseos-rpms [forcebest,targeted,setevr,setarch] +result transaction,problems +#>problem f06d81a4 info package bind-libs-lite-2-2.el8.x86_64 requires libjson-c.so.4()(64bit), but none of the providers can be installed +#>problem f06d81a4 solution 96f9031b allow bind-libs-lite-1-1.el8.x86_64@@System +#>problem f06d81a4 solution c8daf94f allow json-c-2-2.el8.x86_64@rhel-8-for-x86_64-baseos-rpms +#>upgrade bind-libs-lite-1-1.el8.x86_64@@System bind-libs-lite-2-2.el8.x86_64@rhel-8-for-x86_64-baseos-rpms +#>upgrade json-c-1-1.el8.x86_64@@System json-c-2-2.el8.x86_64@rhel-8-for-x86_64-baseos-rpms``` +``` + += changelog = +msg: Filter out advisory pkgs with different arch during advisory upgrade, fixes possible problems in dependency resulution. +type: bugfix +resolves: https://bugzilla.redhat.com/show_bug.cgi?id=2088149 +--- + libdnf/sack/query.cpp | 25 +++++++++++++++++++------ + 1 file changed, 19 insertions(+), 6 deletions(-) + +diff --git a/libdnf/sack/query.cpp b/libdnf/sack/query.cpp +index ac2736b5..03d39659 100644 +--- a/libdnf/sack/query.cpp ++++ b/libdnf/sack/query.cpp +@@ -1877,12 +1877,6 @@ Query::Impl::filterAdvisory(const Filter & f, Map *m, int keyname) + std::vector candidates; + std::vector installed_solvables; + +- Id id = -1; +- while ((id = resultPset->next(id)) != -1) { +- candidates.push_back(pool_id2solvable(pool, id)); +- } +- NameArchEVRComparator cmp_key(pool); +- + if (cmp_type & HY_UPGRADE) { + Query installed(sack, ExcludeFlags::IGNORE_EXCLUDES); + installed.installed(); +@@ -1893,6 +1887,18 @@ Query::Impl::filterAdvisory(const Filter & f, Map *m, int keyname) + installed_solvables.push_back(pool_id2solvable(pool, installed_id)); + } + std::sort(installed_solvables.begin(), installed_solvables.end(), NameArchSolvableComparator); ++ Id id = -1; ++ while ((id = resultPset->next(id)) != -1) { ++ Solvable * s = pool_id2solvable(pool, id); ++ // When doing HY_UPGRADE consider only candidate pkgs that have matching Name and Arch ++ // with some already installed pkg (in other words: some other version of the pkg is already installed). ++ // Otherwise a pkg with different Arch than installed can end up in upgrade set which is wrong. ++ // It can result in dependency issues, reported as: RhBug:2088149. ++ auto low = std::lower_bound(installed_solvables.begin(), installed_solvables.end(), s, NameArchSolvableComparator); ++ if (low != installed_solvables.end() && s->name == (*low)->name && s->arch == (*low)->arch) { ++ candidates.push_back(s); ++ } ++ } + + // Apply security filters only to packages with lower priority - to unify behaviour upgrade + // and upgrade-minimal +@@ -1915,7 +1921,14 @@ Query::Impl::filterAdvisory(const Filter & f, Map *m, int keyname) + } + } + std::swap(candidates, priority_candidates); ++ } else { ++ Id id = -1; ++ while ((id = resultPset->next(id)) != -1) { ++ candidates.push_back(pool_id2solvable(pool, id)); ++ } + } ++ ++ NameArchEVRComparator cmp_key(pool); + std::sort(candidates.begin(), candidates.end(), cmp_key); + for (auto & advisoryPkg : pkgs) { + if (cmp_type & HY_UPGRADE) { +-- +2.36.1 + diff --git a/SOURCES/0040-Add-obsoletes-to-filtering-for-advisory-candidates.patch b/SOURCES/0040-Add-obsoletes-to-filtering-for-advisory-candidates.patch new file mode 100644 index 0000000..e3f7167 --- /dev/null +++ b/SOURCES/0040-Add-obsoletes-to-filtering-for-advisory-candidates.patch @@ -0,0 +1,71 @@ +From 652977360c4253faff9e95d35c603b2f585671fe Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= +Date: Tue, 5 Jul 2022 09:02:22 +0200 +Subject: [PATCH] Add obsoletes to filtering for advisory candidates + +Patch https://github.com/rpm-software-management/libdnf/pull/1526 +introduced a regression where we no longer do a security upgrade if a +package A is installed and package B obsoletes A and B is available in two +versions while there is an advisory for the second version. + +Test: https://github.com/rpm-software-management/ci-dnf-stack/pull/1130 +--- + libdnf/sack/query.cpp | 32 ++++++++++++++++++++++++++++---- + 1 file changed, 28 insertions(+), 4 deletions(-) + +diff --git a/libdnf/sack/query.cpp b/libdnf/sack/query.cpp +index 03d39659..5355f9f7 100644 +--- a/libdnf/sack/query.cpp ++++ b/libdnf/sack/query.cpp +@@ -1878,6 +1878,13 @@ Query::Impl::filterAdvisory(const Filter & f, Map *m, int keyname) + std::vector installed_solvables; + + if (cmp_type & HY_UPGRADE) { ++ // When doing HY_UPGRADE consider only candidate pkgs that have matching Name and Arch with: ++ // * some already installed pkg (in other words: some other version of the pkg is already installed) ++ // or ++ // * with pkg that obsoletes some already installed (or to be installed in this transaction) pkg ++ // Otherwise a pkg with different Arch than installed can end up in upgrade set which is wrong. ++ // It can result in dependency issues, reported as: RhBug:2088149. ++ + Query installed(sack, ExcludeFlags::IGNORE_EXCLUDES); + installed.installed(); + installed.addFilter(HY_PKG_LATEST_PER_ARCH, HY_EQ, 1); +@@ -1887,13 +1894,30 @@ Query::Impl::filterAdvisory(const Filter & f, Map *m, int keyname) + installed_solvables.push_back(pool_id2solvable(pool, installed_id)); + } + std::sort(installed_solvables.begin(), installed_solvables.end(), NameArchSolvableComparator); ++ ++ Query obsoletes(sack, ExcludeFlags::IGNORE_EXCLUDES); ++ obsoletes.addFilter(HY_PKG, HY_EQ, resultPset); ++ obsoletes.available(); ++ ++ Query possibly_obsoleted(sack, ExcludeFlags::IGNORE_EXCLUDES); ++ possibly_obsoleted.addFilter(HY_PKG, HY_EQ, resultPset); ++ possibly_obsoleted.addFilter(HY_PKG_UPGRADES, HY_EQ, 1); ++ possibly_obsoleted.queryUnion(installed); ++ possibly_obsoleted.apply(); ++ ++ obsoletes.addFilter(HY_PKG_OBSOLETES, HY_EQ, possibly_obsoleted.runSet()); ++ obsoletes.apply(); ++ Id obsoleted_id = -1; ++ // Add to candidates resultPset pkgs that obsolete some installed (or to be installed in this transaction) pkg ++ while ((obsoleted_id = obsoletes.pImpl->result->next(obsoleted_id)) != -1) { ++ Solvable * s = pool_id2solvable(pool, obsoleted_id); ++ candidates.push_back(s); ++ } ++ + Id id = -1; ++ // Add to candidates resultPset pkgs that match name and arch with some already installed pkg + while ((id = resultPset->next(id)) != -1) { + Solvable * s = pool_id2solvable(pool, id); +- // When doing HY_UPGRADE consider only candidate pkgs that have matching Name and Arch +- // with some already installed pkg (in other words: some other version of the pkg is already installed). +- // Otherwise a pkg with different Arch than installed can end up in upgrade set which is wrong. +- // It can result in dependency issues, reported as: RhBug:2088149. + auto low = std::lower_bound(installed_solvables.begin(), installed_solvables.end(), s, NameArchSolvableComparator); + if (low != installed_solvables.end() && s->name == (*low)->name && s->arch == (*low)->arch) { + candidates.push_back(s); +-- +2.36.1 + diff --git a/SPECS/libdnf.spec b/SPECS/libdnf.spec index 131c72b..37cde22 100644 --- a/SPECS/libdnf.spec +++ b/SPECS/libdnf.spec @@ -1,4 +1,4 @@ -%global libsolv_version 0.7.17 +%global libsolv_version 0.7.20-3 %global libmodulemd_version 2.11.2-2 %global librepo_version 1.13.1 %global dnf_conflict 4.3.0 @@ -56,7 +56,7 @@ Name: libdnf Version: %{libdnf_major_version}.%{libdnf_minor_version}.%{libdnf_micro_version} -Release: 7%{?dist} +Release: 11%{?dist} Summary: Library providing simplified C and Python API to libsolv License: LGPLv2+ URL: https://github.com/rpm-software-management/libdnf @@ -86,6 +86,22 @@ Patch22: 0022-hawkey-surrogateescape-error-handler-to-decode-UTF-8-string Patch23: 0023-Turn-off-strict-validation-of-modulemd-documents-RhBug200485320071662007167.patch Patch24: 0024-Add-unittest-for-setting-up-repo-with-empty-keyfile-RhBug1994614.patch Patch25: 0025-Add-getLatestModules.patch +Patch26: 0026-context-Substitute-all-repository-config-options-RhB.patch +Patch27: 0027-Use-environment-variable-in-unittest-instead-of-ugly.patch +Patch28: 0028-Add-private-API-for-filling-reading-and-verifying-ne.patch +Patch29: 0029-Use-dnf-solv-userdata-to-check-versions-and-checksum.patch +Patch30: 0030-Update-unittest-to-test-the-new-private-dnf-solvfile.patch +Patch31: 0031-Increase-required-libsolv-version-for-cache-versioni.patch +Patch32: 0032-Add-more-specific-error-handling-for-loading-repomd-.patch +Patch33: 0033-libdnf-transaction-RPMItem-Fix-handling-transaction-.patch +Patch34: 0034-libdnf-transaction-TransactionItem-Set-short-action-.patch +Patch35: 0035-Do-not-print-errors-on-failovermethod-repo-option-Rh.patch +Patch36: 0036-sack-query.hpp-Add-a-missing-include.patch +Patch37: 0037-context-dnf_context_remove-accepts-package-spec-as-d.patch +Patch38: 0038-context-Fix-doc-dnf_context_install-remove-update-di.patch +Patch39: 0039-advisory-upgrade-filter-out-advPkgs-with-different-a.patch +Patch40: 0040-Add-obsoletes-to-filtering-for-advisory-candidates.patch + BuildRequires: cmake BuildRequires: gcc @@ -330,6 +346,20 @@ popd %endif %changelog +* Thu Jul 21 2022 Lukas Hrazky - 0.63.0-11 +- Add obsoletes to filtering for advisory candidates + +* Tue Jun 14 2022 Lukas Hrazky - 0.63.0-10 +- Do not print errors on failovermethod repo option +- the dnf_context_remove() function accepts ``, doc updates +- advisory upgrade: filter out advPkgs with different arch + +* Wed May 04 2022 Lukas Hrazky - 0.63.0-8 +- Substitute all repository config options (fixes substitution of baseurl) +- Use solvfile userdata to store and check checksums and solv versions +- Fix handling transaction id in resolveTransactionItemReason +- Set short action for Reason Change + * Fri Jan 14 2022 Pavla Kratochvilova - 0.63.0-7 - Rebuild with new release number