Files
flatpak/common/flatpak-dir-utils.c
Simon McVittie 14db9d48cf common: Break out the parts of flatpak-utils that deal with FlatpakDir
This breaks the circular dependency between flatpak-utils and flatpak-dir.
There is still a circular dependency between flatpak-dir and
flatpak-dir-utils, but I don't want to make flatpak-dir even larger.

Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-07-09 17:12:55 -03:00

587 lines
20 KiB
C

/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s:
* Copyright © 1995-1998 Free Software Foundation, Inc.
* Copyright © 2014-2019 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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "flatpak-dir-utils-private.h"
#include <glib/gi18n-lib.h>
#include "flatpak-dir-private.h"
#include "flatpak-metadata-private.h"
#include "flatpak-utils-private.h"
char **
flatpak_list_deployed_refs (const char *type,
const char *name_prefix,
const char *arch,
const char *branch,
GCancellable *cancellable,
GError **error)
{
gchar **ret = NULL;
g_autoptr(GPtrArray) names = NULL;
g_autoptr(GHashTable) hash = NULL;
g_autoptr(FlatpakDir) user_dir = NULL;
g_autoptr(GPtrArray) system_dirs = NULL;
int i;
hash = g_hash_table_new_full ((GHashFunc)flatpak_decomposed_hash, (GEqualFunc)flatpak_decomposed_equal, (GDestroyNotify)flatpak_decomposed_unref, NULL);
user_dir = flatpak_dir_get_user ();
system_dirs = flatpak_dir_get_system_list (cancellable, error);
if (system_dirs == NULL)
goto out;
if (!flatpak_dir_collect_deployed_refs (user_dir, type, name_prefix,
arch, branch, hash, cancellable,
error))
goto out;
for (i = 0; i < system_dirs->len; i++)
{
FlatpakDir *system_dir = g_ptr_array_index (system_dirs, i);
if (!flatpak_dir_collect_deployed_refs (system_dir, type, name_prefix,
arch, branch, hash, cancellable,
error))
goto out;
}
names = g_ptr_array_new ();
GLNX_HASH_TABLE_FOREACH (hash, FlatpakDecomposed *, ref)
{
g_ptr_array_add (names, flatpak_decomposed_dup_id (ref));
}
g_ptr_array_sort (names, flatpak_strcmp0_ptr);
g_ptr_array_add (names, NULL);
ret = (char **) g_ptr_array_free (names, FALSE);
names = NULL;
out:
return ret;
}
char **
flatpak_list_unmaintained_refs (const char *name_prefix,
const char *arch,
const char *branch,
GCancellable *cancellable,
GError **error)
{
gchar **ret = NULL;
g_autoptr(GPtrArray) names = NULL;
g_autoptr(GHashTable) hash = NULL;
g_autoptr(FlatpakDir) user_dir = NULL;
const char *key;
GHashTableIter iter;
g_autoptr(GPtrArray) system_dirs = NULL;
int i;
hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
user_dir = flatpak_dir_get_user ();
if (!flatpak_dir_collect_unmaintained_refs (user_dir, name_prefix,
arch, branch, hash, cancellable,
error))
return NULL;
system_dirs = flatpak_dir_get_system_list (cancellable, error);
if (system_dirs == NULL)
return NULL;
for (i = 0; i < system_dirs->len; i++)
{
FlatpakDir *system_dir = g_ptr_array_index (system_dirs, i);
if (!flatpak_dir_collect_unmaintained_refs (system_dir, name_prefix,
arch, branch, hash, cancellable,
error))
return NULL;
}
names = g_ptr_array_new ();
g_hash_table_iter_init (&iter, hash);
while (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL))
g_ptr_array_add (names, g_strdup (key));
g_ptr_array_sort (names, flatpak_strcmp0_ptr);
g_ptr_array_add (names, NULL);
ret = (char **) g_ptr_array_free (names, FALSE);
names = NULL;
return ret;
}
GFile *
flatpak_find_deploy_dir_for_ref (FlatpakDecomposed *ref,
FlatpakDir **dir_out,
GCancellable *cancellable,
GError **error)
{
g_autoptr(FlatpakDir) user_dir = NULL;
g_autoptr(GPtrArray) system_dirs = NULL;
FlatpakDir *dir = NULL;
g_autoptr(GFile) deploy = NULL;
user_dir = flatpak_dir_get_user ();
system_dirs = flatpak_dir_get_system_list (cancellable, error);
if (system_dirs == NULL)
return NULL;
dir = user_dir;
deploy = flatpak_dir_get_if_deployed (dir, ref, NULL, cancellable);
if (deploy == NULL)
{
int i;
for (i = 0; deploy == NULL && i < system_dirs->len; i++)
{
dir = g_ptr_array_index (system_dirs, i);
deploy = flatpak_dir_get_if_deployed (dir, ref, NULL, cancellable);
if (deploy != NULL)
break;
}
}
if (deploy == NULL)
{
flatpak_fail_error (error, FLATPAK_ERROR_NOT_INSTALLED, _("%s not installed"), flatpak_decomposed_get_ref (ref));
return NULL;
}
if (dir_out)
*dir_out = g_object_ref (dir);
return g_steal_pointer (&deploy);
}
GFile *
flatpak_find_files_dir_for_ref (FlatpakDecomposed *ref,
GCancellable *cancellable,
GError **error)
{
g_autoptr(GFile) deploy = NULL;
deploy = flatpak_find_deploy_dir_for_ref (ref, NULL, cancellable, error);
if (deploy == NULL)
return NULL;
return g_file_get_child (deploy, "files");
}
GFile *
flatpak_find_unmaintained_extension_dir_if_exists (const char *name,
const char *arch,
const char *branch,
GCancellable *cancellable)
{
g_autoptr(FlatpakDir) user_dir = NULL;
g_autoptr(GFile) extension_dir = NULL;
g_autoptr(GError) local_error = NULL;
user_dir = flatpak_dir_get_user ();
extension_dir = flatpak_dir_get_unmaintained_extension_dir_if_exists (user_dir, name, arch, branch, cancellable);
if (extension_dir == NULL)
{
g_autoptr(GPtrArray) system_dirs = NULL;
int i;
system_dirs = flatpak_dir_get_system_list (cancellable, &local_error);
if (system_dirs == NULL)
{
g_warning ("Could not get the system installations: %s", local_error->message);
return NULL;
}
for (i = 0; i < system_dirs->len; i++)
{
FlatpakDir *system_dir = g_ptr_array_index (system_dirs, i);
extension_dir = flatpak_dir_get_unmaintained_extension_dir_if_exists (system_dir, name, arch, branch, cancellable);
if (extension_dir != NULL)
break;
}
}
if (extension_dir == NULL)
return NULL;
return g_steal_pointer (&extension_dir);
}
FlatpakDecomposed *
flatpak_find_current_ref (const char *app_id,
GCancellable *cancellable,
GError **error)
{
g_autoptr(FlatpakDecomposed) current_ref = NULL;
g_autoptr(FlatpakDir) user_dir = flatpak_dir_get_user ();
int i;
current_ref = flatpak_dir_current_ref (user_dir, app_id, NULL);
if (current_ref == NULL)
{
g_autoptr(GPtrArray) system_dirs = NULL;
system_dirs = flatpak_dir_get_system_list (cancellable, error);
if (system_dirs == NULL)
return FALSE;
for (i = 0; i < system_dirs->len; i++)
{
FlatpakDir *dir = g_ptr_array_index (system_dirs, i);
current_ref = flatpak_dir_current_ref (dir, app_id, cancellable);
if (current_ref != NULL)
break;
}
}
if (current_ref)
return g_steal_pointer (&current_ref);
flatpak_fail_error (error, FLATPAK_ERROR_NOT_INSTALLED, _("%s not installed"), app_id);
return NULL;
}
FlatpakDeploy *
flatpak_find_deploy_for_ref_in (GPtrArray *dirs,
const char *ref_str,
const char *commit,
GCancellable *cancellable,
GError **error)
{
FlatpakDeploy *deploy = NULL;
int i;
g_autoptr(GError) my_error = NULL;
g_autoptr(FlatpakDecomposed) ref = flatpak_decomposed_new_from_ref (ref_str, error);
if (ref == NULL)
return NULL;
for (i = 0; deploy == NULL && i < dirs->len; i++)
{
FlatpakDir *dir = g_ptr_array_index (dirs, i);
flatpak_log_dir_access (dir);
g_clear_error (&my_error);
deploy = flatpak_dir_load_deployed (dir, ref, commit, cancellable, &my_error);
}
if (deploy == NULL)
g_propagate_error (error, g_steal_pointer (&my_error));
return deploy;
}
FlatpakDeploy *
flatpak_find_deploy_for_ref (const char *ref,
const char *commit,
FlatpakDir *opt_user_dir,
GCancellable *cancellable,
GError **error)
{
g_autoptr(GPtrArray) dirs = NULL;
dirs = flatpak_dir_get_system_list (cancellable, error);
if (dirs == NULL)
return NULL;
/* If an custom dir was passed, use that instead of the user dir.
* This is used when running apply-extra-data where if the target
* is a custom installation location the regular user one may not
* have the (possibly just installed in this transaction) runtime.
*/
if (opt_user_dir)
g_ptr_array_insert (dirs, 0, g_object_ref (opt_user_dir));
else
g_ptr_array_insert (dirs, 0, flatpak_dir_get_user ());
return flatpak_find_deploy_for_ref_in (dirs, ref, commit, cancellable, error);
}
void
flatpak_extension_free (FlatpakExtension *extension)
{
g_free (extension->id);
g_free (extension->installed_id);
g_free (extension->commit);
flatpak_decomposed_unref (extension->ref);
g_free (extension->directory);
g_free (extension->files_path);
g_free (extension->add_ld_path);
g_free (extension->subdir_suffix);
g_strfreev (extension->merge_dirs);
g_free (extension);
}
static int
flatpak_extension_compare (gconstpointer _a,
gconstpointer _b)
{
const FlatpakExtension *a = _a;
const FlatpakExtension *b = _b;
return b->priority - a->priority;
}
static FlatpakExtension *
flatpak_extension_new (const char *id,
const char *extension,
FlatpakDecomposed *ref,
const char *directory,
const char *add_ld_path,
const char *subdir_suffix,
char **merge_dirs,
GFile *files,
GFile *deploy_dir,
gboolean is_unmaintained,
OstreeRepo *repo)
{
FlatpakExtension *ext = g_new0 (FlatpakExtension, 1);
g_autoptr(GBytes) deploy_data = NULL;
ext->id = g_strdup (id);
ext->installed_id = g_strdup (extension);
ext->ref = flatpak_decomposed_ref (ref);
ext->directory = g_strdup (directory);
ext->files_path = g_file_get_path (files);
ext->add_ld_path = g_strdup (add_ld_path);
ext->subdir_suffix = g_strdup (subdir_suffix);
ext->merge_dirs = g_strdupv (merge_dirs);
ext->is_unmaintained = is_unmaintained;
/* Unmaintained extensions won't have a deploy or commit; see
* https://github.com/flatpak/flatpak/issues/167 */
if (deploy_dir && !is_unmaintained)
{
deploy_data = flatpak_load_deploy_data (deploy_dir, ref, repo, FLATPAK_DEPLOY_VERSION_ANY, NULL, NULL);
if (deploy_data)
ext->commit = g_strdup (flatpak_deploy_data_get_commit (deploy_data));
}
if (is_unmaintained)
ext->priority = 1000;
else
{
g_autoptr(GKeyFile) keyfile = g_key_file_new ();
g_autofree char *metadata_path = g_build_filename (ext->files_path, "../metadata", NULL);
if (g_key_file_load_from_file (keyfile, metadata_path, G_KEY_FILE_NONE, NULL))
ext->priority = g_key_file_get_integer (keyfile,
FLATPAK_METADATA_GROUP_EXTENSION_OF,
FLATPAK_METADATA_KEY_PRIORITY,
NULL);
}
return ext;
}
static GList *
add_extension (GKeyFile *metakey,
const char *group,
const char *extension,
const char *arch,
const char *branch,
GList *res)
{
FlatpakExtension *ext;
g_autofree char *directory = g_key_file_get_string (metakey, group,
FLATPAK_METADATA_KEY_DIRECTORY,
NULL);
g_autofree char *add_ld_path = g_key_file_get_string (metakey, group,
FLATPAK_METADATA_KEY_ADD_LD_PATH,
NULL);
g_auto(GStrv) merge_dirs = g_key_file_get_string_list (metakey, group,
FLATPAK_METADATA_KEY_MERGE_DIRS,
NULL, NULL);
g_autofree char *enable_if = g_key_file_get_string (metakey, group,
FLATPAK_METADATA_KEY_ENABLE_IF,
NULL);
g_autofree char *subdir_suffix = g_key_file_get_string (metakey, group,
FLATPAK_METADATA_KEY_SUBDIRECTORY_SUFFIX,
NULL);
g_autoptr(FlatpakDecomposed) ref = NULL;
gboolean is_unmaintained = FALSE;
g_autoptr(GFile) files = NULL;
g_autoptr(GFile) deploy_dir = NULL;
g_autoptr(FlatpakDir) dir = NULL;
if (directory == NULL)
return res;
ref = flatpak_decomposed_new_from_parts (FLATPAK_KINDS_RUNTIME, extension, arch, branch, NULL);
if (ref == NULL)
return res;
files = flatpak_find_unmaintained_extension_dir_if_exists (extension, arch, branch, NULL);
if (files == NULL)
{
deploy_dir = flatpak_find_deploy_dir_for_ref (ref, &dir, NULL, NULL);
if (deploy_dir)
files = g_file_get_child (deploy_dir, "files");
}
else
is_unmaintained = TRUE;
/* Prefer a full extension (org.freedesktop.Locale) over subdirectory ones (org.freedesktop.Locale.sv) */
if (files != NULL)
{
if (flatpak_extension_matches_reason (extension, enable_if, TRUE))
{
ext = flatpak_extension_new (extension, extension, ref, directory,
add_ld_path, subdir_suffix, merge_dirs,
files, deploy_dir, is_unmaintained,
is_unmaintained ? NULL : flatpak_dir_get_repo (dir));
res = g_list_prepend (res, ext);
}
}
else if (g_key_file_get_boolean (metakey, group,
FLATPAK_METADATA_KEY_SUBDIRECTORIES, NULL))
{
g_autofree char *prefix = g_strconcat (extension, ".", NULL);
g_auto(GStrv) ids = NULL;
g_auto(GStrv) unmaintained_refs = NULL;
int j;
ids = flatpak_list_deployed_refs ("runtime", prefix, arch, branch,
NULL, NULL);
for (j = 0; ids != NULL && ids[j] != NULL; j++)
{
const char *id = ids[j];
g_autofree char *extended_dir = g_build_filename (directory, id + strlen (prefix), NULL);
g_autoptr(FlatpakDecomposed) dir_ref = NULL;
g_autoptr(GFile) subdir_deploy_dir = NULL;
g_autoptr(GFile) subdir_files = NULL;
g_autoptr(FlatpakDir) subdir_dir = NULL;
dir_ref = flatpak_decomposed_new_from_parts (FLATPAK_KINDS_RUNTIME, id, arch, branch, NULL);
if (dir_ref == NULL)
continue;
subdir_deploy_dir = flatpak_find_deploy_dir_for_ref (dir_ref, &subdir_dir, NULL, NULL);
if (subdir_deploy_dir)
subdir_files = g_file_get_child (subdir_deploy_dir, "files");
if (subdir_files && flatpak_extension_matches_reason (id, enable_if, TRUE))
{
ext = flatpak_extension_new (extension, id, dir_ref, extended_dir,
add_ld_path, subdir_suffix, merge_dirs,
subdir_files, subdir_deploy_dir, FALSE,
flatpak_dir_get_repo (subdir_dir));
ext->needs_tmpfs = TRUE;
res = g_list_prepend (res, ext);
}
}
unmaintained_refs = flatpak_list_unmaintained_refs (prefix, arch, branch,
NULL, NULL);
for (j = 0; unmaintained_refs != NULL && unmaintained_refs[j] != NULL; j++)
{
g_autofree char *extended_dir = g_build_filename (directory, unmaintained_refs[j] + strlen (prefix), NULL);
g_autoptr(FlatpakDecomposed) dir_ref = NULL;
g_autoptr(GFile) subdir_files = flatpak_find_unmaintained_extension_dir_if_exists (unmaintained_refs[j], arch, branch, NULL);
dir_ref = flatpak_decomposed_new_from_parts (FLATPAK_KINDS_RUNTIME, unmaintained_refs[j], arch, branch, NULL);
if (dir_ref == NULL)
continue;
if (subdir_files && flatpak_extension_matches_reason (unmaintained_refs[j], enable_if, TRUE))
{
ext = flatpak_extension_new (extension, unmaintained_refs[j], dir_ref,
extended_dir, add_ld_path, subdir_suffix,
merge_dirs, subdir_files, NULL, TRUE, NULL);
ext->needs_tmpfs = TRUE;
res = g_list_prepend (res, ext);
}
}
}
return res;
}
GList *
flatpak_list_extensions (GKeyFile *metakey,
const char *arch,
const char *default_branch)
{
g_auto(GStrv) groups = NULL;
int i, j;
GList *res;
res = NULL;
if (arch == NULL)
arch = flatpak_get_arch ();
groups = g_key_file_get_groups (metakey, NULL);
for (i = 0; groups[i] != NULL; i++)
{
char *extension;
if (g_str_has_prefix (groups[i], FLATPAK_METADATA_GROUP_PREFIX_EXTENSION) &&
*(extension = (groups[i] + strlen (FLATPAK_METADATA_GROUP_PREFIX_EXTENSION))) != 0)
{
g_autofree char *version = g_key_file_get_string (metakey, groups[i],
FLATPAK_METADATA_KEY_VERSION,
NULL);
g_auto(GStrv) versions = g_key_file_get_string_list (metakey, groups[i],
FLATPAK_METADATA_KEY_VERSIONS,
NULL, NULL);
g_autofree char *name = NULL;
const char *default_branches[] = { default_branch, NULL};
const char **branches;
flatpak_parse_extension_with_tag (extension, &name, NULL);
if (versions)
branches = (const char **) versions;
else
{
if (version)
default_branches[0] = version;
branches = default_branches;
}
for (j = 0; branches[j] != NULL; j++)
res = add_extension (metakey, groups[i], name, arch, branches[j], res);
}
}
return g_list_sort (g_list_reverse (res), flatpak_extension_compare);
}
void
flatpak_log_dir_access (FlatpakDir *dir)
{
if (dir != NULL)
{
GFile *dir_path = NULL;
g_autofree char *dir_path_str = NULL;
g_autofree char *dir_name = NULL;
dir_path = flatpak_dir_get_path (dir);
if (dir_path != NULL)
dir_path_str = g_file_get_path (dir_path);
dir_name = flatpak_dir_get_name (dir);
g_info ("Opening %s flatpak installation at path %s", dir_name, dir_path_str);
}
}