From 65a4bb0a3d0f2acc100a155c14b3a2eed5c1f033 Mon Sep 17 00:00:00 2001 From: Phaedrus Leeds Date: Tue, 21 Jun 2022 14:20:33 -0700 Subject: [PATCH] uninstall: Ask for confirmation for used runtime extensions Based on discussions on the issue tracker, it seems that users sometimes remove runtime extensions without really understanding whether they're in use. Add a confirmation prompt to address this. Helps: #4549 --- app/flatpak-builtins-uninstall.c | 104 ++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 3 deletions(-) diff --git a/app/flatpak-builtins-uninstall.c b/app/flatpak-builtins-uninstall.c index df10e9ed..f597ecce 100644 --- a/app/flatpak-builtins-uninstall.c +++ b/app/flatpak-builtins-uninstall.c @@ -69,6 +69,8 @@ typedef struct { FlatpakDir *dir; GHashTable *refs_hash; + GHashTable *runtime_app_map; + GHashTable *extension_app_map; GPtrArray *refs; } UninstallDir; @@ -89,18 +91,28 @@ uninstall_dir_free (UninstallDir *udir) { g_object_unref (udir->dir); g_hash_table_unref (udir->refs_hash); + g_clear_pointer (&udir->runtime_app_map, g_hash_table_unref); + g_clear_pointer (&udir->extension_app_map, g_hash_table_unref); g_ptr_array_unref (udir->refs); g_free (udir); } static void uninstall_dir_add_ref (UninstallDir *udir, - FlatpakDecomposed*ref) + FlatpakDecomposed *ref) { if (g_hash_table_insert (udir->refs_hash, flatpak_decomposed_ref (ref), NULL)) g_ptr_array_add (udir->refs, flatpak_decomposed_ref (ref)); } +static void +uninstall_dir_remove_ref (UninstallDir *udir, + FlatpakDecomposed *ref) +{ + g_hash_table_remove (udir->refs_hash, ref); + g_ptr_array_remove (udir->refs, ref); +} + static UninstallDir * uninstall_dir_ensure (GHashTable *uninstall_dirs, FlatpakDir *dir) @@ -141,6 +153,75 @@ flatpak_delete_data (gboolean yes_opt, return TRUE; } +static gboolean +confirm_runtime_extension_removal (gboolean yes_opt, + UninstallDir *udir, + FlatpakDecomposed *ref) +{ + g_autoptr(GPtrArray) apps = NULL; + g_autoptr(GError) local_error = NULL; + g_autofree char *ref_name = NULL; + const char *ref_branch; + const char *on = ""; + const char *off = ""; + + if (flatpak_fancy_output ()) + { + on = FLATPAK_ANSI_BOLD_ON; + off = FLATPAK_ANSI_BOLD_OFF; + } + + apps = flatpak_dir_list_app_refs_with_runtime_extension (udir->dir, + &udir->runtime_app_map, + &udir->extension_app_map, + ref, NULL, &local_error); + if (apps == NULL) + g_debug ("Unable to list apps using extension %s: %s\n", + flatpak_decomposed_get_ref (ref), local_error->message); + + if (apps == NULL || apps->len == 0) + return TRUE; + + /* Exclude any apps that will be removed by the current transaction */ + for (guint i = 0; i < udir->refs->len; i++) + { + FlatpakDecomposed *uninstall_ref = g_ptr_array_index (udir->refs, i); + guint j; + + if (flatpak_decomposed_is_runtime (uninstall_ref)) + continue; + + if (g_ptr_array_find_with_equal_func (apps, uninstall_ref, + (GEqualFunc)flatpak_decomposed_equal, &j)) + g_ptr_array_remove_index_fast (apps, j); + } + + if (apps->len == 0) + return TRUE; + + ref_name = flatpak_decomposed_dup_id (ref); + ref_branch = flatpak_decomposed_get_branch (ref); + + g_print (_("Applications using the runtime %s%s%s branch %s%s%s:\n"), + on, ref_name, off, on, ref_branch, off); + 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 (!yes_opt && + !flatpak_yes_no_prompt (FALSE, _("Really remove?"))) + return FALSE; + + return TRUE; +} + gboolean flatpak_builtin_uninstall (int argc, char **argv, GCancellable *cancellable, GError **error) { @@ -409,14 +490,31 @@ flatpak_builtin_uninstall (int argc, char **argv, GCancellable *cancellable, GEr /* This disables the remote metadata update, since uninstall is a local-only op */ flatpak_transaction_set_no_pull (transaction, TRUE); - for (i = 0; i < udir->refs->len; i++) + /* Walk through the array backwards so we can safely remove */ + for (i = udir->refs->len; i > 0; i--) { - FlatpakDecomposed *ref = g_ptr_array_index (udir->refs, i); + FlatpakDecomposed *ref = g_ptr_array_index (udir->refs, i - 1); + + /* In case it's a required runtime, the transaction will fail later on. + * In case it's an optional runtime extension of an installed app, + * prompt the user for confirmation + */ + if (!opt_force_remove && + flatpak_dir_is_runtime_extension (udir->dir, ref) && + !confirm_runtime_extension_removal (opt_yes, udir, ref)) + { + uninstall_dir_remove_ref (udir, ref); + continue; + } if (!flatpak_transaction_add_uninstall (transaction, flatpak_decomposed_get_ref (ref), error)) return FALSE; } + /* These caches may no longer be valid once the transaction runs */ + g_clear_pointer (&udir->runtime_app_map, g_hash_table_unref); + g_clear_pointer (&udir->extension_app_map, g_hash_table_unref); + if (!flatpak_transaction_run (transaction, cancellable, error)) { if (g_error_matches (*error, FLATPAK_ERROR, FLATPAK_ERROR_ABORTED))