From 44aa0d6830e15fc2223a14d6da786d98964c93e5 Mon Sep 17 00:00:00 2001 From: Phaedrus Leeds Date: Tue, 22 Sep 2020 17:59:27 -0700 Subject: [PATCH] Add internal API for list_unused_refs() and add _with_options() In a few places we are using flatpak_installation_list_unused_refs() and then only using the ref strings not the FlatpakInstalledRef objects, so the resources used to construct those objects are wasted. Add a flatpak_dir_ function to be used internally instead. One day we will figure out how to make flatpak-dir.c less of a wilderness. This also adds the flatpak_installation_list_unused_refs_with_options() verion that has extended features. --- common/flatpak-dir-private.h | 8 + common/flatpak-dir.c | 342 ++++++++++++++++++++++++++++++++++ common/flatpak-installation.c | 186 ++++++------------ common/flatpak-installation.h | 6 + 4 files changed, 412 insertions(+), 130 deletions(-) diff --git a/common/flatpak-dir-private.h b/common/flatpak-dir-private.h index e8146c9d..6a2ce0c3 100644 --- a/common/flatpak-dir-private.h +++ b/common/flatpak-dir-private.h @@ -1019,4 +1019,12 @@ gboolean flatpak_dir_delete_mirror_refs (FlatpakDir *self, GCancellable *cancellable, GError **error); +char ** flatpak_dir_list_unused_refs_with_options (FlatpakDir *self, + const char *arch, + GHashTable *metadata_injection, + const char * const *refs_to_exclude, + gboolean filter_by_eol, + GCancellable *cancellable, + GError **error); + #endif /* __FLATPAK_DIR_H__ */ diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c index a52a88a6..5e90e848 100644 --- a/common/flatpak-dir.c +++ b/common/flatpak-dir.c @@ -14628,3 +14628,345 @@ flatpak_dir_delete_mirror_refs (FlatpakDir *self, return TRUE; } + +static gboolean +maybe_get_metakey_and_origin (FlatpakDir *dir, + const char *ref, + GHashTable *metadata_injection, + GKeyFile **out_metakey, + char **out_origin) +{ + g_autoptr(GKeyFile) metakey = NULL; + g_autofree char *origin = NULL; + + origin = flatpak_dir_get_origin (dir, ref, NULL, NULL); + if (origin == NULL) + return FALSE; + + if (metadata_injection != NULL) + metakey = g_hash_table_lookup (metadata_injection, ref); + if (metakey != NULL) + g_key_file_ref (metakey); + else + { + g_autoptr(FlatpakDeploy) deploy = flatpak_dir_load_deployed (dir, ref, NULL, NULL, NULL); + if (deploy == NULL) + return FALSE; + metakey = flatpak_deploy_get_metadata (deploy); + } + + if (out_metakey) + *out_metakey = g_steal_pointer (&metakey); + if (out_origin) + *out_origin = g_steal_pointer (&origin); + + return TRUE; +} + +static void +find_used_related_refs (FlatpakDir *dir, + FlatpakDir *system_dir, /* nullable */ + GHashTable *used_refs, + GHashTable *metadata_injection, + const char *ref, + GKeyFile *metakey, + const char *origin) +{ + g_autoptr(GPtrArray) related = NULL; + int i; + + if (system_dir == NULL) + g_hash_table_add (used_refs, g_strdup (ref)); + + /* If @system_dir is non-NULL, that means @ref exists in @dir but we should + * look in @system_dir for related things */ + if (system_dir != NULL) + related = flatpak_dir_find_local_related_for_metadata (system_dir, ref, origin, metakey, NULL, NULL); + else + related = flatpak_dir_find_local_related_for_metadata (dir, ref, origin, metakey, NULL, NULL); + + if (related == NULL) + return; + + for (i = 0; i < related->len; i++) + { + FlatpakRelated *rel = g_ptr_array_index (related, i); + + /* Check if this related ref is present in @dir, which implies the one + * in @system_dir is NOT the one being used. */ + if (system_dir != NULL) + { + g_autoptr(FlatpakDeploy) user_related_deploy = flatpak_dir_load_deployed (dir, rel->ref, NULL, NULL, NULL); + if (user_related_deploy != NULL) + continue; + } + + if (!rel->auto_prune && !g_hash_table_contains (used_refs, rel->ref)) + { + g_autofree char *related_origin = NULL; + g_autoptr(GKeyFile) related_metakey = NULL; + + g_hash_table_add (used_refs, g_strdup (rel->ref)); + + if (system_dir != NULL && + maybe_get_metakey_and_origin (system_dir, rel->ref, NULL, &related_metakey, &related_origin)) + find_used_related_refs (system_dir, NULL, used_refs, NULL, + rel->ref, related_metakey, related_origin); + else if (maybe_get_metakey_and_origin (dir, rel->ref, metadata_injection, &related_metakey, &related_origin)) + find_used_related_refs (dir, NULL, used_refs, metadata_injection, + rel->ref, related_metakey, related_origin); + } + } +} + +static void +find_used_refs_for_apps (FlatpakDir *dir, + FlatpakDir *system_dir, /* nullable */ + char **app_refs, + const char *arch, + GHashTable *metadata_injection, + GHashTable *used_runtimes, + GHashTable *used_refs) +{ + /* Check for related refs and runtimes and sdks for each app in @app_refs. + * The apps exist in @dir but if @system_dir is set that's where we check for + * the related/runtime/sdk, and if @system_dir is set we check that said + * runtime does not exist in @dir, so the one in @system_dir is probably the + * one being used. */ + int i; + for (i = 0; app_refs[i] != NULL; i++) + { + const char *ref = app_refs[i]; + g_autofree char *origin = NULL; + g_autofree char *runtime = NULL; + g_autofree char *sdk = NULL; + g_autoptr(GKeyFile) metakey = NULL; + g_auto(GStrv) parts = g_strsplit (ref, "/", -1); + + if (arch != NULL && strcmp (parts[2], arch) != 0) + continue; + + if (!maybe_get_metakey_and_origin (dir, ref, metadata_injection, &metakey, &origin)) + continue; + + find_used_related_refs (dir, system_dir, used_refs, metadata_injection, ref, metakey, origin); + + runtime = g_key_file_get_string (metakey, "Application", "runtime", NULL); + if (runtime) + { + g_autoptr(FlatpakDeploy) runtime_deploy = NULL; + g_autoptr(FlatpakDeploy) user_runtime_deploy = NULL; + if (system_dir != NULL) + { + g_autofree char *runtime_ref = g_strconcat ("runtime/", runtime, NULL); + runtime_deploy = flatpak_dir_load_deployed (system_dir, runtime_ref, NULL, NULL, NULL); + user_runtime_deploy = flatpak_dir_load_deployed (dir, runtime_ref, NULL, NULL, NULL); + } + + if (system_dir == NULL || (runtime_deploy != NULL && user_runtime_deploy == NULL)) + g_hash_table_add (used_runtimes, g_steal_pointer (&runtime)); + } + + sdk = g_key_file_get_string (metakey, "Application", "sdk", NULL); + if (sdk) + { + g_autoptr(FlatpakDeploy) sdk_deploy = NULL; + g_autoptr(FlatpakDeploy) user_sdk_deploy = NULL; + if (system_dir != NULL) + { + g_autofree char *sdk_ref = g_strconcat ("runtime/", sdk, NULL); + sdk_deploy = flatpak_dir_load_deployed (system_dir, sdk_ref, NULL, NULL, NULL); + user_sdk_deploy = flatpak_dir_load_deployed (dir, sdk_ref, NULL, NULL, NULL); + } + + if (system_dir == NULL || (sdk_deploy != NULL && user_sdk_deploy == NULL)) + g_hash_table_add (used_runtimes, g_steal_pointer (&sdk)); + } + } +} + +static void +find_used_refs_for_runtimes (FlatpakDir *dir, + FlatpakDir *system_dir, + GHashTable *metadata_injection, + GHashTable *runtimes, + GHashTable *used_refs) +{ + /* For each runtime in @runtimes, if it's in @dir, add the related refs and + * sdk to @used_refs. If @system_dir is set that's where we look for + * related refs and sdk related refs; the sdk could be in either dir. */ + GLNX_HASH_TABLE_FOREACH (runtimes, const char *, runtime) + { + g_autofree char *runtime_ref = g_strconcat ("runtime/", runtime, NULL); + g_autofree char *origin = NULL; + g_autofree char *sdk = NULL; + g_autoptr(GKeyFile) metakey = NULL; + + if (!maybe_get_metakey_and_origin (dir, runtime_ref, metadata_injection, + &metakey, &origin)) + continue; + + find_used_related_refs (dir, system_dir, used_refs, metadata_injection, + runtime_ref, metakey, origin); + + sdk = g_key_file_get_string (metakey, "Runtime", "sdk", NULL); + if (sdk) + { + g_autofree char *sdk_ref = g_strconcat ("runtime/", sdk, NULL); + g_autofree char *sdk_origin = NULL; + g_autoptr(GKeyFile) sdk_metakey = NULL; + + if (maybe_get_metakey_and_origin (dir, sdk_ref, metadata_injection, + &sdk_metakey, &sdk_origin)) + find_used_related_refs (dir, system_dir, used_refs, metadata_injection, + sdk_ref, sdk_metakey, sdk_origin); + + if (system_dir != NULL && sdk_origin == NULL) + { + g_autofree char *system_sdk_origin = NULL;; + g_autoptr(GKeyFile) system_sdk_metakey = NULL; + if (maybe_get_metakey_and_origin (system_dir, sdk_ref, NULL, + &system_sdk_metakey, &system_sdk_origin)) + find_used_related_refs (system_dir, NULL, used_refs, NULL, + sdk_ref, system_sdk_metakey, system_sdk_origin); + } + } + } +} + +static void +prune_excluded_refs (char **full_refs, + const char * const *refs_to_exclude) +{ + guint len = g_strv_length (full_refs); + for (guint i = 0; i < len; i++) + { + if (g_strv_contains (refs_to_exclude, full_refs[i])) + { + g_free (full_refs[i]); + full_refs[i] = full_refs[len - 1]; + full_refs[len - 1] = NULL; + len--; + i--; + } + } +} + +/* See the documentation for + * flatpak_installation_list_unused_refs_with_options(). + * The returned pointer array is transfer full. */ +char ** +flatpak_dir_list_unused_refs_with_options (FlatpakDir *self, + const char *arch, + GHashTable *metadata_injection, + const char * const *refs_to_exclude, + gboolean filter_by_eol, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GHashTable) refs_hash = NULL; + g_autoptr(GPtrArray) refs = NULL; + g_auto(GStrv) app_refs = NULL; + g_auto(GStrv) runtime_refs = NULL; + g_autoptr(GHashTable) used_refs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + g_autoptr(GHashTable) used_runtimes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + int i; + + if (!flatpak_dir_list_refs (self, "app", &app_refs, cancellable, error)) + return NULL; + + if (!flatpak_dir_list_refs (self, "runtime", &runtime_refs, cancellable, error)) + return NULL; + + if (refs_to_exclude != NULL) + { + prune_excluded_refs (app_refs, refs_to_exclude); + prune_excluded_refs (runtime_refs, refs_to_exclude); + } + + refs_hash = g_hash_table_new (g_str_hash, g_str_equal); + refs = g_ptr_array_new_with_free_func (g_free); + + /* For each app, note the runtime, sdk, and related refs */ + find_used_refs_for_apps (self, NULL, app_refs, arch, metadata_injection, used_runtimes, used_refs); + + /* If @self is a system installation, also check the per-user installation + * for any apps there using runtimes in the system installation or runtimes + * there with sdks or extensions in the system installation. Only do so if + * the per-user installation exists; it wouldn't make sense to create it here + * if not. + */ + if (!flatpak_dir_is_user (self)) + { + g_autoptr(GFile) user_base_dir = flatpak_get_user_base_dir_location (); + + if (g_file_query_exists (user_base_dir, cancellable)) + { + g_autoptr(FlatpakDir) user_dir = flatpak_dir_get_user (); + g_auto(GStrv) user_app_refs = NULL; + g_auto(GStrv) user_runtime_refs = NULL; + g_autoptr(GHashTable) user_runtimes = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); + + if (!flatpak_dir_list_refs (user_dir, "app", &user_app_refs, cancellable, error)) + return NULL; + + /* metadata_injection is NULL because it's not for this installation */ + find_used_refs_for_apps (user_dir, self, user_app_refs, arch, NULL, used_runtimes, used_refs); + + if (!flatpak_dir_list_refs (user_dir, "runtime", &user_runtime_refs, cancellable, error)) + return NULL; + + for (i = 0; user_runtime_refs[i] != NULL; i++) + { + const char *ref = user_runtime_refs[i]; + g_assert (g_str_has_prefix (ref, "runtime/")); + g_hash_table_add (user_runtimes, (char *)ref + strlen ("runtime/")); + } + + find_used_refs_for_runtimes (user_dir, self, NULL, user_runtimes, used_refs); + } + } + + find_used_refs_for_runtimes (self, NULL, metadata_injection, used_runtimes, used_refs); + + for (i = 0; runtime_refs[i] != NULL; i++) + { + const char *ref = runtime_refs[i]; + g_auto(GStrv) parts = g_strsplit (ref, "/", -1); + + if (arch != NULL && strcmp (parts[2], arch) != 0) + continue; + + if (flatpak_dir_ref_is_pinned (self, ref)) + { + g_debug ("Ref %s is pinned, considering as used", ref); + continue; + } + + if (!g_hash_table_contains (used_refs, ref) && + g_hash_table_add (refs_hash, (gpointer) ref)) + { + if (!filter_by_eol) + g_ptr_array_add (refs, g_strdup (ref)); + else + { + g_autoptr(GBytes) deploy_data = NULL; + deploy_data = flatpak_dir_get_deploy_data (self, ref, FLATPAK_DEPLOY_VERSION_ANY, + cancellable, error); + if (deploy_data == NULL) + return NULL; + if (flatpak_deploy_data_get_eol (deploy_data) == NULL && + flatpak_deploy_data_get_eol_rebase (deploy_data) == NULL) + { + g_debug ("Ref %s is not EOL, considering as used", ref); + continue; + } + else + g_ptr_array_add (refs, g_strdup (ref)); + } + } + } + + g_ptr_array_add (refs, NULL); + return (char **)g_ptr_array_free (g_steal_pointer (&refs), FALSE); +} diff --git a/common/flatpak-installation.c b/common/flatpak-installation.c index e2411179..e9e26c08 100644 --- a/common/flatpak-installation.c +++ b/common/flatpak-installation.c @@ -990,13 +990,13 @@ transaction_ready (FlatpakTransaction *transaction, GPtrArray *op_related_to_ops = flatpak_transaction_operation_get_related_to_ops (op); /* (element-type FlatpakTransactionOperation) */ FlatpakTransactionOperationType type = flatpak_transaction_operation_get_operation_type (op); - /* There is currently no way for a set of updates to lead to an - * uninstall, but check anyway. + /* There may be an uninstall op if a runtime will now be considered + * unused after the updates */ if (type == FLATPAK_TRANSACTION_OPERATION_UNINSTALL) { const char *ref = flatpak_transaction_operation_get_ref (op); - g_warning ("Update transaction unexpectedly wants to uninstall %s", ref); + g_debug ("Update transaction wants to uninstall %s", ref); continue; } @@ -2882,39 +2882,6 @@ flatpak_installation_run_triggers (FlatpakInstallation *self, return flatpak_dir_run_triggers (dir, cancellable, error); } - -static void -find_used_refs (FlatpakDir *dir, - GHashTable *used_refs, - const char *ref, - const char *origin) -{ - g_autoptr(GPtrArray) related = NULL; - int i; - - g_hash_table_add (used_refs, g_strdup (ref)); - - related = flatpak_dir_find_local_related (dir, ref, origin, TRUE, NULL, NULL); - if (related == NULL) - return; - - for (i = 0; i < related->len; i++) - { - FlatpakRelated *rel = g_ptr_array_index (related, i); - - if (!rel->auto_prune && !g_hash_table_contains (used_refs, rel->ref)) - { - g_autofree char *related_origin = NULL; - - g_hash_table_add (used_refs, g_strdup (rel->ref)); - - related_origin = flatpak_dir_get_origin (dir, rel->ref, NULL, NULL); - if (related_origin != NULL) - find_used_refs (dir, used_refs, rel->ref, related_origin); - } - } -} - /** * flatpak_installation_list_unused_refs: * @self: a #FlatpakInstallation @@ -2939,111 +2906,70 @@ flatpak_installation_list_unused_refs (FlatpakInstallation *self, const char *arch, GCancellable *cancellable, GError **error) +{ + return flatpak_installation_list_unused_refs_with_options (self, arch, NULL, NULL, cancellable, error); +} + +/** + * flatpak_installation_list_unused_refs_with_options: + * @self: a #FlatpakInstallation + * @arch: (nullable): if non-%NULL, the architecture of refs to collect + * @metadata_injection: (nullable): if non-%NULL, a #GHashTable mapping refs to + * #GKeyFile objects, which when available will + * be used instead of installed metadata + * @options: (nullable): if non-%NULL, a GVariant a{sv} with an extensible set + * of options + * @cancellable: (nullable): a #GCancellable + * @error: return location for a #GError + * + * Like flatpak_installation_list_unused_refs() but supports an extensible set + * of options as well as an @metadata_injection parameter. The following are + * currently defined: + * + * * exclude-refs (as): Act as if these refs are not installed even if they + * are when determining the set of unused refs + * * filter-by-eol (b): Only return refs as unused if they are End-Of-Life. + * Note that if this option is combined with other filters (of which there + * are none currently) non-EOL refs may also be returned. + * + * Returns: (transfer container) (element-type FlatpakInstalledRef): a GPtrArray of + * #FlatpakInstalledRef instances + * + * Since: 1.9.1 + */ +GPtrArray * +flatpak_installation_list_unused_refs_with_options (FlatpakInstallation *self, + const char *arch, + GHashTable *metadata_injection, + GVariant *options, + GCancellable *cancellable, + GError **error) { g_autoptr(FlatpakDir) dir = NULL; - g_autoptr(GHashTable) refs_hash = NULL; g_autoptr(GPtrArray) refs = NULL; - g_auto(GStrv) app_refs = NULL; - g_auto(GStrv) runtime_refs = NULL; - g_autoptr(GHashTable) used_refs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); - g_autoptr(GHashTable) used_runtimes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); - int i; + g_auto(GStrv) refs_strv = NULL; + g_autofree char **refs_to_exclude = NULL; + gboolean filter_by_eol = FALSE; + + if (options) + { + (void) g_variant_lookup (options, "exclude-refs", "^a&s", &refs_to_exclude); + (void) g_variant_lookup (options, "filter-by-eol", "b", &filter_by_eol); + } dir = flatpak_installation_get_dir (self, error); if (dir == NULL) return NULL; - if (!flatpak_dir_list_refs (dir, "app", &app_refs, cancellable, error)) + refs_strv = flatpak_dir_list_unused_refs_with_options (dir, arch, metadata_injection, + (const char * const *)refs_to_exclude, filter_by_eol, + cancellable, error); + if (refs_strv == NULL) return NULL; - if (!flatpak_dir_list_refs (dir, "runtime", &runtime_refs, cancellable, error)) - return NULL; - - refs_hash = g_hash_table_new (g_str_hash, g_str_equal); refs = g_ptr_array_new_with_free_func (g_object_unref); - - for (i = 0; app_refs[i] != NULL; i++) - { - const char *ref = app_refs[i]; - g_autoptr(FlatpakDeploy) deploy = NULL; - g_autofree char *origin = NULL; - g_autofree char *runtime = NULL; - g_autofree char *sdk = NULL; - g_autoptr(GKeyFile) metakey = NULL; - g_auto(GStrv) parts = g_strsplit (ref, "/", -1); - - if (arch != NULL && strcmp (parts[2], arch) != 0) - continue; - - deploy = flatpak_dir_load_deployed (dir, ref, NULL, NULL, NULL); - if (deploy == NULL) - continue; - - origin = flatpak_dir_get_origin (dir, ref, NULL, NULL); - if (origin == NULL) - continue; - - find_used_refs (dir, used_refs, ref, origin); - - metakey = flatpak_deploy_get_metadata (deploy); - runtime = g_key_file_get_string (metakey, "Application", "runtime", NULL); - if (runtime) - g_hash_table_add (used_runtimes, g_steal_pointer (&runtime)); - - sdk = g_key_file_get_string (metakey, "Application", "sdk", NULL); - if (sdk) - g_hash_table_add (used_runtimes, g_steal_pointer (&sdk)); - } - - GLNX_HASH_TABLE_FOREACH (used_runtimes, const char *, runtime) - { - g_autofree char *runtime_ref = g_strconcat ("runtime/", runtime, NULL); - g_autoptr(FlatpakDeploy) deploy = NULL; - g_autofree char *origin = NULL; - g_autofree char *sdk = NULL; - g_autoptr(GKeyFile) metakey = NULL; - - deploy = flatpak_dir_load_deployed (dir, runtime_ref, NULL, NULL, NULL); - if (deploy == NULL) - continue; - - origin = flatpak_dir_get_origin (dir, runtime_ref, NULL, NULL); - if (origin == NULL) - continue; - - find_used_refs (dir, used_refs, runtime_ref, origin); - - metakey = flatpak_deploy_get_metadata (deploy); - sdk = g_key_file_get_string (metakey, "Runtime", "sdk", NULL); - if (sdk) - { - g_autofree char *sdk_ref = g_strconcat ("runtime/", sdk, NULL); - g_autofree char *sdk_origin = flatpak_dir_get_origin (dir, sdk_ref, NULL, NULL); - if (sdk_origin) - find_used_refs (dir, used_refs, sdk_ref, sdk_origin); - } - } - - for (i = 0; runtime_refs[i] != NULL; i++) - { - const char *ref = runtime_refs[i]; - g_auto(GStrv) parts = g_strsplit (ref, "/", -1); - - if (arch != NULL && strcmp (parts[2], arch) != 0) - continue; - - if (flatpak_dir_ref_is_pinned (dir, ref)) - { - g_debug ("Ref %s is pinned, considering as used", ref); - continue; - } - - if (!g_hash_table_contains (used_refs, ref)) - { - if (g_hash_table_add (refs_hash, (gpointer) ref)) - g_ptr_array_add (refs, get_ref (dir, ref, NULL, NULL)); - } - } + for (char **iter = refs_strv; iter && *iter; iter++) + g_ptr_array_add (refs, get_ref (dir, *iter, NULL, NULL)); return g_steal_pointer (&refs); } diff --git a/common/flatpak-installation.h b/common/flatpak-installation.h index 9ad4f827..80838791 100644 --- a/common/flatpak-installation.h +++ b/common/flatpak-installation.h @@ -245,6 +245,12 @@ FLATPAK_EXTERN GPtrArray *flatpak_installation_list_unused_refs (Flatp const char *arch, GCancellable *cancellable, GError **error); +FLATPAK_EXTERN GPtrArray *flatpak_installation_list_unused_refs_with_options (FlatpakInstallation *self, + const char *arch, + GHashTable *metadata_injection, + GVariant *options, + GCancellable *cancellable, + GError **error); FLATPAK_EXTERN GPtrArray *flatpak_installation_list_pinned_refs (FlatpakInstallation *self, const char *arch, GCancellable *cancellable,