/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- * * Copyright (C) 2014 Anne LoVerso * Copyright (C) 2016 Red Hat, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2 of the licence or (at * your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307, USA. */ #include "config.h" #include #include #include #include #include #include #include "rpmostree-builtins.h" #include "rpmostree-libbuiltin.h" #include "rpmostree-dbus-helpers.h" #include "rpmostree-util.h" #include "rpmostree-core.h" #include "rpmostree-rpm-util.h" #include "libsd-locale-util.h" #include "libsd-time-util.h" #include #define RPMOSTREE_AUTOMATIC_TIMER_OBJPATH \ "/org/freedesktop/systemd1/unit/rpm_2dostreed_2dautomatic_2etimer" #define RPMOSTREE_AUTOMATIC_SERVICE_OBJPATH \ "/org/freedesktop/systemd1/unit/rpm_2dostreed_2dautomatic_2eservice" static gboolean opt_pretty; static gboolean opt_verbose; static gboolean opt_verbose_advisories; static gboolean opt_json; static gboolean opt_only_booted; static const char *opt_jsonpath; static GOptionEntry option_entries[] = { { "pretty", 'p', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &opt_pretty, "This option is deprecated and no longer has any effect", NULL }, { "verbose", 'v', 0, G_OPTION_ARG_NONE, &opt_verbose, "Print additional fields (e.g. StateRoot); implies -a", NULL }, { "advisories", 'a', 0, G_OPTION_ARG_NONE, &opt_verbose_advisories, "Expand advisories listing", NULL }, { "json", 0, 0, G_OPTION_ARG_NONE, &opt_json, "Output JSON", NULL }, { "jsonpath", 'J', 0, G_OPTION_ARG_STRING, &opt_jsonpath, "Filter JSONPath expression", "EXPRESSION" }, { "booted", 'b', 0, G_OPTION_ARG_NONE, &opt_only_booted, "Only print the booted deployment", NULL }, { NULL } }; /* return space available for printing value side of kv */ static guint get_textarea_width (guint maxkeylen) { const guint columns = glnx_console_columns (); /* +2 for initial leading spaces */ const guint right_side_width = maxkeylen + 2 + strlen (": "); if (right_side_width >= columns) return G_MAXUINT; /* can't even print keys without wrapping, nothing pretty to do here */ /* the sha is already 64 chars, so no point in trying to use less */ return MAX(OSTREE_SHA256_STRING_LEN, columns - right_side_width); } static GVariant * get_active_txn (RPMOSTreeSysroot *sysroot_proxy) { GVariant* txn = rpmostree_sysroot_get_active_transaction (sysroot_proxy); const char *a, *b, *c; if (txn) { g_variant_get (txn, "(&s&s&s)", &a, &b, &c); if (*a) return txn; } return NULL; } static void print_packages (const char *k, guint max_key_len, const char *const* pkgs, const char *const* omit_pkgs) { g_autoptr(GPtrArray) packages_sorted = g_ptr_array_new_with_free_func (g_free); static gsize regex_initialized; static GRegex *safe_chars_regex; if (g_once_init_enter (®ex_initialized)) { safe_chars_regex = g_regex_new ("^[[:alnum:]-._]+$", 0, 0, NULL); g_assert (safe_chars_regex); g_once_init_leave (®ex_initialized, 1); } for (char **iter = (char**) pkgs; iter && *iter; iter++) { if (omit_pkgs != NULL && g_strv_contains (omit_pkgs, *iter)) continue; /* don't quote if it just has common pkgname/shell-safe chars */ if (g_regex_match (safe_chars_regex, *iter, 0, 0)) g_ptr_array_add (packages_sorted, g_strdup (*iter)); else g_ptr_array_add (packages_sorted, g_shell_quote (*iter)); } const guint n_packages = packages_sorted->len; if (n_packages == 0) return; rpmostree_print_kv_no_newline (k, max_key_len, ""); /* wrap pkglist output ourselves rather than letting the terminal cut us up */ const guint area_width = get_textarea_width (max_key_len); guint current_width = 0; for (guint i = 0; i < n_packages; i++) { const char *pkg = packages_sorted->pdata[i]; const guint pkg_width = strlen (pkg); /* first print */ if (current_width == 0) { g_print ("%s", pkg); current_width += pkg_width; } else if ((current_width + pkg_width + 1) <= area_width) /* +1 for space separator */ { g_print (" %s", pkg); current_width += (pkg_width + 1); } else { /* always print at least one per line, even if we overflow */ putc ('\n', stdout); rpmostree_print_kv_no_newline ("", max_key_len, pkg); current_width = pkg_width; } } putc ('\n', stdout); } static const gchar** lookup_array_and_canonicalize (GVariantDict *dict, const char *key) { g_autofree const gchar **ret = NULL; if (g_variant_dict_lookup (dict, key, "^a&s", &ret)) { /* Canonicalize length 0 strv to NULL */ if (!*ret) g_clear_pointer (&ret, g_free); } return g_steal_pointer (&ret); } static void gv_nevra_to_evr (GString *buffer, GVariant *gv_nevra) { guint64 epoch; const char *version, *release; g_variant_get (gv_nevra, "(sst&s&ss)", NULL, NULL, &epoch, &version, &release, NULL); rpmostree_custom_nevra (buffer, NULL, epoch, version, release, NULL, PKG_NEVRA_FLAGS_EPOCH_VERSION_RELEASE); } typedef enum { AUTO_UPDATE_SDSTATE_TIMER_UNKNOWN, AUTO_UPDATE_SDSTATE_TIMER_INACTIVE, AUTO_UPDATE_SDSTATE_SERVICE_FAILED, AUTO_UPDATE_SDSTATE_SERVICE_RUNNING, AUTO_UPDATE_SDSTATE_SERVICE_EXITED, } AutoUpdateSdState; static gboolean get_last_auto_update_run (GDBusConnection *connection, AutoUpdateSdState *out_state, char **out_last_run, GCancellable *cancellable, GError **error) { GLNX_AUTO_PREFIX_ERROR ("Querying systemd for last auto-update run", error); /* Check if the timer is running, otherwise systemd won't even keep timestamp info on dead * services. Also good to tell users if the policy is not none, but timer is off (though * we don't print it as an error; e.g. the timer might have been explicitly masked). */ g_autoptr(GDBusProxy) timer_unit_proxy = g_dbus_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, NULL, "org.freedesktop.systemd1", RPMOSTREE_AUTOMATIC_TIMER_OBJPATH, "org.freedesktop.systemd1.Unit", cancellable, error); if (!timer_unit_proxy) return FALSE; g_autoptr(GVariant) timer_state_val = g_dbus_proxy_get_cached_property (timer_unit_proxy, "ActiveState"); /* let's not error out if we can't msg systemd (e.g. bad sepol); just mark as unknown */ if (timer_state_val == NULL) { *out_state = AUTO_UPDATE_SDSTATE_TIMER_UNKNOWN; return TRUE; /* NB early return */ } const char *timer_state = g_variant_get_string (timer_state_val, NULL); if (g_str_equal (timer_state, "inactive")) { *out_state = AUTO_UPDATE_SDSTATE_TIMER_INACTIVE; return TRUE; /* NB early return */ } g_autoptr(GDBusProxy) service_unit_proxy = g_dbus_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, NULL, "org.freedesktop.systemd1", RPMOSTREE_AUTOMATIC_SERVICE_OBJPATH, "org.freedesktop.systemd1.Unit", cancellable, error); if (!service_unit_proxy) return FALSE; g_autoptr(GVariant) service_state_val = g_dbus_proxy_get_cached_property (service_unit_proxy, "ActiveState"); const char *service_state = g_variant_get_string (service_state_val, NULL); if (g_str_equal (service_state, "failed")) { *out_state = AUTO_UPDATE_SDSTATE_SERVICE_FAILED; return TRUE; /* NB early return */ } else if (g_str_equal (service_state, "active")) { *out_state = AUTO_UPDATE_SDSTATE_SERVICE_RUNNING; return TRUE; /* NB early return */ } g_autoptr(GDBusProxy) service_proxy = g_dbus_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, NULL, "org.freedesktop.systemd1", RPMOSTREE_AUTOMATIC_SERVICE_OBJPATH, "org.freedesktop.systemd1.Service", cancellable, error); if (!service_proxy) return FALSE; g_autoptr(GVariant) t_val = g_dbus_proxy_get_cached_property (service_proxy, "ExecMainExitTimestamp"); g_autofree char *last_run = NULL; if (t_val) { guint64 t = g_variant_get_uint64 (t_val); if (t > 0) { char time_rel[FORMAT_TIMESTAMP_RELATIVE_MAX] = ""; libsd_format_timestamp_relative (time_rel, sizeof(time_rel), t); last_run = g_strdup (time_rel); } } *out_state = AUTO_UPDATE_SDSTATE_SERVICE_EXITED; *out_last_run = g_steal_pointer (&last_run); return TRUE; } static gboolean print_daemon_state (RPMOSTreeSysroot *sysroot_proxy, GBusType bus_type, GCancellable *cancellable, GError **error) { glnx_unref_object RPMOSTreeTransaction *txn_proxy = NULL; if (!rpmostree_transaction_connect_active (sysroot_proxy, NULL, &txn_proxy, cancellable, error)) return FALSE; const char *policy = rpmostree_sysroot_get_automatic_update_policy (sysroot_proxy); g_print ("State: %s", txn_proxy ? "busy" : "idle"); if (g_str_equal (policy, "none")) g_print ("; auto updates disabled\n"); else { g_print ("; auto updates enabled "); /* don't try to get info from systemd if we're not on the system bus */ if (bus_type != G_BUS_TYPE_SYSTEM) g_print ("(%s)\n", policy); else { AutoUpdateSdState state; g_autofree char *last_run = NULL; GDBusConnection *connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (sysroot_proxy)); if (!get_last_auto_update_run (connection, &state, &last_run, cancellable, error)) return FALSE; switch (state) { case AUTO_UPDATE_SDSTATE_TIMER_UNKNOWN: { g_print ("(%s; unknown timer state)\n", policy); break; } case AUTO_UPDATE_SDSTATE_TIMER_INACTIVE: { g_print ("(%s; timer inactive)\n", policy); break; } case AUTO_UPDATE_SDSTATE_SERVICE_FAILED: { g_print ("(%s; %s%slast run failed%s%s)\n", policy, get_red_start (), get_bold_start (), get_bold_end (), get_red_end ()); break; } case AUTO_UPDATE_SDSTATE_SERVICE_RUNNING: { g_print ("(%s; running)\n", policy); break; } case AUTO_UPDATE_SDSTATE_SERVICE_EXITED: { if (last_run) /* e.g. "last run 4h 32min ago" */ g_print ("(%s; last run %s)\n", policy, last_run); else g_print ("(%s; no runs since boot)\n", policy); break; } default: { g_assert_not_reached (); } } } } if (txn_proxy) { const char *title = rpmostree_transaction_get_title (txn_proxy); g_print ("Transaction: %s\n", title); } return TRUE; } /* Print the result of rpmostree_context_get_rpmmd_repo_commit_metadata() */ static void print_origin_repos (gboolean host_endian, guint maxkeylen, GVariantDict *commit_meta) { g_autoptr(GVariant) reposdata = g_variant_dict_lookup_value (commit_meta, "rpmostree.rpmmd-repos", G_VARIANT_TYPE ("aa{sv}")); if (!reposdata) return; const guint n = g_variant_n_children (reposdata); for (guint i = 0; i < n; i++) { g_autoptr(GVariant) child = g_variant_get_child_value (reposdata, i); g_autoptr(GVariantDict) cdict = g_variant_dict_new (child); const char *id = NULL; if (!g_variant_dict_lookup (cdict, "id", "&s", &id)) continue; guint64 ts; if (!g_variant_dict_lookup (cdict, "timestamp", "t", &ts)) continue; /* `compose tree` commits are canonicalized to BE, but client-side commits * are not. Whee. */ if (!host_endian) ts = GUINT64_FROM_BE (ts); g_autofree char *timestamp_string = rpmostree_timestamp_str_from_unix_utc (ts); g_print (" %*s%s %s (%s)\n", maxkeylen + 2, " ", libsd_special_glyph (TREE_RIGHT), id, timestamp_string); } } static gboolean print_one_deployment (RPMOSTreeSysroot *sysroot_proxy, GVariant *child, gboolean first, gboolean have_any_live_overlay, gboolean have_multiple_stateroots, const char *booted_osname, const char *cached_update_deployment_id, GVariant *cached_update, gboolean *out_printed_cached_update, GError **error) { /* Add the long keys here */ const guint max_key_len = MAX (strlen ("InactiveBaseReplacements"), strlen ("InterruptedLiveCommit")); g_autoptr(GVariantDict) dict = g_variant_dict_new (child); /* osname should always be present. */ const gchar *os_name; const gchar *id; int serial; const gchar *checksum; g_assert (g_variant_dict_lookup (dict, "osname", "&s", &os_name)); g_assert (g_variant_dict_lookup (dict, "id", "&s", &id)); g_assert (g_variant_dict_lookup (dict, "serial", "i", &serial)); g_assert (g_variant_dict_lookup (dict, "checksum", "&s", &checksum)); gboolean is_booted; if (!g_variant_dict_lookup (dict, "booted", "b", &is_booted)) is_booted = FALSE; if (!is_booted && opt_only_booted) return TRUE; const gchar *origin_refspec; g_autofree const gchar **origin_packages = NULL; g_autofree const gchar **origin_requested_packages = NULL; g_autofree const gchar **origin_requested_local_packages = NULL; g_autoptr(GVariant) origin_base_removals = NULL; g_autofree const gchar **origin_requested_base_removals = NULL; g_autoptr(GVariant) origin_base_local_replacements = NULL; g_autofree const gchar **origin_requested_base_local_replacements = NULL; if (g_variant_dict_lookup (dict, "origin", "&s", &origin_refspec)) { origin_packages = lookup_array_and_canonicalize (dict, "packages"); origin_requested_packages = lookup_array_and_canonicalize (dict, "requested-packages"); origin_requested_local_packages = lookup_array_and_canonicalize (dict, "requested-local-packages"); origin_base_removals = g_variant_dict_lookup_value (dict, "base-removals", G_VARIANT_TYPE ("av")); origin_requested_base_removals = lookup_array_and_canonicalize (dict, "requested-base-removals"); origin_base_local_replacements = g_variant_dict_lookup_value (dict, "base-local-replacements", G_VARIANT_TYPE ("a(vv)")); origin_requested_base_local_replacements = lookup_array_and_canonicalize (dict, "requested-base-local-replacements"); } else origin_refspec = NULL; const gchar *version_string; if (!g_variant_dict_lookup (dict, "version", "&s", &version_string)) version_string = NULL; const gchar *unlocked; if (!g_variant_dict_lookup (dict, "unlocked", "&s", &unlocked)) unlocked = NULL; gboolean regenerate_initramfs; if (!g_variant_dict_lookup (dict, "regenerate-initramfs", "b", ®enerate_initramfs)) regenerate_initramfs = FALSE; g_autoptr(GVariant) signatures = g_variant_dict_lookup_value (dict, "signatures", G_VARIANT_TYPE ("av")); if (!first) g_print ("\n"); g_print ("%s ", is_booted ? libsd_special_glyph (BLACK_CIRCLE) : " "); RpmOstreeRefspecType refspectype = RPMOSTREE_REFSPEC_TYPE_OSTREE; if (origin_refspec) { const char *refspec_data; if (!rpmostree_refspec_classify (origin_refspec, &refspectype, &refspec_data, error)) return FALSE; g_autofree char *canonrefspec = rpmostree_refspec_to_string (refspectype, refspec_data); switch (refspectype) { case RPMOSTREE_REFSPEC_TYPE_OSTREE: { g_print ("%s", canonrefspec); } break; case RPMOSTREE_REFSPEC_TYPE_ROJIG: { g_autoptr(GVariant) rojig_description = NULL; g_variant_dict_lookup (dict, "rojig-description", "@a{sv}", &rojig_description); if (rojig_description) { g_autoptr(GVariantDict) dict = g_variant_dict_new (rojig_description); const char *repo = NULL; g_variant_dict_lookup (dict, "repo", "&s", &repo); const char *name = NULL; g_variant_dict_lookup (dict, "name", "&s", &name); const char *evr = NULL; g_variant_dict_lookup (dict, "evr", "&s", &evr); const char *arch = NULL; g_variant_dict_lookup (dict, "arch", "&s", &arch); g_assert (repo && name); g_print ("%s:%s", repo, name); if (evr && arch) g_print ("-%s.%s", evr, arch); } else { g_print ("%s", canonrefspec); } } break; } } else g_print ("%s", checksum); g_print ("\n"); const char *remote_not_found = NULL; g_variant_dict_lookup (dict, "remote-error", "s", &remote_not_found); if (remote_not_found) { g_print ("%s%s", get_red_start (), get_bold_start ()); rpmostree_print_kv ("OstreeRemoteStatus", max_key_len, remote_not_found); g_print ("%s%s", get_bold_end (), get_red_end ()); } const char *base_checksum = NULL; g_variant_dict_lookup (dict, "base-checksum", "&s", &base_checksum); gboolean is_locally_assembled = FALSE; if (base_checksum != NULL) is_locally_assembled = TRUE; /* Load the commit metadata into a dict */ g_autoptr(GVariantDict) commit_meta_dict = ({ g_autoptr(GVariant) commit_meta_v = NULL; g_assert (g_variant_dict_lookup (dict, "base-commit-meta", "@a{sv}", &commit_meta_v)); g_variant_dict_new (commit_meta_v); }); g_autoptr(GVariantDict) layered_commit_meta_dict = NULL; if (is_locally_assembled) { g_autoptr(GVariant) layered_commit_meta_v = NULL; g_assert (g_variant_dict_lookup (dict, "layered-commit-meta", "@a{sv}", &layered_commit_meta_v)); layered_commit_meta_dict = g_variant_dict_new (layered_commit_meta_v); } const gchar *source_title = NULL; g_variant_dict_lookup (commit_meta_dict, OSTREE_COMMIT_META_KEY_SOURCE_TITLE, "&s", &source_title); if (source_title) g_print (" %s %s\n", libsd_special_glyph (TREE_RIGHT), source_title); guint64 t = 0; if (is_locally_assembled) g_assert (g_variant_dict_lookup (dict, "base-timestamp", "t", &t)); else g_assert (g_variant_dict_lookup (dict, "timestamp", "t", &t)); g_autofree char *timestamp_string = rpmostree_timestamp_str_from_unix_utc (t); rpmostree_print_timestamp_version (version_string, timestamp_string, max_key_len); const gchar *live_inprogress; const gchar *live_replaced; if (!g_variant_dict_lookup (dict, "live-inprogress", "&s", &live_inprogress)) live_inprogress = NULL; if (!g_variant_dict_lookup (dict, "live-replaced", "&s", &live_replaced)) live_replaced = NULL; const gboolean have_live_changes = live_inprogress || live_replaced; const gboolean is_ostree_or_verbose = opt_verbose || refspectype == RPMOSTREE_REFSPEC_TYPE_OSTREE; if (is_locally_assembled && is_ostree_or_verbose) { if (have_live_changes) rpmostree_print_kv ("BootedBaseCommit", max_key_len, base_checksum); else rpmostree_print_kv ("BaseCommit", max_key_len, base_checksum); if (opt_verbose) print_origin_repos (FALSE, max_key_len, commit_meta_dict); if (opt_verbose || have_any_live_overlay) rpmostree_print_kv ("Commit", max_key_len, checksum); if (opt_verbose) print_origin_repos (TRUE, max_key_len, layered_commit_meta_dict); } else if (is_ostree_or_verbose) { if (have_live_changes) rpmostree_print_kv ("BootedCommit", max_key_len, checksum); if (!have_live_changes || opt_verbose) rpmostree_print_kv ("Commit", max_key_len, checksum); if (opt_verbose) print_origin_repos (FALSE, max_key_len, commit_meta_dict); } if (live_inprogress) { if (is_booted) g_print ("%s%s", get_red_start (), get_bold_start ()); rpmostree_print_kv ("InterruptedLiveCommit", max_key_len, live_inprogress); if (is_booted) g_print ("%s%s", get_bold_end (), get_red_end ()); } if (live_replaced) { if (is_booted) g_print ("%s%s", get_red_start (), get_bold_start ()); rpmostree_print_kv ("LiveCommit", max_key_len, live_replaced); if (is_booted) g_print ("%s%s", get_bold_end (), get_red_end ()); } gboolean is_staged = FALSE; g_variant_dict_lookup (dict, "staged", "b", &is_staged); if (opt_verbose && (is_staged || first)) rpmostree_print_kv ("Staged", max_key_len, is_staged ? "yes" : "no"); /* This used to be OSName; see https://github.com/ostreedev/ostree/pull/794 */ if (opt_verbose || have_multiple_stateroots) rpmostree_print_kv ("StateRoot", max_key_len, os_name); gboolean gpg_enabled; if (!g_variant_dict_lookup (dict, "gpg-enabled", "b", &gpg_enabled)) gpg_enabled = FALSE; if (gpg_enabled) rpmostree_print_gpg_info (signatures, opt_verbose, max_key_len); /* Print rpm diff and advisories summary if this is a pending deployment matching the * deployment on which the cached update is based. */ if (first && !is_booted && g_strcmp0 (os_name, booted_osname) == 0 && g_strcmp0 (id, cached_update_deployment_id) == 0) { g_auto(GVariantDict) dict; g_variant_dict_init (&dict, cached_update); g_autoptr(GVariant) rpm_diff = g_variant_dict_lookup_value (&dict, "rpm-diff", G_VARIANT_TYPE ("a{sv}")); g_autoptr(GVariant) advisories = g_variant_dict_lookup_value (&dict, "advisories", G_VARIANT_TYPE ("a(suuasa{sv})")); if (!rpmostree_print_diff_advisories (rpm_diff, advisories, opt_verbose, opt_verbose_advisories, max_key_len, error)) return FALSE; *out_printed_cached_update = TRUE; } /* print base overrides before overlays */ g_autoptr(GPtrArray) active_removals = g_ptr_array_new_with_free_func (g_free); if (origin_base_removals) { g_autoptr(GString) str = g_string_new (""); const guint n = g_variant_n_children (origin_base_removals); for (guint i = 0; i < n; i++) { g_autoptr(GVariant) gv_nevra; g_variant_get_child (origin_base_removals, i, "v", &gv_nevra); const char *name, *nevra; g_variant_get_child (gv_nevra, 0, "&s", &nevra); g_variant_get_child (gv_nevra, 1, "&s", &name); if (str->len) g_string_append (str, ", "); g_string_append (str, nevra); g_ptr_array_add (active_removals, g_strdup (name)); } g_ptr_array_add (active_removals, NULL); if (str->len) rpmostree_print_kv ("RemovedBasePackages", max_key_len, str->str); } /* only print inactive base removal requests in verbose mode */ if (origin_requested_base_removals && opt_verbose) print_packages ("InactiveBaseRemovals", max_key_len, origin_requested_base_removals, (const char *const*)active_removals->pdata); g_autoptr(GPtrArray) active_replacements = g_ptr_array_new_with_free_func (g_free); if (origin_base_local_replacements) { g_autoptr(GString) str = g_string_new (""); g_autoptr(GHashTable) grouped_diffs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_ptr_array_unref); const guint n = g_variant_n_children (origin_base_local_replacements); for (guint i = 0; i < n; i++) { g_autoptr(GVariant) gv_nevra_new; g_autoptr(GVariant) gv_nevra_old; g_variant_get_child (origin_base_local_replacements, i, "(vv)", &gv_nevra_new, &gv_nevra_old); const char *nevra_new, *name_new, *name_old; g_variant_get_child (gv_nevra_new, 0, "&s", &nevra_new); g_variant_get_child (gv_nevra_new, 1, "&s", &name_new); g_variant_get_child (gv_nevra_old, 1, "&s", &name_old); /* if pkgnames match, print a nicer version like treediff */ if (g_str_equal (name_new, name_old)) { /* let's just use str as a scratchpad to avoid excessive mallocs; the str * needs to be stretched anyway for the final output */ gsize original_size = str->len; gv_nevra_to_evr (str, gv_nevra_old); g_string_append (str, " -> "); gv_nevra_to_evr (str, gv_nevra_new); const char *diff = str->str + original_size; GPtrArray *pkgs = g_hash_table_lookup (grouped_diffs, diff); if (!pkgs) { pkgs = g_ptr_array_new_with_free_func (g_free); g_hash_table_insert (grouped_diffs, g_strdup (diff), pkgs); } g_ptr_array_add (pkgs, g_strdup (name_new)); g_string_truncate (str, original_size); } else { if (str->len) g_string_append (str, ", "); const char *nevra_old; g_variant_get_child (gv_nevra_old, 0, "&s", &nevra_old); g_string_append_printf (str, "%s -> %s", nevra_old, nevra_new); } g_ptr_array_add (active_replacements, g_strdup (nevra_new)); } GLNX_HASH_TABLE_FOREACH_KV (grouped_diffs, const char*, diff, GPtrArray*, pkgs) { if (str->len) g_string_append (str, ", "); for (guint i = 0, n = pkgs->len; i < n; i++) { const char *pkgname = g_ptr_array_index (pkgs, i); if (i > 0) g_string_append_c (str, ' '); g_string_append (str, pkgname); } g_string_append_c (str, ' '); g_string_append (str, diff); } g_ptr_array_add (active_replacements, NULL); if (str->len) rpmostree_print_kv ("ReplacedBasePackages", max_key_len, str->str); } if (origin_requested_base_local_replacements && opt_verbose) print_packages ("InactiveBaseReplacements", max_key_len, origin_requested_base_local_replacements, (const char *const*)active_replacements->pdata); /* only print inactive layering requests in verbose mode */ if (origin_requested_packages && opt_verbose) /* requested-packages - packages = inactive (i.e. dormant requests) */ print_packages ("InactiveRequests", max_key_len, origin_requested_packages, origin_packages); if (origin_packages) print_packages ("LayeredPackages", max_key_len, origin_packages, NULL); if (origin_requested_local_packages) print_packages ("LocalPackages", max_key_len, origin_requested_local_packages, NULL); if (regenerate_initramfs) { g_autoptr(GString) buf = g_string_new (""); g_autofree char **initramfs_args = NULL; g_variant_dict_lookup (dict, "initramfs-args", "^a&s", &initramfs_args); for (char **iter = initramfs_args; iter && *iter; iter++) { g_string_append (buf, *iter); g_string_append_c (buf, ' '); } if (buf->len == 0) g_string_append (buf, "regenerate"); rpmostree_print_kv ("Initramfs", max_key_len, buf->str); } gboolean pinned = FALSE; g_variant_dict_lookup (dict, "pinned", "b", &pinned); if (pinned) rpmostree_print_kv ("Pinned", max_key_len, "yes"); if (unlocked && g_strcmp0 (unlocked, "none") != 0) { g_print ("%s%s", get_red_start (), get_bold_start ()); rpmostree_print_kv ("Unlocked", max_key_len, unlocked); g_print ("%s%s", get_bold_end (), get_red_end ()); } const char *end_of_life_string = NULL; /* look for endoflife attribute in the deployment */ g_variant_dict_lookup (dict, "endoflife", "&s", &end_of_life_string); if (end_of_life_string) { g_print ("%s%s", get_red_start (), get_bold_start ()); rpmostree_print_kv ("EndOfLife", max_key_len, end_of_life_string); g_print ("%s%s", get_bold_end (), get_red_end ()); } return TRUE; } /* We will have an optimized path for the case where there are just * two deployments, this code will be the generic fallback. */ static gboolean print_deployments (RPMOSTreeSysroot *sysroot_proxy, GVariant *deployments, GVariant *cached_update, gboolean *out_printed_cached_update, GCancellable *cancellable, GError **error) { GVariantIter iter; /* First, gather global state */ const char *booted_osname = NULL; gboolean have_any_live_overlay = FALSE; gboolean have_multiple_stateroots = FALSE; const char *last_osname = NULL; g_variant_iter_init (&iter, deployments); while (TRUE) { g_autoptr(GVariant) child = g_variant_iter_next_value (&iter); if (!child) break; g_autoptr(GVariantDict) dict = g_variant_dict_new (child); const gchar *live_inprogress; if (!g_variant_dict_lookup (dict, "live-inprogress", "&s", &live_inprogress)) live_inprogress = NULL; const gchar *live_replaced; if (!g_variant_dict_lookup (dict, "live-replaced", "&s", &live_replaced)) live_replaced = NULL; const gboolean have_live_changes = live_inprogress || live_replaced; have_any_live_overlay = have_any_live_overlay || have_live_changes; const char *osname = NULL; g_assert (g_variant_dict_lookup (dict, "osname", "&s", &osname)); if (!last_osname) last_osname = osname; else if (!g_str_equal (osname, last_osname)) have_multiple_stateroots = TRUE; gboolean is_booted; if (!g_variant_dict_lookup (dict, "booted", "b", &is_booted)) is_booted = FALSE; if (is_booted) booted_osname = osname; } g_print ("Deployments:\n"); /* just unpack this so that each iteration doesn't have to dig for it */ const char *cached_update_deployment_id = NULL; if (cached_update) { g_auto(GVariantDict) dict; g_variant_dict_init (&dict, cached_update); g_variant_dict_lookup (&dict, "deployment", "&s", &cached_update_deployment_id); } g_variant_iter_init (&iter, deployments); gboolean first = TRUE; while (TRUE) { g_autoptr(GVariant) child = g_variant_iter_next_value (&iter); if (child == NULL) break; if (!print_one_deployment (sysroot_proxy, child, first, have_any_live_overlay, have_multiple_stateroots, booted_osname, cached_update_deployment_id, cached_update, out_printed_cached_update, error)) return FALSE; if (first) first = FALSE; } return TRUE; } gboolean rpmostree_builtin_status (int argc, char **argv, RpmOstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) { g_autoptr(GOptionContext) context = g_option_context_new (""); glnx_unref_object RPMOSTreeOS *os_proxy = NULL; glnx_unref_object RPMOSTreeSysroot *sysroot_proxy = NULL; _cleanup_peer_ GPid peer_pid = 0; GBusType bus_type; if (!rpmostree_option_context_parse (context, option_entries, &argc, &argv, invocation, cancellable, NULL, NULL, &sysroot_proxy, &peer_pid, &bus_type, error)) return FALSE; if (opt_json && opt_jsonpath) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Cannot specify both --json and --jsonpath"); return FALSE; } if (!rpmostree_load_os_proxy (sysroot_proxy, NULL, cancellable, &os_proxy, error)) return FALSE; g_autoptr(GVariant) deployments = rpmostree_sysroot_dup_deployments (sysroot_proxy); g_autoptr(GVariant) cached_update = NULL; if (rpmostree_os_get_has_cached_update_rpm_diff (os_proxy)) cached_update = rpmostree_os_dup_cached_update (os_proxy); if (opt_json || opt_jsonpath) { glnx_unref_object JsonBuilder *builder = json_builder_new (); json_builder_begin_object (builder); json_builder_set_member_name (builder, "deployments"); json_builder_add_value (builder, json_gvariant_serialize (deployments)); json_builder_set_member_name (builder, "transaction"); GVariant *txn = get_active_txn (sysroot_proxy); JsonNode *txn_node = txn ? json_gvariant_serialize (txn) : json_node_new (JSON_NODE_NULL); json_builder_add_value (builder, txn_node); json_builder_set_member_name (builder, "cached-update"); JsonNode *cached_update_node; if (cached_update) cached_update_node = json_gvariant_serialize (cached_update); else cached_update_node = json_node_new (JSON_NODE_NULL); json_builder_add_value (builder, cached_update_node); json_builder_end_object (builder); JsonNode *json_root = json_builder_get_root (builder); glnx_unref_object JsonGenerator *generator = json_generator_new (); if (opt_json) json_generator_set_root (generator, json_root); else { JsonNode *result = json_path_query (opt_jsonpath, json_root, error); if (!result) { g_prefix_error (error, "While compiling jsonpath: "); return FALSE; } json_generator_set_root (generator, result); json_node_free (result); } json_node_free (json_root); glnx_unref_object GOutputStream *stdout_gio = g_unix_output_stream_new (1, FALSE); /* NB: watch out for the misleading API docs */ if (json_generator_to_stream (generator, stdout_gio, NULL, error) <= 0 || (error != NULL && *error != NULL)) return FALSE; } else { if (!print_daemon_state (sysroot_proxy, bus_type, cancellable, error)) return FALSE; gboolean printed_cached_update = FALSE; if (!print_deployments (sysroot_proxy, deployments, cached_update, &printed_cached_update, cancellable, error)) return FALSE; const char *policy = rpmostree_sysroot_get_automatic_update_policy (sysroot_proxy); gboolean auto_updates_enabled = (!g_str_equal (policy, "none")); if (cached_update && !printed_cached_update && auto_updates_enabled) { g_print ("\n"); if (!rpmostree_print_cached_update (cached_update, opt_verbose, opt_verbose_advisories, cancellable, error)) return FALSE; } } return TRUE; }