/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2018 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.1 of the License, 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, see . * * Authors: * Alexander Larsson */ #include "config.h" #include "flatpak-cli-transaction.h" #include "flatpak-transaction-private.h" #include "flatpak-installation-private.h" #include "flatpak-run-private.h" #include "flatpak-table-printer.h" #include "flatpak-utils-private.h" #include "flatpak-error.h" #include struct _FlatpakCliTransaction { FlatpakTransaction parent; gboolean disable_interaction; gboolean stop_on_first_error; gboolean non_default_arch; GError *first_operation_error; GHashTable *eol_actions; GHashTable *runtime_app_map; GHashTable *extension_app_map; int rows; int cols; int table_width; int table_height; int n_ops; int op; int op_progress; gboolean installing; gboolean updating; gboolean uninstalling; int download_col; FlatpakTablePrinter *printer; int progress_row; char *progress_msg; int speed_len; gboolean did_interaction; }; struct _FlatpakCliTransactionClass { FlatpakTransactionClass parent_class; }; G_DEFINE_TYPE (FlatpakCliTransaction, flatpak_cli_transaction, FLATPAK_TYPE_TRANSACTION); static int choose_remote_for_ref (FlatpakTransaction *transaction, const char *for_ref, const char *runtime_ref, const char * const *remotes) { FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction); int n_remotes = g_strv_length ((char **) remotes); int chosen = -1; const char *pref; pref = strchr (for_ref, '/') + 1; self->did_interaction = TRUE; if (self->disable_interaction) { g_print (_("Required runtime for %s (%s) found in remote %s\n"), pref, runtime_ref, remotes[0]); chosen = 0; } else if (n_remotes == 1) { g_print (_("Required runtime for %s (%s) found in remote %s\n"), pref, runtime_ref, remotes[0]); if (flatpak_yes_no_prompt (TRUE, _("Do you want to install it?"))) chosen = 0; } else { flatpak_format_choices ((const char **) remotes, _("Required runtime for %s (%s) found in remotes:"), pref, runtime_ref); chosen = flatpak_number_prompt (TRUE, 0, n_remotes, _("Which do you want to install (0 to abort)?")); chosen -= 1; /* convert from base-1 to base-0 (and -1 to abort) */ } return chosen; } static gboolean add_new_remote (FlatpakTransaction *transaction, FlatpakTransactionRemoteReason reason, const char *from_id, const char *remote_name, const char *url) { FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction); self->did_interaction = TRUE; if (self->disable_interaction) { g_print (_("Configuring %s as new remote '%s'\n"), url, remote_name); return TRUE; } if (reason == FLATPAK_TRANSACTION_REMOTE_GENERIC_REPO) { if (flatpak_yes_no_prompt (TRUE, /* default to yes on Enter */ _("The remote '%s', referred to by '%s' at location %s contains additional applications.\n" "Should the remote be kept for future installations?"), remote_name, from_id, url)) return TRUE; } else if (reason == FLATPAK_TRANSACTION_REMOTE_RUNTIME_DEPS) { if (flatpak_yes_no_prompt (TRUE, /* default to yes on Enter */ _("The application %s depends on runtimes from:\n %s\n" "Configure this as new remote '%s'"), from_id, url, remote_name)) return TRUE; } return FALSE; } static void install_authenticator (FlatpakTransaction *old_transaction, const char *remote, const char *ref) { FlatpakCliTransaction *old_cli = FLATPAK_CLI_TRANSACTION (old_transaction); g_autoptr(FlatpakTransaction) transaction2 = NULL; g_autoptr(GError) local_error = NULL; FlatpakInstallation *installation = flatpak_transaction_get_installation (old_transaction); FlatpakDir *dir = flatpak_installation_get_dir (installation, NULL); if (dir == NULL) { /* This should not happen */ g_warning ("No dir in install_authenticator"); return; } old_cli->did_interaction = TRUE; transaction2 = flatpak_cli_transaction_new (dir, old_cli->disable_interaction, TRUE, FALSE, &local_error); if (transaction2 == NULL) { g_printerr ("Unable to install authenticator: %s\n", local_error->message); return; } g_print ("Installing required authenticator for remote %s\n", remote); if (!flatpak_transaction_add_install (transaction2, remote, ref, NULL, &local_error)) { if (!g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED)) g_printerr ("Unable to install authenticator: %s\n", local_error->message); return; } if (!flatpak_transaction_run (transaction2, NULL, &local_error)) { if (!g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_ABORTED)) g_printerr ("Unable to install authenticator: %s\n", local_error->message); return; } return; } static gboolean redraw (FlatpakCliTransaction *self) { int top; int row; int current_row; int current_col; int skip; /* We may have resized and thus repositioned the cursor since last redraw */ flatpak_get_window_size (&self->rows, &self->cols); if (flatpak_get_cursor_pos (¤t_row, ¤t_col)) { /* We're currently displaying the last row of the table, extept the very first time where the user pressed return for the prompt causing us to scroll down one extra row */ top = current_row - self->table_height + 1; if (top > 0) { row = top; skip = 0; } else { row = 1; skip = 1 - top; } g_print (FLATPAK_ANSI_ROW_N FLATPAK_ANSI_CLEAR, row); // we update table_height and end_row here, since we might have added to the table flatpak_table_printer_print_full (self->printer, skip, self->cols, &self->table_height, &self->table_width); return TRUE; } return FALSE; } static void set_op_progress (FlatpakCliTransaction *self, FlatpakTransactionOperation *op, const char *progress) { if (flatpak_fancy_output ()) { int row = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (op), "row")); g_autofree char *cell = g_strdup_printf ("[%s]", progress); flatpak_table_printer_set_cell (self->printer, row, 1, cell); } } static void spin_op_progress (FlatpakCliTransaction *self, FlatpakTransactionOperation *op) { const char *p[] = { "|", "/", "—", "\\", }; set_op_progress (self, op, p[self->op_progress++ % G_N_ELEMENTS (p)]); } static char * format_duration (guint64 duration) { int h, m, s; m = duration / 60; s = duration % 60; h = m / 60; m = m % 60; if (h > 0) return g_strdup_printf ("%02d:%02d:%02d", h, m, s); else return g_strdup_printf ("%02d:%02d", m, s); } static void progress_changed_cb (FlatpakTransactionProgress *progress, gpointer data) { FlatpakCliTransaction *cli = data; FlatpakTransaction *self = FLATPAK_TRANSACTION (cli); g_autoptr(FlatpakTransactionOperation) op = flatpak_transaction_get_current_operation (self); g_autoptr(GString) str = g_string_new (""); int i; int n_full, partial; g_autofree char *speed = NULL; int bar_length; const char *partial_blocks[] = { " ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", }; const char *full_block = "█"; guint percent = flatpak_transaction_progress_get_progress (progress); guint64 start_time = flatpak_transaction_progress_get_start_time (progress); guint64 elapsed_time = (g_get_monotonic_time () - start_time) / G_USEC_PER_SEC; guint64 transferred = flatpak_transaction_progress_get_bytes_transferred (progress); guint64 max = flatpak_transaction_operation_get_download_size (op); if (elapsed_time > 0) { g_autofree char *formatted_bytes_sec = g_format_size (transferred / elapsed_time); g_autofree char *remaining = NULL; if (elapsed_time > 3 && percent > 0) { guint64 total_time = elapsed_time * 100 / (double) percent; remaining = format_duration (total_time - elapsed_time); } speed = g_strdup_printf ("%s/s%s%s", formatted_bytes_sec, remaining ? " " : "", remaining ? remaining : ""); cli->speed_len = MAX (cli->speed_len, strlen (speed) + 2); } spin_op_progress (cli, op); bar_length = MIN (20, cli->table_width - (strlen (cli->progress_msg) + 6 + cli->speed_len)); n_full = (bar_length * percent) / 100; partial = (((bar_length * percent) % 100) * G_N_ELEMENTS (partial_blocks)) / 100; /* The above should guarantee this: */ g_assert (partial >= 0); g_assert (partial < G_N_ELEMENTS (partial_blocks)); g_string_append (str, cli->progress_msg); g_string_append (str, " "); if (flatpak_fancy_output ()) g_string_append (str, FLATPAK_ANSI_FAINT_ON); for (i = 0; i < n_full; i++) g_string_append (str, full_block); if (i < bar_length) { g_string_append (str, partial_blocks[partial]); i++; } if (flatpak_fancy_output ()) g_string_append (str, FLATPAK_ANSI_FAINT_OFF); for (; i < bar_length; i++) g_string_append (str, " "); g_string_append (str, " "); g_string_append_printf (str, "%3d%%", percent); if (speed) g_string_append_printf (str, " %s", speed); if (flatpak_fancy_output ()) { flatpak_table_printer_set_cell (cli->printer, cli->progress_row, 0, str->str); if (flatpak_transaction_operation_get_operation_type (op) != FLATPAK_TRANSACTION_OPERATION_UNINSTALL) { g_autofree char *formatted_max = NULL; g_autofree char *formatted = NULL; g_autofree char *text = NULL; int row; // avoid "bytes" formatted = transferred < 1000 ? g_format_size (1000) : g_format_size (transferred); formatted_max = max < 1000 ? g_format_size (1000) : g_format_size (max); text = g_strdup_printf ("%s / %s", formatted, formatted_max); row = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (op), "row")); flatpak_table_printer_set_decimal_cell (cli->printer, row, cli->download_col, text); } if (!redraw (cli)) g_print ("\r%s", str->str); /* redraw failed, just update the progress */ } else g_print ("%s\n", str->str); } static void set_progress (FlatpakCliTransaction *self, const char *text) { flatpak_table_printer_set_cell (self->printer, self->progress_row, 0, text); } static void new_operation (FlatpakTransaction *transaction, FlatpakTransactionOperation *op, FlatpakTransactionProgress *progress) { FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction); FlatpakTransactionOperationType op_type = flatpak_transaction_operation_get_operation_type (op); g_autofree char *text = NULL; self->op++; self->op_progress = 0; switch (op_type) { case FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE: case FLATPAK_TRANSACTION_OPERATION_INSTALL: if (self->n_ops == 1) text = g_strdup (_("Installing…")); else text = g_strdup_printf (_("Installing %d/%d…"), self->op, self->n_ops); break; case FLATPAK_TRANSACTION_OPERATION_UPDATE: if (self->n_ops == 1) text = g_strdup (_("Updating…")); else text = g_strdup_printf (_("Updating %d/%d…"), self->op, self->n_ops); break; case FLATPAK_TRANSACTION_OPERATION_UNINSTALL: if (self->n_ops == 1) text = g_strdup (_("Uninstalling…")); else text = g_strdup_printf (_("Uninstalling %d/%d…"), self->op, self->n_ops); break; default: g_assert_not_reached (); break; } if (flatpak_fancy_output ()) { set_progress (self, text); spin_op_progress (self, op); redraw (self); } else g_print ("%s\n", text); g_free (self->progress_msg); self->progress_msg = g_steal_pointer (&text); g_signal_connect (progress, "changed", G_CALLBACK (progress_changed_cb), self); flatpak_transaction_progress_set_update_frequency (progress, FLATPAK_CLI_UPDATE_INTERVAL_MS); } static void operation_done (FlatpakTransaction *transaction, FlatpakTransactionOperation *op, const char *commit, FlatpakTransactionResult details) { FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction); FlatpakTransactionOperationType op_type = flatpak_transaction_operation_get_operation_type (op); if (op_type == FLATPAK_TRANSACTION_OPERATION_UNINSTALL) set_op_progress (self, op, FLATPAK_ANSI_GREEN "-" FLATPAK_ANSI_COLOR_RESET); else set_op_progress (self, op, FLATPAK_ANSI_GREEN "✓" FLATPAK_ANSI_COLOR_RESET); if (flatpak_fancy_output ()) redraw (self); } static gboolean operation_error (FlatpakTransaction *transaction, FlatpakTransactionOperation *op, const GError *error, FlatpakTransactionErrorDetails detail) { FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction); FlatpakTransactionOperationType op_type = flatpak_transaction_operation_get_operation_type (op); const char *ref = flatpak_transaction_operation_get_ref (op); g_autoptr(FlatpakRef) rref = flatpak_ref_parse (ref, NULL); gboolean non_fatal = (detail & FLATPAK_TRANSACTION_ERROR_DETAILS_NON_FATAL) != 0; g_autofree char *text = NULL; const char *on = ""; const char *off = ""; if (flatpak_fancy_output ()) { on = FLATPAK_ANSI_BOLD_ON; off = FLATPAK_ANSI_BOLD_OFF; } if (g_error_matches (error, FLATPAK_ERROR, FLATPAK_ERROR_SKIPPED)) { set_op_progress (self, op, "⍻"); text = g_strdup_printf (_("Info: %s was skipped"), flatpak_ref_get_name (rref)); if (flatpak_fancy_output ()) { flatpak_table_printer_set_cell (self->printer, self->progress_row, 0, text); self->progress_row++; flatpak_table_printer_add_span (self->printer, ""); flatpak_table_printer_finish_row (self->printer); redraw (self); } else g_print ("%s\n", text); return TRUE; } set_op_progress (self, op, "✗"); /* Here we go to great lengths not to split the sentences. See * https://wiki.gnome.org/TranslationProject/DevGuidelines/Never%20split%20sentences */ if (g_error_matches (error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED)) { if (non_fatal) text = g_strdup_printf (_("Warning: %s%s%s already installed"), on, flatpak_ref_get_name (rref), off); else text = g_strdup_printf (_("Error: %s%s%s already installed"), on, flatpak_ref_get_name (rref), off); } else if (g_error_matches (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED)) { if (non_fatal) text = g_strdup_printf (_("Warning: %s%s%s not installed"), on, flatpak_ref_get_name (rref), off); else text = g_strdup_printf (_("Error: %s%s%s not installed"), on, flatpak_ref_get_name (rref), off); } else if (g_error_matches (error, FLATPAK_ERROR, FLATPAK_ERROR_NEED_NEW_FLATPAK)) { if (non_fatal) text = g_strdup_printf (_("Warning: %s%s%s needs a later flatpak version"), on, flatpak_ref_get_name (rref), off); else text = g_strdup_printf (_("Error: %s%s%s needs a later flatpak version"), on, flatpak_ref_get_name (rref), off); } else if (g_error_matches (error, FLATPAK_ERROR, FLATPAK_ERROR_OUT_OF_SPACE)) { if (non_fatal) text = g_strdup (_("Warning: Not enough disk space to complete this operation")); else text = g_strdup (_("Error: Not enough disk space to complete this operation")); } else if (error) { if (non_fatal) text = g_strdup_printf (_("Warning: %s"), error->message); else text = g_strdup_printf (_("Error: %s"), error->message); } else text = g_strdup ("(internal error, please report)"); if (!non_fatal && self->first_operation_error == NULL) { /* Here we go to great lengths not to split the sentences. See * https://wiki.gnome.org/TranslationProject/DevGuidelines/Never%20split%20sentences */ switch (op_type) { case FLATPAK_TRANSACTION_OPERATION_INSTALL: g_propagate_prefixed_error (&self->first_operation_error, g_error_copy (error), _("Failed to install %s%s%s: "), on, flatpak_ref_get_name (rref), off); break; case FLATPAK_TRANSACTION_OPERATION_UPDATE: g_propagate_prefixed_error (&self->first_operation_error, g_error_copy (error), _("Failed to update %s%s%s: "), on, flatpak_ref_get_name (rref), off); break; case FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE: g_propagate_prefixed_error (&self->first_operation_error, g_error_copy (error), _("Failed to install bundle %s%s%s: "), on, flatpak_ref_get_name (rref), off); break; case FLATPAK_TRANSACTION_OPERATION_UNINSTALL: g_propagate_prefixed_error (&self->first_operation_error, g_error_copy (error), _("Failed to uninstall %s%s%s: "), on, flatpak_ref_get_name (rref), off); break; default: g_assert_not_reached (); } } if (flatpak_fancy_output ()) { flatpak_table_printer_set_cell (self->printer, self->progress_row, 0, text); self->progress_row++; flatpak_table_printer_add_span (self->printer, ""); flatpak_table_printer_finish_row (self->printer); redraw (self); } else g_printerr ("%s\n", text); if (!non_fatal && self->stop_on_first_error) return FALSE; return TRUE; /* Continue */ } static gboolean webflow_start (FlatpakTransaction *transaction, const char *remote, const char *url, GVariant *options, guint id) { FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction); const char *browser; g_autoptr(GError) local_error = NULL; const char *args[3] = { NULL, url, NULL }; self->did_interaction = TRUE; if (!self->disable_interaction) { g_print (_("Authentication required for remote '%s'\n"), remote); if (!flatpak_yes_no_prompt (TRUE, _("Open browser?"))) return FALSE; } /* Allow hard overrides with $BROWSER */ browser = g_getenv ("BROWSER"); if (browser != NULL) { args[0] = browser; if (!g_spawn_async (NULL, (char **)args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &local_error)) { g_printerr ("Failed to start browser %s: %s\n", browser, local_error->message); return FALSE; } } else { if (!g_app_info_launch_default_for_uri (url, NULL, &local_error)) { g_printerr ("Failed to show url: %s\n", local_error->message); return FALSE; } } g_print ("Waiting for browser...\n"); return TRUE; } static void webflow_done (FlatpakTransaction *transaction, GVariant *options, guint id) { g_print ("Browser done\n"); } static gboolean basic_auth_start (FlatpakTransaction *transaction, const char *remote, const char *realm, GVariant *options, guint id) { FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction); char *user, *password, *previous_error = NULL; if (self->disable_interaction) return FALSE; self->did_interaction = TRUE; if (g_variant_lookup (options, "previous-error", "&s", &previous_error)) g_print ("%s\n", previous_error); g_print (_("Login required remote %s (realm %s)\n"), remote, realm); user = flatpak_prompt (FALSE, _("User")); if (user == NULL) return FALSE; password = flatpak_password_prompt (_("Password")); if (password == NULL) return FALSE; flatpak_transaction_complete_basic_auth (transaction, id, user, password, NULL); return TRUE; } typedef enum { EOL_UNDECIDED, EOL_IGNORE, /* Don't do anything, we already printed a warning */ EOL_NO_REBASE, /* Choose to not rebase */ EOL_REBASE, /* Choose to rebase */ } EolAction; static void print_eol_info_message (FlatpakDir *dir, FlatpakDecomposed *ref, const char *ref_name, const char *rebased_to_ref, const char *reason) { gboolean is_pinned = flatpak_dir_ref_is_pinned (dir, flatpak_decomposed_get_ref (ref)); g_autofree char *ref_branch = flatpak_decomposed_dup_branch (ref); const char *on = ""; const char *off = ""; if (flatpak_fancy_output ()) { on = FLATPAK_ANSI_BOLD_ON; off = FLATPAK_ANSI_BOLD_OFF; } /* Here we go to great lengths not to split the sentences. See * https://wiki.gnome.org/TranslationProject/DevGuidelines/Never%20split%20sentences */ if (rebased_to_ref) { g_autoptr(FlatpakDecomposed) eolr_decomposed = NULL; g_autofree char *eolr_name = NULL; const char *eolr_branch; eolr_decomposed = flatpak_decomposed_new_from_ref (rebased_to_ref, NULL); /* These are guarantees from FlatpakTransaction */ g_assert (eolr_decomposed != NULL); g_assert (flatpak_decomposed_get_kind (ref) == flatpak_decomposed_get_kind (eolr_decomposed)); eolr_name = flatpak_decomposed_dup_id (eolr_decomposed); eolr_branch = flatpak_decomposed_get_branch (eolr_decomposed); if (is_pinned) { /* Only runtimes can be pinned */ g_print (_("\nInfo: (pinned) runtime %s%s%s branch %s%s%s is end-of-life, in favor of %s%s%s branch %s%s%s\n"), on, ref_name, off, on, ref_branch, off, on, eolr_name, off, on, eolr_branch, off); } else { if (flatpak_decomposed_is_runtime (ref)) g_print (_("\nInfo: runtime %s%s%s branch %s%s%s is end-of-life, in favor of %s%s%s branch %s%s%s\n"), on, ref_name, off, on, ref_branch, off, on, eolr_name, off, on, eolr_branch, off); else g_print (_("\nInfo: app %s%s%s branch %s%s%s is end-of-life, in favor of %s%s%s branch %s%s%s\n"), on, ref_name, off, on, ref_branch, off, on, eolr_name, off, on, eolr_branch, off); } } else if (reason) { if (is_pinned) { /* Only runtimes can be pinned */ g_print (_("\nInfo: (pinned) runtime %s%s%s branch %s%s%s is end-of-life, with reason:\n"), on, ref_name, off, on, ref_branch, off); } else { if (flatpak_decomposed_is_runtime (ref)) g_print (_("\nInfo: runtime %s%s%s branch %s%s%s is end-of-life, with reason:\n"), on, ref_name, off, on, ref_branch, off); else g_print (_("\nInfo: app %s%s%s branch %s%s%s is end-of-life, with reason:\n"), on, ref_name, off, on, ref_branch, off); } g_print (" %s\n", reason); } } static void check_current_transaction_for_dependent_apps (GPtrArray *apps, FlatpakTransaction *transaction, FlatpakDecomposed *ref) { g_autoptr(FlatpakTransactionOperation) ref_op = NULL; GPtrArray *related_ops; ref_op = flatpak_transaction_get_operation_for_ref (transaction, NULL, flatpak_decomposed_get_ref (ref), NULL); g_assert (ref_op != NULL); /* Get the related ops to find any apps that use @ref as a runtime or extension */ related_ops = flatpak_transaction_operation_get_related_to_ops (ref_op); if (related_ops == NULL) return; for (int i = 0; i < related_ops->len; i++) { FlatpakTransactionOperation *related_op = g_ptr_array_index (related_ops, i); const char *related_op_ref = flatpak_transaction_operation_get_ref (related_op); g_autoptr(FlatpakDecomposed) related_op_decomposed = flatpak_decomposed_new_from_ref (related_op_ref, NULL); if (related_op_decomposed == NULL) continue; if (flatpak_decomposed_id_is_subref (related_op_decomposed)) continue; /* Recurse in case @ref was a runtime extension. We need to check since a * runtime can have a runtime extension in its related ops in the * extra-data case, so if we recurse unconditionally it could be infinite * recursion. */ if (flatpak_decomposed_is_runtime (related_op_decomposed)) { GKeyFile *metadata = flatpak_transaction_operation_get_metadata (ref_op); if (g_key_file_has_group (metadata, FLATPAK_METADATA_GROUP_EXTENSION_OF)) check_current_transaction_for_dependent_apps (apps, transaction, related_op_decomposed); } else if (!g_ptr_array_find_with_equal_func (apps, related_op_decomposed, (GEqualFunc)flatpak_decomposed_equal, NULL)) g_ptr_array_add (apps, g_steal_pointer (&related_op_decomposed)); } } static GPtrArray * find_reverse_dep_apps (FlatpakTransaction *transaction, FlatpakDir *dir, FlatpakDecomposed *ref, gboolean *out_is_extension) { FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction); g_autoptr(GPtrArray) apps = NULL; g_autoptr(GError) local_error = NULL; g_assert (out_is_extension); *out_is_extension = flatpak_dir_is_runtime_extension (dir, ref); if (*out_is_extension) { /* Find apps which are using the ref as an extension directly or as an * extension of their runtime. */ apps = flatpak_dir_list_app_refs_with_runtime_extension (dir, &self->runtime_app_map, &self->extension_app_map, ref, NULL, &local_error); if (apps == NULL) { g_info ("Unable to list apps using extension %s: %s\n", flatpak_decomposed_get_ref (ref), local_error->message); return NULL; } } else { /* Find any apps using the runtime directly */ apps = flatpak_dir_list_app_refs_with_runtime (dir, &self->runtime_app_map, ref, NULL, &local_error); if (apps == NULL) { g_info ("Unable to find apps using runtime %s: %s\n", flatpak_decomposed_get_ref (ref), local_error->message); return NULL; } } /* Also check the current transaction since it's possible the EOL ref * and/or any app(s) that depend on it are not installed. It's also * possible the current transaction updates one of the apps to a * newer runtime but we don't handle that yet * (https://github.com/flatpak/flatpak/issues/4832) */ check_current_transaction_for_dependent_apps (apps, transaction, ref); return g_steal_pointer (&apps); } static gboolean end_of_lifed_with_rebase (FlatpakTransaction *transaction, const char *remote, const char *ref_str, const char *reason, const char *rebased_to_ref, const char **previous_ids) { FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction); g_autoptr(FlatpakDecomposed) ref = flatpak_decomposed_new_from_ref (ref_str, NULL); g_autofree char *name = NULL; EolAction action = EOL_UNDECIDED; EolAction old_action = EOL_UNDECIDED; gboolean can_rebase = rebased_to_ref != NULL && remote != NULL; FlatpakInstallation *installation = flatpak_transaction_get_installation (transaction); FlatpakDir *dir = flatpak_installation_get_dir (installation, NULL); if (ref == NULL) return FALSE; /* Shouldn't happen, the ref should be valid */ name = flatpak_decomposed_dup_id (ref); self->did_interaction = TRUE; if (flatpak_decomposed_id_is_subref (ref)) { GLNX_HASH_TABLE_FOREACH_KV (self->eol_actions, FlatpakDecomposed *, eoled_ref, gpointer, value) { guint old_eol_action = GPOINTER_TO_UINT (value); if (flatpak_decomposed_id_is_subref_of (ref, eoled_ref)) { old_action = old_eol_action; /* Do the same */ break; } } } if (old_action != EOL_UNDECIDED) { switch (old_action) { default: case EOL_IGNORE: if (!can_rebase) action = EOL_IGNORE; /* Else, ask if we want to rebase */ break; case EOL_REBASE: case EOL_NO_REBASE: if (can_rebase) action = old_action; else action = EOL_IGNORE; } } if (action == EOL_UNDECIDED) { action = EOL_IGNORE; print_eol_info_message (dir, ref, name, rebased_to_ref, reason); if (flatpak_decomposed_is_runtime (ref) && !rebased_to_ref) { gboolean is_extension; g_autoptr(GPtrArray) apps = find_reverse_dep_apps (transaction, dir, ref, &is_extension); if (apps && apps->len > 0) { if (is_extension) g_print (_("Info: applications using this extension:\n")); else g_print (_("Info: applications using this runtime:\n")); g_print (" "); for (guint i = 0; i < apps->len; i++) { FlatpakDecomposed *app_ref = g_ptr_array_index (apps, i); g_autofree char *id = flatpak_decomposed_dup_id (app_ref); if (i != 0) g_print (", "); g_print ("%s", id); } g_print ("\n"); } } if (rebased_to_ref && remote) { /* The context for this prompt is in print_eol_info_message() */ if (self->disable_interaction || flatpak_yes_no_prompt (TRUE, _("Replace?"))) { if (self->disable_interaction) g_print (_("Updating to rebased version\n")); action = EOL_REBASE; } else action = EOL_NO_REBASE; } } else { g_info ("%s is end-of-life, using action from parent ref", name); } /* Cache for later comparison and reuse */ g_hash_table_insert (self->eol_actions, flatpak_decomposed_ref (ref), GUINT_TO_POINTER (action)); if (action == EOL_REBASE) { g_autoptr(GError) error = NULL; if (!flatpak_transaction_add_rebase (transaction, remote, rebased_to_ref, NULL, previous_ids, &error)) { g_propagate_prefixed_error (&self->first_operation_error, g_error_copy (error), _("Failed to rebase %s to %s: "), name, rebased_to_ref); return FALSE; } if (!flatpak_transaction_add_uninstall (transaction, ref_str, &error)) { /* NOT_INSTALLED error is expected in case the op that triggered this was install not update */ if (g_error_matches (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED)) g_clear_error (&error); else { g_propagate_prefixed_error (&self->first_operation_error, g_error_copy (error), _("Failed to uninstall %s for rebase to %s: "), name, rebased_to_ref); return FALSE; } } return TRUE; /* skip install/update op of end-of-life ref */ } else /* IGNORE or NO_REBASE */ return FALSE; } static int cmpstringp (const void *p1, const void *p2) { return strcmp (*(char * const *) p1, *(char * const *) p2); } static void append_permissions (GPtrArray *permissions, GKeyFile *metadata, GKeyFile *old_metadata, const char *group) { g_auto(GStrv) options = g_key_file_get_string_list (metadata, FLATPAK_METADATA_GROUP_CONTEXT, group, NULL, NULL); g_auto(GStrv) old_options = NULL; int i; if (options == NULL) return; qsort (options, g_strv_length (options), sizeof (const char *), cmpstringp); if (old_metadata) old_options = g_key_file_get_string_list (old_metadata, FLATPAK_METADATA_GROUP_CONTEXT, group, NULL, NULL); for (i = 0; options[i] != NULL; i++) { const char *option = options[i]; if (option[0] == '!') continue; if (old_options && g_strv_contains ((const char * const *) old_options, option)) continue; if (strcmp (group, FLATPAK_METADATA_KEY_DEVICES) == 0 && strcmp (option, "all") == 0) option = "devices"; g_ptr_array_add (permissions, g_strdup (option)); } } static void append_bus (GPtrArray *talk, GPtrArray *own, GKeyFile *metadata, GKeyFile *old_metadata, const char *group) { g_auto(GStrv) keys = NULL; gsize i, keys_count; keys = g_key_file_get_keys (metadata, group, &keys_count, NULL); if (keys == NULL) return; qsort (keys, g_strv_length (keys), sizeof (const char *), cmpstringp); for (i = 0; i < keys_count; i++) { const char *key = keys[i]; g_autofree char *value = g_key_file_get_string (metadata, group, key, NULL); if (g_strcmp0 (value, "none") == 0) continue; if (old_metadata) { g_autofree char *old_value = g_key_file_get_string (old_metadata, group, key, NULL); if (g_strcmp0 (old_value, value) == 0) continue; } if (g_strcmp0 (value, "own") == 0) g_ptr_array_add (own, g_strdup (key)); else g_ptr_array_add (talk, g_strdup (key)); } } static void append_tags (GPtrArray *tags_array, GKeyFile *metadata, GKeyFile *old_metadata) { gsize i, size = 0; g_auto(GStrv) tags = g_key_file_get_string_list (metadata, FLATPAK_METADATA_GROUP_APPLICATION, "tags", &size, NULL); g_auto(GStrv) old_tags = NULL; if (old_metadata) old_tags = g_key_file_get_string_list (old_metadata, FLATPAK_METADATA_GROUP_APPLICATION, "tags", NULL, NULL); for (i = 0; i < size; i++) { const char *tag = tags[i]; if (old_tags == NULL || !g_strv_contains ((const char * const *) old_tags, tag)) g_ptr_array_add (tags_array, g_strdup (tag)); } } static void print_perm_line (int idx, GPtrArray *items, int cols) { g_autoptr(GString) res = g_string_new (NULL); int i; g_string_append_printf (res, " [%d] %s", idx, (char *) items->pdata[0]); for (i = 1; i < items->len; i++) { char *p; int len; p = strrchr (res->str, '\n'); if (!p) p = res->str; len = (res->str + strlen (res->str)) - p; if (len + strlen ((char *) items->pdata[i]) + 2 >= cols) g_string_append_printf (res, ",\n %s", (char *) items->pdata[i]); else g_string_append_printf (res, ", %s", (char *) items->pdata[i]); } g_print ("%s\n", res->str); } static void print_permissions (FlatpakCliTransaction *self, const char *ref, GKeyFile *metadata, GKeyFile *old_metadata) { g_autoptr(FlatpakRef) rref = flatpak_ref_parse (ref, NULL); g_autoptr(GPtrArray) permissions = g_ptr_array_new_with_free_func (g_free); g_autoptr(GPtrArray) files = g_ptr_array_new_with_free_func (g_free); g_autoptr(GPtrArray) session_bus_talk = g_ptr_array_new_with_free_func (g_free); g_autoptr(GPtrArray) session_bus_own = g_ptr_array_new_with_free_func (g_free); g_autoptr(GPtrArray) system_bus_talk = g_ptr_array_new_with_free_func (g_free); g_autoptr(GPtrArray) system_bus_own = g_ptr_array_new_with_free_func (g_free); g_autoptr(GPtrArray) tags = g_ptr_array_new_with_free_func (g_free); g_autoptr(FlatpakTablePrinter) printer = NULL; int max_permission_width; int n_permission_cols; int i, j; int rows, cols; int table_rows, table_cols; const char *on = ""; const char *off = ""; if (flatpak_fancy_output ()) { on = FLATPAK_ANSI_BOLD_ON; off = FLATPAK_ANSI_BOLD_OFF; } if (metadata == NULL) return; /* Only apps have permissions */ if (flatpak_ref_get_kind (rref) != FLATPAK_REF_KIND_APP) return; append_permissions (permissions, metadata, old_metadata, FLATPAK_METADATA_KEY_SHARED); append_permissions (permissions, metadata, old_metadata, FLATPAK_METADATA_KEY_SOCKETS); append_permissions (permissions, metadata, old_metadata, FLATPAK_METADATA_KEY_DEVICES); append_permissions (permissions, metadata, old_metadata, FLATPAK_METADATA_KEY_FEATURES); append_permissions (files, metadata, old_metadata, FLATPAK_METADATA_KEY_FILESYSTEMS); append_bus (session_bus_talk, session_bus_own, metadata, old_metadata, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY); append_bus (system_bus_talk, system_bus_own, metadata, old_metadata, FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY); append_tags (tags, metadata, old_metadata); j = 1; if (files->len > 0) g_ptr_array_add (permissions, g_strdup_printf ("file access [%d]", j++)); if (session_bus_talk->len > 0) g_ptr_array_add (permissions, g_strdup_printf ("dbus access [%d]", j++)); if (session_bus_own->len > 0) g_ptr_array_add (permissions, g_strdup_printf ("bus ownership [%d]", j++)); if (system_bus_talk->len > 0) g_ptr_array_add (permissions, g_strdup_printf ("system dbus access [%d]", j++)); if (system_bus_own->len > 0) g_ptr_array_add (permissions, g_strdup_printf ("system bus ownership [%d]", j++)); if (tags->len > 0) g_ptr_array_add (permissions, g_strdup_printf ("tags [%d]", j++)); /* Early exit if no (or no new) permissions */ if (permissions->len == 0) return; g_print ("\n"); if (old_metadata) g_print (_("New %s%s%s permissions:"), on, flatpak_ref_get_name (rref), off); else g_print (_("%s%s%s permissions:"), on, flatpak_ref_get_name (rref), off); g_print ("\n"); flatpak_get_window_size (&rows, &cols); max_permission_width = 0; for (i = 0; i < permissions->len; i++) max_permission_width = MAX (max_permission_width, strlen (g_ptr_array_index (permissions, i))); /* At least 4 columns, but more if we're guaranteed to fit */ n_permission_cols = MAX (4, cols / (max_permission_width + 4)); printer = flatpak_table_printer_new (); for (i = 0; i < permissions->len; i++) { char *perm = g_ptr_array_index (permissions, i); if (i % n_permission_cols == 0) { g_autofree char *text = NULL; if (i > 0) flatpak_table_printer_finish_row (printer); text = g_strdup_printf (" %s", perm); flatpak_table_printer_add_column (printer, text); } else flatpak_table_printer_add_column (printer, perm); } flatpak_table_printer_finish_row (printer); for (i = 0; i < n_permission_cols; i++) flatpak_table_printer_set_column_expand (printer, i, TRUE); flatpak_table_printer_print_full (printer, 0, cols, &table_rows, &table_cols); g_print ("\n\n"); j = 1; if (files->len > 0) print_perm_line (j++, files, cols); if (session_bus_talk->len > 0) print_perm_line (j++, session_bus_talk, cols); if (session_bus_own->len > 0) print_perm_line (j++, session_bus_own, cols); if (system_bus_talk->len > 0) print_perm_line (j++, system_bus_talk, cols); if (system_bus_own->len > 0) print_perm_line (j++, system_bus_own, cols); if (tags->len > 0) print_perm_line (j++, tags, cols); } static void message_handler (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (user_data); g_autofree char *text = NULL; text = g_strconcat (_("Warning: "), message, NULL); if (flatpak_fancy_output ()) { flatpak_table_printer_set_cell (self->printer, self->progress_row, 0, text); self->progress_row++; flatpak_table_printer_add_span (self->printer, ""); flatpak_table_printer_finish_row (self->printer); redraw (self); } else g_print ("%s\n", text); } static gboolean transaction_ready_pre_auth (FlatpakTransaction *transaction) { FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction); GList *ops = flatpak_transaction_get_operations (transaction); GList *l; int i; FlatpakTablePrinter *printer; const char *op_shorthand[] = { "i", "u", "i", "r" }; /* These caches may no longer be valid once the transaction runs */ g_clear_pointer (&self->runtime_app_map, g_hash_table_unref); g_clear_pointer (&self->extension_app_map, g_hash_table_unref); if (ops == NULL) return TRUE; self->n_ops = g_list_length (ops); for (l = ops; l != NULL; l = l->next) { FlatpakTransactionOperation *op = l->data; FlatpakTransactionOperationType type = flatpak_transaction_operation_get_operation_type (op); switch (type) { case FLATPAK_TRANSACTION_OPERATION_UNINSTALL: self->uninstalling = TRUE; break; case FLATPAK_TRANSACTION_OPERATION_INSTALL: case FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE: self->installing = TRUE; break; case FLATPAK_TRANSACTION_OPERATION_UPDATE: self->updating = TRUE; break; default:; } } /* first, show permissions */ for (l = ops; l != NULL; l = l->next) { FlatpakTransactionOperation *op = l->data; FlatpakTransactionOperationType type = flatpak_transaction_operation_get_operation_type (op); if (type == FLATPAK_TRANSACTION_OPERATION_INSTALL || type == FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE || type == FLATPAK_TRANSACTION_OPERATION_UPDATE) { const char *ref = flatpak_transaction_operation_get_ref (op); GKeyFile *metadata = flatpak_transaction_operation_get_metadata (op); GKeyFile *old_metadata = flatpak_transaction_operation_get_old_metadata (op); print_permissions (self, ref, metadata, old_metadata); } } g_print ("\n"); printer = self->printer = flatpak_table_printer_new (); i = 0; flatpak_table_printer_set_column_title (printer, i++, " "); flatpak_table_printer_set_column_title (printer, i++, " "); flatpak_table_printer_set_column_expand (printer, i, TRUE); flatpak_table_printer_set_column_title (printer, i++, _("ID")); flatpak_table_printer_set_column_expand (printer, i, TRUE); if (!self->non_default_arch) { flatpak_table_printer_set_column_skip_unique (printer, i, TRUE); flatpak_table_printer_set_column_skip_unique_string (printer, i, flatpak_get_arch ()); } flatpak_table_printer_set_column_title (printer, i++, _("Arch")); flatpak_table_printer_set_column_expand (printer, i, TRUE); flatpak_table_printer_set_column_title (printer, i++, _("Branch")); flatpak_table_printer_set_column_expand (printer, i, TRUE); /* translators: This is short for operation, the title of a one-char column */ flatpak_table_printer_set_column_title (printer, i++, _("Op")); if (self->installing || self->updating) { g_autofree char *text1 = NULL; g_autofree char *text2 = NULL; g_autofree char *text = NULL; int size; flatpak_table_printer_set_column_expand (printer, i, TRUE); flatpak_table_printer_set_column_title (printer, i++, _("Remote")); self->download_col = i; /* Avoid resizing the download column too much, * by making the title as long as typical content */ text1 = g_strdup_printf ("< 999.9 kB (%s)", _("partial")); text2 = g_strdup_printf (" 123.4 MB / 999.9 MB"); size = MAX (strlen (text1), strlen (text2)); text = g_strdup_printf ("%-*s", size, _("Download")); flatpak_table_printer_set_column_title (printer, i++, text); } for (l = ops, i = 1; l != NULL; l = l->next, i++) { FlatpakTransactionOperation *op = l->data; FlatpakTransactionOperationType type = flatpak_transaction_operation_get_operation_type (op); FlatpakDecomposed *ref = flatpak_transaction_operation_get_decomposed (op); const char *remote = flatpak_transaction_operation_get_remote (op); g_autofree char *id = flatpak_decomposed_dup_id (ref); const char *branch = flatpak_decomposed_get_branch (ref); g_autofree char *arch = flatpak_decomposed_dup_arch (ref); g_autofree char *rownum = g_strdup_printf ("%2d.", i); flatpak_table_printer_add_column (printer, rownum); flatpak_table_printer_add_column (printer, " "); flatpak_table_printer_add_column (printer, id); flatpak_table_printer_add_column (printer, arch); flatpak_table_printer_add_column (printer, branch); flatpak_table_printer_add_column (printer, op_shorthand[type]); if (type == FLATPAK_TRANSACTION_OPERATION_INSTALL || type == FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE || type == FLATPAK_TRANSACTION_OPERATION_UPDATE) { guint64 download_size; g_autofree char *formatted = NULL; g_autofree char *text = NULL; const char *prefix; download_size = flatpak_transaction_operation_get_download_size (op); formatted = g_format_size (download_size); if (download_size > 0) prefix = "< "; else prefix = ""; flatpak_table_printer_add_column (printer, remote); if (flatpak_transaction_operation_get_subpaths (op) != NULL) text = g_strdup_printf ("%s%s (%s)", prefix, formatted, _("partial")); else text = g_strdup_printf ("%s%s", prefix, formatted); flatpak_table_printer_add_decimal_column (printer, text); } g_object_set_data (G_OBJECT (op), "row", GINT_TO_POINTER (flatpak_table_printer_get_current_row (printer))); flatpak_table_printer_finish_row (printer); } flatpak_get_window_size (&self->rows, &self->cols); g_print ("\n"); flatpak_table_printer_print_full (printer, 0, self->cols, &self->table_height, &self->table_width); g_print ("\n"); if (!self->disable_interaction) { g_autoptr(FlatpakInstallation) installation = flatpak_transaction_get_installation (transaction); const char *name; const char *id; gboolean ret; g_print ("\n"); name = flatpak_installation_get_display_name (installation); id = flatpak_installation_get_id (installation); if (flatpak_installation_get_is_user (installation)) ret = flatpak_yes_no_prompt (TRUE, _("Proceed with these changes to the user installation?")); else if (g_strcmp0 (id, SYSTEM_DIR_DEFAULT_ID) == 0) ret = flatpak_yes_no_prompt (TRUE, _("Proceed with these changes to the system installation?")); else ret = flatpak_yes_no_prompt (TRUE, _("Proceed with these changes to the %s?"), name); if (!ret) { g_list_free_full (ops, g_object_unref); return FALSE; } } else g_print ("\n\n"); self->did_interaction = FALSE; return TRUE; } static gboolean transaction_ready (FlatpakTransaction *transaction) { FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction); GList *ops = flatpak_transaction_get_operations (transaction); GList *l; FlatpakTablePrinter *printer; if (ops == NULL) return TRUE; printer = self->printer; if (self->did_interaction) { /* We did some interaction since ready_pre_auth which messes up the formating, so re-print table */ flatpak_table_printer_print_full (printer, 0, self->cols, &self->table_height, &self->table_width); g_print ("\n\n"); } for (l = ops; l; l = l->next) { FlatpakTransactionOperation *op = l->data; set_op_progress (self, op, " "); } g_list_free_full (ops, g_object_unref); flatpak_table_printer_add_span (printer, ""); flatpak_table_printer_finish_row (printer); flatpak_table_printer_add_span (printer, ""); self->progress_row = flatpak_table_printer_get_current_row (printer); flatpak_table_printer_finish_row (printer); self->table_height += 3; /* 2 for the added lines and one for the newline from the user after the prompt */ if (flatpak_fancy_output ()) { flatpak_hide_cursor (); flatpak_enable_raw_mode (); redraw (self); } g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_WARNING, message_handler, transaction); return TRUE; } static void flatpak_cli_transaction_finalize (GObject *object) { FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (object); if (self->first_operation_error) g_error_free (self->first_operation_error); g_free (self->progress_msg); g_hash_table_unref (self->eol_actions); if (self->runtime_app_map) g_hash_table_unref (self->runtime_app_map); if (self->extension_app_map) g_hash_table_unref (self->extension_app_map); if (self->printer) flatpak_table_printer_free (self->printer); G_OBJECT_CLASS (flatpak_cli_transaction_parent_class)->finalize (object); } static void flatpak_cli_transaction_init (FlatpakCliTransaction *self) { self->eol_actions = g_hash_table_new_full ((GHashFunc)flatpak_decomposed_hash, (GEqualFunc)flatpak_decomposed_equal, (GDestroyNotify)flatpak_decomposed_unref, NULL); } static gboolean flatpak_cli_transaction_run (FlatpakTransaction *transaction, GCancellable *cancellable, GError **error); static void flatpak_cli_transaction_class_init (FlatpakCliTransactionClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FlatpakTransactionClass *transaction_class = FLATPAK_TRANSACTION_CLASS (klass); object_class->finalize = flatpak_cli_transaction_finalize; transaction_class->add_new_remote = add_new_remote; transaction_class->ready = transaction_ready; transaction_class->ready_pre_auth = transaction_ready_pre_auth; transaction_class->new_operation = new_operation; transaction_class->operation_done = operation_done; transaction_class->operation_error = operation_error; transaction_class->choose_remote_for_ref = choose_remote_for_ref; transaction_class->end_of_lifed_with_rebase = end_of_lifed_with_rebase; transaction_class->run = flatpak_cli_transaction_run; transaction_class->webflow_start = webflow_start; transaction_class->webflow_done = webflow_done; transaction_class->basic_auth_start = basic_auth_start; transaction_class->install_authenticator = install_authenticator; } FlatpakTransaction * flatpak_cli_transaction_new (FlatpakDir *dir, gboolean disable_interaction, gboolean stop_on_first_error, gboolean non_default_arch, GError **error) { g_autoptr(FlatpakInstallation) installation = NULL; g_autoptr(FlatpakCliTransaction) self = NULL; installation = flatpak_installation_new_for_dir (dir, NULL, error); if (installation == NULL) return NULL; self = g_initable_new (FLATPAK_TYPE_CLI_TRANSACTION, NULL, error, "installation", installation, NULL); if (self == NULL) return NULL; self->disable_interaction = disable_interaction; self->stop_on_first_error = stop_on_first_error; self->non_default_arch = non_default_arch; flatpak_transaction_set_no_interaction (FLATPAK_TRANSACTION (self), disable_interaction); flatpak_transaction_add_default_dependency_sources (FLATPAK_TRANSACTION (self)); return (FlatpakTransaction *) g_steal_pointer (&self); } static gboolean flatpak_cli_transaction_run (FlatpakTransaction *transaction, GCancellable *cancellable, GError **error) { FlatpakCliTransaction *self = FLATPAK_CLI_TRANSACTION (transaction); gboolean res; res = FLATPAK_TRANSACTION_CLASS (flatpak_cli_transaction_parent_class)->run (transaction, cancellable, error); if (flatpak_fancy_output ()) { flatpak_disable_raw_mode (); flatpak_show_cursor (); } if (res && self->n_ops > 0) { const char *text; if (self->uninstalling + self->installing + self->updating > 1) text = _("Changes complete."); else if (self->uninstalling) text = _("Uninstall complete."); else if (self->installing) text = _("Installation complete."); else text = _("Updates complete."); if (flatpak_fancy_output ()) { set_progress (self, text); redraw (self); } else g_print ("%s", text); g_print ("\n"); } if (self->first_operation_error) { g_clear_error (error); /* We always want to return an error if there was some kind of operation error, as that causes the main CLI to return an error status. */ if (self->stop_on_first_error) { /* For the install/stop_on_first_error we return the first operation error, as we have not yet printed it. */ g_propagate_error (error, g_steal_pointer (&self->first_operation_error)); return FALSE; } else { /* For updates/!stop_on_first_error we already printed all errors so we make up a different one. */ return flatpak_fail (error, _("There were one or more errors")); } } if (!res) return FALSE; return TRUE; }