/* * Copyright (C) 2015 Red Hat, Inc. * * This program 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. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #include #include #include "rpmostree-types.h" #include "rpmostreed-deployment-utils.h" #include "rpmostree-origin.h" #include "rpmostree-core.h" #include "rpmostree-util.h" #include "rpmostree-rpm-util.h" #include "rpmostree-sysroot-core.h" #include "rpmostree-core.h" #include "rpmostree-package-variants.h" #include "rpmostreed-utils.h" #include "rpmostreed-errors.h" /* Get a currently unique (for this host) identifier for the * deployment; TODO - adding the deployment timestamp would make it * persistently unique, needs API in libostree. */ char * rpmostreed_deployment_generate_id (OstreeDeployment *deployment) { g_return_val_if_fail (OSTREE_IS_DEPLOYMENT (deployment), NULL); return g_strdup_printf ("%s-%s.%u", ostree_deployment_get_osname (deployment), ostree_deployment_get_csum (deployment), ostree_deployment_get_deployserial (deployment)); } OstreeDeployment * rpmostreed_deployment_get_for_id (OstreeSysroot *sysroot, const gchar *deploy_id) { g_autoptr(GPtrArray) deployments = ostree_sysroot_get_deployments (sysroot); for (guint i = 0; i < deployments->len; i++) { g_autofree gchar *id = rpmostreed_deployment_generate_id (deployments->pdata[i]); if (g_strcmp0 (deploy_id, id) == 0) return g_object_ref (deployments->pdata[i]); } return NULL; } /* rpmostreed_deployment_get_for_index: * * sysroot: A #OstreeSysroot instance * index: string index being parsed * error: a #GError instance * * Get a deployment based on a string index, * the string is parsed and checked. Then * the deployment at the parsed index will be * returned. * * returns: NULL if an error is being made */ OstreeDeployment * rpmostreed_deployment_get_for_index (OstreeSysroot *sysroot, const gchar *index, GError **error) { g_autoptr(GError) local_error = NULL; int deployment_index = -1; for (int counter = 0; counter < strlen(index); counter++) { if (!g_ascii_isdigit (index[counter])) { local_error = g_error_new (RPM_OSTREED_ERROR, RPM_OSTREED_ERROR_FAILED, "Invalid deployment index %s, must be a number and >= 0", index); g_propagate_error (error, g_steal_pointer (&local_error)); return NULL; } } deployment_index = atoi (index); g_autoptr(GPtrArray) deployments = ostree_sysroot_get_deployments (sysroot); if (deployment_index >= deployments->len) { local_error = g_error_new (RPM_OSTREED_ERROR, RPM_OSTREED_ERROR_FAILED, "Out of range deployment index %d, expected < %d", deployment_index, deployments->len); g_propagate_error (error, g_steal_pointer (&local_error)); return NULL; } return g_object_ref (deployments->pdata[deployment_index]); } static gboolean variant_add_remote_status (OstreeRepo *repo, const gchar *origin_refspec, const gchar *checksum, GVariantDict *dict, GError **error) { GLNX_AUTO_PREFIX_ERROR ("Loading origin status", error); g_autofree gchar *remote = NULL; if (!ostree_parse_refspec (origin_refspec, &remote, NULL, error)) return FALSE; g_autoptr(GError) local_error = NULL; gboolean gpg_verify = FALSE; if (remote) { if (!ostree_repo_remote_get_gpg_verify (repo, remote, &gpg_verify, &local_error)) { /* If the remote doesn't exist, let's note that so that status can * render it specially. */ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_variant_dict_insert (dict, "remote-error", "s", local_error->message); return TRUE; } g_propagate_error (error, g_steal_pointer (&local_error)); return FALSE; } } g_variant_dict_insert (dict, "gpg-enabled", "b", gpg_verify); if (!gpg_verify) return TRUE; /* Note early return; no need to verify signatures! */ g_autoptr(OstreeGpgVerifyResult) verify_result = ostree_repo_verify_commit_for_remote (repo, checksum, remote, NULL, NULL); if (!verify_result) { /* Somehow, we have a deployment which has gpg-verify=true, but *doesn't* have a valid * signature. Let's not just bomb out here. We need to return this in the variant so * that `status` can show the appropriate msg. */ return TRUE; } g_auto(GVariantBuilder) builder; g_variant_builder_init (&builder, G_VARIANT_TYPE ("av")); guint n_sigs = ostree_gpg_verify_result_count_all (verify_result); for (guint i = 0; i < n_sigs; i++) g_variant_builder_add (&builder, "v", ostree_gpg_verify_result_get_all (verify_result, i)); g_variant_dict_insert_value (dict, "signatures", g_variant_builder_end (&builder)); return TRUE; } GVariant * rpmostreed_deployment_generate_blank_variant (void) { GVariantDict dict; g_variant_dict_init (&dict, NULL); return g_variant_dict_end (&dict); } static void variant_add_metadata_attribute (GVariantDict *dict, const gchar *attribute, const gchar *new_attribute, GVariant *commit) { g_autofree gchar *attribute_string_value = NULL; g_autoptr(GVariant) metadata = g_variant_get_child_value (commit, 0); if (metadata != NULL) { g_variant_lookup (metadata, attribute, "s", &attribute_string_value); if (attribute_string_value != NULL) g_variant_dict_insert (dict, new_attribute ?: attribute, "s", attribute_string_value); } } static void variant_add_commit_details (GVariantDict *dict, const char *prefix, GVariant *commit) { g_autoptr(GVariant) metadata = NULL; g_autofree gchar *version_commit = NULL; guint64 timestamp = 0; timestamp = ostree_commit_get_timestamp (commit); metadata = g_variant_get_child_value (commit, 0); if (metadata != NULL) g_variant_lookup (metadata, "version", "s", &version_commit); if (version_commit != NULL) g_variant_dict_insert (dict, glnx_strjoina (prefix ?: "", "version"), "s", version_commit); if (timestamp > 0) g_variant_dict_insert (dict, glnx_strjoina (prefix ?: "", "timestamp"), "t", timestamp); } static void variant_add_from_hash_table (GVariantDict *dict, const char *key, GHashTable *table) { g_autofree char **values = (char**)g_hash_table_get_keys_as_array (table, NULL); g_variant_dict_insert (dict, key, "^as", values); } GVariant * rpmostreed_deployment_generate_variant (OstreeSysroot *sysroot, OstreeDeployment *deployment, const char *booted_id, OstreeRepo *repo, GError **error) { const gchar *osname = ostree_deployment_get_osname (deployment); const gchar *csum = ostree_deployment_get_csum (deployment); gint serial = ostree_deployment_get_deployserial (deployment); g_autoptr(GVariant) commit = NULL; if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, csum, &commit, error)) return NULL; g_autofree gchar *id = rpmostreed_deployment_generate_id (deployment); g_autoptr(RpmOstreeOrigin) origin = rpmostree_origin_parse_deployment (deployment, error); if (!origin) return NULL; RpmOstreeRefspecType refspec_type; g_autofree char *refspec = rpmostree_origin_get_full_refspec (origin, &refspec_type); GVariantDict dict; g_variant_dict_init (&dict, NULL); g_variant_dict_insert (&dict, "id", "s", id); if (osname != NULL) g_variant_dict_insert (&dict, "osname", "s", osname); g_variant_dict_insert (&dict, "serial", "i", serial); g_variant_dict_insert (&dict, "checksum", "s", csum); gboolean is_layered = FALSE; g_autofree char *base_checksum = NULL; g_auto(GStrv) layered_pkgs = NULL; g_autoptr(GVariant) removed_base_pkgs = NULL; g_autoptr(GVariant) replaced_base_pkgs = NULL; if (!rpmostree_deployment_get_layered_info (repo, deployment, &is_layered, &base_checksum, &layered_pkgs, &removed_base_pkgs, &replaced_base_pkgs, error)) return NULL; g_autoptr(GVariant) base_commit = NULL; if (is_layered) { if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, base_checksum, &base_commit, error)) return NULL; g_variant_dict_insert (&dict, "base-checksum", "s", base_checksum); variant_add_commit_details (&dict, "base-", base_commit); /* for layered commits, check if their base commit has end of life attribute */ variant_add_metadata_attribute (&dict, OSTREE_COMMIT_META_KEY_ENDOFLIFE, "endoflife", base_commit); /* See below for base commit metadata */ g_autoptr(GVariant) layered_metadata = g_variant_get_child_value (commit, 0); g_variant_dict_insert (&dict, "layered-commit-meta", "@a{sv}", layered_metadata); } else { base_commit = g_variant_ref (commit); base_checksum = g_strdup (csum); variant_add_metadata_attribute (&dict, OSTREE_COMMIT_META_KEY_ENDOFLIFE, "endoflife", commit); } /* We used to bridge individual keys, but that was annoying; just pass through all * of the commit metadata. */ { g_autoptr(GVariant) base_meta = g_variant_get_child_value (base_commit, 0); g_variant_dict_insert (&dict, "base-commit-meta", "@a{sv}", base_meta); } variant_add_commit_details (&dict, NULL, commit); switch (refspec_type) { case RPMOSTREE_REFSPEC_TYPE_CHECKSUM: { g_autofree char *custom_origin_url = NULL; g_autofree char *custom_origin_description = NULL; rpmostree_origin_get_custom_description (origin, &custom_origin_url, &custom_origin_description); if (custom_origin_url) g_variant_dict_insert (&dict, "custom-origin", "(ss)", custom_origin_url, custom_origin_description); } break; case RPMOSTREE_REFSPEC_TYPE_OSTREE: { if (!variant_add_remote_status (repo, refspec, base_checksum, &dict, error)) return NULL; g_autofree char *pending_base_commitrev = NULL; if (!ostree_repo_resolve_rev (repo, refspec, TRUE, &pending_base_commitrev, error)) return NULL; if (pending_base_commitrev && !g_str_equal (pending_base_commitrev, base_checksum)) { g_autoptr(GVariant) pending_base_commit = NULL; if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, pending_base_commitrev, &pending_base_commit, error)) return NULL; g_variant_dict_insert (&dict, "pending-base-checksum", "s", pending_base_commitrev); variant_add_commit_details (&dict, "pending-base-", pending_base_commit); } } break; case RPMOSTREE_REFSPEC_TYPE_ROJIG: { g_variant_dict_insert (&dict, "rojig-description", "@a{sv}", rpmostree_origin_get_rojig_description (origin)); } break; } g_autofree char *live_inprogress = NULL; g_autofree char *live_replaced = NULL; if (!rpmostree_syscore_deployment_get_live (sysroot, deployment, &live_inprogress, &live_replaced, error)) return NULL; if (live_inprogress) g_variant_dict_insert (&dict, "live-inprogress", "s", live_inprogress); if (live_replaced) g_variant_dict_insert (&dict, "live-replaced", "s", live_replaced); if (ostree_deployment_is_staged (deployment)) g_variant_dict_insert (&dict, "staged", "b", TRUE); if (refspec) g_variant_dict_insert (&dict, "origin", "s", refspec); variant_add_from_hash_table (&dict, "requested-packages", rpmostree_origin_get_packages (origin)); variant_add_from_hash_table (&dict, "requested-local-packages", rpmostree_origin_get_local_packages (origin)); variant_add_from_hash_table (&dict, "requested-base-removals", rpmostree_origin_get_overrides_remove (origin)); variant_add_from_hash_table (&dict, "requested-base-local-replacements", rpmostree_origin_get_overrides_local_replace (origin)); g_variant_dict_insert (&dict, "packages", "^as", layered_pkgs); g_variant_dict_insert_value (&dict, "base-removals", removed_base_pkgs); g_variant_dict_insert_value (&dict, "base-local-replacements", replaced_base_pkgs); g_variant_dict_insert (&dict, "pinned", "b", ostree_deployment_is_pinned (deployment)); g_variant_dict_insert (&dict, "unlocked", "s", ostree_deployment_unlocked_state_to_string (ostree_deployment_get_unlocked (deployment))); g_variant_dict_insert (&dict, "regenerate-initramfs", "b", rpmostree_origin_get_regenerate_initramfs (origin)); { const char *const* args = rpmostree_origin_get_initramfs_args (origin); if (args && *args) g_variant_dict_insert (&dict, "initramfs-args", "^as", args); } if (booted_id != NULL) g_variant_dict_insert (&dict, "booted", "b", g_strcmp0 (booted_id, id) == 0); return g_variant_dict_end (&dict); } /* Adds the following keys to the vardict: * - osname * - checksum * - version * - timestamp * - origin * - signatures * - gpg-enabled * * If not %NULL, @refspec and @checksum override defaults from @deployment. They can also be * used to avoid lookups if they're already available. * */ static gboolean add_all_commit_details_to_vardict (OstreeDeployment *deployment, OstreeRepo *repo, const char *refspec, /* allow-none */ const char *checksum, /* allow-none */ GVariant *commit, /* allow-none */ GVariantDict *dict, /* allow-none */ GError **error) { const gchar *osname = ostree_deployment_get_osname (deployment); g_autofree gchar *refspec_owned = NULL; gboolean refspec_is_ostree = FALSE; RpmOstreeRefspecType refspec_type; if (!refspec) { g_autoptr(RpmOstreeOrigin) origin = rpmostree_origin_parse_deployment (deployment, error); if (!origin) return FALSE; refspec = refspec_owned = rpmostree_origin_get_full_refspec (origin, &refspec_type); } else { const char *refspec_remainder = NULL; if (!rpmostree_refspec_classify (refspec, &refspec_type, &refspec_remainder, error)) return FALSE; refspec = refspec_remainder; } refspec_is_ostree = refspec_type == RPMOSTREE_REFSPEC_TYPE_OSTREE; if (refspec_type == RPMOSTREE_REFSPEC_TYPE_CHECKSUM && !commit) checksum = refspec; g_assert (refspec); /* allow_noent=TRUE since the ref may have been deleted for a rebase */ g_autofree gchar *checksum_owned = NULL; if (!checksum) { if (refspec_is_ostree) { if (!ostree_repo_resolve_rev (repo, refspec, TRUE, &checksum_owned, error)) return FALSE; } /* in that case, use deployment csum */ checksum = checksum_owned ?: ostree_deployment_get_csum (deployment); } g_autoptr(GVariant) commit_owned = NULL; if (!commit) { if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, checksum, &commit_owned, error)) return FALSE; commit = commit_owned; } if (refspec_is_ostree) { if (!variant_add_remote_status (repo, refspec, checksum, dict, error)) return FALSE; } if (osname != NULL) g_variant_dict_insert (dict, "osname", "s", osname); g_variant_dict_insert (dict, "checksum", "s", checksum); variant_add_commit_details (dict, NULL, commit); g_variant_dict_insert (dict, "origin", "s", refspec); return TRUE; } GVariant * rpmostreed_commit_generate_cached_details_variant (OstreeDeployment *deployment, OstreeRepo *repo, const char *refspec, const char *checksum, GError **error) { GVariantDict dict; g_variant_dict_init (&dict, NULL); if (!add_all_commit_details_to_vardict (deployment, repo, refspec, checksum, NULL, &dict, error)) return NULL; return g_variant_ref_sink (g_variant_dict_end (&dict)); } typedef struct { gboolean initialized; GPtrArray *upgraded; GPtrArray *downgraded; GPtrArray *removed; GPtrArray *added; } RpmDiff; static void rpm_diff_init (RpmDiff *diff) { g_assert (!diff->initialized); diff->upgraded = g_ptr_array_new_with_free_func ((GDestroyNotify)g_variant_unref); diff->downgraded = g_ptr_array_new_with_free_func ((GDestroyNotify)g_variant_unref); diff->removed = g_ptr_array_new_with_free_func ((GDestroyNotify)g_variant_unref); diff->added = g_ptr_array_new_with_free_func ((GDestroyNotify)g_variant_unref); diff->initialized = TRUE; } static void rpm_diff_clear (RpmDiff *diff) { if (!diff->initialized) return; g_clear_pointer (&diff->upgraded, (GDestroyNotify)g_ptr_array_unref); g_clear_pointer (&diff->downgraded, (GDestroyNotify)g_ptr_array_unref); g_clear_pointer (&diff->removed, (GDestroyNotify)g_ptr_array_unref); g_clear_pointer (&diff->added, (GDestroyNotify)g_ptr_array_unref); diff->initialized = FALSE; } G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (RpmDiff, rpm_diff_clear); static GVariant* single_pkg_variant_new (RpmOstreePkgTypes type, RpmOstreePackage *pkg) { return g_variant_ref_sink ( g_variant_new ("(usss)", type, rpm_ostree_package_get_name (pkg), rpm_ostree_package_get_evr (pkg), rpm_ostree_package_get_arch (pkg))); } static GVariant* modified_pkg_variant_new (RpmOstreePkgTypes type, RpmOstreePackage *pkg_old, RpmOstreePackage *pkg_new) { const char *name_old = rpm_ostree_package_get_name (pkg_old); const char *name_new = rpm_ostree_package_get_name (pkg_new); g_assert_cmpstr (name_old, ==, name_new); return g_variant_ref_sink ( g_variant_new ("(us(ss)(ss))", type, name_old, rpm_ostree_package_get_evr (pkg_old), rpm_ostree_package_get_arch (pkg_old), rpm_ostree_package_get_evr (pkg_new), rpm_ostree_package_get_arch (pkg_new))); } static GVariant* modified_dnfpkg_variant_new (RpmOstreePkgTypes type, RpmOstreePackage *pkg_old, DnfPackage *pkg_new) { const char *name_old = rpm_ostree_package_get_name (pkg_old); const char *name_new = dnf_package_get_name (pkg_new); g_assert_cmpstr (name_old, ==, name_new); return g_variant_ref_sink ( g_variant_new ("(us(ss)(ss))", type, name_old, rpm_ostree_package_get_evr (pkg_old), rpm_ostree_package_get_arch (pkg_old), dnf_package_get_evr (pkg_new), dnf_package_get_arch (pkg_new))); } static gboolean rpm_diff_add_db_diff (RpmDiff *diff, OstreeRepo *repo, RpmOstreePkgTypes type, const char *old_checksum, const char *new_checksum, GPtrArray **out_modified_new, GCancellable *cancellable, GError **error) { g_autoptr(GPtrArray) removed = NULL; g_autoptr(GPtrArray) added = NULL; g_autoptr(GPtrArray) modified_old = NULL; g_autoptr(GPtrArray) modified_new = NULL; /* Use allow_noent; we'll just skip over the rpm diff if there's no data */ RpmOstreeDbDiffExtFlags flags = RPM_OSTREE_DB_DIFF_EXT_ALLOW_NOENT; if (!rpm_ostree_db_diff_ext (repo, old_checksum, new_checksum, flags, &removed, &added, &modified_old, &modified_new, cancellable, error)) return FALSE; /* check if allow_noent kicked in */ if (!removed) return TRUE; /* NB: early return */ g_assert_cmpuint (modified_old->len, ==, modified_new->len); for (guint i = 0; i < removed->len; i++) g_ptr_array_add (diff->removed, single_pkg_variant_new (type, removed->pdata[i])); for (guint i = 0; i < added->len; i++) g_ptr_array_add (diff->added, single_pkg_variant_new (type, added->pdata[i])); for (guint i = 0; i < modified_old->len; i++) { RpmOstreePackage *old_pkg = modified_old->pdata[i]; RpmOstreePackage *new_pkg = modified_new->pdata[i]; if (rpm_ostree_package_cmp (old_pkg, new_pkg) < 0) g_ptr_array_add (diff->upgraded, modified_pkg_variant_new (type, old_pkg, new_pkg)); else g_ptr_array_add (diff->downgraded, modified_pkg_variant_new (type, old_pkg, new_pkg)); } if (out_modified_new) *out_modified_new = g_steal_pointer (&modified_new); return TRUE; } static void rpm_diff_add_layered_diff (RpmDiff *diff, RpmOstreePackage *old_pkg, DnfPackage *new_pkg) { /* add to upgraded; layered pkgs only go up */ RpmOstreePkgTypes type = RPM_OSTREE_PKG_TYPE_LAYER; g_ptr_array_add (diff->upgraded, modified_dnfpkg_variant_new (type, old_pkg, new_pkg)); } static int sort_pkgvariant_by_name (gconstpointer pkga_pp, gconstpointer pkgb_pp) { GVariant *pkg_a = *((GVariant**)pkga_pp); GVariant *pkg_b = *((GVariant**)pkgb_pp); const char *pkgname_a; g_variant_get_child (pkg_a, 1, "&s", &pkgname_a); const char *pkgname_b; g_variant_get_child (pkg_b, 1, "&s", &pkgname_b); return strcmp (pkgname_a, pkgname_b); } static GVariant* array_to_variant_new (const char *format, GPtrArray *array) { if (array->len == 0) return g_variant_new (format, NULL); /* make doubly sure it's sorted */ g_ptr_array_sort (array, sort_pkgvariant_by_name); g_auto(GVariantBuilder) builder; g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); for (guint i = 0; i < array->len; i++) g_variant_builder_add_value (&builder, array->pdata[i]); return g_variant_builder_end (&builder); } static gboolean rpm_diff_is_empty (RpmDiff *diff) { g_assert (diff->initialized); return !diff->upgraded->len && !diff->downgraded->len && !diff->removed->len && !diff->added->len; } static GVariant* rpm_diff_variant_new (RpmDiff *diff) { g_assert (diff->initialized); g_auto(GVariantDict) dict; g_variant_dict_init (&dict, NULL); g_variant_dict_insert_value (&dict, "upgraded", array_to_variant_new ( RPMOSTREE_DIFF_MODIFIED_GVARIANT_STRING, diff->upgraded)); g_variant_dict_insert_value (&dict, "downgraded", array_to_variant_new ( RPMOSTREE_DIFF_MODIFIED_GVARIANT_STRING, diff->downgraded)); g_variant_dict_insert_value (&dict, "removed", array_to_variant_new ( RPMOSTREE_DIFF_SINGLE_GVARIANT_STRING, diff->removed)); g_variant_dict_insert_value (&dict, "added", array_to_variant_new ( RPMOSTREE_DIFF_SINGLE_GVARIANT_STRING, diff->added)); return g_variant_dict_end (&dict); } static DnfPackage* find_package (DnfSack *sack, gboolean newer, RpmOstreePackage *pkg) { hy_autoquery HyQuery query = hy_query_create (sack); hy_query_filter (query, HY_PKG_NAME, HY_EQ, rpm_ostree_package_get_name (pkg)); if (newer) { hy_query_filter (query, HY_PKG_EVR, HY_GT, rpm_ostree_package_get_evr (pkg)); hy_query_filter (query, HY_PKG_ARCH, HY_NEQ, "src"); hy_query_filter_latest (query, TRUE); } else { /* we want an exact match */ hy_query_filter (query, HY_PKG_NEVRA, HY_EQ, rpm_ostree_package_get_nevra (pkg)); } g_autoptr(GPtrArray) pkgs = hy_query_run (query); if (pkgs->len == 0) return NULL; /* canonicalize to NULL */ g_ptr_array_sort (pkgs, (GCompareFunc)rpmostree_pkg_array_compare); return g_object_ref (pkgs->pdata[pkgs->len-1]); } /* For all layered pkgs, check if there are newer versions in the rpmmd. Add diff to * @rpm_diff, and all new pkgs in @out_newer_packages (these are used later for advisories). * */ static gboolean rpmmd_diff_guess (OstreeRepo *repo, const char *base_checksum, const char *layered_checksum, DnfSack *sack, RpmDiff *rpm_diff, GPtrArray **out_newer_packages, GError **error) { /* Note here that we *don't* actually use layered_pkgs; we want to look at all the RPMs * installed, whereas the layered pkgs (actually patterns) just represent top-level * entries. IOW, we want to run through all layered RPMs, which include deps of * layered_pkgs. */ g_autoptr(GPtrArray) all_layered_pkgs = NULL; RpmOstreeDbDiffExtFlags flags = RPM_OSTREE_DB_DIFF_EXT_ALLOW_NOENT; if (!rpm_ostree_db_diff_ext (repo, base_checksum, layered_checksum, flags, NULL, &all_layered_pkgs, NULL, NULL, NULL, error)) return FALSE; /* XXX: need to filter out local pkgs; though we still want to check for advisories -- * maybe we should do this in status.c instead? */ if (all_layered_pkgs == NULL || /* -> older layer before we injected pkglist metadata */ all_layered_pkgs->len == 0) /* -> no layered pkgs, e.g. override remove only */ { *out_newer_packages = NULL; return TRUE; /* note early return */ } /* for each layered pkg, check if there's a newer version available (in reality, there may * be other new pkgs that need to be layered or some pkgs that no longer need to, but we * won't find out until we have the full commit available -- XXX: we could go the extra * effort and use the rpmdb of new_checksum if we already have it somehow, though that's * probably not the common case */ g_autoptr(GPtrArray) newer_packages = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref); for (guint i = 0; i < all_layered_pkgs->len; i++) { RpmOstreePackage *pkg = all_layered_pkgs->pdata[i]; g_autoptr(DnfPackage) newer_pkg = find_package (sack, TRUE, pkg); if (!newer_pkg) continue; g_ptr_array_add (newer_packages, g_object_ref (newer_pkg)); rpm_diff_add_layered_diff (rpm_diff, pkg, newer_pkg); } /* canonicalize to NULL if there's nothing new */ if (newer_packages->len == 0) g_clear_pointer (&newer_packages, (GDestroyNotify)g_ptr_array_unref); *out_newer_packages = g_steal_pointer (&newer_packages); return TRUE; } /* convert those now to make the D-Bus API nicer and easier for clients */ static RpmOstreeAdvisorySeverity str2severity (const char *str) { if (str == NULL) return RPM_OSTREE_ADVISORY_SEVERITY_NONE; /* these expect RHEL naming conventions; Fedora hopefully should follow soon, see: * https://github.com/fedora-infra/bodhi/pull/2099 */ g_autofree char *str_up = g_ascii_strup (str, -1); if (g_str_equal (str_up, "LOW")) return RPM_OSTREE_ADVISORY_SEVERITY_LOW; if (g_str_equal (str_up, "MODERATE")) return RPM_OSTREE_ADVISORY_SEVERITY_MODERATE; if (g_str_equal (str_up, "IMPORTANT")) return RPM_OSTREE_ADVISORY_SEVERITY_IMPORTANT; if (g_str_equal (str_up, "CRITICAL")) return RPM_OSTREE_ADVISORY_SEVERITY_CRITICAL; return RPM_OSTREE_ADVISORY_SEVERITY_NONE; } /* Returns a *floating* variant ref representing the advisory */ static GVariant* advisory_variant_new (DnfAdvisory *adv, GPtrArray *pkgs) { g_auto(GVariantBuilder) builder; g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE); g_variant_builder_add (&builder, "s", dnf_advisory_get_id (adv)); g_variant_builder_add (&builder, "u", dnf_advisory_get_kind (adv)); g_variant_builder_add (&builder, "u", str2severity (dnf_advisory_get_severity (adv))); { g_auto(GVariantBuilder) pkgs_array; g_variant_builder_init (&pkgs_array, G_VARIANT_TYPE_ARRAY); for (guint i = 0; i < pkgs->len; i++) g_variant_builder_add (&pkgs_array, "s", dnf_package_get_nevra (pkgs->pdata[i])); g_variant_builder_add_value (&builder, g_variant_builder_end (&pkgs_array)); } /* for now we don't ship any extra info about the errata (e.g. title, date, desc, refs) */ g_variant_builder_add_value (&builder, g_variant_new ("a{sv}", NULL)); return g_variant_builder_end (&builder); } /* libdnf creates new DnfAdvisory objects on request */ static guint advisory_hash (gconstpointer v) { return g_str_hash (dnf_advisory_get_id ((DnfAdvisory*)v)); } static gboolean advisory_equal (gconstpointer v1, gconstpointer v2) { return g_str_equal (dnf_advisory_get_id ((DnfAdvisory*)v1), dnf_advisory_get_id ((DnfAdvisory*)v2)); } /* Go through the list of @pkgs and check if there are any advisories open for them. If * no advisories are found, returns %NULL. Otherwise, returns a GVariant of the following * type: 'a(suuasa{sv})' s advisory id (e.g. FEDORA-2018-a1b2c3d4e5f6) u advisory kind (enum DnfAdvisoryKind) u advisory severity (enum RpmOstreeAdvisorySeverity) as list of packages (NEVRAs) contained in the advisory a{sv} additional info about advisory (none so far) */ static GVariant* advisories_variant (DnfSack *sack, GPtrArray *pkgs) { g_autoptr(GHashTable) advisories = g_hash_table_new_full (advisory_hash, advisory_equal, g_object_unref, (GDestroyNotify)g_ptr_array_unref); /* libdnf provides pkg -> set of advisories, but we want advisory -> set of pkgs; * making sure we only keep the pkgs we actually care about */ for (guint i = 0; i < pkgs->len; i++) { DnfPackage *pkg = pkgs->pdata[i]; g_autoptr(GPtrArray) advisories_with_pkg = dnf_package_get_advisories (pkg, HY_EQ); for (guint j = 0; j < advisories_with_pkg->len; j++) { DnfAdvisory *advisory = advisories_with_pkg->pdata[j]; /* for now we're only interested in security erratas */ if (dnf_advisory_get_kind (advisory) != DNF_ADVISORY_KIND_SECURITY) continue; /* reverse mapping */ GPtrArray *pkgs_in_advisory = g_hash_table_lookup (advisories, advisory); if (!pkgs_in_advisory) { pkgs_in_advisory = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref); g_hash_table_insert (advisories, g_object_ref (advisory), pkgs_in_advisory); } g_ptr_array_add (pkgs_in_advisory, g_object_ref (pkg)); } } if (g_hash_table_size (advisories) == 0) return NULL; g_auto(GVariantBuilder) builder; g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); GLNX_HASH_TABLE_FOREACH_KV (advisories, DnfAdvisory*, advisory, GPtrArray*, pkgs) g_variant_builder_add_value (&builder, advisory_variant_new (advisory, pkgs)); return g_variant_ref_sink (g_variant_builder_end (&builder)); } /* try to find the exact same RpmOstreePackage pkgs in the sack */ static GPtrArray* rpm_ostree_pkgs_to_dnf (DnfSack *sack, GPtrArray *rpm_ostree_pkgs) { g_autoptr(GPtrArray) dnf_pkgs = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref); const guint n = rpm_ostree_pkgs->len; for (guint i = 0; i < n; i++) { RpmOstreePackage *pkg = rpm_ostree_pkgs->pdata[i]; hy_autoquery HyQuery query = hy_query_create (sack); hy_query_filter (query, HY_PKG_NAME, HY_EQ, rpm_ostree_package_get_name (pkg)); hy_query_filter (query, HY_PKG_EVR, HY_EQ, rpm_ostree_package_get_evr (pkg)); hy_query_filter (query, HY_PKG_ARCH, HY_EQ, rpm_ostree_package_get_arch (pkg)); g_autoptr(GPtrArray) pkgs = hy_query_run (query); /* 0 --> ostree stream is out of sync with rpmmd repos probably? */ if (pkgs->len > 0) g_ptr_array_add (dnf_pkgs, g_object_ref (pkgs->pdata[0])); } return g_steal_pointer (&dnf_pkgs); } /* The variant returned by this function is backwards compatible with the one returned by * rpmostreed_commit_generate_cached_details_variant(). However, it also includes a base * tree db diff, layered pkgs diff, state, advisories, etc... Also, it will happily return * %NULL if no updates are available. * * If @staged_deployment is %NULL, update details are based on latest downloaded ostree * rpmmd metadata. If @staged_deployment is not %NULL, then the update describes the diff * between @booted_deployment and @staged_deployment. */ gboolean rpmostreed_update_generate_variant (OstreeDeployment *booted_deployment, OstreeDeployment *staged_deployment, OstreeRepo *repo, DnfSack *sack, /* allow-none */ GVariant **out_update, GCancellable *cancellable, GError **error) { GLNX_AUTO_PREFIX_ERROR ("Generating update variant", error); g_autoptr(RpmOstreeOrigin) origin = rpmostree_origin_parse_deployment (booted_deployment, error); if (!origin) return FALSE; const char *refspec = rpmostree_origin_get_refspec (origin); { RpmOstreeRefspecType refspectype = RPMOSTREE_REFSPEC_TYPE_OSTREE; const char *refspec_data; if (!rpmostree_refspec_classify (refspec, &refspectype, &refspec_data, error)) return FALSE; /* we don't support rojig-based origins yet */ switch (refspectype) { case RPMOSTREE_REFSPEC_TYPE_ROJIG: *out_update = NULL; return TRUE; /* NB: early return */ case RPMOSTREE_REFSPEC_TYPE_OSTREE: case RPMOSTREE_REFSPEC_TYPE_CHECKSUM: break; } /* just skip over "ostree://" so we can talk with libostree without thinking about it */ refspec = refspec_data; } /* let's start with the ostree side of things */ const char *current_checksum = ostree_deployment_get_csum (booted_deployment); g_autofree char *current_base_checksum_owned = NULL; if (!rpmostree_deployment_get_base_layer (repo, booted_deployment, ¤t_base_checksum_owned, error)) return FALSE; const char *current_base_checksum = current_base_checksum_owned ?: current_checksum; gboolean is_new_layered = FALSE; const char *new_checksum = NULL; const char *new_base_checksum = NULL; g_autofree char *new_base_checksum_owned = NULL; if (staged_deployment) { new_checksum = ostree_deployment_get_csum (staged_deployment); if (!rpmostree_deployment_get_base_layer (repo, staged_deployment, &new_base_checksum_owned, error)) return FALSE; new_base_checksum = new_base_checksum_owned ?: new_checksum; } else { if (!ostree_repo_resolve_rev_ext (repo, refspec, TRUE, 0, &new_base_checksum_owned, error)) return FALSE; new_base_checksum = new_base_checksum_owned; /* just assume that the hypothetical new deployment would also be layered if we are */ is_new_layered = (current_base_checksum_owned != NULL); } /* Graciously handle rev no longer in repo; e.g. mucking around with rebase/rollback; we * still want to do the rpm-md phase. In that case, just use the current csum. */ gboolean is_new_checksum = FALSE; if (!new_base_checksum) new_base_checksum = current_base_checksum; else is_new_checksum = !g_str_equal (new_base_checksum, current_base_checksum); g_autoptr(GVariant) commit = NULL; if (!ostree_repo_load_commit (repo, new_base_checksum, &commit, NULL, error)) return FALSE; g_auto(GVariantDict) dict; g_variant_dict_init (&dict, NULL); /* first get all the traditional/backcompat stuff */ if (!add_all_commit_details_to_vardict (booted_deployment, repo, refspec, new_base_checksum, commit, &dict, error)) return FALSE; /* This may seem trivial, but it's important to keep the final variant as self-contained * and "diff-based" as possible, since it'll be available as a D-Bus property. This makes * it easier to consume for UIs like GNOME Software and Cockpit. */ g_variant_dict_insert (&dict, "ref-has-new-commit", "b", is_new_checksum); g_auto(RpmDiff) rpm_diff = {0, }; rpm_diff_init (&rpm_diff); /* we'll need these later for advisories, so just keep them around */ g_autoptr(GPtrArray) ostree_modified_new = NULL; g_autoptr(GPtrArray) rpmmd_modified_new = NULL; if (staged_deployment) { /* ok we have a staged deployment; we just need to do a simple diff and BOOM done! */ /* XXX: we're marking all pkgs as BASE right now even though there could be layered * pkgs too -- we can tease those out in the future if needed */ if (!rpm_diff_add_db_diff (&rpm_diff, repo, RPM_OSTREE_PKG_TYPE_BASE, current_checksum, new_checksum, &ostree_modified_new, cancellable, error)) return FALSE; } else { /* no staged deployment; we do our best to come up with a diff: * - if a new base checksum was pulled, do a db diff of the old and new bases * - if there are currently any layered pkgs, lookup in sack for newer versions */ if (is_new_checksum) { if (!rpm_diff_add_db_diff (&rpm_diff, repo, RPM_OSTREE_PKG_TYPE_BASE, current_base_checksum, new_base_checksum, &ostree_modified_new, cancellable, error)) return FALSE; } /* now we look at the rpm-md/layering side */ GHashTable *layered_pkgs = rpmostree_origin_get_packages (origin); /* check that it's actually layered (i.e. the requests are not all just dormant) */ if (sack && is_new_layered && g_hash_table_size (layered_pkgs) > 0) { if (!rpmmd_diff_guess (repo, current_base_checksum, current_checksum, sack, &rpm_diff, &rpmmd_modified_new, error)) return FALSE; } } /* don't bother inserting if there's nothing new */ if (!rpm_diff_is_empty (&rpm_diff)) g_variant_dict_insert (&dict, "rpm-diff", "@a{sv}", rpm_diff_variant_new (&rpm_diff)); /* now we look for advisories */ if (sack && (ostree_modified_new || rpmmd_modified_new)) { /* let's just merge the two now for convenience */ g_autoptr(GPtrArray) new_packages = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref); if (ostree_modified_new) { /* recall that @ostree_modified_new is an array of RpmOstreePackage; try to find * the same pkg in the rpmmd so that we can search for advisories afterwards */ g_autoptr(GPtrArray) pkgs = rpm_ostree_pkgs_to_dnf (sack, ostree_modified_new); for (guint i = 0; i < pkgs->len; i++) g_ptr_array_add (new_packages, g_object_ref (pkgs->pdata[i])); } if (rpmmd_modified_new) { for (guint i = 0; i < rpmmd_modified_new->len; i++) g_ptr_array_add (new_packages, g_object_ref (rpmmd_modified_new->pdata[i])); } g_autoptr(GVariant) advisories = advisories_variant (sack, new_packages); if (advisories) g_variant_dict_insert (&dict, "advisories", "@a(suuasa{sv})", advisories); } if (staged_deployment) { g_autofree char *id = rpmostreed_deployment_generate_id (staged_deployment); g_variant_dict_insert (&dict, "deployment", "s", id); } /* but if there are no updates, then just ditch the whole thing and return NULL */ if (is_new_checksum || rpmmd_modified_new) { /* include a "state" checksum for cache invalidation; for now this is just the * checksum of the deployment against which we ran, though we could base it off more * things later if needed */ g_variant_dict_insert (&dict, "update-sha256", "s", current_checksum); *out_update = g_variant_ref_sink (g_variant_dict_end (&dict)); } else *out_update = NULL; return TRUE; }