/* * Copyright © 2014 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 #include #include #include #include #include "libglnx.h" #include "flatpak-builtins.h" #include "flatpak-builtins-utils.h" #include "flatpak-utils-private.h" #include "flatpak-cli-transaction.h" #include "flatpak-quiet-transaction.h" #include #include #include "flatpak-error.h" static char *opt_arch; static gboolean opt_keep_ref; static gboolean opt_force_remove; static gboolean opt_no_related; static gboolean opt_runtime; static gboolean opt_app; static gboolean opt_all; static gboolean opt_yes; static gboolean opt_unused; static gboolean opt_delete_data; static gboolean opt_noninteractive; static GOptionEntry options[] = { { "arch", 0, 0, G_OPTION_ARG_STRING, &opt_arch, N_("Arch to uninstall"), N_("ARCH") }, { "keep-ref", 0, 0, G_OPTION_ARG_NONE, &opt_keep_ref, N_("Keep ref in local repository"), NULL }, { "no-related", 0, 0, G_OPTION_ARG_NONE, &opt_no_related, N_("Don't uninstall related refs"), NULL }, { "force-remove", 0, 0, G_OPTION_ARG_NONE, &opt_force_remove, N_("Remove files even if running"), NULL }, { "runtime", 0, 0, G_OPTION_ARG_NONE, &opt_runtime, N_("Look for runtime with the specified name"), NULL }, { "app", 0, 0, G_OPTION_ARG_NONE, &opt_app, N_("Look for app with the specified name"), NULL }, { "all", 0, 0, G_OPTION_ARG_NONE, &opt_all, N_("Uninstall all"), NULL }, { "unused", 0, 0, G_OPTION_ARG_NONE, &opt_unused, N_("Uninstall unused"), NULL }, { "delete-data", 0, 0, G_OPTION_ARG_NONE, &opt_delete_data, N_("Delete app data"), NULL }, { "assumeyes", 'y', 0, G_OPTION_ARG_NONE, &opt_yes, N_("Automatically answer yes for all questions"), NULL }, { "noninteractive", 0, 0, G_OPTION_ARG_NONE, &opt_noninteractive, N_("Produce minimal output and don't ask questions"), NULL }, { NULL } }; typedef struct { FlatpakDir *dir; GHashTable *refs_hash; GPtrArray *refs; } UninstallDir; static UninstallDir * uninstall_dir_new (FlatpakDir *dir) { UninstallDir *udir = g_new0 (UninstallDir, 1); udir->dir = g_object_ref (dir); udir->refs = g_ptr_array_new_with_free_func ((GDestroyNotify)flatpak_decomposed_unref); udir->refs_hash = g_hash_table_new_full ((GHashFunc)flatpak_decomposed_hash, (GEqualFunc)flatpak_decomposed_equal, (GDestroyNotify)flatpak_decomposed_unref, NULL); return udir; } static void uninstall_dir_free (UninstallDir *udir) { g_object_unref (udir->dir); g_hash_table_unref (udir->refs_hash); g_ptr_array_unref (udir->refs); g_free (udir); } static void uninstall_dir_add_ref (UninstallDir *udir, 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 UninstallDir * uninstall_dir_ensure (GHashTable *uninstall_dirs, FlatpakDir *dir) { UninstallDir *udir; udir = g_hash_table_lookup (uninstall_dirs, dir); if (udir == NULL) { udir = uninstall_dir_new (dir); g_hash_table_insert (uninstall_dirs, udir->dir, udir); } return udir; } static gboolean flatpak_delete_data (gboolean yes_opt, const char *app_id, GError **error) { g_autofree char *path = g_build_filename (g_get_home_dir (), ".var", "app", app_id, NULL); g_autoptr(GFile) file = g_file_new_for_path (path); if (!yes_opt && !flatpak_yes_no_prompt (FALSE, _("Delete data for %s?"), app_id)) return TRUE; if (g_file_query_exists (file, NULL)) { if (!flatpak_rm_rf (file, NULL, error)) return FALSE; } if (!reset_permissions_for_app (app_id, error)) return FALSE; return TRUE; } gboolean flatpak_builtin_uninstall (int argc, char **argv, GCancellable *cancellable, GError **error) { g_autoptr(GOptionContext) context = NULL; g_autoptr(GPtrArray) dirs = NULL; char **prefs = NULL; int i, j, k, n_prefs; const char *default_branch = NULL; FlatpakKinds kinds; g_autoptr(GHashTable) uninstall_dirs = NULL; context = g_option_context_new (_("[REF…] - Uninstall applications or runtimes")); g_option_context_set_translation_domain (context, GETTEXT_PACKAGE); if (!flatpak_option_context_parse (context, options, &argc, &argv, FLATPAK_BUILTIN_FLAG_ALL_DIRS | FLATPAK_BUILTIN_FLAG_OPTIONAL_REPO, &dirs, cancellable, error)) return FALSE; if (argc < 2 && !opt_all && !opt_unused && !opt_delete_data) return usage_error (context, _("Must specify at least one REF, --unused, --all or --delete-data"), error); if (argc >= 2 && opt_all) return usage_error (context, _("Must not specify REFs when using --all"), error); if (argc >= 2 && opt_unused) return usage_error (context, _("Must not specify REFs when using --unused"), error); prefs = &argv[1]; n_prefs = argc - 1; /* Backwards compat for old "NAME [BRANCH]" argument version */ if (argc == 3 && flatpak_is_valid_name (argv[1], -1, NULL) && looks_like_branch (argv[2])) { default_branch = argv[2]; n_prefs = 1; } kinds = flatpak_kinds_from_bools (opt_app, opt_runtime); uninstall_dirs = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) uninstall_dir_free); if (opt_all) { for (j = 0; j < dirs->len; j++) { FlatpakDir *dir = g_ptr_array_index (dirs, j); UninstallDir *udir; g_autoptr(GPtrArray) refs = NULL; flatpak_dir_maybe_ensure_repo (dir, NULL, NULL); if (flatpak_dir_get_repo (dir) == NULL) continue; udir = uninstall_dir_ensure (uninstall_dirs, dir); refs = flatpak_dir_list_refs (dir, FLATPAK_KINDS_APP | FLATPAK_KINDS_RUNTIME, cancellable, error); if (refs == NULL) return FALSE; for (k = 0; k < refs->len; k++) { FlatpakDecomposed *ref = g_ptr_array_index (refs, k); uninstall_dir_add_ref (udir, ref); } } } else if (opt_unused) { gboolean found_something_to_uninstall = FALSE; for (j = 0; j < dirs->len; j++) { FlatpakDir *dir = g_ptr_array_index (dirs, j); g_autoptr(FlatpakInstallation) installation = NULL; UninstallDir *udir; g_auto(GStrv) unused = NULL; g_autoptr(GPtrArray) pinned = NULL; flatpak_dir_maybe_ensure_repo (dir, NULL, NULL); if (flatpak_dir_get_repo (dir) == NULL) continue; installation = flatpak_installation_new_for_dir (dir, NULL, NULL); pinned = flatpak_installation_list_pinned_refs (installation, opt_arch, cancellable, error); if (pinned == NULL) return FALSE; if (pinned->len > 0) { g_print (_("\nThese runtimes in installation '%s' are pinned and won't be removed; see flatpak-pin(1):\n"), flatpak_dir_get_name_cached (dir)); for (i = 0; i < pinned->len; i++) { FlatpakInstalledRef *rref = g_ptr_array_index (pinned, i); const char *ref = flatpak_ref_format_ref_cached (FLATPAK_REF (rref)); g_print (" %s\n", ref); } } udir = uninstall_dir_ensure (uninstall_dirs, dir); unused = flatpak_dir_list_unused_refs (dir, opt_arch, NULL, NULL, NULL, FALSE, cancellable, error); if (unused == NULL) return FALSE; for (char **iter = unused; iter && *iter; iter++) { const char *ref = *iter; g_autoptr(FlatpakDecomposed) d = flatpak_decomposed_new_from_ref (ref, NULL); if (d) { uninstall_dir_add_ref (udir, d); found_something_to_uninstall = TRUE; } } if (udir->refs->len > 0) found_something_to_uninstall = TRUE; } if (!found_something_to_uninstall) { g_print (_("Nothing unused to uninstall\n")); return TRUE; } } else { for (j = 0; j < n_prefs; j++) { const char *pref = NULL; FlatpakKinds matched_kinds; g_autofree char *match_id = NULL; g_autofree char *match_arch = NULL; g_autofree char *match_branch = NULL; g_autoptr(GError) local_error = NULL; g_autoptr(GPtrArray) ref_dir_pairs = NULL; UninstallDir *udir = NULL; gboolean found_exact_name_match = FALSE; g_autoptr(GPtrArray) chosen_pairs = NULL; FindMatchingRefsFlags matching_refs_flags; pref = prefs[j]; if (!flatpak_allow_fuzzy_matching (pref)) matching_refs_flags = FIND_MATCHING_REFS_FLAGS_NONE; else matching_refs_flags = FIND_MATCHING_REFS_FLAGS_FUZZY; if (matching_refs_flags & FIND_MATCHING_REFS_FLAGS_FUZZY) { flatpak_split_partial_ref_arg_novalidate (pref, kinds, opt_arch, default_branch, &matched_kinds, &match_id, &match_arch, &match_branch); /* We used _novalidate so that the id can be partial, but we can still validate the branch */ if (match_branch != NULL && !flatpak_is_valid_branch (match_branch, -1, &local_error)) return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Invalid branch %s: %s"), match_branch, local_error->message); } else if (!flatpak_split_partial_ref_arg (pref, kinds, opt_arch, default_branch, &matched_kinds, &match_id, &match_arch, &match_branch, error)) { return FALSE; } ref_dir_pairs = g_ptr_array_new_with_free_func ((GDestroyNotify) ref_dir_pair_free); for (k = 0; k < dirs->len; k++) { FlatpakDir *dir = g_ptr_array_index (dirs, k); g_autoptr(GPtrArray) refs = NULL; refs = flatpak_dir_find_installed_refs (dir, match_id, match_branch, match_arch, kinds, matching_refs_flags, error); if (refs == NULL) return FALSE; else if (refs->len == 0) continue; for (int m = 0; m < refs->len; m++) { FlatpakDecomposed *ref = g_ptr_array_index (refs, m); RefDirPair *pair; if (match_id != NULL && flatpak_decomposed_is_id (ref, match_id)) found_exact_name_match = TRUE; pair = ref_dir_pair_new (ref, dir); g_ptr_array_add (ref_dir_pairs, pair); } } if (ref_dir_pairs->len == 0) { if (n_prefs == 1) { g_autoptr(GString) err_str = g_string_new (""); g_string_append_printf (err_str, _("No installed refs found for ‘%s’"), match_id); if (match_arch) g_string_append_printf (err_str, _(" with arch ‘%s’"), match_arch); if (match_branch) g_string_append_printf (err_str, _(" with branch ‘%s’"), match_branch); g_set_error_literal (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED, err_str->str); return FALSE; } g_printerr (_("Warning: %s is not installed\n"), pref); continue; } /* Don't show fuzzy matches if an exact match was found in any installation */ if (found_exact_name_match) { /* Walk through the array backwards so we can safely remove */ for (i = ref_dir_pairs->len; i > 0; i--) { RefDirPair *pair = g_ptr_array_index (ref_dir_pairs, i - 1); if (match_id != NULL && !flatpak_decomposed_is_id (pair->ref, match_id)) g_ptr_array_remove_index (ref_dir_pairs, i - 1); } } chosen_pairs = g_ptr_array_new (); if (!flatpak_resolve_matching_installed_refs (opt_yes, FALSE, ref_dir_pairs, match_id, chosen_pairs, error)) return FALSE; for (i = 0; i < chosen_pairs->len; i++) { RefDirPair *pair = g_ptr_array_index (chosen_pairs, i); udir = uninstall_dir_ensure (uninstall_dirs, pair->dir); uninstall_dir_add_ref (udir, pair->ref); } } } if (n_prefs > 0 && g_hash_table_size (uninstall_dirs) == 0) { g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED, _("None of the specified refs are installed")); return FALSE; } GLNX_HASH_TABLE_FOREACH_V (uninstall_dirs, UninstallDir *, udir) { g_autoptr(FlatpakTransaction) transaction = NULL; if (opt_noninteractive) transaction = flatpak_quiet_transaction_new (udir->dir, error); else transaction = flatpak_cli_transaction_new (udir->dir, opt_yes, TRUE, opt_arch != NULL, error); if (transaction == NULL) return FALSE; flatpak_transaction_set_disable_prune (transaction, opt_keep_ref); flatpak_transaction_set_force_uninstall (transaction, opt_force_remove); flatpak_transaction_set_disable_related (transaction, opt_no_related); /* 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++) { FlatpakDecomposed *ref = g_ptr_array_index (udir->refs, i); if (!flatpak_transaction_add_uninstall (transaction, flatpak_decomposed_get_ref (ref), error)) return FALSE; } if (!flatpak_transaction_run (transaction, cancellable, error)) { if (g_error_matches (*error, FLATPAK_ERROR, FLATPAK_ERROR_ABORTED)) g_clear_error (error); /* Don't report on stderr */ return FALSE; } if (opt_delete_data) { for (i = 0; i < udir->refs->len; i++) { FlatpakDecomposed *ref = g_ptr_array_index (udir->refs, i); g_autofree char *id = flatpak_decomposed_dup_id (ref); if (!flatpak_delete_data (opt_yes, id, error)) return FALSE; } } } if (opt_delete_data && argc < 2) { g_autoptr(GFileEnumerator) enumerator = NULL; g_autofree char *path = g_build_filename (g_get_home_dir (), ".var", "app", NULL); g_autoptr(GFile) app_dir = g_file_new_for_path (path); enumerator = g_file_enumerate_children (app_dir, G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE, G_FILE_QUERY_INFO_NONE, cancellable, error); if (!enumerator) return FALSE; while (TRUE) { GFileInfo *info; GFile *file; g_autoptr(FlatpakDecomposed) ref = NULL; if (!g_file_enumerator_iterate (enumerator, &info, &file, cancellable, error)) return FALSE; if (info == NULL) break; if (g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY) continue; ref = flatpak_find_current_ref (g_file_info_get_name (info), cancellable, NULL); if (ref) continue; if (!flatpak_delete_data (opt_yes, g_file_info_get_name (info), error)) return FALSE; } } return TRUE; } gboolean flatpak_complete_uninstall (FlatpakCompletion *completion) { g_autoptr(GOptionContext) context = NULL; g_autoptr(GPtrArray) dirs = NULL; int i; FlatpakKinds kinds; context = g_option_context_new (""); if (!flatpak_option_context_parse (context, options, &completion->argc, &completion->argv, FLATPAK_BUILTIN_FLAG_ALL_DIRS | FLATPAK_BUILTIN_FLAG_OPTIONAL_REPO, &dirs, NULL, NULL)) return FALSE; kinds = flatpak_kinds_from_bools (opt_app, opt_runtime); switch (completion->argc) { case 0: default: /* REF */ flatpak_complete_options (completion, global_entries); flatpak_complete_options (completion, options); flatpak_complete_options (completion, user_entries); for (i = 0; i < dirs->len; i++) { FlatpakDir *dir = g_ptr_array_index (dirs, i); flatpak_complete_partial_ref (completion, kinds, opt_arch, dir, NULL); } break; } return TRUE; }