mirror of
https://github.com/flatpak/flatpak.git
synced 2026-01-23 23:28:14 -05:00
For historical reasons g_qsort_with_data() "only" works with up to 2**31 items, so it won't necessarily work for pathologically large arrays and therefore is deprecated. One advantage of g_qsort_with_data() and its replacement g_sort_array() is that GLib guarantees that they are a stable sort (will not permute items that already compare equal), which is not a guarantee for glibc's qsort() and qsort_r(). However, I don't think it's actually relevant whether we are doing a stable sort in any of these places: most of the time we are sorting an array of unique items (often the keys of a hash table, which are necessarily unique), therefore the compare function will not compare equal in any case. Another advantage of the GLib functions is that they are portable, unlike qsort_r(). However, Flatpak is Linux-only, so we can freely use useful functions like qsort_r(). Signed-off-by: Simon McVittie <smcv@collabora.com>
16909 lines
603 KiB
C
16909 lines
603 KiB
C
/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s:
|
||
*
|
||
* Copyright © 2014-2019 Red Hat, Inc
|
||
* Copyright © 2017 Endless Mobile, 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/>.
|
||
*
|
||
* Authors:
|
||
* Alexander Larsson <alexl@redhat.com>
|
||
* Philip Withnall <withnall@endlessm.com>
|
||
* Matthew Leeds <matthew.leeds@endlessm.com>
|
||
*/
|
||
|
||
#include "config.h"
|
||
|
||
#include <errno.h>
|
||
#include <fcntl.h>
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <sys/file.h>
|
||
#include <sys/socket.h>
|
||
#include <sys/types.h>
|
||
#include <utime.h>
|
||
|
||
#include <glib/gi18n-lib.h>
|
||
#include <glib/gstdio.h>
|
||
|
||
#include <libxml/parser.h>
|
||
#include <libxml/tree.h>
|
||
|
||
#include <gio/gio.h>
|
||
#include <gio/gunixsocketaddress.h>
|
||
#include <ostree.h>
|
||
|
||
#ifdef USE_SYSTEM_HELPER
|
||
#include <polkit/polkit.h>
|
||
#endif
|
||
|
||
#include "flatpak-appdata-private.h"
|
||
#include "flatpak-dir-private.h"
|
||
#include "flatpak-dir-utils-private.h"
|
||
#include "flatpak-error.h"
|
||
#include "flatpak-locale-utils-private.h"
|
||
#include "flatpak-oci-registry-private.h"
|
||
#include "flatpak-ref.h"
|
||
#include "flatpak-repo-utils-private.h"
|
||
#include "flatpak-run-private.h"
|
||
#include "flatpak-utils-base-private.h"
|
||
#include "flatpak-variant-private.h"
|
||
#include "flatpak-variant-impl-private.h"
|
||
#include "flatpak-xml-utils-private.h"
|
||
#include "libglnx.h"
|
||
#include "system-helper/flatpak-system-helper.h"
|
||
|
||
#ifdef HAVE_LIBMALCONTENT
|
||
#include <libmalcontent/malcontent.h>
|
||
#include "flatpak-parental-controls-private.h"
|
||
#endif
|
||
|
||
#ifdef HAVE_LIBSYSTEMD
|
||
#define SD_JOURNAL_SUPPRESS_LOCATION
|
||
#include <systemd/sd-journal.h>
|
||
#endif
|
||
|
||
#define NO_SYSTEM_HELPER ((FlatpakSystemHelper *) (gpointer) 1)
|
||
|
||
#define SUMMARY_CACHE_TIMEOUT_SEC (60 * 5)
|
||
#define FILTER_MTIME_CHECK_TIMEOUT_MSEC 500
|
||
|
||
#define SYSCONF_INSTALLATIONS_DIR "installations.d"
|
||
#define SYSCONF_INSTALLATIONS_FILE_EXT ".conf"
|
||
|
||
#define FLATPAK_REMOTES_DIR "remotes.d"
|
||
#define FLATPAK_REMOTES_FILE_EXT ".flatpakrepo"
|
||
|
||
#define SIDELOAD_REPOS_DIR_NAME "sideload-repos"
|
||
|
||
#ifdef USE_SYSTEM_HELPER
|
||
/* This uses a weird Auto prefix to avoid conflicts with later added polkit types.
|
||
*/
|
||
typedef PolkitAuthority AutoPolkitAuthority;
|
||
typedef PolkitAuthorizationResult AutoPolkitAuthorizationResult;
|
||
typedef PolkitDetails AutoPolkitDetails;
|
||
typedef PolkitSubject AutoPolkitSubject;
|
||
|
||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (AutoPolkitAuthority, g_object_unref)
|
||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (AutoPolkitAuthorizationResult, g_object_unref)
|
||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (AutoPolkitDetails, g_object_unref)
|
||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (AutoPolkitSubject, g_object_unref)
|
||
#endif
|
||
|
||
static FlatpakOciRegistry *flatpak_dir_create_system_child_oci_registry (FlatpakDir *self,
|
||
GLnxLockFile *file_lock,
|
||
const char *token,
|
||
GError **error);
|
||
|
||
static OstreeRepo * flatpak_dir_create_child_repo (FlatpakDir *self,
|
||
GFile *cache_dir,
|
||
GLnxLockFile *file_lock,
|
||
const char *optional_commit,
|
||
GError **error);
|
||
static OstreeRepo * flatpak_dir_create_system_child_repo (FlatpakDir *self,
|
||
GLnxLockFile *file_lock,
|
||
const char *optional_commit,
|
||
GError **error);
|
||
|
||
static gboolean flatpak_dir_mirror_oci (FlatpakDir *self,
|
||
FlatpakOciRegistry *dst_registry,
|
||
FlatpakRemoteState *state,
|
||
const char *ref,
|
||
const char *opt_rev,
|
||
const char *skip_if_current_is,
|
||
const char *token,
|
||
FlatpakProgress *progress,
|
||
GCancellable *cancellable,
|
||
GError **error);
|
||
|
||
static gboolean flatpak_dir_remote_fetch_summary (FlatpakDir *self,
|
||
const char *name,
|
||
gboolean only_cached,
|
||
GBytes **out_summary,
|
||
GBytes **out_summary_sig,
|
||
GCancellable *cancellable,
|
||
GError **error);
|
||
|
||
static gboolean flatpak_dir_remote_fetch_summary_index (FlatpakDir *self,
|
||
const char *name_or_uri,
|
||
gboolean only_cached,
|
||
GBytes **out_index,
|
||
GBytes **out_index_sig,
|
||
GCancellable *cancellable,
|
||
GError **error);
|
||
|
||
static gboolean flatpak_dir_remote_fetch_indexed_summary (FlatpakDir *self,
|
||
const char *name_or_uri,
|
||
const char *arch,
|
||
GVariant *subsummary_info_v,
|
||
gboolean only_cached,
|
||
GBytes **out_summary,
|
||
GCancellable *cancellable,
|
||
GError **error);
|
||
|
||
static gboolean flatpak_dir_gc_cached_digested_summaries (FlatpakDir *self,
|
||
const char *remote_name,
|
||
const char *dont_prune_file,
|
||
GCancellable *cancellable,
|
||
GError **error);
|
||
|
||
static gboolean flatpak_dir_cleanup_remote_for_url_change (FlatpakDir *self,
|
||
const char *remote_name,
|
||
const char *url,
|
||
GCancellable *cancellable,
|
||
GError **error);
|
||
|
||
static gboolean flatpak_dir_lookup_remote_filter (FlatpakDir *self,
|
||
const char *name,
|
||
gboolean force_load,
|
||
char **checksum_out,
|
||
GRegex **allow_regex,
|
||
GRegex **deny_regex,
|
||
GError **error);
|
||
|
||
static void ensure_http_session (FlatpakDir *self);
|
||
|
||
static void flatpak_dir_log (FlatpakDir *self,
|
||
const char *file,
|
||
int line,
|
||
const char *func,
|
||
const char *source,
|
||
const char *change,
|
||
const char *remote,
|
||
const char *ref,
|
||
const char *commit,
|
||
const char *old_commit,
|
||
const char *url,
|
||
const char *format,
|
||
...) G_GNUC_PRINTF (12, 13);
|
||
|
||
#define flatpak_dir_log(self, change, remote, ref, commit, old_commit, url, format, ...) \
|
||
(flatpak_dir_log) (self, __FILE__, __LINE__, __FUNCTION__, \
|
||
NULL, change, remote, ref, commit, old_commit, url, format, __VA_ARGS__)
|
||
|
||
static GBytes *upgrade_deploy_data (GBytes *deploy_data,
|
||
GFile *deploy_dir,
|
||
FlatpakDecomposed *ref,
|
||
OstreeRepo *repo,
|
||
GCancellable *cancellable,
|
||
GError **error);
|
||
|
||
typedef struct
|
||
{
|
||
GBytes *bytes;
|
||
GBytes *bytes_sig;
|
||
char *name;
|
||
char *url;
|
||
guint64 time;
|
||
} CachedSummary;
|
||
|
||
typedef struct
|
||
{
|
||
char *id;
|
||
char *display_name;
|
||
gint priority;
|
||
FlatpakDirStorageType storage_type;
|
||
} DirExtraData;
|
||
|
||
typedef struct {
|
||
GFile *path;
|
||
GTimeVal mtime;
|
||
guint64 last_mtime_check;
|
||
char *checksum;
|
||
GRegex *allow;
|
||
GRegex *deny;
|
||
} RemoteFilter;
|
||
|
||
struct FlatpakDir
|
||
{
|
||
GObject parent;
|
||
|
||
gboolean user;
|
||
GFile *basedir;
|
||
DirExtraData *extra_data;
|
||
OstreeRepo *repo;
|
||
GFile *cache_dir;
|
||
gboolean no_system_helper;
|
||
gboolean no_interaction;
|
||
pid_t source_pid;
|
||
|
||
GDBusConnection *system_helper_bus;
|
||
|
||
GHashTable *summary_cache;
|
||
|
||
GHashTable *remote_filters;
|
||
|
||
/* Config cache, protected by config_cache lock */
|
||
GRegex *masked;
|
||
GRegex *pinned;
|
||
|
||
FlatpakHttpSession *http_session;
|
||
};
|
||
|
||
G_LOCK_DEFINE_STATIC (config_cache);
|
||
|
||
typedef struct
|
||
{
|
||
GObjectClass parent_class;
|
||
} FlatpakDirClass;
|
||
|
||
struct FlatpakDeploy
|
||
{
|
||
GObject parent;
|
||
|
||
FlatpakDecomposed *ref;
|
||
GFile *dir;
|
||
GKeyFile *metadata;
|
||
FlatpakContext *system_overrides;
|
||
FlatpakContext *user_overrides;
|
||
FlatpakContext *system_app_overrides;
|
||
FlatpakContext *user_app_overrides;
|
||
OstreeRepo *repo;
|
||
};
|
||
|
||
typedef struct
|
||
{
|
||
GObjectClass parent_class;
|
||
} FlatpakDeployClass;
|
||
|
||
G_DEFINE_TYPE (FlatpakDir, flatpak_dir, G_TYPE_OBJECT)
|
||
G_DEFINE_TYPE (FlatpakDeploy, flatpak_deploy, G_TYPE_OBJECT)
|
||
|
||
enum {
|
||
PROP_0,
|
||
|
||
PROP_USER,
|
||
PROP_PATH
|
||
};
|
||
|
||
#define OSTREE_GIO_FAST_QUERYINFO ("standard::name,standard::type,standard::size,standard::is-symlink,standard::symlink-target," \
|
||
"unix::device,unix::inode,unix::mode,unix::uid,unix::gid,unix::rdev")
|
||
|
||
static const char *
|
||
get_config_dir_location (void)
|
||
{
|
||
static gsize path = 0;
|
||
|
||
if (g_once_init_enter (&path))
|
||
{
|
||
gsize setup_value = 0;
|
||
const char *config_dir = g_getenv ("FLATPAK_CONFIG_DIR");
|
||
if (config_dir != NULL)
|
||
setup_value = (gsize) config_dir;
|
||
else
|
||
setup_value = (gsize) FLATPAK_CONFIGDIR;
|
||
g_once_init_leave (&path, setup_value);
|
||
}
|
||
|
||
return (const char *) path;
|
||
}
|
||
|
||
static const char *
|
||
get_run_dir_location (void)
|
||
{
|
||
static gsize path = 0;
|
||
|
||
if (g_once_init_enter (&path))
|
||
{
|
||
gsize setup_value = 0;
|
||
/* Note: $FLATPAK_RUN_DIR should only be set in the unit tests. At
|
||
* runtime, /run/flatpak is assumed by
|
||
* flatpak-create-sideload-symlinks.sh
|
||
*/
|
||
const char *config_dir = g_getenv ("FLATPAK_RUN_DIR");
|
||
if (config_dir != NULL)
|
||
setup_value = (gsize) config_dir;
|
||
else
|
||
setup_value = (gsize) "/run/flatpak";
|
||
g_once_init_leave (&path, setup_value);
|
||
}
|
||
|
||
return (const char *) path;
|
||
}
|
||
|
||
static void
|
||
flatpak_sideload_state_free (FlatpakSideloadState *sideload_state)
|
||
{
|
||
g_object_unref (sideload_state->repo);
|
||
g_variant_unref (sideload_state->summary);
|
||
g_free (sideload_state);
|
||
}
|
||
|
||
static void
|
||
variant_maybe_unref (GVariant *variant)
|
||
{
|
||
if (variant)
|
||
g_variant_unref (variant);
|
||
}
|
||
|
||
static FlatpakRemoteState *
|
||
flatpak_remote_state_new (void)
|
||
{
|
||
FlatpakRemoteState *state = g_new0 (FlatpakRemoteState, 1);
|
||
|
||
state->refcount = 1;
|
||
state->sideload_repos = g_ptr_array_new_with_free_func ((GDestroyNotify)flatpak_sideload_state_free);
|
||
state->subsummaries = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)variant_maybe_unref);
|
||
return state;
|
||
}
|
||
|
||
FlatpakRemoteState *
|
||
flatpak_remote_state_ref (FlatpakRemoteState *remote_state)
|
||
{
|
||
g_assert (remote_state->refcount > 0);
|
||
remote_state->refcount++;
|
||
return remote_state;
|
||
}
|
||
|
||
void
|
||
flatpak_remote_state_unref (FlatpakRemoteState *remote_state)
|
||
{
|
||
g_assert (remote_state->refcount > 0);
|
||
remote_state->refcount--;
|
||
|
||
if (remote_state->refcount == 0)
|
||
{
|
||
g_free (remote_state->remote_name);
|
||
g_free (remote_state->collection_id);
|
||
g_clear_pointer (&remote_state->index, g_variant_unref);
|
||
g_clear_pointer (&remote_state->index_ht, g_hash_table_unref);
|
||
g_clear_pointer (&remote_state->index_sig_bytes, g_bytes_unref);
|
||
g_clear_pointer (&remote_state->subsummaries, g_hash_table_unref);
|
||
g_clear_pointer (&remote_state->summary, g_variant_unref);
|
||
g_clear_pointer (&remote_state->summary_bytes, g_bytes_unref);
|
||
g_clear_pointer (&remote_state->summary_sig_bytes, g_bytes_unref);
|
||
g_clear_error (&remote_state->summary_fetch_error);
|
||
g_clear_pointer (&remote_state->allow_refs, g_regex_unref);
|
||
g_clear_pointer (&remote_state->deny_refs, g_regex_unref);
|
||
g_clear_pointer (&remote_state->sideload_repos, g_ptr_array_unref);
|
||
|
||
g_free (remote_state);
|
||
}
|
||
}
|
||
|
||
static gboolean
|
||
_validate_summary_for_collection_id (GVariant *summary_v,
|
||
const char *collection_id,
|
||
GError **error)
|
||
{
|
||
VarSummaryRef summary;
|
||
summary = var_summary_from_gvariant (summary_v);
|
||
|
||
if (!flatpak_summary_find_ref_map (summary, collection_id, NULL))
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA,
|
||
_("Configured collection ID ‘%s’ not in summary file"), collection_id);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static void
|
||
flatpak_remote_state_add_sideload_repo (FlatpakRemoteState *self,
|
||
GFile *dir)
|
||
{
|
||
g_autoptr(GFile) summary_path = NULL;
|
||
g_autoptr(GMappedFile) mfile = NULL;
|
||
g_autoptr(OstreeRepo) sideload_repo = NULL;
|
||
|
||
/* Sideloading only works if collection id is set */
|
||
if (self->collection_id == NULL)
|
||
return;
|
||
|
||
summary_path = g_file_get_child (dir, "summary");
|
||
sideload_repo = ostree_repo_new (dir);
|
||
|
||
mfile = g_mapped_file_new (flatpak_file_get_path_cached (summary_path), FALSE, NULL);
|
||
if (mfile != NULL && ostree_repo_open (sideload_repo, NULL, NULL))
|
||
{
|
||
g_autoptr(GError) local_error = NULL;
|
||
g_autoptr(GBytes) summary_bytes = g_mapped_file_get_bytes (mfile);
|
||
FlatpakSideloadState *ss = g_new0 (FlatpakSideloadState, 1);
|
||
|
||
ss->repo = g_steal_pointer (&sideload_repo);
|
||
ss->summary = g_variant_ref_sink (g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, summary_bytes, TRUE));
|
||
|
||
if (!_validate_summary_for_collection_id (ss->summary, self->collection_id, &local_error))
|
||
{
|
||
/* We expect to hit this code path when the repo is providing things
|
||
* from other remotes
|
||
*/
|
||
g_info ("Sideload repo at path %s not valid for remote %s: %s",
|
||
flatpak_file_get_path_cached (dir), self->remote_name, local_error->message);
|
||
flatpak_sideload_state_free (ss);
|
||
}
|
||
else
|
||
{
|
||
g_ptr_array_add (self->sideload_repos, ss);
|
||
g_info ("Using sideloaded repo %s for remote %s", flatpak_file_get_path_cached (dir), self->remote_name);
|
||
}
|
||
}
|
||
}
|
||
|
||
static void add_sideload_subdirs (GPtrArray *res,
|
||
GFile *parent,
|
||
gboolean recurse);
|
||
|
||
static void
|
||
add_sideload_create_usb_subdirs (GPtrArray *res,
|
||
GFile *parent)
|
||
{
|
||
g_autoptr(GFile) ostree_repo_subpath = NULL;
|
||
g_autoptr(GFile) dot_ostree_repo_subpath = NULL;
|
||
g_autoptr(GFile) dot_ostree_repo_d_subpath = NULL;
|
||
g_autoptr(OstreeRepo) ostree_repo_subpath_repo = NULL;
|
||
g_autoptr(OstreeRepo) dot_ostree_repo_subpath_repo = NULL;
|
||
|
||
/* This path is not used by "flatpak create-usb" but it's a standard location
|
||
* recognized by libostree; see the man page ostree create-usb(1)
|
||
*/
|
||
ostree_repo_subpath = g_file_resolve_relative_path (parent, "ostree/repo");
|
||
ostree_repo_subpath_repo = ostree_repo_new (ostree_repo_subpath);
|
||
if (ostree_repo_open (ostree_repo_subpath_repo, NULL, NULL))
|
||
g_ptr_array_add (res, g_object_ref (ostree_repo_subpath));
|
||
|
||
/* These paths are used by "flatpak create-usb" */
|
||
dot_ostree_repo_subpath = g_file_resolve_relative_path (parent, ".ostree/repo");
|
||
dot_ostree_repo_subpath_repo = ostree_repo_new (dot_ostree_repo_subpath);
|
||
if (ostree_repo_open (dot_ostree_repo_subpath_repo, NULL, NULL))
|
||
g_ptr_array_add (res, g_object_ref (dot_ostree_repo_subpath));
|
||
|
||
dot_ostree_repo_d_subpath = g_file_resolve_relative_path (parent, ".ostree/repos.d");
|
||
add_sideload_subdirs (res, dot_ostree_repo_d_subpath, FALSE);
|
||
}
|
||
|
||
static void
|
||
add_sideload_subdirs (GPtrArray *res,
|
||
GFile *parent,
|
||
gboolean recurse)
|
||
{
|
||
g_autoptr(GFileEnumerator) dir_enum = NULL;
|
||
|
||
dir_enum = g_file_enumerate_children (parent,
|
||
G_FILE_ATTRIBUTE_STANDARD_NAME ","
|
||
G_FILE_ATTRIBUTE_STANDARD_TYPE,
|
||
G_FILE_QUERY_INFO_NONE,
|
||
NULL, NULL);
|
||
if (dir_enum == NULL)
|
||
return;
|
||
|
||
while (TRUE)
|
||
{
|
||
GFileInfo *info;
|
||
GFile *path;
|
||
|
||
if (!g_file_enumerator_iterate (dir_enum, &info, &path, NULL, NULL) ||
|
||
info == NULL)
|
||
break;
|
||
|
||
/* Here we support either a plain repo or, if @recurse is TRUE, the root
|
||
* directory of a USB created with "flatpak create-usb"
|
||
*/
|
||
if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
|
||
{
|
||
g_autoptr(OstreeRepo) repo = ostree_repo_new (path);
|
||
|
||
if (ostree_repo_open (repo, NULL, NULL))
|
||
g_ptr_array_add (res, g_object_ref (path));
|
||
else if (recurse)
|
||
add_sideload_create_usb_subdirs (res, path);
|
||
}
|
||
}
|
||
}
|
||
|
||
void
|
||
flatpak_remote_state_add_sideload_dir (FlatpakRemoteState *self,
|
||
GFile *dir)
|
||
{
|
||
g_autoptr(GPtrArray) sideload_paths = g_ptr_array_new_with_free_func (g_object_unref);
|
||
|
||
/* The directory could be a repo */
|
||
flatpak_remote_state_add_sideload_repo (self, dir);
|
||
|
||
/* Or it could be a directory with repos in well-known subdirectories */
|
||
add_sideload_create_usb_subdirs (sideload_paths, dir);
|
||
for (int i = 0; i < sideload_paths->len; i++)
|
||
flatpak_remote_state_add_sideload_repo (self, g_ptr_array_index (sideload_paths, i));
|
||
}
|
||
|
||
gboolean
|
||
flatpak_remote_state_ensure_summary (FlatpakRemoteState *self,
|
||
GError **error)
|
||
{
|
||
if (self->index == NULL && self->summary == NULL)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Unable to load summary from remote %s: %s"), self->remote_name,
|
||
self->summary_fetch_error != NULL ? self->summary_fetch_error->message : "unknown error");
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_remote_state_ensure_subsummary (FlatpakRemoteState *self,
|
||
FlatpakDir *dir,
|
||
const char *arch,
|
||
gboolean only_cached,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
GVariant *subsummary;
|
||
const char *alt_arch;
|
||
GVariant *subsummary_info_v;
|
||
|
||
g_autoptr(GBytes) bytes = NULL;
|
||
|
||
if (self->summary != NULL)
|
||
return TRUE; /* We have them all anyway */
|
||
|
||
if (self->index == NULL)
|
||
return TRUE; /* Don't fail unnecessarily in e.g. the sideload case */
|
||
|
||
if (g_hash_table_contains (self->subsummaries, arch))
|
||
return TRUE;
|
||
|
||
/* If i.e. we already loaded x86_64 subsummary (which has i386 refs),
|
||
* don't load i386 one */
|
||
alt_arch = flatpak_get_compat_arch_reverse (arch);
|
||
if (alt_arch != NULL &&
|
||
g_hash_table_contains (self->subsummaries, alt_arch))
|
||
return TRUE;
|
||
|
||
subsummary_info_v = g_hash_table_lookup (self->index_ht, arch);
|
||
if (subsummary_info_v == NULL)
|
||
return TRUE; /* No refs for this arch */
|
||
|
||
if (!flatpak_dir_remote_fetch_indexed_summary (dir, self->remote_name, arch, subsummary_info_v, only_cached,
|
||
&bytes, cancellable, error))
|
||
return FALSE;
|
||
|
||
subsummary = g_variant_ref_sink (g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, bytes, FALSE));
|
||
g_hash_table_insert (self->subsummaries, g_strdup (arch), subsummary);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_remote_state_ensure_subsummary_all_arches (FlatpakRemoteState *self,
|
||
FlatpakDir *dir,
|
||
gboolean only_cached,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
if (self->index_ht == NULL)
|
||
return TRUE; /* No subsummaries, got all arches anyway */
|
||
|
||
GLNX_HASH_TABLE_FOREACH (self->index_ht, const char *, arch)
|
||
{
|
||
g_autoptr(GError) local_error = NULL;
|
||
|
||
if (!flatpak_remote_state_ensure_subsummary (self, dir, arch, only_cached, cancellable, &local_error))
|
||
{
|
||
/* Don't error on non-cached subsummaries */
|
||
if (only_cached && g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_CACHED))
|
||
continue;
|
||
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
gboolean
|
||
flatpak_remote_state_allow_ref (FlatpakRemoteState *self,
|
||
const char *ref)
|
||
{
|
||
return flatpak_filters_allow_ref (self->allow_refs, self->deny_refs, ref);
|
||
}
|
||
|
||
|
||
static guint64
|
||
get_timestamp_from_ref_info (VarRefInfoRef info)
|
||
{
|
||
VarMetadataRef metadata = var_ref_info_get_metadata (info);
|
||
return GUINT64_FROM_BE(var_metadata_lookup_uint64 (metadata, OSTREE_COMMIT_TIMESTAMP, 0));
|
||
}
|
||
|
||
|
||
GFile *
|
||
flatpak_remote_state_lookup_sideload_checksum (FlatpakRemoteState *self,
|
||
char *checksum)
|
||
{
|
||
for (int i = 0; i < self->sideload_repos->len; i++)
|
||
{
|
||
FlatpakSideloadState *ss = g_ptr_array_index (self->sideload_repos, i);
|
||
OstreeRepoCommitState commit_state;
|
||
|
||
if (ostree_repo_load_commit (ss->repo, checksum, NULL, &commit_state, NULL) &&
|
||
commit_state == OSTREE_REPO_COMMIT_STATE_NORMAL)
|
||
return g_object_ref (ostree_repo_get_path (ss->repo));
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_remote_state_resolve_sideloaded_ref (FlatpakRemoteState *self,
|
||
const char *ref,
|
||
char **out_checksum,
|
||
guint64 *out_timestamp,
|
||
VarRefInfoRef *out_info,
|
||
FlatpakSideloadState **out_sideload_state,
|
||
GError **error)
|
||
{
|
||
g_autofree char *latest_checksum = NULL;
|
||
guint64 latest_timestamp = 0;
|
||
FlatpakSideloadState *latest_ss = NULL;
|
||
VarRefInfoRef latest_sideload_info;
|
||
|
||
for (int i = 0; i < self->sideload_repos->len; i++)
|
||
{
|
||
FlatpakSideloadState *ss = g_ptr_array_index (self->sideload_repos, i);
|
||
g_autofree char *sideload_checksum = NULL;
|
||
VarRefInfoRef sideload_info;
|
||
|
||
if (flatpak_summary_lookup_ref (ss->summary, self->collection_id, ref, &sideload_checksum, &sideload_info))
|
||
{
|
||
guint64 timestamp = get_timestamp_from_ref_info (sideload_info);
|
||
|
||
if (latest_checksum == 0 || latest_timestamp < timestamp)
|
||
{
|
||
g_free (latest_checksum);
|
||
latest_checksum = g_steal_pointer (&sideload_checksum);
|
||
latest_timestamp = timestamp;
|
||
latest_sideload_info = sideload_info;
|
||
latest_ss = ss;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (latest_checksum == NULL)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND,
|
||
_("No such ref '%s' in remote %s"),
|
||
ref, self->remote_name);
|
||
|
||
if (out_checksum)
|
||
*out_checksum = g_steal_pointer (&latest_checksum);
|
||
if (out_timestamp)
|
||
*out_timestamp = latest_timestamp;
|
||
if (out_info)
|
||
*out_info = latest_sideload_info;
|
||
if (out_sideload_state)
|
||
*out_sideload_state = latest_ss;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static GVariant *
|
||
get_summary_for_ref (FlatpakRemoteState *self,
|
||
const char *ref)
|
||
{
|
||
GVariant *summary = NULL;
|
||
|
||
if (self->index != NULL)
|
||
{
|
||
g_autofree char * arch = flatpak_get_arch_for_ref (ref);
|
||
|
||
if (arch != NULL)
|
||
summary = g_hash_table_lookup (self->subsummaries, arch);
|
||
|
||
if (summary == NULL && arch != NULL)
|
||
{
|
||
const char *non_compat_arch = flatpak_get_compat_arch_reverse (arch);
|
||
|
||
if (non_compat_arch != NULL)
|
||
summary = g_hash_table_lookup (self->subsummaries, non_compat_arch);
|
||
}
|
||
}
|
||
else
|
||
summary = self->summary;
|
||
|
||
return summary;
|
||
}
|
||
|
||
/* Returns TRUE if the ref is found in the summary or cache.
|
||
* out_checksum and out_variant are only set when the ref is found.
|
||
*/
|
||
gboolean
|
||
flatpak_remote_state_lookup_ref (FlatpakRemoteState *self,
|
||
const char *ref,
|
||
char **out_checksum,
|
||
guint64 *out_timestamp,
|
||
VarRefInfoRef *out_info,
|
||
GFile **out_sideload_path,
|
||
GError **error)
|
||
{
|
||
if (!flatpak_remote_state_allow_ref (self, ref))
|
||
{
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND,
|
||
_("No entry for %s in remote %s summary flatpak cache"),
|
||
ref, self->remote_name);
|
||
}
|
||
|
||
/* If there is a summary we use it for metadata and for latest. We may later install from a sideloaded source though */
|
||
if (self->summary != NULL || self->index != NULL)
|
||
{
|
||
VarRefInfoRef info;
|
||
g_autofree char *checksum = NULL;
|
||
GVariant *summary;
|
||
|
||
summary = get_summary_for_ref (self, ref);
|
||
if (summary == NULL ||
|
||
!flatpak_summary_lookup_ref (summary, NULL, ref, &checksum, &info))
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND,
|
||
_("No such ref '%s' in remote %s"),
|
||
ref, self->remote_name);
|
||
|
||
/* Even if its available in the summary we want to install it from a sideload repo if available */
|
||
|
||
if (out_sideload_path)
|
||
{
|
||
g_autoptr(GFile) found_sideload_path = NULL;
|
||
|
||
for (int i = 0; i < self->sideload_repos->len; i++)
|
||
{
|
||
FlatpakSideloadState *ss = g_ptr_array_index (self->sideload_repos, i);
|
||
OstreeRepoCommitState commit_state;
|
||
|
||
if (ostree_repo_load_commit (ss->repo, checksum, NULL, &commit_state, NULL) &&
|
||
commit_state == OSTREE_REPO_COMMIT_STATE_NORMAL)
|
||
{
|
||
found_sideload_path = g_object_ref (ostree_repo_get_path (ss->repo));
|
||
break;
|
||
}
|
||
}
|
||
|
||
*out_sideload_path = g_steal_pointer (&found_sideload_path);
|
||
}
|
||
|
||
if (out_info)
|
||
*out_info = info;
|
||
if (out_checksum)
|
||
*out_checksum = g_steal_pointer (&checksum);
|
||
if (out_timestamp)
|
||
*out_timestamp = get_timestamp_from_ref_info (info);
|
||
}
|
||
else
|
||
{
|
||
FlatpakSideloadState *ss = NULL;
|
||
|
||
if (!flatpak_remote_state_resolve_sideloaded_ref (self, ref, out_checksum, out_timestamp, out_info, &ss, error))
|
||
return FALSE;
|
||
|
||
if (out_sideload_path)
|
||
*out_sideload_path = g_object_ref (ostree_repo_get_path (ss->repo));
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
GPtrArray *
|
||
flatpak_remote_state_match_subrefs (FlatpakRemoteState *self,
|
||
FlatpakDecomposed *ref)
|
||
{
|
||
GVariant *summary;
|
||
|
||
if (self->summary == NULL && self->index == NULL)
|
||
{
|
||
g_info ("flatpak_remote_state_match_subrefs with no summary");
|
||
return g_ptr_array_new_with_free_func ((GDestroyNotify)flatpak_decomposed_unref);
|
||
}
|
||
|
||
summary = get_summary_for_ref (self, flatpak_decomposed_get_ref (ref));
|
||
if (summary == NULL)
|
||
return g_ptr_array_new_with_free_func ((GDestroyNotify)flatpak_decomposed_unref);
|
||
|
||
return flatpak_summary_match_subrefs (summary, NULL, ref);
|
||
}
|
||
|
||
static VarMetadataRef
|
||
flatpak_remote_state_get_main_metadata (FlatpakRemoteState *self)
|
||
{
|
||
VarSummaryRef summary;
|
||
VarSummaryIndexRef index;
|
||
VarMetadataRef meta;
|
||
|
||
if (self->index)
|
||
{
|
||
index = var_summary_index_from_gvariant (self->index);
|
||
meta = var_summary_index_get_metadata (index);
|
||
}
|
||
else if (self->summary)
|
||
{
|
||
summary = var_summary_from_gvariant (self->summary);
|
||
meta = var_summary_get_metadata (summary);
|
||
}
|
||
else
|
||
g_assert_not_reached ();
|
||
|
||
return meta;
|
||
}
|
||
|
||
|
||
/* 0 if not specified */
|
||
static guint32
|
||
flatpak_remote_state_get_cache_version (FlatpakRemoteState *self)
|
||
{
|
||
VarMetadataRef meta;
|
||
|
||
if (!flatpak_remote_state_ensure_summary (self, NULL))
|
||
return 0;
|
||
|
||
meta = flatpak_remote_state_get_main_metadata (self);
|
||
return GUINT32_FROM_LE (var_metadata_lookup_uint32 (meta, "xa.cache-version", 0));
|
||
}
|
||
|
||
gboolean
|
||
flatpak_remote_state_lookup_cache (FlatpakRemoteState *self,
|
||
const char *ref,
|
||
guint64 *out_download_size,
|
||
guint64 *out_installed_size,
|
||
const char **out_metadata,
|
||
GError **error)
|
||
{
|
||
VarCacheDataRef cache_data;
|
||
VarMetadataRef meta;
|
||
VarSummaryRef summary;
|
||
guint32 summary_version;
|
||
GVariant *summary_v;
|
||
|
||
if (!flatpak_remote_state_ensure_summary (self, error))
|
||
return FALSE;
|
||
|
||
summary_v = get_summary_for_ref (self, ref);
|
||
if (summary_v == NULL)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND,
|
||
_("No entry for %s in remote %s summary flatpak cache"),
|
||
ref, self->remote_name);
|
||
|
||
|
||
summary = var_summary_from_gvariant (summary_v);
|
||
meta = var_summary_get_metadata (summary);
|
||
|
||
summary_version = GUINT32_FROM_LE (var_metadata_lookup_uint32 (meta, "xa.summary-version", 0));
|
||
|
||
if (summary_version == 0)
|
||
{
|
||
VarCacheRef cache;
|
||
gsize pos;
|
||
VarVariantRef cache_vv;
|
||
VarVariantRef cache_v;
|
||
|
||
if (!var_metadata_lookup (meta, "xa.cache", NULL, &cache_vv))
|
||
{
|
||
flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("No summary or Flatpak cache available for remote %s"),
|
||
self->remote_name);
|
||
return FALSE;
|
||
}
|
||
|
||
/* For stupid historical reasons the xa.cache is double-wrapped in a variant */
|
||
cache_v = var_variant_from_variant (cache_vv);
|
||
cache = var_cache_from_variant (cache_v);
|
||
|
||
if (!var_cache_lookup (cache, ref, &pos, &cache_data))
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND,
|
||
_("No entry for %s in remote %s summary flatpak cache"),
|
||
ref, self->remote_name);
|
||
}
|
||
else if (summary_version == 1)
|
||
{
|
||
VarRefMapRef ref_map = var_summary_get_ref_map (summary);
|
||
VarRefInfoRef info;
|
||
VarMetadataRef commit_metadata;
|
||
VarVariantRef cache_data_v;
|
||
|
||
if (!flatpak_var_ref_map_lookup_ref (ref_map, ref, &info))
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND,
|
||
_("No entry for %s in remote %s summary flatpak cache"),
|
||
ref, self->remote_name);
|
||
|
||
commit_metadata = var_ref_info_get_metadata (info);
|
||
if (!var_metadata_lookup (commit_metadata, "xa.data", NULL, &cache_data_v))
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Missing xa.data in summary for remote %s"),
|
||
self->remote_name);
|
||
cache_data = var_cache_data_from_variant (cache_data_v);
|
||
}
|
||
else
|
||
{
|
||
flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Unsupported summary version %d for remote %s"),
|
||
summary_version, self->remote_name);
|
||
return FALSE;
|
||
}
|
||
|
||
if (out_installed_size)
|
||
*out_installed_size = var_cache_data_get_installed_size (cache_data);
|
||
|
||
if (out_download_size)
|
||
*out_download_size = var_cache_data_get_download_size (cache_data);
|
||
|
||
if (out_metadata)
|
||
*out_metadata = var_cache_data_get_metadata (cache_data);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_remote_state_load_data (FlatpakRemoteState *self,
|
||
const char *ref,
|
||
guint64 *out_download_size,
|
||
guint64 *out_installed_size,
|
||
char **out_metadata,
|
||
GError **error)
|
||
{
|
||
if (self->summary || self->index)
|
||
{
|
||
const char *metadata = NULL;
|
||
if (!flatpak_remote_state_lookup_cache (self, ref, out_download_size, out_installed_size, &metadata, error))
|
||
return FALSE;
|
||
|
||
if (out_metadata)
|
||
*out_metadata = g_strdup (metadata);
|
||
}
|
||
else
|
||
{
|
||
/* Look up from sideload */
|
||
g_autofree char *checksum = NULL;
|
||
guint64 timestamp;
|
||
VarRefInfoRef info;
|
||
FlatpakSideloadState *ss = NULL;
|
||
g_autoptr(GVariant) commit_data = NULL;
|
||
g_autoptr(GVariant) commit_metadata = NULL;
|
||
const char *xa_metadata = NULL;
|
||
guint64 download_size = 0;
|
||
guint64 installed_size = 0;
|
||
|
||
/* Use sideload refs if any */
|
||
|
||
if (!flatpak_remote_state_resolve_sideloaded_ref (self, ref, &checksum, ×tamp,
|
||
&info, &ss, error))
|
||
return FALSE;
|
||
|
||
if (!ostree_repo_load_commit (ss->repo, checksum, &commit_data, NULL, error))
|
||
return FALSE;
|
||
|
||
commit_metadata = g_variant_get_child_value (commit_data, 0);
|
||
g_variant_lookup (commit_metadata, "xa.metadata", "&s", &xa_metadata);
|
||
if (xa_metadata == NULL)
|
||
return flatpak_fail (error, "No xa.metadata in sideload commit %s ref %s", checksum, ref);
|
||
|
||
if (g_variant_lookup (commit_metadata, "xa.download-size", "t", &download_size))
|
||
download_size = GUINT64_FROM_BE (download_size);
|
||
if (g_variant_lookup (commit_metadata, "xa.installed-size", "t", &installed_size))
|
||
installed_size = GUINT64_FROM_BE (installed_size);
|
||
|
||
if (out_installed_size)
|
||
*out_installed_size = installed_size;
|
||
|
||
if (out_download_size)
|
||
*out_download_size = download_size;
|
||
|
||
if (out_metadata)
|
||
*out_metadata = g_strdup (xa_metadata);
|
||
}
|
||
return TRUE;
|
||
}
|
||
|
||
static char *
|
||
lookup_oci_registry_uri_from_summary (GVariant *summary,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GVariant) extensions = g_variant_get_child_value (summary, 1);
|
||
g_autofree char *registry_uri = NULL;
|
||
|
||
if (!g_variant_lookup (extensions, "xa.oci-registry-uri", "s", ®istry_uri))
|
||
{
|
||
flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Remote OCI index has no registry uri"));
|
||
return NULL;
|
||
}
|
||
|
||
return g_steal_pointer (®istry_uri);
|
||
}
|
||
|
||
static FlatpakOciRegistry *
|
||
flatpak_remote_state_new_oci_registry (FlatpakRemoteState *self,
|
||
const char *token,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autofree char *registry_uri = NULL;
|
||
g_autoptr(FlatpakOciRegistry) registry = NULL;
|
||
|
||
if (!flatpak_remote_state_ensure_summary (self, error))
|
||
return NULL;
|
||
|
||
registry_uri = lookup_oci_registry_uri_from_summary (self->summary, error);
|
||
if (registry_uri == NULL)
|
||
return NULL;
|
||
|
||
registry = flatpak_oci_registry_new (registry_uri, FALSE, -1, NULL, error);
|
||
if (registry == NULL)
|
||
return NULL;
|
||
|
||
flatpak_oci_registry_set_token (registry, token);
|
||
|
||
return g_steal_pointer (®istry);
|
||
}
|
||
|
||
static GVariant *
|
||
flatpak_remote_state_fetch_commit_object_oci (FlatpakRemoteState *self,
|
||
FlatpakDir *dir,
|
||
const char *ref,
|
||
const char *checksum,
|
||
const char *token,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(FlatpakOciRegistry) registry = NULL;
|
||
g_autoptr(FlatpakOciVersioned) versioned = NULL;
|
||
g_autoptr(FlatpakOciImage) image_config = NULL;
|
||
g_autofree char *oci_digest = NULL;
|
||
g_autofree char *latest_rev = NULL;
|
||
VarRefInfoRef latest_rev_info;
|
||
VarMetadataRef metadata;
|
||
const char *oci_repository = NULL;
|
||
GHashTable *labels;
|
||
g_autofree char *subject = NULL;
|
||
g_autofree char *body = NULL;
|
||
g_autofree char *manifest_ref = NULL;
|
||
g_autofree char *parent = NULL;
|
||
guint64 timestamp = 0;
|
||
g_autoptr(GVariantBuilder) metadata_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
|
||
g_autoptr(GVariant) metadata_v = NULL;
|
||
|
||
registry = flatpak_remote_state_new_oci_registry (self, token, cancellable, error);
|
||
if (registry == NULL)
|
||
return NULL;
|
||
|
||
/* We extract the rev info from the latest, even if we don't use the latest digest, assuming refs don't move */
|
||
if (!flatpak_remote_state_lookup_ref (self, ref, &latest_rev, NULL, &latest_rev_info, NULL, error))
|
||
return NULL;
|
||
|
||
if (latest_rev == NULL)
|
||
{
|
||
flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND,
|
||
_("Couldn't find ref %s in remote %s"),
|
||
ref, self->remote_name);
|
||
return NULL;
|
||
}
|
||
|
||
metadata = var_ref_info_get_metadata (latest_rev_info);
|
||
oci_repository = var_metadata_lookup_string (metadata, "xa.oci-repository", NULL);
|
||
|
||
oci_digest = g_strconcat ("sha256:", checksum, NULL);
|
||
|
||
versioned = flatpak_oci_registry_load_versioned (registry, oci_repository, oci_digest,
|
||
NULL, NULL, cancellable, error);
|
||
if (versioned == NULL)
|
||
return NULL;
|
||
|
||
if (!FLATPAK_IS_OCI_MANIFEST (versioned))
|
||
{
|
||
flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Image is not a manifest"));
|
||
return NULL;
|
||
}
|
||
|
||
image_config = flatpak_oci_registry_load_image_config (registry, oci_repository,
|
||
FLATPAK_OCI_MANIFEST (versioned)->config.digest,
|
||
(const char **)FLATPAK_OCI_MANIFEST (versioned)->config.urls,
|
||
NULL, cancellable, error);
|
||
if (image_config == NULL)
|
||
return NULL;
|
||
|
||
labels = flatpak_oci_image_get_labels (image_config);
|
||
if (labels)
|
||
flatpak_oci_parse_commit_labels (labels, ×tamp,
|
||
&subject, &body,
|
||
&manifest_ref, NULL, &parent,
|
||
metadata_builder);
|
||
|
||
|
||
if (g_strcmp0 (manifest_ref, ref) != 0)
|
||
{
|
||
flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Commit has no requested ref ‘%s’ in ref binding metadata"), ref);
|
||
return NULL;
|
||
}
|
||
|
||
metadata_v = g_variant_ref_sink (g_variant_builder_end (metadata_builder));
|
||
|
||
/* This isn't going to be exactly the same as the reconstructed one from the pull, because we don't have the contents, but its useful to get metadata */
|
||
return
|
||
g_variant_ref_sink (g_variant_new ("(@a{sv}@ay@a(say)sst@ay@ay)",
|
||
metadata_v,
|
||
parent ? ostree_checksum_to_bytes_v (parent) : g_variant_new_from_data (G_VARIANT_TYPE ("ay"), NULL, 0, FALSE, NULL, NULL),
|
||
g_variant_new_array (G_VARIANT_TYPE ("(say)"), NULL, 0),
|
||
subject, body,
|
||
GUINT64_TO_BE (timestamp),
|
||
ostree_checksum_to_bytes_v ("0000000000000000000000000000000000000000000000000000000000000000"),
|
||
ostree_checksum_to_bytes_v ("0000000000000000000000000000000000000000000000000000000000000000")));
|
||
}
|
||
|
||
static GVariant *
|
||
flatpak_remote_state_fetch_commit_object (FlatpakRemoteState *self,
|
||
FlatpakDir *dir,
|
||
const char *ref,
|
||
const char *checksum,
|
||
const char *token,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autofree char *base_url = NULL;
|
||
g_autofree char *object_url = NULL;
|
||
g_autofree char *part1 = NULL;
|
||
g_autofree char *part2 = NULL;
|
||
g_autoptr(GBytes) bytes = NULL;
|
||
g_autoptr(GVariant) commit_data = NULL;
|
||
g_autoptr(GVariant) commit_metadata = NULL;
|
||
|
||
if (!ostree_repo_remote_get_url (dir->repo, self->remote_name, &base_url, error))
|
||
return NULL;
|
||
|
||
ensure_http_session (dir);
|
||
|
||
part1 = g_strndup (checksum, 2);
|
||
part2 = g_strdup_printf ("%s.commit", checksum + 2);
|
||
|
||
object_url = g_build_filename (base_url, "objects", part1, part2, NULL);
|
||
|
||
bytes = flatpak_load_uri (dir->http_session, object_url, 0, token,
|
||
NULL, NULL, NULL,
|
||
cancellable, error);
|
||
if (bytes == NULL)
|
||
return NULL;
|
||
|
||
commit_data = g_variant_ref_sink (g_variant_new_from_bytes (OSTREE_COMMIT_GVARIANT_FORMAT,
|
||
bytes, FALSE));
|
||
|
||
/* We downloaded this without validating the signature, so we do some basic verification
|
||
of it. However, the signature will be checked when the download is done, and the final
|
||
metadata is compared to what we got here, so its pretty ok to use it for resolving
|
||
the transaction op. However, we do some basic checks. */
|
||
if (!ostree_validate_structureof_commit (commit_data, error))
|
||
return NULL;
|
||
|
||
commit_metadata = g_variant_get_child_value (commit_data, 0);
|
||
if (ref != NULL)
|
||
{
|
||
const char *xa_ref = NULL;
|
||
const char *collection_binding = NULL;
|
||
g_autofree const char **commit_refs = NULL;
|
||
|
||
if ((g_variant_lookup (commit_metadata, "xa.ref", "&s", &xa_ref) &&
|
||
g_strcmp0 (xa_ref, ref) != 0) ||
|
||
(g_variant_lookup (commit_metadata, OSTREE_COMMIT_META_KEY_REF_BINDING, "^a&s", &commit_refs) &&
|
||
!g_strv_contains ((const char * const *) commit_refs, ref)))
|
||
{
|
||
flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Commit has no requested ref ‘%s’ in ref binding metadata"), ref);
|
||
return NULL;
|
||
}
|
||
|
||
/* Check that the locally configured collection ID is correct by looking
|
||
* for it in the commit metadata */
|
||
if (self->collection_id != NULL &&
|
||
(!g_variant_lookup (commit_metadata, OSTREE_COMMIT_META_KEY_COLLECTION_BINDING, "&s", &collection_binding) ||
|
||
g_strcmp0 (self->collection_id, collection_binding) != 0))
|
||
{
|
||
g_autoptr(GVariantIter) collection_refs_iter = NULL;
|
||
gboolean found_in_collection_refs_binding = FALSE;
|
||
/* Note: the OSTREE_COMMIT_META_... define for this is not yet merged
|
||
* in https://github.com/ostreedev/ostree/pull/1805 */
|
||
if (g_variant_lookup (commit_metadata, "ostree.collection-refs-binding", "a(ss)", &collection_refs_iter))
|
||
{
|
||
const gchar *crb_collection_id, *crb_ref_name;
|
||
while (g_variant_iter_loop (collection_refs_iter, "(&s&s)", &crb_collection_id, &crb_ref_name))
|
||
{
|
||
if (g_strcmp0 (self->collection_id, crb_collection_id) == 0 &&
|
||
g_strcmp0 (ref, crb_ref_name) == 0)
|
||
{
|
||
found_in_collection_refs_binding = TRUE;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!found_in_collection_refs_binding)
|
||
{
|
||
flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA,
|
||
_("Configured collection ID ‘%s’ not in binding metadata"),
|
||
self->collection_id);
|
||
return NULL;
|
||
}
|
||
}
|
||
}
|
||
|
||
return g_steal_pointer (&commit_data);
|
||
}
|
||
|
||
|
||
/* Tries to load the specified commit object that we resolved from
|
||
this remote. This either comes from the already available local
|
||
repo, or from one of the sideloading repos, and if not available we
|
||
download it from the actual remote. */
|
||
GVariant *
|
||
flatpak_remote_state_load_ref_commit (FlatpakRemoteState *self,
|
||
FlatpakDir *dir,
|
||
const char *ref,
|
||
const char *opt_commit,
|
||
const char *token,
|
||
char **out_commit,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GVariant) commit_data = NULL;
|
||
g_autofree char *commit = NULL;
|
||
|
||
if (opt_commit == NULL)
|
||
{
|
||
if (!flatpak_remote_state_lookup_ref (self, ref, &commit, NULL, NULL, NULL, error))
|
||
return NULL;
|
||
|
||
if (commit == NULL)
|
||
{
|
||
flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND,
|
||
_("Couldn't find latest checksum for ref %s in remote %s"),
|
||
ref, self->remote_name);
|
||
return NULL;
|
||
}
|
||
}
|
||
else
|
||
commit = g_strdup (opt_commit);
|
||
|
||
/* First try local availability */
|
||
if (ostree_repo_load_commit (dir->repo, commit, &commit_data, NULL, NULL))
|
||
goto out;
|
||
|
||
for (int i = 0; i < self->sideload_repos->len; i++)
|
||
{
|
||
FlatpakSideloadState *ss = g_ptr_array_index (self->sideload_repos, i);
|
||
|
||
if (ostree_repo_load_commit (ss->repo, commit, &commit_data, NULL, NULL))
|
||
goto out;
|
||
}
|
||
|
||
if (flatpak_dir_get_remote_oci (dir, self->remote_name))
|
||
commit_data = flatpak_remote_state_fetch_commit_object_oci (self, dir, ref, commit, token,
|
||
cancellable, error);
|
||
else
|
||
commit_data = flatpak_remote_state_fetch_commit_object (self, dir, ref, commit, token,
|
||
cancellable, error);
|
||
|
||
out:
|
||
if (out_commit)
|
||
*out_commit = g_steal_pointer (&commit);
|
||
|
||
return g_steal_pointer (&commit_data);
|
||
}
|
||
|
||
|
||
gboolean
|
||
flatpak_remote_state_lookup_sparse_cache (FlatpakRemoteState *self,
|
||
const char *ref,
|
||
VarMetadataRef *out_metadata,
|
||
GError **error)
|
||
{
|
||
VarSummaryRef summary;
|
||
VarMetadataRef meta;
|
||
VarVariantRef sparse_cache_v;
|
||
guint32 summary_version;
|
||
GVariant *summary_v;
|
||
|
||
if (!flatpak_remote_state_ensure_summary (self, error))
|
||
return FALSE;
|
||
|
||
summary_v = get_summary_for_ref (self, ref);
|
||
if (summary_v == NULL)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND,
|
||
_("No entry for %s in remote %s summary flatpak sparse cache"),
|
||
ref, self->remote_name);
|
||
|
||
summary = var_summary_from_gvariant (summary_v);
|
||
meta = var_summary_get_metadata (summary);
|
||
|
||
summary_version = GUINT32_FROM_LE (var_metadata_lookup_uint32 (meta, "xa.summary-version", 0));
|
||
|
||
if (summary_version == 0)
|
||
{
|
||
if (var_metadata_lookup (meta, "xa.sparse-cache", NULL, &sparse_cache_v))
|
||
{
|
||
VarSparseCacheRef sparse_cache = var_sparse_cache_from_variant (sparse_cache_v);
|
||
if (var_sparse_cache_lookup (sparse_cache, ref, NULL, out_metadata))
|
||
return TRUE;
|
||
}
|
||
}
|
||
else if (summary_version == 1)
|
||
{
|
||
VarRefMapRef ref_map = var_summary_get_ref_map (summary);
|
||
VarRefInfoRef info;
|
||
|
||
if (flatpak_var_ref_map_lookup_ref (ref_map, ref, &info))
|
||
{
|
||
*out_metadata = var_ref_info_get_metadata (info);
|
||
return TRUE;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Unsupported summary version %d for remote %s"),
|
||
summary_version, self->remote_name);
|
||
return FALSE;
|
||
}
|
||
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND,
|
||
_("No entry for %s in remote %s summary flatpak sparse cache"),
|
||
ref, self->remote_name);
|
||
}
|
||
|
||
static DirExtraData *
|
||
dir_extra_data_new (const char *id,
|
||
const char *display_name,
|
||
gint priority,
|
||
FlatpakDirStorageType type)
|
||
{
|
||
DirExtraData *dir_extra_data = g_new0 (DirExtraData, 1);
|
||
|
||
dir_extra_data->id = g_strdup (id);
|
||
dir_extra_data->display_name = g_strdup (display_name);
|
||
dir_extra_data->priority = priority;
|
||
dir_extra_data->storage_type = type;
|
||
|
||
return dir_extra_data;
|
||
}
|
||
|
||
static DirExtraData *
|
||
dir_extra_data_clone (DirExtraData *extra_data)
|
||
{
|
||
if (extra_data != NULL)
|
||
return dir_extra_data_new (extra_data->id,
|
||
extra_data->display_name,
|
||
extra_data->priority,
|
||
extra_data->storage_type);
|
||
return NULL;
|
||
}
|
||
|
||
static void
|
||
dir_extra_data_free (DirExtraData *dir_extra_data)
|
||
{
|
||
g_free (dir_extra_data->id);
|
||
g_free (dir_extra_data->display_name);
|
||
g_free (dir_extra_data);
|
||
}
|
||
|
||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (DirExtraData, dir_extra_data_free);
|
||
|
||
static GVariant *
|
||
variant_new_ay_bytes (GBytes *bytes)
|
||
{
|
||
gsize size;
|
||
gconstpointer data;
|
||
|
||
data = g_bytes_get_data (bytes, &size);
|
||
g_bytes_ref (bytes);
|
||
return g_variant_ref_sink (g_variant_new_from_data (G_VARIANT_TYPE ("ay"), data, size,
|
||
TRUE, (GDestroyNotify) g_bytes_unref, bytes));
|
||
}
|
||
|
||
static void
|
||
flatpak_deploy_finalize (GObject *object)
|
||
{
|
||
FlatpakDeploy *self = FLATPAK_DEPLOY (object);
|
||
|
||
g_clear_pointer (&self->ref, flatpak_decomposed_unref);
|
||
g_clear_object (&self->dir);
|
||
g_clear_pointer (&self->metadata, g_key_file_unref);
|
||
g_clear_pointer (&self->system_overrides, flatpak_context_free);
|
||
g_clear_pointer (&self->user_overrides, flatpak_context_free);
|
||
g_clear_pointer (&self->system_app_overrides, flatpak_context_free);
|
||
g_clear_pointer (&self->user_app_overrides, flatpak_context_free);
|
||
g_clear_object (&self->repo);
|
||
|
||
G_OBJECT_CLASS (flatpak_deploy_parent_class)->finalize (object);
|
||
}
|
||
|
||
static void
|
||
flatpak_deploy_class_init (FlatpakDeployClass *klass)
|
||
{
|
||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||
|
||
object_class->finalize = flatpak_deploy_finalize;
|
||
}
|
||
|
||
static void
|
||
flatpak_deploy_init (FlatpakDeploy *self)
|
||
{
|
||
}
|
||
|
||
GFile *
|
||
flatpak_deploy_get_dir (FlatpakDeploy *deploy)
|
||
{
|
||
return g_object_ref (deploy->dir);
|
||
}
|
||
|
||
GBytes *
|
||
flatpak_load_deploy_data (GFile *deploy_dir,
|
||
FlatpakDecomposed *ref,
|
||
OstreeRepo *repo,
|
||
int required_version,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) data_file = NULL;
|
||
g_autoptr(GBytes) deploy_data = NULL;
|
||
gchar *contents;
|
||
gsize len;
|
||
|
||
data_file = g_file_get_child (deploy_dir, "deploy");
|
||
|
||
if (!g_file_load_contents (data_file, cancellable, &contents, &len, NULL, error))
|
||
return NULL;
|
||
|
||
deploy_data = g_bytes_new_take (contents, len);
|
||
|
||
if (flatpak_deploy_data_get_version (deploy_data) < required_version)
|
||
return upgrade_deploy_data (deploy_data, deploy_dir, ref, repo, cancellable, error);
|
||
|
||
return g_steal_pointer (&deploy_data);
|
||
}
|
||
|
||
|
||
GBytes *
|
||
flatpak_deploy_get_deploy_data (FlatpakDeploy *deploy,
|
||
int required_version,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
return flatpak_load_deploy_data (deploy->dir,
|
||
deploy->ref,
|
||
deploy->repo,
|
||
required_version,
|
||
cancellable,
|
||
error);
|
||
}
|
||
|
||
GFile *
|
||
flatpak_deploy_get_files (FlatpakDeploy *deploy)
|
||
{
|
||
return g_file_get_child (deploy->dir, "files");
|
||
}
|
||
|
||
FlatpakContext *
|
||
flatpak_deploy_get_overrides (FlatpakDeploy *deploy)
|
||
{
|
||
FlatpakContext *overrides = flatpak_context_new ();
|
||
|
||
if (deploy->system_overrides)
|
||
{
|
||
flatpak_context_dump (deploy->system_overrides,
|
||
"System-wide overrides for all apps");
|
||
flatpak_context_merge (overrides, deploy->system_overrides);
|
||
}
|
||
else
|
||
{
|
||
g_debug ("No system-wide overrides for all apps");
|
||
}
|
||
|
||
if (deploy->system_app_overrides)
|
||
{
|
||
flatpak_context_dump (deploy->system_app_overrides,
|
||
"System-wide overrides for specified app");
|
||
flatpak_context_merge (overrides, deploy->system_app_overrides);
|
||
}
|
||
else
|
||
{
|
||
g_debug ("No system-wide per-app overrides for specified app");
|
||
}
|
||
|
||
if (deploy->user_overrides)
|
||
{
|
||
flatpak_context_dump (deploy->user_overrides,
|
||
"Per-user overrides for all apps");
|
||
flatpak_context_merge (overrides, deploy->user_overrides);
|
||
}
|
||
else
|
||
{
|
||
g_debug ("No per-user overrides for all apps");
|
||
}
|
||
|
||
if (deploy->user_app_overrides)
|
||
{
|
||
flatpak_context_dump (deploy->user_app_overrides,
|
||
"Per-user overrides for specified app");
|
||
flatpak_context_merge (overrides, deploy->user_app_overrides);
|
||
}
|
||
else
|
||
{
|
||
g_debug ("No per-user overrides for specified app");
|
||
}
|
||
|
||
return overrides;
|
||
}
|
||
|
||
GKeyFile *
|
||
flatpak_deploy_get_metadata (FlatpakDeploy *deploy)
|
||
{
|
||
return g_key_file_ref (deploy->metadata);
|
||
}
|
||
|
||
static FlatpakDeploy *
|
||
flatpak_deploy_new (GFile *dir,
|
||
FlatpakDecomposed *ref,
|
||
GKeyFile *metadata,
|
||
OstreeRepo *repo)
|
||
{
|
||
FlatpakDeploy *deploy;
|
||
|
||
deploy = g_object_new (FLATPAK_TYPE_DEPLOY, NULL);
|
||
deploy->ref = flatpak_decomposed_ref (ref);
|
||
deploy->dir = g_object_ref (dir);
|
||
deploy->metadata = g_key_file_ref (metadata);
|
||
deploy->repo = g_object_ref (repo);
|
||
|
||
return deploy;
|
||
}
|
||
|
||
GFile *
|
||
flatpak_get_system_default_base_dir_location (void)
|
||
{
|
||
static gsize file = 0;
|
||
|
||
if (g_once_init_enter (&file))
|
||
{
|
||
gsize setup_value = 0;
|
||
const char *path;
|
||
const char *system_dir = g_getenv ("FLATPAK_SYSTEM_DIR");
|
||
if (system_dir != NULL && *system_dir != 0)
|
||
path = system_dir;
|
||
else
|
||
path = FLATPAK_SYSTEMDIR;
|
||
|
||
setup_value = (gsize) g_file_new_for_path (path);
|
||
|
||
g_once_init_leave (&file, setup_value);
|
||
}
|
||
|
||
return g_object_ref ((GFile *) file);
|
||
}
|
||
|
||
static FlatpakDirStorageType
|
||
parse_storage_type (const char *type_string)
|
||
{
|
||
if (type_string != NULL)
|
||
{
|
||
g_autofree char *type_low = NULL;
|
||
|
||
type_low = g_ascii_strdown (type_string, -1);
|
||
if (g_strcmp0 (type_low, "network") == 0)
|
||
return FLATPAK_DIR_STORAGE_TYPE_NETWORK;
|
||
|
||
if (g_strcmp0 (type_low, "mmc") == 0)
|
||
return FLATPAK_DIR_STORAGE_TYPE_MMC;
|
||
|
||
if (g_strcmp0 (type_low, "sdcard") == 0)
|
||
return FLATPAK_DIR_STORAGE_TYPE_SDCARD;
|
||
|
||
if (g_strcmp0 (type_low, "hardisk") == 0)
|
||
return FLATPAK_DIR_STORAGE_TYPE_HARD_DISK;
|
||
}
|
||
|
||
return FLATPAK_DIR_STORAGE_TYPE_DEFAULT;
|
||
}
|
||
|
||
static gboolean
|
||
has_system_location (GPtrArray *locations,
|
||
const char *id)
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < locations->len; i++)
|
||
{
|
||
GFile *path = g_ptr_array_index (locations, i);
|
||
DirExtraData *extra_data = g_object_get_data (G_OBJECT (path), "extra-data");
|
||
if (extra_data != NULL && g_strcmp0 (extra_data->id, id) == 0)
|
||
return TRUE;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static void
|
||
append_new_system_location (GPtrArray *locations,
|
||
GFile *location,
|
||
const char *id,
|
||
const char *display_name,
|
||
FlatpakDirStorageType storage_type,
|
||
gint priority)
|
||
{
|
||
DirExtraData *extra_data = NULL;
|
||
|
||
extra_data = dir_extra_data_new (id, display_name, priority, storage_type);
|
||
g_object_set_data_full (G_OBJECT (location), "extra-data", extra_data,
|
||
(GDestroyNotify) dir_extra_data_free);
|
||
|
||
g_ptr_array_add (locations, location);
|
||
}
|
||
|
||
static gboolean
|
||
is_good_installation_id (const char *id)
|
||
{
|
||
if (strcmp (id, "") == 0 ||
|
||
strcmp (id, "user") == 0 ||
|
||
strcmp (id, SYSTEM_DIR_DEFAULT_ID) == 0 ||
|
||
strcmp (id, "system") == 0)
|
||
return FALSE;
|
||
|
||
if (!g_str_is_ascii (id) ||
|
||
strpbrk (id, " /\n"))
|
||
return FALSE;
|
||
|
||
if (strlen (id) > 80)
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
append_locations_from_config_file (GPtrArray *locations,
|
||
const char *file_path,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GKeyFile) keyfile = NULL;
|
||
g_auto(GStrv) groups = NULL;
|
||
g_autoptr(GError) my_error = NULL;
|
||
gboolean ret = FALSE;
|
||
gsize n_groups;
|
||
int i;
|
||
|
||
keyfile = g_key_file_new ();
|
||
|
||
if (!g_key_file_load_from_file (keyfile, file_path, G_KEY_FILE_NONE, &my_error))
|
||
{
|
||
g_info ("Could not get list of system installations from '%s': %s", file_path, my_error->message);
|
||
g_propagate_error (error, g_steal_pointer (&my_error));
|
||
goto out;
|
||
}
|
||
|
||
/* One configuration file might define more than one installation */
|
||
groups = g_key_file_get_groups (keyfile, &n_groups);
|
||
for (i = 0; i < n_groups; i++)
|
||
{
|
||
g_autofree char *id = NULL;
|
||
g_autofree char *path = NULL;
|
||
size_t len;
|
||
|
||
if (!g_str_has_prefix (groups[i], "Installation \""))
|
||
{
|
||
if (g_str_has_prefix (groups[i], "Installation "))
|
||
g_warning ("Installation without quotes (%s). Ignoring", groups[i]);
|
||
continue;
|
||
}
|
||
|
||
id = g_strdup (&groups[i][14]);
|
||
if (!g_str_has_suffix (id, "\""))
|
||
{
|
||
g_warning ("While reading '%s': Installation without closing quote (%s). Ignoring", file_path, groups[i]);
|
||
continue;
|
||
}
|
||
|
||
len = strlen (id);
|
||
if (len > 0)
|
||
id[len - 1] = '\0';
|
||
|
||
if (!is_good_installation_id (id))
|
||
{
|
||
g_warning ("While reading '%s': Bad installation ID '%s'. Ignoring", file_path, id);
|
||
continue;
|
||
}
|
||
|
||
if (has_system_location (locations, id))
|
||
{
|
||
g_warning ("While reading '%s': Duplicate installation ID '%s'. Ignoring", file_path, id);
|
||
continue;
|
||
}
|
||
|
||
path = g_key_file_get_string (keyfile, groups[i], "Path", &my_error);
|
||
if (path == NULL)
|
||
{
|
||
g_info ("While reading '%s': Unable to get path for installation '%s': %s", file_path, id, my_error->message);
|
||
g_propagate_error (error, g_steal_pointer (&my_error));
|
||
goto out;
|
||
}
|
||
else
|
||
{
|
||
GFile *location = NULL;
|
||
g_autofree char *display_name = NULL;
|
||
g_autofree char *priority = NULL;
|
||
g_autofree char *storage_type = NULL;
|
||
gint priority_val = 0;
|
||
|
||
display_name = g_key_file_get_string (keyfile, groups[i], "DisplayName", NULL);
|
||
priority = g_key_file_get_string (keyfile, groups[i], "Priority", NULL);
|
||
storage_type = g_key_file_get_string (keyfile, groups[i], "StorageType", NULL);
|
||
|
||
if (priority != NULL)
|
||
priority_val = g_ascii_strtoll (priority, NULL, 10);
|
||
|
||
location = g_file_new_for_path (path);
|
||
append_new_system_location (locations, location, id, display_name,
|
||
parse_storage_type (storage_type),
|
||
priority_val);
|
||
}
|
||
}
|
||
|
||
ret = TRUE;
|
||
|
||
out:
|
||
return ret;
|
||
}
|
||
|
||
static gint
|
||
system_locations_compare_func (gconstpointer location_a, gconstpointer location_b)
|
||
{
|
||
const GFile *location_object_a = *(const GFile **) location_a;
|
||
const GFile *location_object_b = *(const GFile **) location_b;
|
||
DirExtraData *extra_data_a = NULL;
|
||
DirExtraData *extra_data_b = NULL;
|
||
gint prio_a = 0;
|
||
gint prio_b = 0;
|
||
|
||
extra_data_a = g_object_get_data (G_OBJECT (location_object_a), "extra-data");
|
||
prio_a = (extra_data_a != NULL) ? extra_data_a->priority : 0;
|
||
|
||
extra_data_b = g_object_get_data (G_OBJECT (location_object_b), "extra-data");
|
||
prio_b = (extra_data_b != NULL) ? extra_data_b->priority : 0;
|
||
|
||
return prio_b - prio_a;
|
||
}
|
||
|
||
static GPtrArray *
|
||
system_locations_from_configuration (GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GPtrArray) locations = NULL;
|
||
g_autoptr(GFile) conf_dir = NULL;
|
||
g_autoptr(GFileEnumerator) dir_enum = NULL;
|
||
g_autoptr(GError) my_error = NULL;
|
||
g_autofree char *config_dir = NULL;
|
||
|
||
locations = g_ptr_array_new_with_free_func (g_object_unref);
|
||
config_dir = g_strdup_printf ("%s/%s",
|
||
get_config_dir_location (),
|
||
SYSCONF_INSTALLATIONS_DIR);
|
||
|
||
if (!g_file_test (config_dir, G_FILE_TEST_IS_DIR))
|
||
{
|
||
g_info ("No installations directory in %s. Skipping", config_dir);
|
||
goto out;
|
||
}
|
||
|
||
conf_dir = g_file_new_for_path (config_dir);
|
||
dir_enum = g_file_enumerate_children (conf_dir,
|
||
G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE,
|
||
G_FILE_QUERY_INFO_NONE,
|
||
cancellable, &my_error);
|
||
if (my_error != NULL)
|
||
{
|
||
g_info ("Unexpected error retrieving extra installations in %s: %s",
|
||
config_dir, my_error->message);
|
||
g_propagate_error (error, g_steal_pointer (&my_error));
|
||
goto out;
|
||
}
|
||
|
||
while (TRUE)
|
||
{
|
||
GFileInfo *file_info;
|
||
GFile *path;
|
||
const char *name;
|
||
guint32 type;
|
||
|
||
if (!g_file_enumerator_iterate (dir_enum, &file_info, &path,
|
||
cancellable, &my_error))
|
||
{
|
||
g_info ("Unexpected error reading file in %s: %s",
|
||
config_dir, my_error->message);
|
||
g_propagate_error (error, g_steal_pointer (&my_error));
|
||
goto out;
|
||
}
|
||
|
||
if (file_info == NULL)
|
||
break;
|
||
|
||
name = g_file_info_get_attribute_byte_string (file_info, "standard::name");
|
||
type = g_file_info_get_attribute_uint32 (file_info, "standard::type");
|
||
|
||
if (type == G_FILE_TYPE_REGULAR && g_str_has_suffix (name, SYSCONF_INSTALLATIONS_FILE_EXT))
|
||
{
|
||
g_autofree char *path_str = g_file_get_path (path);
|
||
if (!append_locations_from_config_file (locations, path_str, cancellable, error))
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
out:
|
||
return g_steal_pointer (&locations);
|
||
}
|
||
|
||
static GPtrArray *
|
||
get_system_locations (GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GPtrArray) locations = NULL;
|
||
|
||
/* This will always return a GPtrArray, being an empty one
|
||
* if no additional system installations have been configured.
|
||
*/
|
||
locations = system_locations_from_configuration (cancellable, error);
|
||
|
||
/* Only fill the details of the default directory if not overridden. */
|
||
if (!has_system_location (locations, SYSTEM_DIR_DEFAULT_ID))
|
||
{
|
||
append_new_system_location (locations,
|
||
flatpak_get_system_default_base_dir_location (),
|
||
SYSTEM_DIR_DEFAULT_ID,
|
||
SYSTEM_DIR_DEFAULT_DISPLAY_NAME,
|
||
SYSTEM_DIR_DEFAULT_STORAGE_TYPE,
|
||
SYSTEM_DIR_DEFAULT_PRIORITY);
|
||
}
|
||
|
||
/* Store the list of system locations sorted according to priorities */
|
||
g_ptr_array_sort (locations, system_locations_compare_func);
|
||
|
||
return g_steal_pointer (&locations);
|
||
}
|
||
|
||
GPtrArray *
|
||
flatpak_get_system_base_dir_locations (GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
static gsize initialized = 0;
|
||
static GPtrArray *array = NULL;
|
||
|
||
if (g_once_init_enter (&initialized))
|
||
{
|
||
array = get_system_locations (cancellable, error);
|
||
g_once_init_leave (&initialized, 1);
|
||
}
|
||
|
||
return array;
|
||
}
|
||
|
||
GFile *
|
||
flatpak_get_user_base_dir_location (void)
|
||
{
|
||
static gsize file = 0;
|
||
|
||
if (g_once_init_enter (&file))
|
||
{
|
||
gsize setup_value = 0;
|
||
const char *path;
|
||
g_autofree char *free_me = NULL;
|
||
const char *user_dir = g_getenv ("FLATPAK_USER_DIR");
|
||
if (user_dir != NULL && *user_dir != 0)
|
||
path = user_dir;
|
||
else
|
||
path = free_me = g_build_filename (g_get_user_data_dir (), "flatpak", NULL);
|
||
|
||
setup_value = (gsize) g_file_new_for_path (path);
|
||
|
||
g_once_init_leave (&file, setup_value);
|
||
}
|
||
|
||
return g_object_ref ((GFile *) file);
|
||
}
|
||
|
||
static gboolean
|
||
validate_commit_metadata (GVariant *commit_data,
|
||
const char *ref,
|
||
const char *required_metadata,
|
||
gsize required_metadata_size,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GVariant) commit_metadata = NULL;
|
||
g_autoptr(GVariant) xa_metadata_v = NULL;
|
||
const char *xa_metadata = NULL;
|
||
gsize xa_metadata_size = 0;
|
||
|
||
commit_metadata = g_variant_get_child_value (commit_data, 0);
|
||
|
||
if (commit_metadata != NULL)
|
||
{
|
||
xa_metadata_v = g_variant_lookup_value (commit_metadata,
|
||
"xa.metadata",
|
||
G_VARIANT_TYPE_STRING);
|
||
if (xa_metadata_v)
|
||
xa_metadata = g_variant_get_string (xa_metadata_v, &xa_metadata_size);
|
||
}
|
||
|
||
if (xa_metadata == NULL ||
|
||
xa_metadata_size != required_metadata_size ||
|
||
memcmp (xa_metadata, required_metadata, xa_metadata_size) != 0)
|
||
{
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
|
||
_("Commit metadata for %s not matching expected metadata"), ref);
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* This is a cache directory similar to ~/.cache/flatpak/system-cache,
|
||
* but in /var/tmp. This is useful for things like the system child
|
||
* repos, because it is more likely to be on the same filesystem as
|
||
* the system repo (thus increasing chances for e.g. reflink copying),
|
||
* and avoids filling the users homedirectory with temporary data.
|
||
*
|
||
* In order to re-use this between instances we create a symlink
|
||
* in /run to it and verify it before use.
|
||
*/
|
||
static GFile *
|
||
flatpak_ensure_system_user_cache_dir_location (GError **error)
|
||
{
|
||
g_autofree char *path = NULL;
|
||
g_autofree char *symlink_path = NULL;
|
||
struct stat st_buf;
|
||
const char *custom_path = g_getenv ("FLATPAK_SYSTEM_CACHE_DIR");
|
||
|
||
if (custom_path != NULL && *custom_path != 0)
|
||
{
|
||
if (g_mkdir_with_parents (custom_path, 0755) != 0)
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
return NULL;
|
||
}
|
||
|
||
return g_file_new_for_path (custom_path);
|
||
}
|
||
|
||
symlink_path = g_build_filename (g_get_user_runtime_dir (), ".flatpak-cache", NULL);
|
||
path = flatpak_readlink (symlink_path, NULL);
|
||
|
||
if (stat (path, &st_buf) == 0 &&
|
||
/* Must be owned by us */
|
||
st_buf.st_uid == getuid () &&
|
||
/* and not writeable by others, but readable */
|
||
(st_buf.st_mode & 0777) == 0755)
|
||
return g_file_new_for_path (path);
|
||
|
||
g_clear_pointer (&path, g_free);
|
||
|
||
path = g_strdup ("/var/tmp/flatpak-cache-XXXXXX");
|
||
|
||
if (g_mkdtemp_full (path, 0755) == NULL)
|
||
{
|
||
flatpak_fail (error, "Can't create temporary directory");
|
||
return NULL;
|
||
}
|
||
|
||
unlink (symlink_path);
|
||
if (symlink (path, symlink_path) != 0)
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
return NULL;
|
||
}
|
||
|
||
return g_file_new_for_path (path);
|
||
}
|
||
|
||
static GFile *
|
||
flatpak_get_user_cache_dir_location (void)
|
||
{
|
||
g_autoptr(GFile) base_dir = g_file_new_for_path (g_get_user_cache_dir ());
|
||
|
||
return g_file_resolve_relative_path (base_dir, "flatpak/system-cache");
|
||
}
|
||
|
||
static GFile *
|
||
flatpak_ensure_user_cache_dir_location (GError **error)
|
||
{
|
||
g_autoptr(GFile) cache_dir = NULL;
|
||
g_autofree char *cache_path = NULL;
|
||
|
||
cache_dir = flatpak_get_user_cache_dir_location ();
|
||
cache_path = g_file_get_path (cache_dir);
|
||
|
||
if (g_mkdir_with_parents (cache_path, 0755) != 0)
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
return NULL;
|
||
}
|
||
|
||
return g_steal_pointer (&cache_dir);
|
||
}
|
||
|
||
static GFile *
|
||
flatpak_dir_get_oci_cache_file (FlatpakDir *self,
|
||
const char *remote,
|
||
const char *suffix,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) oci_dir = NULL;
|
||
g_autofree char *filename = NULL;
|
||
|
||
oci_dir = g_file_get_child (flatpak_dir_get_path (self), "oci");
|
||
if (g_mkdir_with_parents (flatpak_file_get_path_cached (oci_dir), 0755) != 0)
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
return NULL;
|
||
}
|
||
|
||
filename = g_strconcat (remote, suffix, NULL);
|
||
return g_file_get_child (oci_dir, filename);
|
||
}
|
||
|
||
static GFile *
|
||
flatpak_dir_get_oci_index_location (FlatpakDir *self,
|
||
const char *remote,
|
||
GError **error)
|
||
{
|
||
return flatpak_dir_get_oci_cache_file (self, remote, ".index.gz", error);
|
||
}
|
||
|
||
static GFile *
|
||
flatpak_dir_get_oci_summary_location (FlatpakDir *self,
|
||
const char *remote,
|
||
GError **error)
|
||
{
|
||
return flatpak_dir_get_oci_cache_file (self, remote, ".summary", error);
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_remove_oci_file (FlatpakDir *self,
|
||
const char *remote,
|
||
const char *suffix,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) file = flatpak_dir_get_oci_cache_file (self, remote, suffix, error);
|
||
g_autoptr(GError) local_error = NULL;
|
||
|
||
if (file == NULL)
|
||
return FALSE;
|
||
|
||
if (!g_file_delete (file, cancellable, &local_error) &&
|
||
!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_remove_oci_files (FlatpakDir *self,
|
||
const char *remote,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
if (!flatpak_dir_remove_oci_file (self, remote, ".index.gz", cancellable, error) ||
|
||
!flatpak_dir_remove_oci_file (self, remote, ".summary", cancellable, error))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gchar *
|
||
flatpak_dir_revokefs_fuse_create_mountpoint (FlatpakDecomposed *ref,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) cache_dir = NULL;
|
||
g_autofree gchar *cache_dir_path = NULL;
|
||
g_autofree gchar *mnt_dir = NULL;
|
||
g_autofree gchar *id = NULL;
|
||
g_autofree gchar *mountpoint = NULL;
|
||
|
||
cache_dir = flatpak_ensure_system_user_cache_dir_location (error);
|
||
if (cache_dir == NULL)
|
||
return NULL;
|
||
|
||
id = flatpak_decomposed_dup_id (ref);
|
||
cache_dir_path = g_file_get_path (cache_dir);
|
||
mnt_dir = g_strdup_printf ("%s-XXXXXX", id);
|
||
mountpoint = g_mkdtemp_full (g_build_filename (cache_dir_path, mnt_dir, NULL), 0755);
|
||
if (mountpoint == NULL)
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
return NULL;
|
||
}
|
||
|
||
return g_steal_pointer (&mountpoint);
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_revokefs_fuse_unmount (OstreeRepo **repo,
|
||
GLnxLockFile *lockfile,
|
||
const gchar *mnt_dir,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GSubprocess) fusermount = NULL;
|
||
|
||
/* Clear references to child_repo as not to leave any open fds. This is needed for
|
||
* a clean umount operation.
|
||
*/
|
||
g_clear_pointer (repo, g_object_unref);
|
||
glnx_release_lock_file (lockfile);
|
||
|
||
fusermount = g_subprocess_new (G_SUBPROCESS_FLAGS_NONE,
|
||
error,
|
||
FUSERMOUNT, "-u", "-z", mnt_dir, NULL);
|
||
if (g_subprocess_wait_check (fusermount, NULL, error))
|
||
{
|
||
g_autoptr(GFile) mnt_dir_file = g_file_new_for_path (mnt_dir);
|
||
g_autoptr(GError) tmp_error = NULL;
|
||
|
||
if (!flatpak_rm_rf (mnt_dir_file, NULL, &tmp_error))
|
||
g_warning ("Unable to remove mountpoint directory %s: %s", mnt_dir, tmp_error->message);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_use_system_helper (FlatpakDir *self,
|
||
const char *installing_from_remote)
|
||
{
|
||
#ifdef USE_SYSTEM_HELPER
|
||
if (self->no_system_helper || self->user || getuid () == 0)
|
||
return FALSE;
|
||
|
||
/* OCI doesn't do signatures atm, so we can't use the system helper for this */
|
||
if (installing_from_remote != NULL && flatpak_dir_get_remote_oci (self, installing_from_remote))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
#else
|
||
return FALSE;
|
||
#endif
|
||
}
|
||
|
||
static GVariant *
|
||
flatpak_dir_system_helper_call (FlatpakDir *self,
|
||
const gchar *method_name,
|
||
GVariant *parameters,
|
||
const GVariantType *reply_type,
|
||
GUnixFDList **out_fd_list,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
GVariant *res;
|
||
|
||
if (g_once_init_enter (&self->system_helper_bus))
|
||
{
|
||
const char *on_session = g_getenv ("FLATPAK_SYSTEM_HELPER_ON_SESSION");
|
||
GDBusConnection *system_helper_bus =
|
||
g_bus_get_sync (on_session != NULL ? G_BUS_TYPE_SESSION : G_BUS_TYPE_SYSTEM,
|
||
cancellable, NULL);
|
||
|
||
/* To ensure reverse mapping */
|
||
flatpak_error_quark ();
|
||
|
||
g_once_init_leave (&self->system_helper_bus, system_helper_bus ? system_helper_bus : (gpointer) 1 );
|
||
}
|
||
|
||
if (self->system_helper_bus == (gpointer) 1)
|
||
{
|
||
flatpak_fail (error, _("Unable to connect to system bus"));
|
||
return NULL;
|
||
}
|
||
|
||
g_info ("Calling system helper: %s", method_name);
|
||
res = g_dbus_connection_call_with_unix_fd_list_sync (self->system_helper_bus,
|
||
FLATPAK_SYSTEM_HELPER_BUS_NAME,
|
||
FLATPAK_SYSTEM_HELPER_PATH,
|
||
FLATPAK_SYSTEM_HELPER_INTERFACE,
|
||
method_name,
|
||
parameters,
|
||
reply_type,
|
||
G_DBUS_CALL_FLAGS_NONE, G_MAXINT,
|
||
NULL, out_fd_list,
|
||
cancellable,
|
||
error);
|
||
|
||
if (res == NULL && error)
|
||
g_dbus_error_strip_remote_error (*error);
|
||
|
||
return res;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_system_helper_call_deploy (FlatpakDir *self,
|
||
const gchar *arg_repo_path,
|
||
guint arg_flags,
|
||
const gchar *arg_ref,
|
||
const gchar *arg_origin,
|
||
const gchar *const *arg_subpaths,
|
||
const gchar *const *arg_previous_ids,
|
||
const gchar *arg_installation,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
const char *empty[] = { NULL };
|
||
|
||
if (arg_subpaths == NULL)
|
||
arg_subpaths = empty;
|
||
if (arg_previous_ids == NULL)
|
||
arg_previous_ids = empty;
|
||
|
||
if (flatpak_dir_get_no_interaction (self))
|
||
arg_flags |= FLATPAK_HELPER_DEPLOY_FLAGS_NO_INTERACTION;
|
||
|
||
g_autoptr(GVariant) ret =
|
||
flatpak_dir_system_helper_call (self, "Deploy",
|
||
g_variant_new ("(^ayuss^as^ass)",
|
||
arg_repo_path,
|
||
arg_flags,
|
||
arg_ref,
|
||
arg_origin,
|
||
arg_subpaths,
|
||
arg_previous_ids,
|
||
arg_installation),
|
||
G_VARIANT_TYPE ("()"), NULL,
|
||
cancellable, error);
|
||
return ret != NULL;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_system_helper_call_deploy_appstream (FlatpakDir *self,
|
||
const gchar *arg_repo_path,
|
||
guint arg_flags,
|
||
const gchar *arg_origin,
|
||
const gchar *arg_arch,
|
||
const gchar *arg_installation,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
if (flatpak_dir_get_no_interaction (self))
|
||
arg_flags |= FLATPAK_HELPER_DEPLOY_APPSTREAM_FLAGS_NO_INTERACTION;
|
||
|
||
g_autoptr(GVariant) ret =
|
||
flatpak_dir_system_helper_call (self, "DeployAppstream",
|
||
g_variant_new ("(^ayusss)",
|
||
arg_repo_path,
|
||
arg_flags,
|
||
arg_origin,
|
||
arg_arch,
|
||
arg_installation),
|
||
G_VARIANT_TYPE ("()"), NULL,
|
||
cancellable, error);
|
||
return ret != NULL;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_system_helper_call_uninstall (FlatpakDir *self,
|
||
guint arg_flags,
|
||
const gchar *arg_ref,
|
||
const gchar *arg_installation,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
if (flatpak_dir_get_no_interaction (self))
|
||
arg_flags |= FLATPAK_HELPER_UNINSTALL_FLAGS_NO_INTERACTION;
|
||
|
||
g_autoptr(GVariant) ret =
|
||
flatpak_dir_system_helper_call (self, "Uninstall",
|
||
g_variant_new ("(uss)",
|
||
arg_flags,
|
||
arg_ref,
|
||
arg_installation),
|
||
G_VARIANT_TYPE ("()"), NULL,
|
||
cancellable, error);
|
||
return ret != NULL;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_system_helper_call_install_bundle (FlatpakDir *self,
|
||
const gchar *arg_bundle_path,
|
||
guint arg_flags,
|
||
const gchar *arg_remote,
|
||
const gchar *arg_installation,
|
||
gchar **out_ref,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
if (flatpak_dir_get_no_interaction (self))
|
||
arg_flags |= FLATPAK_HELPER_INSTALL_BUNDLE_FLAGS_NO_INTERACTION;
|
||
|
||
g_autoptr(GVariant) ret =
|
||
flatpak_dir_system_helper_call (self, "InstallBundle",
|
||
g_variant_new ("(^ayuss)",
|
||
arg_bundle_path,
|
||
arg_flags,
|
||
arg_remote,
|
||
arg_installation),
|
||
G_VARIANT_TYPE ("(s)"), NULL,
|
||
cancellable, error);
|
||
if (ret == NULL)
|
||
return FALSE;
|
||
|
||
g_variant_get (ret, "(s)", out_ref);
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_system_helper_call_configure_remote (FlatpakDir *self,
|
||
guint arg_flags,
|
||
const gchar *arg_remote,
|
||
const gchar *arg_config,
|
||
GVariant *arg_gpg_key,
|
||
const gchar *arg_installation,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
if (flatpak_dir_get_no_interaction (self))
|
||
arg_flags |= FLATPAK_HELPER_CONFIGURE_REMOTE_FLAGS_NO_INTERACTION;
|
||
|
||
g_autoptr(GVariant) ret =
|
||
flatpak_dir_system_helper_call (self, "ConfigureRemote",
|
||
g_variant_new ("(uss@ays)",
|
||
arg_flags,
|
||
arg_remote,
|
||
arg_config,
|
||
arg_gpg_key,
|
||
arg_installation),
|
||
G_VARIANT_TYPE ("()"), NULL,
|
||
cancellable,
|
||
error);
|
||
return ret != NULL;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_system_helper_call_configure (FlatpakDir *self,
|
||
guint arg_flags,
|
||
const gchar *arg_key,
|
||
const gchar *arg_value,
|
||
const gchar *arg_installation,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
if (flatpak_dir_get_no_interaction (self))
|
||
arg_flags |= FLATPAK_HELPER_CONFIGURE_FLAGS_NO_INTERACTION;
|
||
|
||
g_autoptr(GVariant) ret =
|
||
flatpak_dir_system_helper_call (self, "Configure",
|
||
g_variant_new ("(usss)",
|
||
arg_flags,
|
||
arg_key,
|
||
arg_value,
|
||
arg_installation),
|
||
G_VARIANT_TYPE ("()"), NULL,
|
||
cancellable, error);
|
||
return ret != NULL;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_system_helper_call_update_remote (FlatpakDir *self,
|
||
guint arg_flags,
|
||
const gchar *arg_remote,
|
||
const gchar *arg_installation,
|
||
const gchar *arg_summary_path,
|
||
const gchar *arg_summary_sig_path,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
if (flatpak_dir_get_no_interaction (self))
|
||
arg_flags |= FLATPAK_HELPER_UPDATE_REMOTE_FLAGS_NO_INTERACTION;
|
||
|
||
g_autoptr(GVariant) ret =
|
||
flatpak_dir_system_helper_call (self, "UpdateRemote",
|
||
g_variant_new ("(uss^ay^ay)",
|
||
arg_flags,
|
||
arg_remote,
|
||
arg_installation,
|
||
arg_summary_path,
|
||
arg_summary_sig_path),
|
||
G_VARIANT_TYPE ("()"), NULL,
|
||
cancellable, error);
|
||
return ret != NULL;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_system_helper_call_remove_local_ref (FlatpakDir *self,
|
||
guint arg_flags,
|
||
const gchar *arg_remote,
|
||
const gchar *arg_ref,
|
||
const gchar *arg_installation,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
if (flatpak_dir_get_no_interaction (self))
|
||
arg_flags |= FLATPAK_HELPER_REMOVE_LOCAL_REF_FLAGS_NO_INTERACTION;
|
||
|
||
g_autoptr(GVariant) ret =
|
||
flatpak_dir_system_helper_call (self, "RemoveLocalRef",
|
||
g_variant_new ("(usss)",
|
||
arg_flags,
|
||
arg_remote,
|
||
arg_ref,
|
||
arg_installation),
|
||
G_VARIANT_TYPE ("()"), NULL,
|
||
cancellable, error);
|
||
return ret != NULL;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_system_helper_call_prune_local_repo (FlatpakDir *self,
|
||
guint arg_flags,
|
||
const gchar *arg_installation,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
if (flatpak_dir_get_no_interaction (self))
|
||
arg_flags |= FLATPAK_HELPER_PRUNE_LOCAL_REPO_FLAGS_NO_INTERACTION;
|
||
|
||
g_autoptr(GVariant) ret =
|
||
flatpak_dir_system_helper_call (self, "PruneLocalRepo",
|
||
g_variant_new ("(us)",
|
||
arg_flags,
|
||
arg_installation),
|
||
G_VARIANT_TYPE ("()"), NULL,
|
||
cancellable, error);
|
||
return ret != NULL;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_system_helper_call_run_triggers (FlatpakDir *self,
|
||
guint arg_flags,
|
||
const gchar *arg_installation,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
if (flatpak_dir_get_no_interaction (self))
|
||
arg_flags |= FLATPAK_HELPER_RUN_TRIGGERS_FLAGS_NO_INTERACTION;
|
||
|
||
g_autoptr(GVariant) ret =
|
||
flatpak_dir_system_helper_call (self, "RunTriggers",
|
||
g_variant_new ("(us)",
|
||
arg_flags,
|
||
arg_installation),
|
||
G_VARIANT_TYPE ("()"), NULL,
|
||
cancellable, error);
|
||
return ret != NULL;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_system_helper_call_ensure_repo (FlatpakDir *self,
|
||
guint arg_flags,
|
||
const gchar *arg_installation,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
if (flatpak_dir_get_no_interaction (self))
|
||
arg_flags |= FLATPAK_HELPER_ENSURE_REPO_FLAGS_NO_INTERACTION;
|
||
|
||
g_autoptr(GVariant) ret =
|
||
flatpak_dir_system_helper_call (self, "EnsureRepo",
|
||
g_variant_new ("(us)",
|
||
arg_flags,
|
||
arg_installation),
|
||
G_VARIANT_TYPE ("()"), NULL,
|
||
cancellable, error);
|
||
return ret != NULL;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_system_helper_call_cancel_pull (FlatpakDir *self,
|
||
guint arg_flags,
|
||
const gchar *arg_installation,
|
||
const gchar *arg_src_dir,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
if (flatpak_dir_get_no_interaction (self))
|
||
arg_flags |= FLATPAK_HELPER_CANCEL_PULL_FLAGS_NO_INTERACTION;
|
||
|
||
g_info ("Calling system helper: CancelPull");
|
||
|
||
g_autoptr(GVariant) ret =
|
||
flatpak_dir_system_helper_call (self, "CancelPull",
|
||
g_variant_new ("(uss)",
|
||
arg_flags,
|
||
arg_installation,
|
||
arg_src_dir),
|
||
NULL, NULL,
|
||
cancellable, error);
|
||
|
||
return ret != NULL;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_system_helper_call_get_revokefs_fd (FlatpakDir *self,
|
||
guint arg_flags,
|
||
const gchar *arg_installation,
|
||
gint *out_socket,
|
||
gchar **out_src_dir,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GUnixFDList) out_fd_list = NULL;
|
||
gint fd = -1;
|
||
gint fd_index = -1;
|
||
|
||
if (flatpak_dir_get_no_interaction (self))
|
||
arg_flags |= FLATPAK_HELPER_GET_REVOKEFS_FD_FLAGS_NO_INTERACTION;
|
||
|
||
g_info ("Calling system helper: GetRevokefsFd");
|
||
|
||
g_autoptr(GVariant) ret =
|
||
flatpak_dir_system_helper_call (self, "GetRevokefsFd",
|
||
g_variant_new ("(us)",
|
||
arg_flags,
|
||
arg_installation),
|
||
G_VARIANT_TYPE ("(hs)"),
|
||
&out_fd_list,
|
||
cancellable, error);
|
||
|
||
if (ret == NULL)
|
||
return FALSE;
|
||
|
||
g_variant_get (ret, "(hs)", &fd_index, out_src_dir);
|
||
fd = g_unix_fd_list_get (out_fd_list, fd_index, error);
|
||
if (fd == -1)
|
||
return FALSE;
|
||
|
||
*out_socket = fd;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_system_helper_call_update_summary (FlatpakDir *self,
|
||
guint arg_flags,
|
||
const gchar *arg_installation,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
if (flatpak_dir_get_no_interaction (self))
|
||
arg_flags |= FLATPAK_HELPER_UPDATE_SUMMARY_FLAGS_NO_INTERACTION;
|
||
|
||
g_autoptr(GVariant) ret =
|
||
flatpak_dir_system_helper_call (self, "UpdateSummary",
|
||
g_variant_new ("(us)",
|
||
arg_flags,
|
||
arg_installation),
|
||
G_VARIANT_TYPE ("()"), NULL,
|
||
cancellable, error);
|
||
return ret != NULL;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_system_helper_call_generate_oci_summary (FlatpakDir *self,
|
||
guint arg_flags,
|
||
const gchar *arg_origin,
|
||
const gchar *arg_installation,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
if (flatpak_dir_get_no_interaction (self))
|
||
arg_flags |= FLATPAK_HELPER_GENERATE_OCI_SUMMARY_FLAGS_NO_INTERACTION;
|
||
|
||
g_autoptr(GVariant) ret =
|
||
flatpak_dir_system_helper_call (self, "GenerateOciSummary",
|
||
g_variant_new ("(uss)",
|
||
arg_flags,
|
||
arg_origin,
|
||
arg_installation),
|
||
G_VARIANT_TYPE ("()"), NULL,
|
||
cancellable, error);
|
||
return ret != NULL;
|
||
}
|
||
|
||
static void
|
||
flatpak_dir_finalize (GObject *object)
|
||
{
|
||
FlatpakDir *self = FLATPAK_DIR (object);
|
||
|
||
g_clear_object (&self->repo);
|
||
g_clear_object (&self->cache_dir);
|
||
g_clear_object (&self->basedir);
|
||
g_clear_pointer (&self->extra_data, dir_extra_data_free);
|
||
|
||
if (self->system_helper_bus != (gpointer) 1)
|
||
g_clear_object (&self->system_helper_bus);
|
||
|
||
g_clear_pointer (&self->http_session, flatpak_http_session_free);
|
||
g_clear_pointer (&self->summary_cache, g_hash_table_unref);
|
||
g_clear_pointer (&self->remote_filters, g_hash_table_unref);
|
||
g_clear_pointer (&self->masked, g_regex_unref);
|
||
g_clear_pointer (&self->pinned, g_regex_unref);
|
||
|
||
G_OBJECT_CLASS (flatpak_dir_parent_class)->finalize (object);
|
||
}
|
||
|
||
static void
|
||
flatpak_dir_set_property (GObject *object,
|
||
guint prop_id,
|
||
const GValue *value,
|
||
GParamSpec *pspec)
|
||
{
|
||
FlatpakDir *self = FLATPAK_DIR (object);
|
||
|
||
switch (prop_id)
|
||
{
|
||
case PROP_PATH:
|
||
/* Canonicalize */
|
||
self->basedir = g_file_new_for_path (flatpak_file_get_path_cached (g_value_get_object (value)));
|
||
break;
|
||
|
||
case PROP_USER:
|
||
self->user = g_value_get_boolean (value);
|
||
break;
|
||
|
||
default:
|
||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
break;
|
||
}
|
||
}
|
||
|
||
static void
|
||
flatpak_dir_get_property (GObject *object,
|
||
guint prop_id,
|
||
GValue *value,
|
||
GParamSpec *pspec)
|
||
{
|
||
FlatpakDir *self = FLATPAK_DIR (object);
|
||
|
||
switch (prop_id)
|
||
{
|
||
case PROP_PATH:
|
||
g_value_set_object (value, self->basedir);
|
||
break;
|
||
|
||
case PROP_USER:
|
||
g_value_set_boolean (value, self->user);
|
||
break;
|
||
|
||
default:
|
||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
break;
|
||
}
|
||
}
|
||
|
||
static void
|
||
flatpak_dir_class_init (FlatpakDirClass *klass)
|
||
{
|
||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||
|
||
object_class->get_property = flatpak_dir_get_property;
|
||
object_class->set_property = flatpak_dir_set_property;
|
||
object_class->finalize = flatpak_dir_finalize;
|
||
|
||
g_object_class_install_property (object_class,
|
||
PROP_USER,
|
||
g_param_spec_boolean ("user",
|
||
"",
|
||
"",
|
||
FALSE,
|
||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
||
g_object_class_install_property (object_class,
|
||
PROP_PATH,
|
||
g_param_spec_object ("path",
|
||
"",
|
||
"",
|
||
G_TYPE_FILE,
|
||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
||
}
|
||
|
||
static void
|
||
flatpak_dir_init (FlatpakDir *self)
|
||
{
|
||
/* Work around possible deadlock due to: https://bugzilla.gnome.org/show_bug.cgi?id=674885 */
|
||
g_type_ensure (G_TYPE_UNIX_SOCKET_ADDRESS);
|
||
|
||
/* Optional data that needs initialization */
|
||
self->extra_data = NULL;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_is_user (FlatpakDir *self)
|
||
{
|
||
return self->user;
|
||
}
|
||
|
||
void
|
||
flatpak_dir_set_no_system_helper (FlatpakDir *self,
|
||
gboolean no_system_helper)
|
||
{
|
||
self->no_system_helper = no_system_helper;
|
||
}
|
||
|
||
void
|
||
flatpak_dir_set_no_interaction (FlatpakDir *self,
|
||
gboolean no_interaction)
|
||
{
|
||
self->no_interaction = no_interaction;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_get_no_interaction (FlatpakDir *self)
|
||
{
|
||
return self->no_interaction;
|
||
}
|
||
|
||
GFile *
|
||
flatpak_dir_get_path (FlatpakDir *self)
|
||
{
|
||
return self->basedir;
|
||
}
|
||
|
||
GFile *
|
||
flatpak_dir_get_changed_path (FlatpakDir *self)
|
||
{
|
||
return g_file_get_child (self->basedir, ".changed");
|
||
}
|
||
|
||
const char *
|
||
flatpak_dir_get_id (FlatpakDir *self)
|
||
{
|
||
if (self->user)
|
||
return "user";
|
||
|
||
if (self->extra_data != NULL)
|
||
return self->extra_data->id;
|
||
|
||
return NULL;
|
||
}
|
||
|
||
char *
|
||
flatpak_dir_get_name (FlatpakDir *self)
|
||
{
|
||
const char *id = NULL;
|
||
|
||
if (self->user)
|
||
return g_strdup ("user");
|
||
|
||
id = flatpak_dir_get_id (self);
|
||
if (id != NULL && g_strcmp0 (id, SYSTEM_DIR_DEFAULT_ID) != 0)
|
||
return g_strdup_printf ("system (%s)", id);
|
||
|
||
return g_strdup ("system");
|
||
}
|
||
|
||
const char *
|
||
flatpak_dir_get_name_cached (FlatpakDir *self)
|
||
{
|
||
char *name;
|
||
|
||
name = g_object_get_data (G_OBJECT (self), "cached-name");
|
||
if (!name)
|
||
{
|
||
name = flatpak_dir_get_name (self),
|
||
g_object_set_data_full (G_OBJECT (self), "cached-name", name, g_free);
|
||
}
|
||
|
||
return (const char *) name;
|
||
}
|
||
|
||
char *
|
||
flatpak_dir_get_display_name (FlatpakDir *self)
|
||
{
|
||
if (self->user)
|
||
return g_strdup (_("User installation"));
|
||
|
||
if (self->extra_data != NULL && g_strcmp0 (self->extra_data->id, SYSTEM_DIR_DEFAULT_ID) != 0)
|
||
{
|
||
if (self->extra_data->display_name)
|
||
return g_strdup (self->extra_data->display_name);
|
||
|
||
return g_strdup_printf (_("System (%s) installation"), self->extra_data->id);
|
||
}
|
||
|
||
return g_strdup (SYSTEM_DIR_DEFAULT_DISPLAY_NAME);
|
||
}
|
||
|
||
gint
|
||
flatpak_dir_get_priority (FlatpakDir *self)
|
||
{
|
||
if (self->extra_data != NULL)
|
||
return self->extra_data->priority;
|
||
|
||
return 0;
|
||
}
|
||
|
||
FlatpakDirStorageType
|
||
flatpak_dir_get_storage_type (FlatpakDir *self)
|
||
{
|
||
if (self->extra_data != NULL)
|
||
return self->extra_data->storage_type;
|
||
|
||
return FLATPAK_DIR_STORAGE_TYPE_DEFAULT;
|
||
}
|
||
|
||
char *
|
||
flatpak_dir_load_override (FlatpakDir *self,
|
||
const char *app_id,
|
||
gsize *length,
|
||
GFile **file_out,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) override_dir = NULL;
|
||
g_autoptr(GFile) file = NULL;
|
||
char *metadata_contents;
|
||
|
||
override_dir = g_file_get_child (self->basedir, "overrides");
|
||
|
||
if (app_id)
|
||
file = g_file_get_child (override_dir, app_id);
|
||
else
|
||
file = g_file_get_child (override_dir, "global");
|
||
|
||
if (!g_file_load_contents (file, NULL,
|
||
&metadata_contents, length, NULL, NULL))
|
||
{
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
||
_("No overrides found for %s"), app_id);
|
||
return NULL;
|
||
}
|
||
|
||
if (file_out != NULL)
|
||
*file_out = g_object_ref (file);
|
||
|
||
return metadata_contents;
|
||
}
|
||
|
||
GKeyFile *
|
||
flatpak_load_override_keyfile (const char *app_id, gboolean user, GError **error)
|
||
{
|
||
g_autofree char *metadata_contents = NULL;
|
||
gsize metadata_size;
|
||
g_autoptr(GFile) file = NULL;
|
||
g_autoptr(GKeyFile) metakey = g_key_file_new ();
|
||
g_autoptr(FlatpakDir) dir = NULL;
|
||
|
||
dir = user ? flatpak_dir_get_user () : flatpak_dir_get_system_default ();
|
||
|
||
metadata_contents = flatpak_dir_load_override (dir, app_id, &metadata_size, &file, error);
|
||
if (metadata_contents == NULL)
|
||
return NULL;
|
||
|
||
if (!g_key_file_load_from_data (metakey,
|
||
metadata_contents,
|
||
metadata_size,
|
||
0, error))
|
||
return glnx_prefix_error_null (error, "%s", flatpak_file_get_path_cached (file));
|
||
|
||
return g_steal_pointer (&metakey);
|
||
}
|
||
|
||
FlatpakContext *
|
||
flatpak_load_override_file (const char *app_id, gboolean user, GError **error)
|
||
{
|
||
g_autoptr(FlatpakContext) overrides = flatpak_context_new ();
|
||
g_autoptr(GKeyFile) metakey = NULL;
|
||
g_autoptr(GError) my_error = NULL;
|
||
|
||
metakey = flatpak_load_override_keyfile (app_id, user, &my_error);
|
||
if (metakey == NULL)
|
||
{
|
||
if (!g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&my_error));
|
||
return NULL;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (!flatpak_context_load_metadata (overrides, metakey, error))
|
||
return NULL;
|
||
}
|
||
|
||
return g_steal_pointer (&overrides);
|
||
}
|
||
|
||
gboolean
|
||
flatpak_save_override_keyfile (GKeyFile *metakey,
|
||
const char *app_id,
|
||
gboolean user,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) base_dir = NULL;
|
||
g_autoptr(GFile) override_dir = NULL;
|
||
g_autoptr(GFile) file = NULL;
|
||
g_autofree char *filename = NULL;
|
||
g_autofree char *parent = NULL;
|
||
|
||
if (user)
|
||
base_dir = flatpak_get_user_base_dir_location ();
|
||
else
|
||
base_dir = flatpak_get_system_default_base_dir_location ();
|
||
|
||
override_dir = g_file_get_child (base_dir, "overrides");
|
||
|
||
if (app_id)
|
||
file = g_file_get_child (override_dir, app_id);
|
||
else
|
||
file = g_file_get_child (override_dir, "global");
|
||
|
||
filename = g_file_get_path (file);
|
||
parent = g_path_get_dirname (filename);
|
||
if (g_mkdir_with_parents (parent, 0755))
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
return FALSE;
|
||
}
|
||
|
||
return g_key_file_save_to_file (metakey, filename, error);
|
||
}
|
||
|
||
gboolean
|
||
flatpak_remove_override_keyfile (const char *app_id,
|
||
gboolean user,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) base_dir = NULL;
|
||
g_autoptr(GFile) override_dir = NULL;
|
||
g_autoptr(GFile) file = NULL;
|
||
g_autoptr(GError) local_error = NULL;
|
||
|
||
if (user)
|
||
base_dir = flatpak_get_user_base_dir_location ();
|
||
else
|
||
base_dir = flatpak_get_system_default_base_dir_location ();
|
||
|
||
override_dir = g_file_get_child (base_dir, "overrides");
|
||
|
||
if (app_id)
|
||
file = g_file_get_child (override_dir, app_id);
|
||
else
|
||
file = g_file_get_child (override_dir, "global");
|
||
|
||
if (!g_file_delete (file, NULL, &local_error) &&
|
||
!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* Note: passing a checksum only works here for non-sub-set deploys, not
|
||
e.g. a partial locale install, because it will not find the real
|
||
deploy directory. This is ok for now, because checksum is only
|
||
currently passed from flatpak_installation_launch() when launching
|
||
a particular version of an app, which is not used for locales. */
|
||
FlatpakDeploy *
|
||
flatpak_dir_load_deployed (FlatpakDir *self,
|
||
FlatpakDecomposed *ref,
|
||
const char *checksum,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) deploy_dir = NULL;
|
||
g_autoptr(GKeyFile) metakey = NULL;
|
||
g_autoptr(GFile) metadata = NULL;
|
||
g_autofree char *metadata_contents = NULL;
|
||
FlatpakDeploy *deploy;
|
||
gsize metadata_size;
|
||
|
||
deploy_dir = flatpak_dir_get_if_deployed (self, ref, checksum, cancellable);
|
||
if (deploy_dir == NULL)
|
||
{
|
||
if (checksum == NULL)
|
||
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED,
|
||
_("%s not installed"), flatpak_decomposed_get_ref (ref));
|
||
else
|
||
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED,
|
||
_("%s (commit %s) not installed"), flatpak_decomposed_get_ref (ref), checksum);
|
||
return NULL;
|
||
}
|
||
|
||
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
||
return NULL;
|
||
|
||
metadata = g_file_get_child (deploy_dir, "metadata");
|
||
if (!g_file_load_contents (metadata, cancellable, &metadata_contents, &metadata_size, NULL, error))
|
||
return NULL;
|
||
|
||
metakey = g_key_file_new ();
|
||
if (!g_key_file_load_from_data (metakey, metadata_contents, metadata_size, 0, error))
|
||
return glnx_prefix_error_null (error, "%s", flatpak_file_get_path_cached (metadata));
|
||
|
||
deploy = flatpak_deploy_new (deploy_dir, ref, metakey, self->repo);
|
||
|
||
/* Only load system global overrides for system installed apps */
|
||
if (!self->user)
|
||
{
|
||
deploy->system_overrides = flatpak_load_override_file (NULL, FALSE, error);
|
||
if (deploy->system_overrides == NULL)
|
||
return NULL;
|
||
}
|
||
|
||
/* Always load user global overrides */
|
||
deploy->user_overrides = flatpak_load_override_file (NULL, TRUE, error);
|
||
if (deploy->user_overrides == NULL)
|
||
return NULL;
|
||
|
||
/* Only apps have app overrides */
|
||
if (flatpak_decomposed_is_app (ref))
|
||
{
|
||
g_autofree char *id = flatpak_decomposed_dup_id (ref);
|
||
|
||
/* Only load system overrides for system installed apps */
|
||
if (!self->user)
|
||
{
|
||
deploy->system_app_overrides = flatpak_load_override_file (id, FALSE, error);
|
||
if (deploy->system_app_overrides == NULL)
|
||
return NULL;
|
||
}
|
||
|
||
/* Always load user overrides */
|
||
deploy->user_app_overrides = flatpak_load_override_file (id, TRUE, error);
|
||
if (deploy->user_app_overrides == NULL)
|
||
return NULL;
|
||
}
|
||
|
||
return deploy;
|
||
}
|
||
|
||
GFile *
|
||
flatpak_dir_get_deploy_dir (FlatpakDir *self,
|
||
FlatpakDecomposed *ref)
|
||
{
|
||
return g_file_resolve_relative_path (self->basedir, flatpak_decomposed_get_ref (ref));
|
||
}
|
||
|
||
char *
|
||
flatpak_dir_get_deploy_subdir (FlatpakDir *self,
|
||
const char *checksum,
|
||
const char * const * subpaths)
|
||
{
|
||
if (subpaths == NULL || *subpaths == NULL)
|
||
return g_strdup (checksum);
|
||
else
|
||
{
|
||
GString *str = g_string_new (checksum);
|
||
int i;
|
||
for (i = 0; subpaths[i] != NULL; i++)
|
||
{
|
||
const char *s = subpaths[i];
|
||
g_string_append_c (str, '-');
|
||
while (*s)
|
||
{
|
||
if (*s != '/')
|
||
g_string_append_c (str, *s);
|
||
s++;
|
||
}
|
||
}
|
||
return g_string_free (str, FALSE);
|
||
}
|
||
}
|
||
|
||
GFile *
|
||
flatpak_dir_get_unmaintained_extension_dir (FlatpakDir *self,
|
||
const char *name,
|
||
const char *arch,
|
||
const char *branch)
|
||
{
|
||
g_autofree char *unmaintained_ref = NULL;
|
||
|
||
unmaintained_ref = g_build_filename ("extension", name, arch, branch, NULL);
|
||
return g_file_resolve_relative_path (self->basedir, unmaintained_ref);
|
||
}
|
||
|
||
GFile *
|
||
flatpak_dir_get_exports_dir (FlatpakDir *self)
|
||
{
|
||
return g_file_get_child (self->basedir, "exports");
|
||
}
|
||
|
||
GFile *
|
||
flatpak_dir_get_removed_dir (FlatpakDir *self)
|
||
{
|
||
return g_file_get_child (self->basedir, ".removed");
|
||
}
|
||
|
||
GFile *
|
||
flatpak_dir_get_sideload_repos_dir (FlatpakDir *self)
|
||
{
|
||
return g_file_get_child (self->basedir, SIDELOAD_REPOS_DIR_NAME);
|
||
}
|
||
|
||
GFile *
|
||
flatpak_dir_get_runtime_sideload_repos_dir (FlatpakDir *self)
|
||
{
|
||
g_autoptr(GFile) base = g_file_new_for_path (get_run_dir_location ());
|
||
return g_file_get_child (base, SIDELOAD_REPOS_DIR_NAME);
|
||
}
|
||
|
||
OstreeRepo *
|
||
flatpak_dir_get_repo (FlatpakDir *self)
|
||
{
|
||
return self->repo;
|
||
}
|
||
|
||
|
||
/* This is an exclusive per flatpak installation file lock that is taken
|
||
* whenever any config in the directory outside the repo is to be changed. For
|
||
* instance deployments, overrides or active commit changes.
|
||
*
|
||
* For concurrency protection of the actual repository we rely on ostree
|
||
* to do the right thing.
|
||
*/
|
||
gboolean
|
||
flatpak_dir_lock (FlatpakDir *self,
|
||
GLnxLockFile *lockfile,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) lock_file = g_file_get_child (flatpak_dir_get_path (self), "lock");
|
||
g_autofree char *lock_path = g_file_get_path (lock_file);
|
||
|
||
return glnx_make_lock_file (AT_FDCWD, lock_path, LOCK_EX, lockfile, error);
|
||
}
|
||
|
||
|
||
/* This is an lock that protects the repo itself. Any operation that
|
||
* relies on objects not disappearing from the repo need to hold this
|
||
* in a non-exclusive mode, while anything that can remove objects
|
||
* (i.e. prune) need to take it in exclusive mode.
|
||
*
|
||
* The following operations depends on objects not disappearing:
|
||
* * pull into a staging directory (pre-existing objects are not downloaded)
|
||
* * moving a staging directory into the repo (no ref keeps the object alive during copy)
|
||
* * Deploying a ref (a parallel update + prune could cause objects to be removed)
|
||
*
|
||
* In practice this means we hold a shared lock during deploy and
|
||
* pull, and an excusive lock during prune.
|
||
*/
|
||
gboolean
|
||
flatpak_dir_repo_lock (FlatpakDir *self,
|
||
GLnxLockFile *lockfile,
|
||
int operation,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) lock_file = g_file_get_child (flatpak_dir_get_path (self), "repo-lock");
|
||
g_autofree char *lock_path = g_file_get_path (lock_file);
|
||
|
||
return glnx_make_lock_file (AT_FDCWD, lock_path, operation, lockfile, error);
|
||
}
|
||
|
||
const char *
|
||
flatpak_deploy_data_get_origin (GBytes *deploy_data)
|
||
{
|
||
VarDeployDataRef ref = var_deploy_data_from_bytes (deploy_data);
|
||
return var_deploy_data_get_origin (ref);
|
||
}
|
||
|
||
const char *
|
||
flatpak_deploy_data_get_commit (GBytes *deploy_data)
|
||
{
|
||
VarDeployDataRef ref = var_deploy_data_from_bytes (deploy_data);
|
||
return var_deploy_data_get_commit (ref);
|
||
}
|
||
|
||
gint32
|
||
flatpak_deploy_data_get_version (GBytes *deploy_data)
|
||
{
|
||
VarDeployDataRef ref = var_deploy_data_from_bytes (deploy_data);
|
||
VarMetadataRef metadata = var_deploy_data_get_metadata (ref);
|
||
|
||
return var_metadata_lookup_int32 (metadata, "deploy-version", 0);
|
||
}
|
||
|
||
/* Note: This will return 0 if this is unset, which happens on deloy data updates, so ensure we handle that in all callers */
|
||
guint64
|
||
flatpak_deploy_data_get_timestamp (GBytes *deploy_data)
|
||
{
|
||
VarDeployDataRef ref = var_deploy_data_from_bytes (deploy_data);
|
||
VarMetadataRef metadata = var_deploy_data_get_metadata (ref);
|
||
|
||
return var_metadata_lookup_uint64 (metadata, "timestamp", 0);
|
||
}
|
||
|
||
static const char *
|
||
flatpak_deploy_data_get_string (GBytes *deploy_data, const char *key)
|
||
{
|
||
VarDeployDataRef ref = var_deploy_data_from_bytes (deploy_data);
|
||
VarMetadataRef metadata = var_deploy_data_get_metadata (ref);
|
||
|
||
return var_metadata_lookup_string (metadata, key, NULL);
|
||
}
|
||
|
||
static const char *
|
||
flatpak_deploy_data_get_localed_string (GBytes *deploy_data, const char *key)
|
||
{
|
||
VarDeployDataRef ref = var_deploy_data_from_bytes (deploy_data);
|
||
VarMetadataRef metadata = var_deploy_data_get_metadata (ref);
|
||
const char * const * languages = g_get_language_names ();
|
||
int i;
|
||
|
||
for (i = 0; languages[i]; ++i)
|
||
{
|
||
g_autofree char *localed_key = NULL;
|
||
VarVariantRef value_v;
|
||
|
||
if (strcmp (languages[i], "C") == 0)
|
||
localed_key = g_strdup (key);
|
||
else
|
||
localed_key = g_strdup_printf ("%s@%s", key, languages[i]);
|
||
|
||
if (var_metadata_lookup (metadata, localed_key, NULL, &value_v) &&
|
||
var_variant_is_type (value_v, G_VARIANT_TYPE_STRING))
|
||
return var_variant_get_string (value_v);
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
const char *
|
||
flatpak_deploy_data_get_alt_id (GBytes *deploy_data)
|
||
{
|
||
return flatpak_deploy_data_get_string (deploy_data, "alt-id");
|
||
}
|
||
|
||
const char *
|
||
flatpak_deploy_data_get_eol (GBytes *deploy_data)
|
||
{
|
||
return flatpak_deploy_data_get_string (deploy_data, "eol");
|
||
}
|
||
|
||
const char *
|
||
flatpak_deploy_data_get_eol_rebase (GBytes *deploy_data)
|
||
{
|
||
return flatpak_deploy_data_get_string (deploy_data, "eolr");
|
||
}
|
||
|
||
/*<private>
|
||
* flatpak_deploy_data_get_previous_ids:
|
||
*
|
||
* Returns: (array length=length zero-terminated=1) (transfer container): an array of constant strings
|
||
**/
|
||
const char **
|
||
flatpak_deploy_data_get_previous_ids (GBytes *deploy_data, gsize *length)
|
||
{
|
||
VarDeployDataRef ref = var_deploy_data_from_bytes (deploy_data);
|
||
VarMetadataRef metadata = var_deploy_data_get_metadata (ref);
|
||
VarVariantRef previous_ids_v;
|
||
|
||
if (var_metadata_lookup (metadata, "previous-ids", NULL, &previous_ids_v))
|
||
return var_arrayofstring_to_strv (var_arrayofstring_from_variant (previous_ids_v), length);
|
||
|
||
if (length != NULL)
|
||
*length = 0;
|
||
|
||
return NULL;
|
||
}
|
||
|
||
const char *
|
||
flatpak_deploy_data_get_runtime (GBytes *deploy_data)
|
||
{
|
||
return flatpak_deploy_data_get_string (deploy_data, "runtime");
|
||
}
|
||
|
||
const char *
|
||
flatpak_deploy_data_get_extension_of (GBytes *deploy_data)
|
||
{
|
||
return flatpak_deploy_data_get_string (deploy_data, "extension-of");
|
||
}
|
||
|
||
const char *
|
||
flatpak_deploy_data_get_appdata_name (GBytes *deploy_data)
|
||
{
|
||
return flatpak_deploy_data_get_localed_string (deploy_data, "appdata-name");
|
||
}
|
||
|
||
const char *
|
||
flatpak_deploy_data_get_appdata_summary (GBytes *deploy_data)
|
||
{
|
||
return flatpak_deploy_data_get_localed_string (deploy_data, "appdata-summary");
|
||
}
|
||
|
||
const char *
|
||
flatpak_deploy_data_get_appdata_version (GBytes *deploy_data)
|
||
{
|
||
return flatpak_deploy_data_get_string (deploy_data, "appdata-version");
|
||
}
|
||
|
||
const char *
|
||
flatpak_deploy_data_get_appdata_license (GBytes *deploy_data)
|
||
{
|
||
return flatpak_deploy_data_get_string (deploy_data, "appdata-license");
|
||
}
|
||
|
||
const char *
|
||
flatpak_deploy_data_get_appdata_content_rating_type (GBytes *deploy_data)
|
||
{
|
||
VarDeployDataRef ref = var_deploy_data_from_bytes (deploy_data);
|
||
VarMetadataRef metadata = var_deploy_data_get_metadata (ref);
|
||
VarVariantRef rating_v;
|
||
|
||
if (var_metadata_lookup (metadata, "appdata-content-rating", NULL, &rating_v))
|
||
{
|
||
VarContentRatingRef rating = var_content_rating_from_variant (rating_v);
|
||
return var_content_rating_get_rating_type (rating);
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
GHashTable * /* (transfer container) (nullable) */
|
||
flatpak_deploy_data_get_appdata_content_rating (GBytes *deploy_data)
|
||
{
|
||
VarDeployDataRef ref = var_deploy_data_from_bytes (deploy_data);
|
||
VarMetadataRef metadata = var_deploy_data_get_metadata (ref);
|
||
VarVariantRef rating_v;
|
||
g_autoptr(GHashTable) content_rating = NULL;
|
||
|
||
if (var_metadata_lookup (metadata, "appdata-content-rating", NULL, &rating_v))
|
||
{
|
||
VarContentRatingRef rating = var_content_rating_from_variant (rating_v);
|
||
VarRatingsRef ratings = var_content_rating_get_ratings (rating);
|
||
gsize len, i;
|
||
|
||
content_rating = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
|
||
|
||
len = var_ratings_get_length (ratings);
|
||
for (i = 0; i < len; i++)
|
||
{
|
||
VarRatingsEntryRef entry = var_ratings_get_at (ratings, i);
|
||
|
||
g_hash_table_insert (content_rating,
|
||
(gpointer) g_intern_string (var_ratings_entry_get_key (entry)),
|
||
(gpointer) g_intern_string (var_ratings_entry_get_value (entry)));
|
||
}
|
||
}
|
||
|
||
return g_steal_pointer (&content_rating);
|
||
}
|
||
|
||
/*<private>
|
||
* flatpak_deploy_data_get_subpaths:
|
||
*
|
||
* Returns: (array zero-terminated=1) (transfer container): an array of constant strings
|
||
**/
|
||
const char **
|
||
flatpak_deploy_data_get_subpaths (GBytes *deploy_data)
|
||
{
|
||
VarDeployDataRef ref = var_deploy_data_from_bytes (deploy_data);
|
||
return var_arrayofstring_to_strv (var_deploy_data_get_subpaths (ref), NULL);
|
||
}
|
||
|
||
gboolean
|
||
flatpak_deploy_data_has_subpaths (GBytes *deploy_data)
|
||
{
|
||
VarDeployDataRef ref = var_deploy_data_from_bytes (deploy_data);
|
||
VarArrayofstringRef subpaths = var_deploy_data_get_subpaths (ref);
|
||
|
||
return var_arrayofstring_get_length (subpaths) != 0;
|
||
}
|
||
|
||
guint64
|
||
flatpak_deploy_data_get_installed_size (GBytes *deploy_data)
|
||
{
|
||
VarDeployDataRef ref = var_deploy_data_from_bytes (deploy_data);
|
||
return var_deploy_data_get_installed_size (ref);
|
||
}
|
||
|
||
static char *
|
||
read_appdata_xml_from_deploy_dir (GFile *deploy_dir, const char *id)
|
||
{
|
||
g_autoptr(GFile) appdata_file = NULL;
|
||
g_autofree char *appdata_name = NULL;
|
||
g_autoptr(GFileInputStream) appdata_in = NULL;
|
||
gsize size;
|
||
|
||
appdata_name = g_strconcat (id, ".xml.gz", NULL);
|
||
appdata_file = flatpak_build_file (deploy_dir, "files/share/app-info/xmls", appdata_name, NULL);
|
||
|
||
appdata_in = g_file_read (appdata_file, NULL, NULL);
|
||
if (appdata_in)
|
||
{
|
||
g_autoptr(GZlibDecompressor) decompressor = g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP);
|
||
g_autoptr(GInputStream) converter = g_converter_input_stream_new (G_INPUT_STREAM (appdata_in), G_CONVERTER (decompressor));
|
||
g_autoptr(GBytes) appdata_xml = NULL;
|
||
|
||
appdata_xml = flatpak_read_stream (converter, TRUE, NULL);
|
||
if (appdata_xml)
|
||
return g_bytes_unref_to_data (g_steal_pointer (&appdata_xml), &size);
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
static void
|
||
add_locale_metadata_string (GVariantDict *metadata_dict,
|
||
const char *keyname,
|
||
GHashTable *values)
|
||
{
|
||
if (values == NULL)
|
||
return;
|
||
|
||
GLNX_HASH_TABLE_FOREACH_KV (values, const char *, locale, const char *, value)
|
||
{
|
||
const char *key;
|
||
g_autofree char *key_free = NULL;
|
||
|
||
if (strcmp (locale, "C") == 0)
|
||
key = keyname;
|
||
else
|
||
{
|
||
key_free = g_strdup_printf ("%s@%s", keyname, locale);
|
||
key = key_free;
|
||
}
|
||
|
||
g_variant_dict_insert_value (metadata_dict, key,
|
||
g_variant_new_string (value));
|
||
}
|
||
}
|
||
|
||
/* Convert @content_rating_type and @content_rating to a floating #GVariant of
|
||
* type `(sa{ss})`. */
|
||
static GVariant *
|
||
appdata_content_rating_to_variant (const char *content_rating_type,
|
||
GHashTable *content_rating)
|
||
{
|
||
g_autoptr(GVariantBuilder) builder = g_variant_builder_new (G_VARIANT_TYPE ("(sa{ss})"));
|
||
GHashTableIter iter;
|
||
gpointer key, value;
|
||
|
||
g_variant_builder_add (builder, "s", content_rating_type);
|
||
g_variant_builder_open (builder, G_VARIANT_TYPE ("a{ss}"));
|
||
|
||
g_hash_table_iter_init (&iter, content_rating);
|
||
|
||
while (g_hash_table_iter_next (&iter, &key, &value))
|
||
{
|
||
const char *id = key, *val = value;
|
||
g_variant_builder_add (builder, "{ss}", id, val);
|
||
}
|
||
|
||
g_variant_builder_close (builder);
|
||
|
||
return g_variant_builder_end (builder);
|
||
}
|
||
|
||
static void
|
||
add_appdata_to_deploy_data (GVariantDict *metadata_dict,
|
||
GFile *deploy_dir,
|
||
const char *id)
|
||
{
|
||
g_autofree char *appdata_xml = NULL;
|
||
g_autoptr(GHashTable) names = NULL;
|
||
g_autoptr(GHashTable) comments = NULL;
|
||
g_autofree char *version = NULL;
|
||
g_autofree char *license = NULL;
|
||
g_autofree char *content_rating_type = NULL;
|
||
g_autoptr(GHashTable) content_rating = NULL;
|
||
|
||
appdata_xml = read_appdata_xml_from_deploy_dir (deploy_dir, id);
|
||
if (appdata_xml == NULL)
|
||
return;
|
||
|
||
if (flatpak_parse_appdata (appdata_xml, id, &names, &comments, &version, &license,
|
||
&content_rating_type, &content_rating))
|
||
{
|
||
add_locale_metadata_string (metadata_dict, "appdata-name", names);
|
||
add_locale_metadata_string (metadata_dict, "appdata-summary", comments);
|
||
if (version)
|
||
g_variant_dict_insert_value (metadata_dict, "appdata-version",
|
||
g_variant_new_string (version));
|
||
if (license)
|
||
g_variant_dict_insert_value (metadata_dict, "appdata-license",
|
||
g_variant_new_string (license));
|
||
if (content_rating_type != NULL && content_rating != NULL)
|
||
g_variant_dict_insert_value (metadata_dict, "appdata-content-rating",
|
||
appdata_content_rating_to_variant (content_rating_type, content_rating));
|
||
}
|
||
}
|
||
|
||
static void
|
||
add_commit_metadata_to_deploy_data (GVariantDict *metadata_dict,
|
||
GVariant *commit_metadata)
|
||
{
|
||
const char *alt_id = NULL;
|
||
const char *eol = NULL;
|
||
const char *eol_rebase = NULL;
|
||
|
||
g_variant_lookup (commit_metadata, "xa.alt-id", "&s", &alt_id);
|
||
g_variant_lookup (commit_metadata, OSTREE_COMMIT_META_KEY_ENDOFLIFE, "&s", &eol);
|
||
g_variant_lookup (commit_metadata, OSTREE_COMMIT_META_KEY_ENDOFLIFE_REBASE, "&s", &eol_rebase);
|
||
|
||
if (alt_id)
|
||
g_variant_dict_insert_value (metadata_dict, "alt-id",
|
||
g_variant_new_string (alt_id));
|
||
if (eol)
|
||
g_variant_dict_insert_value (metadata_dict, "eol",
|
||
g_variant_new_string (eol));
|
||
if (eol_rebase)
|
||
g_variant_dict_insert_value (metadata_dict, "eolr",
|
||
g_variant_new_string (eol_rebase));
|
||
}
|
||
|
||
static void
|
||
add_metadata_to_deploy_data (GVariantDict *metadata_dict,
|
||
GKeyFile *keyfile)
|
||
{
|
||
g_autofree char *application_runtime = NULL;
|
||
g_autofree char *extension_of = NULL;
|
||
|
||
application_runtime = g_key_file_get_string (keyfile,
|
||
FLATPAK_METADATA_GROUP_APPLICATION,
|
||
FLATPAK_METADATA_KEY_RUNTIME, NULL);
|
||
extension_of = g_key_file_get_string (keyfile,
|
||
FLATPAK_METADATA_GROUP_EXTENSION_OF,
|
||
FLATPAK_METADATA_KEY_REF, NULL);
|
||
|
||
if (application_runtime)
|
||
g_variant_dict_insert_value (metadata_dict, "runtime",
|
||
g_variant_new_string (application_runtime));
|
||
if (extension_of)
|
||
g_variant_dict_insert_value (metadata_dict, "extension-of",
|
||
g_variant_new_string (extension_of));
|
||
}
|
||
|
||
static GBytes *
|
||
flatpak_dir_new_deploy_data (FlatpakDir *self,
|
||
GFile *deploy_dir,
|
||
GVariant *commit_data,
|
||
GVariant *commit_metadata,
|
||
GKeyFile *metadata,
|
||
const char *id,
|
||
const char *origin,
|
||
const char *commit,
|
||
char **subpaths,
|
||
guint64 installed_size,
|
||
const char * const *previous_ids)
|
||
{
|
||
char *empty_subpaths[] = {NULL};
|
||
g_auto(GVariantDict) metadata_dict = FLATPAK_VARIANT_DICT_INITIALIZER;
|
||
g_autoptr(GVariant) res = NULL;
|
||
|
||
g_variant_dict_init (&metadata_dict, NULL);
|
||
g_variant_dict_insert_value (&metadata_dict, "deploy-version",
|
||
g_variant_new_int32 (FLATPAK_DEPLOY_VERSION_CURRENT));
|
||
g_variant_dict_insert_value (&metadata_dict, "timestamp",
|
||
g_variant_new_uint64 (ostree_commit_get_timestamp (commit_data)));
|
||
|
||
if (previous_ids)
|
||
g_variant_dict_insert_value (&metadata_dict, "previous-ids",
|
||
g_variant_new_strv (previous_ids, -1));
|
||
|
||
add_commit_metadata_to_deploy_data (&metadata_dict, commit_metadata);
|
||
add_metadata_to_deploy_data (&metadata_dict, metadata);
|
||
add_appdata_to_deploy_data (&metadata_dict, deploy_dir, id);
|
||
|
||
res = g_variant_ref_sink (g_variant_new ("(ss^ast@a{sv})",
|
||
origin,
|
||
commit,
|
||
subpaths ? subpaths : empty_subpaths,
|
||
GUINT64_TO_BE (installed_size),
|
||
g_variant_dict_end (&metadata_dict)));
|
||
return g_variant_get_data_as_bytes (res);
|
||
}
|
||
|
||
static GBytes *
|
||
upgrade_deploy_data (GBytes *deploy_data,
|
||
GFile *deploy_dir,
|
||
FlatpakDecomposed *ref,
|
||
OstreeRepo *repo,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
VarDeployDataRef deploy_ref = var_deploy_data_from_bytes (deploy_data);
|
||
g_autoptr(GVariant) metadata = g_variant_ref_sink (var_metadata_peek_as_gvariant (var_deploy_data_get_metadata (deploy_ref)));
|
||
g_auto(GVariantDict) metadata_dict = FLATPAK_VARIANT_DICT_INITIALIZER;
|
||
g_autofree const char **subpaths = NULL;
|
||
g_autoptr(GVariant) res = NULL;
|
||
int i, n, old_version;
|
||
|
||
g_variant_dict_init (&metadata_dict, NULL);
|
||
g_variant_dict_insert_value (&metadata_dict, "deploy-version",
|
||
g_variant_new_int32 (FLATPAK_DEPLOY_VERSION_CURRENT));
|
||
|
||
/* Copy all metadata except version from old */
|
||
n = g_variant_n_children (metadata);
|
||
for (i = 0; i < n; i++)
|
||
{
|
||
const char *key;
|
||
g_autoptr(GVariant) value = NULL;
|
||
|
||
g_variant_get_child (metadata, i, "{&s@v}", &key, &value);
|
||
if (strcmp (key, "deploy-version") == 0)
|
||
continue;
|
||
g_variant_dict_insert_value (&metadata_dict, key, value);
|
||
}
|
||
|
||
old_version = flatpak_deploy_data_get_version (deploy_data);
|
||
if (old_version < 1)
|
||
{
|
||
g_autofree char *id = flatpak_decomposed_dup_id (ref);
|
||
add_appdata_to_deploy_data (&metadata_dict, deploy_dir, id);
|
||
}
|
||
|
||
if (old_version < 3)
|
||
{
|
||
/* We don't know what timestamp to use here, use 0 and special case that for update checks */
|
||
g_variant_dict_insert_value (&metadata_dict, "timestamp",
|
||
g_variant_new_uint64 (0));
|
||
}
|
||
|
||
/* Deploy versions older than 4 might have some of the below fields, but it's
|
||
* not guaranteed if the deploy was first created with an old Flatpak version
|
||
*/
|
||
if (old_version < 4)
|
||
{
|
||
const char *commit;
|
||
g_autoptr(GVariant) commit_data = NULL;
|
||
g_autoptr(GVariant) commit_metadata = NULL;
|
||
g_autoptr(GKeyFile) keyfile = NULL;
|
||
g_autoptr(GFile) metadata_file = NULL;
|
||
g_autofree char *metadata_contents = NULL;
|
||
gsize metadata_size = 0;
|
||
g_autofree char *id = flatpak_decomposed_dup_id (ref);
|
||
|
||
/* Add fields from commit metadata to deploy */
|
||
commit = flatpak_deploy_data_get_commit (deploy_data);
|
||
if (!ostree_repo_load_commit (repo, commit, &commit_data, NULL, error))
|
||
return NULL;
|
||
commit_metadata = g_variant_get_child_value (commit_data, 0);
|
||
add_commit_metadata_to_deploy_data (&metadata_dict, commit_metadata);
|
||
|
||
/* Add fields from metadata file to deploy */
|
||
keyfile = g_key_file_new ();
|
||
metadata_file = g_file_resolve_relative_path (deploy_dir, "metadata");
|
||
if (!g_file_load_contents (metadata_file, cancellable,
|
||
&metadata_contents, &metadata_size, NULL, error))
|
||
return NULL;
|
||
if (!g_key_file_load_from_data (keyfile, metadata_contents, metadata_size, 0, error))
|
||
return glnx_prefix_error_null (error, "%s", flatpak_file_get_path_cached (metadata_file));
|
||
add_metadata_to_deploy_data (&metadata_dict, keyfile);
|
||
|
||
/* Add fields from appdata to deploy, since appdata-content-rating wasn't
|
||
* added when upgrading from version 2 as it should have been
|
||
*/
|
||
if (old_version >= 1)
|
||
add_appdata_to_deploy_data (&metadata_dict, deploy_dir, id);
|
||
}
|
||
|
||
subpaths = flatpak_deploy_data_get_subpaths (deploy_data);
|
||
res = g_variant_ref_sink (g_variant_new ("(ss^ast@a{sv})",
|
||
flatpak_deploy_data_get_origin (deploy_data),
|
||
flatpak_deploy_data_get_commit (deploy_data),
|
||
subpaths,
|
||
GUINT64_TO_BE (flatpak_deploy_data_get_installed_size (deploy_data)),
|
||
g_variant_dict_end (&metadata_dict)));
|
||
return g_variant_get_data_as_bytes (res);
|
||
}
|
||
|
||
GBytes *
|
||
flatpak_dir_get_deploy_data (FlatpakDir *self,
|
||
FlatpakDecomposed *ref,
|
||
int required_version,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) deploy_dir = NULL;
|
||
|
||
deploy_dir = flatpak_dir_get_if_deployed (self, ref, NULL, cancellable);
|
||
if (deploy_dir == NULL)
|
||
{
|
||
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED,
|
||
_("%s not installed"), flatpak_decomposed_get_ref (ref));
|
||
return NULL;
|
||
}
|
||
|
||
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
||
return NULL;
|
||
|
||
return flatpak_load_deploy_data (deploy_dir,
|
||
ref,
|
||
self->repo,
|
||
required_version,
|
||
cancellable,
|
||
error);
|
||
}
|
||
|
||
char *
|
||
flatpak_dir_get_origin (FlatpakDir *self,
|
||
FlatpakDecomposed *ref,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
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;
|
||
|
||
return g_strdup (flatpak_deploy_data_get_origin (deploy_data));
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_ensure_path (FlatpakDir *self,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
/* In the system case, we use default perms */
|
||
if (!self->user)
|
||
return flatpak_mkdir_p (self->basedir, cancellable, error);
|
||
else
|
||
{
|
||
/* First make the parent */
|
||
g_autoptr(GFile) parent = g_file_get_parent (self->basedir);
|
||
if (!flatpak_mkdir_p (parent, cancellable, error))
|
||
return FALSE;
|
||
glnx_autofd int parent_dfd = -1;
|
||
if (!glnx_opendirat (AT_FDCWD, flatpak_file_get_path_cached (parent), TRUE,
|
||
&parent_dfd, error))
|
||
return FALSE;
|
||
g_autofree char *name = g_file_get_basename (self->basedir);
|
||
/* Use 0700 in the user case to neuter any suid or world-writable
|
||
* bits that happen to be in content; see
|
||
* https://github.com/flatpak/flatpak/pull/837
|
||
*/
|
||
if (mkdirat (parent_dfd, name, 0700) < 0)
|
||
{
|
||
if (errno == EEXIST)
|
||
{
|
||
/* And fix up any existing installs that had too-wide perms */
|
||
struct stat stbuf;
|
||
if (fstatat (parent_dfd, name, &stbuf, 0) < 0)
|
||
return glnx_throw_errno_prefix (error, "fstatat");
|
||
if (stbuf.st_mode & S_IXOTH)
|
||
{
|
||
if (fchmodat (parent_dfd, name, 0700, 0) < 0)
|
||
return glnx_throw_errno_prefix (error, "fchmodat");
|
||
}
|
||
}
|
||
else
|
||
return glnx_throw_errno_prefix (error, "mkdirat");
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_migrate_config (FlatpakDir *self,
|
||
gboolean *changed,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_auto(GStrv) remotes = NULL;
|
||
g_autoptr(GKeyFile) config = NULL;
|
||
int i;
|
||
|
||
if (changed != NULL)
|
||
*changed = FALSE;
|
||
|
||
/* Only do anything if it exists */
|
||
if (!flatpak_dir_maybe_ensure_repo (self, NULL, NULL))
|
||
return TRUE;
|
||
|
||
remotes = flatpak_dir_list_remotes (self, cancellable, NULL);
|
||
if (remotes == NULL)
|
||
return TRUE;
|
||
|
||
/* Enable gpg-verify-summary for all remotes with a collection id *and* gpg-verify set, because
|
||
* we want to use summary verification, but older versions of collection-id didn't work with it */
|
||
for (i = 0; remotes != NULL && remotes[i] != NULL; i++)
|
||
{
|
||
g_autofree char *remote_collection_id = NULL;
|
||
const char *remote = remotes[i];
|
||
gboolean gpg_verify_summary;
|
||
gboolean gpg_verify;
|
||
|
||
if (flatpak_dir_get_remote_disabled (self, remote))
|
||
continue;
|
||
|
||
remote_collection_id = flatpak_dir_get_remote_collection_id (self, remotes[i]);
|
||
if (remote_collection_id == NULL)
|
||
continue;
|
||
|
||
if (!ostree_repo_remote_get_gpg_verify_summary (self->repo, remote, &gpg_verify_summary, NULL))
|
||
continue;
|
||
|
||
if (!ostree_repo_remote_get_gpg_verify (self->repo, remote, &gpg_verify, NULL))
|
||
continue;
|
||
|
||
if (gpg_verify && !gpg_verify_summary)
|
||
{
|
||
g_autofree char *group = g_strdup_printf ("remote \"%s\"", remote);
|
||
if (config == NULL)
|
||
config = ostree_repo_copy_config (flatpak_dir_get_repo (self));
|
||
|
||
g_info ("Migrating remote '%s' to gpg-verify-summary", remote);
|
||
g_key_file_set_boolean (config, group, "gpg-verify-summary", TRUE);
|
||
}
|
||
}
|
||
|
||
if (config != NULL)
|
||
{
|
||
if (flatpak_dir_use_system_helper (self, NULL))
|
||
{
|
||
g_autoptr(GError) local_error = NULL;
|
||
const char *installation = flatpak_dir_get_id (self);
|
||
|
||
if (!flatpak_dir_system_helper_call_ensure_repo (self,
|
||
FLATPAK_HELPER_ENSURE_REPO_FLAGS_NONE,
|
||
installation ? installation : "",
|
||
NULL, &local_error))
|
||
g_info ("Failed to migrate system config: %s", local_error->message);
|
||
}
|
||
else
|
||
{
|
||
if (!ostree_repo_write_config (self->repo, config, error))
|
||
return FALSE;
|
||
}
|
||
|
||
if (changed != NULL)
|
||
*changed = TRUE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* Warning: This is not threadsafe, don't use in libflatpak */
|
||
gboolean
|
||
flatpak_dir_recreate_repo (FlatpakDir *self,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
gboolean res;
|
||
OstreeRepo *old_repo = g_steal_pointer (&self->repo);
|
||
|
||
/* This is also set by ensure repo, so clear it too */
|
||
g_clear_object (&self->cache_dir);
|
||
|
||
res = flatpak_dir_ensure_repo (self, cancellable, error);
|
||
g_clear_object (&old_repo);
|
||
|
||
G_LOCK (config_cache);
|
||
|
||
g_clear_pointer (&self->masked, g_regex_unref);
|
||
g_clear_pointer (&self->pinned, g_regex_unref);
|
||
|
||
G_UNLOCK (config_cache);
|
||
|
||
return res;
|
||
}
|
||
|
||
static void
|
||
copy_remote_config (GKeyFile *config,
|
||
GKeyFile *group_config,
|
||
const char *remote_name)
|
||
{
|
||
g_auto(GStrv) keys = NULL;
|
||
g_autofree char *group = g_strdup_printf ("remote \"%s\"", remote_name);
|
||
int i;
|
||
|
||
g_key_file_remove_group (config, group, NULL);
|
||
|
||
keys = g_key_file_get_keys (group_config, group, NULL, NULL);
|
||
if (keys == NULL)
|
||
return;
|
||
|
||
for (i = 0; keys[i] != NULL; i++)
|
||
{
|
||
g_autofree gchar *value = g_key_file_get_value (group_config, group, keys[i], NULL);
|
||
if (value &&
|
||
/* Canonicalize empty filter to unset */
|
||
(strcmp (keys[i], "xa.filter") != 0 ||
|
||
*value != 0))
|
||
g_key_file_set_value (config, group, keys[i], value);
|
||
}
|
||
}
|
||
|
||
static void
|
||
_flatpak_dir_scan_new_flatpakrepos (const char *dir_str,
|
||
GHashTable **flatpakrepos,
|
||
const char * const *remotes)
|
||
{
|
||
g_autoptr(GFile) dir = NULL;
|
||
g_autoptr(GFileEnumerator) dir_enum = NULL;
|
||
|
||
g_return_if_fail (dir_str != NULL);
|
||
g_return_if_fail (flatpakrepos != NULL);
|
||
|
||
dir = g_file_new_for_path (dir_str);
|
||
dir_enum = g_file_enumerate_children (dir,
|
||
G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE,
|
||
G_FILE_QUERY_INFO_NONE,
|
||
NULL, NULL);
|
||
if (dir_enum == NULL)
|
||
return;
|
||
|
||
while (TRUE)
|
||
{
|
||
GFileInfo *file_info;
|
||
const char *name;
|
||
guint32 type;
|
||
g_autoptr(GError) local_error = NULL;
|
||
|
||
if (!g_file_enumerator_iterate (dir_enum, &file_info, NULL,
|
||
NULL, &local_error))
|
||
{
|
||
g_info ("Unexpected error reading file in %s: %s",
|
||
dir_str, local_error->message);
|
||
break;
|
||
}
|
||
|
||
if (file_info == NULL)
|
||
break;
|
||
|
||
name = g_file_info_get_name (file_info);
|
||
type = g_file_info_get_file_type (file_info);
|
||
|
||
if (type == G_FILE_TYPE_REGULAR && g_str_has_suffix (name, FLATPAK_REMOTES_FILE_EXT))
|
||
{
|
||
g_autofree char *remote_name = g_strndup (name, strlen (name) - strlen (FLATPAK_REMOTES_FILE_EXT));
|
||
|
||
if (remotes && g_strv_contains (remotes, remote_name))
|
||
continue;
|
||
|
||
if (*flatpakrepos == NULL)
|
||
*flatpakrepos = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
|
||
|
||
g_hash_table_insert (*flatpakrepos, g_steal_pointer (&remote_name), g_file_enumerator_get_child (dir_enum, file_info));
|
||
}
|
||
}
|
||
}
|
||
|
||
static GHashTable *
|
||
_flatpak_dir_find_new_flatpakrepos (FlatpakDir *self, OstreeRepo *repo)
|
||
{
|
||
g_autoptr(GHashTable) flatpakrepos = NULL;
|
||
g_autofree char *dir_str = NULL;
|
||
g_auto(GStrv) ostree_remotes = NULL;
|
||
g_auto(GStrv) applied_remotes = NULL;
|
||
g_autoptr(GPtrArray) remotes = NULL;
|
||
|
||
g_assert (repo != NULL);
|
||
|
||
/* Predefined remotes only applies for the default system installation */
|
||
if (self->user ||
|
||
(self->extra_data &&
|
||
g_strcmp0 (self->extra_data->id, SYSTEM_DIR_DEFAULT_ID) != 0))
|
||
return NULL;
|
||
|
||
ostree_remotes = ostree_repo_remote_list (repo, NULL);
|
||
applied_remotes = g_key_file_get_string_list (ostree_repo_get_config (repo),
|
||
"core", "xa.applied-remotes", NULL, NULL);
|
||
remotes = g_ptr_array_new ();
|
||
for (int i = 0; ostree_remotes && ostree_remotes[i]; i++)
|
||
g_ptr_array_add (remotes, ostree_remotes[i]);
|
||
for (int i = 0; applied_remotes && applied_remotes[i]; i++)
|
||
g_ptr_array_add (remotes, applied_remotes[i]);
|
||
g_ptr_array_add (remotes, NULL);
|
||
|
||
dir_str = g_build_filename (get_config_dir_location (), FLATPAK_REMOTES_DIR, NULL);
|
||
_flatpak_dir_scan_new_flatpakrepos (dir_str,
|
||
&flatpakrepos,
|
||
(const char * const *) remotes->pdata);
|
||
|
||
_flatpak_dir_scan_new_flatpakrepos (FLATPAK_BASEDIR "/" FLATPAK_REMOTES_DIR,
|
||
&flatpakrepos,
|
||
(const char * const *) remotes->pdata);
|
||
|
||
return g_steal_pointer (&flatpakrepos);
|
||
}
|
||
|
||
static gboolean
|
||
apply_new_flatpakrepo (const char *remote_name,
|
||
GFile *file,
|
||
OstreeRepo *repo,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GBytes) gpg_data = NULL;
|
||
g_autoptr(GKeyFile) group_config = NULL;
|
||
g_autoptr(GKeyFile) keyfile = g_key_file_new ();
|
||
g_autoptr(GError) local_error = NULL;
|
||
g_autoptr(GKeyFile) new_config = NULL;
|
||
g_auto(GStrv) old_applied_remotes = NULL;
|
||
g_autoptr(GPtrArray) new_applied_remotes = NULL;
|
||
int i;
|
||
|
||
if (!g_key_file_load_from_file (keyfile, flatpak_file_get_path_cached (file), 0, &local_error))
|
||
{
|
||
flatpak_fail (error, _("Can't load file %s: %s\n"), flatpak_file_get_path_cached (file), local_error->message);
|
||
return FALSE;
|
||
}
|
||
|
||
group_config = flatpak_parse_repofile (remote_name, FALSE, keyfile, &gpg_data, NULL, &local_error);
|
||
if (group_config == NULL)
|
||
{
|
||
flatpak_fail (error, _("Error parsing system flatpakrepo file for %s: %s"), remote_name, local_error->message);
|
||
return FALSE;
|
||
}
|
||
|
||
new_config = ostree_repo_copy_config (repo);
|
||
|
||
old_applied_remotes = g_key_file_get_string_list (new_config, "core", "xa.applied-remotes", NULL, NULL);
|
||
|
||
copy_remote_config (new_config, group_config, remote_name);
|
||
|
||
new_applied_remotes = g_ptr_array_new_with_free_func (g_free);
|
||
for (i = 0; old_applied_remotes != NULL && old_applied_remotes[i] != NULL; i++)
|
||
g_ptr_array_add (new_applied_remotes, g_strdup (old_applied_remotes[i]));
|
||
|
||
g_ptr_array_add (new_applied_remotes, g_strdup (remote_name));
|
||
|
||
g_key_file_set_string_list (new_config, "core", "xa.applied-remotes",
|
||
(const char * const *) new_applied_remotes->pdata, new_applied_remotes->len);
|
||
|
||
if (!ostree_repo_write_config (repo, new_config, error))
|
||
return FALSE;
|
||
|
||
if (!ostree_repo_reload_config (repo, NULL, error))
|
||
return FALSE;
|
||
|
||
if (gpg_data != NULL)
|
||
{
|
||
g_autoptr(GInputStream) input_stream = g_memory_input_stream_new_from_bytes (gpg_data);
|
||
guint imported = 0;
|
||
|
||
if (!ostree_repo_remote_gpg_import (repo, remote_name, input_stream,
|
||
NULL, &imported, NULL, error))
|
||
return FALSE;
|
||
|
||
g_info ("Imported %u GPG key%s to remote \"%s\"", imported, (imported == 1) ? "" : "s", remote_name);
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
system_helper_maybe_ensure_repo (FlatpakDir *self,
|
||
FlatpakHelperEnsureRepoFlags flags,
|
||
gboolean allow_empty,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GError) local_error = NULL;
|
||
const char *installation = flatpak_dir_get_id (self);
|
||
|
||
if (!flatpak_dir_system_helper_call_ensure_repo (self,
|
||
flags,
|
||
installation ? installation : "",
|
||
cancellable, &local_error))
|
||
{
|
||
if (allow_empty)
|
||
return TRUE;
|
||
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
ensure_repo_opened (OstreeRepo *repo,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
if (!ostree_repo_open (repo, cancellable, error))
|
||
{
|
||
g_autofree char *repopath = NULL;
|
||
|
||
repopath = g_file_get_path (ostree_repo_get_path (repo));
|
||
g_prefix_error (error, _("While opening repository %s: "), repopath);
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
_flatpak_dir_ensure_repo (FlatpakDir *self,
|
||
gboolean allow_empty,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) repodir = NULL;
|
||
g_autoptr(OstreeRepo) repo = NULL;
|
||
g_autoptr(GError) my_error = NULL;
|
||
g_autoptr(GFile) cache_dir = NULL;
|
||
g_autoptr(GHashTable) flatpakrepos = NULL;
|
||
FlatpakHelperEnsureRepoFlags ensure_flags = FLATPAK_HELPER_ENSURE_REPO_FLAGS_NONE;
|
||
|
||
if (self->repo != NULL)
|
||
return TRUE;
|
||
|
||
/* Don't trigger polkit prompts if we are just doing this opportunistically */
|
||
if (allow_empty)
|
||
ensure_flags |= FLATPAK_HELPER_ENSURE_REPO_FLAGS_NO_INTERACTION;
|
||
|
||
if (!g_file_query_exists (self->basedir, cancellable))
|
||
{
|
||
if (flatpak_dir_use_system_helper (self, NULL))
|
||
{
|
||
if (!system_helper_maybe_ensure_repo (self, ensure_flags, allow_empty, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
else
|
||
{
|
||
g_autoptr(GError) local_error = NULL;
|
||
if (!flatpak_dir_ensure_path (self, cancellable, &local_error))
|
||
{
|
||
if (allow_empty)
|
||
return TRUE;
|
||
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return FALSE;
|
||
}
|
||
}
|
||
}
|
||
|
||
repodir = g_file_get_child (self->basedir, "repo");
|
||
|
||
repo = ostree_repo_new (repodir);
|
||
|
||
if (!g_file_query_exists (repodir, cancellable))
|
||
{
|
||
/* We always use bare-user-only these days, except old installations
|
||
that still user bare-user */
|
||
OstreeRepoMode mode = OSTREE_REPO_MODE_BARE_USER_ONLY;
|
||
|
||
if (flatpak_dir_use_system_helper (self, NULL))
|
||
{
|
||
if (!system_helper_maybe_ensure_repo (self, ensure_flags, allow_empty, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (!ensure_repo_opened (repo, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
else
|
||
{
|
||
if (!ostree_repo_create (repo, mode, cancellable, &my_error))
|
||
{
|
||
const char *repo_path = flatpak_file_get_path_cached (repodir);
|
||
|
||
flatpak_rm_rf (repodir, cancellable, NULL);
|
||
|
||
if (allow_empty)
|
||
return TRUE;
|
||
|
||
/* As of 2022, the error message from libostree is not the most helpful:
|
||
* Creating repo: mkdirat: Permission denied
|
||
* If the repository path is in the error message, assume this
|
||
* has been fixed. If not, add it. */
|
||
if (strstr (my_error->message, repo_path) != NULL)
|
||
g_propagate_error (error, g_steal_pointer (&my_error));
|
||
else
|
||
g_set_error (error, my_error->domain, my_error->code,
|
||
"Unable to create repository at %s (%s)",
|
||
repo_path, my_error->message);
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* Create .changed file early to avoid polling non-existing file in monitor */
|
||
if (!flatpak_dir_mark_changed (self, &my_error))
|
||
{
|
||
g_warning ("Error marking directory as changed: %s", my_error->message);
|
||
g_clear_error (&my_error);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (!ensure_repo_opened (repo, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
/* In the system-helper case we're directly using the global repo, and we can't write any
|
||
* caches for summaries there, so we need to set a custom dir for this. Note, as per #3303
|
||
* this has to be called after ostree_repo_open() in order to the custom cachedir being
|
||
* overridden if the system dir is writable (like in the testsuite).
|
||
*/
|
||
if (flatpak_dir_use_system_helper (self, NULL))
|
||
{
|
||
g_autofree char *cache_path = NULL;
|
||
|
||
cache_dir = flatpak_ensure_user_cache_dir_location (error);
|
||
if (cache_dir == NULL)
|
||
return FALSE;
|
||
|
||
cache_path = g_file_get_path (cache_dir);
|
||
if (!ostree_repo_set_cache_dir (repo,
|
||
AT_FDCWD, cache_path,
|
||
cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
/* Earlier flatpak used to reset min-free-space-percent to 0 every time, but now we
|
||
* favor min-free-space-size instead of it (See below).
|
||
*/
|
||
if (!flatpak_dir_use_system_helper (self, NULL))
|
||
{
|
||
GKeyFile *orig_config = NULL;
|
||
g_autoptr(GKeyFile) new_config = NULL;
|
||
g_autofree char *orig_min_free_space_percent = NULL;
|
||
g_autofree char *orig_min_free_space_size = NULL;
|
||
const char *min_free_space_size = "500MB";
|
||
guint64 min_free_space_percent_int;
|
||
|
||
orig_config = ostree_repo_get_config (repo);
|
||
orig_min_free_space_percent = g_key_file_get_value (orig_config, "core", "min-free-space-percent", NULL);
|
||
orig_min_free_space_size = g_key_file_get_value (orig_config, "core", "min-free-space-size", NULL);
|
||
|
||
if (orig_min_free_space_size == NULL)
|
||
new_config = ostree_repo_copy_config (repo);
|
||
|
||
/* Scrap previously written min-free-space-percent=0 and replace it with min-free-space-size */
|
||
if (orig_min_free_space_size == NULL &&
|
||
orig_min_free_space_percent != NULL &&
|
||
g_ascii_string_to_unsigned (orig_min_free_space_percent, 10,
|
||
0, G_MAXUINT64,
|
||
&min_free_space_percent_int, &my_error))
|
||
{
|
||
if (min_free_space_percent_int == 0)
|
||
{
|
||
g_key_file_remove_key (new_config, "core", "min-free-space-percent", NULL);
|
||
g_key_file_set_string (new_config, "core", "min-free-space-size", min_free_space_size);
|
||
}
|
||
}
|
||
else if (my_error != NULL)
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&my_error));
|
||
return FALSE;
|
||
}
|
||
|
||
if (orig_min_free_space_size == NULL &&
|
||
orig_min_free_space_percent == NULL)
|
||
g_key_file_set_string (new_config, "core", "min-free-space-size", min_free_space_size);
|
||
|
||
if (new_config != NULL)
|
||
{
|
||
if (!ostree_repo_write_config (repo, new_config, error))
|
||
return FALSE;
|
||
|
||
if (!ostree_repo_reload_config (repo, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
flatpakrepos = _flatpak_dir_find_new_flatpakrepos (self, repo);
|
||
if (flatpakrepos)
|
||
{
|
||
if (flatpak_dir_use_system_helper (self, NULL))
|
||
{
|
||
if (!system_helper_maybe_ensure_repo (self, ensure_flags, allow_empty, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (!ostree_repo_reload_config (repo, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
else
|
||
{
|
||
GLNX_HASH_TABLE_FOREACH_KV (flatpakrepos, const char *, remote_name, GFile *, file)
|
||
{
|
||
if (!apply_new_flatpakrepo (remote_name, file, repo, error))
|
||
return FALSE;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
if (cache_dir == NULL)
|
||
cache_dir = g_file_get_child (repodir, "tmp/cache");
|
||
|
||
/* Make sure we didn't reenter weirdly */
|
||
g_assert (self->repo == NULL);
|
||
self->repo = g_object_ref (repo);
|
||
self->cache_dir = g_object_ref (cache_dir);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_ensure_repo (FlatpakDir *self,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
return _flatpak_dir_ensure_repo (self, FALSE, cancellable, error);
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_maybe_ensure_repo (FlatpakDir *self,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
return _flatpak_dir_ensure_repo (self, TRUE, cancellable, error);
|
||
}
|
||
|
||
static gboolean
|
||
_flatpak_dir_reload_config (FlatpakDir *self,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
if (self->repo)
|
||
{
|
||
if (!ostree_repo_reload_config (self->repo, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
/* Clear cached stuff from repo config */
|
||
G_LOCK (config_cache);
|
||
|
||
g_clear_pointer (&self->masked, g_regex_unref);
|
||
g_clear_pointer (&self->pinned, g_regex_unref);
|
||
|
||
G_UNLOCK (config_cache);
|
||
return TRUE;
|
||
}
|
||
|
||
char *
|
||
flatpak_dir_get_config (FlatpakDir *self,
|
||
const char *key,
|
||
GError **error)
|
||
{
|
||
GKeyFile *config;
|
||
g_autofree char *ostree_key = NULL;
|
||
|
||
if (!flatpak_dir_maybe_ensure_repo (self, NULL, error))
|
||
return NULL;
|
||
|
||
if (self->repo == NULL)
|
||
{
|
||
g_set_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND,
|
||
_("The config key %s is not set"), key);
|
||
return NULL;
|
||
}
|
||
|
||
config = ostree_repo_get_config (self->repo);
|
||
ostree_key = g_strconcat ("xa.", key, NULL);
|
||
|
||
return g_key_file_get_string (config, "core", ostree_key, error);
|
||
}
|
||
|
||
GPtrArray *
|
||
flatpak_dir_get_config_patterns (FlatpakDir *dir, const char *key)
|
||
{
|
||
g_autoptr(GPtrArray) patterns = NULL;
|
||
g_autofree char *key_value = NULL;
|
||
int i;
|
||
|
||
patterns = g_ptr_array_new_with_free_func (g_free);
|
||
|
||
key_value = flatpak_dir_get_config (dir, key, NULL);
|
||
if (key_value)
|
||
{
|
||
g_auto(GStrv) oldv = g_strsplit (key_value, ";", -1);
|
||
|
||
for (i = 0; oldv[i] != NULL; i++)
|
||
{
|
||
const char *old = oldv[i];
|
||
|
||
if (*old != 0 && !flatpak_g_ptr_array_contains_string (patterns, old))
|
||
g_ptr_array_add (patterns, g_strdup (old));
|
||
}
|
||
}
|
||
|
||
return g_steal_pointer (&patterns);
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_set_config (FlatpakDir *self,
|
||
const char *key,
|
||
const char *value,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GKeyFile) config = NULL;
|
||
g_autofree char *ostree_key = NULL;
|
||
|
||
if (!flatpak_dir_ensure_repo (self, NULL, error))
|
||
return FALSE;
|
||
|
||
config = ostree_repo_copy_config (self->repo);
|
||
ostree_key = g_strconcat ("xa.", key, NULL);
|
||
|
||
if (flatpak_dir_use_system_helper (self, NULL))
|
||
{
|
||
FlatpakHelperConfigureFlags flags = 0;
|
||
const char *installation = flatpak_dir_get_id (self);
|
||
|
||
if (value == NULL)
|
||
{
|
||
flags |= FLATPAK_HELPER_CONFIGURE_FLAGS_UNSET;
|
||
value = "";
|
||
}
|
||
|
||
if (!flatpak_dir_system_helper_call_configure (self,
|
||
flags, key, value,
|
||
installation ? installation : "",
|
||
NULL, error))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
if (value == NULL)
|
||
g_key_file_remove_key (config, "core", ostree_key, NULL);
|
||
else
|
||
g_key_file_set_value (config, "core", ostree_key, value);
|
||
|
||
if (!ostree_repo_write_config (self->repo, config, error))
|
||
return FALSE;
|
||
|
||
if (!_flatpak_dir_reload_config (self, NULL, error))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_config_append_pattern (FlatpakDir *self,
|
||
const char *key,
|
||
const char *pattern,
|
||
gboolean runtime_only,
|
||
gboolean *out_already_present,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GPtrArray) patterns = flatpak_dir_get_config_patterns (self, key);
|
||
g_autofree char *regexp = NULL;
|
||
gboolean already_present;
|
||
g_autofree char *merged_patterns = NULL;
|
||
|
||
regexp = flatpak_filter_glob_to_regexp (pattern, runtime_only, error);
|
||
if (regexp == NULL)
|
||
return FALSE;
|
||
|
||
if (!(already_present = flatpak_g_ptr_array_contains_string (patterns, pattern)))
|
||
g_ptr_array_add (patterns, g_strdup (pattern));
|
||
|
||
if (out_already_present)
|
||
*out_already_present = already_present;
|
||
|
||
g_ptr_array_sort (patterns, flatpak_strcmp0_ptr);
|
||
|
||
g_ptr_array_add (patterns, NULL);
|
||
merged_patterns = g_strjoinv (";", (char **)patterns->pdata);
|
||
|
||
return flatpak_dir_set_config (self, key, merged_patterns, error);
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_config_remove_pattern (FlatpakDir *self,
|
||
const char *key,
|
||
const char *pattern,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GPtrArray) patterns = flatpak_dir_get_config_patterns (self, key);
|
||
g_autofree char *merged_patterns = NULL;
|
||
int j;
|
||
|
||
for (j = 0; j < patterns->len; j++)
|
||
{
|
||
if (strcmp (g_ptr_array_index (patterns, j), pattern) == 0)
|
||
break;
|
||
}
|
||
|
||
if (j == patterns->len)
|
||
return flatpak_fail (error, _("No current %s pattern matching %s"), key, pattern);
|
||
else
|
||
g_ptr_array_remove_index (patterns, j);
|
||
|
||
g_ptr_array_add (patterns, NULL);
|
||
merged_patterns = g_strjoinv (";", (char **)patterns->pdata);
|
||
|
||
return flatpak_dir_set_config (self, key, merged_patterns, error);
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_mark_changed (FlatpakDir *self,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) changed_file = NULL;
|
||
g_autofree char * changed_path = NULL;
|
||
|
||
changed_file = flatpak_dir_get_changed_path (self);
|
||
changed_path = g_file_get_path (changed_file);
|
||
|
||
if (!g_utime (changed_path, NULL))
|
||
return TRUE;
|
||
|
||
if (errno != ENOENT)
|
||
return glnx_throw_errno (error);
|
||
|
||
if (!g_file_replace_contents (changed_file, "", 0, NULL, FALSE,
|
||
G_FILE_CREATE_NONE, NULL, NULL, error))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_remove_appstream (FlatpakDir *self,
|
||
const char *remote,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) appstream_dir = NULL;
|
||
g_autoptr(GFile) remote_dir = NULL;
|
||
|
||
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
||
return FALSE;
|
||
|
||
appstream_dir = g_file_get_child (flatpak_dir_get_path (self), "appstream");
|
||
remote_dir = g_file_get_child (appstream_dir, remote);
|
||
|
||
if (g_file_query_exists (remote_dir, cancellable) &&
|
||
!flatpak_rm_rf (remote_dir, cancellable, error))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
#define SECS_PER_MINUTE (60)
|
||
#define SECS_PER_HOUR (60 * SECS_PER_MINUTE)
|
||
#define SECS_PER_DAY (24 * SECS_PER_HOUR)
|
||
|
||
/* This looks for old temporary files created by previous versions of
|
||
flatpak_dir_deploy_appstream(). These are all either directories
|
||
starting with a dot, or symlinks starting with a dot. Such temp
|
||
files if found can be from a concurrent deploy, so we only remove
|
||
any such files older than a day to avoid races.
|
||
*/
|
||
static void
|
||
remove_old_appstream_tmpdirs (GFile *dir)
|
||
{
|
||
g_auto(GLnxDirFdIterator) dir_iter = { 0 };
|
||
time_t now = time (NULL);
|
||
|
||
if (!glnx_dirfd_iterator_init_at (AT_FDCWD, flatpak_file_get_path_cached (dir),
|
||
FALSE, &dir_iter, NULL))
|
||
return;
|
||
|
||
while (TRUE)
|
||
{
|
||
struct stat stbuf;
|
||
struct dirent *dent;
|
||
g_autoptr(GFile) tmp = NULL;
|
||
|
||
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dir_iter, &dent, NULL, NULL))
|
||
break;
|
||
|
||
if (dent == NULL)
|
||
break;
|
||
|
||
/* We ignore non-dotfiles and .timestamps as they are not tempfiles */
|
||
if (dent->d_name[0] != '.' ||
|
||
strcmp (dent->d_name, ".timestamp") == 0)
|
||
continue;
|
||
|
||
/* Check for right types and names */
|
||
if (dent->d_type == DT_DIR)
|
||
{
|
||
if (strlen (dent->d_name) != 72 ||
|
||
dent->d_name[65] != '-')
|
||
continue;
|
||
}
|
||
else if (dent->d_type == DT_LNK)
|
||
{
|
||
if (!g_str_has_prefix (dent->d_name, ".active-"))
|
||
continue;
|
||
}
|
||
else
|
||
continue;
|
||
|
||
/* Check that the file is at least a day old to avoid races */
|
||
if (!glnx_fstatat (dir_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW, NULL))
|
||
continue;
|
||
|
||
if (stbuf.st_mtime >= now ||
|
||
now - stbuf.st_mtime < SECS_PER_DAY)
|
||
continue;
|
||
|
||
tmp = g_file_get_child (dir, dent->d_name);
|
||
|
||
/* We ignore errors here, no need to worry anyone */
|
||
g_info ("Deleting stale appstream deploy tmpdir %s", flatpak_file_get_path_cached (tmp));
|
||
(void)flatpak_rm_rf (tmp, NULL, NULL);
|
||
}
|
||
}
|
||
|
||
/* Like the function above, this looks for old temporary directories created by
|
||
* previous versions of flatpak_dir_deploy().
|
||
* These are all directories starting with a dot. Such directories can be from a
|
||
* concurrent deploy, so we only remove directories older than a day to avoid
|
||
* races.
|
||
*/
|
||
static void
|
||
remove_old_deploy_tmpdirs (GFile *dir)
|
||
{
|
||
g_auto(GLnxDirFdIterator) dir_iter = { 0 };
|
||
time_t now = time (NULL);
|
||
|
||
if (!glnx_dirfd_iterator_init_at (AT_FDCWD, flatpak_file_get_path_cached (dir),
|
||
FALSE, &dir_iter, NULL))
|
||
return;
|
||
|
||
while (TRUE)
|
||
{
|
||
struct stat stbuf;
|
||
struct dirent *dent;
|
||
g_autoptr(GFile) tmp = NULL;
|
||
|
||
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dir_iter, &dent, NULL, NULL))
|
||
break;
|
||
|
||
if (dent == NULL)
|
||
break;
|
||
|
||
/* We ignore non-dotfiles and .timestamps as they are not tempfiles */
|
||
if (dent->d_name[0] != '.' ||
|
||
strcmp (dent->d_name, ".timestamp") == 0)
|
||
continue;
|
||
|
||
/* Check for right types and names. The format we’re looking for is:
|
||
* .[0-9a-f]{64}-[0-9A-Z]{6} */
|
||
if (dent->d_type == DT_DIR)
|
||
{
|
||
if (strlen (dent->d_name) != 72 ||
|
||
dent->d_name[65] != '-')
|
||
continue;
|
||
}
|
||
else
|
||
continue;
|
||
|
||
/* Check that the file is at least a day old to avoid races */
|
||
if (!glnx_fstatat (dir_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW, NULL))
|
||
continue;
|
||
|
||
if (stbuf.st_mtime >= now ||
|
||
now - stbuf.st_mtime < SECS_PER_DAY)
|
||
continue;
|
||
|
||
tmp = g_file_get_child (dir, dent->d_name);
|
||
|
||
/* We ignore errors here, no need to worry anyone */
|
||
g_info ("Deleting stale deploy tmpdir %s", flatpak_file_get_path_cached (tmp));
|
||
(void)flatpak_rm_rf (tmp, NULL, NULL);
|
||
}
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_deploy_appstream (FlatpakDir *self,
|
||
const char *remote,
|
||
const char *arch,
|
||
gboolean *out_changed,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) appstream_dir = NULL;
|
||
g_autoptr(GFile) remote_dir = NULL;
|
||
g_autoptr(GFile) arch_dir = NULL;
|
||
g_autoptr(GFile) checkout_dir = NULL;
|
||
g_autoptr(GFile) real_checkout_dir = NULL;
|
||
g_autoptr(GFile) timestamp_file = NULL;
|
||
g_autofree char *arch_path = NULL;
|
||
gboolean checkout_exists;
|
||
const char *old_dir = NULL;
|
||
g_autofree char *new_checksum = NULL;
|
||
g_autoptr(GFile) active_link = NULL;
|
||
g_autofree char *branch = NULL;
|
||
g_autoptr(GFile) old_checkout_dir = NULL;
|
||
g_autoptr(GFile) active_tmp_link = NULL;
|
||
g_autoptr(GError) tmp_error = NULL;
|
||
g_autofree char *new_dir = NULL;
|
||
OstreeRepoCheckoutAtOptions options = { 0, };
|
||
glnx_autofd int dfd = -1;
|
||
g_autoptr(GFileInfo) file_info = NULL;
|
||
g_autofree char *tmpname = g_strdup (".active-XXXXXX");
|
||
g_auto(GLnxLockFile) lock = { 0, };
|
||
gboolean do_compress = FALSE;
|
||
gboolean do_uncompress = TRUE;
|
||
g_autofree char *filter_checksum = NULL;
|
||
g_autoptr(GRegex) allow_refs = NULL;
|
||
g_autoptr(GRegex) deny_refs = NULL;
|
||
g_autofree char *subset = NULL;
|
||
g_auto(GLnxTmpDir) tmpdir = { 0, };
|
||
g_autoptr(FlatpakTempDir) tmplink = NULL;
|
||
|
||
/* Keep a shared repo lock to avoid prunes removing objects we're relying on
|
||
* while we do the checkout. This could happen if the ref changes after we
|
||
* read its current value for the checkout. */
|
||
if (!flatpak_dir_repo_lock (self, &lock, LOCK_SH, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (!flatpak_dir_lookup_remote_filter (self, remote, TRUE, &filter_checksum, &allow_refs, &deny_refs, error))
|
||
return FALSE;
|
||
|
||
appstream_dir = g_file_get_child (flatpak_dir_get_path (self), "appstream");
|
||
remote_dir = g_file_get_child (appstream_dir, remote);
|
||
arch_dir = g_file_get_child (remote_dir, arch);
|
||
active_link = g_file_get_child (arch_dir, "active");
|
||
timestamp_file = g_file_get_child (arch_dir, ".timestamp");
|
||
|
||
arch_path = g_file_get_path (arch_dir);
|
||
if (g_mkdir_with_parents (arch_path, 0755) != 0)
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
return FALSE;
|
||
}
|
||
|
||
if (!glnx_opendirat (AT_FDCWD, arch_path, TRUE, &dfd, error))
|
||
return FALSE;
|
||
|
||
old_dir = NULL;
|
||
file_info = g_file_query_info (active_link, OSTREE_GIO_FAST_QUERYINFO,
|
||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||
cancellable, NULL);
|
||
if (file_info != NULL)
|
||
old_dir = g_file_info_get_symlink_target (file_info);
|
||
|
||
subset = flatpak_dir_get_remote_subset (self, remote);
|
||
|
||
if (subset)
|
||
branch = g_strdup_printf ("appstream2/%s-%s", subset, arch);
|
||
else
|
||
branch = g_strdup_printf ("appstream2/%s", arch);
|
||
|
||
if (!flatpak_repo_resolve_rev (self->repo, NULL, remote, branch, TRUE,
|
||
&new_checksum, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (new_checksum == NULL && subset == NULL)
|
||
{
|
||
/* Fall back to old branch (only exist on non-subsets) */
|
||
g_clear_pointer (&branch, g_free);
|
||
branch = g_strdup_printf ("appstream/%s", arch);
|
||
if (!flatpak_repo_resolve_rev (self->repo, NULL, remote, branch, TRUE,
|
||
&new_checksum, cancellable, error))
|
||
return FALSE;
|
||
do_compress = FALSE;
|
||
do_uncompress = TRUE;
|
||
}
|
||
else
|
||
{
|
||
do_compress = TRUE;
|
||
do_uncompress = FALSE;
|
||
}
|
||
|
||
if (new_checksum == NULL)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("No appstream commit to deploy"));
|
||
|
||
if (filter_checksum)
|
||
new_dir = g_strconcat (new_checksum, "-", filter_checksum, NULL);
|
||
else
|
||
new_dir = g_strdup (new_checksum);
|
||
|
||
real_checkout_dir = g_file_get_child (arch_dir, new_dir);
|
||
checkout_exists = g_file_query_exists (real_checkout_dir, NULL);
|
||
|
||
if (old_dir != NULL && new_dir != NULL &&
|
||
strcmp (old_dir, new_dir) == 0 &&
|
||
checkout_exists)
|
||
{
|
||
if (!g_file_replace_contents (timestamp_file, "", 0, NULL, FALSE,
|
||
G_FILE_CREATE_REPLACE_DESTINATION, NULL, NULL, error))
|
||
return FALSE;
|
||
|
||
if (out_changed)
|
||
*out_changed = FALSE;
|
||
|
||
return TRUE; /* No changes, don't checkout */
|
||
}
|
||
|
||
{
|
||
g_autofree char *template = g_strdup_printf (".%s-XXXXXX", new_dir);
|
||
g_autoptr(GFile) tmp_dir_template = g_file_get_child (arch_dir, template);
|
||
|
||
if (!glnx_mkdtempat (AT_FDCWD, flatpak_file_get_path_cached (tmp_dir_template), 0755,
|
||
&tmpdir, error))
|
||
return FALSE;
|
||
}
|
||
|
||
checkout_dir = g_file_new_for_path (tmpdir.path);
|
||
|
||
options.mode = OSTREE_REPO_CHECKOUT_MODE_USER;
|
||
options.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES;
|
||
options.enable_fsync = FALSE; /* We checkout to a temp dir and sync before moving it in place */
|
||
options.bareuseronly_dirs = TRUE; /* https://github.com/ostreedev/ostree/pull/927 */
|
||
|
||
if (!ostree_repo_checkout_at (self->repo, &options,
|
||
AT_FDCWD, tmpdir.path, new_checksum,
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
/* Old appstream format don't have uncompressed file, so we uncompress it */
|
||
if (do_uncompress)
|
||
{
|
||
g_autoptr(GFile) appstream_xml = g_file_get_child (checkout_dir, "appstream.xml");
|
||
g_autoptr(GFile) appstream_gz_xml = g_file_get_child (checkout_dir, "appstream.xml.gz");
|
||
g_autoptr(GOutputStream) out2 = NULL;
|
||
g_autoptr(GFileOutputStream) out = NULL;
|
||
g_autoptr(GFileInputStream) in = NULL;
|
||
|
||
in = g_file_read (appstream_gz_xml, NULL, NULL);
|
||
if (in)
|
||
{
|
||
g_autoptr(GZlibDecompressor) decompressor = g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP);
|
||
out = g_file_replace (appstream_xml, NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION,
|
||
NULL, error);
|
||
if (out == NULL)
|
||
return FALSE;
|
||
|
||
out2 = g_converter_output_stream_new (G_OUTPUT_STREAM (out), G_CONVERTER (decompressor));
|
||
if (g_output_stream_splice (out2, G_INPUT_STREAM (in), G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
|
||
NULL, error) < 0)
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
if (deny_refs)
|
||
{
|
||
g_autoptr(GFile) appstream_xml = g_file_get_child (checkout_dir, "appstream.xml");
|
||
g_autoptr(GFileInputStream) in = NULL;
|
||
|
||
/* We need some ref filtering, so parse the xml */
|
||
|
||
in = g_file_read (appstream_xml, NULL, NULL);
|
||
if (in)
|
||
{
|
||
g_autoptr(FlatpakXml) appstream = NULL;
|
||
g_autoptr(GBytes) content = NULL;
|
||
|
||
appstream = flatpak_xml_parse (G_INPUT_STREAM (in), FALSE, cancellable, error);
|
||
if (appstream == NULL)
|
||
return FALSE;
|
||
|
||
flatpak_appstream_xml_filter (appstream, allow_refs, deny_refs);
|
||
|
||
if (!flatpak_appstream_xml_root_to_data (appstream, &content, NULL, error))
|
||
return FALSE;
|
||
|
||
if (!g_file_replace_contents (appstream_xml,
|
||
g_bytes_get_data (content, NULL), g_bytes_get_size (content),
|
||
NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION, NULL,
|
||
cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
do_compress = TRUE; /* We need to recompress this */
|
||
}
|
||
|
||
/* New appstream format don't have compressed file, so we compress it */
|
||
if (do_compress)
|
||
{
|
||
g_autoptr(GFile) appstream_xml = g_file_get_child (checkout_dir, "appstream.xml");
|
||
g_autoptr(GFile) appstream_gz_xml = g_file_get_child (checkout_dir, "appstream.xml.gz");
|
||
g_autoptr(GZlibCompressor) compressor = NULL;
|
||
g_autoptr(GOutputStream) out2 = NULL;
|
||
g_autoptr(GFileOutputStream) out = NULL;
|
||
g_autoptr(GFileInputStream) in = NULL;
|
||
|
||
in = g_file_read (appstream_xml, NULL, NULL);
|
||
if (in)
|
||
{
|
||
compressor = g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP, -1);
|
||
out = g_file_replace (appstream_gz_xml, NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION,
|
||
NULL, error);
|
||
if (out == NULL)
|
||
return FALSE;
|
||
|
||
out2 = g_converter_output_stream_new (G_OUTPUT_STREAM (out), G_CONVERTER (compressor));
|
||
if (g_output_stream_splice (out2, G_INPUT_STREAM (in), G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
|
||
NULL, error) < 0)
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
glnx_gen_temp_name (tmpname);
|
||
active_tmp_link = g_file_get_child (arch_dir, tmpname);
|
||
|
||
if (!g_file_make_symbolic_link (active_tmp_link, new_dir, cancellable, error))
|
||
return FALSE;
|
||
|
||
/* This is a link, not a dir, but it will remove the same way on destroy */
|
||
tmplink = g_object_ref (active_tmp_link);
|
||
|
||
if (syncfs (dfd) != 0)
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
return FALSE;
|
||
}
|
||
|
||
/* By now the checkout to the temporary directory is on disk, as is the temporary
|
||
symlink pointing to the final target. */
|
||
|
||
if (!g_file_move (checkout_dir, real_checkout_dir, G_FILE_COPY_NO_FALLBACK_FOR_MOVE,
|
||
cancellable, NULL, NULL, error))
|
||
return FALSE;
|
||
|
||
/* Don't delete tmpdir now that it's moved */
|
||
glnx_tmpdir_unset (&tmpdir);
|
||
|
||
if (syncfs (dfd) != 0)
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
return FALSE;
|
||
}
|
||
|
||
if (!flatpak_file_rename (active_tmp_link,
|
||
active_link,
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
/* Don't delete tmplink now that it's moved */
|
||
g_object_unref (g_steal_pointer (&tmplink));
|
||
|
||
if (old_dir != NULL &&
|
||
g_strcmp0 (old_dir, new_dir) != 0)
|
||
{
|
||
old_checkout_dir = g_file_get_child (arch_dir, old_dir);
|
||
if (!flatpak_rm_rf (old_checkout_dir, cancellable, &tmp_error))
|
||
g_warning ("Unable to remove old appstream checkout: %s", tmp_error->message);
|
||
}
|
||
|
||
if (!g_file_replace_contents (timestamp_file, "", 0, NULL, FALSE,
|
||
G_FILE_CREATE_REPLACE_DESTINATION, NULL, NULL, error))
|
||
return FALSE;
|
||
|
||
/* If we added a new checkout, touch the toplevel dir to tell people that they need
|
||
to re-scan */
|
||
if (!checkout_exists)
|
||
{
|
||
g_autofree char *appstream_dir_path = g_file_get_path (appstream_dir);
|
||
utime (appstream_dir_path, NULL);
|
||
}
|
||
|
||
/* There used to be an bug here where temporary files where not removed, which could use
|
||
* quite a lot of space over time, so we check for these and remove them. */
|
||
remove_old_appstream_tmpdirs (arch_dir);
|
||
|
||
if (out_changed)
|
||
*out_changed = TRUE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean repo_get_remote_collection_id (OstreeRepo *repo,
|
||
const char *remote_name,
|
||
char **collection_id_out,
|
||
GError **error);
|
||
|
||
|
||
gboolean
|
||
flatpak_dir_find_latest_rev (FlatpakDir *self,
|
||
FlatpakRemoteState *state,
|
||
const char *ref,
|
||
const char *checksum_or_latest,
|
||
char **out_rev,
|
||
guint64 *out_timestamp,
|
||
GFile **out_sideload_path,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autofree char *latest_rev = NULL;
|
||
|
||
g_return_val_if_fail (out_rev != NULL, FALSE);
|
||
|
||
if (!flatpak_remote_state_lookup_ref (state, ref, &latest_rev, out_timestamp, NULL, out_sideload_path, error))
|
||
return FALSE;
|
||
if (latest_rev == NULL)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND,
|
||
_("Couldn't find latest checksum for ref %s in remote %s"),
|
||
ref, state->remote_name);
|
||
|
||
if (out_rev != NULL)
|
||
*out_rev = g_steal_pointer (&latest_rev);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
get_mtime (GFile *file,
|
||
GTimeVal *result,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFileInfo) info = g_file_query_info (file,
|
||
G_FILE_ATTRIBUTE_TIME_MODIFIED,
|
||
G_FILE_QUERY_INFO_NONE,
|
||
cancellable, error);
|
||
if (info)
|
||
{
|
||
g_file_info_get_modification_time (info, result);
|
||
return TRUE;
|
||
}
|
||
else
|
||
{
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
static gboolean
|
||
check_destination_mtime (GFile *src,
|
||
GFile *dest,
|
||
GCancellable *cancellable)
|
||
{
|
||
GTimeVal src_mtime;
|
||
GTimeVal dest_mtime;
|
||
|
||
return get_mtime (src, &src_mtime, cancellable, NULL) &&
|
||
get_mtime (dest, &dest_mtime, cancellable, NULL) &&
|
||
(src_mtime.tv_sec < dest_mtime.tv_sec ||
|
||
(src_mtime.tv_sec == dest_mtime.tv_sec && src_mtime.tv_usec < dest_mtime.tv_usec));
|
||
}
|
||
|
||
static GFile *
|
||
flatpak_dir_update_oci_index (FlatpakDir *self,
|
||
const char *remote,
|
||
char **index_uri_out,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GError) local_error = NULL;
|
||
g_autoptr(GFile) index_cache = NULL;
|
||
g_autofree char *oci_uri = NULL;
|
||
|
||
index_cache = flatpak_dir_get_oci_index_location (self, remote, error);
|
||
if (index_cache == NULL)
|
||
return NULL;
|
||
|
||
ensure_http_session (self);
|
||
|
||
if (!ostree_repo_remote_get_url (self->repo,
|
||
remote,
|
||
&oci_uri,
|
||
error))
|
||
return NULL;
|
||
|
||
if (!flatpak_oci_index_ensure_cached (self->http_session, oci_uri,
|
||
index_cache, index_uri_out,
|
||
cancellable, &local_error))
|
||
{
|
||
if (!g_error_matches (local_error, FLATPAK_HTTP_ERROR, FLATPAK_HTTP_ERROR_NOT_CHANGED))
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return NULL;
|
||
}
|
||
|
||
g_clear_error (&local_error);
|
||
}
|
||
|
||
return g_steal_pointer (&index_cache);
|
||
}
|
||
|
||
static gboolean
|
||
replace_contents_compressed (GFile *dest,
|
||
GBytes *contents,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GZlibCompressor) compressor = NULL;
|
||
g_autoptr(GFileOutputStream) out = NULL;
|
||
g_autoptr(GOutputStream) out2 = NULL;
|
||
|
||
compressor = g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP, -1);
|
||
out = g_file_replace (dest, NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION,
|
||
NULL, error);
|
||
out2 = g_converter_output_stream_new (G_OUTPUT_STREAM (out), G_CONVERTER (compressor));
|
||
if (out == NULL)
|
||
return FALSE;
|
||
|
||
if (!g_output_stream_write_all (out2,
|
||
g_bytes_get_data (contents, NULL),
|
||
g_bytes_get_size (contents),
|
||
NULL,
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
if (!g_output_stream_close (out2, cancellable, error))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_update_appstream_oci (FlatpakDir *self,
|
||
const char *remote,
|
||
const char *arch,
|
||
gboolean *out_changed,
|
||
FlatpakProgress *progress,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) arch_dir = NULL;
|
||
g_autoptr(GFile) lock_file = NULL;
|
||
g_auto(GLnxLockFile) lock = { 0, };
|
||
g_autoptr(GFile) index_cache = NULL;
|
||
g_autofree char *index_uri = NULL;
|
||
g_autoptr(GFile) timestamp_file = NULL;
|
||
g_autoptr(GFile) icons_dir = NULL;
|
||
glnx_autofd int icons_dfd = -1;
|
||
g_autoptr(GBytes) appstream = NULL;
|
||
g_autoptr(GFile) new_appstream_file = NULL;
|
||
|
||
arch_dir = flatpak_build_file (flatpak_dir_get_path (self),
|
||
"appstream", remote, arch, NULL);
|
||
if (g_mkdir_with_parents (flatpak_file_get_path_cached (arch_dir), 0755) != 0)
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
return FALSE;
|
||
}
|
||
|
||
lock_file = g_file_get_child (arch_dir, "lock");
|
||
if (!glnx_make_lock_file (AT_FDCWD, flatpak_file_get_path_cached (lock_file),
|
||
LOCK_EX, &lock, error))
|
||
return FALSE;
|
||
|
||
index_cache = flatpak_dir_update_oci_index (self, remote, &index_uri, cancellable, error);
|
||
if (index_cache == NULL)
|
||
return FALSE;
|
||
|
||
timestamp_file = g_file_get_child (arch_dir, ".timestamp");
|
||
if (check_destination_mtime (index_cache, timestamp_file, cancellable))
|
||
return TRUE;
|
||
|
||
icons_dir = g_file_get_child (arch_dir, "icons");
|
||
if (g_mkdir_with_parents (flatpak_file_get_path_cached (icons_dir), 0755) != 0)
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
return FALSE;
|
||
}
|
||
|
||
if (!glnx_opendirat (AT_FDCWD, flatpak_file_get_path_cached (icons_dir),
|
||
FALSE, &icons_dfd, error))
|
||
return FALSE;
|
||
|
||
ensure_http_session (self);
|
||
|
||
appstream = flatpak_oci_index_make_appstream (self->http_session,
|
||
index_cache,
|
||
index_uri,
|
||
arch,
|
||
icons_dfd,
|
||
cancellable,
|
||
error);
|
||
if (appstream == NULL)
|
||
return FALSE;
|
||
|
||
new_appstream_file = g_file_get_child (arch_dir, "appstream.xml.gz");
|
||
if (!replace_contents_compressed (new_appstream_file, appstream, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (!g_file_replace_contents (timestamp_file, "", 0, NULL, FALSE,
|
||
G_FILE_CREATE_REPLACE_DESTINATION, NULL, NULL, error))
|
||
return FALSE;
|
||
|
||
if (out_changed)
|
||
*out_changed = TRUE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_update_appstream (FlatpakDir *self,
|
||
const char *remote,
|
||
const char *arch,
|
||
gboolean *out_changed,
|
||
FlatpakProgress *progress,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autofree char *new_branch = NULL;
|
||
g_autofree char *old_branch = NULL;
|
||
const char *used_branch = NULL;
|
||
g_autofree char *new_checksum = NULL;
|
||
g_autoptr(GError) first_error = NULL;
|
||
g_autoptr(GError) second_error = NULL;
|
||
g_autoptr(FlatpakRemoteState) state = NULL;
|
||
g_autofree char *appstream_commit = NULL;
|
||
g_autofree char *subset = NULL;
|
||
g_autoptr(GFile) appstream_sideload_path = NULL;
|
||
const char *installation;
|
||
gboolean is_oci;
|
||
|
||
if (out_changed)
|
||
*out_changed = FALSE;
|
||
|
||
if (arch == NULL)
|
||
arch = flatpak_get_arch ();
|
||
|
||
subset = flatpak_dir_get_remote_subset (self, remote);
|
||
|
||
if (subset)
|
||
{
|
||
new_branch = g_strdup_printf ("appstream2/%s-%s", subset, arch);
|
||
old_branch = g_strdup_printf ("appstream/%s-%s", subset, arch);
|
||
}
|
||
else
|
||
{
|
||
new_branch = g_strdup_printf ("appstream2/%s", arch);
|
||
old_branch = g_strdup_printf ("appstream/%s", arch);
|
||
}
|
||
|
||
is_oci = flatpak_dir_get_remote_oci (self, remote);
|
||
|
||
state = flatpak_dir_get_remote_state_optional (self, remote, FALSE, cancellable, error);
|
||
if (state == NULL)
|
||
return FALSE;
|
||
|
||
used_branch = new_branch;
|
||
if (!is_oci)
|
||
{
|
||
if (!flatpak_dir_find_latest_rev (self, state, used_branch, NULL, &appstream_commit, NULL, &appstream_sideload_path, cancellable, &first_error))
|
||
{
|
||
used_branch = old_branch;
|
||
if (!flatpak_dir_find_latest_rev (self, state, used_branch, NULL, &appstream_commit, NULL, &appstream_sideload_path, cancellable, &second_error))
|
||
{
|
||
g_prefix_error (&first_error, "Error updating appstream2: ");
|
||
g_prefix_error (&second_error, "Error updating appstream: ");
|
||
g_propagate_prefixed_error (error, g_steal_pointer (&second_error), "%s; ", first_error->message);
|
||
return FALSE;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (flatpak_dir_use_system_helper (self, NULL))
|
||
{
|
||
g_auto(GLnxLockFile) child_repo_lock = { 0, };
|
||
g_autofree char *url = NULL;
|
||
g_autoptr(GFile) child_repo_file = NULL;
|
||
g_autofree char *child_repo_path = NULL;
|
||
gboolean gpg_verify_summary;
|
||
gboolean gpg_verify;
|
||
|
||
if (!ostree_repo_remote_get_url (self->repo,
|
||
state->remote_name,
|
||
&url,
|
||
error))
|
||
return FALSE;
|
||
|
||
if (!ostree_repo_remote_get_gpg_verify_summary (self->repo, state->remote_name,
|
||
&gpg_verify_summary, error))
|
||
return FALSE;
|
||
|
||
if (!ostree_repo_remote_get_gpg_verify (self->repo, state->remote_name,
|
||
&gpg_verify, error))
|
||
return FALSE;
|
||
|
||
if (is_oci)
|
||
{
|
||
/* In the OCI case, we just ask the system helper do the network i/o, since
|
||
* there is no way to verify the index validity without actually downloading it.
|
||
* While we try to avoid network i/o as root, there's no hard line where doing
|
||
* network i/o as root is much worse than parsing the results of network i/o
|
||
* as root. A trusted, but unprivileged helper could be used to do the download
|
||
* if necessary.
|
||
*/
|
||
}
|
||
else if (!gpg_verify_summary || !gpg_verify)
|
||
{
|
||
/* The remote is not gpg verified, so we don't want to allow installation via
|
||
a download in the home directory, as there is no way to verify you're not
|
||
injecting anything into the remote. However, in the case of a remote
|
||
configured to a local filesystem we can just let the system helper do
|
||
the installation, as it can then avoid network i/o and be certain the
|
||
data comes from the right place. */
|
||
if (!g_str_has_prefix (url, "file:"))
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_UNTRUSTED, _("Can't pull from untrusted non-gpg verified remote"));
|
||
}
|
||
else
|
||
{
|
||
g_autoptr(OstreeRepo) child_repo = flatpak_dir_create_system_child_repo (self, &child_repo_lock, NULL, error);
|
||
if (child_repo == NULL)
|
||
return FALSE;
|
||
|
||
if (!flatpak_dir_pull (self, state, used_branch, appstream_commit, NULL, appstream_sideload_path, NULL, NULL,
|
||
child_repo, FLATPAK_PULL_FLAGS_NONE, 0,
|
||
progress, cancellable, error))
|
||
{
|
||
g_prefix_error (&first_error, "Error updating appstream: ");
|
||
return FALSE;
|
||
}
|
||
|
||
if (!flatpak_repo_resolve_rev (child_repo, NULL, remote, used_branch, TRUE,
|
||
&new_checksum, cancellable, error))
|
||
return FALSE;
|
||
|
||
child_repo_file = g_object_ref (ostree_repo_get_path (child_repo));
|
||
}
|
||
|
||
if (child_repo_file)
|
||
child_repo_path = g_file_get_path (child_repo_file);
|
||
|
||
installation = flatpak_dir_get_id (self);
|
||
|
||
if (!flatpak_dir_system_helper_call_deploy_appstream (self,
|
||
child_repo_path ? child_repo_path : "",
|
||
FLATPAK_HELPER_DEPLOY_APPSTREAM_FLAGS_NONE,
|
||
remote,
|
||
arch,
|
||
installation ? installation : "",
|
||
cancellable,
|
||
error))
|
||
return FALSE;
|
||
|
||
if (child_repo_file)
|
||
(void) flatpak_rm_rf (child_repo_file, NULL, NULL);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
if (is_oci)
|
||
{
|
||
return flatpak_dir_update_appstream_oci (self, remote, arch,
|
||
out_changed, progress, cancellable,
|
||
error);
|
||
}
|
||
|
||
|
||
if (!flatpak_dir_pull (self, state, used_branch, appstream_commit, NULL, appstream_sideload_path, NULL, NULL, NULL,
|
||
FLATPAK_PULL_FLAGS_NONE, OSTREE_REPO_PULL_FLAGS_NONE, progress,
|
||
cancellable, error))
|
||
{
|
||
g_prefix_error (&first_error, "Error updating appstream: ");
|
||
return FALSE;
|
||
}
|
||
|
||
if (!flatpak_repo_resolve_rev (self->repo, NULL, remote, used_branch, TRUE,
|
||
&new_checksum, cancellable, error))
|
||
return FALSE;
|
||
|
||
return flatpak_dir_deploy_appstream (self,
|
||
remote,
|
||
arch,
|
||
out_changed,
|
||
cancellable,
|
||
error);
|
||
}
|
||
|
||
/* Get the configured collection-id for @remote_name, squashing empty strings into
|
||
* %NULL. Return %TRUE if the ID was fetched successfully, or if it was unset or
|
||
* empty. */
|
||
static gboolean
|
||
repo_get_remote_collection_id (OstreeRepo *repo,
|
||
const char *remote_name,
|
||
char **collection_id_out,
|
||
GError **error)
|
||
{
|
||
if (collection_id_out != NULL)
|
||
{
|
||
if (!ostree_repo_get_remote_option (repo, remote_name, "collection-id",
|
||
NULL, collection_id_out, error))
|
||
return FALSE;
|
||
if (*collection_id_out != NULL && **collection_id_out == '\0')
|
||
g_clear_pointer (collection_id_out, g_free);
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* Get options for the OSTree pull operation which can be shared between
|
||
* collection-based and normal pulls. Update @builder in place. */
|
||
static void
|
||
get_common_pull_options (GVariantBuilder *builder,
|
||
FlatpakRemoteState *state,
|
||
const char *ref_to_fetch,
|
||
const char *token,
|
||
const gchar * const *dirs_to_pull,
|
||
const char *current_local_checksum,
|
||
gboolean force_disable_deltas,
|
||
OstreeRepoPullFlags flags,
|
||
FlatpakProgress *progress)
|
||
{
|
||
guint32 update_interval = 0;
|
||
GVariantBuilder hdr_builder;
|
||
|
||
if (state->summary_bytes && state->summary_sig_bytes)
|
||
{
|
||
g_variant_builder_add (builder, "{s@v}", "summary-bytes",
|
||
g_variant_new_variant (g_variant_new_from_bytes (G_VARIANT_TYPE ("ay"), state->summary_bytes, TRUE)));
|
||
g_variant_builder_add (builder, "{s@v}", "summary-sig-bytes",
|
||
g_variant_new_variant (g_variant_new_from_bytes (G_VARIANT_TYPE ("ay"), state->summary_sig_bytes, TRUE)));
|
||
}
|
||
|
||
if (dirs_to_pull)
|
||
{
|
||
g_variant_builder_add (builder, "{s@v}", "subdirs",
|
||
g_variant_new_variant (g_variant_new_strv ((const char * const *) dirs_to_pull, -1)));
|
||
force_disable_deltas = TRUE;
|
||
}
|
||
|
||
if (force_disable_deltas)
|
||
{
|
||
g_variant_builder_add (builder, "{s@v}", "disable-static-deltas",
|
||
g_variant_new_variant (g_variant_new_boolean (TRUE)));
|
||
}
|
||
|
||
g_variant_builder_add (builder, "{s@v}", "inherit-transaction",
|
||
g_variant_new_variant (g_variant_new_boolean (TRUE)));
|
||
|
||
g_variant_builder_add (builder, "{s@v}", "flags",
|
||
g_variant_new_variant (g_variant_new_int32 (flags)));
|
||
|
||
|
||
g_variant_builder_init (&hdr_builder, G_VARIANT_TYPE ("a(ss)"));
|
||
g_variant_builder_add (&hdr_builder, "(ss)", "Flatpak-Ref", ref_to_fetch);
|
||
if (token)
|
||
{
|
||
g_autofree char *bearer_token = g_strdup_printf ("Bearer %s", token);
|
||
g_variant_builder_add (&hdr_builder, "(ss)", "Authorization", bearer_token);
|
||
}
|
||
if (current_local_checksum)
|
||
g_variant_builder_add (&hdr_builder, "(ss)", "Flatpak-Upgrade-From", current_local_checksum);
|
||
g_variant_builder_add (builder, "{s@v}", "http-headers",
|
||
g_variant_new_variant (g_variant_builder_end (&hdr_builder)));
|
||
g_variant_builder_add (builder, "{s@v}", "append-user-agent",
|
||
g_variant_new_variant (g_variant_new_string ("flatpak/" PACKAGE_VERSION)));
|
||
|
||
update_interval = flatpak_progress_get_update_interval (progress);
|
||
|
||
g_variant_builder_add (builder, "{s@v}", "update-frequency",
|
||
g_variant_new_variant (g_variant_new_uint32 (update_interval)));
|
||
}
|
||
|
||
static gboolean
|
||
translate_ostree_repo_pull_errors (GError **error)
|
||
{
|
||
if (*error)
|
||
{
|
||
if (strstr ((*error)->message, "min-free-space-size") ||
|
||
strstr ((*error)->message, "min-free-space-percent"))
|
||
{
|
||
(*error)->domain = FLATPAK_ERROR;
|
||
(*error)->code = FLATPAK_ERROR_OUT_OF_SPACE;
|
||
}
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static gboolean
|
||
repo_pull (OstreeRepo *self,
|
||
FlatpakRemoteState *state,
|
||
const char **dirs_to_pull,
|
||
const char *ref_to_fetch,
|
||
const char *rev_to_fetch,
|
||
GFile *sideload_repo,
|
||
const char *token,
|
||
FlatpakPullFlags flatpak_flags,
|
||
OstreeRepoPullFlags flags,
|
||
FlatpakProgress *progress,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
gboolean force_disable_deltas = (flatpak_flags & FLATPAK_PULL_FLAGS_NO_STATIC_DELTAS) != 0;
|
||
g_autofree char *current_checksum = NULL;
|
||
g_autoptr(GVariant) old_commit = NULL;
|
||
g_autoptr(GVariant) new_commit = NULL;
|
||
const char *revs_to_fetch[2];
|
||
g_autoptr(GError) dummy_error = NULL;
|
||
GVariantBuilder builder;
|
||
g_autoptr(GVariant) options = NULL;
|
||
const char *refs_to_fetch[2];
|
||
g_autofree char *sideload_url = NULL;
|
||
|
||
g_return_val_if_fail (ref_to_fetch != NULL, FALSE);
|
||
g_return_val_if_fail (rev_to_fetch != NULL, FALSE);
|
||
|
||
/* The ostree fetcher asserts if error is NULL */
|
||
if (error == NULL)
|
||
error = &dummy_error;
|
||
|
||
/* We always want this on for every type of pull */
|
||
flags |= OSTREE_REPO_PULL_FLAGS_BAREUSERONLY_FILES;
|
||
|
||
if (!flatpak_repo_resolve_rev (self, NULL, state->remote_name, ref_to_fetch, TRUE,
|
||
¤t_checksum, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (current_checksum != NULL &&
|
||
!ostree_repo_load_commit (self, current_checksum, &old_commit, NULL, error))
|
||
return FALSE;
|
||
|
||
/* Pull options */
|
||
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
|
||
get_common_pull_options (&builder, state, ref_to_fetch, token, dirs_to_pull, current_checksum,
|
||
force_disable_deltas, flags, progress);
|
||
|
||
if (sideload_repo)
|
||
{
|
||
GVariantBuilder colref_builder;
|
||
|
||
sideload_url = g_file_get_uri (sideload_repo);
|
||
|
||
g_info ("Sideloading %s from %s in pull", ref_to_fetch, sideload_url);
|
||
|
||
g_assert (state->collection_id != NULL);
|
||
|
||
g_variant_builder_init (&colref_builder, G_VARIANT_TYPE ("a(sss)"));
|
||
g_variant_builder_add (&colref_builder, "(sss)", state->collection_id, ref_to_fetch, rev_to_fetch);
|
||
|
||
g_variant_builder_add (&builder, "{s@v}", "collection-refs",
|
||
g_variant_new_variant (g_variant_builder_end (&colref_builder)));
|
||
g_variant_builder_add (&builder, "{s@v}", "override-remote-name",
|
||
g_variant_new_variant (g_variant_new_string (state->remote_name)));
|
||
}
|
||
else
|
||
{
|
||
refs_to_fetch[0] = ref_to_fetch;
|
||
refs_to_fetch[1] = NULL;
|
||
g_variant_builder_add (&builder, "{s@v}", "refs",
|
||
g_variant_new_variant (g_variant_new_strv ((const char * const *) refs_to_fetch, -1)));
|
||
|
||
revs_to_fetch[0] = rev_to_fetch;
|
||
revs_to_fetch[1] = NULL;
|
||
g_variant_builder_add (&builder, "{s@v}", "override-commit-ids",
|
||
g_variant_new_variant (g_variant_new_strv ((const char * const *) revs_to_fetch, -1)));
|
||
|
||
|
||
if (state->sideload_repos->len > 0)
|
||
{
|
||
GVariantBuilder localcache_repos_builder;
|
||
|
||
g_variant_builder_init (&localcache_repos_builder, G_VARIANT_TYPE ("as"));
|
||
for (int i = 0; i < state->sideload_repos->len; i++)
|
||
{
|
||
FlatpakSideloadState *ss = g_ptr_array_index (state->sideload_repos, i);
|
||
GFile *sideload_path = ostree_repo_get_path (ss->repo);
|
||
|
||
g_variant_builder_add (&localcache_repos_builder, "s",
|
||
flatpak_file_get_path_cached (sideload_path));
|
||
}
|
||
g_variant_builder_add (&builder, "{s@v}", "localcache-repos",
|
||
g_variant_new_variant (g_variant_builder_end (&localcache_repos_builder)));
|
||
}
|
||
}
|
||
|
||
options = g_variant_ref_sink (g_variant_builder_end (&builder));
|
||
|
||
{
|
||
g_auto(FlatpakMainContext) context = FLATKPAK_MAIN_CONTEXT_INIT;
|
||
flatpak_progress_init_main_context (progress, &context);
|
||
|
||
if (!ostree_repo_pull_with_options (self,
|
||
sideload_url ? sideload_url : state->remote_name,
|
||
options, context.ostree_progress, cancellable, error))
|
||
return translate_ostree_repo_pull_errors (error);
|
||
}
|
||
|
||
if (old_commit &&
|
||
(flatpak_flags & FLATPAK_PULL_FLAGS_ALLOW_DOWNGRADE) == 0)
|
||
{
|
||
guint64 old_timestamp;
|
||
guint64 new_timestamp;
|
||
|
||
if (!ostree_repo_load_commit (self, rev_to_fetch, &new_commit, NULL, error))
|
||
return FALSE;
|
||
|
||
old_timestamp = ostree_commit_get_timestamp (old_commit);
|
||
new_timestamp = ostree_commit_get_timestamp (new_commit);
|
||
|
||
if (new_timestamp < old_timestamp)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_DOWNGRADE, "Update is older than current version");
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static void
|
||
ensure_http_session (FlatpakDir *self)
|
||
{
|
||
if (g_once_init_enter (&self->http_session))
|
||
{
|
||
FlatpakHttpSession *http_session;
|
||
|
||
http_session = flatpak_create_http_session (PACKAGE_STRING);
|
||
|
||
g_once_init_leave (&self->http_session, http_session);
|
||
}
|
||
}
|
||
|
||
static void
|
||
extra_data_progress_report (guint64 downloaded_bytes,
|
||
gpointer user_data)
|
||
{
|
||
FlatpakProgress *progress = FLATPAK_PROGRESS (user_data);
|
||
|
||
flatpak_progress_update_extra_data (progress, downloaded_bytes);
|
||
}
|
||
|
||
static void
|
||
compute_extra_data_download_size (GVariant *commitv,
|
||
guint64 *out_n_extra_data,
|
||
guint64 *out_total_download_size)
|
||
{
|
||
guint64 i;
|
||
guint64 n_extra_data = 0;
|
||
guint64 total_download_size = 0;
|
||
g_autoptr(GVariant) extra_data_sources = NULL;
|
||
|
||
extra_data_sources = flatpak_commit_get_extra_data_sources (commitv, NULL);
|
||
if (extra_data_sources != NULL)
|
||
{
|
||
n_extra_data = g_variant_n_children (extra_data_sources);
|
||
for (i = 0; i < n_extra_data; i++)
|
||
{
|
||
guint64 download_size;
|
||
flatpak_repo_parse_extra_data_sources (extra_data_sources, i,
|
||
NULL,
|
||
&download_size,
|
||
NULL,
|
||
NULL,
|
||
NULL);
|
||
total_download_size += download_size;
|
||
}
|
||
}
|
||
|
||
*out_n_extra_data = n_extra_data;
|
||
*out_total_download_size = total_download_size;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_setup_extra_data (FlatpakDir *self,
|
||
FlatpakRemoteState *state,
|
||
OstreeRepo *repo,
|
||
const char *ref,
|
||
const char *rev,
|
||
GFile *sideload_repo,
|
||
const char *token,
|
||
FlatpakPullFlags flatpak_flags,
|
||
FlatpakProgress *progress,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
guint64 n_extra_data = 0;
|
||
guint64 total_download_size = 0;
|
||
|
||
/* ostree-metadata and appstreams never have extra data, so ignore those */
|
||
if (g_str_has_prefix (ref, "app/") || g_str_has_prefix (ref, "runtime/"))
|
||
{
|
||
g_autofree char *summary_checksum = NULL;
|
||
GVariant *summary;
|
||
|
||
/* Version 1 added extra data details, so we can rely on it
|
||
* either being in the sparse cache or no extra data. However,
|
||
* it only applies to the commit the summary contains, so verify
|
||
* that too.
|
||
*/
|
||
summary = get_summary_for_ref (state, ref);
|
||
if (summary != NULL &&
|
||
flatpak_summary_lookup_ref (summary, NULL, ref, &summary_checksum, NULL) &&
|
||
g_strcmp0 (rev, summary_checksum) == 0 &&
|
||
flatpak_remote_state_get_cache_version (state) >= 1)
|
||
{
|
||
VarMetadataRef metadata;
|
||
VarVariantRef res;
|
||
|
||
if (flatpak_remote_state_lookup_sparse_cache (state, ref, &metadata, NULL) &&
|
||
var_metadata_lookup (metadata, FLATPAK_SPARSE_CACHE_KEY_EXTRA_DATA_SIZE, NULL, &res) &&
|
||
var_variant_is_type (res, VAR_EXTRA_DATA_SIZE_TYPEFORMAT))
|
||
{
|
||
VarExtraDataSizeRef eds = var_extra_data_size_from_variant (res);
|
||
n_extra_data = var_extra_data_size_get_n_extra_data (eds);
|
||
total_download_size = var_extra_data_size_get_total_size (eds);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/* No summary/cache or old cache version, download commit and get size from there */
|
||
g_autoptr(GVariant) commitv = flatpak_remote_state_load_ref_commit (state, self, ref, rev, token, NULL, cancellable, error);
|
||
if (commitv == NULL)
|
||
return FALSE;
|
||
|
||
compute_extra_data_download_size (commitv, &n_extra_data, &total_download_size);
|
||
}
|
||
}
|
||
|
||
if (n_extra_data > 0 &&
|
||
(flatpak_flags & FLATPAK_PULL_FLAGS_DOWNLOAD_EXTRA_DATA) == 0)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_UNTRUSTED, _("Extra data not supported for non-gpg-verified local system installs"));
|
||
|
||
flatpak_progress_init_extra_data (progress, n_extra_data, total_download_size);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_pull_extra_data (FlatpakDir *self,
|
||
OstreeRepo *repo,
|
||
const char *repository,
|
||
const char *ref,
|
||
const char *rev,
|
||
FlatpakPullFlags flatpak_flags,
|
||
FlatpakProgress *progress,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GVariant) extra_data_sources = NULL;
|
||
g_autoptr(GVariant) detached_metadata = NULL;
|
||
g_auto(GVariantDict) new_metadata_dict = FLATPAK_VARIANT_DICT_INITIALIZER;
|
||
g_autoptr(GVariantBuilder) extra_data_builder = NULL;
|
||
g_autoptr(GVariant) new_detached_metadata = NULL;
|
||
g_autoptr(GVariant) extra_data = NULL;
|
||
g_autoptr(GFile) base_dir = NULL;
|
||
int i;
|
||
gsize n_extra_data;
|
||
|
||
extra_data_sources = flatpak_repo_get_extra_data_sources (repo, rev, cancellable, NULL);
|
||
if (extra_data_sources == NULL)
|
||
return TRUE;
|
||
|
||
n_extra_data = g_variant_n_children (extra_data_sources);
|
||
if (n_extra_data == 0)
|
||
return TRUE;
|
||
|
||
if ((flatpak_flags & FLATPAK_PULL_FLAGS_DOWNLOAD_EXTRA_DATA) == 0)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_UNTRUSTED, _("Extra data not supported for non-gpg-verified local system installs"));
|
||
|
||
extra_data_builder = g_variant_builder_new (G_VARIANT_TYPE ("a(ayay)"));
|
||
|
||
/* Other fields were already set in flatpak_dir_setup_extra_data() */
|
||
flatpak_progress_start_extra_data (progress);
|
||
|
||
base_dir = flatpak_get_user_base_dir_location ();
|
||
|
||
for (i = 0; i < n_extra_data; i++)
|
||
{
|
||
const char *extra_data_uri = NULL;
|
||
g_autofree char *extra_data_sha256 = NULL;
|
||
const char *extra_data_name = NULL;
|
||
guint64 download_size;
|
||
guint64 installed_size;
|
||
g_autofree char *sha256 = NULL;
|
||
const guchar *sha256_bytes;
|
||
g_autoptr(GBytes) bytes = NULL;
|
||
g_autoptr(GFile) extra_local_file = NULL;
|
||
|
||
flatpak_repo_parse_extra_data_sources (extra_data_sources, i,
|
||
&extra_data_name,
|
||
&download_size,
|
||
&installed_size,
|
||
&sha256_bytes,
|
||
&extra_data_uri);
|
||
|
||
if (sha256_bytes == NULL)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid checksum for extra data uri %s"), extra_data_uri);
|
||
|
||
extra_data_sha256 = ostree_checksum_from_bytes (sha256_bytes);
|
||
|
||
if (*extra_data_name == 0)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Empty name for extra data uri %s"), extra_data_uri);
|
||
|
||
/* Don't allow file uris here as that could read local files based on remote data */
|
||
if (!g_str_has_prefix (extra_data_uri, "http:") &&
|
||
!g_str_has_prefix (extra_data_uri, "https:"))
|
||
{
|
||
flatpak_progress_reset_extra_data (progress);
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Unsupported extra data uri %s"), extra_data_uri);
|
||
}
|
||
|
||
/* TODO: Download to disk to support resumed downloads on error */
|
||
|
||
extra_local_file = flatpak_build_file (base_dir, "extra-data", extra_data_sha256, extra_data_name, NULL);
|
||
if (g_file_query_exists (extra_local_file, cancellable))
|
||
{
|
||
g_info ("Loading extra-data from local file %s", flatpak_file_get_path_cached (extra_local_file));
|
||
gsize extra_local_size;
|
||
g_autofree char *extra_local_contents = NULL;
|
||
g_autoptr(GError) my_error = NULL;
|
||
|
||
if (!g_file_load_contents (extra_local_file, cancellable, &extra_local_contents, &extra_local_size, NULL, &my_error))
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Failed to load local extra-data %s: %s"),
|
||
flatpak_file_get_path_cached (extra_local_file), my_error->message);
|
||
if (extra_local_size != download_size)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Wrong size for extra-data %s"), flatpak_file_get_path_cached (extra_local_file));
|
||
|
||
bytes = g_bytes_new (extra_local_contents, extra_local_size);
|
||
}
|
||
else
|
||
{
|
||
ensure_http_session (self);
|
||
bytes = flatpak_load_uri (self->http_session, extra_data_uri, 0, NULL,
|
||
extra_data_progress_report, progress, NULL,
|
||
cancellable, error);
|
||
}
|
||
|
||
if (bytes == NULL)
|
||
{
|
||
flatpak_progress_reset_extra_data (progress);
|
||
g_prefix_error (error, _("While downloading %s: "), extra_data_uri);
|
||
return FALSE;
|
||
}
|
||
|
||
if (g_bytes_get_size (bytes) != download_size)
|
||
{
|
||
flatpak_progress_reset_extra_data (progress);
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Wrong size for extra data %s"), extra_data_uri);
|
||
}
|
||
|
||
flatpak_progress_complete_extra_data_download (progress, download_size);
|
||
|
||
sha256 = g_compute_checksum_for_bytes (G_CHECKSUM_SHA256, bytes);
|
||
if (strcmp (sha256, extra_data_sha256) != 0)
|
||
{
|
||
flatpak_progress_reset_extra_data (progress);
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid checksum for extra data %s"), extra_data_uri);
|
||
}
|
||
|
||
g_variant_builder_add (extra_data_builder,
|
||
"(^ay@ay)",
|
||
extra_data_name,
|
||
g_variant_new_from_bytes (G_VARIANT_TYPE ("ay"), bytes, TRUE));
|
||
}
|
||
|
||
extra_data = g_variant_ref_sink (g_variant_builder_end (extra_data_builder));
|
||
|
||
flatpak_progress_reset_extra_data (progress);
|
||
|
||
if (!ostree_repo_read_commit_detached_metadata (repo, rev, &detached_metadata,
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
g_variant_dict_init (&new_metadata_dict, detached_metadata);
|
||
g_variant_dict_insert_value (&new_metadata_dict, "xa.extra-data", extra_data);
|
||
new_detached_metadata = g_variant_ref_sink (g_variant_dict_end (&new_metadata_dict));
|
||
|
||
/* There is a commitmeta size limit when pulling, so we have to side-load it
|
||
when installing in the system repo */
|
||
if (flatpak_flags & FLATPAK_PULL_FLAGS_SIDELOAD_EXTRA_DATA)
|
||
{
|
||
int dfd = ostree_repo_get_dfd (repo);
|
||
g_autoptr(GVariant) normalized = g_variant_get_normal_form (new_detached_metadata);
|
||
gsize normalized_size = g_variant_get_size (normalized);
|
||
const guint8 *data = g_variant_get_data (normalized);
|
||
g_autofree char *filename = NULL;
|
||
|
||
filename = g_strconcat (rev, ".commitmeta", NULL);
|
||
if (!glnx_file_replace_contents_at (dfd, filename,
|
||
data, normalized_size,
|
||
0, cancellable, error))
|
||
{
|
||
g_prefix_error (error, "Unable to write sideloaded detached metadata: ");
|
||
return FALSE;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (!ostree_repo_write_commit_detached_metadata (repo, rev, new_detached_metadata,
|
||
cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static void
|
||
oci_pull_progress_cb (guint64 total_size, guint64 pulled_size,
|
||
guint32 n_layers, guint32 pulled_layers,
|
||
gpointer data)
|
||
{
|
||
FlatpakProgress *progress = data;
|
||
|
||
flatpak_progress_update_oci_pull (progress, total_size, pulled_size, n_layers, pulled_layers);
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_mirror_oci (FlatpakDir *self,
|
||
FlatpakOciRegistry *dst_registry,
|
||
FlatpakRemoteState *state,
|
||
const char *ref,
|
||
const char *opt_rev,
|
||
const char *skip_if_current_is,
|
||
const char *token,
|
||
FlatpakProgress *progress,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(FlatpakOciRegistry) registry = NULL;
|
||
g_autofree char *oci_digest = NULL;
|
||
g_autofree char *latest_rev = NULL;
|
||
VarRefInfoRef latest_rev_info;
|
||
VarMetadataRef metadata;
|
||
const char *oci_repository = NULL;
|
||
const char *delta_url = NULL;
|
||
const char *rev;
|
||
gboolean res;
|
||
|
||
/* We use the summary so that we can reuse any cached json */
|
||
if (!flatpak_remote_state_lookup_ref (state, ref, &latest_rev, NULL, &latest_rev_info, NULL, error))
|
||
return FALSE;
|
||
if (latest_rev == NULL)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND,
|
||
_("Couldn't find latest checksum for ref %s in remote %s"),
|
||
ref, state->remote_name);
|
||
|
||
rev = opt_rev != NULL ? opt_rev : latest_rev;
|
||
|
||
if (skip_if_current_is != NULL && strcmp (rev, skip_if_current_is) == 0)
|
||
{
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_ALREADY_INSTALLED,
|
||
_("%s commit %s already installed"),
|
||
ref, rev);
|
||
}
|
||
|
||
metadata = var_ref_info_get_metadata (latest_rev_info);
|
||
oci_repository = var_metadata_lookup_string (metadata, "xa.oci-repository", NULL);
|
||
delta_url = var_metadata_lookup_string (metadata, "xa.delta-url", NULL);
|
||
|
||
oci_digest = g_strconcat ("sha256:", rev, NULL);
|
||
|
||
registry = flatpak_remote_state_new_oci_registry (state, token, cancellable, error);
|
||
if (registry == NULL)
|
||
return FALSE;
|
||
|
||
flatpak_progress_start_oci_pull (progress);
|
||
|
||
g_info ("Mirroring OCI image %s", oci_digest);
|
||
|
||
res = flatpak_mirror_image_from_oci (dst_registry, registry, oci_repository, oci_digest, state->remote_name, ref, delta_url, self->repo, oci_pull_progress_cb,
|
||
progress, cancellable, error);
|
||
|
||
if (!res)
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_pull_oci (FlatpakDir *self,
|
||
FlatpakRemoteState *state,
|
||
const char *ref,
|
||
const char *opt_rev,
|
||
OstreeRepo *repo,
|
||
FlatpakPullFlags flatpak_flags,
|
||
OstreeRepoPullFlags flags,
|
||
const char *token,
|
||
FlatpakProgress *progress,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(FlatpakOciRegistry) registry = NULL;
|
||
g_autoptr(FlatpakOciVersioned) versioned = NULL;
|
||
g_autoptr(FlatpakOciImage) image_config = NULL;
|
||
const char *oci_repository = NULL;
|
||
const char *delta_url = NULL;
|
||
g_autofree char *oci_digest = NULL;
|
||
g_autofree char *checksum = NULL;
|
||
VarRefInfoRef latest_rev_info;
|
||
g_autofree char *latest_alt_commit = NULL;
|
||
VarMetadataRef metadata;
|
||
g_autofree char *latest_rev = NULL;
|
||
G_GNUC_UNUSED g_autofree char *latest_commit =
|
||
flatpak_dir_read_latest (self, state->remote_name, ref, &latest_alt_commit, cancellable, NULL);
|
||
g_autofree char *name = NULL;
|
||
|
||
/* We use the summary so that we can reuse any cached json */
|
||
if (!flatpak_remote_state_lookup_ref (state, ref, &latest_rev, NULL, &latest_rev_info, NULL, error))
|
||
return FALSE;
|
||
if (latest_rev == NULL)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND,
|
||
_("Couldn't find latest checksum for ref %s in remote %s"),
|
||
ref, state->remote_name);
|
||
|
||
metadata = var_ref_info_get_metadata (latest_rev_info);
|
||
oci_repository = var_metadata_lookup_string (metadata, "xa.oci-repository", NULL);
|
||
delta_url = var_metadata_lookup_string (metadata, "xa.delta-url", NULL);
|
||
|
||
oci_digest = g_strconcat ("sha256:", opt_rev != NULL ? opt_rev : latest_rev, NULL);
|
||
|
||
/* Short circuit if we've already got this commit */
|
||
if (latest_alt_commit != NULL && strcmp (oci_digest + strlen ("sha256:"), latest_alt_commit) == 0)
|
||
return TRUE;
|
||
|
||
registry = flatpak_remote_state_new_oci_registry (state, token, cancellable, error);
|
||
if (registry == NULL)
|
||
return FALSE;
|
||
|
||
versioned = flatpak_oci_registry_load_versioned (registry, oci_repository, oci_digest,
|
||
NULL, NULL, cancellable, error);
|
||
if (versioned == NULL)
|
||
return FALSE;
|
||
|
||
if (!FLATPAK_IS_OCI_MANIFEST (versioned))
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Image is not a manifest"));
|
||
|
||
image_config = flatpak_oci_registry_load_image_config (registry, oci_repository,
|
||
FLATPAK_OCI_MANIFEST (versioned)->config.digest,
|
||
(const char **)FLATPAK_OCI_MANIFEST (versioned)->config.urls,
|
||
NULL, cancellable, error);
|
||
if (image_config == NULL)
|
||
return FALSE;
|
||
|
||
if (repo == NULL)
|
||
repo = self->repo;
|
||
|
||
flatpak_progress_start_oci_pull (progress);
|
||
|
||
g_info ("Pulling OCI image %s", oci_digest);
|
||
|
||
checksum = flatpak_pull_from_oci (repo, registry, oci_repository, oci_digest, delta_url, FLATPAK_OCI_MANIFEST (versioned), image_config,
|
||
state->remote_name, ref, flatpak_flags, oci_pull_progress_cb, progress, cancellable, error);
|
||
|
||
if (checksum == NULL)
|
||
return FALSE;
|
||
|
||
g_info ("Imported OCI image as checksum %s", checksum);
|
||
|
||
if (repo == self->repo)
|
||
name = flatpak_dir_get_name (self);
|
||
else
|
||
{
|
||
GFile *file = ostree_repo_get_path (repo);
|
||
name = g_file_get_path (file);
|
||
}
|
||
|
||
(flatpak_dir_log) (self, __FILE__, __LINE__, __FUNCTION__, name,
|
||
"pull oci", flatpak_oci_registry_get_uri (registry), ref, NULL, NULL, NULL,
|
||
"Pulled %s from %s", ref, flatpak_oci_registry_get_uri (registry));
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_pull (FlatpakDir *self,
|
||
FlatpakRemoteState *state,
|
||
const char *ref,
|
||
const char *opt_rev,
|
||
const char **subpaths,
|
||
GFile *sideload_repo,
|
||
GBytes *require_metadata,
|
||
const char *token,
|
||
OstreeRepo *repo,
|
||
FlatpakPullFlags flatpak_flags,
|
||
OstreeRepoPullFlags flags,
|
||
FlatpakProgress *progress,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
gboolean ret = FALSE;
|
||
gboolean have_commit = FALSE;
|
||
g_autofree char *rev = NULL;
|
||
g_autofree char *url = NULL;
|
||
g_autoptr(GPtrArray) subdirs_arg = NULL;
|
||
g_auto(GLnxLockFile) lock = { 0, };
|
||
g_autofree char *name = NULL;
|
||
g_autofree char *current_checksum = NULL;
|
||
|
||
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
||
return FALSE;
|
||
|
||
/* Keep a shared repo lock to avoid prunes removing objects we're relying on
|
||
* while we do the pull. There are two cases we protect against. 1) objects we
|
||
* need but that we already decided are locally available could be removed,
|
||
* and 2) during the transaction commit objects that don't yet have a ref to
|
||
* them could be considered unreachable.
|
||
*/
|
||
if (repo == NULL && !flatpak_dir_repo_lock (self, &lock, LOCK_SH, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (flatpak_dir_get_remote_oci (self, state->remote_name))
|
||
return flatpak_dir_pull_oci (self, state, ref, opt_rev, repo, flatpak_flags,
|
||
flags, token, progress, cancellable, error);
|
||
|
||
if (!ostree_repo_remote_get_url (self->repo,
|
||
state->remote_name,
|
||
&url,
|
||
error))
|
||
return FALSE;
|
||
|
||
if (*url == 0)
|
||
return TRUE; /* Empty url, silently disables updates */
|
||
|
||
/* We get the rev ahead of time so that we know it for looking up e.g. extra-data
|
||
and to make sure we're atomically using a single rev if we happen to do multiple
|
||
pulls (e.g. with subpaths) */
|
||
if (opt_rev != NULL)
|
||
{
|
||
rev = g_strdup (opt_rev);
|
||
}
|
||
else if (!flatpak_remote_state_lookup_ref (state, ref, &rev, NULL, NULL, NULL, error))
|
||
{
|
||
g_assert (error == NULL || *error != NULL);
|
||
return FALSE;
|
||
}
|
||
|
||
g_info ("%s: Using commit %s for pull of ref %s from remote %s%s%s",
|
||
G_STRFUNC, rev, ref, state->remote_name,
|
||
sideload_repo ? "sideloaded from " : "",
|
||
sideload_repo ? flatpak_file_get_path_cached (sideload_repo) : ""
|
||
);
|
||
|
||
if (repo == NULL)
|
||
repo = self->repo;
|
||
|
||
/* Past this we must use goto out, so we clean up console and
|
||
abort the transaction on error */
|
||
|
||
if (subpaths != NULL && subpaths[0] != NULL)
|
||
{
|
||
subdirs_arg = g_ptr_array_new_with_free_func (g_free);
|
||
int i;
|
||
g_ptr_array_add (subdirs_arg, g_strdup ("/metadata"));
|
||
for (i = 0; subpaths[i] != NULL; i++)
|
||
g_ptr_array_add (subdirs_arg,
|
||
g_build_filename ("/files", subpaths[i], NULL));
|
||
g_ptr_array_add (subdirs_arg, NULL);
|
||
}
|
||
|
||
/* Setup extra data information before starting to pull, so we can have precise
|
||
* progress reports */
|
||
if (!flatpak_dir_setup_extra_data (self, state, repo,
|
||
ref, rev, sideload_repo, token,
|
||
flatpak_flags,
|
||
progress,
|
||
cancellable,
|
||
error))
|
||
goto out;
|
||
|
||
/* Work around a libostree bug where the pull may succeed but the pulled
|
||
* commit will be incomplete by preemptively marking the commit partial.
|
||
* Note this has to be done before ostree_repo_prepare_transaction() so we
|
||
* aren't checking the staging dir for the commit.
|
||
* https://github.com/flatpak/flatpak/issues/3479
|
||
* https://github.com/ostreedev/ostree/pull/2549
|
||
*/
|
||
{
|
||
g_autoptr(GError) local_error = NULL;
|
||
|
||
if (!ostree_repo_has_object (repo, OSTREE_OBJECT_TYPE_COMMIT, rev, &have_commit, NULL, &local_error))
|
||
g_warning ("Encountered error checking for commit object %s: %s", rev, local_error->message);
|
||
else if (!have_commit &&
|
||
!ostree_repo_mark_commit_partial (repo, rev, TRUE, &local_error))
|
||
g_warning ("Encountered error marking commit partial: %s: %s", rev, local_error->message);
|
||
}
|
||
|
||
if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error))
|
||
goto out;
|
||
|
||
flatpak_repo_resolve_rev (repo, NULL, state->remote_name, ref, TRUE,
|
||
¤t_checksum, NULL, NULL);
|
||
|
||
if (!repo_pull (repo, state,
|
||
subdirs_arg ? (const char **) subdirs_arg->pdata : NULL,
|
||
ref, rev, sideload_repo, token, flatpak_flags, flags,
|
||
progress,
|
||
cancellable, error))
|
||
{
|
||
g_prefix_error (error, _("While pulling %s from remote %s: "), ref, state->remote_name);
|
||
goto out;
|
||
}
|
||
|
||
|
||
if (require_metadata)
|
||
{
|
||
g_autoptr(GVariant) commit_data = NULL;
|
||
if (!ostree_repo_load_commit (repo, rev, &commit_data, NULL, error) ||
|
||
!validate_commit_metadata (commit_data,
|
||
ref,
|
||
(const char *)g_bytes_get_data (require_metadata, NULL),
|
||
g_bytes_get_size (require_metadata),
|
||
error))
|
||
goto out;
|
||
}
|
||
|
||
if (!flatpak_dir_pull_extra_data (self, repo,
|
||
state->remote_name,
|
||
ref, rev,
|
||
flatpak_flags,
|
||
progress,
|
||
cancellable,
|
||
error))
|
||
goto out;
|
||
|
||
|
||
if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error))
|
||
goto out;
|
||
|
||
ret = TRUE;
|
||
|
||
if (repo == self->repo)
|
||
name = flatpak_dir_get_name (self);
|
||
else
|
||
{
|
||
GFile *file = ostree_repo_get_path (repo);
|
||
name = g_file_get_path (file);
|
||
}
|
||
|
||
(flatpak_dir_log) (self, __FILE__, __LINE__, __FUNCTION__, name,
|
||
"pull", state->remote_name, ref, rev, current_checksum, NULL,
|
||
"Pulled %s from %s", ref, state->remote_name);
|
||
|
||
out:
|
||
if (!ret)
|
||
{
|
||
ostree_repo_abort_transaction (repo, cancellable, NULL);
|
||
g_assert (error == NULL || *error != NULL);
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
static gboolean
|
||
repo_pull_local_untrusted (FlatpakDir *self,
|
||
OstreeRepo *repo,
|
||
const char *remote_name,
|
||
const char *url,
|
||
const char **dirs_to_pull,
|
||
const char *ref,
|
||
const char *checksum,
|
||
FlatpakProgress *progress,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
/* The latter flag was introduced in https://github.com/ostreedev/ostree/pull/926 */
|
||
const OstreeRepoPullFlags flags = OSTREE_REPO_PULL_FLAGS_UNTRUSTED | OSTREE_REPO_PULL_FLAGS_BAREUSERONLY_FILES;
|
||
GVariantBuilder builder;
|
||
g_autoptr(GVariant) options = NULL;
|
||
gboolean res;
|
||
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
|
||
const char *refs[2] = { NULL, NULL };
|
||
const char *commits[2] = { NULL, NULL };
|
||
g_autoptr(GError) dummy_error = NULL;
|
||
g_auto(FlatpakMainContext) context = FLATKPAK_MAIN_CONTEXT_INIT;
|
||
|
||
/* The ostree fetcher asserts if error is NULL */
|
||
if (error == NULL)
|
||
error = &dummy_error;
|
||
|
||
refs[0] = ref;
|
||
commits[0] = checksum;
|
||
|
||
g_variant_builder_add (&builder, "{s@v}", "refs",
|
||
g_variant_new_variant (g_variant_new_strv ((const char * const *) refs, -1)));
|
||
g_variant_builder_add (&builder, "{s@v}", "override-commit-ids",
|
||
g_variant_new_variant (g_variant_new_strv ((const char * const *) commits, -1)));
|
||
|
||
g_variant_builder_add (&builder, "{s@v}", "flags",
|
||
g_variant_new_variant (g_variant_new_int32 (flags)));
|
||
g_variant_builder_add (&builder, "{s@v}", "override-remote-name",
|
||
g_variant_new_variant (g_variant_new_string (remote_name)));
|
||
g_variant_builder_add (&builder, "{s@v}", "gpg-verify",
|
||
g_variant_new_variant (g_variant_new_boolean (TRUE)));
|
||
g_variant_builder_add (&builder, "{s@v}", "gpg-verify-summary",
|
||
g_variant_new_variant (g_variant_new_boolean (FALSE)));
|
||
g_variant_builder_add (&builder, "{s@v}", "inherit-transaction",
|
||
g_variant_new_variant (g_variant_new_boolean (TRUE)));
|
||
g_variant_builder_add (&builder, "{s@v}", "update-frequency",
|
||
g_variant_new_variant (g_variant_new_uint32 (FLATPAK_DEFAULT_UPDATE_INTERVAL_MS)));
|
||
|
||
if (dirs_to_pull)
|
||
{
|
||
g_variant_builder_add (&builder, "{s@v}", "subdirs",
|
||
g_variant_new_variant (g_variant_new_strv ((const char * const *) dirs_to_pull, -1)));
|
||
g_variant_builder_add (&builder, "{s@v}", "disable-static-deltas",
|
||
g_variant_new_variant (g_variant_new_boolean (TRUE)));
|
||
}
|
||
|
||
options = g_variant_ref_sink (g_variant_builder_end (&builder));
|
||
|
||
flatpak_progress_init_main_context (progress, &context);
|
||
res = ostree_repo_pull_with_options (repo, url, options,
|
||
context.ostree_progress, cancellable, error);
|
||
if (!res)
|
||
translate_ostree_repo_pull_errors (error);
|
||
|
||
return res;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_pull_untrusted_local (FlatpakDir *self,
|
||
const char *src_path,
|
||
const char *remote_name,
|
||
const char *ref,
|
||
const char **subpaths,
|
||
FlatpakProgress *progress,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) path_file = g_file_new_for_path (src_path);
|
||
g_autofree char *url = g_file_get_uri (path_file);
|
||
g_autofree char *checksum = NULL;
|
||
g_autofree char *current_checksum = NULL;
|
||
gboolean gpg_verify_summary;
|
||
gboolean gpg_verify;
|
||
g_autoptr(OstreeGpgVerifyResult) gpg_result = NULL;
|
||
g_autoptr(GVariant) old_commit = NULL;
|
||
g_autoptr(OstreeRepo) src_repo = NULL;
|
||
g_autoptr(GVariant) new_commit = NULL;
|
||
g_autoptr(GVariant) new_commit_metadata = NULL;
|
||
g_autoptr(GVariant) extra_data_sources = NULL;
|
||
g_autoptr(GPtrArray) subdirs_arg = NULL;
|
||
g_auto(GLnxLockFile) lock = { 0, };
|
||
gboolean ret = FALSE;
|
||
g_autofree const char **ref_bindings = NULL;
|
||
|
||
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
||
return FALSE;
|
||
|
||
/* Keep a shared repo lock to avoid prunes removing objects we're relying on
|
||
* while we do the pull. There are two cases we protect against. 1) objects we
|
||
* need but that we already decided are locally available could be removed,
|
||
* and 2) during the transaction commit objects that don't yet have a ref to
|
||
* them could be considered unreachable.
|
||
*/
|
||
if (!flatpak_dir_repo_lock (self, &lock, LOCK_SH, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (!ostree_repo_remote_get_gpg_verify_summary (self->repo, remote_name,
|
||
&gpg_verify_summary, error))
|
||
return FALSE;
|
||
|
||
if (!ostree_repo_remote_get_gpg_verify (self->repo, remote_name,
|
||
&gpg_verify, error))
|
||
return FALSE;
|
||
|
||
/* This was verified in the client, but lets do it here too */
|
||
if (!gpg_verify_summary || !gpg_verify)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_UNTRUSTED, _("Can't pull from untrusted non-gpg verified remote"));
|
||
|
||
if (!flatpak_repo_resolve_rev (self->repo, NULL, remote_name, ref, TRUE,
|
||
¤t_checksum, NULL, error))
|
||
return FALSE;
|
||
|
||
if (current_checksum != NULL &&
|
||
!ostree_repo_load_commit (self->repo, current_checksum, &old_commit, NULL, error))
|
||
return FALSE;
|
||
|
||
src_repo = ostree_repo_new (path_file);
|
||
if (!ostree_repo_open (src_repo, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (!flatpak_repo_resolve_rev (src_repo, NULL, remote_name, ref, FALSE, &checksum, NULL, error))
|
||
return FALSE;
|
||
|
||
if (gpg_verify)
|
||
{
|
||
gpg_result = ostree_repo_verify_commit_for_remote (src_repo, checksum, remote_name, cancellable, error);
|
||
if (gpg_result == NULL)
|
||
return FALSE;
|
||
|
||
if (ostree_gpg_verify_result_count_valid (gpg_result) == 0)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_UNTRUSTED, _("GPG signatures found, but none are in trusted keyring"));
|
||
}
|
||
|
||
g_clear_object (&gpg_result);
|
||
|
||
if (!ostree_repo_load_commit (src_repo, checksum, &new_commit, NULL, error))
|
||
return FALSE;
|
||
|
||
/* Here we check that there is actually a ref binding, otherwise we
|
||
could allow installing a ref as another app, because both would
|
||
pass gpg validation. Note that ostree pull actually also verifies
|
||
the ref-bindings, but only if they exist. We could do only the
|
||
ref-binding existence check, but if we got something weird might as
|
||
well stop handling it early. */
|
||
|
||
new_commit_metadata = g_variant_get_child_value (new_commit, 0);
|
||
if (!g_variant_lookup (new_commit_metadata, OSTREE_COMMIT_META_KEY_REF_BINDING, "^a&s", &ref_bindings))
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Commit for ‘%s’ has no ref binding"), ref);
|
||
|
||
if (!g_strv_contains ((const char *const *) ref_bindings, ref))
|
||
{
|
||
g_autofree char *as_string = g_strjoinv (", ", (char **)ref_bindings);
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Commit for ‘%s’ is not in expected bound refs: %s"), ref, as_string);
|
||
}
|
||
|
||
if (old_commit)
|
||
{
|
||
guint64 old_timestamp;
|
||
guint64 new_timestamp;
|
||
|
||
old_timestamp = ostree_commit_get_timestamp (old_commit);
|
||
new_timestamp = ostree_commit_get_timestamp (new_commit);
|
||
|
||
if (new_timestamp < old_timestamp)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_DOWNGRADE, "Not allowed to downgrade %s (old_commit: %s/%" G_GINT64_FORMAT " new_commit: %s/%" G_GINT64_FORMAT ")",
|
||
ref, current_checksum, old_timestamp, checksum, new_timestamp);
|
||
}
|
||
|
||
if (subpaths != NULL && subpaths[0] != NULL)
|
||
{
|
||
subdirs_arg = g_ptr_array_new_with_free_func (g_free);
|
||
int i;
|
||
g_ptr_array_add (subdirs_arg, g_strdup ("/metadata"));
|
||
for (i = 0; subpaths[i] != NULL; i++)
|
||
g_ptr_array_add (subdirs_arg,
|
||
g_build_filename ("/files", subpaths[i], NULL));
|
||
g_ptr_array_add (subdirs_arg, NULL);
|
||
}
|
||
|
||
if (!ostree_repo_prepare_transaction (self->repo, NULL, cancellable, error))
|
||
goto out;
|
||
|
||
/* Past this we must use goto out, so we abort the transaction on error */
|
||
|
||
if (!repo_pull_local_untrusted (self, self->repo, remote_name, url,
|
||
subdirs_arg ? (const char **) subdirs_arg->pdata : NULL,
|
||
ref, checksum, progress,
|
||
cancellable, error))
|
||
{
|
||
g_prefix_error (error, _("While pulling %s from remote %s: "), ref, remote_name);
|
||
goto out;
|
||
}
|
||
|
||
/* Get the out of bands extra-data required due to an ostree pull
|
||
commitmeta size limit */
|
||
extra_data_sources = flatpak_commit_get_extra_data_sources (new_commit, NULL);
|
||
if (extra_data_sources)
|
||
{
|
||
GFile *dir = ostree_repo_get_path (src_repo);
|
||
g_autoptr(GFile) file = NULL;
|
||
g_autofree char *filename = NULL;
|
||
g_autofree char *commitmeta = NULL;
|
||
gsize commitmeta_size;
|
||
g_autoptr(GVariant) new_metadata = NULL;
|
||
|
||
filename = g_strconcat (checksum, ".commitmeta", NULL);
|
||
file = g_file_get_child (dir, filename);
|
||
if (!g_file_load_contents (file, cancellable,
|
||
&commitmeta, &commitmeta_size,
|
||
NULL, error))
|
||
goto out;
|
||
|
||
new_metadata = g_variant_ref_sink (g_variant_new_from_data (G_VARIANT_TYPE ("a{sv}"),
|
||
commitmeta, commitmeta_size,
|
||
FALSE,
|
||
g_free, commitmeta));
|
||
g_steal_pointer (&commitmeta); /* steal into the variant */
|
||
|
||
if (!ostree_repo_write_commit_detached_metadata (self->repo, checksum, new_metadata, cancellable, error))
|
||
goto out;
|
||
}
|
||
|
||
if (!ostree_repo_commit_transaction (self->repo, NULL, cancellable, error))
|
||
goto out;
|
||
|
||
ret = TRUE;
|
||
|
||
flatpak_dir_log (self, "pull local", src_path, ref, checksum, current_checksum, NULL,
|
||
"Pulled %s from %s", ref, src_path);
|
||
out:
|
||
if (!ret)
|
||
ostree_repo_abort_transaction (self->repo, cancellable, NULL);
|
||
|
||
return ret;
|
||
}
|
||
|
||
FlatpakDecomposed *
|
||
flatpak_dir_current_ref (FlatpakDir *self,
|
||
const char *name,
|
||
GCancellable *cancellable)
|
||
{
|
||
g_autoptr(GFile) base = NULL;
|
||
g_autoptr(GFile) dir = NULL;
|
||
g_autoptr(GFile) current_link = NULL;
|
||
g_autoptr(GFileInfo) file_info = NULL;
|
||
FlatpakDecomposed *decomposed;
|
||
char *ref;
|
||
|
||
base = g_file_get_child (flatpak_dir_get_path (self), "app");
|
||
dir = g_file_get_child (base, name);
|
||
|
||
current_link = g_file_get_child (dir, "current");
|
||
|
||
file_info = g_file_query_info (current_link, OSTREE_GIO_FAST_QUERYINFO,
|
||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||
cancellable, NULL);
|
||
if (file_info == NULL)
|
||
return NULL;
|
||
|
||
ref = g_strconcat ("app/", name, "/", g_file_info_get_symlink_target (file_info), NULL);
|
||
decomposed = flatpak_decomposed_new_from_ref_take (ref, NULL);
|
||
if (decomposed == NULL)
|
||
g_free (ref);
|
||
|
||
return decomposed;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_drop_current_ref (FlatpakDir *self,
|
||
const char *name,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) base = NULL;
|
||
g_autoptr(GFile) dir = NULL;
|
||
g_autoptr(GFile) current_link = NULL;
|
||
g_autoptr(GPtrArray) refs = NULL;
|
||
g_autoptr(FlatpakDecomposed) current_ref = NULL;
|
||
FlatpakDecomposed *other_ref = NULL;
|
||
|
||
current_ref = flatpak_dir_current_ref (self, name, cancellable);
|
||
if (current_ref)
|
||
{
|
||
refs = flatpak_dir_list_refs_for_name (self, FLATPAK_KINDS_APP, name, cancellable, NULL);
|
||
if (refs)
|
||
{
|
||
for (int i = 0; i < refs->len; i++)
|
||
{
|
||
FlatpakDecomposed *ref = g_ptr_array_index (refs, i);
|
||
if (!flatpak_decomposed_equal (ref, current_ref))
|
||
{
|
||
other_ref = ref;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
base = g_file_get_child (flatpak_dir_get_path (self), "app");
|
||
dir = g_file_get_child (base, name);
|
||
|
||
current_link = g_file_get_child (dir, "current");
|
||
if (!g_file_delete (current_link, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (other_ref)
|
||
{
|
||
if (!flatpak_dir_make_current_ref (self, other_ref, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_make_current_ref (FlatpakDir *self,
|
||
FlatpakDecomposed *ref,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GError) local_error = NULL;
|
||
g_autoptr(GFile) base = NULL;
|
||
g_autoptr(GFile) dir = NULL;
|
||
g_autoptr(GFile) current_link = NULL;
|
||
g_autofree char *id = NULL;
|
||
const char *rest;
|
||
|
||
if (!flatpak_decomposed_is_app (ref))
|
||
return flatpak_fail (error, _("Only applications can be made current"));
|
||
|
||
base = g_file_get_child (flatpak_dir_get_path (self), flatpak_decomposed_get_kind_str (ref));
|
||
|
||
id = flatpak_decomposed_dup_id (ref);
|
||
dir = g_file_get_child (base, id);
|
||
|
||
current_link = g_file_get_child (dir, "current");
|
||
|
||
if (!g_file_delete (current_link, cancellable, &local_error) &&
|
||
!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return FALSE;
|
||
}
|
||
|
||
rest = flatpak_decomposed_peek_arch (ref, NULL);
|
||
if (!g_file_make_symbolic_link (current_link, rest, cancellable, error))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
_flatpak_dir_list_refs_for_name (FlatpakDir *self,
|
||
GFile *base_dir,
|
||
FlatpakKinds kind,
|
||
const char *name,
|
||
GPtrArray *refs,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) dir = NULL;
|
||
g_autoptr(GFileEnumerator) dir_enum = NULL;
|
||
g_autoptr(GFileInfo) child_info = NULL;
|
||
GError *temp_error = NULL;
|
||
|
||
g_assert (kind == FLATPAK_KINDS_RUNTIME || kind == FLATPAK_KINDS_APP);
|
||
|
||
dir = g_file_get_child (base_dir, name);
|
||
|
||
if (!g_file_query_exists (dir, cancellable))
|
||
return TRUE;
|
||
|
||
dir_enum = g_file_enumerate_children (dir, OSTREE_GIO_FAST_QUERYINFO,
|
||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||
cancellable, error);
|
||
if (!dir_enum)
|
||
return FALSE;
|
||
|
||
while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)))
|
||
{
|
||
g_autoptr(GFile) child = NULL;
|
||
g_autoptr(GFileEnumerator) dir_enum2 = NULL;
|
||
g_autoptr(GFileInfo) child_info2 = NULL;
|
||
const char *arch;
|
||
|
||
arch = g_file_info_get_name (child_info);
|
||
|
||
if (g_file_info_get_file_type (child_info) != G_FILE_TYPE_DIRECTORY ||
|
||
strcmp (arch, "data") == 0 /* There used to be a data dir here, lets ignore it */)
|
||
{
|
||
g_clear_object (&child_info);
|
||
continue;
|
||
}
|
||
|
||
child = g_file_get_child (dir, arch);
|
||
g_clear_object (&dir_enum2);
|
||
dir_enum2 = g_file_enumerate_children (child, OSTREE_GIO_FAST_QUERYINFO,
|
||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||
cancellable, error);
|
||
if (!dir_enum2)
|
||
return FALSE;
|
||
|
||
while ((child_info2 = g_file_enumerator_next_file (dir_enum2, cancellable, &temp_error)))
|
||
{
|
||
const char *branch = g_file_info_get_name (child_info2);
|
||
|
||
if (g_file_info_get_file_type (child_info2) == G_FILE_TYPE_DIRECTORY)
|
||
{
|
||
g_autoptr(GFile) deploy = flatpak_build_file (child, branch, "active/deploy", NULL);
|
||
|
||
if (g_file_query_exists (deploy, NULL))
|
||
{
|
||
FlatpakDecomposed *ref = flatpak_decomposed_new_from_parts (kind, name, arch, branch, NULL);
|
||
if (ref)
|
||
g_ptr_array_add (refs, ref);
|
||
}
|
||
}
|
||
|
||
g_clear_object (&child_info2);
|
||
}
|
||
|
||
if (temp_error != NULL)
|
||
{
|
||
g_propagate_error (error, temp_error);
|
||
return FALSE;
|
||
}
|
||
|
||
g_clear_object (&child_info);
|
||
}
|
||
|
||
if (temp_error != NULL)
|
||
{
|
||
g_propagate_error (error, temp_error);
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
GPtrArray *
|
||
flatpak_dir_list_refs_for_name (FlatpakDir *self,
|
||
FlatpakKinds kinds,
|
||
const char *name,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GPtrArray) refs = NULL;
|
||
|
||
refs = g_ptr_array_new_with_free_func ((GDestroyNotify)flatpak_decomposed_unref);
|
||
|
||
if ((kinds & FLATPAK_KINDS_APP) != 0)
|
||
{
|
||
g_autoptr(GFile) base = g_file_get_child (flatpak_dir_get_path (self), "app");
|
||
|
||
if (!_flatpak_dir_list_refs_for_name (self, base, FLATPAK_KINDS_APP, name, refs, cancellable, error))
|
||
return NULL;
|
||
}
|
||
|
||
if ((kinds & FLATPAK_KINDS_RUNTIME) != 0)
|
||
{
|
||
g_autoptr(GFile) base = g_file_get_child (flatpak_dir_get_path (self), "runtime");
|
||
|
||
if (!_flatpak_dir_list_refs_for_name (self, base, FLATPAK_KINDS_RUNTIME, name, refs, cancellable, error))
|
||
return NULL;
|
||
}
|
||
|
||
g_ptr_array_sort (refs, (GCompareFunc)flatpak_decomposed_strcmp_p);
|
||
|
||
return g_steal_pointer (&refs);
|
||
}
|
||
|
||
GPtrArray *
|
||
flatpak_dir_list_refs (FlatpakDir *self,
|
||
FlatpakKinds kinds,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GPtrArray) refs = NULL;
|
||
|
||
refs = g_ptr_array_new_with_free_func ((GDestroyNotify)flatpak_decomposed_unref);
|
||
|
||
if (kinds & FLATPAK_KINDS_APP)
|
||
{
|
||
g_autoptr(GFile) base = NULL;
|
||
g_autoptr(GFileEnumerator) dir_enum = NULL;
|
||
g_autoptr(GFileInfo) child_info = NULL;
|
||
GError *temp_error = NULL;
|
||
|
||
base = g_file_get_child (flatpak_dir_get_path (self), "app");
|
||
|
||
if (g_file_query_exists (base, cancellable))
|
||
{
|
||
dir_enum = g_file_enumerate_children (base, OSTREE_GIO_FAST_QUERYINFO,
|
||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||
cancellable, error);
|
||
if (!dir_enum)
|
||
return NULL;
|
||
|
||
while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)))
|
||
{
|
||
const char *name = g_file_info_get_name (child_info);
|
||
|
||
if (g_file_info_get_file_type (child_info) != G_FILE_TYPE_DIRECTORY)
|
||
{
|
||
g_clear_object (&child_info);
|
||
continue;
|
||
}
|
||
|
||
if (!_flatpak_dir_list_refs_for_name (self, base, FLATPAK_KINDS_APP, name, refs, cancellable, error))
|
||
return NULL;
|
||
|
||
g_clear_object (&child_info);
|
||
}
|
||
|
||
if (temp_error != NULL)
|
||
{
|
||
g_propagate_error (error, temp_error);
|
||
return NULL;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (kinds & FLATPAK_KINDS_RUNTIME)
|
||
{
|
||
g_autoptr(GFile) base = NULL;
|
||
g_autoptr(GFileEnumerator) dir_enum = NULL;
|
||
g_autoptr(GFileInfo) child_info = NULL;
|
||
GError *temp_error = NULL;
|
||
|
||
base = g_file_get_child (flatpak_dir_get_path (self), "runtime");
|
||
|
||
if (g_file_query_exists (base, cancellable))
|
||
{
|
||
dir_enum = g_file_enumerate_children (base, OSTREE_GIO_FAST_QUERYINFO,
|
||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||
cancellable, error);
|
||
if (!dir_enum)
|
||
return NULL;
|
||
|
||
while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)))
|
||
{
|
||
const char *name = g_file_info_get_name (child_info);
|
||
|
||
if (g_file_info_get_file_type (child_info) != G_FILE_TYPE_DIRECTORY)
|
||
{
|
||
g_clear_object (&child_info);
|
||
continue;
|
||
}
|
||
|
||
if (!_flatpak_dir_list_refs_for_name (self, base, FLATPAK_KINDS_RUNTIME, name, refs, cancellable, error))
|
||
return NULL;
|
||
|
||
g_clear_object (&child_info);
|
||
}
|
||
|
||
if (temp_error != NULL)
|
||
{
|
||
g_propagate_error (error, temp_error);
|
||
return NULL;
|
||
}
|
||
}
|
||
}
|
||
|
||
g_ptr_array_sort (refs, (GCompareFunc)flatpak_decomposed_strcmp_p);
|
||
|
||
return g_steal_pointer (&refs);
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_is_runtime_extension (FlatpakDir *self,
|
||
FlatpakDecomposed *ref)
|
||
{
|
||
g_autoptr(GBytes) ext_deploy_data = NULL;
|
||
|
||
if (!flatpak_decomposed_is_runtime (ref))
|
||
return FALSE;
|
||
|
||
/* deploy v4 guarantees extension-of info */
|
||
ext_deploy_data = flatpak_dir_get_deploy_data (self, ref, 4, NULL, NULL);
|
||
if (ext_deploy_data && flatpak_deploy_data_get_extension_of (ext_deploy_data) != NULL)
|
||
return TRUE;
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static GHashTable *
|
||
flatpak_dir_get_runtime_app_map (FlatpakDir *self,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GHashTable) runtime_app_map = g_hash_table_new_full ((GHashFunc)flatpak_decomposed_hash,
|
||
(GEqualFunc)flatpak_decomposed_equal,
|
||
(GDestroyNotify)flatpak_decomposed_unref,
|
||
(GDestroyNotify)g_ptr_array_unref);
|
||
g_autoptr(GPtrArray) app_refs = NULL;
|
||
|
||
app_refs = flatpak_dir_list_refs (self, FLATPAK_KINDS_APP, cancellable, error);
|
||
if (app_refs == NULL)
|
||
return NULL;
|
||
|
||
for (guint i = 0; i < app_refs->len; i++)
|
||
{
|
||
FlatpakDecomposed *app_ref = g_ptr_array_index (app_refs, i);
|
||
/* deploy v4 guarantees runtime info */
|
||
g_autoptr(GBytes) app_deploy_data = flatpak_dir_get_deploy_data (self, app_ref, 4, NULL, NULL);
|
||
g_autoptr(FlatpakDecomposed) runtime_decomposed = NULL;
|
||
g_autoptr(GPtrArray) runtime_apps = NULL;
|
||
const char *runtime_pref;
|
||
|
||
if (app_deploy_data == NULL)
|
||
continue;
|
||
|
||
runtime_pref = flatpak_deploy_data_get_runtime (app_deploy_data);
|
||
runtime_decomposed = flatpak_decomposed_new_from_pref (FLATPAK_KINDS_RUNTIME, runtime_pref, error);
|
||
if (runtime_decomposed == NULL)
|
||
return NULL;
|
||
|
||
runtime_apps = g_hash_table_lookup (runtime_app_map, runtime_decomposed);
|
||
if (runtime_apps == NULL)
|
||
{
|
||
runtime_apps = g_ptr_array_new_with_free_func ((GDestroyNotify)flatpak_decomposed_unref);
|
||
g_hash_table_insert (runtime_app_map, flatpak_decomposed_ref (runtime_decomposed), g_ptr_array_ref (runtime_apps));
|
||
}
|
||
else
|
||
g_ptr_array_ref (runtime_apps);
|
||
|
||
g_ptr_array_add (runtime_apps, flatpak_decomposed_ref (app_ref));
|
||
}
|
||
|
||
return g_steal_pointer (&runtime_app_map);
|
||
}
|
||
|
||
GPtrArray *
|
||
flatpak_dir_list_app_refs_with_runtime (FlatpakDir *self,
|
||
GHashTable **runtime_app_map,
|
||
FlatpakDecomposed *runtime_ref,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
GPtrArray *apps;
|
||
|
||
g_assert (runtime_app_map != NULL);
|
||
|
||
if (*runtime_app_map == NULL)
|
||
*runtime_app_map = flatpak_dir_get_runtime_app_map (self, cancellable, error);
|
||
|
||
if (*runtime_app_map == NULL)
|
||
return NULL;
|
||
|
||
apps = g_hash_table_lookup (*runtime_app_map, runtime_ref);
|
||
if (apps == NULL) /* unused runtime */
|
||
return g_ptr_array_new_with_free_func ((GDestroyNotify)flatpak_decomposed_unref);
|
||
|
||
return g_ptr_array_ref (apps);
|
||
}
|
||
|
||
static GHashTable *
|
||
flatpak_dir_get_extension_app_map (FlatpakDir *self,
|
||
GHashTable *runtime_app_map,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GHashTable) extension_app_map = g_hash_table_new_full ((GHashFunc)flatpak_decomposed_hash,
|
||
(GEqualFunc)flatpak_decomposed_equal,
|
||
(GDestroyNotify)flatpak_decomposed_unref,
|
||
(GDestroyNotify)g_ptr_array_unref);
|
||
g_autoptr(GPtrArray) all_refs = NULL;
|
||
|
||
g_assert (runtime_app_map != NULL);
|
||
|
||
all_refs = flatpak_dir_list_refs (self, FLATPAK_KINDS_RUNTIME | FLATPAK_KINDS_APP, NULL, NULL);
|
||
for (guint i = 0; all_refs != NULL && i < all_refs->len; i++)
|
||
{
|
||
FlatpakDecomposed *ref = g_ptr_array_index (all_refs, i);
|
||
g_autoptr(GPtrArray) related = NULL;
|
||
GPtrArray *runtime_apps = NULL;
|
||
|
||
if (flatpak_decomposed_id_is_subref (ref))
|
||
continue;
|
||
|
||
if (flatpak_decomposed_is_runtime (ref))
|
||
{
|
||
runtime_apps = g_hash_table_lookup (runtime_app_map, ref);
|
||
if (runtime_apps == NULL)
|
||
continue;
|
||
}
|
||
|
||
related = flatpak_dir_find_local_related (self, ref, NULL, TRUE, cancellable, error);
|
||
if (related == NULL)
|
||
return NULL;
|
||
|
||
for (guint j = 0; j < related->len; j++)
|
||
{
|
||
FlatpakRelated *rel = g_ptr_array_index (related, j);
|
||
g_autoptr(GPtrArray) extension_apps = g_hash_table_lookup (extension_app_map, rel->ref);
|
||
if (extension_apps == NULL)
|
||
{
|
||
extension_apps = g_ptr_array_new_with_free_func ((GDestroyNotify)flatpak_decomposed_unref);
|
||
g_hash_table_insert (extension_app_map, flatpak_decomposed_ref (rel->ref), g_ptr_array_ref (extension_apps));
|
||
}
|
||
else
|
||
g_ptr_array_ref (extension_apps);
|
||
|
||
if (flatpak_decomposed_is_runtime (ref))
|
||
{
|
||
g_assert (runtime_apps);
|
||
for (guint k = 0; runtime_apps && k < runtime_apps->len; k++)
|
||
g_ptr_array_add (extension_apps, flatpak_decomposed_ref (g_ptr_array_index (runtime_apps, k)));
|
||
}
|
||
else
|
||
g_ptr_array_add (extension_apps, flatpak_decomposed_ref (ref));
|
||
}
|
||
}
|
||
|
||
return g_steal_pointer (&extension_app_map);
|
||
}
|
||
|
||
GPtrArray *
|
||
flatpak_dir_list_app_refs_with_runtime_extension (FlatpakDir *self,
|
||
GHashTable **runtime_app_map,
|
||
GHashTable **extension_app_map,
|
||
FlatpakDecomposed *runtime_ext_ref,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
GPtrArray *apps;
|
||
|
||
g_assert (runtime_app_map != NULL);
|
||
g_assert (extension_app_map != NULL);
|
||
|
||
if (*runtime_app_map == NULL)
|
||
*runtime_app_map = flatpak_dir_get_runtime_app_map (self, cancellable, error);
|
||
|
||
if (*runtime_app_map == NULL)
|
||
return NULL;
|
||
|
||
if (*extension_app_map == NULL)
|
||
*extension_app_map = flatpak_dir_get_extension_app_map (self, *runtime_app_map, cancellable, error);
|
||
|
||
if (*extension_app_map == NULL)
|
||
return NULL;
|
||
|
||
apps = g_hash_table_lookup (*extension_app_map, runtime_ext_ref);
|
||
if (apps == NULL) /* unused extension */
|
||
return g_ptr_array_new_with_free_func ((GDestroyNotify)flatpak_decomposed_unref);
|
||
|
||
return g_ptr_array_ref (apps);
|
||
}
|
||
|
||
GVariant *
|
||
flatpak_dir_read_latest_commit (FlatpakDir *self,
|
||
const char *remote,
|
||
FlatpakDecomposed *ref,
|
||
char **out_checksum,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autofree char *res = NULL;
|
||
g_autoptr(GVariant) commit_data = NULL;
|
||
|
||
if (!flatpak_repo_resolve_rev (self->repo, NULL, remote, flatpak_decomposed_get_ref (ref), FALSE,
|
||
&res, cancellable, error))
|
||
return NULL;
|
||
|
||
if (!ostree_repo_load_commit (self->repo, res, &commit_data, NULL, error))
|
||
return NULL;
|
||
|
||
if (out_checksum)
|
||
*out_checksum = g_steal_pointer (&res);
|
||
|
||
return g_steal_pointer (&commit_data);
|
||
}
|
||
|
||
|
||
char *
|
||
flatpak_dir_read_latest (FlatpakDir *self,
|
||
const char *remote,
|
||
const char *ref,
|
||
char **out_alt_id,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autofree char *alt_id = NULL;
|
||
g_autofree char *res = NULL;
|
||
|
||
if (!flatpak_repo_resolve_rev (self->repo, NULL, remote, ref, FALSE,
|
||
&res, cancellable, error))
|
||
return NULL;
|
||
|
||
if (out_alt_id)
|
||
{
|
||
g_autoptr(GVariant) commit_data = NULL;
|
||
g_autoptr(GVariant) commit_metadata = NULL;
|
||
|
||
if (!ostree_repo_load_commit (self->repo, res, &commit_data, NULL, error))
|
||
return NULL;
|
||
|
||
commit_metadata = g_variant_get_child_value (commit_data, 0);
|
||
g_variant_lookup (commit_metadata, "xa.alt-id", "s", &alt_id);
|
||
|
||
*out_alt_id = g_steal_pointer (&alt_id);
|
||
}
|
||
|
||
return g_steal_pointer (&res);
|
||
}
|
||
|
||
char *
|
||
flatpak_dir_read_active (FlatpakDir *self,
|
||
FlatpakDecomposed *ref,
|
||
GCancellable *cancellable)
|
||
{
|
||
g_autoptr(GFile) deploy_base = NULL;
|
||
g_autoptr(GFile) active_link = NULL;
|
||
g_autoptr(GFileInfo) file_info = NULL;
|
||
|
||
deploy_base = flatpak_dir_get_deploy_dir (self, ref);
|
||
active_link = g_file_get_child (deploy_base, "active");
|
||
|
||
file_info = g_file_query_info (active_link, OSTREE_GIO_FAST_QUERYINFO,
|
||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||
cancellable, NULL);
|
||
if (file_info == NULL)
|
||
return NULL;
|
||
|
||
return g_strdup (g_file_info_get_symlink_target (file_info));
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_set_active (FlatpakDir *self,
|
||
FlatpakDecomposed *ref,
|
||
const char *active_id,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
gboolean ret = FALSE;
|
||
g_autoptr(GFile) deploy_base = NULL;
|
||
g_autoptr(GFile) active_tmp_link = NULL;
|
||
g_autoptr(GFile) active_link = NULL;
|
||
g_autoptr(GError) my_error = NULL;
|
||
g_autofree char *tmpname = g_strdup (".active-XXXXXX");
|
||
|
||
deploy_base = flatpak_dir_get_deploy_dir (self, ref);
|
||
active_link = g_file_get_child (deploy_base, "active");
|
||
|
||
if (active_id != NULL)
|
||
{
|
||
glnx_gen_temp_name (tmpname);
|
||
active_tmp_link = g_file_get_child (deploy_base, tmpname);
|
||
if (!g_file_make_symbolic_link (active_tmp_link, active_id, cancellable, error))
|
||
goto out;
|
||
|
||
if (!flatpak_file_rename (active_tmp_link,
|
||
active_link,
|
||
cancellable, error))
|
||
goto out;
|
||
}
|
||
else
|
||
{
|
||
if (!g_file_delete (active_link, cancellable, &my_error) &&
|
||
!g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||
{
|
||
g_propagate_error (error, my_error);
|
||
my_error = NULL;
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
ret = TRUE;
|
||
out:
|
||
return ret;
|
||
}
|
||
|
||
static void
|
||
maybe_reload_dbus_config (GCancellable *cancellable)
|
||
{
|
||
g_autoptr(GDBusConnection) session_bus = NULL;
|
||
g_autoptr(GVariant) ret = NULL;
|
||
|
||
session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, cancellable, NULL);
|
||
if (!session_bus)
|
||
return;
|
||
|
||
ret = g_dbus_connection_call_sync (session_bus,
|
||
"org.freedesktop.DBus",
|
||
"/org/freedesktop/DBus",
|
||
"org.freedesktop.DBus",
|
||
"ReloadConfig",
|
||
NULL,
|
||
NULL,
|
||
G_DBUS_CALL_FLAGS_NONE,
|
||
2000,
|
||
cancellable,
|
||
NULL);
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_run_triggers (FlatpakDir *self,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
gboolean ret = FALSE;
|
||
g_autoptr(GFileEnumerator) dir_enum = NULL;
|
||
g_autoptr(GFileInfo) child_info = NULL;
|
||
g_autoptr(GFile) triggersdir = NULL;
|
||
GError *temp_error = NULL;
|
||
const char *triggerspath;
|
||
|
||
maybe_reload_dbus_config (cancellable);
|
||
|
||
if (flatpak_dir_use_system_helper (self, NULL))
|
||
{
|
||
const char *installation = flatpak_dir_get_id (self);
|
||
|
||
if (!flatpak_dir_system_helper_call_run_triggers (self,
|
||
FLATPAK_HELPER_RUN_TRIGGERS_FLAGS_NONE,
|
||
installation ? installation : "",
|
||
cancellable,
|
||
error))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
triggerspath = g_getenv ("FLATPAK_TRIGGERSDIR");
|
||
if (triggerspath == NULL)
|
||
triggerspath = FLATPAK_TRIGGERDIR;
|
||
|
||
g_info ("running triggers from %s", triggerspath);
|
||
|
||
triggersdir = g_file_new_for_path (triggerspath);
|
||
|
||
dir_enum = g_file_enumerate_children (triggersdir, "standard::type,standard::name",
|
||
0, cancellable, error);
|
||
if (!dir_enum)
|
||
goto out;
|
||
|
||
while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL)
|
||
{
|
||
g_autoptr(GFile) child = NULL;
|
||
const char *name;
|
||
GError *trigger_error = NULL;
|
||
|
||
name = g_file_info_get_name (child_info);
|
||
|
||
child = g_file_get_child (triggersdir, name);
|
||
|
||
if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_REGULAR &&
|
||
g_str_has_suffix (name, ".trigger"))
|
||
{
|
||
/* We need to canonicalize the basedir, because if has a symlink
|
||
somewhere the bind mount will be on the target of that, not
|
||
at that exact path. */
|
||
g_autofree char *basedir_orig = g_file_get_path (self->basedir);
|
||
g_autofree char *basedir = realpath (basedir_orig, NULL);
|
||
g_autoptr(FlatpakBwrap) bwrap = NULL;
|
||
g_autofree char *commandline = NULL;
|
||
|
||
g_info ("running trigger %s", name);
|
||
|
||
bwrap = flatpak_bwrap_new (NULL);
|
||
|
||
#ifndef DISABLE_SANDBOXED_TRIGGERS
|
||
flatpak_bwrap_add_arg (bwrap, flatpak_get_bwrap ());
|
||
flatpak_bwrap_add_args (bwrap,
|
||
"--unshare-ipc",
|
||
"--unshare-net",
|
||
"--unshare-pid",
|
||
"--ro-bind", "/", "/",
|
||
"--proc", "/proc",
|
||
"--dev", "/dev",
|
||
"--bind", basedir, basedir,
|
||
"--",
|
||
NULL);
|
||
#endif
|
||
flatpak_bwrap_add_args (bwrap,
|
||
flatpak_file_get_path_cached (child),
|
||
basedir,
|
||
NULL);
|
||
flatpak_bwrap_finish (bwrap);
|
||
|
||
commandline = flatpak_quote_argv ((const char **) bwrap->argv->pdata, -1);
|
||
g_info ("Running '%s'", commandline);
|
||
|
||
/* We use LEAVE_DESCRIPTORS_OPEN and close them in the child_setup
|
||
* to work around a deadlock in GLib < 2.60 */
|
||
if (!g_spawn_sync ("/",
|
||
(char **) bwrap->argv->pdata,
|
||
NULL,
|
||
G_SPAWN_SEARCH_PATH | G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
|
||
flatpak_bwrap_child_setup_cb, bwrap->fds,
|
||
NULL, NULL,
|
||
NULL, &trigger_error))
|
||
{
|
||
g_warning ("Error running trigger %s: %s", name, trigger_error->message);
|
||
g_clear_error (&trigger_error);
|
||
}
|
||
}
|
||
|
||
g_clear_object (&child_info);
|
||
}
|
||
|
||
if (temp_error != NULL)
|
||
{
|
||
g_propagate_error (error, temp_error);
|
||
goto out;
|
||
}
|
||
|
||
ret = TRUE;
|
||
out:
|
||
return ret;
|
||
}
|
||
|
||
static gboolean
|
||
read_fd (int fd,
|
||
struct stat *stat_buf,
|
||
gchar **contents,
|
||
gsize *length,
|
||
GError **error)
|
||
{
|
||
gchar *buf;
|
||
gsize bytes_read;
|
||
gsize size;
|
||
gsize alloc_size;
|
||
|
||
size = stat_buf->st_size;
|
||
|
||
alloc_size = size + 1;
|
||
buf = g_try_malloc (alloc_size);
|
||
|
||
if (buf == NULL)
|
||
{
|
||
g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_NOMEM,
|
||
_("Not enough memory"));
|
||
return FALSE;
|
||
}
|
||
|
||
bytes_read = 0;
|
||
while (bytes_read < size)
|
||
{
|
||
gssize rc;
|
||
|
||
rc = read (fd, buf + bytes_read, size - bytes_read);
|
||
|
||
if (rc < 0)
|
||
{
|
||
if (errno != EINTR)
|
||
{
|
||
int save_errno = errno;
|
||
|
||
g_free (buf);
|
||
g_set_error_literal (error, G_FILE_ERROR, g_file_error_from_errno (save_errno),
|
||
_("Failed to read from exported file"));
|
||
return FALSE;
|
||
}
|
||
}
|
||
else if (rc == 0)
|
||
{
|
||
break;
|
||
}
|
||
else
|
||
{
|
||
bytes_read += rc;
|
||
}
|
||
}
|
||
|
||
buf[bytes_read] = '\0';
|
||
|
||
if (length)
|
||
*length = bytes_read;
|
||
|
||
*contents = buf;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* This is conservative, but lets us avoid escaping most
|
||
regular Exec= lines, which is nice as that can sometimes
|
||
cause problems for apps launching desktop files. */
|
||
static gboolean
|
||
need_quotes (const char *str)
|
||
{
|
||
const char *p;
|
||
|
||
for (p = str; *p; p++)
|
||
{
|
||
if (!g_ascii_isalnum (*p) &&
|
||
strchr ("-_%.=:/@", *p) == NULL)
|
||
return TRUE;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static char *
|
||
maybe_quote (const char *str)
|
||
{
|
||
if (need_quotes (str))
|
||
return g_shell_quote (str);
|
||
return g_strdup (str);
|
||
}
|
||
|
||
typedef enum {
|
||
INI_FILE_TYPE_SEARCH_PROVIDER = 1,
|
||
} ExportedIniFileType;
|
||
|
||
static gboolean
|
||
export_ini_file (int parent_fd,
|
||
const char *name,
|
||
ExportedIniFileType ini_type,
|
||
struct stat *stat_buf,
|
||
char **target,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
glnx_autofd int desktop_fd = -1;
|
||
g_autofree char *tmpfile_name = g_strdup_printf ("export-ini-XXXXXX");
|
||
g_autoptr(GOutputStream) out_stream = NULL;
|
||
g_autofree gchar *data = NULL;
|
||
gsize data_len;
|
||
g_autofree gchar *new_data = NULL;
|
||
gsize new_data_len;
|
||
g_autoptr(GKeyFile) keyfile = NULL;
|
||
|
||
if (!flatpak_openat_noatime (parent_fd, name, &desktop_fd, cancellable, error) ||
|
||
!read_fd (desktop_fd, stat_buf, &data, &data_len, error))
|
||
return FALSE;
|
||
|
||
keyfile = g_key_file_new ();
|
||
if (!g_key_file_load_from_data (keyfile, data, data_len, G_KEY_FILE_KEEP_TRANSLATIONS, error))
|
||
return FALSE;
|
||
|
||
if (ini_type == INI_FILE_TYPE_SEARCH_PROVIDER)
|
||
g_key_file_set_boolean (keyfile, "Shell Search Provider", "DefaultDisabled", TRUE);
|
||
|
||
new_data = g_key_file_to_data (keyfile, &new_data_len, error);
|
||
if (new_data == NULL)
|
||
return FALSE;
|
||
|
||
if (!flatpak_open_in_tmpdir_at (parent_fd, 0755, tmpfile_name, &out_stream, cancellable, error) ||
|
||
!g_output_stream_write_all (out_stream, new_data, new_data_len, NULL, cancellable, error) ||
|
||
!g_output_stream_close (out_stream, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (target)
|
||
*target = g_steal_pointer (&tmpfile_name);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static inline void
|
||
xml_autoptr_cleanup_generic_free (void *p)
|
||
{
|
||
void **pp = (void **) p;
|
||
|
||
if (*pp)
|
||
xmlFree (*pp);
|
||
}
|
||
|
||
|
||
#define xml_autofree _GLIB_CLEANUP (xml_autoptr_cleanup_generic_free)
|
||
|
||
/* This verifies the basic layout of the files, then it removes
|
||
* any magic matches, and makes all glob matches have a very low
|
||
* priority (weight = 5). This should make it pretty safe to
|
||
* export mime types, because the should not override the system
|
||
* ones in any weird ways. */
|
||
static gboolean
|
||
rewrite_mime_xml (xmlDoc *doc)
|
||
{
|
||
xmlNode *root_element = xmlDocGetRootElement (doc);
|
||
xmlNode *top_node = NULL;
|
||
|
||
for (top_node = root_element; top_node; top_node = top_node->next)
|
||
{
|
||
xmlNode *mime_node = NULL;
|
||
if (top_node->type != XML_ELEMENT_NODE)
|
||
continue;
|
||
|
||
if (strcmp ((char *) top_node->name, "mime-info") != 0)
|
||
return FALSE;
|
||
|
||
for (mime_node = top_node->children; mime_node; mime_node = mime_node->next)
|
||
{
|
||
xmlNode *sub_node = NULL;
|
||
xmlNode *next_sub_node = NULL;
|
||
|
||
if (mime_node->type != XML_ELEMENT_NODE)
|
||
continue;
|
||
|
||
if (strcmp ((char *) mime_node->name, "mime-type") != 0)
|
||
return FALSE;
|
||
|
||
for (sub_node = mime_node->children; sub_node; sub_node = next_sub_node)
|
||
{
|
||
next_sub_node = sub_node->next;
|
||
|
||
if (sub_node->type != XML_ELEMENT_NODE)
|
||
continue;
|
||
|
||
if (strcmp ((char *) sub_node->name, "magic") == 0)
|
||
{
|
||
g_warning ("Removing magic mime rule from exports");
|
||
xmlUnlinkNode (sub_node);
|
||
xmlFreeNode (sub_node);
|
||
}
|
||
else if (strcmp ((char *) sub_node->name, "glob") == 0)
|
||
{
|
||
xmlSetProp (sub_node,
|
||
(const xmlChar *) "weight",
|
||
(const xmlChar *) "5");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
export_mime_file (int parent_fd,
|
||
const char *name,
|
||
struct stat *stat_buf,
|
||
char **target,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
glnx_autofd int desktop_fd = -1;
|
||
g_autofree char *tmpfile_name = g_strdup_printf ("export-mime-XXXXXX");
|
||
g_autoptr(GOutputStream) out_stream = NULL;
|
||
g_autofree gchar *data = NULL;
|
||
gsize data_len;
|
||
xmlDoc *doc = NULL;
|
||
xml_autofree xmlChar *xmlbuff = NULL;
|
||
int buffersize;
|
||
|
||
if (!flatpak_openat_noatime (parent_fd, name, &desktop_fd, cancellable, error) ||
|
||
!read_fd (desktop_fd, stat_buf, &data, &data_len, error))
|
||
return FALSE;
|
||
|
||
doc = xmlReadMemory (data, data_len, NULL, NULL, 0);
|
||
if (doc == NULL)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_EXPORT_FAILED, _("Error reading mimetype xml file"));
|
||
|
||
if (!rewrite_mime_xml (doc))
|
||
{
|
||
xmlFreeDoc (doc);
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_EXPORT_FAILED, _("Invalid mimetype xml file"));
|
||
}
|
||
|
||
xmlDocDumpFormatMemory (doc, &xmlbuff, &buffersize, 1);
|
||
xmlFreeDoc (doc);
|
||
|
||
if (!flatpak_open_in_tmpdir_at (parent_fd, 0755, tmpfile_name, &out_stream, cancellable, error) ||
|
||
!g_output_stream_write_all (out_stream, xmlbuff, buffersize, NULL, cancellable, error) ||
|
||
!g_output_stream_close (out_stream, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (target)
|
||
*target = g_steal_pointer (&tmpfile_name);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static char *
|
||
format_flatpak_run_args_from_run_opts (GStrv flatpak_run_args)
|
||
{
|
||
GString *str;
|
||
GStrv iter = flatpak_run_args;
|
||
|
||
if (flatpak_run_args == NULL)
|
||
return NULL;
|
||
|
||
str = g_string_new ("");
|
||
for (; *iter != NULL; ++iter)
|
||
{
|
||
if (g_strcmp0 (*iter, "no-a11y-bus") == 0)
|
||
g_string_append_printf (str, " --no-a11y-bus");
|
||
else if (g_strcmp0 (*iter, "no-documents-portal") == 0)
|
||
g_string_append_printf (str, " --no-documents-portal");
|
||
}
|
||
|
||
return g_string_free (str, FALSE);
|
||
}
|
||
|
||
static gboolean
|
||
export_desktop_file (const char *app,
|
||
const char *branch,
|
||
const char *arch,
|
||
GKeyFile *metadata,
|
||
const char * const *previous_ids,
|
||
int parent_fd,
|
||
const char *name,
|
||
struct stat *stat_buf,
|
||
char **target,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
glnx_autofd int desktop_fd = -1;
|
||
g_autofree char *tmpfile_name = g_strdup_printf ("export-desktop-XXXXXX");
|
||
g_autoptr(GOutputStream) out_stream = NULL;
|
||
g_autofree gchar *data = NULL;
|
||
gsize data_len;
|
||
g_autofree gchar *new_data = NULL;
|
||
gsize new_data_len;
|
||
g_autoptr(GKeyFile) keyfile = NULL;
|
||
g_auto(GStrv) groups = NULL;
|
||
g_autofree char *escaped_app = maybe_quote (app);
|
||
g_autofree char *escaped_branch = maybe_quote (branch);
|
||
g_autofree char *escaped_arch = maybe_quote (arch);
|
||
int i;
|
||
const char *flatpak;
|
||
|
||
if (!flatpak_openat_noatime (parent_fd, name, &desktop_fd, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (!read_fd (desktop_fd, stat_buf, &data, &data_len, error))
|
||
return FALSE;
|
||
|
||
keyfile = g_key_file_new ();
|
||
if (!g_key_file_load_from_data (keyfile, data, data_len, G_KEY_FILE_KEEP_TRANSLATIONS, error))
|
||
return FALSE;
|
||
|
||
if (g_str_has_suffix (name, ".service"))
|
||
{
|
||
g_autofree gchar *dbus_name = NULL;
|
||
g_autofree gchar *expected_dbus_name = g_strndup (name, strlen (name) - strlen (".service"));
|
||
|
||
dbus_name = g_key_file_get_string (keyfile, "D-BUS Service", "Name", NULL);
|
||
|
||
if (dbus_name == NULL || strcmp (dbus_name, expected_dbus_name) != 0)
|
||
{
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_EXPORT_FAILED,
|
||
_("D-Bus service file '%s' has wrong name"), name);
|
||
}
|
||
}
|
||
|
||
if (g_str_has_suffix (name, ".desktop"))
|
||
{
|
||
gsize length;
|
||
g_auto(GStrv) tags = g_key_file_get_string_list (metadata,
|
||
"Application",
|
||
"tags", &length,
|
||
NULL);
|
||
|
||
if (tags != NULL)
|
||
{
|
||
g_key_file_set_string_list (keyfile,
|
||
G_KEY_FILE_DESKTOP_GROUP,
|
||
"X-Flatpak-Tags",
|
||
(const char * const *) tags, length);
|
||
}
|
||
|
||
/* Add a marker so consumers can easily find out that this launches a sandbox */
|
||
g_key_file_set_string (keyfile, G_KEY_FILE_DESKTOP_GROUP, "X-Flatpak", app);
|
||
|
||
/* Disable krunner dbusplugins by default, so that flatpak applications cannot
|
||
* unintentionally grab sensitive search data.
|
||
*/
|
||
if (g_key_file_get_boolean (keyfile, G_KEY_FILE_DESKTOP_GROUP,
|
||
"X-KDE-PluginInfo-EnabledByDefault", NULL))
|
||
{
|
||
g_key_file_set_boolean (keyfile, G_KEY_FILE_DESKTOP_GROUP, "X-KDE-PluginInfo-EnabledByDefault", FALSE);
|
||
}
|
||
|
||
/* If the app has been renamed, add its old .desktop filename to
|
||
* X-Flatpak-RenamedFrom in the new .desktop file, taking care not to
|
||
* introduce duplicates.
|
||
*/
|
||
if (previous_ids != NULL)
|
||
{
|
||
const char *X_FLATPAK_RENAMED_FROM = "X-Flatpak-RenamedFrom";
|
||
g_auto(GStrv) renamed_from = g_key_file_get_string_list (keyfile,
|
||
G_KEY_FILE_DESKTOP_GROUP,
|
||
X_FLATPAK_RENAMED_FROM,
|
||
NULL, NULL);
|
||
g_autoptr(GPtrArray) merged = g_ptr_array_new_with_free_func (g_free);
|
||
g_autoptr(GHashTable) seen = g_hash_table_new (g_str_hash, g_str_equal);
|
||
const char *new_suffix;
|
||
|
||
for (i = 0; renamed_from != NULL && renamed_from[i] != NULL; i++)
|
||
{
|
||
if (!g_hash_table_contains (seen, renamed_from[i]))
|
||
{
|
||
gchar *copy = g_strdup (renamed_from[i]);
|
||
g_hash_table_insert (seen, copy, copy);
|
||
g_ptr_array_add (merged, g_steal_pointer (©));
|
||
}
|
||
}
|
||
|
||
/* If an app was renamed from com.example.Foo to net.example.Bar, and
|
||
* the new version exports net.example.Bar-suffix.desktop, we assume the
|
||
* old version exported com.example.Foo-suffix.desktop.
|
||
*
|
||
* This assertion is true because
|
||
* flatpak_name_matches_one_wildcard_prefix() is called on all
|
||
* exported files before we get here.
|
||
*/
|
||
g_assert (g_str_has_prefix (name, app));
|
||
/* ".desktop" for the "main" desktop file; something like
|
||
* "-suffix.desktop" for extra ones.
|
||
*/
|
||
new_suffix = name + strlen (app);
|
||
|
||
for (i = 0; previous_ids[i] != NULL; i++)
|
||
{
|
||
g_autofree gchar *previous_desktop = g_strconcat (previous_ids[i], new_suffix, NULL);
|
||
if (!g_hash_table_contains (seen, previous_desktop))
|
||
{
|
||
g_hash_table_insert (seen, previous_desktop, previous_desktop);
|
||
g_ptr_array_add (merged, g_steal_pointer (&previous_desktop));
|
||
}
|
||
}
|
||
|
||
if (merged->len > 0)
|
||
{
|
||
g_ptr_array_add (merged, NULL);
|
||
g_key_file_set_string_list (keyfile,
|
||
G_KEY_FILE_DESKTOP_GROUP,
|
||
X_FLATPAK_RENAMED_FROM,
|
||
(const char * const *) merged->pdata,
|
||
merged->len - 1);
|
||
}
|
||
}
|
||
}
|
||
|
||
groups = g_key_file_get_groups (keyfile, NULL);
|
||
|
||
for (i = 0; groups[i] != NULL; i++)
|
||
{
|
||
gint old_argc;
|
||
g_auto(GStrv) old_argv = NULL;
|
||
g_autofree gchar *old_exec = NULL;
|
||
g_autoptr(GString) new_exec = NULL;
|
||
g_auto(GStrv) flatpak_run_opts = g_key_file_get_string_list (keyfile, groups[i], "X-Flatpak-RunOptions", NULL, NULL);
|
||
g_autofree char *flatpak_run_args = format_flatpak_run_args_from_run_opts (flatpak_run_opts);
|
||
|
||
g_key_file_remove_key (keyfile, groups[i], "X-Flatpak-RunOptions", NULL);
|
||
g_key_file_remove_key (keyfile, groups[i], "TryExec", NULL);
|
||
|
||
/* Remove this to make sure nothing tries to execute it outside the sandbox*/
|
||
g_key_file_remove_key (keyfile, groups[i], "X-GNOME-Bugzilla-ExtraInfoScript", NULL);
|
||
|
||
new_exec = g_string_new ("");
|
||
if ((flatpak = g_getenv ("FLATPAK_BINARY")) == NULL)
|
||
flatpak = FLATPAK_BINDIR "/flatpak";
|
||
|
||
g_string_append_printf (new_exec,
|
||
"%s run --branch=%s --arch=%s",
|
||
flatpak,
|
||
escaped_branch,
|
||
escaped_arch);
|
||
|
||
if (flatpak_run_args != NULL)
|
||
g_string_append_printf (new_exec, "%s", flatpak_run_args);
|
||
|
||
old_exec = g_key_file_get_string (keyfile, groups[i], "Exec", NULL);
|
||
if (old_exec && g_shell_parse_argv (old_exec, &old_argc, &old_argv, NULL) && old_argc >= 1)
|
||
{
|
||
int j;
|
||
g_autofree char *command = maybe_quote (old_argv[0]);
|
||
|
||
g_string_append_printf (new_exec, " --command=%s", command);
|
||
|
||
for (j = 1; j < old_argc; j++)
|
||
{
|
||
if (strcasecmp (old_argv[j], "%f") == 0 ||
|
||
strcasecmp (old_argv[j], "%u") == 0)
|
||
{
|
||
g_string_append (new_exec, " --file-forwarding");
|
||
break;
|
||
}
|
||
}
|
||
|
||
g_string_append (new_exec, " ");
|
||
g_string_append (new_exec, escaped_app);
|
||
|
||
for (j = 1; j < old_argc; j++)
|
||
{
|
||
g_autofree char *arg = maybe_quote (old_argv[j]);
|
||
|
||
if (strcasecmp (arg, "%f") == 0)
|
||
g_string_append_printf (new_exec, " @@ %s @@", arg);
|
||
else if (strcasecmp (arg, "%u") == 0)
|
||
g_string_append_printf (new_exec, " @@u %s @@", arg);
|
||
else if (g_str_has_prefix (arg, "@@"))
|
||
{
|
||
flatpak_fail_error (error, FLATPAK_ERROR_EXPORT_FAILED,
|
||
_("Invalid Exec argument %s"), arg);
|
||
return FALSE;
|
||
}
|
||
else
|
||
g_string_append_printf (new_exec, " %s", arg);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
g_string_append (new_exec, " ");
|
||
g_string_append (new_exec, escaped_app);
|
||
}
|
||
|
||
g_key_file_set_string (keyfile, groups[i], G_KEY_FILE_DESKTOP_KEY_EXEC, new_exec->str);
|
||
}
|
||
|
||
new_data = g_key_file_to_data (keyfile, &new_data_len, error);
|
||
if (new_data == NULL)
|
||
return FALSE;
|
||
|
||
if (!flatpak_open_in_tmpdir_at (parent_fd, 0755, tmpfile_name, &out_stream, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (!g_output_stream_write_all (out_stream, new_data, new_data_len, NULL, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (!g_output_stream_close (out_stream, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (target)
|
||
*target = g_steal_pointer (&tmpfile_name);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
rewrite_export_dir (const char *app,
|
||
const char *branch,
|
||
const char *arch,
|
||
GKeyFile *metadata,
|
||
const char * const *previous_ids,
|
||
FlatpakContext *context,
|
||
int source_parent_fd,
|
||
const char *source_name,
|
||
const char *source_path,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
gboolean ret = FALSE;
|
||
g_auto(GLnxDirFdIterator) source_iter = {0};
|
||
g_autoptr(GHashTable) visited_children = NULL;
|
||
struct dirent *dent;
|
||
gboolean exports_allowed = FALSE;
|
||
g_auto(GStrv) allowed_prefixes = NULL;
|
||
g_auto(GStrv) allowed_extensions = NULL;
|
||
gboolean require_exact_match = FALSE;
|
||
|
||
if (!glnx_dirfd_iterator_init_at (source_parent_fd, source_name, FALSE, &source_iter, error))
|
||
goto out;
|
||
|
||
exports_allowed = flatpak_context_get_allowed_exports (context, source_path, app,
|
||
&allowed_extensions, &allowed_prefixes, &require_exact_match);
|
||
|
||
visited_children = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
||
|
||
while (TRUE)
|
||
{
|
||
struct stat stbuf;
|
||
|
||
if (!glnx_dirfd_iterator_next_dent (&source_iter, &dent, cancellable, error))
|
||
goto out;
|
||
|
||
if (dent == NULL)
|
||
break;
|
||
|
||
if (g_hash_table_contains (visited_children, dent->d_name))
|
||
continue;
|
||
|
||
/* Avoid processing the same file again if it was re-created during an export */
|
||
g_hash_table_insert (visited_children, g_strdup (dent->d_name), GINT_TO_POINTER (1));
|
||
|
||
if (fstatat (source_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) == -1)
|
||
{
|
||
if (errno == ENOENT)
|
||
{
|
||
continue;
|
||
}
|
||
else
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
if (S_ISDIR (stbuf.st_mode))
|
||
{
|
||
g_autofree char *path = g_build_filename (source_path, dent->d_name, NULL);
|
||
|
||
if (!rewrite_export_dir (app, branch, arch, metadata, previous_ids, context,
|
||
source_iter.fd, dent->d_name,
|
||
path, cancellable, error))
|
||
goto out;
|
||
}
|
||
else if (S_ISREG (stbuf.st_mode) && exports_allowed)
|
||
{
|
||
g_autofree gchar *name_without_extension = NULL;
|
||
g_autofree gchar *new_name = NULL;
|
||
int i;
|
||
|
||
for (i = 0; allowed_extensions[i] != NULL; i++)
|
||
{
|
||
if (g_str_has_suffix (dent->d_name, allowed_extensions[i]))
|
||
break;
|
||
}
|
||
|
||
if (allowed_extensions[i] == NULL)
|
||
{
|
||
g_warning ("Invalid extension for %s in app %s, removing.", dent->d_name, app);
|
||
if (unlinkat (source_iter.fd, dent->d_name, 0) != 0 && errno != ENOENT)
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
goto out;
|
||
}
|
||
continue;
|
||
}
|
||
|
||
name_without_extension = g_strndup (dent->d_name, strlen (dent->d_name) - strlen (allowed_extensions[i]));
|
||
|
||
if (!flatpak_name_matches_one_wildcard_prefix (name_without_extension, (const char * const *) allowed_prefixes, require_exact_match))
|
||
{
|
||
g_warning ("Non-prefixed filename %s in app %s, removing.", dent->d_name, app);
|
||
if (unlinkat (source_iter.fd, dent->d_name, 0) != 0 && errno != ENOENT)
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
if (g_str_has_suffix (dent->d_name, ".desktop") ||
|
||
g_str_has_suffix (dent->d_name, ".service"))
|
||
{
|
||
if (!export_desktop_file (app, branch, arch, metadata, previous_ids,
|
||
source_iter.fd, dent->d_name, &stbuf, &new_name, cancellable, error))
|
||
goto out;
|
||
}
|
||
|
||
if (strcmp (source_name, "search-providers") == 0 &&
|
||
g_str_has_suffix (dent->d_name, ".ini"))
|
||
{
|
||
if (!export_ini_file (source_iter.fd, dent->d_name, INI_FILE_TYPE_SEARCH_PROVIDER,
|
||
&stbuf, &new_name, cancellable, error))
|
||
goto out;
|
||
}
|
||
|
||
if (strcmp (source_name, "packages") == 0 &&
|
||
g_str_has_suffix (dent->d_name, ".xml"))
|
||
{
|
||
if (!export_mime_file (source_iter.fd, dent->d_name,
|
||
&stbuf, &new_name, cancellable, error))
|
||
goto out;
|
||
}
|
||
|
||
if (new_name)
|
||
{
|
||
g_hash_table_insert (visited_children, g_strdup (new_name), GINT_TO_POINTER (1));
|
||
|
||
if (renameat (source_iter.fd, new_name, source_iter.fd, dent->d_name) != 0)
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
goto out;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
g_warning ("Not exporting file %s of unsupported type.", dent->d_name);
|
||
if (unlinkat (source_iter.fd, dent->d_name, 0) != 0 && errno != ENOENT)
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
goto out;
|
||
}
|
||
}
|
||
}
|
||
|
||
ret = TRUE;
|
||
out:
|
||
|
||
return ret;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_rewrite_export_dir (const char *app,
|
||
const char *branch,
|
||
const char *arch,
|
||
GKeyFile *metadata,
|
||
const char * const *previous_ids,
|
||
GFile *source,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
gboolean ret = FALSE;
|
||
g_autoptr(GFile) parent = g_file_get_parent (source);
|
||
glnx_autofd int parentfd = -1;
|
||
g_autofree char *name = g_file_get_basename (source);
|
||
|
||
/* Start with a source path of "" - we don't care about
|
||
* the "export" component and we want to start path traversal
|
||
* relative to it. */
|
||
const char *source_path = "";
|
||
g_autoptr(FlatpakContext) context = flatpak_context_new ();
|
||
|
||
if (!flatpak_context_load_metadata (context, metadata, error))
|
||
return FALSE;
|
||
|
||
if (!glnx_opendirat (AT_FDCWD,
|
||
flatpak_file_get_path_cached (parent),
|
||
TRUE,
|
||
&parentfd,
|
||
error))
|
||
return FALSE;
|
||
|
||
/* The fds are closed by this call */
|
||
if (!rewrite_export_dir (app, branch, arch, metadata, previous_ids, context,
|
||
parentfd, name, source_path,
|
||
cancellable, error))
|
||
goto out;
|
||
|
||
ret = TRUE;
|
||
|
||
out:
|
||
return ret;
|
||
}
|
||
|
||
|
||
static gboolean
|
||
export_dir (int source_parent_fd,
|
||
const char *source_name,
|
||
const char *source_symlink_prefix,
|
||
const char *source_relpath,
|
||
int destination_parent_fd,
|
||
const char *destination_name,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
gboolean ret = FALSE;
|
||
int res;
|
||
g_auto(GLnxDirFdIterator) source_iter = {0};
|
||
glnx_autofd int destination_dfd = -1;
|
||
struct dirent *dent;
|
||
|
||
if (!glnx_dirfd_iterator_init_at (source_parent_fd, source_name, FALSE, &source_iter, error))
|
||
goto out;
|
||
|
||
do
|
||
res = mkdirat (destination_parent_fd, destination_name, 0755);
|
||
while (G_UNLIKELY (res == -1 && errno == EINTR));
|
||
if (res == -1)
|
||
{
|
||
if (errno != EEXIST)
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
if (!glnx_opendirat (destination_parent_fd, destination_name, TRUE,
|
||
&destination_dfd, error))
|
||
goto out;
|
||
|
||
while (TRUE)
|
||
{
|
||
struct stat stbuf;
|
||
|
||
if (!glnx_dirfd_iterator_next_dent (&source_iter, &dent, cancellable, error))
|
||
goto out;
|
||
|
||
if (dent == NULL)
|
||
break;
|
||
|
||
if (fstatat (source_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) == -1)
|
||
{
|
||
if (errno == ENOENT)
|
||
{
|
||
continue;
|
||
}
|
||
else
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
if (S_ISDIR (stbuf.st_mode))
|
||
{
|
||
g_autofree gchar *child_symlink_prefix = g_build_filename ("..", source_symlink_prefix, dent->d_name, NULL);
|
||
g_autofree gchar *child_relpath = g_strconcat (source_relpath, dent->d_name, "/", NULL);
|
||
|
||
if (!export_dir (source_iter.fd, dent->d_name, child_symlink_prefix, child_relpath, destination_dfd, dent->d_name,
|
||
cancellable, error))
|
||
goto out;
|
||
}
|
||
else if (S_ISREG (stbuf.st_mode))
|
||
{
|
||
g_autofree char *symlink_name = g_strdup (".export-symlink-XXXXXX");
|
||
g_autofree gchar *target = NULL;
|
||
|
||
target = g_build_filename (source_symlink_prefix, dent->d_name, NULL);
|
||
|
||
for (int count = 0; count < 100; count++)
|
||
{
|
||
glnx_gen_temp_name (symlink_name);
|
||
|
||
if (symlinkat (target, destination_dfd, symlink_name) != 0)
|
||
{
|
||
if (errno == EEXIST)
|
||
continue;
|
||
|
||
glnx_set_error_from_errno (error);
|
||
goto out;
|
||
}
|
||
|
||
if (renameat (destination_dfd, symlink_name, destination_dfd, dent->d_name) != 0)
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
goto out;
|
||
}
|
||
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
ret = TRUE;
|
||
out:
|
||
|
||
return ret;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_export_dir (GFile *source,
|
||
GFile *destination,
|
||
const char *symlink_prefix,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
const char *exported_subdirs[] = {
|
||
"share/applications", "../..",
|
||
"share/icons", "../..",
|
||
"share/dbus-1/services", "../../..",
|
||
"share/gnome-shell/search-providers", "../../..",
|
||
"share/krunner/dbusplugins", "../../..",
|
||
"share/mime/packages", "../../..",
|
||
"share/metainfo", "../..",
|
||
"bin", "..",
|
||
};
|
||
int i;
|
||
|
||
for (i = 0; i < G_N_ELEMENTS (exported_subdirs); i = i + 2)
|
||
{
|
||
/* The fds are closed by this call */
|
||
g_autoptr(GFile) sub_source = g_file_resolve_relative_path (source, exported_subdirs[i]);
|
||
g_autoptr(GFile) sub_destination = g_file_resolve_relative_path (destination, exported_subdirs[i]);
|
||
g_autofree char *sub_symlink_prefix = g_build_filename (exported_subdirs[i + 1], symlink_prefix, exported_subdirs[i], NULL);
|
||
|
||
if (!g_file_query_exists (sub_source, cancellable))
|
||
continue;
|
||
|
||
if (!flatpak_mkdir_p (sub_destination, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (!export_dir (AT_FDCWD, flatpak_file_get_path_cached (sub_source), sub_symlink_prefix, "",
|
||
AT_FDCWD, flatpak_file_get_path_cached (sub_destination),
|
||
cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_update_exports (FlatpakDir *self,
|
||
const char *changed_app,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
gboolean ret = FALSE;
|
||
g_autoptr(GFile) exports = NULL;
|
||
g_autoptr(FlatpakDecomposed) current_ref = NULL;
|
||
g_autofree char *active_id = NULL;
|
||
g_autofree char *symlink_prefix = NULL;
|
||
|
||
exports = flatpak_dir_get_exports_dir (self);
|
||
|
||
if (!flatpak_mkdir_p (exports, cancellable, error))
|
||
goto out;
|
||
|
||
if (changed_app &&
|
||
(current_ref = flatpak_dir_current_ref (self, changed_app, cancellable)) &&
|
||
(active_id = flatpak_dir_read_active (self, current_ref, cancellable)))
|
||
{
|
||
g_autoptr(GFile) deploy_base = NULL;
|
||
g_autoptr(GFile) active = NULL;
|
||
g_autoptr(GFile) export = NULL;
|
||
|
||
deploy_base = flatpak_dir_get_deploy_dir (self, current_ref);
|
||
active = g_file_get_child (deploy_base, active_id);
|
||
export = g_file_get_child (active, "export");
|
||
|
||
if (g_file_query_exists (export, cancellable))
|
||
{
|
||
symlink_prefix = g_build_filename ("..", "app", changed_app, "current", "active", "export", NULL);
|
||
if (!flatpak_export_dir (export, exports,
|
||
symlink_prefix,
|
||
cancellable,
|
||
error))
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
if (!flatpak_remove_dangling_symlinks (exports, cancellable, error))
|
||
goto out;
|
||
|
||
ret = TRUE;
|
||
|
||
out:
|
||
return ret;
|
||
}
|
||
|
||
static gboolean
|
||
extract_extra_data (FlatpakDir *self,
|
||
const char *checksum,
|
||
GFile *extradir,
|
||
gboolean *created_extra_data,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GVariant) detached_metadata = NULL;
|
||
g_autoptr(GVariant) extra_data = NULL;
|
||
g_autoptr(GVariant) extra_data_sources = NULL;
|
||
g_autoptr(GError) local_error = NULL;
|
||
gsize i, n_extra_data = 0;
|
||
gsize n_extra_data_sources;
|
||
|
||
extra_data_sources = flatpak_repo_get_extra_data_sources (self->repo, checksum,
|
||
cancellable, &local_error);
|
||
if (extra_data_sources == NULL)
|
||
{
|
||
/* This should protect us against potential errors at the OSTree level
|
||
(e.g. ostree_repo_load_variant), so that we don't report success. */
|
||
if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
n_extra_data_sources = g_variant_n_children (extra_data_sources);
|
||
if (n_extra_data_sources == 0)
|
||
return TRUE;
|
||
|
||
g_info ("extracting extra data to %s", flatpak_file_get_path_cached (extradir));
|
||
|
||
if (!ostree_repo_read_commit_detached_metadata (self->repo, checksum, &detached_metadata,
|
||
cancellable, error))
|
||
{
|
||
g_prefix_error (error, _("While getting detached metadata: "));
|
||
return FALSE;
|
||
}
|
||
|
||
if (detached_metadata == NULL)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Extra data missing in detached metadata"));
|
||
|
||
extra_data = g_variant_lookup_value (detached_metadata, "xa.extra-data",
|
||
G_VARIANT_TYPE ("a(ayay)"));
|
||
if (extra_data == NULL)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Extra data missing in detached metadata"));
|
||
|
||
n_extra_data = g_variant_n_children (extra_data);
|
||
if (n_extra_data < n_extra_data_sources)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Extra data missing in detached metadata"));
|
||
|
||
if (!flatpak_mkdir_p (extradir, cancellable, error))
|
||
{
|
||
g_prefix_error (error, _("While creating extradir: "));
|
||
return FALSE;
|
||
}
|
||
|
||
for (i = 0; i < n_extra_data_sources; i++)
|
||
{
|
||
g_autofree char *extra_data_sha256 = NULL;
|
||
const guchar *extra_data_sha256_bytes;
|
||
const char *extra_data_source_name = NULL;
|
||
guint64 download_size;
|
||
gboolean found;
|
||
int j;
|
||
|
||
flatpak_repo_parse_extra_data_sources (extra_data_sources, i,
|
||
&extra_data_source_name,
|
||
&download_size,
|
||
NULL,
|
||
&extra_data_sha256_bytes,
|
||
NULL);
|
||
|
||
if (extra_data_sha256_bytes == NULL)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid checksum for extra data"));
|
||
|
||
extra_data_sha256 = ostree_checksum_from_bytes (extra_data_sha256_bytes);
|
||
|
||
/* We need to verify the data in the commitmeta again, because the only signed
|
||
thing is the commit, which has the source info. We could have accidentally
|
||
picked up some other commitmeta stuff from the remote, or via the untrusted
|
||
local-pull of the system helper. */
|
||
found = FALSE;
|
||
for (j = 0; j < n_extra_data; j++)
|
||
{
|
||
g_autoptr(GVariant) content = NULL;
|
||
g_autoptr(GFile) dest = NULL;
|
||
g_autofree char *sha256 = NULL;
|
||
const char *extra_data_name = NULL;
|
||
const guchar *data;
|
||
gsize len;
|
||
|
||
g_variant_get_child (extra_data, j, "(^ay@ay)",
|
||
&extra_data_name,
|
||
&content);
|
||
|
||
if (strcmp (extra_data_source_name, extra_data_name) != 0)
|
||
continue;
|
||
|
||
data = g_variant_get_data (content);
|
||
len = g_variant_get_size (content);
|
||
|
||
if (len != download_size)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Wrong size for extra data"));
|
||
|
||
sha256 = g_compute_checksum_for_data (G_CHECKSUM_SHA256, data, len);
|
||
if (strcmp (sha256, extra_data_sha256) != 0)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid checksum for extra data"));
|
||
|
||
dest = g_file_get_child (extradir, extra_data_name);
|
||
if (!g_file_replace_contents (dest,
|
||
g_variant_get_data (content),
|
||
g_variant_get_size (content),
|
||
NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION,
|
||
NULL, cancellable, error))
|
||
{
|
||
g_prefix_error (error, _("While writing extra data file '%s': "), extra_data_name);
|
||
return FALSE;
|
||
}
|
||
found = TRUE;
|
||
}
|
||
|
||
if (!found)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA,
|
||
_("Extra data %s missing in detached metadata"),
|
||
extra_data_source_name);
|
||
}
|
||
|
||
*created_extra_data = TRUE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static void
|
||
child_setup (gpointer user_data)
|
||
{
|
||
GArray *fd_array = user_data;
|
||
int i;
|
||
|
||
/* If no fd_array was specified, don't care. */
|
||
if (fd_array == NULL)
|
||
return;
|
||
|
||
/* Otherwise, mark not - close-on-exec all the fds in the array */
|
||
for (i = 0; i < fd_array->len; i++)
|
||
{
|
||
int fd = g_array_index (fd_array, int, i);
|
||
|
||
/* We also seek all fds to the start, because this lets
|
||
us use the same fd_array multiple times */
|
||
if (lseek (fd, 0, SEEK_SET) < 0)
|
||
g_printerr ("lseek error in child setup");
|
||
|
||
fcntl (fd, F_SETFD, 0);
|
||
}
|
||
}
|
||
|
||
static gboolean
|
||
apply_extra_data (FlatpakDir *self,
|
||
GFile *checkoutdir,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) metadata = NULL;
|
||
g_autofree char *metadata_contents = NULL;
|
||
gsize metadata_size;
|
||
g_autoptr(GKeyFile) metakey = NULL;
|
||
g_autofree char *id = NULL;
|
||
g_autofree char *runtime_pref = NULL;
|
||
g_autoptr(FlatpakDecomposed) runtime_ref = NULL;
|
||
g_autoptr(FlatpakDeploy) runtime_deploy = NULL;
|
||
g_autoptr(FlatpakBwrap) bwrap = NULL;
|
||
g_autoptr(GFile) app_files = NULL;
|
||
g_autoptr(GFile) apply_extra_file = NULL;
|
||
g_autoptr(GFile) app_export_file = NULL;
|
||
g_autoptr(GFile) extra_export_file = NULL;
|
||
g_autoptr(GFile) extra_files = NULL;
|
||
g_autoptr(GFile) runtime_files = NULL;
|
||
g_autoptr(FlatpakContext) app_context = NULL;
|
||
g_auto(GStrv) minimal_envp = NULL;
|
||
g_autofree char *runtime_arch = NULL;
|
||
int exit_status;
|
||
const char *group = FLATPAK_METADATA_GROUP_APPLICATION;
|
||
g_autoptr(GError) local_error = NULL;
|
||
FlatpakRunFlags run_flags;
|
||
|
||
apply_extra_file = g_file_resolve_relative_path (checkoutdir, "files/bin/apply_extra");
|
||
if (!g_file_query_exists (apply_extra_file, cancellable))
|
||
return TRUE;
|
||
|
||
metadata = g_file_get_child (checkoutdir, "metadata");
|
||
|
||
if (!g_file_load_contents (metadata, cancellable, &metadata_contents, &metadata_size, NULL, error))
|
||
return FALSE;
|
||
|
||
metakey = g_key_file_new ();
|
||
if (!g_key_file_load_from_data (metakey, metadata_contents, metadata_size, 0, error))
|
||
return FALSE;
|
||
|
||
id = g_key_file_get_string (metakey, group, FLATPAK_METADATA_KEY_NAME,
|
||
&local_error);
|
||
if (id == NULL)
|
||
{
|
||
group = FLATPAK_METADATA_GROUP_RUNTIME;
|
||
id = g_key_file_get_string (metakey, group, FLATPAK_METADATA_KEY_NAME,
|
||
NULL);
|
||
if (id == NULL)
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return FALSE;
|
||
}
|
||
g_clear_error (&local_error);
|
||
}
|
||
|
||
runtime_pref = g_key_file_get_string (metakey, group,
|
||
FLATPAK_METADATA_KEY_RUNTIME, error);
|
||
if (runtime_pref == NULL)
|
||
runtime_pref = g_key_file_get_string (metakey, FLATPAK_METADATA_GROUP_EXTENSION_OF,
|
||
FLATPAK_METADATA_KEY_RUNTIME, NULL);
|
||
if (runtime_pref == NULL)
|
||
return FALSE;
|
||
|
||
runtime_ref = flatpak_decomposed_new_from_pref (FLATPAK_KINDS_RUNTIME, runtime_pref, error);
|
||
if (runtime_ref == NULL)
|
||
return FALSE;
|
||
runtime_arch = flatpak_decomposed_dup_arch (runtime_ref);
|
||
|
||
if (!g_key_file_get_boolean (metakey, FLATPAK_METADATA_GROUP_EXTRA_DATA,
|
||
FLATPAK_METADATA_KEY_NO_RUNTIME, NULL))
|
||
{
|
||
/* We pass in self here so that we ensure that we find the runtime in case it only
|
||
exists in this installation (which might be custom) */
|
||
/* TODO: This is a circular dependency between flatpak-dir (which
|
||
* deals with a single installation) and flatpak-dir-utils (which
|
||
* deals with all the installations on the system). */
|
||
runtime_deploy = flatpak_find_deploy_for_ref (flatpak_decomposed_get_ref (runtime_ref), NULL, self, cancellable, error);
|
||
if (runtime_deploy == NULL)
|
||
return FALSE;
|
||
runtime_files = flatpak_deploy_get_files (runtime_deploy);
|
||
}
|
||
|
||
app_files = g_file_get_child (checkoutdir, "files");
|
||
app_export_file = g_file_get_child (checkoutdir, "export");
|
||
extra_files = g_file_get_child (app_files, "extra");
|
||
extra_export_file = g_file_get_child (extra_files, "export");
|
||
|
||
minimal_envp = flatpak_run_get_minimal_env (FALSE, FALSE);
|
||
bwrap = flatpak_bwrap_new (minimal_envp);
|
||
flatpak_bwrap_add_args (bwrap, flatpak_get_bwrap (), NULL);
|
||
|
||
if (runtime_files)
|
||
flatpak_bwrap_add_args (bwrap,
|
||
"--ro-bind", flatpak_file_get_path_cached (runtime_files), "/usr",
|
||
"--lock-file", "/usr/.ref",
|
||
NULL);
|
||
|
||
flatpak_bwrap_add_args (bwrap,
|
||
"--ro-bind", flatpak_file_get_path_cached (app_files), "/app",
|
||
"--bind", flatpak_file_get_path_cached (extra_files), "/app/extra",
|
||
"--chdir", "/app/extra",
|
||
/* We run as root in the system-helper case, so drop all caps */
|
||
"--cap-drop", "ALL",
|
||
NULL);
|
||
|
||
/* Run flags which equal flatpak run --sandbox */
|
||
run_flags = (FLATPAK_RUN_FLAG_SANDBOX |
|
||
FLATPAK_RUN_FLAG_NO_SESSION_HELPER |
|
||
FLATPAK_RUN_FLAG_NO_SESSION_BUS_PROXY |
|
||
FLATPAK_RUN_FLAG_NO_SYSTEM_BUS_PROXY |
|
||
FLATPAK_RUN_FLAG_NO_A11Y_BUS_PROXY);
|
||
|
||
/* Might need multiarch in apply_extra (see e.g. #3742).
|
||
* Should be pretty safe in this limited context. */
|
||
run_flags |= FLATPAK_RUN_FLAG_MULTIARCH;
|
||
|
||
/* This sandbox is run as root and /proc/self/exe can sometimes be used to
|
||
* access outside files (see cd21428).
|
||
* Disable /proc entirely in this context. */
|
||
run_flags |= FLATPAK_RUN_FLAG_NO_PROC;
|
||
|
||
if (!flatpak_run_setup_base_argv (bwrap, runtime_files, NULL, runtime_arch,
|
||
run_flags, error))
|
||
return FALSE;
|
||
|
||
app_context = flatpak_context_new ();
|
||
|
||
if (!flatpak_run_add_environment_args (bwrap, NULL, run_flags, id,
|
||
app_context, NULL, NULL, -1,
|
||
NULL, NULL, cancellable, error))
|
||
return FALSE;
|
||
|
||
flatpak_bwrap_populate_runtime_dir (bwrap, NULL);
|
||
|
||
flatpak_bwrap_envp_to_args (bwrap);
|
||
|
||
flatpak_bwrap_add_args (bwrap, "--", "/app/bin/apply_extra", NULL);
|
||
|
||
flatpak_bwrap_finish (bwrap);
|
||
|
||
g_info ("Running /app/bin/apply_extra ");
|
||
|
||
/* We run the sandbox without caps, but it can still create files owned by itself with
|
||
* arbitrary permissions, including setuid myself. This is extra risky in the case where
|
||
* this runs as root in the system helper case. We canonicalize the permissions at the
|
||
* end, but to avoid non-canonical permissions leaking out before then we make the
|
||
* toplevel dir only accessible to the user */
|
||
if (chmod (flatpak_file_get_path_cached (extra_files), 0700) != 0)
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
return FALSE;
|
||
}
|
||
|
||
if (!g_spawn_sync (NULL,
|
||
(char **) bwrap->argv->pdata,
|
||
bwrap->envp,
|
||
G_SPAWN_SEARCH_PATH,
|
||
child_setup, bwrap->fds,
|
||
NULL, NULL,
|
||
&exit_status,
|
||
error))
|
||
return FALSE;
|
||
|
||
if (!flatpak_canonicalize_permissions (AT_FDCWD, flatpak_file_get_path_cached (extra_files),
|
||
getuid () == 0 ? 0 : -1,
|
||
getuid () == 0 ? 0 : -1,
|
||
error))
|
||
return FALSE;
|
||
|
||
if (exit_status != 0)
|
||
{
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||
_("apply_extra script failed, exit status %d"), exit_status);
|
||
return FALSE;
|
||
}
|
||
|
||
if (g_file_query_exists (extra_export_file, cancellable))
|
||
{
|
||
if (!flatpak_mkdir_p (app_export_file, cancellable, error))
|
||
return FALSE;
|
||
if (!flatpak_cp_a (extra_export_file,
|
||
app_export_file,
|
||
FLATPAK_CP_FLAGS_MERGE,
|
||
cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* Check the user’s parental controls allow installation of @ref by looking at
|
||
* its cached @deploy_data, which contains its content rating as extracted from
|
||
* its AppData when it was originally downloaded. That’s compared to the
|
||
* parental controls policy loaded from the #MctManager.
|
||
*
|
||
* If @ref should not be installed, an error is returned. */
|
||
static gboolean
|
||
flatpak_dir_check_parental_controls (FlatpakDir *self,
|
||
const char *ref,
|
||
GBytes *deploy_data,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
#ifdef HAVE_LIBMALCONTENT
|
||
#ifdef USE_SYSTEM_HELPER
|
||
g_autoptr(GError) local_error = NULL;
|
||
const char *on_session = g_getenv ("FLATPAK_SYSTEM_HELPER_ON_SESSION");
|
||
g_autoptr(GDBusConnection) dbus_connection = NULL;
|
||
g_autoptr(MctManager) manager = NULL;
|
||
g_autoptr(MctAppFilter) app_filter = NULL;
|
||
const char *content_rating_type;
|
||
g_autoptr(GHashTable) content_rating = NULL;
|
||
g_autoptr(AutoPolkitAuthority) authority = NULL;
|
||
g_autoptr(AutoPolkitSubject) subject = NULL;
|
||
gint subject_uid;
|
||
g_autoptr(AutoPolkitAuthorizationResult) result = NULL;
|
||
gboolean authorized;
|
||
gboolean repo_installation_allowed, app_is_appropriate;
|
||
PolkitCheckAuthorizationFlags polkit_flags;
|
||
MctManagerGetValueFlags manager_flags;
|
||
|
||
/* Assume that root is allowed to install any ref and shouldn't have any
|
||
* parental controls restrictions applied to them. Note that this branch
|
||
* must not be taken if this code is running within the system-helper, as that
|
||
* runs as root but on behalf of another process. If running within the
|
||
* system-helper, self->source_pid is non-zero. */
|
||
if (self->source_pid == 0 && getuid () == 0)
|
||
{
|
||
g_info ("Skipping parental controls check for %s due to running as root", ref);
|
||
return TRUE;
|
||
}
|
||
|
||
/* The ostree-metadata and appstream/ branches should not have any parental
|
||
* controls restrictions. Similarly, for the moment, there is no point in
|
||
* restricting runtimes. */
|
||
if (!g_str_has_prefix (ref, "app/"))
|
||
return TRUE;
|
||
|
||
g_info ("Getting parental controls details for %s from %s",
|
||
ref, flatpak_deploy_data_get_origin (deploy_data));
|
||
|
||
if (on_session != NULL)
|
||
{
|
||
/* FIXME: Instead of skipping the parental controls check in the test
|
||
* environment, make a mock service for it.
|
||
* https://github.com/flatpak/flatpak/issues/2993 */
|
||
g_info ("Skipping parental controls check for %s since the "
|
||
"system bus is unavailable in the test environment", ref);
|
||
return TRUE;
|
||
}
|
||
|
||
dbus_connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, cancellable, &local_error);
|
||
if (dbus_connection == NULL)
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return FALSE;
|
||
}
|
||
|
||
if (self->user || self->source_pid == 0)
|
||
subject = polkit_unix_process_new_for_owner (getpid (), 0, getuid ());
|
||
else
|
||
subject = polkit_unix_process_new_for_owner (self->source_pid, 0, -1);
|
||
|
||
/* Get the parental controls for the invoking user. */
|
||
subject_uid = polkit_unix_process_get_uid (POLKIT_UNIX_PROCESS (subject));
|
||
if (subject_uid == -1)
|
||
{
|
||
g_set_error_literal (error, G_DBUS_ERROR, G_DBUS_ERROR_AUTH_FAILED,
|
||
"Failed to get subject UID");
|
||
return FALSE;
|
||
}
|
||
|
||
manager = mct_manager_new (dbus_connection);
|
||
manager_flags = MCT_MANAGER_GET_VALUE_FLAGS_NONE;
|
||
if (!flatpak_dir_get_no_interaction (self))
|
||
manager_flags |= MCT_MANAGER_GET_VALUE_FLAGS_INTERACTIVE;
|
||
app_filter = mct_manager_get_app_filter (manager, subject_uid,
|
||
manager_flags,
|
||
cancellable, &local_error);
|
||
if (g_error_matches (local_error, MCT_MANAGER_ERROR, MCT_MANAGER_ERROR_DISABLED))
|
||
{
|
||
g_info ("Skipping parental controls check for %s since parental "
|
||
"controls are disabled globally", ref);
|
||
return TRUE;
|
||
}
|
||
else if (g_error_matches (local_error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN) ||
|
||
g_error_matches (local_error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER))
|
||
{
|
||
g_info ("Skipping parental controls check for %s since a required "
|
||
"service was not found", ref);
|
||
return TRUE;
|
||
}
|
||
else if (local_error != NULL)
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return FALSE;
|
||
}
|
||
|
||
/* Check the content rating against the parental controls. If the app is
|
||
* allowed to be installed, return so immediately. */
|
||
repo_installation_allowed = ((self->user && mct_app_filter_is_user_installation_allowed (app_filter)) ||
|
||
(!self->user && mct_app_filter_is_system_installation_allowed (app_filter)));
|
||
|
||
content_rating_type = flatpak_deploy_data_get_appdata_content_rating_type (deploy_data);
|
||
content_rating = flatpak_deploy_data_get_appdata_content_rating (deploy_data);
|
||
app_is_appropriate = flatpak_oars_check_rating (content_rating, content_rating_type,
|
||
app_filter);
|
||
|
||
if (repo_installation_allowed && app_is_appropriate)
|
||
{
|
||
g_info ("Parental controls policy satisfied for %s", ref);
|
||
return TRUE;
|
||
}
|
||
|
||
/* Otherwise, check polkit to see if the admin is going to allow the user to
|
||
* override their parental controls policy. We can’t pass any details to this
|
||
* polkit check, since it could be run by the user or by the system helper,
|
||
* and non-root users can’t pass details to polkit checks. */
|
||
authority = polkit_authority_get_sync (NULL, error);
|
||
if (authority == NULL)
|
||
return FALSE;
|
||
|
||
polkit_flags = POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE;
|
||
if (!flatpak_dir_get_no_interaction (self))
|
||
polkit_flags |= POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION;
|
||
result = polkit_authority_check_authorization_sync (authority, subject,
|
||
"org.freedesktop.Flatpak.override-parental-controls",
|
||
NULL,
|
||
polkit_flags,
|
||
cancellable, error);
|
||
if (result == NULL)
|
||
return FALSE;
|
||
|
||
authorized = polkit_authorization_result_get_is_authorized (result);
|
||
|
||
if (!authorized)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_PERMISSION_DENIED,
|
||
/* Translators: The placeholder is for an app ref. */
|
||
_("Installing %s is not allowed by the policy set by your administrator"),
|
||
ref);
|
||
|
||
g_info ("Parental controls policy overridden by polkit for %s", ref);
|
||
#endif /* USE_SYSTEM_HELPER */
|
||
#endif /* HAVE_LIBMALCONTENT */
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* We create a deploy ref for the currently deployed version of all refs to avoid
|
||
deployed commits being pruned when e.g. we pull --no-deploy. */
|
||
static gboolean
|
||
flatpak_dir_update_deploy_ref (FlatpakDir *self,
|
||
const char *ref,
|
||
const char *checksum,
|
||
GError **error)
|
||
{
|
||
g_autofree char *deploy_ref = g_strconcat ("deploy/", ref, NULL);
|
||
|
||
if (!ostree_repo_set_ref_immediate (self->repo, NULL, deploy_ref, checksum, NULL, error))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_deploy (FlatpakDir *self,
|
||
const char *origin,
|
||
FlatpakDecomposed *ref,
|
||
const char *checksum_or_latest,
|
||
const char * const * subpaths,
|
||
const char * const * previous_ids,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autofree char *resolved_ref = NULL;
|
||
g_autofree char *ref_id = NULL;
|
||
g_autoptr(GFile) root = NULL;
|
||
g_autoptr(GFile) deploy_base = NULL;
|
||
glnx_autofd int deploy_base_dfd = -1;
|
||
g_autoptr(GFile) checkoutdir = NULL;
|
||
g_autoptr(GFile) bindir = NULL;
|
||
g_autofree char *checkoutdirpath = NULL;
|
||
const char *checkoutdir_basename;
|
||
g_autoptr(GFile) real_checkoutdir = NULL;
|
||
g_autoptr(GFile) dotref = NULL;
|
||
g_autoptr(GFile) files_etc = NULL;
|
||
g_autoptr(GFile) deploy_data_file = NULL;
|
||
g_autoptr(GVariant) commit_data = NULL;
|
||
g_autoptr(GBytes) deploy_data = NULL;
|
||
g_autoptr(GFile) export = NULL;
|
||
g_autoptr(GFile) extradir = NULL;
|
||
g_autoptr(GKeyFile) keyfile = NULL;
|
||
guint64 installed_size = 0;
|
||
OstreeRepoCheckoutAtOptions options = { 0, };
|
||
const char *checksum;
|
||
glnx_autofd int checkoutdir_dfd = -1;
|
||
const char *xa_ref = NULL;
|
||
g_autofree char *checkout_basename = NULL;
|
||
gboolean created_extra_data = FALSE;
|
||
g_autoptr(GVariant) commit_metadata = NULL;
|
||
g_auto(GLnxLockFile) lock = { 0, };
|
||
g_autoptr(GFile) metadata_file = NULL;
|
||
g_autofree char *metadata_contents = NULL;
|
||
gsize metadata_size = 0;
|
||
const char *flatpak;
|
||
g_auto(GLnxTmpDir) tmp_dir_handle = { 0, };
|
||
|
||
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
||
return FALSE;
|
||
|
||
ref_id = flatpak_decomposed_dup_id (ref);
|
||
|
||
/* Keep a shared repo lock to avoid prunes removing objects we're relying on
|
||
* while we do the checkout. This could happen if the ref changes after we
|
||
* read its current value for the checkout. */
|
||
if (!flatpak_dir_repo_lock (self, &lock, LOCK_SH, cancellable, error))
|
||
return FALSE;
|
||
|
||
deploy_base = flatpak_dir_get_deploy_dir (self, ref);
|
||
|
||
if (!glnx_opendirat (AT_FDCWD, flatpak_file_get_path_cached (deploy_base), TRUE, &deploy_base_dfd, error))
|
||
return FALSE;
|
||
|
||
/* There used to be a bug here where temporary files beneath @deploy_base were not removed,
|
||
* which could use quite a lot of space over time, so we check for these and remove them.
|
||
* We only do so for the current app to avoid every deploy operation iterating over
|
||
* every app directory and all their immediate descendents. That would be a bit much I/O. */
|
||
remove_old_deploy_tmpdirs (deploy_base);
|
||
|
||
if (checksum_or_latest == NULL)
|
||
{
|
||
g_info ("No checksum specified, getting tip of %s from origin %s", flatpak_decomposed_get_ref (ref), origin);
|
||
|
||
resolved_ref = flatpak_dir_read_latest (self, origin, flatpak_decomposed_get_ref (ref), NULL, cancellable, error);
|
||
if (resolved_ref == NULL)
|
||
{
|
||
g_prefix_error (error, _("While trying to resolve ref %s: "), flatpak_decomposed_get_ref (ref));
|
||
return FALSE;
|
||
}
|
||
|
||
checksum = resolved_ref;
|
||
g_info ("tip resolved to: %s", checksum);
|
||
}
|
||
else
|
||
{
|
||
checksum = checksum_or_latest;
|
||
g_info ("Looking for checksum %s in local repo", checksum);
|
||
if (!ostree_repo_read_commit (self->repo, checksum, NULL, NULL, cancellable, NULL))
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("%s is not available"), flatpak_decomposed_get_ref (ref));
|
||
}
|
||
|
||
if (!ostree_repo_load_commit (self->repo, checksum, &commit_data, NULL, error))
|
||
return FALSE;
|
||
|
||
commit_metadata = g_variant_get_child_value (commit_data, 0);
|
||
checkout_basename = flatpak_dir_get_deploy_subdir (self, checksum, subpaths);
|
||
|
||
real_checkoutdir = g_file_get_child (deploy_base, checkout_basename);
|
||
if (g_file_query_exists (real_checkoutdir, cancellable))
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_ALREADY_INSTALLED,
|
||
_("%s commit %s already installed"), flatpak_decomposed_get_ref (ref), checksum);
|
||
|
||
g_autofree char *template = g_strdup_printf (".%s-XXXXXX", checkout_basename);
|
||
|
||
if (!glnx_mkdtempat (deploy_base_dfd, template, 0755, &tmp_dir_handle, NULL))
|
||
{
|
||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||
_("Can't create deploy directory"));
|
||
return FALSE;
|
||
}
|
||
|
||
checkoutdir = g_file_get_child (deploy_base, tmp_dir_handle.path);
|
||
|
||
if (!ostree_repo_read_commit (self->repo, checksum, &root, NULL, cancellable, error))
|
||
{
|
||
g_prefix_error (error, _("Failed to read commit %s: "), checksum);
|
||
return FALSE;
|
||
}
|
||
|
||
if (!flatpak_repo_collect_sizes (self->repo, root, &installed_size, NULL, cancellable, error))
|
||
return FALSE;
|
||
|
||
options.mode = OSTREE_REPO_CHECKOUT_MODE_USER;
|
||
options.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES;
|
||
options.enable_fsync = FALSE; /* We checkout to a temp dir and sync before moving it in place */
|
||
options.bareuseronly_dirs = TRUE; /* https://github.com/ostreedev/ostree/pull/927 */
|
||
checkoutdirpath = g_file_get_path (checkoutdir);
|
||
checkoutdir_basename = tmp_dir_handle.path; /* so checkoutdirpath = deploy_base_dfd / checkoutdir_basename */
|
||
|
||
if (subpaths == NULL || *subpaths == NULL)
|
||
{
|
||
if (!ostree_repo_checkout_at (self->repo, &options,
|
||
deploy_base_dfd, checkoutdir_basename,
|
||
checksum,
|
||
cancellable, error))
|
||
{
|
||
g_prefix_error (error, _("While trying to checkout %s into %s: "), checksum, checkoutdirpath);
|
||
return FALSE;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
g_autoptr(GFile) files = g_file_get_child (checkoutdir, "files");
|
||
int i;
|
||
|
||
if (!g_file_make_directory_with_parents (files, cancellable, error))
|
||
return FALSE;
|
||
|
||
options.subpath = "metadata";
|
||
|
||
if (!ostree_repo_checkout_at (self->repo, &options,
|
||
deploy_base_dfd, checkoutdir_basename,
|
||
checksum,
|
||
cancellable, error))
|
||
{
|
||
g_prefix_error (error, _("While trying to checkout metadata subpath: "));
|
||
return FALSE;
|
||
}
|
||
|
||
for (i = 0; subpaths[i] != NULL; i++)
|
||
{
|
||
g_autofree char *subpath = g_build_filename ("files", subpaths[i], NULL);
|
||
g_autofree char *dstpath = g_build_filename (checkoutdirpath, "/files", subpaths[i], NULL);
|
||
g_autofree char *dstpath_parent = g_path_get_dirname (dstpath);
|
||
g_autofree char *dstpath_relative_to_deploy_base = g_build_filename (checkoutdir_basename, "/files", subpaths[i], NULL);
|
||
g_autoptr(GFile) child = NULL;
|
||
|
||
child = g_file_resolve_relative_path (root, subpath);
|
||
|
||
if (!g_file_query_exists (child, cancellable))
|
||
{
|
||
g_info ("subpath %s not in tree", subpaths[i]);
|
||
continue;
|
||
}
|
||
|
||
if (g_mkdir_with_parents (dstpath_parent, 0755))
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
return FALSE;
|
||
}
|
||
|
||
options.subpath = subpath;
|
||
if (!ostree_repo_checkout_at (self->repo, &options,
|
||
deploy_base_dfd, dstpath_relative_to_deploy_base,
|
||
checksum,
|
||
cancellable, error))
|
||
{
|
||
g_prefix_error (error, _("While trying to checkout subpath ‘%s’: "), subpath);
|
||
return FALSE;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Extract any extra data */
|
||
extradir = g_file_resolve_relative_path (checkoutdir, "files/extra");
|
||
if (!flatpak_rm_rf (extradir, cancellable, error))
|
||
{
|
||
g_prefix_error (error, _("While trying to remove existing extra dir: "));
|
||
return FALSE;
|
||
}
|
||
|
||
if (!extract_extra_data (self, checksum, extradir, &created_extra_data, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (created_extra_data)
|
||
{
|
||
if (!apply_extra_data (self, checkoutdir, cancellable, error))
|
||
{
|
||
g_prefix_error (error, _("While trying to apply extra data: "));
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
g_variant_lookup (commit_metadata, "xa.ref", "&s", &xa_ref);
|
||
if (xa_ref != NULL)
|
||
{
|
||
gboolean gpg_verify_summary;
|
||
|
||
if (!ostree_repo_remote_get_gpg_verify_summary (self->repo, origin, &gpg_verify_summary, error))
|
||
return FALSE;
|
||
|
||
if (gpg_verify_summary)
|
||
{
|
||
/* If we're using signed summaries, then the security is really due to the signatures on
|
||
* the summary, and the xa.ref is not needed for security. In particular, endless are
|
||
* currently using one single commit on multiple branches to handle devel/stable promotion.
|
||
* So, to support this we report branch discrepancies as a warning, rather than as an error.
|
||
* See https://github.com/flatpak/flatpak/pull/1013 for more discussion.
|
||
*/
|
||
FlatpakDecomposed *checkout_ref = ref;
|
||
g_autoptr(FlatpakDecomposed) commit_ref = NULL;
|
||
|
||
commit_ref = flatpak_decomposed_new_from_ref (xa_ref, error);
|
||
if (commit_ref == NULL)
|
||
{
|
||
g_prefix_error (error, _("Invalid commit ref %s: "), xa_ref);
|
||
return FALSE;
|
||
}
|
||
|
||
/* Fatal if kind/name/arch don't match. Warn for branch mismatch. */
|
||
if (!flatpak_decomposed_equal_except_branch (checkout_ref, commit_ref))
|
||
{
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
|
||
_("Deployed ref %s does not match commit (%s)"),
|
||
flatpak_decomposed_get_ref (ref), xa_ref);
|
||
return FALSE;
|
||
}
|
||
|
||
if (strcmp (flatpak_decomposed_get_branch (checkout_ref), flatpak_decomposed_get_branch (commit_ref)) != 0)
|
||
g_warning (_("Deployed ref %s branch does not match commit (%s)"),
|
||
flatpak_decomposed_get_ref (ref), xa_ref);
|
||
}
|
||
else if (strcmp (flatpak_decomposed_get_ref (ref), xa_ref) != 0)
|
||
{
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
|
||
_("Deployed ref %s does not match commit (%s)"), flatpak_decomposed_get_ref (ref), xa_ref);
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
keyfile = g_key_file_new ();
|
||
metadata_file = g_file_resolve_relative_path (checkoutdir, "metadata");
|
||
if (g_file_load_contents (metadata_file, NULL,
|
||
&metadata_contents,
|
||
&metadata_size, NULL, NULL))
|
||
{
|
||
if (!g_key_file_load_from_data (keyfile,
|
||
metadata_contents,
|
||
metadata_size,
|
||
0, error))
|
||
return FALSE;
|
||
|
||
if (!flatpak_check_required_version (flatpak_decomposed_get_ref (ref), keyfile, error))
|
||
return FALSE;
|
||
}
|
||
|
||
/* Check the metadata in the commit to make sure it matches the actual
|
||
* deployed metadata, in case we relied on the one in the commit for
|
||
* a decision
|
||
*/
|
||
if (!validate_commit_metadata (commit_data, flatpak_decomposed_get_ref (ref),
|
||
metadata_contents, metadata_size, error))
|
||
return FALSE;
|
||
|
||
dotref = g_file_resolve_relative_path (checkoutdir, "files/.ref");
|
||
if (!g_file_replace_contents (dotref, "", 0, NULL, FALSE,
|
||
G_FILE_CREATE_REPLACE_DESTINATION, NULL, cancellable, error))
|
||
return FALSE;
|
||
|
||
export = g_file_get_child (checkoutdir, "export");
|
||
|
||
/* Never export any binaries bundled with the app */
|
||
bindir = g_file_get_child (export, "bin");
|
||
if (!flatpak_rm_rf (bindir, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (flatpak_decomposed_is_runtime (ref))
|
||
{
|
||
/* Ensure that various files exist as regular files in /usr/etc, as we
|
||
want to bind-mount over them */
|
||
files_etc = g_file_resolve_relative_path (checkoutdir, "files/etc");
|
||
if (g_file_query_exists (files_etc, cancellable))
|
||
{
|
||
static const char * const etcfiles[] = {"passwd", "group", "machine-id" };
|
||
g_autoptr(GFile) etc_resolve_conf = g_file_get_child (files_etc, "resolv.conf");
|
||
int i;
|
||
for (i = 0; i < G_N_ELEMENTS (etcfiles); i++)
|
||
{
|
||
g_autoptr(GFile) etc_file = g_file_get_child (files_etc, etcfiles[i]);
|
||
GFileType type;
|
||
|
||
type = g_file_query_file_type (etc_file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||
cancellable);
|
||
if (type == G_FILE_TYPE_REGULAR)
|
||
continue;
|
||
|
||
if (type != G_FILE_TYPE_UNKNOWN)
|
||
{
|
||
/* Already exists, but not regular, probably symlink. Remove it */
|
||
if (!g_file_delete (etc_file, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
if (!g_file_replace_contents (etc_file, "", 0, NULL, FALSE,
|
||
G_FILE_CREATE_REPLACE_DESTINATION,
|
||
NULL, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
if (g_file_query_exists (etc_resolve_conf, cancellable) &&
|
||
!g_file_delete (etc_resolve_conf, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (!g_file_make_symbolic_link (etc_resolve_conf,
|
||
"/run/host/monitor/resolv.conf",
|
||
cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
/* Runtime should never export anything */
|
||
if (!flatpak_rm_rf (export, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
else /* is app */
|
||
{
|
||
g_autofree char *ref_arch = flatpak_decomposed_dup_arch (ref);
|
||
g_autofree char *ref_branch = flatpak_decomposed_dup_branch (ref);
|
||
g_autoptr(GFile) wrapper = g_file_get_child (bindir, ref_id);
|
||
g_autofree char *escaped_app = maybe_quote (ref_id);
|
||
g_autofree char *escaped_branch = maybe_quote (ref_branch);
|
||
g_autofree char *escaped_arch = maybe_quote (ref_arch);
|
||
g_autofree char *bin_data = NULL;
|
||
int r;
|
||
|
||
if (!flatpak_mkdir_p (bindir, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (!flatpak_rewrite_export_dir (ref_id, ref_branch, ref_arch,
|
||
keyfile, previous_ids, export,
|
||
cancellable,
|
||
error))
|
||
return FALSE;
|
||
if ((flatpak = g_getenv ("FLATPAK_BINARY")) == NULL)
|
||
flatpak = FLATPAK_BINDIR "/flatpak";
|
||
|
||
bin_data = g_strdup_printf ("#!/bin/sh\nexec %s run --branch=%s --arch=%s %s \"$@\"\n",
|
||
flatpak, escaped_branch, escaped_arch, escaped_app);
|
||
if (!g_file_replace_contents (wrapper, bin_data, strlen (bin_data), NULL, FALSE,
|
||
G_FILE_CREATE_REPLACE_DESTINATION, NULL, cancellable, error))
|
||
return FALSE;
|
||
|
||
do
|
||
r = fchmodat (AT_FDCWD, flatpak_file_get_path_cached (wrapper), 0755, 0);
|
||
while (G_UNLIKELY (r == -1 && errno == EINTR));
|
||
if (r == -1)
|
||
return glnx_throw_errno_prefix (error, "fchmodat");
|
||
}
|
||
|
||
deploy_data = flatpak_dir_new_deploy_data (self,
|
||
checkoutdir,
|
||
commit_data,
|
||
commit_metadata,
|
||
keyfile,
|
||
ref_id,
|
||
origin,
|
||
checksum,
|
||
(char **) subpaths,
|
||
installed_size,
|
||
previous_ids);
|
||
|
||
/* Check the app is actually allowed to be used by this user. This can block
|
||
* on getting authorisation. */
|
||
if (!flatpak_dir_check_parental_controls (self, flatpak_decomposed_get_ref (ref), deploy_data, cancellable, error))
|
||
return FALSE;
|
||
|
||
deploy_data_file = g_file_get_child (checkoutdir, "deploy");
|
||
if (!flatpak_bytes_save (deploy_data_file, deploy_data, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (!glnx_opendirat (deploy_base_dfd, checkoutdir_basename, TRUE, &checkoutdir_dfd, error))
|
||
return FALSE;
|
||
|
||
if (syncfs (checkoutdir_dfd) != 0)
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
return FALSE;
|
||
}
|
||
|
||
if (!g_file_move (checkoutdir, real_checkoutdir, G_FILE_COPY_NO_FALLBACK_FOR_MOVE,
|
||
cancellable, NULL, NULL, error))
|
||
return FALSE;
|
||
|
||
glnx_tmpdir_unset (&tmp_dir_handle);
|
||
|
||
if (!flatpak_dir_set_active (self, ref, checkout_basename, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (!flatpak_dir_update_deploy_ref (self, flatpak_decomposed_get_ref (ref), checksum, error))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* -origin remotes are deleted when the last ref referring to it is undeployed */
|
||
void
|
||
flatpak_dir_prune_origin_remote (FlatpakDir *self,
|
||
const char *remote)
|
||
{
|
||
if (remote != NULL &&
|
||
g_str_has_suffix (remote, "-origin") &&
|
||
flatpak_dir_get_remote_noenumerate (self, remote) &&
|
||
!flatpak_dir_remote_has_deploys (self, remote))
|
||
{
|
||
if (flatpak_dir_use_system_helper (self, NULL))
|
||
{
|
||
const char *installation = flatpak_dir_get_id (self);
|
||
g_autoptr(GVariant) gpg_data_v = NULL;
|
||
|
||
gpg_data_v = g_variant_ref_sink (g_variant_new_from_data (G_VARIANT_TYPE ("ay"), "", 0, TRUE, NULL, NULL));
|
||
|
||
flatpak_dir_system_helper_call_configure_remote (self,
|
||
FLATPAK_HELPER_CONFIGURE_FLAGS_NONE,
|
||
remote,
|
||
"",
|
||
gpg_data_v,
|
||
installation ? installation : "",
|
||
NULL, NULL);
|
||
}
|
||
else
|
||
flatpak_dir_remove_remote (self, FALSE, remote, NULL, NULL);
|
||
}
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_deploy_install (FlatpakDir *self,
|
||
FlatpakDecomposed *ref,
|
||
const char *origin,
|
||
const char **subpaths,
|
||
const char **previous_ids,
|
||
gboolean reinstall,
|
||
gboolean pin_on_deploy,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_auto(GLnxLockFile) lock = { 0, };
|
||
g_autoptr(GFile) deploy_base = NULL;
|
||
g_autoptr(GFile) old_deploy_dir = NULL;
|
||
gboolean created_deploy_base = FALSE;
|
||
gboolean ret = FALSE;
|
||
g_autoptr(GError) local_error = NULL;
|
||
g_autofree char *remove_ref_from_remote = NULL;
|
||
g_autofree char *commit = NULL;
|
||
g_autofree char *old_active = NULL;
|
||
|
||
if (!flatpak_dir_lock (self, &lock,
|
||
cancellable, error))
|
||
goto out;
|
||
|
||
old_deploy_dir = flatpak_dir_get_if_deployed (self, ref, NULL, cancellable);
|
||
if (old_deploy_dir != NULL)
|
||
{
|
||
old_active = flatpak_dir_read_active (self, ref, cancellable);
|
||
|
||
if (reinstall)
|
||
{
|
||
g_autoptr(GBytes) old_deploy = NULL;
|
||
const char *old_origin;
|
||
|
||
old_deploy = flatpak_load_deploy_data (old_deploy_dir, ref, self->repo, FLATPAK_DEPLOY_VERSION_ANY, cancellable, error);
|
||
if (old_deploy == NULL)
|
||
goto out;
|
||
|
||
/* If the old install was from a different remote, remove the ref */
|
||
old_origin = flatpak_deploy_data_get_origin (old_deploy);
|
||
if (strcmp (old_origin, origin) != 0)
|
||
remove_ref_from_remote = g_strdup (old_origin);
|
||
|
||
g_info ("Removing old deployment for reinstall");
|
||
if (!flatpak_dir_undeploy (self, ref, old_active,
|
||
TRUE, FALSE,
|
||
cancellable, error))
|
||
goto out;
|
||
}
|
||
else
|
||
{
|
||
g_autofree char *id = flatpak_decomposed_dup_id (ref);
|
||
g_autofree char *branch = flatpak_decomposed_dup_branch (ref);
|
||
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED,
|
||
_("%s branch %s already installed"), id, branch);
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
deploy_base = flatpak_dir_get_deploy_dir (self, ref);
|
||
if (!g_file_make_directory_with_parents (deploy_base, cancellable, &local_error))
|
||
{
|
||
if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_EXISTS))
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
/* After we create the deploy base we must goto out on errors */
|
||
created_deploy_base = TRUE;
|
||
|
||
if (!flatpak_dir_deploy (self, origin, ref, NULL, (const char * const *) subpaths,
|
||
previous_ids, cancellable, error))
|
||
goto out;
|
||
|
||
if (flatpak_decomposed_is_app (ref))
|
||
{
|
||
g_autofree char *id = flatpak_decomposed_dup_id (ref);
|
||
|
||
if (!flatpak_dir_make_current_ref (self, ref, cancellable, error))
|
||
goto out;
|
||
|
||
if (!flatpak_dir_update_exports (self, id, cancellable, error))
|
||
goto out;
|
||
}
|
||
|
||
/* Remove old ref if the reinstalled was from a different remote */
|
||
if (remove_ref_from_remote != NULL)
|
||
{
|
||
if (!flatpak_dir_remove_ref (self, remove_ref_from_remote, flatpak_decomposed_get_ref (ref), cancellable, error))
|
||
goto out;
|
||
|
||
flatpak_dir_prune_origin_remote (self, remove_ref_from_remote);
|
||
}
|
||
|
||
/* Release lock before doing possibly slow prune */
|
||
glnx_release_lock_file (&lock);
|
||
|
||
flatpak_dir_cleanup_removed (self, cancellable, NULL);
|
||
|
||
if (!flatpak_dir_mark_changed (self, error))
|
||
goto out;
|
||
|
||
/* Pin runtimes that are installed explicitly rather than pulled as
|
||
* dependencies so they are not automatically removed. */
|
||
if (pin_on_deploy &&
|
||
!flatpak_dir_config_append_pattern (self, "pinned",
|
||
flatpak_decomposed_get_ref (ref),
|
||
TRUE, NULL, error))
|
||
goto out;
|
||
|
||
ret = TRUE;
|
||
|
||
commit = flatpak_dir_read_active (self, ref, cancellable);
|
||
flatpak_dir_log (self, "deploy install", origin, flatpak_decomposed_get_ref (ref), commit, old_active, NULL,
|
||
"Installed %s from %s", flatpak_decomposed_get_ref (ref), origin);
|
||
|
||
out:
|
||
if (created_deploy_base && !ret)
|
||
flatpak_rm_rf (deploy_base, cancellable, NULL);
|
||
|
||
return ret;
|
||
}
|
||
|
||
|
||
gboolean
|
||
flatpak_dir_deploy_update (FlatpakDir *self,
|
||
FlatpakDecomposed *ref,
|
||
const char *checksum_or_latest,
|
||
const char **opt_subpaths,
|
||
const char **opt_previous_ids,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GBytes) old_deploy_data = NULL;
|
||
g_auto(GLnxLockFile) lock = { 0, };
|
||
g_autofree const char **old_subpaths = NULL;
|
||
g_autofree char *old_active = NULL;
|
||
const char *old_origin;
|
||
g_autofree char *commit = NULL;
|
||
g_autofree const char **previous_ids = NULL;
|
||
g_auto(GStrv) previous_ids_owned = NULL;
|
||
|
||
if (!flatpak_dir_lock (self, &lock,
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
old_deploy_data = flatpak_dir_get_deploy_data (self, ref,
|
||
FLATPAK_DEPLOY_VERSION_ANY,
|
||
cancellable, error);
|
||
if (old_deploy_data == NULL)
|
||
return FALSE;
|
||
|
||
old_active = flatpak_dir_read_active (self, ref, cancellable);
|
||
|
||
old_origin = flatpak_deploy_data_get_origin (old_deploy_data);
|
||
old_subpaths = flatpak_deploy_data_get_subpaths (old_deploy_data);
|
||
|
||
previous_ids = flatpak_deploy_data_get_previous_ids (old_deploy_data, NULL);
|
||
if (opt_previous_ids)
|
||
{
|
||
previous_ids_owned = flatpak_strv_merge ((char **) previous_ids, (char **) opt_previous_ids);
|
||
g_clear_pointer (&previous_ids, g_free);
|
||
}
|
||
else
|
||
previous_ids_owned = g_strdupv ((char **) previous_ids);
|
||
|
||
if (!flatpak_dir_deploy (self,
|
||
old_origin,
|
||
ref,
|
||
checksum_or_latest,
|
||
opt_subpaths ? opt_subpaths : old_subpaths,
|
||
(const char * const *) previous_ids_owned,
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
if (old_active &&
|
||
!flatpak_dir_undeploy (self, ref, old_active,
|
||
TRUE, FALSE,
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
if (flatpak_decomposed_is_app (ref))
|
||
{
|
||
g_autofree char *id = flatpak_decomposed_dup_id (ref);
|
||
|
||
if (!flatpak_dir_update_exports (self, id, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
/* Release lock before doing possibly slow prune */
|
||
glnx_release_lock_file (&lock);
|
||
|
||
if (!flatpak_dir_mark_changed (self, error))
|
||
return FALSE;
|
||
|
||
flatpak_dir_cleanup_removed (self, cancellable, NULL);
|
||
|
||
commit = flatpak_dir_read_active (self, ref, cancellable);
|
||
flatpak_dir_log (self, "deploy update", old_origin, flatpak_decomposed_get_ref (ref), commit, old_active, NULL,
|
||
"Updated %s from %s", flatpak_decomposed_get_ref (ref), old_origin);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static void
|
||
rewrite_one_dynamic_launcher (const char *portal_desktop_dir,
|
||
const char *portal_icon_dir,
|
||
const char *desktop_name,
|
||
const char *old_app_id,
|
||
const char *new_app_id)
|
||
{
|
||
g_autoptr(GKeyFile) old_key_file = NULL;
|
||
g_autoptr(GKeyFile) new_key_file = NULL;
|
||
g_autoptr(GFile) link_file = NULL;
|
||
g_autoptr(GFile) new_link_file = NULL;
|
||
g_autoptr(GString) data_string = NULL;
|
||
g_autoptr(GError) local_error = NULL;
|
||
g_autofree char *old_data = NULL;
|
||
g_autofree char *desktop_path = NULL;
|
||
g_autofree char *new_desktop_path = NULL;
|
||
g_autofree char *icon_path = NULL;
|
||
g_autofree char *relative_path = NULL;
|
||
g_autofree char *new_desktop = NULL;
|
||
const gchar *desktop_suffix;
|
||
|
||
g_assert (g_str_has_suffix (desktop_name, ".desktop"));
|
||
g_assert (g_str_has_prefix (desktop_name, old_app_id));
|
||
|
||
desktop_path = g_build_filename (portal_desktop_dir,
|
||
desktop_name, NULL);
|
||
old_key_file = g_key_file_new ();
|
||
if (!g_key_file_load_from_file (old_key_file, desktop_path,
|
||
G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS,
|
||
&local_error))
|
||
{
|
||
g_warning ("Error encountered loading key file %s: %s", desktop_path, local_error->message);
|
||
return;
|
||
}
|
||
if (!g_key_file_has_key (old_key_file, G_KEY_FILE_DESKTOP_GROUP, "X-Flatpak", NULL))
|
||
{
|
||
g_info ("Ignoring non-Flatpak dynamic launcher: %s", desktop_path);
|
||
return;
|
||
}
|
||
|
||
/* Fix paths in desktop file with a find-and-replace. The portal handled
|
||
* quoting the app ID in the Exec line for us.
|
||
*/
|
||
old_data = g_key_file_to_data (old_key_file, NULL, NULL);
|
||
data_string = g_string_new ((const char *)old_data);
|
||
g_string_replace (data_string, old_app_id, new_app_id, 0);
|
||
new_key_file = g_key_file_new ();
|
||
if (!g_key_file_load_from_data (new_key_file, data_string->str, -1,
|
||
G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS,
|
||
&local_error))
|
||
{
|
||
g_warning ("Cannot load desktop file %s after rewrite: %s", desktop_path, local_error->message);
|
||
g_warning ("Key file contents:\n%s\n", (const char *)data_string->str);
|
||
return;
|
||
}
|
||
|
||
/* Write it out at the new path */
|
||
desktop_suffix = desktop_name + strlen (old_app_id);
|
||
new_desktop = g_strconcat (new_app_id, desktop_suffix, NULL);
|
||
new_desktop_path = g_build_filename (portal_desktop_dir, new_desktop, NULL);
|
||
if (!g_key_file_save_to_file (new_key_file, new_desktop_path, &local_error))
|
||
{
|
||
g_warning ("Couldn't rewrite desktop file from %s to %s: %s",
|
||
desktop_path, new_desktop_path, local_error->message);
|
||
return;
|
||
}
|
||
|
||
/* Fix symlink */
|
||
link_file = g_file_new_build_filename (g_get_user_data_dir (), "applications", desktop_name, NULL);
|
||
relative_path = g_build_filename ("..", "xdg-desktop-portal", "applications", new_desktop, NULL);
|
||
if (!g_file_delete (link_file, NULL, &local_error) &&
|
||
!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||
{
|
||
g_info ("Unable to delete desktop file link %s: %s", desktop_name, local_error->message);
|
||
g_clear_error (&local_error);
|
||
}
|
||
|
||
new_link_file = g_file_new_build_filename (g_get_user_data_dir (), "applications", new_desktop, NULL);
|
||
if (!g_file_make_symbolic_link (new_link_file, relative_path, NULL, &local_error))
|
||
{
|
||
g_warning ("Unable to rename desktop file link %s -> %s: %s",
|
||
desktop_name, new_desktop, local_error->message);
|
||
return;
|
||
}
|
||
|
||
/* Delete the old desktop file */
|
||
unlink (desktop_path);
|
||
|
||
/* And rename the icon */
|
||
icon_path = g_key_file_get_string (old_key_file, G_KEY_FILE_DESKTOP_GROUP, "Icon", NULL);
|
||
if (g_str_has_prefix (icon_path, portal_icon_dir))
|
||
{
|
||
g_autoptr(GFile) icon_file = NULL;
|
||
g_autofree char *icon_basename = NULL;
|
||
g_autofree char *new_icon = NULL;
|
||
gchar *icon_suffix;
|
||
|
||
icon_file = g_file_new_for_path (icon_path);
|
||
icon_basename = g_path_get_basename (icon_path);
|
||
if (g_str_has_prefix (icon_basename, old_app_id))
|
||
{
|
||
icon_suffix = icon_basename + strlen (old_app_id);
|
||
new_icon = g_strconcat (new_app_id, icon_suffix, NULL);
|
||
if (!g_file_set_display_name (icon_file, new_icon, NULL, &local_error))
|
||
{
|
||
g_warning ("Unable to rename icon file %s -> %s: %s", icon_basename, new_icon,
|
||
local_error->message);
|
||
g_clear_error (&local_error);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
static void
|
||
rewrite_dynamic_launchers (FlatpakDecomposed *ref,
|
||
const char * const * previous_ids)
|
||
|
||
{
|
||
g_autoptr(GFile) portal_desktop_dir = NULL;
|
||
g_autofree char *portal_icon_path = NULL;
|
||
g_autofree char *app_id = NULL;
|
||
g_autoptr(GFileEnumerator) dir_enum = NULL;
|
||
g_autoptr(GError) local_error = NULL;
|
||
|
||
if (!flatpak_decomposed_is_app (ref))
|
||
return;
|
||
|
||
app_id = flatpak_decomposed_dup_id (ref);
|
||
|
||
/* Rename any dynamic launchers written by xdg-desktop-portal. The
|
||
* portal has its own code for renaming launchers on session start but we
|
||
* need to do it here as well so the launchers are correct in both cases:
|
||
* (1) the app rename transaction is being executed by the same user that
|
||
* has the launchers, or (2) the app is installed system-wide and another
|
||
* user has launchers.
|
||
*/
|
||
if (previous_ids != NULL)
|
||
{
|
||
portal_desktop_dir = g_file_new_build_filename (g_get_user_data_dir (),
|
||
"xdg-desktop-portal",
|
||
"applications", NULL);
|
||
portal_icon_path = g_build_filename (g_get_user_data_dir (),
|
||
"xdg-desktop-portal", "icons", NULL);
|
||
dir_enum = g_file_enumerate_children (portal_desktop_dir,
|
||
G_FILE_ATTRIBUTE_STANDARD_NAME,
|
||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||
NULL, &local_error);
|
||
}
|
||
if (dir_enum == NULL)
|
||
{
|
||
if (local_error && !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||
{
|
||
g_warning ("Failed to enumerate portal desktop dir %s: %s",
|
||
flatpak_file_get_path_cached (portal_desktop_dir),
|
||
local_error->message);
|
||
}
|
||
g_clear_error (&local_error);
|
||
}
|
||
else
|
||
{
|
||
g_autoptr(GFileInfo) child_info = NULL;
|
||
g_auto(GStrv) previous_ids_sorted = NULL;
|
||
|
||
/* Sort by decreasing length so we get the longest prefix below */
|
||
previous_ids_sorted = flatpak_strv_sort_by_length (previous_ids);
|
||
|
||
while ((child_info = g_file_enumerator_next_file (dir_enum, NULL, &local_error)) != NULL)
|
||
{
|
||
const char *desktop_name;
|
||
int i;
|
||
|
||
desktop_name = g_file_info_get_name (child_info);
|
||
if (!g_str_has_suffix (desktop_name, ".desktop"))
|
||
continue;
|
||
|
||
for (i = 0; previous_ids_sorted[i] != NULL; i++)
|
||
{
|
||
if (g_str_has_prefix (desktop_name, previous_ids_sorted[i]))
|
||
{
|
||
rewrite_one_dynamic_launcher (flatpak_file_get_path_cached (portal_desktop_dir),
|
||
portal_icon_path, desktop_name,
|
||
previous_ids_sorted[i], app_id);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if (local_error)
|
||
{
|
||
g_warning ("Failed to enumerate portal desktop dir %s: %s",
|
||
flatpak_file_get_path_cached (portal_desktop_dir),
|
||
local_error->message);
|
||
}
|
||
}
|
||
}
|
||
|
||
static FlatpakOciRegistry *
|
||
flatpak_dir_create_system_child_oci_registry (FlatpakDir *self,
|
||
GLnxLockFile *file_lock,
|
||
const char *token,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) cache_dir = NULL;
|
||
g_autoptr(GFile) repo_dir = NULL;
|
||
g_autofree char *repo_url = NULL;
|
||
g_autofree char *tmpdir_name = NULL;
|
||
g_autoptr(FlatpakOciRegistry) new_registry = NULL;
|
||
|
||
g_assert (!self->user);
|
||
|
||
if (!flatpak_dir_ensure_repo (self, NULL, error))
|
||
return NULL;
|
||
|
||
cache_dir = flatpak_ensure_system_user_cache_dir_location (error);
|
||
if (cache_dir == NULL)
|
||
return NULL;
|
||
|
||
if (!flatpak_allocate_tmpdir (AT_FDCWD,
|
||
flatpak_file_get_path_cached (cache_dir),
|
||
"child-oci-", &tmpdir_name,
|
||
NULL,
|
||
file_lock,
|
||
NULL,
|
||
NULL, error))
|
||
return NULL;
|
||
|
||
repo_dir = g_file_get_child (cache_dir, tmpdir_name);
|
||
repo_url = g_file_get_uri (repo_dir);
|
||
|
||
new_registry = flatpak_oci_registry_new (repo_url, TRUE, -1,
|
||
NULL, error);
|
||
if (new_registry == NULL)
|
||
return NULL;
|
||
|
||
flatpak_oci_registry_set_token (new_registry, token);
|
||
|
||
return g_steal_pointer (&new_registry);
|
||
}
|
||
|
||
static OstreeRepo *
|
||
flatpak_dir_create_child_repo (FlatpakDir *self,
|
||
GFile *cache_dir,
|
||
GLnxLockFile *file_lock,
|
||
const char *optional_commit,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) repo_dir = NULL;
|
||
g_autoptr(GFile) repo_dir_config = NULL;
|
||
g_autoptr(OstreeRepo) repo = NULL;
|
||
g_autofree char *tmpdir_name = NULL;
|
||
g_autoptr(OstreeRepo) new_repo = NULL;
|
||
g_autoptr(GKeyFile) config = NULL;
|
||
g_autofree char *current_mode = NULL;
|
||
GKeyFile *orig_config = NULL;
|
||
g_autofree char *orig_min_free_space_percent = NULL;
|
||
g_autofree char *orig_min_free_space_size = NULL;
|
||
|
||
/* We use bare-user-only here now, which means we don't need xattrs
|
||
* for the child repo. This only works as long as the pulled repo
|
||
* is valid in a bare-user-only repo, i.e. doesn't have xattrs or
|
||
* weird permissions, because then the pull into the system repo
|
||
* would complain that the checksum was wrong. However, by now all
|
||
* flatpak builds are likely to be valid, so this is fine.
|
||
*/
|
||
OstreeRepoMode mode = OSTREE_REPO_MODE_BARE_USER_ONLY;
|
||
const char *mode_str = "bare-user-only";
|
||
|
||
if (!flatpak_dir_ensure_repo (self, NULL, error))
|
||
return NULL;
|
||
|
||
orig_config = ostree_repo_get_config (self->repo);
|
||
|
||
if (!flatpak_allocate_tmpdir (AT_FDCWD,
|
||
flatpak_file_get_path_cached (cache_dir),
|
||
"repo-", &tmpdir_name,
|
||
NULL,
|
||
file_lock,
|
||
NULL,
|
||
NULL, error))
|
||
return NULL;
|
||
|
||
repo_dir = g_file_get_child (cache_dir, tmpdir_name);
|
||
|
||
new_repo = ostree_repo_new (repo_dir);
|
||
|
||
repo_dir_config = g_file_get_child (repo_dir, "config");
|
||
if (!g_file_query_exists (repo_dir_config, NULL))
|
||
{
|
||
if (!ostree_repo_create (new_repo, mode, NULL, error))
|
||
return NULL;
|
||
}
|
||
else
|
||
{
|
||
/* Try to open, but on failure, re-create */
|
||
if (!ostree_repo_open (new_repo, NULL, NULL))
|
||
{
|
||
flatpak_rm_rf (repo_dir, NULL, NULL);
|
||
if (!ostree_repo_create (new_repo, mode, NULL, error))
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
config = ostree_repo_copy_config (new_repo);
|
||
|
||
/* Verify that the mode is the expected one; if it isn't, recreate the repo */
|
||
current_mode = g_key_file_get_string (config, "core", "mode", NULL);
|
||
if (current_mode == NULL || g_strcmp0 (current_mode, mode_str) != 0)
|
||
{
|
||
flatpak_rm_rf (repo_dir, NULL, NULL);
|
||
|
||
/* Re-initialize the object because its dir's contents have been deleted (and it
|
||
* holds internal references to them) */
|
||
g_object_unref (new_repo);
|
||
new_repo = ostree_repo_new (repo_dir);
|
||
|
||
if (!ostree_repo_create (new_repo, mode, NULL, error))
|
||
return NULL;
|
||
|
||
/* Reload the repo config */
|
||
g_key_file_free (config);
|
||
config = ostree_repo_copy_config (new_repo);
|
||
}
|
||
|
||
/* Ensure the config is updated */
|
||
g_key_file_set_string (config, "core", "parent",
|
||
flatpak_file_get_path_cached (ostree_repo_get_path (self->repo)));
|
||
|
||
/* Copy the min space percent value so it affects the temporary repo too */
|
||
orig_min_free_space_percent = g_key_file_get_value (orig_config, "core", "min-free-space-percent", NULL);
|
||
if (orig_min_free_space_percent)
|
||
g_key_file_set_value (config, "core", "min-free-space-percent", orig_min_free_space_percent);
|
||
|
||
/* Copy the min space size value so it affects the temporary repo too */
|
||
orig_min_free_space_size = g_key_file_get_value (orig_config, "core", "min-free-space-size", NULL);
|
||
if (orig_min_free_space_size)
|
||
g_key_file_set_value (config, "core", "min-free-space-size", orig_min_free_space_size);
|
||
|
||
if (!ostree_repo_write_config (new_repo, config, error))
|
||
return NULL;
|
||
|
||
/* We need to reopen to apply the parent config */
|
||
repo = ostree_repo_new (repo_dir);
|
||
|
||
if (!ostree_repo_open (repo, NULL, error))
|
||
return NULL;
|
||
|
||
/* We don't need to sync the child repos, they are never used for stable storage, and we
|
||
verify + fsync when importing to stable storage */
|
||
ostree_repo_set_disable_fsync (repo, TRUE);
|
||
|
||
g_autoptr(GFile) user_cache_dir = flatpak_ensure_user_cache_dir_location (error);
|
||
if (user_cache_dir == NULL)
|
||
return FALSE;
|
||
|
||
if (!ostree_repo_set_cache_dir (repo, AT_FDCWD,
|
||
flatpak_file_get_path_cached (user_cache_dir),
|
||
NULL, error))
|
||
return FALSE;
|
||
|
||
/* Create a commitpartial in the child repo if needed to ensure we download everything, because
|
||
any commitpartial state in the parent will not otherwise be inherited */
|
||
if (optional_commit)
|
||
{
|
||
g_autofree char *commitpartial_basename = g_strconcat (optional_commit, ".commitpartial", NULL);
|
||
g_autoptr(GFile) orig_commitpartial =
|
||
flatpak_build_file (ostree_repo_get_path (self->repo),
|
||
"state", commitpartial_basename, NULL);
|
||
if (g_file_query_exists (orig_commitpartial, NULL))
|
||
{
|
||
g_autoptr(GFile) commitpartial =
|
||
flatpak_build_file (ostree_repo_get_path (repo),
|
||
"state", commitpartial_basename, NULL);
|
||
g_file_replace_contents (commitpartial, "", 0, NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION, NULL, NULL, NULL);
|
||
}
|
||
}
|
||
return g_steal_pointer (&repo);
|
||
}
|
||
|
||
static OstreeRepo *
|
||
flatpak_dir_create_system_child_repo (FlatpakDir *self,
|
||
GLnxLockFile *file_lock,
|
||
const char *optional_commit,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) cache_dir = NULL;
|
||
|
||
g_assert (!self->user);
|
||
|
||
cache_dir = flatpak_ensure_system_user_cache_dir_location (error);
|
||
if (cache_dir == NULL)
|
||
return NULL;
|
||
|
||
return flatpak_dir_create_child_repo (self, cache_dir, file_lock, optional_commit, error);
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_setup_revokefs_fuse_mount (FlatpakDir *self,
|
||
FlatpakDecomposed *ref,
|
||
const gchar *installation,
|
||
gchar **out_src_dir,
|
||
gchar **out_mnt_dir,
|
||
GCancellable *cancellable)
|
||
{
|
||
g_autoptr (GError) local_error = NULL;
|
||
g_autofree gchar *src_dir_tmp = NULL;
|
||
g_autofree gchar *mnt_dir_tmp = NULL;
|
||
gint socket = -1;
|
||
gboolean res = FALSE;
|
||
const char *revokefs_fuse_bin = LIBEXECDIR "/revokefs-fuse";
|
||
|
||
if (g_getenv ("FLATPAK_REVOKEFS_FUSE"))
|
||
revokefs_fuse_bin = g_getenv ("FLATPAK_REVOKEFS_FUSE");
|
||
|
||
if (!flatpak_dir_system_helper_call_get_revokefs_fd (self,
|
||
FLATPAK_HELPER_GET_REVOKEFS_FD_FLAGS_NONE,
|
||
installation ? installation : "",
|
||
&socket,
|
||
&src_dir_tmp,
|
||
cancellable,
|
||
&local_error))
|
||
{
|
||
if (g_error_matches (local_error, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED))
|
||
g_info ("revokefs-fuse not supported on your installation: %s", local_error->message);
|
||
else
|
||
g_warning ("Failed to get revokefs-fuse socket from system-helper: %s", local_error->message);
|
||
|
||
goto out;
|
||
}
|
||
else
|
||
{
|
||
g_autoptr(GSubprocess) revokefs_fuse = NULL;
|
||
g_autoptr(GSubprocessLauncher) launcher = NULL;
|
||
g_autofree gchar *client_uid = NULL;
|
||
|
||
mnt_dir_tmp = flatpak_dir_revokefs_fuse_create_mountpoint (ref, &local_error);
|
||
if (mnt_dir_tmp == NULL)
|
||
{
|
||
g_warning ("Failed to create a mountpoint for revokefs-fuse: %s", local_error->message);
|
||
close (socket);
|
||
goto out;
|
||
}
|
||
|
||
client_uid = g_strdup_printf ("uid=%d", getuid ());
|
||
launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
|
||
g_subprocess_launcher_take_fd (launcher, socket, 3);
|
||
revokefs_fuse = g_subprocess_launcher_spawn (launcher,
|
||
&local_error,
|
||
revokefs_fuse_bin, "-o", client_uid, "--socket=3",
|
||
src_dir_tmp, mnt_dir_tmp, NULL);
|
||
if (revokefs_fuse == NULL ||
|
||
!g_subprocess_wait_check (revokefs_fuse, NULL, &local_error))
|
||
{
|
||
g_warning ("Error spawning revokefs-fuse: %s", local_error->message);
|
||
close (socket);
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
res = TRUE;
|
||
|
||
out:
|
||
/* It is unconventional to steal these values on error. However, it depends on where
|
||
* this function failed. If we are able to spawn the revokefs backend (src_dir_tmp
|
||
* is non-NULL) but failed to create mountpoint or spawning revokefs-fuse here,
|
||
* we still need the src_dir_tmp value to cleanup the revokefs backend properly
|
||
* through the system-helper's CancelPull(). Hence, always stealing values can tell
|
||
* the caller under what circumstances this function failed and cleanup accordingly. */
|
||
*out_mnt_dir = g_steal_pointer (&mnt_dir_tmp);
|
||
*out_src_dir = g_steal_pointer (&src_dir_tmp);
|
||
|
||
return res;
|
||
}
|
||
|
||
static void
|
||
flatpak_dir_unmount_and_cancel_pull (FlatpakDir *self,
|
||
guint arg_flags,
|
||
GCancellable *cancellable,
|
||
OstreeRepo **repo,
|
||
GLnxLockFile *lockfile,
|
||
const char *mnt_dir,
|
||
const char *src_dir)
|
||
{
|
||
const char *installation = flatpak_dir_get_id (self);
|
||
g_autoptr(GError) error = NULL;
|
||
|
||
if (mnt_dir &&
|
||
!flatpak_dir_revokefs_fuse_unmount (repo, lockfile, mnt_dir, &error))
|
||
g_warning ("Could not unmount revokefs-fuse filesystem at %s: %s", mnt_dir, error->message);
|
||
|
||
g_clear_error (&error);
|
||
|
||
if (src_dir &&
|
||
!flatpak_dir_system_helper_call_cancel_pull (self,
|
||
arg_flags,
|
||
installation ? installation : "",
|
||
src_dir, cancellable, &error))
|
||
g_warning ("Error cancelling ongoing pull at %s: %s", src_dir, error->message);
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_install (FlatpakDir *self,
|
||
gboolean no_pull,
|
||
gboolean no_deploy,
|
||
gboolean no_static_deltas,
|
||
gboolean reinstall,
|
||
gboolean app_hint,
|
||
gboolean pin_on_deploy,
|
||
FlatpakRemoteState *state,
|
||
FlatpakDecomposed *ref,
|
||
const char *opt_commit,
|
||
const char **opt_subpaths,
|
||
const char **opt_previous_ids,
|
||
GFile *sideload_repo,
|
||
GBytes *require_metadata,
|
||
const char *token,
|
||
FlatpakProgress *progress,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
FlatpakPullFlags flatpak_flags;
|
||
|
||
flatpak_flags = FLATPAK_PULL_FLAGS_DOWNLOAD_EXTRA_DATA;
|
||
if (no_static_deltas)
|
||
flatpak_flags |= FLATPAK_PULL_FLAGS_NO_STATIC_DELTAS;
|
||
|
||
if (flatpak_dir_use_system_helper (self, NULL))
|
||
{
|
||
g_autoptr(OstreeRepo) child_repo = NULL;
|
||
g_auto(GLnxLockFile) child_repo_lock = { 0, };
|
||
const char *installation = flatpak_dir_get_id (self);
|
||
const char *empty_subpaths[] = {NULL};
|
||
const char **subpaths;
|
||
g_autofree char *child_repo_path = NULL;
|
||
FlatpakHelperDeployFlags helper_flags = 0;
|
||
g_autofree char *url = NULL;
|
||
gboolean gpg_verify_summary;
|
||
gboolean gpg_verify;
|
||
gboolean is_oci;
|
||
gboolean is_revokefs_pull = FALSE;
|
||
|
||
if (opt_subpaths)
|
||
subpaths = opt_subpaths;
|
||
else
|
||
subpaths = empty_subpaths;
|
||
|
||
if (!ostree_repo_remote_get_url (self->repo,
|
||
state->remote_name,
|
||
&url,
|
||
error))
|
||
return FALSE;
|
||
|
||
if (!ostree_repo_remote_get_gpg_verify_summary (self->repo, state->remote_name,
|
||
&gpg_verify_summary, error))
|
||
return FALSE;
|
||
|
||
if (!ostree_repo_remote_get_gpg_verify (self->repo, state->remote_name,
|
||
&gpg_verify, error))
|
||
return FALSE;
|
||
|
||
is_oci = flatpak_dir_get_remote_oci (self, state->remote_name);
|
||
if (no_pull)
|
||
{
|
||
/* Do nothing */
|
||
}
|
||
else if (is_oci)
|
||
{
|
||
g_autoptr(FlatpakOciRegistry) registry = NULL;
|
||
g_autoptr(GFile) registry_file = NULL;
|
||
|
||
registry = flatpak_dir_create_system_child_oci_registry (self, &child_repo_lock, token, error);
|
||
if (registry == NULL)
|
||
return FALSE;
|
||
|
||
registry_file = g_file_new_for_uri (flatpak_oci_registry_get_uri (registry));
|
||
|
||
child_repo_path = g_file_get_path (registry_file);
|
||
|
||
if (!flatpak_dir_mirror_oci (self, registry, state, flatpak_decomposed_get_ref (ref), opt_commit, NULL, token, progress, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
else if (!gpg_verify_summary || !gpg_verify)
|
||
{
|
||
/* The remote is not gpg verified, so we don't want to allow installation via
|
||
a download in the home directory, as there is no way to verify you're not
|
||
injecting anything into the remote. However, in the case of a remote
|
||
configured to a local filesystem we can just let the system helper do
|
||
the installation, as it can then avoid network i/o and be certain the
|
||
data comes from the right place. */
|
||
if (g_str_has_prefix (url, "file:"))
|
||
helper_flags |= FLATPAK_HELPER_DEPLOY_FLAGS_LOCAL_PULL;
|
||
else
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_UNTRUSTED, _("Can't pull from untrusted non-gpg verified remote"));
|
||
}
|
||
else
|
||
{
|
||
/* For system pulls, the pull has to be made in a child repo first,
|
||
which is then pulled into the system's one. The pull from child
|
||
repo into the system repo can occur in one of the two following ways:
|
||
1) Hard-link the child repo into system's one.
|
||
2) Copy and verify each object from the child repo to the system's one.
|
||
|
||
2) poses the problem of using double disk-space which might fail the
|
||
installation of very big applications. For e.g. at endless, the encyclopedia app
|
||
is about ~6GB, hence ~12GB of free disk-space is required to get it installed.
|
||
|
||
For 1), we need to make sure that we address all the security concerns that
|
||
might escalate during the pull from a remote into child repo and subsequently,
|
||
hard-linking it into the (root-owned)system repo. This is taken care of by a
|
||
special FUSE process(revokefs-fuse) which guards all the writes made to the
|
||
child repo and ensures that no file descriptors remain open to the child repo
|
||
before the hard-linkable pull is made into the system's repo.
|
||
More details about the security issues dealt here are present at
|
||
https://github.com/flatpak/flatpak/wiki/Noncopying-system-app-installation
|
||
|
||
In case we fail to apply pull approach 1), the pull automatically fallbacks to use 2). */
|
||
g_autofree gchar *src_dir = NULL;
|
||
g_autofree gchar *mnt_dir = NULL;
|
||
g_autoptr(GError) local_error = NULL;
|
||
|
||
if (!flatpak_dir_setup_revokefs_fuse_mount (self,
|
||
ref,
|
||
installation,
|
||
&src_dir, &mnt_dir,
|
||
cancellable))
|
||
{
|
||
flatpak_dir_unmount_and_cancel_pull (self, FLATPAK_HELPER_CANCEL_PULL_FLAGS_NONE,
|
||
cancellable,
|
||
&child_repo, &child_repo_lock,
|
||
mnt_dir, src_dir);
|
||
}
|
||
else
|
||
{
|
||
g_autofree gchar *repo_basename = NULL;
|
||
g_autoptr(GFile) mnt_dir_file = NULL;
|
||
|
||
mnt_dir_file = g_file_new_for_path (mnt_dir);
|
||
child_repo = flatpak_dir_create_child_repo (self, mnt_dir_file, &child_repo_lock, opt_commit, &local_error);
|
||
if (child_repo == NULL)
|
||
{
|
||
g_warning ("Cannot create repo on revokefs mountpoint %s: %s", mnt_dir, local_error->message);
|
||
flatpak_dir_unmount_and_cancel_pull (self,
|
||
FLATPAK_HELPER_CANCEL_PULL_FLAGS_NONE,
|
||
cancellable,
|
||
&child_repo, &child_repo_lock,
|
||
mnt_dir, src_dir);
|
||
g_clear_error (&local_error);
|
||
}
|
||
else
|
||
{
|
||
repo_basename = g_file_get_basename (ostree_repo_get_path (child_repo));
|
||
child_repo_path = g_build_filename (src_dir, repo_basename, NULL);
|
||
is_revokefs_pull = TRUE;
|
||
}
|
||
}
|
||
|
||
/* Fallback if revokefs-fuse setup does not succeed. This makes the pull
|
||
* temporarily use double disk-space. */
|
||
if (!is_revokefs_pull)
|
||
{
|
||
/* We're pulling from a remote source, we do the network mirroring pull as a
|
||
user and hand back the resulting data to the system-helper, that trusts us
|
||
due to the GPG signatures in the repo */
|
||
child_repo = flatpak_dir_create_system_child_repo (self, &child_repo_lock, NULL, error);
|
||
if (child_repo == NULL)
|
||
return FALSE;
|
||
else
|
||
child_repo_path = g_file_get_path (ostree_repo_get_path (child_repo));
|
||
}
|
||
|
||
flatpak_flags |= FLATPAK_PULL_FLAGS_SIDELOAD_EXTRA_DATA;
|
||
|
||
if (!flatpak_dir_pull (self, state, flatpak_decomposed_get_ref (ref), opt_commit, subpaths, sideload_repo, require_metadata, token,
|
||
child_repo,
|
||
flatpak_flags,
|
||
0,
|
||
progress, cancellable, error))
|
||
{
|
||
if (is_revokefs_pull)
|
||
{
|
||
flatpak_dir_unmount_and_cancel_pull (self,
|
||
FLATPAK_HELPER_CANCEL_PULL_FLAGS_PRESERVE_PULL,
|
||
cancellable,
|
||
&child_repo, &child_repo_lock,
|
||
mnt_dir, src_dir);
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
g_assert (child_repo_path != NULL);
|
||
|
||
if (is_revokefs_pull &&
|
||
!flatpak_dir_revokefs_fuse_unmount (&child_repo, &child_repo_lock, mnt_dir, &local_error))
|
||
{
|
||
g_propagate_prefixed_error (error, g_steal_pointer (&local_error),
|
||
_("Could not unmount revokefs-fuse filesystem at %s: "), mnt_dir);
|
||
|
||
if (src_dir &&
|
||
!flatpak_dir_system_helper_call_cancel_pull (self,
|
||
FLATPAK_HELPER_CANCEL_PULL_FLAGS_PRESERVE_PULL,
|
||
installation ? installation : "",
|
||
src_dir, cancellable, &local_error))
|
||
g_warning ("Error cancelling ongoing pull at %s: %s", src_dir, local_error->message);
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
if (no_deploy)
|
||
helper_flags |= FLATPAK_HELPER_DEPLOY_FLAGS_NO_DEPLOY;
|
||
|
||
if (reinstall)
|
||
helper_flags |= FLATPAK_HELPER_DEPLOY_FLAGS_REINSTALL;
|
||
|
||
if (app_hint)
|
||
helper_flags |= FLATPAK_HELPER_DEPLOY_FLAGS_APP_HINT;
|
||
|
||
if (pin_on_deploy)
|
||
helper_flags |= FLATPAK_HELPER_DEPLOY_FLAGS_UPDATE_PINNED;
|
||
|
||
helper_flags |= FLATPAK_HELPER_DEPLOY_FLAGS_INSTALL_HINT;
|
||
|
||
if (!flatpak_dir_system_helper_call_deploy (self,
|
||
child_repo_path ? child_repo_path : "",
|
||
helper_flags, flatpak_decomposed_get_ref (ref), state->remote_name,
|
||
(const char * const *) subpaths,
|
||
(const char * const *) opt_previous_ids,
|
||
installation ? installation : "",
|
||
cancellable,
|
||
error))
|
||
return FALSE;
|
||
|
||
if (child_repo_path && !is_revokefs_pull)
|
||
(void) glnx_shutil_rm_rf_at (AT_FDCWD, child_repo_path, NULL, NULL);
|
||
|
||
/* In case the app is being renamed, rewrite any launchers made by
|
||
* xdg-desktop-portal. This has to be done as the user so can't be in the
|
||
* system helper.
|
||
*/
|
||
if (opt_previous_ids)
|
||
rewrite_dynamic_launchers (ref, opt_previous_ids);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
if (!no_pull)
|
||
{
|
||
if (!flatpak_dir_pull (self, state, flatpak_decomposed_get_ref (ref), opt_commit, opt_subpaths, sideload_repo, require_metadata, token, NULL,
|
||
flatpak_flags, OSTREE_REPO_PULL_FLAGS_NONE,
|
||
progress, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
if (!no_deploy)
|
||
{
|
||
if (!flatpak_dir_deploy_install (self, ref, state->remote_name, opt_subpaths,
|
||
opt_previous_ids, reinstall, pin_on_deploy,
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
/* In case the app is being renamed, rewrite any launchers made by
|
||
* xdg-desktop-portal.
|
||
*/
|
||
if (opt_previous_ids)
|
||
rewrite_dynamic_launchers (ref, opt_previous_ids);
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
char *
|
||
flatpak_dir_ensure_bundle_remote (FlatpakDir *self,
|
||
GFile *file,
|
||
GBytes *extra_gpg_data,
|
||
FlatpakDecomposed **out_ref,
|
||
char **out_checksum,
|
||
char **out_metadata,
|
||
gboolean *out_created_remote,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(FlatpakDecomposed) ref = NULL;
|
||
gboolean created_remote = FALSE;
|
||
g_autoptr(GBytes) deploy_data = NULL;
|
||
g_autoptr(GVariant) metadata = NULL;
|
||
g_autofree char *origin = NULL;
|
||
g_autofree char *fp_metadata = NULL;
|
||
g_autofree char *basename = NULL;
|
||
g_autoptr(GBytes) included_gpg_data = NULL;
|
||
GBytes *gpg_data = NULL;
|
||
g_autofree char *to_checksum = NULL;
|
||
g_autofree char *remote = NULL;
|
||
g_autofree char *collection_id = NULL;
|
||
|
||
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
||
return NULL;
|
||
|
||
metadata = flatpak_bundle_load (file, &to_checksum,
|
||
&ref,
|
||
&origin,
|
||
NULL, &fp_metadata, NULL,
|
||
&included_gpg_data,
|
||
&collection_id,
|
||
error);
|
||
if (metadata == NULL)
|
||
return NULL;
|
||
|
||
/* If we rely on metadata (to e.g. print permissions), check it exists before creating the remote */
|
||
if (out_metadata && fp_metadata == NULL)
|
||
{
|
||
flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, "No metadata in bundle header");
|
||
return NULL;
|
||
}
|
||
|
||
gpg_data = extra_gpg_data ? extra_gpg_data : included_gpg_data;
|
||
|
||
deploy_data = flatpak_dir_get_deploy_data (self, ref, FLATPAK_DEPLOY_VERSION_ANY, cancellable, NULL);
|
||
if (deploy_data != NULL)
|
||
{
|
||
remote = g_strdup (flatpak_deploy_data_get_origin (deploy_data));
|
||
|
||
/* We need to import any gpg keys because otherwise the pull will fail */
|
||
if (gpg_data != NULL)
|
||
{
|
||
g_autoptr(GKeyFile) new_config = NULL;
|
||
|
||
new_config = ostree_repo_copy_config (flatpak_dir_get_repo (self));
|
||
|
||
if (!flatpak_dir_modify_remote (self, remote, new_config,
|
||
gpg_data, cancellable, error))
|
||
return NULL;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
g_autofree char *id = flatpak_decomposed_dup_id (ref);
|
||
/* Add a remote for later updates */
|
||
basename = g_file_get_basename (file);
|
||
remote = flatpak_dir_create_origin_remote (self,
|
||
origin,
|
||
id,
|
||
basename,
|
||
flatpak_decomposed_get_ref (ref),
|
||
gpg_data,
|
||
collection_id,
|
||
&created_remote,
|
||
cancellable,
|
||
error);
|
||
if (remote == NULL)
|
||
return NULL;
|
||
}
|
||
|
||
if (out_created_remote)
|
||
*out_created_remote = created_remote;
|
||
|
||
if (out_ref)
|
||
*out_ref = g_steal_pointer (&ref);
|
||
|
||
if (out_checksum)
|
||
*out_checksum = g_steal_pointer (&to_checksum);
|
||
|
||
if (out_metadata)
|
||
*out_metadata = g_steal_pointer (&fp_metadata);
|
||
|
||
|
||
return g_steal_pointer (&remote);
|
||
}
|
||
|
||
/* If core.add-remotes-config-dir is set for this repository (which is
|
||
* not a common configuration, but it is possible), we will fail to modify
|
||
* remote configuration when using a combination of
|
||
* ostree_repo_remote_[add|change]() and ostree_repo_write_config() due to
|
||
* adding remote config in /etc/flatpak/remotes.d and also in
|
||
* /ostree/repo/config. Avoid that.
|
||
*
|
||
* FIXME: See https://github.com/flatpak/flatpak/issues/1665. In future, we
|
||
* should just write the remote config to the correct place, factoring
|
||
* core.add-remotes-config-dir in. */
|
||
static gboolean
|
||
flatpak_dir_check_add_remotes_config_dir (FlatpakDir *self,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GError) local_error = NULL;
|
||
gboolean val;
|
||
GKeyFile *config;
|
||
|
||
if (!flatpak_dir_maybe_ensure_repo (self, NULL, error))
|
||
return FALSE;
|
||
|
||
if (self->repo == NULL)
|
||
return TRUE;
|
||
|
||
config = ostree_repo_get_config (self->repo);
|
||
|
||
if (config == NULL)
|
||
return TRUE;
|
||
|
||
val = g_key_file_get_boolean (config, "core", "add-remotes-config-dir", &local_error);
|
||
|
||
if (local_error != NULL)
|
||
{
|
||
if (g_error_matches (local_error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND))
|
||
{
|
||
g_clear_error (&local_error);
|
||
val = ostree_repo_is_system (self->repo);
|
||
}
|
||
else
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
if (!val)
|
||
return TRUE;
|
||
|
||
return flatpak_fail (error,
|
||
"Can’t update remote configuration on a repository with "
|
||
"core.add-remotes-config-dir=true");
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_install_bundle (FlatpakDir *self,
|
||
GFile *file,
|
||
const char *remote,
|
||
FlatpakDecomposed **out_ref,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autofree char *ref_str = NULL;
|
||
g_autoptr(FlatpakDecomposed) ref = NULL;
|
||
g_autoptr(GBytes) deploy_data = NULL;
|
||
g_autoptr(GVariant) metadata = NULL;
|
||
g_autofree char *origin = NULL;
|
||
g_autofree char *to_checksum = NULL;
|
||
gboolean gpg_verify;
|
||
|
||
if (!flatpak_dir_check_add_remotes_config_dir (self, error))
|
||
return FALSE;
|
||
|
||
if (flatpak_dir_use_system_helper (self, NULL))
|
||
{
|
||
const char *installation = flatpak_dir_get_id (self);
|
||
|
||
if (!flatpak_dir_system_helper_call_install_bundle (self,
|
||
flatpak_file_get_path_cached (file),
|
||
0, remote,
|
||
installation ? installation : "",
|
||
&ref_str,
|
||
cancellable,
|
||
error))
|
||
return FALSE;
|
||
|
||
|
||
ref = flatpak_decomposed_new_from_ref (ref_str, error);
|
||
if (ref == NULL)
|
||
return FALSE;
|
||
|
||
if (out_ref)
|
||
*out_ref = g_steal_pointer (&ref);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
||
return FALSE;
|
||
|
||
metadata = flatpak_bundle_load (file, &to_checksum,
|
||
&ref,
|
||
&origin,
|
||
NULL, NULL,
|
||
NULL, NULL, NULL,
|
||
error);
|
||
if (metadata == NULL)
|
||
return FALSE;
|
||
|
||
deploy_data = flatpak_dir_get_deploy_data (self, ref, FLATPAK_DEPLOY_VERSION_ANY, cancellable, NULL);
|
||
if (deploy_data != NULL)
|
||
{
|
||
if (strcmp (flatpak_deploy_data_get_commit (deploy_data), to_checksum) == 0)
|
||
{
|
||
g_autofree char *id = flatpak_decomposed_dup_id (ref);
|
||
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED,
|
||
_("This version of %s is already installed"), id);
|
||
return FALSE;
|
||
}
|
||
|
||
if (strcmp (remote, flatpak_deploy_data_get_origin (deploy_data)) != 0)
|
||
{
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||
_("Can't change remote during bundle install"));
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
if (!ostree_repo_remote_get_gpg_verify (self->repo, remote,
|
||
&gpg_verify, error))
|
||
return FALSE;
|
||
|
||
if (!flatpak_pull_from_bundle (self->repo,
|
||
file,
|
||
remote,
|
||
flatpak_decomposed_get_ref (ref),
|
||
gpg_verify,
|
||
cancellable,
|
||
error))
|
||
return FALSE;
|
||
|
||
if (deploy_data != NULL)
|
||
{
|
||
g_autofree char *group = g_strdup_printf ("remote \"%s\"", remote);
|
||
g_autofree char *old_url = NULL;
|
||
g_autoptr(GKeyFile) new_config = NULL;
|
||
|
||
/* The pull succeeded, and this is an update. So, we need to update the repo config
|
||
if anything changed */
|
||
ostree_repo_remote_get_url (self->repo,
|
||
remote,
|
||
&old_url,
|
||
NULL);
|
||
if (origin != NULL &&
|
||
(old_url == NULL || strcmp (old_url, origin) != 0))
|
||
{
|
||
if (new_config == NULL)
|
||
new_config = ostree_repo_copy_config (self->repo);
|
||
|
||
g_key_file_set_value (new_config, group, "url", origin);
|
||
}
|
||
|
||
if (new_config)
|
||
{
|
||
if (!flatpak_dir_cleanup_remote_for_url_change (self, remote,
|
||
origin, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (!ostree_repo_write_config (self->repo, new_config, error))
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
if (deploy_data)
|
||
{
|
||
if (!flatpak_dir_deploy_update (self, ref, NULL, NULL, NULL, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
else
|
||
{
|
||
if (!flatpak_dir_deploy_install (self, ref, remote, NULL, NULL, FALSE, FALSE, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
if (out_ref)
|
||
*out_ref = g_steal_pointer (&ref);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
_g_strv_equal0 (const char * const *a, const char * const *b)
|
||
{
|
||
if (a == NULL && b == NULL)
|
||
return TRUE;
|
||
|
||
if (a == NULL || b == NULL)
|
||
return FALSE;
|
||
|
||
return g_strv_equal (a, b);
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_needs_update_for_commit_and_subpaths (FlatpakDir *self,
|
||
const char *remote,
|
||
FlatpakDecomposed *ref,
|
||
const char *target_commit,
|
||
const char **opt_subpaths)
|
||
{
|
||
g_autoptr(GBytes) deploy_data = NULL;
|
||
g_autofree const char **old_subpaths = NULL;
|
||
const char **subpaths;
|
||
g_autofree char *url = NULL;
|
||
const char *installed_commit;
|
||
const char *installed_alt_id;
|
||
const char *extension_of;
|
||
|
||
g_assert (target_commit != NULL);
|
||
|
||
/* Never update from disabled remotes */
|
||
if (!ostree_repo_remote_get_url (self->repo, remote, &url, NULL))
|
||
return FALSE;
|
||
|
||
if (*url == 0)
|
||
return FALSE;
|
||
|
||
/* deploy v4 guarantees alt-id/extension-of info */
|
||
deploy_data = flatpak_dir_get_deploy_data (self, ref, 4, NULL, NULL);
|
||
if (deploy_data != NULL)
|
||
old_subpaths = flatpak_deploy_data_get_subpaths (deploy_data);
|
||
else
|
||
old_subpaths = g_new0 (const char *, 1); /* Empty strv == all subpaths*/
|
||
|
||
if (opt_subpaths)
|
||
subpaths = opt_subpaths;
|
||
else
|
||
subpaths = old_subpaths;
|
||
|
||
/* Not deployed => need update */
|
||
if (deploy_data == NULL)
|
||
return TRUE;
|
||
|
||
/* If masked, don't update */
|
||
if (flatpak_dir_ref_is_masked (self, flatpak_decomposed_get_ref (ref)))
|
||
return FALSE;
|
||
|
||
extension_of = flatpak_deploy_data_get_extension_of (deploy_data);
|
||
/* If the main ref is masked, don't update extensions of it (like .Locale or .Debug) */
|
||
if (extension_of && flatpak_dir_ref_is_masked (self, extension_of))
|
||
return FALSE;
|
||
|
||
installed_commit = flatpak_deploy_data_get_commit (deploy_data);
|
||
installed_alt_id = flatpak_deploy_data_get_alt_id (deploy_data);
|
||
|
||
/* Different target commit than deployed => update */
|
||
if (g_strcmp0 (target_commit, installed_commit) != 0 &&
|
||
g_strcmp0 (target_commit, installed_alt_id) != 0)
|
||
return TRUE;
|
||
|
||
/* target commit is the same as current, but maybe something else that is different? */
|
||
|
||
/* Same commit, but different subpaths => update */
|
||
if (!_g_strv_equal0 (subpaths, old_subpaths))
|
||
return TRUE;
|
||
|
||
/* Same subpaths and commit, no need to update */
|
||
return FALSE;
|
||
}
|
||
|
||
/* This is called by the old-school non-transaction flatpak_installation_update, so doesn't do a lot. */
|
||
char *
|
||
flatpak_dir_check_for_update (FlatpakDir *self,
|
||
FlatpakRemoteState *state,
|
||
FlatpakDecomposed *ref,
|
||
const char *checksum_or_latest,
|
||
const char **opt_subpaths,
|
||
gboolean no_pull,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autofree char *latest_rev = NULL;
|
||
const char *target_rev = NULL;
|
||
|
||
if (no_pull)
|
||
{
|
||
if (!flatpak_repo_resolve_rev (self->repo, NULL, state->remote_name,
|
||
flatpak_decomposed_get_ref (ref), FALSE, &latest_rev, NULL, NULL))
|
||
{
|
||
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED,
|
||
_("%s already installed"), flatpak_decomposed_get_ref (ref));
|
||
return NULL; /* No update, because nothing to update to */
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (!flatpak_dir_find_latest_rev (self, state, flatpak_decomposed_get_ref (ref), checksum_or_latest, &latest_rev,
|
||
NULL, NULL, cancellable, error))
|
||
return NULL;
|
||
}
|
||
|
||
if (checksum_or_latest != NULL)
|
||
target_rev = checksum_or_latest;
|
||
else
|
||
target_rev = latest_rev;
|
||
|
||
if (flatpak_dir_needs_update_for_commit_and_subpaths (self, state->remote_name, ref, target_rev, opt_subpaths))
|
||
return g_strdup (target_rev);
|
||
|
||
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED,
|
||
_("%s commit %s already installed"), flatpak_decomposed_get_ref (ref), target_rev);
|
||
return NULL;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_update (FlatpakDir *self,
|
||
gboolean no_pull,
|
||
gboolean no_deploy,
|
||
gboolean no_static_deltas,
|
||
gboolean allow_downgrade,
|
||
gboolean app_hint,
|
||
gboolean install_hint,
|
||
FlatpakRemoteState *state,
|
||
FlatpakDecomposed *ref,
|
||
const char *commit,
|
||
const char **opt_subpaths,
|
||
const char **opt_previous_ids,
|
||
GFile *sideload_repo,
|
||
GBytes *require_metadata,
|
||
const char *token,
|
||
FlatpakProgress *progress,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GBytes) deploy_data = NULL;
|
||
const char **subpaths = NULL;
|
||
const char *empty_subpaths[] = {NULL};
|
||
g_autofree char *url = NULL;
|
||
FlatpakPullFlags flatpak_flags;
|
||
g_autofree const char **old_subpaths = NULL;
|
||
gboolean is_oci;
|
||
|
||
/* This is calculated in check_for_update */
|
||
g_assert (commit != NULL);
|
||
|
||
flatpak_flags = FLATPAK_PULL_FLAGS_DOWNLOAD_EXTRA_DATA;
|
||
if (allow_downgrade)
|
||
flatpak_flags |= FLATPAK_PULL_FLAGS_ALLOW_DOWNGRADE;
|
||
if (no_static_deltas)
|
||
flatpak_flags |= FLATPAK_PULL_FLAGS_NO_STATIC_DELTAS;
|
||
|
||
deploy_data = flatpak_dir_get_deploy_data (self, ref, FLATPAK_DEPLOY_VERSION_ANY,
|
||
cancellable, NULL);
|
||
|
||
if (deploy_data != NULL)
|
||
old_subpaths = flatpak_deploy_data_get_subpaths (deploy_data);
|
||
|
||
if (opt_subpaths)
|
||
subpaths = opt_subpaths;
|
||
else if (old_subpaths)
|
||
subpaths = old_subpaths;
|
||
else
|
||
subpaths = empty_subpaths;
|
||
|
||
if (!ostree_repo_remote_get_url (self->repo, state->remote_name, &url, error))
|
||
return FALSE;
|
||
|
||
if (*url == 0)
|
||
return TRUE; /* Empty URL => disabled */
|
||
|
||
is_oci = flatpak_dir_get_remote_oci (self, state->remote_name);
|
||
|
||
if (flatpak_dir_use_system_helper (self, NULL))
|
||
{
|
||
const char *installation = flatpak_dir_get_id (self);
|
||
g_autoptr(OstreeRepo) child_repo = NULL;
|
||
g_auto(GLnxLockFile) child_repo_lock = { 0, };
|
||
g_autofree char *child_repo_path = NULL;
|
||
FlatpakHelperDeployFlags helper_flags = 0;
|
||
gboolean gpg_verify_summary;
|
||
gboolean gpg_verify;
|
||
gboolean is_revokefs_pull = FALSE;
|
||
|
||
if (allow_downgrade)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_DOWNGRADE,
|
||
_("Can't update to a specific commit without root permissions"));
|
||
|
||
helper_flags = FLATPAK_HELPER_DEPLOY_FLAGS_UPDATE;
|
||
|
||
if (!ostree_repo_remote_get_gpg_verify_summary (self->repo, state->remote_name,
|
||
&gpg_verify_summary, error))
|
||
return FALSE;
|
||
|
||
if (!ostree_repo_remote_get_gpg_verify (self->repo, state->remote_name,
|
||
&gpg_verify, error))
|
||
return FALSE;
|
||
|
||
if (no_pull)
|
||
{
|
||
/* Nothing to do here */
|
||
}
|
||
else if (is_oci)
|
||
{
|
||
g_autoptr(FlatpakOciRegistry) registry = NULL;
|
||
g_autoptr(GFile) registry_file = NULL;
|
||
|
||
registry = flatpak_dir_create_system_child_oci_registry (self, &child_repo_lock, token, error);
|
||
if (registry == NULL)
|
||
return FALSE;
|
||
|
||
registry_file = g_file_new_for_uri (flatpak_oci_registry_get_uri (registry));
|
||
|
||
child_repo_path = g_file_get_path (registry_file);
|
||
|
||
if (!flatpak_dir_mirror_oci (self, registry, state, flatpak_decomposed_get_ref (ref),
|
||
commit, NULL, token, progress, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
else if (!gpg_verify_summary || !gpg_verify)
|
||
{
|
||
/* The remote is not gpg verified, so we don't want to allow installation via
|
||
a download in the home directory, as there is no way to verify you're not
|
||
injecting anything into the remote. However, in the case of a remote
|
||
configured to a local filesystem we can just let the system helper do
|
||
the installation, as it can then avoid network i/o and be certain the
|
||
data comes from the right place.
|
||
|
||
If @collection_id is non-%NULL, we can verify the refs in commit
|
||
metadata, so don’t need to verify the summary. */
|
||
if (g_str_has_prefix (url, "file:"))
|
||
helper_flags |= FLATPAK_HELPER_DEPLOY_FLAGS_LOCAL_PULL;
|
||
else
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_UNTRUSTED, _("Can't pull from untrusted non-gpg verified remote"));
|
||
}
|
||
else
|
||
{
|
||
/* First try to update using revokefs-fuse codepath. If it fails, try to update using a
|
||
* temporary child-repo. Read flatpak_dir_install for more details on using revokefs-fuse */
|
||
g_autofree gchar *src_dir = NULL;
|
||
g_autofree gchar *mnt_dir = NULL;
|
||
g_autoptr(GError) local_error = NULL;
|
||
|
||
if (!flatpak_dir_setup_revokefs_fuse_mount (self,
|
||
ref,
|
||
installation,
|
||
&src_dir, &mnt_dir,
|
||
cancellable))
|
||
{
|
||
flatpak_dir_unmount_and_cancel_pull (self, FLATPAK_HELPER_CANCEL_PULL_FLAGS_NONE,
|
||
cancellable,
|
||
&child_repo, &child_repo_lock,
|
||
mnt_dir, src_dir);
|
||
}
|
||
else
|
||
{
|
||
g_autofree gchar *repo_basename = NULL;
|
||
g_autoptr(GFile) mnt_dir_file = NULL;
|
||
|
||
mnt_dir_file = g_file_new_for_path (mnt_dir);
|
||
child_repo = flatpak_dir_create_child_repo (self, mnt_dir_file, &child_repo_lock, commit, &local_error);
|
||
if (child_repo == NULL)
|
||
{
|
||
g_warning ("Cannot create repo on revokefs mountpoint %s: %s", mnt_dir, local_error->message);
|
||
flatpak_dir_unmount_and_cancel_pull (self,
|
||
FLATPAK_HELPER_CANCEL_PULL_FLAGS_NONE,
|
||
cancellable,
|
||
&child_repo, &child_repo_lock,
|
||
mnt_dir, src_dir);
|
||
g_clear_error (&local_error);
|
||
}
|
||
else
|
||
{
|
||
repo_basename = g_file_get_basename (ostree_repo_get_path (child_repo));
|
||
child_repo_path = g_build_filename (src_dir, repo_basename, NULL);
|
||
is_revokefs_pull = TRUE;
|
||
}
|
||
}
|
||
|
||
/* Fallback if revokefs-fuse setup does not succeed. This makes the pull
|
||
* temporarily use double disk-space. */
|
||
if (!is_revokefs_pull)
|
||
{
|
||
/* We're pulling from a remote source, we do the network mirroring pull as a
|
||
user and hand back the resulting data to the system-helper, that trusts us
|
||
due to the GPG signatures in the repo */
|
||
|
||
child_repo = flatpak_dir_create_system_child_repo (self, &child_repo_lock, commit, error);
|
||
if (child_repo == NULL)
|
||
return FALSE;
|
||
else
|
||
child_repo_path = g_file_get_path (ostree_repo_get_path (child_repo));
|
||
}
|
||
|
||
flatpak_flags |= FLATPAK_PULL_FLAGS_SIDELOAD_EXTRA_DATA;
|
||
if (!flatpak_dir_pull (self, state, flatpak_decomposed_get_ref (ref),
|
||
commit, subpaths, sideload_repo, require_metadata, token,
|
||
child_repo,
|
||
flatpak_flags, 0,
|
||
progress, cancellable, error))
|
||
{
|
||
if (is_revokefs_pull)
|
||
{
|
||
flatpak_dir_unmount_and_cancel_pull (self,
|
||
FLATPAK_HELPER_CANCEL_PULL_FLAGS_PRESERVE_PULL,
|
||
cancellable,
|
||
&child_repo, &child_repo_lock,
|
||
mnt_dir, src_dir);
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
g_assert (child_repo_path != NULL);
|
||
|
||
if (is_revokefs_pull &&
|
||
!flatpak_dir_revokefs_fuse_unmount (&child_repo, &child_repo_lock, mnt_dir, &local_error))
|
||
{
|
||
g_warning ("Could not unmount revokefs-fuse filesystem at %s: %s", mnt_dir, local_error->message);
|
||
flatpak_dir_unmount_and_cancel_pull (self,
|
||
FLATPAK_HELPER_CANCEL_PULL_FLAGS_PRESERVE_PULL,
|
||
cancellable,
|
||
&child_repo, &child_repo_lock,
|
||
mnt_dir, src_dir);
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
if (no_deploy)
|
||
helper_flags |= FLATPAK_HELPER_DEPLOY_FLAGS_NO_DEPLOY;
|
||
|
||
if (app_hint)
|
||
helper_flags |= FLATPAK_HELPER_DEPLOY_FLAGS_APP_HINT;
|
||
|
||
if (install_hint)
|
||
helper_flags |= FLATPAK_HELPER_DEPLOY_FLAGS_INSTALL_HINT;
|
||
|
||
if (!flatpak_dir_system_helper_call_deploy (self,
|
||
child_repo_path ? child_repo_path : "",
|
||
helper_flags, flatpak_decomposed_get_ref (ref), state->remote_name,
|
||
subpaths, opt_previous_ids,
|
||
installation ? installation : "",
|
||
cancellable,
|
||
error))
|
||
return FALSE;
|
||
|
||
if (child_repo_path && !is_revokefs_pull)
|
||
(void) glnx_shutil_rm_rf_at (AT_FDCWD, child_repo_path, NULL, NULL);
|
||
|
||
/* In case the app is being renamed, rewrite any launchers made by
|
||
* xdg-desktop-portal. This has to be done as the user so can't be in the
|
||
* system helper.
|
||
*/
|
||
if (opt_previous_ids)
|
||
rewrite_dynamic_launchers (ref, opt_previous_ids);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
if (!no_pull)
|
||
{
|
||
if (!flatpak_dir_pull (self, state, flatpak_decomposed_get_ref (ref),
|
||
commit, subpaths, sideload_repo, require_metadata, token,
|
||
NULL, flatpak_flags, OSTREE_REPO_PULL_FLAGS_NONE,
|
||
progress, cancellable, error))
|
||
return FALSE;
|
||
|
||
/* Take this opportunity to clean up refs/mirrors/ since a prune will happen
|
||
* after this update operation. See
|
||
* https://github.com/flatpak/flatpak/issues/3222
|
||
* Note: For the system-helper case we do this in handle_deploy()
|
||
*/
|
||
if (!flatpak_dir_delete_mirror_refs (self, FALSE, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
if (!no_deploy)
|
||
{
|
||
if (!flatpak_dir_deploy_update (self, ref,
|
||
/* We don't know the local commit id in the OCI case, and
|
||
we only support one version anyway */
|
||
is_oci ? NULL : commit,
|
||
subpaths, opt_previous_ids,
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
/* In case the app is being renamed, rewrite any launchers made by
|
||
* xdg-desktop-portal.
|
||
*/
|
||
if (opt_previous_ids)
|
||
rewrite_dynamic_launchers (ref, opt_previous_ids);
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_uninstall (FlatpakDir *self,
|
||
FlatpakDecomposed *ref,
|
||
FlatpakHelperUninstallFlags flags,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
const char *repository;
|
||
g_autoptr(FlatpakDecomposed) current_ref = NULL;
|
||
gboolean was_deployed;
|
||
g_autofree char *name = NULL;
|
||
g_autofree char *old_active = NULL;
|
||
g_auto(GLnxLockFile) lock = { 0, };
|
||
g_autoptr(GBytes) deploy_data = NULL;
|
||
gboolean keep_ref = flags & FLATPAK_HELPER_UNINSTALL_FLAGS_KEEP_REF;
|
||
gboolean force_remove = flags & FLATPAK_HELPER_UNINSTALL_FLAGS_FORCE_REMOVE;
|
||
|
||
name = flatpak_decomposed_dup_id (ref);
|
||
|
||
if (flatpak_dir_use_system_helper (self, NULL))
|
||
{
|
||
const char *installation = flatpak_dir_get_id (self);
|
||
|
||
if (!flatpak_dir_system_helper_call_uninstall (self,
|
||
flags, flatpak_decomposed_get_ref (ref),
|
||
installation ? installation : "",
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
if (!flatpak_dir_lock (self, &lock,
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
deploy_data = flatpak_dir_get_deploy_data (self, ref, FLATPAK_DEPLOY_VERSION_ANY,
|
||
cancellable, error);
|
||
if (deploy_data == NULL)
|
||
return FALSE;
|
||
|
||
/* Note: the origin remote usually exists but it's not guaranteed (the user
|
||
* could have run remote-delete --force) */
|
||
repository = flatpak_deploy_data_get_origin (deploy_data);
|
||
if (repository == NULL)
|
||
return FALSE;
|
||
|
||
if (flatpak_decomposed_is_runtime (ref) && !force_remove)
|
||
{
|
||
g_autoptr(GHashTable) runtime_app_map = NULL;
|
||
g_autoptr(GPtrArray) blocking = NULL;
|
||
|
||
/* Look for apps that need this runtime */
|
||
blocking = flatpak_dir_list_app_refs_with_runtime (self, &runtime_app_map, ref, cancellable, error);
|
||
if (blocking == NULL)
|
||
return FALSE;
|
||
|
||
if (blocking->len > 0)
|
||
{
|
||
g_autoptr(GString) joined = g_string_new ("");
|
||
for (int i = 0; i < blocking->len; i++)
|
||
{
|
||
FlatpakDecomposed *blocking_ref = g_ptr_array_index (blocking, i);
|
||
g_autofree char *id = flatpak_decomposed_dup_id (blocking_ref);
|
||
if (i != 0)
|
||
g_string_append (joined, ", ");
|
||
g_string_append (joined, id);
|
||
}
|
||
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_RUNTIME_USED,
|
||
_("Can't remove %s, it is needed for: %s"), flatpak_decomposed_get_pref (ref), joined->str);
|
||
}
|
||
}
|
||
|
||
old_active = g_strdup (flatpak_deploy_data_get_commit (deploy_data));
|
||
|
||
g_info ("dropping active ref");
|
||
if (!flatpak_dir_set_active (self, ref, NULL, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (flatpak_decomposed_is_app (ref))
|
||
{
|
||
current_ref = flatpak_dir_current_ref (self, name, cancellable);
|
||
if (current_ref != NULL &&
|
||
flatpak_decomposed_equal (ref, current_ref))
|
||
{
|
||
g_info ("dropping current ref");
|
||
if (!flatpak_dir_drop_current_ref (self, name, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
if (!flatpak_dir_update_deploy_ref (self, flatpak_decomposed_get_ref (ref), NULL, error))
|
||
return FALSE;
|
||
|
||
if (!flatpak_dir_undeploy_all (self, ref, force_remove, &was_deployed, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (!keep_ref &&
|
||
!flatpak_dir_remove_ref (self, repository, flatpak_decomposed_get_ref (ref), cancellable, error))
|
||
return FALSE;
|
||
|
||
/* Take this opportunity to clean up refs/mirrors/ since a prune will happen
|
||
* after this uninstall operation. See
|
||
* https://github.com/flatpak/flatpak/issues/3222
|
||
*/
|
||
if (!flatpak_dir_delete_mirror_refs (self, FALSE, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (flatpak_decomposed_is_app (ref) &&
|
||
!flatpak_dir_update_exports (self, name, cancellable, error))
|
||
return FALSE;
|
||
|
||
glnx_release_lock_file (&lock);
|
||
|
||
flatpak_dir_prune_origin_remote (self, repository);
|
||
|
||
flatpak_dir_cleanup_removed (self, cancellable, NULL);
|
||
|
||
if (!flatpak_dir_mark_changed (self, error))
|
||
return FALSE;
|
||
|
||
if (!was_deployed)
|
||
{
|
||
const char *branch = flatpak_decomposed_get_branch (ref);
|
||
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED,
|
||
_("%s branch %s is not installed"), name, branch);
|
||
return FALSE;
|
||
}
|
||
|
||
flatpak_dir_log (self, "uninstall", NULL, flatpak_decomposed_get_ref (ref), NULL, old_active, NULL,
|
||
"Uninstalled %s", flatpak_decomposed_get_ref (ref));
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_collect_deployed_refs (FlatpakDir *self,
|
||
const char *type,
|
||
const char *name_prefix,
|
||
const char *arch,
|
||
const char *branch,
|
||
GHashTable *hash,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
gboolean ret = FALSE;
|
||
g_autoptr(GFile) dir = NULL;
|
||
g_autoptr(GFileEnumerator) dir_enum = NULL;
|
||
g_autoptr(GFileInfo) child_info = NULL;
|
||
GError *temp_error = NULL;
|
||
FlatpakKinds kind;
|
||
|
||
if (strcmp (type, "app") == 0)
|
||
kind = FLATPAK_KINDS_APP;
|
||
else
|
||
kind = FLATPAK_KINDS_RUNTIME;
|
||
|
||
dir = g_file_get_child (self->basedir, type);
|
||
if (!g_file_query_exists (dir, cancellable))
|
||
return TRUE;
|
||
|
||
dir_enum = g_file_enumerate_children (dir, OSTREE_GIO_FAST_QUERYINFO,
|
||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||
cancellable,
|
||
error);
|
||
if (!dir_enum)
|
||
goto out;
|
||
|
||
while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL)
|
||
{
|
||
const char *name = g_file_info_get_name (child_info);
|
||
|
||
if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY &&
|
||
name[0] != '.' && (name_prefix == NULL || g_str_has_prefix (name, name_prefix)))
|
||
{
|
||
g_autoptr(GFile) child1 = g_file_get_child (dir, name);
|
||
g_autoptr(GFile) child2 = g_file_get_child (child1, arch);
|
||
g_autoptr(GFile) child3 = g_file_get_child (child2, branch);
|
||
g_autoptr(GFile) active = g_file_get_child (child3, "active");
|
||
|
||
if (g_file_query_exists (active, cancellable))
|
||
{
|
||
FlatpakDecomposed *ref = flatpak_decomposed_new_from_parts (kind, name, arch, branch, NULL);
|
||
if (ref)
|
||
g_hash_table_add (hash, ref);
|
||
}
|
||
}
|
||
|
||
g_clear_object (&child_info);
|
||
}
|
||
|
||
if (temp_error != NULL)
|
||
{
|
||
g_propagate_error (error, temp_error);
|
||
goto out;
|
||
}
|
||
|
||
ret = TRUE;
|
||
out:
|
||
return ret;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_collect_unmaintained_refs (FlatpakDir *self,
|
||
const char *name_prefix,
|
||
const char *arch,
|
||
const char *branch,
|
||
GHashTable *hash,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
gboolean ret = FALSE;
|
||
g_autoptr(GFile) unmaintained_dir = NULL;
|
||
g_autoptr(GFileEnumerator) unmaintained_dir_enum = NULL;
|
||
g_autoptr(GFileInfo) child_info = NULL;
|
||
GError *temp_error = NULL;
|
||
|
||
unmaintained_dir = g_file_get_child (self->basedir, "extension");
|
||
if (!g_file_query_exists (unmaintained_dir, cancellable))
|
||
return TRUE;
|
||
|
||
unmaintained_dir_enum = g_file_enumerate_children (unmaintained_dir, G_FILE_ATTRIBUTE_STANDARD_NAME,
|
||
G_FILE_QUERY_INFO_NONE,
|
||
cancellable,
|
||
error);
|
||
if (!unmaintained_dir_enum)
|
||
goto out;
|
||
|
||
while ((child_info = g_file_enumerator_next_file (unmaintained_dir_enum, cancellable, &temp_error)) != NULL)
|
||
{
|
||
const char *name = g_file_info_get_name (child_info);
|
||
|
||
if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY &&
|
||
name[0] != '.' && (name_prefix == NULL || g_str_has_prefix (name, name_prefix)))
|
||
{
|
||
g_autoptr(GFile) child1 = g_file_get_child (unmaintained_dir, name);
|
||
g_autoptr(GFile) child2 = g_file_get_child (child1, arch);
|
||
g_autoptr(GFile) child3 = g_file_get_child (child2, branch);
|
||
|
||
if (g_file_query_exists (child3, cancellable))
|
||
g_hash_table_add (hash, g_strdup (name));
|
||
}
|
||
|
||
g_clear_object (&child_info);
|
||
}
|
||
|
||
if (temp_error != NULL)
|
||
{
|
||
g_propagate_error (error, temp_error);
|
||
goto out;
|
||
}
|
||
|
||
ret = TRUE;
|
||
out:
|
||
return ret;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_list_deployed (FlatpakDir *self,
|
||
FlatpakDecomposed *ref,
|
||
char ***deployed_ids,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
gboolean ret = FALSE;
|
||
g_autoptr(GFile) deploy_base = NULL;
|
||
g_autoptr(GPtrArray) ids = NULL;
|
||
GError *temp_error = NULL;
|
||
g_autoptr(GFileEnumerator) dir_enum = NULL;
|
||
g_autoptr(GFile) child = NULL;
|
||
g_autoptr(GFileInfo) child_info = NULL;
|
||
g_autoptr(GError) my_error = NULL;
|
||
|
||
deploy_base = flatpak_dir_get_deploy_dir (self, ref);
|
||
|
||
ids = g_ptr_array_new_with_free_func (g_free);
|
||
|
||
dir_enum = g_file_enumerate_children (deploy_base, OSTREE_GIO_FAST_QUERYINFO,
|
||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||
cancellable,
|
||
&my_error);
|
||
if (!dir_enum)
|
||
{
|
||
if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||
ret = TRUE; /* Success, but empty */
|
||
else
|
||
g_propagate_error (error, g_steal_pointer (&my_error));
|
||
goto out;
|
||
}
|
||
|
||
while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL)
|
||
{
|
||
const char *name;
|
||
|
||
name = g_file_info_get_name (child_info);
|
||
|
||
g_clear_object (&child);
|
||
child = g_file_get_child (deploy_base, name);
|
||
|
||
if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY &&
|
||
name[0] != '.' &&
|
||
strlen (name) == 64)
|
||
g_ptr_array_add (ids, g_strdup (name));
|
||
|
||
g_clear_object (&child_info);
|
||
}
|
||
|
||
if (temp_error != NULL)
|
||
{
|
||
g_propagate_error (error, temp_error);
|
||
goto out;
|
||
}
|
||
|
||
ret = TRUE;
|
||
|
||
out:
|
||
if (ret)
|
||
{
|
||
g_ptr_array_add (ids, NULL);
|
||
*deployed_ids = (char **) g_ptr_array_free (g_steal_pointer (&ids), FALSE);
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
static gboolean
|
||
dir_is_locked (GFile *dir)
|
||
{
|
||
glnx_autofd int ref_fd = -1;
|
||
struct flock lock = {0};
|
||
g_autoptr(GFile) reffile = NULL;
|
||
|
||
reffile = g_file_resolve_relative_path (dir, "files/.ref");
|
||
|
||
ref_fd = open (flatpak_file_get_path_cached (reffile), O_RDWR | O_CLOEXEC);
|
||
if (ref_fd != -1)
|
||
{
|
||
lock.l_type = F_WRLCK;
|
||
lock.l_whence = SEEK_SET;
|
||
lock.l_start = 0;
|
||
lock.l_len = 0;
|
||
|
||
if (fcntl (ref_fd, F_GETLK, &lock) == 0)
|
||
return lock.l_type != F_UNLCK;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_undeploy (FlatpakDir *self,
|
||
FlatpakDecomposed *ref,
|
||
const char *active_id,
|
||
gboolean is_update,
|
||
gboolean force_remove,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) deploy_base = NULL;
|
||
g_autoptr(GFile) checkoutdir = NULL;
|
||
g_autoptr(GFile) removed_subdir = NULL;
|
||
g_autoptr(GFile) removed_dir = NULL;
|
||
g_autofree char *id = NULL;
|
||
g_autofree char *dirname = NULL;
|
||
g_autofree char *current_active = NULL;
|
||
g_autoptr(GFile) change_file = NULL;
|
||
g_autoptr(GError) child_error = NULL;
|
||
int i, retry;
|
||
|
||
g_assert (ref != NULL);
|
||
g_assert (active_id != NULL);
|
||
|
||
deploy_base = flatpak_dir_get_deploy_dir (self, ref);
|
||
|
||
checkoutdir = g_file_get_child (deploy_base, active_id);
|
||
if (!g_file_query_exists (checkoutdir, cancellable))
|
||
{
|
||
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED,
|
||
_("%s commit %s not installed"),
|
||
flatpak_decomposed_get_ref (ref), active_id);
|
||
return FALSE;
|
||
}
|
||
|
||
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
||
return FALSE;
|
||
|
||
current_active = flatpak_dir_read_active (self, ref, cancellable);
|
||
if (current_active != NULL && strcmp (current_active, active_id) == 0)
|
||
{
|
||
g_auto(GStrv) deployed_ids = NULL;
|
||
const char *some_deployment;
|
||
|
||
/* We're removing the active deployment, start by repointing that
|
||
to another deployment if one exists */
|
||
|
||
if (!flatpak_dir_list_deployed (self, ref,
|
||
&deployed_ids,
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
some_deployment = NULL;
|
||
for (i = 0; deployed_ids[i] != NULL; i++)
|
||
{
|
||
if (strcmp (deployed_ids[i], active_id) == 0)
|
||
continue;
|
||
|
||
some_deployment = deployed_ids[i];
|
||
break;
|
||
}
|
||
|
||
if (!flatpak_dir_set_active (self, ref, some_deployment, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
removed_dir = flatpak_dir_get_removed_dir (self);
|
||
if (!flatpak_mkdir_p (removed_dir, cancellable, error))
|
||
return FALSE;
|
||
|
||
id = flatpak_decomposed_dup_id (ref);
|
||
dirname = g_strdup_printf ("%s-%s", id, active_id);
|
||
|
||
removed_subdir = g_file_get_child (removed_dir, dirname);
|
||
|
||
retry = 0;
|
||
while (TRUE)
|
||
{
|
||
g_autoptr(GError) local_error = NULL;
|
||
g_autoptr(GFile) tmpdir = NULL;
|
||
g_autofree char *tmpname = NULL;
|
||
|
||
if (flatpak_file_rename (checkoutdir,
|
||
removed_subdir,
|
||
cancellable, &local_error))
|
||
break;
|
||
|
||
if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_EXISTS) || retry >= 10)
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return FALSE;
|
||
}
|
||
|
||
retry++;
|
||
|
||
/* Destination already existed, move that aside, as we want to use the exact
|
||
* removed dirname for the latest undeployed version */
|
||
|
||
tmpname = g_strdup_printf ("%s-XXXXXX", dirname);
|
||
glnx_gen_temp_name (tmpname);
|
||
tmpdir = g_file_get_child (removed_dir, tmpname);
|
||
|
||
if (!flatpak_file_rename (removed_subdir,
|
||
tmpdir,
|
||
cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
if (is_update)
|
||
change_file = g_file_resolve_relative_path (removed_subdir, "files/.updated");
|
||
else
|
||
change_file = g_file_resolve_relative_path (removed_subdir, "files/.removed");
|
||
|
||
if (!g_file_replace_contents (change_file, "", 0, NULL, FALSE,
|
||
G_FILE_CREATE_REPLACE_DESTINATION, NULL, NULL, &child_error))
|
||
{
|
||
g_autofree gchar *path = g_file_get_path (change_file);
|
||
g_warning ("Unable to clear %s: %s", path, child_error->message);
|
||
g_clear_error (&child_error);
|
||
}
|
||
|
||
if (force_remove || !dir_is_locked (removed_subdir))
|
||
{
|
||
g_autoptr(GError) tmp_error = NULL;
|
||
|
||
if (!flatpak_rm_rf (removed_subdir, cancellable, &tmp_error))
|
||
g_warning ("Unable to remove old checkout: %s", tmp_error->message);
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_undeploy_all (FlatpakDir *self,
|
||
FlatpakDecomposed *ref,
|
||
gboolean force_remove,
|
||
gboolean *was_deployed_out,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_auto(GStrv) deployed = NULL;
|
||
g_autoptr(GFile) deploy_base = NULL;
|
||
g_autoptr(GFile) arch_dir = NULL;
|
||
g_autoptr(GFile) top_dir = NULL;
|
||
GError *temp_error = NULL;
|
||
int i;
|
||
gboolean was_deployed;
|
||
|
||
if (!flatpak_dir_list_deployed (self, ref, &deployed, cancellable, error))
|
||
return FALSE;
|
||
|
||
for (i = 0; deployed[i] != NULL; i++)
|
||
{
|
||
g_info ("undeploying %s", deployed[i]);
|
||
if (!flatpak_dir_undeploy (self, ref, deployed[i], FALSE, force_remove, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
deploy_base = flatpak_dir_get_deploy_dir (self, ref);
|
||
was_deployed = g_file_query_exists (deploy_base, cancellable);
|
||
if (was_deployed)
|
||
{
|
||
g_info ("removing deploy base");
|
||
if (!flatpak_rm_rf (deploy_base, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
g_info ("cleaning up empty directories");
|
||
arch_dir = g_file_get_parent (deploy_base);
|
||
if (g_file_query_exists (arch_dir, cancellable) &&
|
||
!g_file_delete (arch_dir, cancellable, &temp_error))
|
||
{
|
||
if (!g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_EMPTY))
|
||
{
|
||
g_propagate_error (error, temp_error);
|
||
return FALSE;
|
||
}
|
||
g_clear_error (&temp_error);
|
||
}
|
||
|
||
top_dir = g_file_get_parent (arch_dir);
|
||
if (g_file_query_exists (top_dir, cancellable) &&
|
||
!g_file_delete (top_dir, cancellable, &temp_error))
|
||
{
|
||
if (!g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_EMPTY))
|
||
{
|
||
g_propagate_error (error, temp_error);
|
||
return FALSE;
|
||
}
|
||
g_clear_error (&temp_error);
|
||
}
|
||
|
||
if (was_deployed_out)
|
||
*was_deployed_out = was_deployed;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* flatpak_dir_remove_ref:
|
||
* @self: a #FlatpakDir
|
||
* @remote_name: the name of the remote
|
||
* @ref: the flatpak ref to remove
|
||
* @cancellable: (nullable) (optional): a #GCancellable
|
||
* @error: a #GError
|
||
*
|
||
* Remove the flatpak ref given by @remote_name:@ref from the underlying
|
||
* OSTree repo. Attempting to remove a ref that is currently deployed
|
||
* is an error, you need to uninstall the flatpak first. Note that this does
|
||
* not remove the objects bound to @ref from the disk, you will need to
|
||
* call flatpak_dir_prune() to do that.
|
||
*
|
||
* Returns: %TRUE if removing the ref succeeded, %FALSE otherwise.
|
||
*/
|
||
gboolean
|
||
flatpak_dir_remove_ref (FlatpakDir *self,
|
||
const char *remote_name,
|
||
const char *ref, /* NOTE: Not necessarily a app/runtime ref */
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
if (flatpak_dir_use_system_helper (self, NULL))
|
||
{
|
||
const char *installation = flatpak_dir_get_id (self);
|
||
|
||
if (!flatpak_dir_system_helper_call_remove_local_ref (self,
|
||
FLATPAK_HELPER_REMOVE_LOCAL_REF_FLAGS_NONE,
|
||
remote_name,
|
||
ref,
|
||
installation ? installation : "",
|
||
cancellable,
|
||
error))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
if (!ostree_repo_set_ref_immediate (self->repo,
|
||
remote_name,
|
||
ref,
|
||
NULL,
|
||
cancellable,
|
||
error))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_cleanup_removed (FlatpakDir *self,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
gboolean ret = FALSE;
|
||
g_autoptr(GFile) removed_dir = NULL;
|
||
g_autoptr(GFileEnumerator) dir_enum = NULL;
|
||
g_autoptr(GFileInfo) child_info = NULL;
|
||
GError *temp_error = NULL;
|
||
|
||
removed_dir = flatpak_dir_get_removed_dir (self);
|
||
if (!g_file_query_exists (removed_dir, cancellable))
|
||
return TRUE;
|
||
|
||
dir_enum = g_file_enumerate_children (removed_dir, OSTREE_GIO_FAST_QUERYINFO,
|
||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||
cancellable,
|
||
error);
|
||
if (!dir_enum)
|
||
goto out;
|
||
|
||
while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL)
|
||
{
|
||
const char *name = g_file_info_get_name (child_info);
|
||
g_autoptr(GFile) child = g_file_get_child (removed_dir, name);
|
||
|
||
if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY &&
|
||
!dir_is_locked (child))
|
||
{
|
||
g_autoptr(GError) tmp_error = NULL;
|
||
if (!flatpak_rm_rf (child, cancellable, &tmp_error))
|
||
g_warning ("Unable to remove old checkout: %s", tmp_error->message);
|
||
}
|
||
|
||
g_clear_object (&child_info);
|
||
}
|
||
|
||
if (temp_error != NULL)
|
||
{
|
||
g_propagate_error (error, temp_error);
|
||
goto out;
|
||
}
|
||
|
||
ret = TRUE;
|
||
out:
|
||
return ret;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_prune (FlatpakDir *self,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
gboolean ret = FALSE;
|
||
gint objects_total, objects_pruned;
|
||
guint64 pruned_object_size_total;
|
||
g_autofree char *formatted_freed_size = NULL;
|
||
g_autoptr(GError) local_error = NULL;
|
||
g_autoptr(GError) lock_error = NULL;
|
||
g_auto(GLnxLockFile) lock = { 0, };
|
||
|
||
if (error == NULL)
|
||
error = &local_error;
|
||
|
||
if (flatpak_dir_use_system_helper (self, NULL))
|
||
{
|
||
const char *installation = flatpak_dir_get_id (self);
|
||
|
||
if (!flatpak_dir_system_helper_call_prune_local_repo (self,
|
||
FLATPAK_HELPER_PRUNE_LOCAL_REPO_FLAGS_NONE,
|
||
installation ? installation : "",
|
||
cancellable,
|
||
error))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
||
goto out;
|
||
|
||
/* This could remove objects, so take an exclusive repo lock */
|
||
if (!flatpak_dir_repo_lock (self, &lock, LOCK_EX | LOCK_NB, cancellable, &lock_error))
|
||
{
|
||
/* If we can't get an exclusive lock, don't block for a long time. Eventually
|
||
the shared lock operation is released and we will do a prune then */
|
||
if (g_error_matches (lock_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
|
||
{
|
||
g_info ("Skipping prune due to in progress operation");
|
||
return TRUE;
|
||
}
|
||
|
||
g_propagate_error (error, g_steal_pointer (&lock_error));
|
||
return FALSE;
|
||
}
|
||
|
||
g_info ("Pruning repo");
|
||
if (!ostree_repo_prune (self->repo,
|
||
OSTREE_REPO_PRUNE_FLAGS_REFS_ONLY,
|
||
0,
|
||
&objects_total,
|
||
&objects_pruned,
|
||
&pruned_object_size_total,
|
||
cancellable, error))
|
||
goto out;
|
||
|
||
formatted_freed_size = g_format_size_full (pruned_object_size_total, 0);
|
||
g_info ("Pruned %d/%d objects, size %s", objects_total, objects_pruned, formatted_freed_size);
|
||
|
||
ret = TRUE;
|
||
|
||
out:
|
||
|
||
/* There was an issue in ostree where for local pulls we don't get a .commitpartial (now fixed),
|
||
which caused errors when pruning. We print these here, but don't stop processing. */
|
||
if (local_error != NULL)
|
||
g_print (_("Pruning repo failed: %s"), local_error->message);
|
||
|
||
return ret;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_update_summary (FlatpakDir *self,
|
||
gboolean delete,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
if (flatpak_dir_use_system_helper (self, NULL))
|
||
{
|
||
const char *installation = flatpak_dir_get_id (self);
|
||
|
||
return flatpak_dir_system_helper_call_update_summary (self,
|
||
delete ? FLATPAK_HELPER_UPDATE_SUMMARY_FLAGS_DELETE
|
||
: FLATPAK_HELPER_UPDATE_SUMMARY_FLAGS_NONE,
|
||
installation ? installation : "",
|
||
cancellable,
|
||
error);
|
||
}
|
||
|
||
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (delete)
|
||
{
|
||
g_autoptr(GError) local_error = NULL;
|
||
g_autoptr(GFile) summary_file = NULL;
|
||
|
||
g_info ("Deleting summary");
|
||
|
||
summary_file = g_file_get_child (ostree_repo_get_path (self->repo), "summary");
|
||
|
||
if (!g_file_delete (summary_file, cancellable, &local_error) &&
|
||
!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return FALSE;
|
||
}
|
||
return TRUE;
|
||
}
|
||
else
|
||
{
|
||
g_auto(GLnxLockFile) lock = { 0, };
|
||
|
||
g_info ("Updating summary");
|
||
|
||
/* Keep a shared repo lock to avoid prunes removing objects we're relying on
|
||
* while generating the summary. */
|
||
if (!flatpak_dir_repo_lock (self, &lock, LOCK_SH, cancellable, error))
|
||
return FALSE;
|
||
|
||
return ostree_repo_regenerate_summary (self->repo, NULL, cancellable, error);
|
||
}
|
||
}
|
||
|
||
GFile *
|
||
flatpak_dir_get_if_deployed (FlatpakDir *self,
|
||
FlatpakDecomposed *ref,
|
||
const char *checksum,
|
||
GCancellable *cancellable)
|
||
{
|
||
g_autoptr(GFile) deploy_base = NULL;
|
||
g_autoptr(GFile) deploy_dir = NULL;
|
||
|
||
deploy_base = flatpak_dir_get_deploy_dir (self, ref);
|
||
|
||
if (checksum != NULL)
|
||
{
|
||
deploy_dir = g_file_get_child (deploy_base, checksum);
|
||
}
|
||
else
|
||
{
|
||
g_autoptr(GFile) active_link = g_file_get_child (deploy_base, "active");
|
||
g_autoptr(GFileInfo) info = NULL;
|
||
const char *target;
|
||
|
||
info = g_file_query_info (active_link,
|
||
G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
|
||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||
NULL,
|
||
NULL);
|
||
if (info == NULL)
|
||
return NULL;
|
||
|
||
target = g_file_info_get_symlink_target (info);
|
||
if (target == NULL)
|
||
return NULL;
|
||
|
||
deploy_dir = g_file_get_child (deploy_base, target);
|
||
}
|
||
|
||
if (g_file_query_file_type (deploy_dir, G_FILE_QUERY_INFO_NONE, cancellable) == G_FILE_TYPE_DIRECTORY)
|
||
return g_steal_pointer (&deploy_dir);
|
||
|
||
/* Maybe it was removed but is still living? */
|
||
if (checksum != NULL)
|
||
{
|
||
g_autoptr(GFile) removed_dir = flatpak_dir_get_removed_dir (self);
|
||
g_autoptr(GFile) removed_deploy_dir = NULL;
|
||
g_autofree char *id = flatpak_decomposed_dup_id (ref);
|
||
g_autofree char *dirname = NULL;
|
||
|
||
dirname = g_strdup_printf ("%s-%s", id, checksum);
|
||
removed_deploy_dir = g_file_get_child (removed_dir, dirname);
|
||
|
||
if (g_file_query_file_type (removed_deploy_dir, G_FILE_QUERY_INFO_NONE, cancellable) == G_FILE_TYPE_DIRECTORY)
|
||
return g_steal_pointer (&removed_deploy_dir);
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
GFile *
|
||
flatpak_dir_get_unmaintained_extension_dir_if_exists (FlatpakDir *self,
|
||
const char *name,
|
||
const char *arch,
|
||
const char *branch,
|
||
GCancellable *cancellable)
|
||
{
|
||
g_autoptr(GFile) extension_dir = NULL;
|
||
g_autoptr(GFileInfo) extension_dir_info = NULL;
|
||
|
||
extension_dir = flatpak_dir_get_unmaintained_extension_dir (self, name, arch, branch);
|
||
|
||
extension_dir_info = g_file_query_info (extension_dir,
|
||
G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET "," G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK,
|
||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||
cancellable,
|
||
NULL);
|
||
if (extension_dir_info == NULL)
|
||
return NULL;
|
||
|
||
if (g_file_info_get_is_symlink (extension_dir_info))
|
||
return g_file_new_for_path (g_file_info_get_symlink_target (extension_dir_info));
|
||
else
|
||
return g_steal_pointer (&extension_dir);
|
||
}
|
||
|
||
static void
|
||
remote_filter_free (RemoteFilter *remote_filter)
|
||
{
|
||
g_free (remote_filter->checksum);
|
||
g_object_unref (remote_filter->path);
|
||
if (remote_filter->allow)
|
||
g_regex_unref (remote_filter->allow);
|
||
if (remote_filter->deny)
|
||
g_regex_unref (remote_filter->deny);
|
||
|
||
g_free (remote_filter);
|
||
}
|
||
|
||
|
||
static RemoteFilter *
|
||
remote_filter_load (GFile *path, GError **error)
|
||
{
|
||
RemoteFilter *filter;
|
||
g_autofree char *data = NULL;
|
||
gsize data_size;
|
||
GTimeVal mtime;
|
||
g_autoptr(GRegex) allow_refs = NULL;
|
||
g_autoptr(GRegex) deny_refs = NULL;
|
||
|
||
/* Save mtime before loading to avoid races */
|
||
if (!get_mtime (path, &mtime, NULL, error))
|
||
{
|
||
glnx_prefix_error (error, _("Failed to load filter '%s'"), flatpak_file_get_path_cached (path));
|
||
return NULL;
|
||
}
|
||
|
||
if (!g_file_load_contents (path, NULL, &data, &data_size, NULL, error))
|
||
{
|
||
glnx_prefix_error (error, _("Failed to load filter '%s'"), flatpak_file_get_path_cached (path));
|
||
return NULL;
|
||
}
|
||
|
||
if (!flatpak_parse_filters (data, &allow_refs, &deny_refs, error))
|
||
{
|
||
glnx_prefix_error (error, _("Failed to parse filter '%s'"), flatpak_file_get_path_cached (path));
|
||
return NULL;
|
||
}
|
||
|
||
filter = g_new0 (RemoteFilter, 1);
|
||
filter->checksum = g_compute_checksum_for_data (G_CHECKSUM_SHA256, (guchar *)data, data_size);
|
||
filter->path = g_object_ref (path);
|
||
filter->mtime = mtime;
|
||
filter->last_mtime_check = g_get_monotonic_time ();
|
||
filter->allow = g_steal_pointer (&allow_refs);
|
||
filter->deny = g_steal_pointer (&deny_refs);
|
||
|
||
return filter;
|
||
}
|
||
|
||
G_LOCK_DEFINE_STATIC (filters);
|
||
|
||
static gboolean
|
||
flatpak_dir_lookup_remote_filter (FlatpakDir *self,
|
||
const char *name,
|
||
gboolean force_load,
|
||
char **checksum_out,
|
||
GRegex **allow_regex,
|
||
GRegex **deny_regex,
|
||
GError **error)
|
||
{
|
||
RemoteFilter *filter = NULL;
|
||
g_autofree char *filter_path = NULL;
|
||
gboolean handled_fallback = FALSE;
|
||
g_autoptr(GFile) filter_file = NULL;
|
||
|
||
if (checksum_out)
|
||
*checksum_out = NULL;
|
||
*allow_regex = NULL;
|
||
*deny_regex = NULL;
|
||
|
||
filter_path = flatpak_dir_get_remote_filter (self, name);
|
||
|
||
if (filter_path == NULL)
|
||
return TRUE;
|
||
|
||
filter_file = g_file_new_for_path (filter_path);
|
||
|
||
G_LOCK (filters);
|
||
|
||
if (self->remote_filters == NULL)
|
||
self->remote_filters = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) remote_filter_free);
|
||
|
||
filter = g_hash_table_lookup (self->remote_filters, name);
|
||
if (filter)
|
||
{
|
||
guint64 now = g_get_monotonic_time ();
|
||
GTimeVal mtime;
|
||
|
||
if (g_file_equal (filter->path, filter_file) != 0)
|
||
filter = NULL; /* New path, reload */
|
||
else if ((now - filter->last_mtime_check) > (1000 * (FILTER_MTIME_CHECK_TIMEOUT_MSEC)))
|
||
{
|
||
/* Fall back to backup copy if remote filter disappears */
|
||
handled_fallback = TRUE;
|
||
if (!g_file_query_exists (filter_file, NULL))
|
||
{
|
||
g_autofree char *basename = g_strconcat (name, ".filter", NULL);
|
||
g_object_unref (filter_file);
|
||
filter_file = flatpak_build_file (self->basedir, "repo", basename, NULL);
|
||
}
|
||
|
||
filter->last_mtime_check = now;
|
||
if (!get_mtime (filter_file, &mtime, NULL, NULL) ||
|
||
mtime.tv_sec != filter->mtime.tv_sec ||
|
||
mtime.tv_usec != filter->mtime.tv_usec)
|
||
filter = NULL; /* Different mtime, reload */
|
||
}
|
||
}
|
||
|
||
if (filter)
|
||
{
|
||
if (checksum_out)
|
||
*checksum_out = g_strdup (filter->checksum);
|
||
if (filter->allow)
|
||
*allow_regex = g_regex_ref (filter->allow);
|
||
if (filter->deny)
|
||
*deny_regex = g_regex_ref (filter->deny);
|
||
}
|
||
|
||
G_UNLOCK (filters);
|
||
|
||
if (filter) /* This is outside the lock, but we already copied the returned data, and we're not dereferencing filter */
|
||
return TRUE;
|
||
|
||
/* Fall back to backup copy if remote filter disappears */
|
||
if (!handled_fallback && !g_file_query_exists (filter_file, NULL))
|
||
{
|
||
g_autofree char *basename = g_strconcat (name, ".filter", NULL);
|
||
g_object_unref (filter_file);
|
||
filter_file = flatpak_build_file (self->basedir, "repo", basename, NULL);
|
||
}
|
||
|
||
filter = remote_filter_load (filter_file, error);
|
||
if (filter == NULL)
|
||
return FALSE;
|
||
|
||
if (checksum_out)
|
||
*checksum_out = g_strdup (filter->checksum);
|
||
if (filter->allow)
|
||
*allow_regex = g_regex_ref (filter->allow);
|
||
if (filter->deny)
|
||
*deny_regex = g_regex_ref (filter->deny);
|
||
|
||
G_LOCK (filters);
|
||
g_hash_table_replace (self->remote_filters, g_strdup (name), filter);
|
||
G_UNLOCK (filters);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
G_LOCK_DEFINE_STATIC (cache);
|
||
|
||
/* FIXME: Move all this caching into libostree. */
|
||
static void
|
||
cached_summary_free (CachedSummary *summary)
|
||
{
|
||
g_bytes_unref (summary->bytes);
|
||
if (summary->bytes_sig)
|
||
g_bytes_unref (summary->bytes_sig);
|
||
g_free (summary->name);
|
||
g_free (summary->url);
|
||
g_free (summary);
|
||
}
|
||
|
||
static CachedSummary *
|
||
cached_summary_new (GBytes *bytes,
|
||
GBytes *bytes_sig,
|
||
const char *name,
|
||
const char *url)
|
||
{
|
||
CachedSummary *summary = g_new0 (CachedSummary, 1);
|
||
|
||
summary->bytes = g_bytes_ref (bytes);
|
||
if (bytes_sig)
|
||
summary->bytes_sig = g_bytes_ref (bytes_sig);
|
||
summary->url = g_strdup (url);
|
||
summary->name = g_strdup (name);
|
||
summary->time = g_get_monotonic_time ();
|
||
return summary;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_lookup_cached_summary (FlatpakDir *self,
|
||
GBytes **bytes_out,
|
||
GBytes **bytes_sig_out,
|
||
const char *name,
|
||
const char *url)
|
||
{
|
||
CachedSummary *summary;
|
||
gboolean res = FALSE;
|
||
|
||
G_LOCK (cache);
|
||
|
||
if (self->summary_cache == NULL)
|
||
self->summary_cache = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) cached_summary_free);
|
||
|
||
summary = g_hash_table_lookup (self->summary_cache, name);
|
||
if (summary)
|
||
{
|
||
guint64 now = g_get_monotonic_time ();
|
||
if ((now - summary->time) / G_USEC_PER_SEC < SUMMARY_CACHE_TIMEOUT_SEC &&
|
||
strcmp (url, summary->url) == 0)
|
||
{
|
||
/* g_info ("Using cached summary for remote %s", name); */
|
||
*bytes_out = g_bytes_ref (summary->bytes);
|
||
if (bytes_sig_out)
|
||
{
|
||
if (summary->bytes_sig)
|
||
*bytes_sig_out = g_bytes_ref (summary->bytes_sig);
|
||
else
|
||
*bytes_sig_out = NULL;
|
||
}
|
||
res = TRUE;
|
||
|
||
/* Bump the cache expiry time */
|
||
summary->time = now;
|
||
}
|
||
else
|
||
{
|
||
/* Timed out or URL has changed; remove the entry */
|
||
g_hash_table_remove (self->summary_cache, name);
|
||
res = FALSE;
|
||
}
|
||
}
|
||
|
||
G_UNLOCK (cache);
|
||
|
||
return res;
|
||
}
|
||
|
||
static void
|
||
flatpak_dir_cache_summary (FlatpakDir *self,
|
||
GBytes *bytes,
|
||
GBytes *bytes_sig,
|
||
const char *name,
|
||
const char *url)
|
||
{
|
||
CachedSummary *summary;
|
||
|
||
/* No sense caching the summary if there isn't one */
|
||
if (!bytes)
|
||
return;
|
||
|
||
G_LOCK (cache);
|
||
|
||
/* This was already initialized in the cache-miss lookup */
|
||
g_assert (self->summary_cache != NULL);
|
||
|
||
summary = cached_summary_new (bytes, bytes_sig, name, url);
|
||
g_hash_table_replace (self->summary_cache, summary->name, summary);
|
||
|
||
G_UNLOCK (cache);
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_remote_make_oci_summary (FlatpakDir *self,
|
||
const char *remote,
|
||
gboolean only_cached,
|
||
GBytes **out_summary,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GVariant) summary = NULL;
|
||
g_autoptr(GFile) index_cache = NULL;
|
||
g_autofree char *index_uri = NULL;
|
||
g_autoptr(GFile) summary_cache = NULL;
|
||
g_autoptr(GError) local_error = NULL;
|
||
g_autoptr(GMappedFile) mfile = NULL;
|
||
g_autoptr(GBytes) cache_bytes = NULL;
|
||
g_autoptr(GBytes) summary_bytes = NULL;
|
||
|
||
if (flatpak_dir_use_system_helper (self, NULL))
|
||
{
|
||
const char *installation = flatpak_dir_get_id (self);
|
||
FlatpakHelperGenerateOciSummaryFlags flags = FLATPAK_HELPER_GENERATE_OCI_SUMMARY_FLAGS_NONE;
|
||
|
||
if (only_cached)
|
||
flags |= FLATPAK_HELPER_GENERATE_OCI_SUMMARY_FLAGS_ONLY_CACHED;
|
||
|
||
if (!flatpak_dir_system_helper_call_generate_oci_summary (self,
|
||
flags,
|
||
remote,
|
||
installation ? installation : "",
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
summary_cache = flatpak_dir_get_oci_summary_location (self, remote, error);
|
||
if (summary_cache == NULL)
|
||
return FALSE;
|
||
}
|
||
else
|
||
{
|
||
index_cache = flatpak_dir_update_oci_index (self, remote, &index_uri, cancellable, error);
|
||
if (index_cache == NULL)
|
||
return FALSE;
|
||
|
||
summary_cache = flatpak_dir_get_oci_summary_location (self, remote, error);
|
||
if (summary_cache == NULL)
|
||
return FALSE;
|
||
|
||
if (!only_cached && !check_destination_mtime (index_cache, summary_cache, cancellable))
|
||
{
|
||
summary = flatpak_oci_index_make_summary (index_cache, index_uri, cancellable, &local_error);
|
||
if (summary == NULL)
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return FALSE;
|
||
}
|
||
|
||
summary_bytes = g_variant_get_data_as_bytes (summary);
|
||
|
||
if (!g_file_replace_contents (summary_cache,
|
||
g_bytes_get_data (summary_bytes, NULL),
|
||
g_bytes_get_size (summary_bytes),
|
||
NULL, FALSE, 0, NULL, cancellable, error))
|
||
{
|
||
g_prefix_error (error, _("Failed to write summary cache: "));
|
||
return FALSE;
|
||
}
|
||
|
||
if (out_summary)
|
||
*out_summary = g_steal_pointer (&summary_bytes);
|
||
return TRUE;
|
||
}
|
||
}
|
||
|
||
if (out_summary)
|
||
{
|
||
mfile = g_mapped_file_new (flatpak_file_get_path_cached (summary_cache), FALSE, error);
|
||
if (mfile == NULL)
|
||
{
|
||
if (only_cached)
|
||
{
|
||
g_clear_error (error);
|
||
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_CACHED,
|
||
_("No oci summary cached for remote '%s'"), remote);
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
cache_bytes = g_mapped_file_get_bytes (mfile);
|
||
*out_summary = g_steal_pointer (&cache_bytes);
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
typedef struct {
|
||
char *filename;
|
||
gint64 mtime;
|
||
} CachedSummaryData;
|
||
|
||
static void
|
||
cached_summary_data_free (CachedSummaryData *data)
|
||
{
|
||
g_free (data->filename);
|
||
g_free (data);
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_gc_cached_digested_summaries (FlatpakDir *self,
|
||
const char *remote_name,
|
||
const char *dont_prune_file,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GHashTable) cached_data_for_arch = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)cached_summary_data_free);
|
||
g_autoptr(GFile) cache_dir = flatpak_build_file (self->cache_dir, "summaries", NULL);
|
||
g_auto(GLnxDirFdIterator) iter = {0};
|
||
struct dirent *dent;
|
||
g_autoptr(GError) local_error = NULL;
|
||
|
||
if (!glnx_dirfd_iterator_init_at (AT_FDCWD, flatpak_file_get_path_cached (cache_dir), FALSE, &iter, &local_error))
|
||
{
|
||
if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||
return TRUE;
|
||
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return FALSE;
|
||
}
|
||
|
||
g_autofree char *prefix = g_strconcat (remote_name, "-", NULL);
|
||
|
||
while (TRUE)
|
||
{
|
||
struct stat stbuf;
|
||
const char *arch_start, *arch_end;
|
||
g_autofree char *arch = NULL;
|
||
|
||
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&iter, &dent, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (dent == NULL)
|
||
break;
|
||
|
||
/* Cached are regular file named "${remote-name}-${arch}-${sha256}.sub", ignore anything else */
|
||
if (dent->d_type != DT_REG ||
|
||
!g_str_has_prefix (dent->d_name, prefix) ||
|
||
!g_str_has_suffix (dent->d_name, ".sub"))
|
||
continue;
|
||
|
||
arch_start = dent->d_name + strlen (prefix);
|
||
arch_end = strchr (arch_start, '-');
|
||
if (arch_end == NULL)
|
||
continue;
|
||
|
||
/* Keep the latest subsummary for each remote-name + arch so we can use it for deltas */
|
||
if (fstatat (iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) == -1)
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
return FALSE;
|
||
}
|
||
|
||
arch = g_strndup (arch_start, arch_end - arch_start);
|
||
|
||
CachedSummaryData *old_data = g_hash_table_lookup (cached_data_for_arch, arch);
|
||
if (old_data == NULL || stbuf.st_mtime > old_data->mtime)
|
||
{
|
||
CachedSummaryData *new_data;
|
||
|
||
if (old_data &&
|
||
strcmp (dont_prune_file, old_data->filename) != 0 &&
|
||
unlinkat (iter.fd, old_data->filename, 0) != 0)
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
return FALSE;
|
||
}
|
||
|
||
new_data = g_new0 (CachedSummaryData, 1);
|
||
new_data->filename = g_strdup (dent->d_name);
|
||
new_data->mtime = stbuf.st_mtime;
|
||
g_hash_table_insert (cached_data_for_arch, g_steal_pointer (&arch), new_data);
|
||
}
|
||
else /* stbuf.st_mtime <= old_data->mtime */
|
||
{
|
||
if (stbuf.st_mtime < old_data->mtime &&
|
||
strcmp (dont_prune_file, dent->d_name) != 0 &&
|
||
unlinkat (iter.fd, dent->d_name, 0) != 0)
|
||
{
|
||
glnx_set_error_from_errno (error);
|
||
return FALSE;
|
||
}
|
||
}
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
_flatpak_dir_remote_clear_cached_summary (FlatpakDir *self,
|
||
const char *remote,
|
||
const char *extension,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) cache_dir = flatpak_build_file (self->cache_dir, "summaries", NULL);
|
||
g_autofree char *filename = g_strconcat (remote, extension, NULL);
|
||
g_autoptr(GFile) file = flatpak_build_file (cache_dir, filename, NULL);
|
||
g_autoptr(GError) local_error = NULL;
|
||
|
||
if (!g_file_delete (file, NULL, &local_error) &&
|
||
!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_remote_clear_cached_summary (FlatpakDir *self,
|
||
const char *remote,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_info ("Clearing cached summaries for remote %s", remote);
|
||
if (!_flatpak_dir_remote_clear_cached_summary (self, remote, NULL, cancellable, error))
|
||
return FALSE;
|
||
if (!_flatpak_dir_remote_clear_cached_summary (self, remote, ".sig", cancellable, error))
|
||
return FALSE;
|
||
if (!_flatpak_dir_remote_clear_cached_summary (self, remote, ".idx", cancellable, error))
|
||
return FALSE;
|
||
if (!_flatpak_dir_remote_clear_cached_summary (self, remote, ".idx.sig", cancellable, error))
|
||
return FALSE;
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
static gboolean
|
||
flatpak_dir_remote_save_cached_summary (FlatpakDir *self,
|
||
const char *basename,
|
||
const char *main_ext,
|
||
const char *sig_ext,
|
||
GBytes *main,
|
||
GBytes *sig,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autofree char *main_file_name = g_strconcat (basename, main_ext, NULL);
|
||
g_autofree char *sig_file_name = g_strconcat (basename, sig_ext, NULL);
|
||
g_autoptr(GFile) cache_dir = flatpak_build_file (self->cache_dir, "summaries", NULL);
|
||
g_autoptr(GFile) main_cache_file = flatpak_build_file (cache_dir, main_file_name, NULL);
|
||
g_autoptr(GFile) sig_cache_file = flatpak_build_file (cache_dir, sig_file_name, NULL);
|
||
g_autoptr(GError) local_error = NULL;
|
||
|
||
if (!flatpak_mkdir_p (cache_dir, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (!g_file_replace_contents (main_cache_file, g_bytes_get_data (main, NULL), g_bytes_get_size (main), NULL, FALSE,
|
||
G_FILE_CREATE_REPLACE_DESTINATION, NULL, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (sig_ext)
|
||
{
|
||
if (sig)
|
||
{
|
||
if (!g_file_replace_contents (sig_cache_file, g_bytes_get_data (sig, NULL), g_bytes_get_size (sig), NULL, FALSE,
|
||
G_FILE_CREATE_REPLACE_DESTINATION, NULL, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
else
|
||
{
|
||
if (!g_file_delete (sig_cache_file, NULL, &local_error) &&
|
||
!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return FALSE;
|
||
}
|
||
}
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_remote_load_cached_summary (FlatpakDir *self,
|
||
const char *basename,
|
||
const char *checksum,
|
||
const char *main_ext,
|
||
const char *sig_ext,
|
||
GBytes **out_main,
|
||
GBytes **out_sig,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autofree char *main_file_name = g_strconcat (basename, main_ext, NULL);
|
||
g_autofree char *sig_file_name = g_strconcat (basename, sig_ext, NULL);
|
||
g_autoptr(GFile) main_cache_file = flatpak_build_file (self->cache_dir, "summaries", main_file_name, NULL);
|
||
g_autoptr(GFile) sig_cache_file = flatpak_build_file (self->cache_dir, "summaries", sig_file_name, NULL);
|
||
g_autoptr(GMappedFile) mfile = NULL;
|
||
g_autoptr(GMappedFile) sig_mfile = NULL;
|
||
g_autoptr(GBytes) mfile_bytes = NULL;
|
||
g_autofree char *sha256 = NULL;
|
||
|
||
mfile = g_mapped_file_new (flatpak_file_get_path_cached (main_cache_file), FALSE, NULL);
|
||
if (mfile == NULL)
|
||
{
|
||
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_CACHED,
|
||
_("No cached summary for remote '%s'"), basename);
|
||
return FALSE;
|
||
}
|
||
|
||
if (out_sig)
|
||
sig_mfile = g_mapped_file_new (flatpak_file_get_path_cached (sig_cache_file), FALSE, NULL);
|
||
|
||
mfile_bytes = g_mapped_file_get_bytes (mfile);
|
||
|
||
/* The checksum would've already been verified before the file was written,
|
||
* but check again in case something went wrong during disk I/O. This is
|
||
* especially important since the variant-schema-compiler code assumes the
|
||
* GVariant data is well formed and asserts otherwise.
|
||
*/
|
||
if (checksum != NULL)
|
||
{
|
||
sha256 = g_compute_checksum_for_bytes (G_CHECKSUM_SHA256, mfile_bytes);
|
||
if (strcmp (sha256, checksum) != 0)
|
||
{
|
||
g_autoptr(GError) local_error = NULL;
|
||
|
||
if (!g_file_delete (main_cache_file, NULL, &local_error) &&
|
||
!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||
{
|
||
g_autofree char *path = g_file_get_path (main_cache_file);
|
||
g_info ("Unable to delete file %s: %s", path, local_error->message);
|
||
g_clear_error (&local_error);
|
||
}
|
||
|
||
if (sig_ext)
|
||
{
|
||
if (!g_file_delete (sig_cache_file, NULL, &local_error) &&
|
||
!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||
{
|
||
g_autofree char *path = g_file_get_path (sig_cache_file);
|
||
g_info ("Unable to delete file %s: %s", path, local_error->message);
|
||
g_clear_error (&local_error);
|
||
}
|
||
}
|
||
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA,
|
||
_("Invalid checksum for indexed summary %s read from %s"),
|
||
checksum, flatpak_file_get_path_cached (main_cache_file));
|
||
}
|
||
}
|
||
|
||
*out_main = g_steal_pointer (&mfile_bytes);
|
||
if (sig_mfile)
|
||
*out_sig = g_mapped_file_get_bytes (sig_mfile);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_remote_fetch_summary (FlatpakDir *self,
|
||
const char *name_or_uri,
|
||
gboolean only_cached,
|
||
GBytes **out_summary,
|
||
GBytes **out_summary_sig,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autofree char *url = NULL;
|
||
gboolean is_local;
|
||
g_autoptr(GError) local_error = NULL;
|
||
g_autoptr(GBytes) summary = NULL;
|
||
g_autoptr(GBytes) summary_sig = NULL;
|
||
|
||
if (!ostree_repo_remote_get_url (self->repo, name_or_uri, &url, error))
|
||
return FALSE;
|
||
|
||
if (!g_str_has_prefix (name_or_uri, "file:") && flatpak_dir_get_remote_disabled (self, name_or_uri))
|
||
{
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
|
||
"Can't fetch summary from disabled remote ‘%s’", name_or_uri);
|
||
return FALSE;
|
||
}
|
||
|
||
is_local = g_str_has_prefix (url, "file:");
|
||
|
||
/* Seems ostree asserts if this is NULL */
|
||
if (error == NULL)
|
||
error = &local_error;
|
||
|
||
if (flatpak_dir_get_remote_oci (self, name_or_uri))
|
||
{
|
||
if (!flatpak_dir_remote_make_oci_summary (self, name_or_uri,
|
||
only_cached,
|
||
&summary,
|
||
cancellable,
|
||
error))
|
||
return FALSE;
|
||
}
|
||
else
|
||
{
|
||
if (only_cached)
|
||
{
|
||
if (!flatpak_dir_remote_load_cached_summary (self, name_or_uri, NULL, NULL, ".sig",
|
||
&summary, &summary_sig, cancellable, error))
|
||
return FALSE;
|
||
g_info ("Loaded summary from cache for remote ‘%s’", name_or_uri);
|
||
}
|
||
else
|
||
{
|
||
g_info ("Fetching summary file for remote ‘%s’", name_or_uri);
|
||
if (!ostree_repo_remote_fetch_summary (self->repo, name_or_uri,
|
||
&summary, &summary_sig,
|
||
cancellable,
|
||
error))
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
if (summary == NULL)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Remote listing for %s not available; server has no summary file. Check the URL passed to remote-add was valid."), name_or_uri);
|
||
|
||
if (!is_local && !only_cached)
|
||
{
|
||
g_autofree char *cache_key = g_strconcat ("summary-", name_or_uri, NULL);
|
||
flatpak_dir_cache_summary (self, summary, summary_sig, cache_key, url);
|
||
}
|
||
|
||
*out_summary = g_steal_pointer (&summary);
|
||
if (out_summary_sig)
|
||
*out_summary_sig = g_steal_pointer (&summary_sig);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
remote_verify_signature (OstreeRepo *repo,
|
||
const char *remote_name,
|
||
GBytes *data,
|
||
GBytes *sig_file,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GVariant) signatures_variant = NULL;
|
||
g_autoptr(GVariant) signaturedata = NULL;
|
||
g_autoptr (GBytes) signatures = NULL;
|
||
g_autoptr(GByteArray) buffer = NULL;
|
||
g_autoptr(OstreeGpgVerifyResult) verify_result = NULL;
|
||
GVariantIter iter;
|
||
GVariant *child;
|
||
|
||
signatures_variant = g_variant_new_from_bytes (OSTREE_SUMMARY_SIG_GVARIANT_FORMAT,
|
||
sig_file, FALSE);
|
||
signaturedata = g_variant_lookup_value (signatures_variant, "ostree.gpgsigs", G_VARIANT_TYPE ("aay"));
|
||
if (signaturedata == NULL)
|
||
{
|
||
g_set_error_literal (error, OSTREE_GPG_ERROR, OSTREE_GPG_ERROR_NO_SIGNATURE,
|
||
"GPG verification enabled, but no signatures found (use gpg-verify=false in remote config to disable)");
|
||
return FALSE;
|
||
}
|
||
|
||
buffer = g_byte_array_new ();
|
||
g_variant_iter_init (&iter, signaturedata);
|
||
while ((child = g_variant_iter_next_value (&iter)) != NULL)
|
||
{
|
||
g_byte_array_append (buffer,
|
||
g_variant_get_data (child),
|
||
g_variant_get_size (child));
|
||
g_variant_unref (child);
|
||
}
|
||
signatures = g_byte_array_free_to_bytes (g_steal_pointer (&buffer));
|
||
|
||
verify_result = ostree_repo_gpg_verify_data (repo,
|
||
remote_name,
|
||
data,
|
||
signatures,
|
||
NULL, NULL,
|
||
cancellable, error);
|
||
if (!ostree_gpg_verify_result_require_valid_signature (verify_result, error))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static GBytes *
|
||
load_uri_with_fallback (FlatpakHttpSession *http_session,
|
||
const char *uri,
|
||
const char *uri2,
|
||
FlatpakHTTPFlags flags,
|
||
const char *token,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GError) local_error = NULL;
|
||
GBytes *res;
|
||
|
||
res = flatpak_load_uri (http_session, uri, flags, token,
|
||
NULL, NULL, NULL,
|
||
cancellable, &local_error);
|
||
if (res)
|
||
return res;
|
||
|
||
if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return NULL;
|
||
}
|
||
|
||
return flatpak_load_uri (http_session, uri2, flags, token,
|
||
NULL, NULL, NULL,
|
||
cancellable, error);
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_remote_fetch_summary_index (FlatpakDir *self,
|
||
const char *name_or_uri,
|
||
gboolean only_cached,
|
||
GBytes **out_index,
|
||
GBytes **out_index_sig,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autofree char *url = NULL;
|
||
gboolean is_local;
|
||
g_autoptr(GError) local_error = NULL;
|
||
g_autoptr(GError) cache_error = NULL;
|
||
g_autoptr(GBytes) cached_index = NULL;
|
||
g_autoptr(GBytes) cached_index_sig = NULL;
|
||
g_autoptr(GBytes) index = NULL;
|
||
g_autoptr(GBytes) index_sig = NULL;
|
||
gboolean gpg_verify_summary;
|
||
|
||
ensure_http_session (self);
|
||
|
||
if (!ostree_repo_remote_get_url (self->repo, name_or_uri, &url, error))
|
||
return FALSE;
|
||
|
||
if (!ostree_repo_remote_get_gpg_verify_summary (self->repo, name_or_uri, &gpg_verify_summary, NULL))
|
||
return FALSE;
|
||
|
||
if (!g_str_has_prefix (name_or_uri, "file:") && flatpak_dir_get_remote_disabled (self, name_or_uri))
|
||
{
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
|
||
"Can't fetch summary from disabled remote ‘%s’", name_or_uri);
|
||
return FALSE;
|
||
}
|
||
|
||
if (flatpak_dir_get_remote_oci (self, name_or_uri))
|
||
{
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
||
"No index in OCI remote ‘%s’", name_or_uri);
|
||
return FALSE;
|
||
}
|
||
|
||
is_local = g_str_has_prefix (url, "file:");
|
||
|
||
/* Seems ostree asserts if this is NULL */
|
||
if (error == NULL)
|
||
error = &local_error;
|
||
|
||
flatpak_dir_remote_load_cached_summary (self, name_or_uri, NULL, ".idx", ".idx.sig",
|
||
&cached_index, &cached_index_sig, cancellable, &cache_error);
|
||
|
||
if (only_cached)
|
||
{
|
||
if (cached_index == NULL)
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&cache_error));
|
||
return FALSE;
|
||
}
|
||
g_info ("Loaded summary index from cache for remote ‘%s’", name_or_uri);
|
||
|
||
index = g_steal_pointer (&cached_index);
|
||
if (gpg_verify_summary)
|
||
index_sig = g_steal_pointer (&cached_index_sig);
|
||
}
|
||
else
|
||
{
|
||
g_autofree char *index_url = g_build_filename (url, "summary.idx", NULL);
|
||
g_autoptr(GBytes) dl_index = NULL;
|
||
gboolean used_download = FALSE;
|
||
|
||
g_info ("Fetching summary index file for remote ‘%s’", name_or_uri);
|
||
|
||
dl_index = flatpak_load_uri (self->http_session, index_url, 0, NULL,
|
||
NULL, NULL, NULL,
|
||
cancellable, error);
|
||
if (dl_index == NULL)
|
||
return FALSE;
|
||
|
||
/* If the downloaded index is the same as the cached one we need not re-download or
|
||
* re-verify, just use the cache (which we verified before) */
|
||
if (cached_index != NULL && g_bytes_equal (cached_index, dl_index))
|
||
{
|
||
index = g_steal_pointer (&cached_index);
|
||
if (gpg_verify_summary)
|
||
index_sig = g_steal_pointer (&cached_index_sig);
|
||
}
|
||
else
|
||
{
|
||
index = g_steal_pointer (&dl_index);
|
||
used_download = TRUE;
|
||
}
|
||
|
||
if (gpg_verify_summary && index_sig == NULL)
|
||
{
|
||
g_autofree char *index_digest = g_compute_checksum_for_bytes (G_CHECKSUM_SHA256, index);
|
||
g_autofree char *index_sig_filename = g_strconcat (index_digest, ".idx.sig", NULL);
|
||
g_autofree char *index_sig_url = g_build_filename (url, "summaries", index_sig_filename, NULL);
|
||
g_autofree char *index_sig_url2 = g_build_filename (url, "summary.idx.sig", NULL);
|
||
g_autoptr(GError) dl_sig_error = NULL;
|
||
g_autoptr (GBytes) dl_index_sig = NULL;
|
||
|
||
dl_index_sig = load_uri_with_fallback (self->http_session, index_sig_url, index_sig_url2, 0, NULL,
|
||
cancellable, &dl_sig_error);
|
||
if (dl_index_sig == NULL)
|
||
{
|
||
if (g_error_matches (dl_sig_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||
g_set_error (error, OSTREE_GPG_ERROR, OSTREE_GPG_ERROR_NO_SIGNATURE,
|
||
"GPG verification enabled, but no summary signatures found (use gpg-verify-summary=false in remote config to disable)");
|
||
else
|
||
g_propagate_error (error, g_steal_pointer (&dl_sig_error));
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
if (!remote_verify_signature (self->repo, name_or_uri,
|
||
index, dl_index_sig,
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
index_sig = g_steal_pointer (&dl_index_sig);
|
||
used_download = TRUE;
|
||
}
|
||
|
||
g_assert (index != NULL);
|
||
if (gpg_verify_summary)
|
||
g_assert (index_sig != NULL);
|
||
|
||
/* Update cache on disk if we downloaded anything, but never cache for file: repos */
|
||
if (used_download && !is_local &&
|
||
!flatpak_dir_remote_save_cached_summary (self, name_or_uri, ".idx", ".idx.sig",
|
||
index, index_sig, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
/* Cache in memory */
|
||
if (!is_local && !only_cached)
|
||
{
|
||
g_autofree char *cache_key = g_strconcat ("index-", name_or_uri, NULL);
|
||
flatpak_dir_cache_summary (self, index, index_sig, cache_key, url);
|
||
}
|
||
|
||
*out_index = g_steal_pointer (&index);
|
||
if (out_index_sig)
|
||
*out_index_sig = g_steal_pointer (&index_sig);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_remote_fetch_indexed_summary (FlatpakDir *self,
|
||
const char *name_or_uri,
|
||
const char *arch,
|
||
GVariant *subsummary_info_v,
|
||
gboolean only_cached,
|
||
GBytes **out_summary,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autofree char *url = NULL;
|
||
gboolean is_local;
|
||
g_autoptr(GError) cache_error = NULL;
|
||
g_autoptr(GBytes) summary_z = NULL;
|
||
g_autoptr(GBytes) summary = NULL;
|
||
g_autofree char *sha256 = NULL;
|
||
VarSubsummaryRef subsummary_info;
|
||
gsize checksum_bytes_len;
|
||
const guchar *checksum_bytes;
|
||
g_autofree char *checksum = NULL;
|
||
g_autofree char *cache_name = NULL;
|
||
|
||
ensure_http_session (self);
|
||
|
||
if (!ostree_repo_remote_get_url (self->repo, name_or_uri, &url, error))
|
||
return FALSE;
|
||
|
||
if (!g_str_has_prefix (name_or_uri, "file:") && flatpak_dir_get_remote_disabled (self, name_or_uri))
|
||
{
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
|
||
"Can't fetch summary from disabled remote ‘%s’", name_or_uri);
|
||
return FALSE;
|
||
}
|
||
|
||
subsummary_info = var_subsummary_from_gvariant (subsummary_info_v);
|
||
checksum_bytes = var_subsummary_peek_checksum (subsummary_info, &checksum_bytes_len);
|
||
g_assert (checksum_bytes_len == OSTREE_SHA256_DIGEST_LEN); /* We verified this when scanning index */
|
||
checksum = ostree_checksum_from_bytes (checksum_bytes);
|
||
|
||
is_local = g_str_has_prefix (url, "file:");
|
||
|
||
/* No in-memory caching for local files */
|
||
if (!is_local)
|
||
{
|
||
if (flatpak_dir_lookup_cached_summary (self, out_summary, NULL, checksum, url))
|
||
return TRUE;
|
||
}
|
||
|
||
cache_name = g_strconcat (name_or_uri, "-", arch, "-", checksum, NULL);
|
||
|
||
/* First look for an on-disk cache */
|
||
if (!flatpak_dir_remote_load_cached_summary (self, cache_name, checksum, ".sub", NULL,
|
||
&summary, NULL, cancellable, &cache_error))
|
||
{
|
||
g_autofree char *old_checksum = NULL;
|
||
g_autoptr(GBytes) old_summary = NULL;
|
||
|
||
/* Else fetch it */
|
||
if (only_cached)
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&cache_error));
|
||
return FALSE;
|
||
}
|
||
|
||
/* Warn if the on-disk cache is corrupt; perhaps the write was interrupted? */
|
||
if (g_error_matches (cache_error, FLATPAK_ERROR, FLATPAK_ERROR_INVALID_DATA))
|
||
g_warning ("%s", cache_error->message);
|
||
|
||
/* Look for first applicable deltas */
|
||
VarArrayofChecksumRef history = var_subsummary_get_history (subsummary_info);
|
||
gsize history_len = var_arrayof_checksum_get_length (history);
|
||
for (gsize i = 0; i < history_len; i++)
|
||
{
|
||
VarChecksumRef old = var_arrayof_checksum_get_at (history, i);
|
||
g_autofree char *old_cache_name = NULL;
|
||
|
||
if (var_checksum_get_length (old) != OSTREE_SHA256_DIGEST_LEN)
|
||
continue;
|
||
|
||
old_checksum = ostree_checksum_from_bytes (var_checksum_peek (old));
|
||
old_cache_name = g_strconcat (name_or_uri, "-", arch, "-", old_checksum, NULL);
|
||
if (flatpak_dir_remote_load_cached_summary (self, old_cache_name, old_checksum, ".sub", NULL,
|
||
&old_summary, NULL, cancellable, NULL))
|
||
break;
|
||
|
||
g_clear_pointer (&old_checksum, g_free);
|
||
}
|
||
|
||
if (old_summary)
|
||
{
|
||
g_autoptr(GError) delta_error = NULL;
|
||
|
||
g_autofree char *delta_filename = g_strconcat (old_checksum, "-", checksum, ".delta", NULL);
|
||
g_autofree char *delta_url = g_build_filename (url, "summaries", delta_filename, NULL);
|
||
|
||
g_info ("Fetching indexed summary delta %s for remote ‘%s’", delta_filename, name_or_uri);
|
||
|
||
g_autoptr(GBytes) delta = flatpak_load_uri (self->http_session, delta_url, 0, NULL,
|
||
NULL, NULL, NULL,
|
||
cancellable, &delta_error);
|
||
if (delta == NULL)
|
||
g_info ("Failed to load delta, falling back: %s", delta_error->message);
|
||
else
|
||
{
|
||
g_autoptr(GBytes) applied = flatpak_summary_apply_diff (old_summary, delta, &delta_error);
|
||
|
||
if (applied == NULL)
|
||
g_warning ("Failed to apply delta, falling back: %s", delta_error->message);
|
||
else
|
||
{
|
||
sha256 = g_compute_checksum_for_bytes (G_CHECKSUM_SHA256, applied);
|
||
if (strcmp (sha256, checksum) != 0)
|
||
g_warning ("Applying delta gave wrong checksum, falling back");
|
||
else
|
||
summary = g_steal_pointer (&applied);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (summary == NULL)
|
||
{
|
||
g_autofree char *filename = g_strconcat (checksum, ".gz", NULL);
|
||
g_info ("Fetching indexed summary file %s for remote ‘%s’", filename, name_or_uri);
|
||
g_autofree char *subsummary_url = g_build_filename (url, "summaries", filename, NULL);
|
||
summary_z = flatpak_load_uri (self->http_session, subsummary_url, 0, NULL,
|
||
NULL, NULL, NULL,
|
||
cancellable, error);
|
||
if (summary_z == NULL)
|
||
return FALSE;
|
||
|
||
summary = flatpak_zlib_decompress_bytes (summary_z, error);
|
||
if (summary == NULL)
|
||
return FALSE;
|
||
|
||
g_free (sha256);
|
||
sha256 = g_compute_checksum_for_bytes (G_CHECKSUM_SHA256, summary);
|
||
if (strcmp (sha256, checksum) != 0)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid checksum for indexed summary %s for remote '%s'"), checksum, name_or_uri);
|
||
}
|
||
|
||
/* Save to disk */
|
||
if (!is_local)
|
||
{
|
||
if (!flatpak_dir_remote_save_cached_summary (self, cache_name, ".sub", NULL,
|
||
summary, NULL,
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
if (!flatpak_dir_gc_cached_digested_summaries (self, name_or_uri, cache_name,
|
||
cancellable, error))
|
||
return FALSE;
|
||
}
|
||
}
|
||
else
|
||
g_info ("Loaded indexed summary file %s from cache for remote ‘%s’", checksum, name_or_uri);
|
||
|
||
/* Cache in memory */
|
||
if (!is_local && !only_cached)
|
||
flatpak_dir_cache_summary (self, summary, NULL, checksum, url);
|
||
|
||
*out_summary = g_steal_pointer (&summary);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static FlatpakRemoteState *
|
||
_flatpak_dir_get_remote_state (FlatpakDir *self,
|
||
const char *remote_or_uri,
|
||
gboolean optional,
|
||
gboolean local_only,
|
||
gboolean only_cached,
|
||
gboolean opt_summary_is_index,
|
||
GBytes *opt_summary,
|
||
GBytes *opt_summary_sig,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(FlatpakRemoteState) state = flatpak_remote_state_new ();
|
||
g_autoptr(GPtrArray) sideload_paths = NULL;
|
||
g_autofree char *url = NULL;
|
||
g_autoptr(GError) my_error = NULL;
|
||
gboolean is_local;
|
||
gboolean got_summary = FALSE;
|
||
const char *arch = flatpak_get_default_arch ();
|
||
g_autoptr(GBytes) index_bytes = NULL;
|
||
g_autoptr(GBytes) index_sig_bytes = NULL;
|
||
g_autoptr(GBytes) summary_bytes = NULL;
|
||
g_autoptr(GBytes) summary_sig_bytes = NULL;
|
||
|
||
if (error == NULL)
|
||
error = &my_error;
|
||
|
||
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
||
return NULL;
|
||
|
||
state->remote_name = g_strdup (remote_or_uri);
|
||
state->is_file_uri = is_local = g_str_has_prefix (remote_or_uri, "file:");
|
||
if (!is_local)
|
||
{
|
||
if (!flatpak_dir_has_remote (self, remote_or_uri, error))
|
||
return NULL;
|
||
if (!repo_get_remote_collection_id (self->repo, remote_or_uri, &state->collection_id, error))
|
||
return NULL;
|
||
if (!flatpak_dir_lookup_remote_filter (self, remote_or_uri, FALSE, NULL, &state->allow_refs, &state->deny_refs, error))
|
||
return NULL;
|
||
if (!ostree_repo_remote_get_url (self->repo, remote_or_uri, &url, error))
|
||
return NULL;
|
||
|
||
state->default_token_type = flatpak_dir_get_remote_default_token_type (self, remote_or_uri);
|
||
}
|
||
|
||
sideload_paths = flatpak_dir_get_sideload_repo_paths (self);
|
||
for (int i = 0; i < sideload_paths->len; i++)
|
||
flatpak_remote_state_add_sideload_repo (state, g_ptr_array_index (sideload_paths, i));
|
||
|
||
if (local_only)
|
||
{
|
||
flatpak_fail (&state->summary_fetch_error, "Internal error, local_only state");
|
||
return g_steal_pointer (&state);
|
||
}
|
||
|
||
if (opt_summary)
|
||
{
|
||
if (opt_summary_sig)
|
||
{
|
||
/* If specified, must be valid signature */
|
||
g_autoptr(OstreeGpgVerifyResult) gpg_result =
|
||
ostree_repo_verify_summary (self->repo,
|
||
state->remote_name,
|
||
opt_summary,
|
||
opt_summary_sig,
|
||
NULL, error);
|
||
if (gpg_result == NULL ||
|
||
!ostree_gpg_verify_result_require_valid_signature (gpg_result, error))
|
||
return NULL;
|
||
}
|
||
|
||
if (opt_summary_is_index)
|
||
{
|
||
if (opt_summary_sig)
|
||
index_sig_bytes = g_bytes_ref (opt_summary_sig);
|
||
index_bytes = g_bytes_ref (opt_summary);
|
||
}
|
||
else
|
||
{
|
||
if (opt_summary_sig)
|
||
summary_sig_bytes = g_bytes_ref (opt_summary_sig);
|
||
summary_bytes = g_bytes_ref (opt_summary);
|
||
}
|
||
|
||
got_summary = TRUE;
|
||
}
|
||
|
||
/* First try the memory cache. Note: No in-memory caching for local files. */
|
||
if (!is_local)
|
||
{
|
||
if (!got_summary)
|
||
{
|
||
g_autofree char *index_cache_key = g_strconcat ("index-", remote_or_uri, NULL);
|
||
if (flatpak_dir_lookup_cached_summary (self, &index_bytes, &index_sig_bytes, index_cache_key, url))
|
||
got_summary = TRUE;
|
||
}
|
||
|
||
if (!got_summary)
|
||
{
|
||
g_autofree char *summary_cache_key = g_strconcat ("summary-", remote_or_uri, NULL);
|
||
if (flatpak_dir_lookup_cached_summary (self, &summary_bytes, &summary_sig_bytes, summary_cache_key, url))
|
||
got_summary = TRUE;
|
||
}
|
||
}
|
||
|
||
/* Then look for an indexed summary on disk/network */
|
||
if (!got_summary)
|
||
{
|
||
g_autoptr(GError) local_error = NULL;
|
||
|
||
if (flatpak_dir_remote_fetch_summary_index (self, remote_or_uri, only_cached, &index_bytes, &index_sig_bytes,
|
||
cancellable, &local_error))
|
||
{
|
||
got_summary = TRUE;
|
||
}
|
||
else
|
||
{
|
||
if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) &&
|
||
!g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_CACHED))
|
||
{
|
||
/* We got an error other than not-found, assume we're indexed but there is some network error */
|
||
got_summary = TRUE;
|
||
if (optional && !g_cancellable_is_cancelled (cancellable))
|
||
{
|
||
g_info ("Failed to download optional summary index: %s", local_error->message);
|
||
state->summary_fetch_error = g_steal_pointer (&local_error);
|
||
}
|
||
else
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return NULL;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!got_summary)
|
||
{
|
||
/* No index, fall back to full summary */
|
||
g_autoptr(GError) local_error = NULL;
|
||
|
||
if (flatpak_dir_remote_fetch_summary (self, remote_or_uri, only_cached, &summary_bytes, &summary_sig_bytes,
|
||
cancellable, &local_error))
|
||
{
|
||
got_summary = TRUE;
|
||
}
|
||
else
|
||
{
|
||
if (optional && !g_cancellable_is_cancelled (cancellable))
|
||
{
|
||
g_info ("Failed to download optional summary: %s", local_error->message);
|
||
state->summary_fetch_error = g_steal_pointer (&local_error);
|
||
}
|
||
else
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return NULL;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (index_bytes)
|
||
{
|
||
state->index = g_variant_ref_sink (g_variant_new_from_bytes (FLATPAK_SUMMARY_INDEX_GVARIANT_FORMAT,
|
||
index_bytes, FALSE));
|
||
state->index_sig_bytes = g_steal_pointer (&index_sig_bytes);
|
||
}
|
||
else if (summary_bytes)
|
||
{
|
||
state->summary = g_variant_ref_sink (g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT,
|
||
summary_bytes, FALSE));
|
||
state->summary_bytes = g_steal_pointer (&summary_bytes);
|
||
state->summary_sig_bytes = g_steal_pointer (&summary_sig_bytes);
|
||
}
|
||
|
||
if (state->index)
|
||
{
|
||
g_autofree char *require_subset = flatpak_dir_get_remote_subset (self, state->remote_name);
|
||
VarSummaryIndexRef index = var_summary_index_from_gvariant (state->index);
|
||
VarSummaryIndexSubsummariesRef subsummaries = var_summary_index_get_subsummaries (index);
|
||
gsize n_subsummaries = var_summary_index_subsummaries_get_length (subsummaries);
|
||
|
||
state->index_ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_variant_unref);
|
||
|
||
for (gsize i = 0; i < n_subsummaries; i++)
|
||
{
|
||
VarSummaryIndexSubsummariesEntryRef entry = var_summary_index_subsummaries_get_at (subsummaries, i);
|
||
const char *name = var_summary_index_subsummaries_entry_get_key (entry);
|
||
VarSubsummaryRef subsummary = var_summary_index_subsummaries_entry_get_value (entry);
|
||
gsize checksum_bytes_len;
|
||
const char *dash, *subsummary_arch;
|
||
|
||
dash = strchr (name, '-');
|
||
subsummary_arch = dash == NULL ? name : dash + 1;
|
||
|
||
if (dash == NULL) /* No subset */
|
||
{
|
||
if (require_subset != NULL)
|
||
continue;
|
||
}
|
||
else /* Subset */
|
||
{
|
||
if (require_subset == NULL)
|
||
continue;
|
||
else
|
||
{
|
||
g_autofree char *subset = g_strndup (name, dash - name);
|
||
if (strcmp (require_subset, subset) != 0)
|
||
continue;
|
||
}
|
||
}
|
||
|
||
var_subsummary_peek_checksum (subsummary, &checksum_bytes_len);
|
||
if (G_UNLIKELY (checksum_bytes_len != OSTREE_SHA256_DIGEST_LEN))
|
||
{
|
||
g_info ("Invalid checksum for digested summary, not using cache");
|
||
continue;
|
||
}
|
||
|
||
g_hash_table_insert (state->index_ht, g_strdup (subsummary_arch), var_subsummary_to_owned_gvariant (subsummary, state->index));
|
||
}
|
||
|
||
/* Always load default (or specified) arch subsummary. Further arches can be manually loaded with flatpak_remote_state_ensure_subsummary. */
|
||
if (opt_summary == NULL &&
|
||
!flatpak_remote_state_ensure_subsummary (state, self, arch, only_cached, cancellable, error))
|
||
return NULL;
|
||
}
|
||
|
||
if (state->collection_id != NULL &&
|
||
state->summary != NULL &&
|
||
!_validate_summary_for_collection_id (state->summary, state->collection_id, error))
|
||
return NULL;
|
||
|
||
if (flatpak_dir_get_remote_oci (self, remote_or_uri))
|
||
{
|
||
state->default_token_type = 1;
|
||
}
|
||
|
||
if (state->summary != NULL || state->index != NULL) /* In the optional case we might not have a summary */
|
||
{
|
||
VarMetadataRef meta = flatpak_remote_state_get_main_metadata (state);
|
||
VarVariantRef res;
|
||
|
||
if (var_metadata_lookup (meta, "xa.default-token-type", NULL, &res) &&
|
||
var_variant_is_type (res, G_VARIANT_TYPE_INT32))
|
||
state->default_token_type = GINT32_FROM_LE (var_variant_get_int32 (res));
|
||
}
|
||
|
||
return g_steal_pointer (&state);
|
||
}
|
||
|
||
FlatpakRemoteState *
|
||
flatpak_dir_get_remote_state (FlatpakDir *self,
|
||
const char *remote,
|
||
gboolean only_cached,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
return _flatpak_dir_get_remote_state (self, remote, FALSE, FALSE, only_cached, FALSE, NULL, NULL, cancellable, error);
|
||
}
|
||
|
||
/* This is an alternative way to get the state where the summary is
|
||
* from elsewhere. It is mainly used by the system-helper where the
|
||
* summary is from the user-mode part which downloaded an update
|
||
*
|
||
* It will verify the summary if a signature is passed in, but not
|
||
* otherwise.
|
||
**/
|
||
FlatpakRemoteState *
|
||
flatpak_dir_get_remote_state_for_summary (FlatpakDir *self,
|
||
const char *remote,
|
||
GBytes *opt_summary,
|
||
GBytes *opt_summary_sig,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
return _flatpak_dir_get_remote_state (self, remote, FALSE, FALSE, FALSE, FALSE, opt_summary, opt_summary_sig, cancellable, error);
|
||
}
|
||
|
||
FlatpakRemoteState *
|
||
flatpak_dir_get_remote_state_for_index (FlatpakDir *self,
|
||
const char *remote,
|
||
GBytes *opt_index,
|
||
GBytes *opt_index_sig,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
return _flatpak_dir_get_remote_state (self, remote, FALSE, FALSE, FALSE, TRUE, opt_index, opt_index_sig, cancellable, error);
|
||
}
|
||
|
||
/* This is an alternative way to get the remote state that doesn't
|
||
* error out if the summary or metadata is not available.
|
||
* For example, we want to be able to update an app even when
|
||
* we can't talk to the main repo, but there is a local (p2p/sdcard)
|
||
* source for apps, and we want to be able to deploy a ref without pulling it,
|
||
* e.g. because we are installing with FLATPAK_INSTALL_FLAGS_NO_PULL, and we
|
||
* already pulled it out of band beforehand.
|
||
*/
|
||
FlatpakRemoteState *
|
||
flatpak_dir_get_remote_state_optional (FlatpakDir *self,
|
||
const char *remote,
|
||
gboolean only_cached,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
return _flatpak_dir_get_remote_state (self, remote, TRUE, FALSE, only_cached, FALSE, NULL, NULL, cancellable, error);
|
||
}
|
||
|
||
|
||
/* This doesn't do any i/o at all, just keeps track of the local details like
|
||
remote and collection-id. Useful when doing no-pull operations */
|
||
FlatpakRemoteState *
|
||
flatpak_dir_get_remote_state_local_only (FlatpakDir *self,
|
||
const char *remote,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
return _flatpak_dir_get_remote_state (self, remote, TRUE, TRUE, FALSE, FALSE, NULL, NULL, cancellable, error);
|
||
}
|
||
|
||
static void
|
||
populate_hash_table_from_refs_map (GHashTable *ret_all_refs,
|
||
GHashTable *ref_timestamps,
|
||
VarRefMapRef ref_map,
|
||
const char *opt_collection_id,
|
||
FlatpakRemoteState *state)
|
||
{
|
||
gsize len, i;
|
||
|
||
len = var_ref_map_get_length (ref_map);
|
||
for (i = 0; i < len; i++)
|
||
{
|
||
VarRefMapEntryRef entry = var_ref_map_get_at (ref_map, i);
|
||
const char *ref_name = var_ref_map_entry_get_ref (entry);
|
||
const guint8 *csum_bytes;
|
||
gsize csum_len;
|
||
VarRefInfoRef info;
|
||
guint64 *new_timestamp = NULL;
|
||
g_autoptr(FlatpakDecomposed) decomposed = NULL;
|
||
|
||
if (!flatpak_remote_state_allow_ref (state, ref_name))
|
||
continue;
|
||
|
||
info = var_ref_map_entry_get_info (entry);
|
||
|
||
csum_bytes = var_ref_info_peek_checksum (info, &csum_len);
|
||
if (csum_len != OSTREE_SHA256_DIGEST_LEN)
|
||
continue;
|
||
|
||
decomposed = flatpak_decomposed_new_from_col_ref (ref_name, opt_collection_id, NULL);
|
||
if (decomposed == NULL)
|
||
continue;
|
||
|
||
if (ref_timestamps)
|
||
{
|
||
guint64 timestamp = get_timestamp_from_ref_info (info);
|
||
gpointer value;
|
||
|
||
if (g_hash_table_lookup_extended (ref_timestamps, ref_name, NULL, &value))
|
||
{
|
||
guint64 *old_timestamp = value;
|
||
if (*old_timestamp >= timestamp)
|
||
continue; /* New timestamp is older, skip this commit */
|
||
}
|
||
|
||
new_timestamp = g_memdup2 (×tamp, sizeof (guint64));
|
||
}
|
||
|
||
g_hash_table_replace (ret_all_refs, g_steal_pointer (&decomposed), ostree_checksum_from_bytes (csum_bytes));
|
||
if (new_timestamp)
|
||
g_hash_table_replace (ref_timestamps, g_strdup (ref_name), new_timestamp);
|
||
}
|
||
}
|
||
|
||
|
||
/* This tries to list all available remote refs but also tries to keep
|
||
* working when offline, so it looks in sideloaded repos. Also it uses
|
||
* in-memory cached summaries which ostree doesn't. */
|
||
gboolean
|
||
flatpak_dir_list_all_remote_refs (FlatpakDir *self,
|
||
FlatpakRemoteState *state,
|
||
GHashTable **out_all_refs,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GHashTable) ret_all_refs = NULL;
|
||
VarSummaryRef summary;
|
||
VarMetadataRef exts;
|
||
VarRefMapRef ref_map;
|
||
VarVariantRef v;
|
||
|
||
/* This is ref->commit */
|
||
ret_all_refs = g_hash_table_new_full ((GHashFunc)flatpak_decomposed_hash, (GEqualFunc)flatpak_decomposed_equal, (GDestroyNotify)flatpak_decomposed_unref, g_free);
|
||
|
||
if (state->index != NULL)
|
||
{
|
||
/* We're online, so report only the refs from the summary */
|
||
GLNX_HASH_TABLE_FOREACH_KV (state->subsummaries, const char *, arch, GVariant *, subsummary)
|
||
{
|
||
summary = var_summary_from_gvariant (subsummary);
|
||
ref_map = var_summary_get_ref_map (summary);
|
||
|
||
/* NOTE: collection id is NULL here not state->collection_id, see the
|
||
* note on flatpak_decomposed_get_collection_id()
|
||
*/
|
||
populate_hash_table_from_refs_map (ret_all_refs, NULL, ref_map, NULL /* collection id */, state);
|
||
}
|
||
}
|
||
else if (state->summary != NULL)
|
||
{
|
||
/* We're online, so report only the refs from the summary */
|
||
const char *main_collection_id = NULL;
|
||
|
||
summary = var_summary_from_gvariant (state->summary);
|
||
|
||
exts = var_summary_get_metadata (summary);
|
||
|
||
if (state->is_file_uri)
|
||
{
|
||
/* This is a local repo, generally this means we gave a file: uri to a sideload repo so
|
||
* we can enumerate it. We special case this by also adding all the collection_ref maps,
|
||
* with collection_id set on the decomposed refs and setting the right collection id for
|
||
* the main ref_map.
|
||
*/
|
||
main_collection_id = var_metadata_lookup_string (exts, "ostree.summary.collection-id", NULL);
|
||
if (var_metadata_lookup (exts, "ostree.summary.collection-map", NULL, &v))
|
||
{
|
||
VarCollectionMapRef map = var_collection_map_from_variant (v);
|
||
|
||
gsize len = var_collection_map_get_length (map);
|
||
for (gsize i = 0; i < len; i++)
|
||
{
|
||
VarCollectionMapEntryRef entry = var_collection_map_get_at (map, i);
|
||
const char *collection_id = var_collection_map_entry_get_key (entry);
|
||
ref_map = var_collection_map_entry_get_value (entry);
|
||
|
||
populate_hash_table_from_refs_map (ret_all_refs, NULL, ref_map, collection_id, state);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* refs that match the main collection-id,
|
||
NOTE: We only set collection id if this is a file: uri remote */
|
||
ref_map = var_summary_get_ref_map (summary);
|
||
populate_hash_table_from_refs_map (ret_all_refs, NULL, ref_map, main_collection_id, state);
|
||
}
|
||
else if (state->collection_id)
|
||
{
|
||
g_autoptr(GHashTable) ref_mtimes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
||
|
||
/* No main summary, add just all sideloded refs, with the latest version of each checksum */
|
||
|
||
for (int i = 0; i < state->sideload_repos->len; i++)
|
||
{
|
||
FlatpakSideloadState *ss = g_ptr_array_index (state->sideload_repos, i);
|
||
|
||
summary = var_summary_from_gvariant (ss->summary);
|
||
exts = var_summary_get_metadata (summary);
|
||
|
||
if (var_metadata_lookup (exts, "ostree.summary.collection-map", NULL, &v))
|
||
{
|
||
VarCollectionMapRef map = var_collection_map_from_variant (v);
|
||
|
||
if (var_collection_map_lookup (map, state->collection_id, NULL, &ref_map))
|
||
populate_hash_table_from_refs_map (ret_all_refs, ref_mtimes, ref_map, NULL, state);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* If no sideloaded refs, might as well return the summary error if set */
|
||
if (g_hash_table_size (ret_all_refs) == 0 &&
|
||
!flatpak_remote_state_ensure_summary (state, error))
|
||
return FALSE;
|
||
|
||
*out_all_refs = g_steal_pointer (&ret_all_refs);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static GPtrArray *
|
||
find_matching_refs (GHashTable *refs,
|
||
const char *opt_name,
|
||
const char *opt_branch,
|
||
const char *opt_default_branch,
|
||
const char **valid_arches, /* NULL => any arch */
|
||
const char *opt_default_arch,
|
||
FlatpakKinds kinds,
|
||
FindMatchingRefsFlags flags,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GPtrArray) matched_refs = NULL;
|
||
g_autoptr(GError) local_error = NULL;
|
||
gboolean found_exact_name_match = FALSE;
|
||
gboolean found_default_branch_match = FALSE;
|
||
gboolean found_default_arch_match = FALSE;
|
||
|
||
if (opt_name && !(flags & FIND_MATCHING_REFS_FLAGS_FUZZY) &&
|
||
!flatpak_is_valid_name (opt_name, -1, &local_error))
|
||
{
|
||
flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("'%s' is not a valid name: %s"), opt_name, local_error->message);
|
||
return NULL;
|
||
}
|
||
|
||
if (opt_branch && !flatpak_is_valid_branch (opt_branch, -1, &local_error))
|
||
{
|
||
flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("'%s' is not a valid branch name: %s"), opt_branch, local_error->message);
|
||
return NULL;
|
||
}
|
||
|
||
matched_refs = g_ptr_array_new_with_free_func ((GDestroyNotify)flatpak_decomposed_unref);
|
||
|
||
GLNX_HASH_TABLE_FOREACH (refs, FlatpakDecomposed *, ref)
|
||
{
|
||
if ((flatpak_decomposed_get_kinds (ref) & kinds) == 0)
|
||
continue;
|
||
|
||
if (opt_name)
|
||
{
|
||
if ((flags & FIND_MATCHING_REFS_FLAGS_FUZZY) && !flatpak_decomposed_id_is_subref (ref))
|
||
{
|
||
if (!flatpak_decomposed_is_id_fuzzy (ref, opt_name))
|
||
continue;
|
||
}
|
||
else
|
||
{
|
||
if (!flatpak_decomposed_is_id (ref, opt_name))
|
||
continue;
|
||
}
|
||
}
|
||
|
||
if (valid_arches != NULL &&
|
||
!flatpak_decomposed_is_arches (ref, -1, valid_arches))
|
||
continue;
|
||
|
||
if (opt_branch != NULL && !flatpak_decomposed_is_branch (ref, opt_branch))
|
||
continue;
|
||
|
||
if (opt_name != NULL && flatpak_decomposed_is_id (ref, opt_name))
|
||
found_exact_name_match = TRUE;
|
||
|
||
if (opt_default_arch != NULL && flatpak_decomposed_is_arch (ref, opt_default_arch))
|
||
found_default_arch_match = TRUE;
|
||
|
||
if (opt_default_branch != NULL && flatpak_decomposed_is_branch (ref, opt_default_branch))
|
||
found_default_branch_match = TRUE;
|
||
|
||
g_ptr_array_add (matched_refs, flatpak_decomposed_ref (ref));
|
||
}
|
||
|
||
/* Don't show fuzzy matches if we found at least one exact name match, and
|
||
* enforce the default arch/branch */
|
||
if (found_exact_name_match || found_default_arch_match || found_default_branch_match)
|
||
{
|
||
guint i;
|
||
|
||
/* Walk through the array backwards so we can safely remove */
|
||
for (i = matched_refs->len; i > 0; i--)
|
||
{
|
||
FlatpakDecomposed *matched_ref = g_ptr_array_index (matched_refs, i - 1);
|
||
|
||
if (found_exact_name_match && !flatpak_decomposed_is_id (matched_ref, opt_name))
|
||
g_ptr_array_remove_index (matched_refs, i - 1);
|
||
else if (found_default_arch_match && !flatpak_decomposed_is_arch (matched_ref, opt_default_arch))
|
||
g_ptr_array_remove_index (matched_refs, i - 1);
|
||
else if (found_default_branch_match && !flatpak_decomposed_is_branch (matched_ref, opt_default_branch))
|
||
g_ptr_array_remove_index (matched_refs, i - 1);
|
||
}
|
||
}
|
||
|
||
return g_steal_pointer (&matched_refs);
|
||
}
|
||
|
||
static GPtrArray *
|
||
get_refs_for_arch (GPtrArray *refs, const char *arch)
|
||
{
|
||
g_autoptr(GPtrArray) arched_refs = g_ptr_array_new_with_free_func ((GDestroyNotify)flatpak_decomposed_unref);
|
||
|
||
for (int i = 0; i < refs->len; i++)
|
||
{
|
||
FlatpakDecomposed *ref = g_ptr_array_index (refs, i);
|
||
if (flatpak_decomposed_is_arch (ref, arch))
|
||
g_ptr_array_add (arched_refs, flatpak_decomposed_ref (ref));
|
||
}
|
||
|
||
return g_steal_pointer (&arched_refs);
|
||
}
|
||
|
||
static gpointer
|
||
fail_multiple_refs (GError **error,
|
||
const char *name,
|
||
GPtrArray *refs)
|
||
{
|
||
g_autoptr(GString) err = g_string_new ("");
|
||
|
||
g_string_printf (err, _("Multiple branches available for %s, you must specify one of: "), name);
|
||
g_ptr_array_sort (refs, flatpak_strcmp0_ptr);
|
||
|
||
for (int i = 0; i < refs->len; i++)
|
||
{
|
||
FlatpakDecomposed *ref = g_ptr_array_index (refs, i);
|
||
if (i != 0)
|
||
g_string_append (err, ", ");
|
||
|
||
g_string_append (err, flatpak_decomposed_get_pref (ref));
|
||
}
|
||
flatpak_fail (error, "%s", err->str);
|
||
|
||
return NULL;
|
||
}
|
||
|
||
static FlatpakDecomposed *
|
||
find_matching_ref (GHashTable *refs,
|
||
const char *name,
|
||
const char *opt_branch,
|
||
const char *opt_default_branch,
|
||
const char **valid_arches, /* NULL => any arch */
|
||
const char *opt_default_arch,
|
||
FlatpakKinds kinds,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GPtrArray) matched_refs = NULL;
|
||
|
||
matched_refs = find_matching_refs (refs,
|
||
name,
|
||
opt_branch,
|
||
opt_default_branch,
|
||
valid_arches,
|
||
opt_default_arch,
|
||
kinds,
|
||
FIND_MATCHING_REFS_FLAGS_NONE,
|
||
error);
|
||
if (matched_refs == NULL)
|
||
return NULL;
|
||
|
||
if (valid_arches != NULL)
|
||
{
|
||
/* Filter by valid, arch. We stop at the first arch (in prio order) that has a match */
|
||
for (int i = 0; valid_arches[i] != NULL; i++)
|
||
{
|
||
const char *arch = valid_arches[i];
|
||
|
||
g_autoptr(GPtrArray) arched_refs = get_refs_for_arch (matched_refs, arch);
|
||
|
||
if (arched_refs->len == 1)
|
||
return flatpak_decomposed_ref (g_ptr_array_index (arched_refs, 0));
|
||
|
||
if (arched_refs->len > 1)
|
||
return fail_multiple_refs (error, name, arched_refs);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (matched_refs->len == 1)
|
||
return flatpak_decomposed_ref (g_ptr_array_index (matched_refs, 0));
|
||
|
||
if (matched_refs->len > 1)
|
||
return fail_multiple_refs (error, name, matched_refs);
|
||
}
|
||
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
||
_("Nothing matches %s"), name);
|
||
return NULL;
|
||
}
|
||
|
||
char *
|
||
flatpak_dir_get_remote_collection_id (FlatpakDir *self,
|
||
const char *remote_name)
|
||
{
|
||
char *collection_id = NULL;
|
||
|
||
if (!flatpak_dir_ensure_repo (self, NULL, NULL))
|
||
return NULL;
|
||
|
||
repo_get_remote_collection_id (self->repo, remote_name, &collection_id, NULL);
|
||
|
||
return collection_id;
|
||
}
|
||
|
||
/* This tries to find all available refs based on the specified name/branch/arch
|
||
* triplet from a remote. If arch is not specified, matches only on compatible arches.
|
||
*/
|
||
GPtrArray *
|
||
flatpak_dir_find_remote_refs (FlatpakDir *self,
|
||
FlatpakRemoteState *state,
|
||
const char *name,
|
||
const char *opt_branch,
|
||
const char *opt_default_branch,
|
||
const char *opt_arch,
|
||
const char *opt_default_arch,
|
||
FlatpakKinds kinds,
|
||
FindMatchingRefsFlags flags,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GHashTable) remote_refs = NULL;
|
||
g_autoptr(GPtrArray) matched_refs = NULL;
|
||
const char **valid_arches = flatpak_get_arches ();
|
||
const char *opt_arches[] = {opt_arch, NULL};
|
||
|
||
if (opt_arch != NULL)
|
||
valid_arches = opt_arches;
|
||
|
||
if (!flatpak_dir_list_all_remote_refs (self, state,
|
||
&remote_refs, cancellable, error))
|
||
return NULL;
|
||
|
||
matched_refs = find_matching_refs (remote_refs,
|
||
name,
|
||
opt_branch,
|
||
opt_default_branch,
|
||
valid_arches,
|
||
opt_default_arch,
|
||
kinds,
|
||
flags,
|
||
error);
|
||
if (matched_refs == NULL)
|
||
return NULL;
|
||
|
||
/* If we can't match anything and we had an error downloading (offline?), report that as its more helpful */
|
||
if (matched_refs->len == 0 && state->summary_fetch_error)
|
||
{
|
||
g_propagate_error (error, g_error_copy (state->summary_fetch_error));
|
||
return NULL;
|
||
}
|
||
|
||
return g_steal_pointer (&matched_refs);
|
||
}
|
||
|
||
static FlatpakDecomposed *
|
||
find_ref_for_refs_set (GHashTable *refs,
|
||
const char *name,
|
||
const char *opt_branch,
|
||
const char *opt_default_branch,
|
||
const char *opt_arch,
|
||
FlatpakKinds kinds,
|
||
GError **error)
|
||
{
|
||
const char **valid_arches = flatpak_get_arches ();
|
||
const char *opt_arches[] = {opt_arch, NULL};
|
||
|
||
if (opt_arch != NULL)
|
||
valid_arches = opt_arches;
|
||
|
||
g_autoptr(GError) my_error = NULL;
|
||
g_autoptr(FlatpakDecomposed) ref = find_matching_ref (refs,
|
||
name,
|
||
opt_branch,
|
||
opt_default_branch,
|
||
valid_arches,
|
||
NULL,
|
||
kinds,
|
||
&my_error);
|
||
if (ref == NULL)
|
||
{
|
||
if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||
g_clear_error (&my_error);
|
||
else
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&my_error));
|
||
return NULL;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
return g_steal_pointer (&ref);
|
||
}
|
||
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
||
_("Can't find ref %s%s%s%s%s"), name,
|
||
(opt_arch != NULL || opt_branch != NULL) ? "/" : "",
|
||
opt_arch ? opt_arch : "",
|
||
opt_branch ? "/" : "",
|
||
opt_branch ? opt_branch : "");
|
||
|
||
return NULL;
|
||
}
|
||
|
||
/* This tries to find a single ref based on the specfied name/branch/arch
|
||
* triplet from a remote. If arch is not specified, matches only on compatible arches.
|
||
*/
|
||
FlatpakDecomposed *
|
||
flatpak_dir_find_remote_ref (FlatpakDir *self,
|
||
FlatpakRemoteState *state,
|
||
const char *name,
|
||
const char *opt_branch,
|
||
const char *opt_default_branch,
|
||
const char *opt_arch,
|
||
FlatpakKinds kinds,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(FlatpakDecomposed) remote_ref = NULL;
|
||
g_autoptr(GHashTable) remote_refs = NULL;
|
||
g_autoptr(GError) my_error = NULL;
|
||
|
||
/* Avoid work if the entire ref was specified */
|
||
if (opt_branch != NULL && opt_arch != NULL && (kinds == FLATPAK_KINDS_APP || kinds == FLATPAK_KINDS_RUNTIME))
|
||
return flatpak_decomposed_new_from_parts (kinds, name, opt_arch, opt_branch, error);
|
||
|
||
if (!flatpak_dir_list_all_remote_refs (self, state,
|
||
&remote_refs, cancellable, error))
|
||
return NULL;
|
||
|
||
remote_ref = find_ref_for_refs_set (remote_refs, name, opt_branch,
|
||
opt_default_branch, opt_arch,
|
||
kinds, &my_error);
|
||
if (!remote_ref)
|
||
{
|
||
if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||
{
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
||
_("Error searching remote %s: %s"),
|
||
state->remote_name,
|
||
my_error->message);
|
||
return NULL;
|
||
}
|
||
else
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&my_error));
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
return g_steal_pointer (&remote_ref);
|
||
}
|
||
|
||
static GHashTable *
|
||
refspecs_decompose_steal (GHashTable *refspecs)
|
||
{
|
||
g_autoptr(GHashTable) refs = NULL;
|
||
GHashTableIter iter;
|
||
gpointer key, value;
|
||
|
||
refs = g_hash_table_new_full ((GHashFunc)flatpak_decomposed_hash, (GEqualFunc)flatpak_decomposed_equal,
|
||
(GDestroyNotify)flatpak_decomposed_unref, g_free);
|
||
|
||
g_hash_table_iter_init (&iter, refspecs);
|
||
while (g_hash_table_iter_next (&iter, &key, &value))
|
||
{
|
||
char *checksum = value;
|
||
char *refspec = key;
|
||
FlatpakDecomposed *decomposed;
|
||
|
||
g_hash_table_iter_steal (&iter);
|
||
|
||
decomposed = flatpak_decomposed_new_from_refspec_take (refspec, NULL);
|
||
if (decomposed)
|
||
{
|
||
g_hash_table_insert (refs, decomposed, checksum);
|
||
}
|
||
else
|
||
{
|
||
g_free (checksum);
|
||
g_free (refspec);
|
||
}
|
||
}
|
||
|
||
return g_steal_pointer (&refs);
|
||
}
|
||
|
||
GPtrArray *
|
||
flatpak_dir_find_local_refs (FlatpakDir *self,
|
||
const char *remote,
|
||
const char *name,
|
||
const char *opt_branch,
|
||
const char *opt_default_branch,
|
||
const char *opt_arch,
|
||
const char *opt_default_arch,
|
||
FlatpakKinds kinds,
|
||
FindMatchingRefsFlags flags,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GHashTable) local_refs = NULL;
|
||
g_autoptr(GHashTable) local_refspecs = NULL;
|
||
g_autoptr(GError) my_error = NULL;
|
||
g_autofree char *refspec_prefix = g_strconcat (remote, ":.", NULL);
|
||
g_autoptr(GPtrArray) matched_refs = NULL;
|
||
const char **valid_arches = flatpak_get_arches ();
|
||
const char *opt_arches[] = {opt_arch, NULL};
|
||
|
||
if (opt_arch != NULL)
|
||
valid_arches = opt_arches;
|
||
|
||
if (!flatpak_dir_ensure_repo (self, NULL, error))
|
||
return NULL;
|
||
|
||
if (!ostree_repo_list_refs (self->repo,
|
||
refspec_prefix,
|
||
&local_refspecs, cancellable, error))
|
||
return NULL;
|
||
|
||
local_refs = refspecs_decompose_steal (local_refspecs);
|
||
|
||
matched_refs = find_matching_refs (local_refs,
|
||
name,
|
||
opt_branch,
|
||
opt_default_branch,
|
||
valid_arches,
|
||
opt_default_arch,
|
||
kinds,
|
||
flags,
|
||
&my_error);
|
||
if (matched_refs == NULL)
|
||
{
|
||
if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||
{
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
||
_("Error searching local repository: %s"),
|
||
my_error->message);
|
||
return NULL;
|
||
}
|
||
else
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&my_error));
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
return g_steal_pointer (&matched_refs);
|
||
}
|
||
|
||
static GHashTable *
|
||
flatpak_dir_get_all_installed_refs (FlatpakDir *self,
|
||
FlatpakKinds kinds,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GHashTable) local_refs = NULL;
|
||
|
||
if (!flatpak_dir_maybe_ensure_repo (self, NULL, error))
|
||
return NULL;
|
||
|
||
local_refs = g_hash_table_new_full ((GHashFunc)flatpak_decomposed_hash, (GEqualFunc)flatpak_decomposed_equal, (GDestroyNotify)flatpak_decomposed_unref, NULL);
|
||
if (kinds & FLATPAK_KINDS_APP)
|
||
{
|
||
g_autoptr(GPtrArray) app_refs = flatpak_dir_list_refs (self, FLATPAK_KINDS_APP, NULL, error);
|
||
if (app_refs == NULL)
|
||
return NULL;
|
||
|
||
for (int i = 0; i < app_refs->len; i++)
|
||
{
|
||
FlatpakDecomposed *app_ref = g_ptr_array_index (app_refs, i);
|
||
g_hash_table_add (local_refs, flatpak_decomposed_ref (app_ref));
|
||
}
|
||
}
|
||
if (kinds & FLATPAK_KINDS_RUNTIME)
|
||
{
|
||
g_autoptr(GPtrArray) runtime_refs = flatpak_dir_list_refs (self, FLATPAK_KINDS_RUNTIME, NULL, error);
|
||
if (runtime_refs == NULL)
|
||
return NULL;
|
||
|
||
for (int i = 0; i < runtime_refs->len; i++)
|
||
{
|
||
FlatpakDecomposed *runtime_ref = g_ptr_array_index (runtime_refs, i);
|
||
g_hash_table_add (local_refs, flatpak_decomposed_ref (runtime_ref));
|
||
}
|
||
}
|
||
|
||
return g_steal_pointer (&local_refs);
|
||
}
|
||
|
||
/* This tries to find a all installed refs based on the specfied name/branch/arch
|
||
* triplet. Matches on all arches.
|
||
*/
|
||
GPtrArray *
|
||
flatpak_dir_find_installed_refs (FlatpakDir *self,
|
||
const char *opt_name,
|
||
const char *opt_branch,
|
||
const char *opt_arch,
|
||
FlatpakKinds kinds,
|
||
FindMatchingRefsFlags flags,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GHashTable) local_refs = NULL;
|
||
g_autoptr(GPtrArray) matched_refs = NULL;
|
||
const char **valid_arches = NULL; /* List all installed arches if unspecified */
|
||
const char *opt_arches[] = {opt_arch, NULL};
|
||
|
||
if (opt_arch != NULL)
|
||
valid_arches = opt_arches;
|
||
|
||
local_refs = flatpak_dir_get_all_installed_refs (self, kinds, error);
|
||
if (local_refs == NULL)
|
||
return NULL;
|
||
|
||
matched_refs = find_matching_refs (local_refs,
|
||
opt_name,
|
||
opt_branch,
|
||
NULL, /* default branch */
|
||
valid_arches,
|
||
NULL, /* default arch */
|
||
kinds,
|
||
flags,
|
||
error);
|
||
if (matched_refs == NULL)
|
||
return NULL;
|
||
|
||
return g_steal_pointer (&matched_refs);
|
||
}
|
||
|
||
/* This tries to find a single ref based on the specfied name/branch/arch
|
||
* triplet. This matches on all (installed) arches, but defaults to the primary
|
||
* arch if that is installed. Otherwise, ambiguity is an error.
|
||
*/
|
||
FlatpakDecomposed *
|
||
flatpak_dir_find_installed_ref (FlatpakDir *self,
|
||
const char *opt_name,
|
||
const char *opt_branch,
|
||
const char *opt_arch,
|
||
FlatpakKinds kinds,
|
||
GError **error)
|
||
{
|
||
g_autoptr(FlatpakDecomposed) local_ref = NULL;
|
||
g_autoptr(GHashTable) local_refs = NULL;
|
||
g_autoptr(GError) my_error = NULL;
|
||
const char **valid_arches = NULL; /* All are valid unless specified in opt_arch */
|
||
const char *default_arch = flatpak_get_arch ();
|
||
const char *opt_arches[] = {opt_arch, NULL};
|
||
|
||
if (opt_arch != NULL)
|
||
valid_arches = opt_arches;
|
||
|
||
local_refs = flatpak_dir_get_all_installed_refs (self, kinds, error);
|
||
if (local_refs == NULL)
|
||
return NULL;
|
||
|
||
local_ref = find_matching_ref (local_refs, opt_name, opt_branch, NULL,
|
||
valid_arches, default_arch,
|
||
kinds, &my_error);
|
||
if (local_ref == NULL)
|
||
{
|
||
if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||
g_clear_error (&my_error);
|
||
else
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&my_error));
|
||
return NULL;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
return g_steal_pointer (&local_ref);
|
||
}
|
||
|
||
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED,
|
||
_("%s/%s/%s not installed"),
|
||
opt_name ? opt_name : "*unspecified*",
|
||
opt_arch ? opt_arch : "*unspecified*",
|
||
opt_branch ? opt_branch : "*unspecified*");
|
||
return NULL;
|
||
}
|
||
|
||
/* Given a list of decomposed refs in local_refspecs, remove any refs that have already
|
||
* been deployed and return a new GPtrArray containing only the undeployed
|
||
* refs. This is used by flatpak_dir_cleanup_undeployed_refs to determine
|
||
* which undeployed refs need to be removed from the local repository.
|
||
*
|
||
* Returns: (transfer-full): A #GPtrArray
|
||
*/
|
||
static GPtrArray *
|
||
filter_out_deployed_refs (FlatpakDir *self,
|
||
GPtrArray *local_refspecs,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GPtrArray) undeployed_refs = g_ptr_array_new_full (local_refspecs->len, (GDestroyNotify)flatpak_decomposed_unref);
|
||
gsize i;
|
||
|
||
for (i = 0; i < local_refspecs->len; ++i)
|
||
{
|
||
FlatpakDecomposed *ref = g_ptr_array_index (local_refspecs, i);
|
||
g_autoptr(GBytes) deploy_data = NULL;
|
||
|
||
deploy_data = flatpak_dir_get_deploy_data (self, ref, FLATPAK_DEPLOY_VERSION_ANY, NULL, NULL);
|
||
|
||
if (!deploy_data)
|
||
g_ptr_array_add (undeployed_refs, flatpak_decomposed_ref (ref));
|
||
}
|
||
|
||
return g_steal_pointer (&undeployed_refs);
|
||
}
|
||
|
||
/**
|
||
* flatpak_dir_cleanup_undeployed_refs:
|
||
* @self: a #FlatpakDir
|
||
* @cancellable: (nullable) (optional): a #GCancellable
|
||
* @error: a #GError
|
||
*
|
||
* Find all flatpak refs in the local repository which have not been deployed
|
||
* in the dir and remove them from the repository. You might want to call this
|
||
* function if you pulled refs into the dir but then decided that you did
|
||
* not want to deploy them for some reason. Note that this does not prune
|
||
* objects bound to the cleaned up refs from the underlying OSTree repository,
|
||
* you should consider using flatpak_dir_prune() to do that.
|
||
*
|
||
* Since: 0.10.0
|
||
* Returns: %TRUE if cleaning up the refs succeeded, %FALSE otherwise
|
||
*/
|
||
gboolean
|
||
flatpak_dir_cleanup_undeployed_refs (FlatpakDir *self,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GHashTable) local_refspecs = NULL;
|
||
g_autoptr(GHashTable) local_refs = NULL;
|
||
g_autoptr(GPtrArray) local_flatpak_refspecs = NULL;
|
||
g_autoptr(GPtrArray) undeployed_refs = NULL;
|
||
gsize i = 0;
|
||
|
||
if (!ostree_repo_list_refs (self->repo, NULL, &local_refspecs, cancellable, error))
|
||
return FALSE;
|
||
|
||
local_refs = refspecs_decompose_steal (local_refspecs);
|
||
|
||
local_flatpak_refspecs = find_matching_refs (local_refs,
|
||
NULL, NULL, NULL, NULL, NULL,
|
||
FLATPAK_KINDS_APP |
|
||
FLATPAK_KINDS_RUNTIME,
|
||
0, error);
|
||
|
||
if (!local_flatpak_refspecs)
|
||
return FALSE;
|
||
|
||
undeployed_refs = filter_out_deployed_refs (self, local_flatpak_refspecs, error);
|
||
|
||
if (!undeployed_refs)
|
||
return FALSE;
|
||
|
||
for (; i < undeployed_refs->len; ++i)
|
||
{
|
||
FlatpakDecomposed *ref = g_ptr_array_index (undeployed_refs, i);
|
||
g_autofree gchar *remote = flatpak_decomposed_dup_remote (ref);
|
||
|
||
if (!flatpak_dir_remove_ref (self, remote, flatpak_decomposed_get_ref (ref), cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static FlatpakDir *
|
||
flatpak_dir_new_full (GFile *path, gboolean user, DirExtraData *extra_data)
|
||
{
|
||
FlatpakDir *dir = g_object_new (FLATPAK_TYPE_DIR, "path", path, "user", user, NULL);
|
||
|
||
if (extra_data != NULL)
|
||
dir->extra_data = dir_extra_data_clone (extra_data);
|
||
|
||
return dir;
|
||
}
|
||
|
||
FlatpakDir *
|
||
flatpak_dir_new (GFile *path, gboolean user)
|
||
{
|
||
/* We are only interested on extra data for system-wide installations, in which
|
||
case we use _new_full() directly, so here we just call it passing NULL */
|
||
return flatpak_dir_new_full (path, user, NULL);
|
||
}
|
||
|
||
FlatpakDir *
|
||
flatpak_dir_clone (FlatpakDir *self)
|
||
{
|
||
FlatpakDir *clone;
|
||
|
||
clone = flatpak_dir_new_full (self->basedir, self->user, self->extra_data);
|
||
|
||
flatpak_dir_set_no_system_helper (clone, self->no_system_helper);
|
||
flatpak_dir_set_no_interaction (clone, self->no_interaction);
|
||
|
||
return clone;
|
||
}
|
||
|
||
FlatpakDir *
|
||
flatpak_dir_get_system_default (void)
|
||
{
|
||
g_autoptr(GFile) path = flatpak_get_system_default_base_dir_location ();
|
||
g_autoptr(DirExtraData) extra_data = dir_extra_data_new (SYSTEM_DIR_DEFAULT_ID,
|
||
SYSTEM_DIR_DEFAULT_DISPLAY_NAME,
|
||
SYSTEM_DIR_DEFAULT_PRIORITY,
|
||
SYSTEM_DIR_DEFAULT_STORAGE_TYPE);
|
||
return flatpak_dir_new_full (path, FALSE, extra_data);
|
||
}
|
||
|
||
/* This figures out if it is a user or system dir automatically */
|
||
FlatpakDir *
|
||
flatpak_dir_get_by_path (GFile *path)
|
||
{
|
||
GPtrArray *locations = flatpak_get_system_base_dir_locations (NULL, NULL);
|
||
int i;
|
||
|
||
if (locations)
|
||
{
|
||
for (i = 0; i < locations->len; i++)
|
||
{
|
||
GFile *system_path = g_ptr_array_index (locations, i);
|
||
|
||
if (g_file_equal (system_path, path))
|
||
{
|
||
DirExtraData *extra_data = g_object_get_data (G_OBJECT (path), "extra-data");
|
||
return flatpak_dir_new_full (path, FALSE, extra_data);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* If its not configured as a system installation it will not have
|
||
an installation id and we can't use the system helper, so assume
|
||
user (and fail later with permission issues if its not owned by
|
||
the caller) */
|
||
|
||
return flatpak_dir_new (path, TRUE);
|
||
}
|
||
|
||
FlatpakDir *
|
||
flatpak_dir_get_system_by_id (const char *id,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GError) local_error = NULL;
|
||
GPtrArray *locations = NULL;
|
||
FlatpakDir *ret = NULL;
|
||
int i;
|
||
|
||
if (id == NULL || g_strcmp0 (id, SYSTEM_DIR_DEFAULT_ID) == 0)
|
||
return flatpak_dir_get_system_default ();
|
||
|
||
/* An error in flatpak_get_system_base_dir_locations() will still return
|
||
* return an empty array with the GError set, but we want to return NULL.
|
||
*/
|
||
locations = flatpak_get_system_base_dir_locations (cancellable, &local_error);
|
||
if (local_error != NULL)
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return NULL;
|
||
}
|
||
|
||
for (i = 0; i < locations->len; i++)
|
||
{
|
||
GFile *path = g_ptr_array_index (locations, i);
|
||
DirExtraData *extra_data = g_object_get_data (G_OBJECT (path), "extra-data");
|
||
if (extra_data != NULL && g_strcmp0 (extra_data->id, id) == 0)
|
||
{
|
||
ret = flatpak_dir_new_full (path, FALSE, extra_data);
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (ret == NULL)
|
||
{
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
||
_("Could not find installation %s"), id);
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
GPtrArray *
|
||
flatpak_dir_get_system_list (GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GPtrArray) result = NULL;
|
||
g_autoptr(GError) local_error = NULL;
|
||
GPtrArray *locations = NULL;
|
||
int i;
|
||
|
||
/* An error in flatpak_get_system_base_dir_locations() will still return
|
||
* return an empty array with the GError set, but we want to return NULL.
|
||
*/
|
||
locations = flatpak_get_system_base_dir_locations (cancellable, &local_error);
|
||
if (local_error != NULL)
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return NULL;
|
||
}
|
||
|
||
result = g_ptr_array_new_with_free_func (g_object_unref);
|
||
for (i = 0; i < locations->len; i++)
|
||
{
|
||
GFile *path = g_ptr_array_index (locations, i);
|
||
DirExtraData *extra_data = g_object_get_data (G_OBJECT (path), "extra-data");
|
||
g_ptr_array_add (result, flatpak_dir_new_full (path, FALSE, extra_data));
|
||
}
|
||
|
||
return g_steal_pointer (&result);
|
||
}
|
||
|
||
FlatpakDir *
|
||
flatpak_dir_get_user (void)
|
||
{
|
||
g_autoptr(GFile) path = flatpak_get_user_base_dir_location ();
|
||
return flatpak_dir_new (path, TRUE);
|
||
}
|
||
|
||
static char *
|
||
get_group (const char *remote_name)
|
||
{
|
||
return g_strdup_printf ("remote \"%s\"", remote_name);
|
||
}
|
||
|
||
static GKeyFile *
|
||
flatpak_dir_get_repo_config (FlatpakDir *self)
|
||
{
|
||
if (!flatpak_dir_ensure_repo (self, NULL, NULL))
|
||
return NULL;
|
||
|
||
return ostree_repo_get_config (self->repo);
|
||
}
|
||
|
||
char **
|
||
flatpak_dir_list_remote_config_keys (FlatpakDir *self,
|
||
const char *remote_name)
|
||
{
|
||
GKeyFile *config = flatpak_dir_get_repo_config (self);
|
||
g_autofree char *group = get_group (remote_name);
|
||
|
||
if (config)
|
||
return g_key_file_get_keys (config, group, NULL, NULL);
|
||
|
||
return NULL;
|
||
}
|
||
|
||
GPtrArray *
|
||
flatpak_dir_get_sideload_repo_paths (FlatpakDir *self)
|
||
{
|
||
g_autoptr(GFile) sideload_repos_dir = flatpak_dir_get_sideload_repos_dir (self);
|
||
g_autoptr(GFile) runtime_sideload_repos_dir = flatpak_dir_get_runtime_sideload_repos_dir (self);
|
||
g_autoptr(GPtrArray) res = g_ptr_array_new_with_free_func (g_object_unref);
|
||
|
||
add_sideload_subdirs (res, sideload_repos_dir, TRUE);
|
||
add_sideload_subdirs (res, runtime_sideload_repos_dir, TRUE);
|
||
|
||
return g_steal_pointer (&res);
|
||
}
|
||
|
||
|
||
char *
|
||
flatpak_dir_get_remote_title (FlatpakDir *self,
|
||
const char *remote_name)
|
||
{
|
||
GKeyFile *config = flatpak_dir_get_repo_config (self);
|
||
g_autofree char *group = get_group (remote_name);
|
||
|
||
if (config)
|
||
return g_key_file_get_string (config, group, "xa.title", NULL);
|
||
|
||
return NULL;
|
||
}
|
||
|
||
static const char *
|
||
canonical_filter (const char *filter)
|
||
{
|
||
/* Canonicalize "no filter", to NULL (empty means the same) */
|
||
if (filter && *filter == 0)
|
||
return NULL;
|
||
return filter;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_compare_remote_filter (FlatpakDir *self,
|
||
const char *remote_name,
|
||
const char *filter)
|
||
{
|
||
g_autofree char *current_filter = flatpak_dir_get_remote_filter (self, remote_name);
|
||
|
||
return g_strcmp0 (current_filter, canonical_filter (filter)) == 0;
|
||
}
|
||
|
||
/* returns the canonical form */
|
||
char *
|
||
flatpak_dir_get_remote_filter (FlatpakDir *self,
|
||
const char *remote_name)
|
||
{
|
||
GKeyFile *config = flatpak_dir_get_repo_config (self);
|
||
g_autofree char *group = get_group (remote_name);
|
||
|
||
if (config)
|
||
{
|
||
g_autofree char *filter = g_key_file_get_string (config, group, "xa.filter", NULL);
|
||
|
||
if (filter && *filter != 0)
|
||
return g_steal_pointer (&filter);
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
char *
|
||
flatpak_dir_get_remote_comment (FlatpakDir *self,
|
||
const char *remote_name)
|
||
{
|
||
GKeyFile *config = flatpak_dir_get_repo_config (self);
|
||
g_autofree char *group = get_group (remote_name);
|
||
|
||
if (config)
|
||
return g_key_file_get_string (config, group, "xa.comment", NULL);
|
||
|
||
return NULL;
|
||
}
|
||
|
||
char *
|
||
flatpak_dir_get_remote_description (FlatpakDir *self,
|
||
const char *remote_name)
|
||
{
|
||
GKeyFile *config = flatpak_dir_get_repo_config (self);
|
||
g_autofree char *group = get_group (remote_name);
|
||
|
||
if (config)
|
||
return g_key_file_get_string (config, group, "xa.description", NULL);
|
||
|
||
return NULL;
|
||
}
|
||
|
||
char *
|
||
flatpak_dir_get_remote_homepage (FlatpakDir *self,
|
||
const char *remote_name)
|
||
{
|
||
GKeyFile *config = flatpak_dir_get_repo_config (self);
|
||
g_autofree char *group = get_group (remote_name);
|
||
|
||
if (config)
|
||
return g_key_file_get_string (config, group, "xa.homepage", NULL);
|
||
|
||
return NULL;
|
||
}
|
||
|
||
char *
|
||
flatpak_dir_get_remote_icon (FlatpakDir *self,
|
||
const char *remote_name)
|
||
{
|
||
GKeyFile *config = flatpak_dir_get_repo_config (self);
|
||
g_autofree char *group = get_group (remote_name);
|
||
|
||
if (config)
|
||
return g_key_file_get_string (config, group, "xa.icon", NULL);
|
||
|
||
return NULL;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_get_remote_oci (FlatpakDir *self,
|
||
const char *remote_name)
|
||
{
|
||
g_autofree char *url = NULL;
|
||
|
||
if (!flatpak_dir_ensure_repo (self, NULL, NULL))
|
||
return FALSE;
|
||
|
||
if (!ostree_repo_remote_get_url (self->repo, remote_name, &url, NULL))
|
||
return FALSE;
|
||
|
||
return url && g_str_has_prefix (url, "oci+");
|
||
}
|
||
|
||
gint32
|
||
flatpak_dir_get_remote_default_token_type (FlatpakDir *self,
|
||
const char *remote_name)
|
||
{
|
||
GKeyFile *config = flatpak_dir_get_repo_config (self);
|
||
g_autofree char *group = get_group (remote_name);
|
||
|
||
if (config)
|
||
return (gint32)g_key_file_get_integer (config, group, "xa.default-token-type", NULL);
|
||
|
||
return 0;
|
||
}
|
||
|
||
char *
|
||
flatpak_dir_get_remote_main_ref (FlatpakDir *self,
|
||
const char *remote_name)
|
||
{
|
||
GKeyFile *config = flatpak_dir_get_repo_config (self);
|
||
g_autofree char *group = get_group (remote_name);
|
||
|
||
if (config)
|
||
return g_key_file_get_string (config, group, "xa.main-ref", NULL);
|
||
|
||
return NULL;
|
||
}
|
||
|
||
char *
|
||
flatpak_dir_get_remote_default_branch (FlatpakDir *self,
|
||
const char *remote_name)
|
||
{
|
||
GKeyFile *config = flatpak_dir_get_repo_config (self);
|
||
g_autofree char *group = get_group (remote_name);
|
||
|
||
if (config)
|
||
return g_key_file_get_string (config, group, "xa.default-branch", NULL);
|
||
|
||
return NULL;
|
||
}
|
||
|
||
int
|
||
flatpak_dir_get_remote_prio (FlatpakDir *self,
|
||
const char *remote_name)
|
||
{
|
||
GKeyFile *config = flatpak_dir_get_repo_config (self);
|
||
g_autofree char *group = get_group (remote_name);
|
||
|
||
if (config && g_key_file_has_key (config, group, "xa.prio", NULL))
|
||
return g_key_file_get_integer (config, group, "xa.prio", NULL);
|
||
|
||
return 1;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_get_remote_noenumerate (FlatpakDir *self,
|
||
const char *remote_name)
|
||
{
|
||
GKeyFile *config = flatpak_dir_get_repo_config (self);
|
||
g_autofree char *group = get_group (remote_name);
|
||
|
||
if (config)
|
||
return g_key_file_get_boolean (config, group, "xa.noenumerate", NULL);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_get_remote_nodeps (FlatpakDir *self,
|
||
const char *remote_name)
|
||
{
|
||
GKeyFile *config = flatpak_dir_get_repo_config (self);
|
||
g_autofree char *group = get_group (remote_name);
|
||
|
||
if (config)
|
||
return g_key_file_get_boolean (config, group, "xa.nodeps", NULL);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
char *
|
||
flatpak_dir_get_remote_subset (FlatpakDir *self,
|
||
const char *remote_name)
|
||
{
|
||
GKeyFile *config = flatpak_dir_get_repo_config (self);
|
||
g_autofree char *group = get_group (remote_name);
|
||
g_autofree char *subset = NULL;
|
||
|
||
if (config == NULL)
|
||
return NULL;
|
||
|
||
subset = g_key_file_get_string (config, group, "xa.subset", NULL);
|
||
if (subset == NULL || *subset == 0)
|
||
return NULL;
|
||
|
||
return g_steal_pointer (&subset);
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_get_remote_disabled (FlatpakDir *self,
|
||
const char *remote_name)
|
||
{
|
||
GKeyFile *config = flatpak_dir_get_repo_config (self);
|
||
g_autofree char *group = get_group (remote_name);
|
||
g_autofree char *url = NULL;
|
||
|
||
if (config &&
|
||
g_key_file_get_boolean (config, group, "xa.disable", NULL))
|
||
return TRUE;
|
||
|
||
if (self->repo &&
|
||
ostree_repo_remote_get_url (self->repo, remote_name, &url, NULL) && *url == 0)
|
||
return TRUE; /* Empty URL => disabled */
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static char *
|
||
flatpak_dir_get_remote_install_authenticator_name (FlatpakDir *self,
|
||
const char *remote_name)
|
||
{
|
||
GKeyFile *config = flatpak_dir_get_repo_config (self);
|
||
g_autofree char *group = get_group (remote_name);
|
||
|
||
if (config == NULL ||
|
||
!g_key_file_get_boolean (config, group, "xa.authenticator-install", NULL))
|
||
return NULL;
|
||
|
||
return g_key_file_get_string (config, group, "xa.authenticator-name", NULL);
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_remote_has_deploys (FlatpakDir *self,
|
||
const char *remote)
|
||
{
|
||
g_autoptr(GHashTable) refs = NULL;
|
||
GHashTableIter hash_iter;
|
||
gpointer key;
|
||
|
||
refs = flatpak_dir_get_all_installed_refs (self, FLATPAK_KINDS_APP | FLATPAK_KINDS_RUNTIME, NULL);
|
||
if (refs == NULL)
|
||
return FALSE;
|
||
|
||
g_hash_table_iter_init (&hash_iter, refs);
|
||
while (g_hash_table_iter_next (&hash_iter, &key, NULL))
|
||
{
|
||
FlatpakDecomposed *ref = key;
|
||
g_autofree char *origin = flatpak_dir_get_origin (self, ref, NULL, NULL);
|
||
|
||
if (strcmp (remote, origin) == 0)
|
||
return TRUE;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static gint
|
||
cmp_remote (gconstpointer a,
|
||
gconstpointer b,
|
||
gpointer user_data)
|
||
{
|
||
FlatpakDir *self = user_data;
|
||
const char *a_name = *(const char **) a;
|
||
const char *b_name = *(const char **) b;
|
||
int prio_a, prio_b;
|
||
|
||
prio_a = flatpak_dir_get_remote_prio (self, a_name);
|
||
prio_b = flatpak_dir_get_remote_prio (self, b_name);
|
||
|
||
if (prio_b != prio_a)
|
||
return prio_b - prio_a;
|
||
|
||
/* Ensure we have a well-defined order for same prio */
|
||
return strcmp (a_name, b_name);
|
||
}
|
||
|
||
static gboolean
|
||
origin_remote_matches (OstreeRepo *repo,
|
||
const char *remote_name,
|
||
const char *url,
|
||
const char *main_ref,
|
||
gboolean gpg_verify)
|
||
{
|
||
g_autofree char *real_url = NULL;
|
||
g_autofree char *real_main_ref = NULL;
|
||
gboolean noenumerate;
|
||
gboolean real_gpg_verify;
|
||
|
||
/* Must match url */
|
||
if (url == NULL)
|
||
return FALSE;
|
||
|
||
if (!ostree_repo_remote_get_url (repo, remote_name, &real_url, NULL))
|
||
return FALSE;
|
||
|
||
if (g_strcmp0 (url, real_url) != 0)
|
||
return FALSE;
|
||
|
||
/* Must be noenumerate */
|
||
if (!ostree_repo_get_remote_boolean_option (repo, remote_name,
|
||
"xa.noenumerate",
|
||
FALSE, &noenumerate,
|
||
NULL) ||
|
||
!noenumerate)
|
||
return FALSE;
|
||
|
||
/* Must match gpg-verify
|
||
* NOTE: We assume if all else matches the actual gpg key matches too. */
|
||
if (!ostree_repo_get_remote_boolean_option (repo, remote_name,
|
||
"gpg-verify",
|
||
FALSE, &real_gpg_verify,
|
||
NULL) ||
|
||
real_gpg_verify != gpg_verify)
|
||
return FALSE;
|
||
|
||
/* Must match main-ref */
|
||
if (ostree_repo_get_remote_option (repo, remote_name,
|
||
"xa.main-ref",
|
||
NULL, &real_main_ref,
|
||
NULL) &&
|
||
g_strcmp0 (main_ref, real_main_ref) != 0)
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static char *
|
||
create_origin_remote_config (OstreeRepo *repo,
|
||
const char *url,
|
||
const char *id,
|
||
const char *title,
|
||
const char *main_ref,
|
||
gboolean gpg_verify,
|
||
const char *collection_id,
|
||
GKeyFile **new_config)
|
||
{
|
||
g_autofree char *remote = NULL;
|
||
g_auto(GStrv) remotes = NULL;
|
||
int version = 0;
|
||
g_autofree char *group = NULL;
|
||
g_autofree char *prefix = NULL;
|
||
const char *last_dot;
|
||
|
||
remotes = ostree_repo_remote_list (repo, NULL);
|
||
|
||
last_dot = strrchr (id, '.');
|
||
prefix = g_ascii_strdown (last_dot ? last_dot + 1 : id, -1);
|
||
|
||
do
|
||
{
|
||
g_autofree char *name = NULL;
|
||
if (version == 0)
|
||
name = g_strdup_printf ("%s-origin", prefix);
|
||
else
|
||
name = g_strdup_printf ("%s%d-origin", prefix, version);
|
||
version++;
|
||
|
||
if (origin_remote_matches (repo, name, url, main_ref, gpg_verify))
|
||
return g_steal_pointer (&name);
|
||
|
||
if (remotes == NULL ||
|
||
!g_strv_contains ((const char * const *) remotes, name))
|
||
remote = g_steal_pointer (&name);
|
||
}
|
||
while (remote == NULL);
|
||
|
||
group = g_strdup_printf ("remote \"%s\"", remote);
|
||
|
||
*new_config = g_key_file_new ();
|
||
|
||
g_key_file_set_string (*new_config, group, "url", url ? url : "");
|
||
if (title)
|
||
g_key_file_set_string (*new_config, group, "xa.title", title);
|
||
g_key_file_set_string (*new_config, group, "xa.noenumerate", "true");
|
||
g_key_file_set_string (*new_config, group, "xa.prio", "0");
|
||
g_key_file_set_string (*new_config, group, "gpg-verify-summary", gpg_verify ? "true" : "false");
|
||
g_key_file_set_string (*new_config, group, "gpg-verify", gpg_verify ? "true" : "false");
|
||
if (main_ref)
|
||
g_key_file_set_string (*new_config, group, "xa.main-ref", main_ref);
|
||
|
||
if (collection_id)
|
||
g_key_file_set_string (*new_config, group, "collection-id", collection_id);
|
||
|
||
return g_steal_pointer (&remote);
|
||
}
|
||
|
||
char *
|
||
flatpak_dir_create_origin_remote (FlatpakDir *self,
|
||
const char *url,
|
||
const char *id,
|
||
const char *title,
|
||
const char *main_ref,
|
||
GBytes *gpg_data,
|
||
const char *collection_id,
|
||
gboolean *changed_config,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GKeyFile) new_config = NULL;
|
||
g_autofree char *remote = NULL;
|
||
|
||
remote = create_origin_remote_config (self->repo, url, id, title, main_ref, gpg_data != NULL, collection_id, &new_config);
|
||
|
||
if (new_config &&
|
||
!flatpak_dir_modify_remote (self, remote, new_config,
|
||
gpg_data, cancellable, error))
|
||
return NULL;
|
||
|
||
if (new_config && !_flatpak_dir_reload_config (self, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (changed_config)
|
||
*changed_config = (new_config != NULL);
|
||
|
||
return g_steal_pointer (&remote);
|
||
}
|
||
|
||
static gboolean
|
||
parse_ref_file (GKeyFile *keyfile,
|
||
char **name_out,
|
||
char **branch_out,
|
||
char **url_out,
|
||
GBytes **gpg_data_out,
|
||
gboolean *is_runtime_out,
|
||
char **collection_id_out,
|
||
GError **error)
|
||
{
|
||
g_autofree char *url = NULL;
|
||
g_autofree char *name = NULL;
|
||
g_autofree char *branch = NULL;
|
||
g_autofree char *version = NULL;
|
||
g_autoptr(GBytes) gpg_data = NULL;
|
||
gboolean is_runtime = FALSE;
|
||
g_autofree char *collection_id = NULL;
|
||
g_autofree char *str = NULL;
|
||
|
||
*name_out = NULL;
|
||
*branch_out = NULL;
|
||
*url_out = NULL;
|
||
*gpg_data_out = NULL;
|
||
*is_runtime_out = FALSE;
|
||
*collection_id_out = NULL;
|
||
|
||
if (!g_key_file_has_group (keyfile, FLATPAK_REF_GROUP))
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid file format, no %s group"), FLATPAK_REF_GROUP);
|
||
|
||
version = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP,
|
||
FLATPAK_REF_VERSION_KEY, NULL);
|
||
if (version != NULL && strcmp (version, "1") != 0)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid version %s, only 1 supported"), version);
|
||
|
||
url = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP,
|
||
FLATPAK_REF_URL_KEY, NULL);
|
||
if (url == NULL)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid file format, no %s specified"), FLATPAK_REF_URL_KEY);
|
||
|
||
name = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP,
|
||
FLATPAK_REF_NAME_KEY, NULL);
|
||
if (name == NULL)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid file format, no %s specified"), FLATPAK_REF_NAME_KEY);
|
||
|
||
branch = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP,
|
||
FLATPAK_REF_BRANCH_KEY, NULL);
|
||
if (branch == NULL)
|
||
branch = g_strdup ("master");
|
||
|
||
is_runtime = g_key_file_get_boolean (keyfile, FLATPAK_REF_GROUP,
|
||
FLATPAK_REF_IS_RUNTIME_KEY, NULL);
|
||
|
||
str = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP,
|
||
FLATPAK_REF_GPGKEY_KEY, NULL);
|
||
if (str != NULL)
|
||
{
|
||
g_autofree guchar *decoded = NULL;
|
||
gsize decoded_len;
|
||
|
||
str = g_strstrip (str);
|
||
decoded = g_base64_decode (str, &decoded_len);
|
||
if (decoded_len < 10) /* Check some minimal size so we don't get crap */
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid file format, gpg key invalid"));
|
||
|
||
gpg_data = g_bytes_new_take (g_steal_pointer (&decoded), decoded_len);
|
||
}
|
||
|
||
/* We have a hierarchy of keys for setting the collection ID, which all have
|
||
* the same effect. The only difference is which versions of Flatpak support
|
||
* them, and therefore what P2P implementation is enabled by them:
|
||
* DeploySideloadCollectionID: supported by Flatpak >= 1.12.8 (1.7.1
|
||
* introduced sideload support but this key was added late)
|
||
* DeployCollectionID: supported by Flatpak >= 1.0.6
|
||
* CollectionID: supported by Flatpak >= 0.9.8
|
||
*/
|
||
collection_id = flatpak_keyfile_get_string_non_empty (keyfile, FLATPAK_REF_GROUP,
|
||
FLATPAK_REF_DEPLOY_SIDELOAD_COLLECTION_ID_KEY);
|
||
|
||
if (collection_id == NULL)
|
||
{
|
||
collection_id = flatpak_keyfile_get_string_non_empty (keyfile, FLATPAK_REF_GROUP,
|
||
FLATPAK_REF_DEPLOY_COLLECTION_ID_KEY);
|
||
}
|
||
if (collection_id == NULL)
|
||
{
|
||
collection_id = flatpak_keyfile_get_string_non_empty (keyfile, FLATPAK_REF_GROUP,
|
||
FLATPAK_REF_COLLECTION_ID_KEY);
|
||
}
|
||
|
||
if (collection_id != NULL && gpg_data == NULL)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Collection ID requires GPG key to be provided"));
|
||
|
||
*name_out = g_steal_pointer (&name);
|
||
*branch_out = g_steal_pointer (&branch);
|
||
*url_out = g_steal_pointer (&url);
|
||
*gpg_data_out = g_steal_pointer (&gpg_data);
|
||
*is_runtime_out = is_runtime;
|
||
*collection_id_out = g_steal_pointer (&collection_id);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_create_remote_for_ref_file (FlatpakDir *self,
|
||
GKeyFile *keyfile,
|
||
const char *default_arch,
|
||
char **remote_name_out,
|
||
char **collection_id_out,
|
||
FlatpakDecomposed **ref_out,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GBytes) gpg_data = NULL;
|
||
g_autofree char *name = NULL;
|
||
g_autofree char *branch = NULL;
|
||
g_autofree char *url = NULL;
|
||
g_autofree char *remote = NULL;
|
||
gboolean is_runtime = FALSE;
|
||
g_autofree char *collection_id = NULL;
|
||
g_autoptr(GFile) deploy_dir = NULL;
|
||
g_autoptr(FlatpakDecomposed) ref = NULL;
|
||
|
||
if (!parse_ref_file (keyfile, &name, &branch, &url, &gpg_data, &is_runtime, &collection_id, error))
|
||
return FALSE;
|
||
|
||
ref = flatpak_decomposed_new_from_parts (is_runtime ? FLATPAK_KINDS_RUNTIME : FLATPAK_KINDS_APP,
|
||
name, default_arch, branch, error);
|
||
if (ref == NULL)
|
||
return FALSE;
|
||
|
||
deploy_dir = flatpak_dir_get_if_deployed (self, ref, NULL, NULL);
|
||
if (deploy_dir != NULL)
|
||
{
|
||
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED,
|
||
is_runtime ? _("Runtime %s, branch %s is already installed") :
|
||
_("App %s, branch %s is already installed"),
|
||
name, branch);
|
||
return FALSE;
|
||
}
|
||
|
||
/* First try to reuse existing remote */
|
||
remote = flatpak_dir_find_remote_by_uri (self, url);
|
||
|
||
if (remote == NULL)
|
||
{
|
||
/* title is NULL because the title from the ref file is the title of the app not the remote */
|
||
remote = flatpak_dir_create_origin_remote (self, url, name, NULL, flatpak_decomposed_get_ref (ref),
|
||
gpg_data, collection_id, NULL, NULL, error);
|
||
if (remote == NULL)
|
||
return FALSE;
|
||
}
|
||
|
||
if (collection_id_out != NULL)
|
||
*collection_id_out = g_steal_pointer (&collection_id);
|
||
|
||
*remote_name_out = g_steal_pointer (&remote);
|
||
*ref_out = g_steal_pointer (&ref);
|
||
return TRUE;
|
||
}
|
||
|
||
/* This tries to find a pre-configured remote for the specified uri.
|
||
*
|
||
* We consider non-OCI URLs equal even if one lacks a trailing slash.
|
||
*/
|
||
char *
|
||
flatpak_dir_find_remote_by_uri (FlatpakDir *self,
|
||
const char *uri)
|
||
{
|
||
g_auto(GStrv) remotes = NULL;
|
||
|
||
g_return_val_if_fail (self != NULL, NULL);
|
||
g_return_val_if_fail (uri != NULL, NULL);
|
||
|
||
if (!flatpak_dir_ensure_repo (self, NULL, NULL))
|
||
return NULL;
|
||
|
||
remotes = flatpak_dir_list_enumerated_remotes (self, NULL, NULL);
|
||
if (remotes)
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; remotes != NULL && remotes[i] != NULL; i++)
|
||
{
|
||
const char *remote = remotes[i];
|
||
g_autofree char *remote_uri = NULL;
|
||
|
||
if (!ostree_repo_remote_get_url (self->repo,
|
||
remote,
|
||
&remote_uri,
|
||
NULL))
|
||
continue;
|
||
|
||
if (flatpak_uri_equal (uri, remote_uri))
|
||
return g_strdup (remote);
|
||
}
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_has_remote (FlatpakDir *self,
|
||
const char *remote_name,
|
||
GError **error)
|
||
{
|
||
GKeyFile *config = NULL;
|
||
g_autofree char *group = g_strdup_printf ("remote \"%s\"", remote_name);
|
||
|
||
if (flatpak_dir_maybe_ensure_repo (self, NULL, NULL) &&
|
||
self->repo != NULL)
|
||
{
|
||
config = ostree_repo_get_config (self->repo);
|
||
if (config && g_key_file_has_group (config, group))
|
||
return TRUE;
|
||
}
|
||
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_REMOTE_NOT_FOUND,
|
||
"Remote \"%s\" not found", remote_name);
|
||
}
|
||
|
||
|
||
char **
|
||
flatpak_dir_list_remotes (FlatpakDir *self,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
char **res = NULL;
|
||
|
||
if (!flatpak_dir_maybe_ensure_repo (self, cancellable, error))
|
||
return NULL;
|
||
|
||
if (self->repo)
|
||
res = ostree_repo_remote_list (self->repo, NULL);
|
||
|
||
if (res == NULL)
|
||
res = g_new0 (char *, 1); /* Return empty array, not error */
|
||
|
||
qsort_r (res, g_strv_length (res), sizeof (char *), cmp_remote, self);
|
||
|
||
return res;
|
||
}
|
||
|
||
char **
|
||
flatpak_dir_list_enumerated_remotes (FlatpakDir *self,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GPtrArray) res = g_ptr_array_new_with_free_func (g_free);
|
||
g_auto(GStrv) remotes = NULL;
|
||
int i;
|
||
|
||
remotes = flatpak_dir_list_remotes (self, cancellable, error);
|
||
if (remotes == NULL)
|
||
return NULL;
|
||
|
||
for (i = 0; remotes != NULL && remotes[i] != NULL; i++)
|
||
{
|
||
const char *remote = remotes[i];
|
||
|
||
if (flatpak_dir_get_remote_disabled (self, remote))
|
||
continue;
|
||
|
||
if (flatpak_dir_get_remote_noenumerate (self, remote))
|
||
continue;
|
||
|
||
g_ptr_array_add (res, g_strdup (remote));
|
||
}
|
||
|
||
g_ptr_array_add (res, NULL);
|
||
return (char **) g_ptr_array_free (g_steal_pointer (&res), FALSE);
|
||
}
|
||
|
||
char **
|
||
flatpak_dir_list_dependency_remotes (FlatpakDir *self,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GPtrArray) res = g_ptr_array_new_with_free_func (g_free);
|
||
g_auto(GStrv) remotes = NULL;
|
||
int i;
|
||
|
||
remotes = flatpak_dir_list_remotes (self, cancellable, error);
|
||
if (remotes == NULL)
|
||
return NULL;
|
||
|
||
for (i = 0; remotes != NULL && remotes[i] != NULL; i++)
|
||
{
|
||
const char *remote = remotes[i];
|
||
|
||
if (flatpak_dir_get_remote_disabled (self, remote))
|
||
continue;
|
||
|
||
if (flatpak_dir_get_remote_noenumerate (self, remote))
|
||
continue;
|
||
|
||
if (flatpak_dir_get_remote_nodeps (self, remote))
|
||
continue;
|
||
|
||
g_ptr_array_add (res, g_strdup (remote));
|
||
}
|
||
|
||
g_ptr_array_add (res, NULL);
|
||
return (char **) g_ptr_array_free (g_steal_pointer (&res), FALSE);
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_remove_remote (FlatpakDir *self,
|
||
gboolean force_remove,
|
||
const char *remote_name,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autofree char *prefix = NULL;
|
||
g_autoptr(GHashTable) refs = NULL;
|
||
GHashTableIter hash_iter;
|
||
gpointer key;
|
||
g_autofree char *url = NULL;
|
||
|
||
if (flatpak_dir_use_system_helper (self, NULL))
|
||
{
|
||
g_autoptr(GVariant) gpg_data_v = NULL;
|
||
FlatpakHelperConfigureRemoteFlags flags = 0;
|
||
const char *installation = flatpak_dir_get_id (self);
|
||
|
||
gpg_data_v = g_variant_ref_sink (g_variant_new_from_data (G_VARIANT_TYPE ("ay"), "", 0, TRUE, NULL, NULL));
|
||
|
||
if (force_remove)
|
||
flags |= FLATPAK_HELPER_CONFIGURE_REMOTE_FLAGS_FORCE_REMOVE;
|
||
|
||
if (!flatpak_dir_system_helper_call_configure_remote (self,
|
||
flags, remote_name,
|
||
"",
|
||
gpg_data_v,
|
||
installation ? installation : "",
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (!ostree_repo_list_refs (self->repo,
|
||
NULL,
|
||
&refs,
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
prefix = g_strdup_printf ("%s:", remote_name);
|
||
|
||
if (!force_remove)
|
||
{
|
||
g_hash_table_iter_init (&hash_iter, refs);
|
||
while (g_hash_table_iter_next (&hash_iter, &key, NULL))
|
||
{
|
||
const char *refspec = key;
|
||
|
||
if (!g_str_has_prefix (refspec, prefix))
|
||
continue;
|
||
|
||
g_autoptr(FlatpakDecomposed) ref = flatpak_decomposed_new_from_refspec (refspec, NULL);
|
||
if (ref == NULL)
|
||
continue;
|
||
|
||
g_autofree char *origin = flatpak_dir_get_origin (self, ref, cancellable, NULL);
|
||
if (g_strcmp0 (origin, remote_name) == 0)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_REMOTE_USED,
|
||
_("Can't remove remote '%s' with installed ref %s (at least)"),
|
||
remote_name, flatpak_decomposed_get_ref (ref));
|
||
}
|
||
}
|
||
|
||
/* Remove all refs */
|
||
g_hash_table_iter_init (&hash_iter, refs);
|
||
while (g_hash_table_iter_next (&hash_iter, &key, NULL))
|
||
{
|
||
const char *refspec = key;
|
||
|
||
if (g_str_has_prefix (refspec, prefix) &&
|
||
!flatpak_dir_remove_ref (self, remote_name, refspec + strlen (prefix), cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
if (!flatpak_dir_remove_appstream (self, remote_name,
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
if (flatpak_dir_get_remote_oci (self, remote_name) &&
|
||
!flatpak_dir_remove_oci_files (self, remote_name,
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
ostree_repo_remote_get_url (self->repo, remote_name, &url, NULL);
|
||
|
||
if (!ostree_repo_remote_change (self->repo, NULL,
|
||
OSTREE_REPO_REMOTE_CHANGE_DELETE,
|
||
remote_name, NULL,
|
||
NULL,
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
if (!flatpak_dir_mark_changed (self, error))
|
||
return FALSE;
|
||
|
||
flatpak_dir_log (self, "remove remote",
|
||
remote_name, NULL, NULL, NULL, url,
|
||
"Removed remote %s", remote_name);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
flatpak_dir_cleanup_remote_for_url_change (FlatpakDir *self,
|
||
const char *remote_name,
|
||
const char *url,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autofree char *old_url = NULL;
|
||
|
||
/* We store things a bit differently for OCI and non-OCI remotes,
|
||
* so when changing from one to the other, we need to clean up cached
|
||
* files.
|
||
*/
|
||
if (ostree_repo_remote_get_url (self->repo,
|
||
remote_name,
|
||
&old_url,
|
||
NULL))
|
||
{
|
||
gboolean was_oci = g_str_has_prefix (old_url, "oci+");
|
||
gboolean will_be_oci = g_str_has_prefix (url, "oci+");
|
||
|
||
if (was_oci != will_be_oci)
|
||
{
|
||
if (!flatpak_dir_remove_appstream (self, remote_name,
|
||
cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
if (was_oci && !will_be_oci)
|
||
{
|
||
if (!flatpak_dir_remove_oci_files (self, remote_name,
|
||
cancellable, error))
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_modify_remote (FlatpakDir *self,
|
||
const char *remote_name,
|
||
GKeyFile *config,
|
||
GBytes *gpg_data,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autofree char *group = g_strdup_printf ("remote \"%s\"", remote_name);
|
||
g_autofree char *url = NULL;
|
||
g_autofree char *metalink = NULL;
|
||
g_autoptr(GKeyFile) new_config = NULL;
|
||
g_autofree gchar *filter_path = NULL;
|
||
gboolean has_remote;
|
||
|
||
if (strchr (remote_name, '/') != NULL)
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_REMOTE_NOT_FOUND, _("Invalid character '/' in remote name: %s"),
|
||
remote_name);
|
||
|
||
has_remote = flatpak_dir_has_remote (self, remote_name, NULL);
|
||
|
||
if (!g_key_file_has_group (config, group))
|
||
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("No configuration for remote %s specified"),
|
||
remote_name);
|
||
|
||
if (!flatpak_dir_check_add_remotes_config_dir (self, error))
|
||
return FALSE;
|
||
|
||
if (flatpak_dir_use_system_helper (self, NULL))
|
||
{
|
||
g_autofree char *config_data = g_key_file_to_data (config, NULL, NULL);
|
||
g_autoptr(GVariant) gpg_data_v = NULL;
|
||
const char *installation = flatpak_dir_get_id (self);
|
||
|
||
if (gpg_data != NULL)
|
||
gpg_data_v = variant_new_ay_bytes (gpg_data);
|
||
else
|
||
gpg_data_v = g_variant_ref_sink (g_variant_new_from_data (G_VARIANT_TYPE ("ay"), "", 0, TRUE, NULL, NULL));
|
||
|
||
if (!flatpak_dir_system_helper_call_configure_remote (self,
|
||
0, remote_name,
|
||
config_data,
|
||
gpg_data_v,
|
||
installation ? installation : "",
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
/* If we e.g. changed url or gpg config the cached summary may be invalid */
|
||
if (!flatpak_dir_remote_clear_cached_summary (self, remote_name, cancellable, error))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
metalink = g_key_file_get_string (config, group, "metalink", NULL);
|
||
if (metalink != NULL && *metalink != 0)
|
||
url = g_strconcat ("metalink=", metalink, NULL);
|
||
else
|
||
url = g_key_file_get_string (config, group, "url", NULL);
|
||
|
||
/* No url => disabled */
|
||
if (url == NULL)
|
||
url = g_strdup ("");
|
||
|
||
if (!flatpak_dir_cleanup_remote_for_url_change (self, remote_name, url, cancellable, error))
|
||
return FALSE;
|
||
|
||
/* Add it if its not there yet */
|
||
if (!ostree_repo_remote_change (self->repo, NULL,
|
||
OSTREE_REPO_REMOTE_CHANGE_ADD_IF_NOT_EXISTS,
|
||
remote_name,
|
||
url, NULL, cancellable, error))
|
||
return FALSE;
|
||
|
||
new_config = ostree_repo_copy_config (self->repo);
|
||
|
||
copy_remote_config (new_config, config, remote_name);
|
||
|
||
if (!ostree_repo_write_config (self->repo, new_config, error))
|
||
return FALSE;
|
||
|
||
if (gpg_data != NULL)
|
||
{
|
||
g_autoptr(GInputStream) input_stream = g_memory_input_stream_new_from_bytes (gpg_data);
|
||
guint imported = 0;
|
||
|
||
if (!ostree_repo_remote_gpg_import (self->repo, remote_name, input_stream,
|
||
NULL, &imported, cancellable, error))
|
||
return FALSE;
|
||
|
||
/* XXX If we ever add internationalization, use ngettext() here. */
|
||
g_info ("Imported %u GPG key%s to remote \"%s\"",
|
||
imported, (imported == 1) ? "" : "s", remote_name);
|
||
}
|
||
|
||
filter_path = g_key_file_get_value (new_config, group, "xa.filter", NULL);
|
||
if (filter_path && *filter_path && g_file_test (filter_path, G_FILE_TEST_EXISTS))
|
||
{
|
||
/* Make a backup filter copy in case it goes away later */
|
||
g_autofree char *filter_name = g_strconcat (remote_name, ".filter", NULL);
|
||
g_autoptr(GFile) filter_file = g_file_new_for_path (filter_path);
|
||
g_autoptr(GFile) filter_copy = flatpak_build_file (self->basedir, "repo", filter_name, NULL);
|
||
g_autoptr(GError) local_error = NULL;
|
||
g_autofree char *backup_data = NULL;
|
||
gsize backup_data_size;
|
||
|
||
if (g_file_load_contents (filter_file, cancellable, &backup_data, &backup_data_size, NULL, &local_error))
|
||
{
|
||
g_autofree char *backup_data_copy =
|
||
g_strdup_printf ("# backup copy of %s, do not edit!\n%s", filter_path, backup_data);
|
||
|
||
if (!g_file_replace_contents (filter_copy, backup_data_copy, strlen (backup_data_copy),
|
||
NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION, NULL, cancellable, &local_error))
|
||
g_info ("Failed to save backup copy of filter file %s: %s\n", filter_path, local_error->message);
|
||
}
|
||
else
|
||
{
|
||
g_info ("Failed to read filter %s file while making a backup copy: %s\n", filter_path, local_error->message);
|
||
}
|
||
}
|
||
|
||
/* If we e.g. changed url or gpg config the cached summary may be invalid */
|
||
if (!flatpak_dir_remote_clear_cached_summary (self, remote_name, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (!flatpak_dir_mark_changed (self, error))
|
||
return FALSE;
|
||
|
||
if (has_remote)
|
||
flatpak_dir_log (self, "modify remote", remote_name, NULL, NULL, NULL, url,
|
||
"Modified remote %s to %s", remote_name, url);
|
||
else
|
||
flatpak_dir_log (self, "add remote", remote_name, NULL, NULL, NULL, url,
|
||
"Added remote %s to %s", remote_name, url);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_list_remote_refs (FlatpakDir *self,
|
||
FlatpakRemoteState *state,
|
||
GHashTable **refs,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GError) my_error = NULL;
|
||
|
||
if (error == NULL)
|
||
error = &my_error;
|
||
|
||
if (!flatpak_dir_list_all_remote_refs (self, state, refs,
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
if (flatpak_dir_get_remote_noenumerate (self, state->remote_name))
|
||
{
|
||
g_autoptr(GHashTable) decomposed_local_refs =
|
||
g_hash_table_new_full ((GHashFunc)flatpak_decomposed_hash, (GEqualFunc)flatpak_decomposed_equal, (GDestroyNotify)flatpak_decomposed_unref, NULL);
|
||
g_autoptr(GHashTable) local_refs = NULL;
|
||
g_autoptr(FlatpakDecomposed) decomposed_main_ref = NULL;
|
||
GHashTableIter hash_iter;
|
||
gpointer key;
|
||
g_autofree char *refspec_prefix = g_strconcat (state->remote_name, ":.", NULL);
|
||
g_autofree char *remote_main_ref = NULL;
|
||
|
||
/* For noenumerate remotes, only return data for already locally
|
||
* available refs, or the ref set as xa.main-ref on the remote, or
|
||
* extensions of that main ref */
|
||
|
||
if (!ostree_repo_list_refs (self->repo, refspec_prefix, &local_refs,
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
g_hash_table_iter_init (&hash_iter, local_refs);
|
||
while (g_hash_table_iter_next (&hash_iter, &key, NULL))
|
||
{
|
||
const char *refspec = key;
|
||
g_autofree char *ref = NULL;
|
||
g_autoptr(FlatpakDecomposed) d = NULL;
|
||
|
||
if (!ostree_parse_refspec (refspec, NULL, &ref, error))
|
||
return FALSE;
|
||
|
||
d = flatpak_decomposed_new_from_ref (ref, NULL);
|
||
if (d)
|
||
g_hash_table_insert (decomposed_local_refs, g_steal_pointer (&d), NULL);
|
||
}
|
||
|
||
remote_main_ref = flatpak_dir_get_remote_main_ref (self, state->remote_name);
|
||
if (remote_main_ref != NULL && *remote_main_ref != '\0')
|
||
decomposed_main_ref = flatpak_decomposed_new_from_col_ref (remote_main_ref, state->collection_id, NULL);
|
||
|
||
/* Then we remove all remote refs not in the local refs set, not the main
|
||
* ref, and not an extension of the main ref */
|
||
GLNX_HASH_TABLE_FOREACH_IT (*refs, it, FlatpakDecomposed *, d, void *, v)
|
||
{
|
||
if (g_hash_table_contains (decomposed_local_refs, d))
|
||
continue;
|
||
|
||
if (decomposed_main_ref != NULL)
|
||
{
|
||
g_autofree char *main_ref_id = NULL;
|
||
g_autofree char *main_ref_prefix = NULL;
|
||
|
||
if (flatpak_decomposed_equal (decomposed_main_ref, d))
|
||
continue;
|
||
|
||
main_ref_id = flatpak_decomposed_dup_id (decomposed_main_ref);
|
||
main_ref_prefix = g_strconcat (main_ref_id, ".", NULL);
|
||
if (flatpak_decomposed_id_has_prefix (d, main_ref_prefix))
|
||
continue;
|
||
}
|
||
|
||
g_hash_table_iter_remove (&it);
|
||
}
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
strv_contains_prefix (const gchar * const *strv,
|
||
const gchar *str)
|
||
{
|
||
g_return_val_if_fail (strv != NULL, FALSE);
|
||
g_return_val_if_fail (str != NULL, FALSE);
|
||
|
||
for (; *strv != NULL; strv++)
|
||
{
|
||
if (g_str_has_prefix (str, *strv))
|
||
return TRUE;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_update_remote_configuration_for_state (FlatpakDir *self,
|
||
FlatpakRemoteState *remote_state,
|
||
gboolean dry_run,
|
||
gboolean *has_changed_out,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
/* We only support those configuration parameters that can
|
||
be set in the server when building the repo (see the
|
||
flatpak_repo_set_* () family of functions) */
|
||
static const char *const supported_params[] = {
|
||
"xa.title",
|
||
"xa.comment",
|
||
"xa.description",
|
||
"xa.homepage",
|
||
"xa.icon",
|
||
"xa.default-branch",
|
||
"xa.gpg-keys",
|
||
"xa.redirect-url",
|
||
"xa.authenticator-name",
|
||
"xa.authenticator-install",
|
||
OSTREE_META_KEY_DEPLOY_COLLECTION_ID,
|
||
"xa.deploy-collection-id", /* This is a new version only supported in post p2p flatpak (1.7) */
|
||
NULL
|
||
};
|
||
static const char *const supported_param_prefixes[] = {
|
||
"xa.authenticator-options.",
|
||
NULL
|
||
};
|
||
g_autoptr(GPtrArray) updated_params = NULL;
|
||
g_autoptr(GVariant) metadata = NULL;
|
||
GVariantIter iter;
|
||
g_autoptr(GBytes) gpg_keys = NULL;
|
||
|
||
updated_params = g_ptr_array_new_with_free_func (g_free);
|
||
|
||
if (!flatpak_remote_state_ensure_summary (remote_state, error))
|
||
return FALSE;
|
||
|
||
if (remote_state->index)
|
||
metadata = g_variant_get_child_value (remote_state->index, 1);
|
||
else
|
||
metadata = g_variant_get_child_value (remote_state->summary, 1);
|
||
|
||
g_variant_iter_init (&iter, metadata);
|
||
if (g_variant_iter_n_children (&iter) > 0)
|
||
{
|
||
GVariant *value_var = NULL;
|
||
char *key = NULL;
|
||
|
||
while (g_variant_iter_next (&iter, "{sv}", &key, &value_var))
|
||
{
|
||
if (g_strv_contains (supported_params, key) ||
|
||
strv_contains_prefix (supported_param_prefixes, key))
|
||
{
|
||
if (strcmp (key, "xa.gpg-keys") == 0)
|
||
{
|
||
if (g_variant_is_of_type (value_var, G_VARIANT_TYPE_BYTESTRING))
|
||
{
|
||
const guchar *gpg_data = g_variant_get_data (value_var);
|
||
gsize gpg_size = g_variant_get_size (value_var);
|
||
g_autofree gchar *gpg_data_checksum = g_compute_checksum_for_data (G_CHECKSUM_SHA256, gpg_data, gpg_size);
|
||
|
||
gpg_keys = g_bytes_new (gpg_data, gpg_size);
|
||
|
||
/* We store the hash so that we can detect when things changed or not
|
||
instead of re-importing the key over-and-over */
|
||
g_ptr_array_add (updated_params, g_strdup ("xa.gpg-keys-hash"));
|
||
g_ptr_array_add (updated_params, g_steal_pointer (&gpg_data_checksum));
|
||
}
|
||
}
|
||
else if (g_variant_is_of_type (value_var, G_VARIANT_TYPE_STRING))
|
||
{
|
||
const char *value = g_variant_get_string (value_var, NULL);
|
||
if (value != NULL && *value != 0)
|
||
{
|
||
if (strcmp (key, "xa.redirect-url") == 0)
|
||
g_ptr_array_add (updated_params, g_strdup ("url"));
|
||
else if (strcmp (key, OSTREE_META_KEY_DEPLOY_COLLECTION_ID) == 0)
|
||
g_ptr_array_add (updated_params, g_strdup ("collection-id"));
|
||
else if (strcmp (key, "xa.deploy-collection-id") == 0)
|
||
g_ptr_array_add (updated_params, g_strdup ("collection-id"));
|
||
else
|
||
g_ptr_array_add (updated_params, g_strdup (key));
|
||
g_ptr_array_add (updated_params, g_strdup (value));
|
||
}
|
||
}
|
||
else if (g_variant_is_of_type (value_var, G_VARIANT_TYPE_BOOLEAN))
|
||
{
|
||
gboolean value = g_variant_get_boolean (value_var);
|
||
g_ptr_array_add (updated_params, g_strdup (key));
|
||
if (value)
|
||
g_ptr_array_add (updated_params, g_strdup ("true"));
|
||
else
|
||
g_ptr_array_add (updated_params, g_strdup ("false"));
|
||
}
|
||
}
|
||
|
||
g_variant_unref (value_var);
|
||
g_free (key);
|
||
}
|
||
}
|
||
|
||
if (updated_params->len > 0)
|
||
{
|
||
g_autoptr(GKeyFile) config = NULL;
|
||
g_autofree char *group = NULL;
|
||
gboolean has_changed = FALSE;
|
||
int i;
|
||
|
||
config = ostree_repo_copy_config (flatpak_dir_get_repo (self));
|
||
group = g_strdup_printf ("remote \"%s\"", remote_state->remote_name);
|
||
|
||
i = 0;
|
||
while (i < (updated_params->len - 1))
|
||
{
|
||
/* This array should have an even number of elements with
|
||
keys in the odd positions and values on even ones. */
|
||
const char *key = g_ptr_array_index (updated_params, i);
|
||
const char *new_val = g_ptr_array_index (updated_params, i + 1);
|
||
g_autofree char *current_val = NULL;
|
||
g_autofree char *is_set_key = g_strconcat (key, "-is-set", NULL);
|
||
gboolean is_set = FALSE;
|
||
|
||
is_set = g_key_file_get_boolean (config, group, is_set_key, NULL);
|
||
if (!is_set)
|
||
{
|
||
current_val = g_key_file_get_string (config, group, key, NULL);
|
||
if ((!g_str_equal (key, "collection-id") &&
|
||
g_strcmp0 (current_val, new_val) != 0) ||
|
||
(g_str_equal (key, "collection-id") &&
|
||
(current_val == NULL || *current_val == '\0') &&
|
||
new_val != NULL && *new_val != '\0'))
|
||
{
|
||
has_changed = TRUE;
|
||
g_key_file_set_string (config, group, key, new_val);
|
||
}
|
||
}
|
||
|
||
i += 2;
|
||
}
|
||
|
||
if (has_changed_out)
|
||
*has_changed_out = has_changed;
|
||
|
||
if (dry_run || !has_changed)
|
||
return TRUE;
|
||
|
||
/* Update the local remote configuration with the updated info. */
|
||
if (!flatpak_dir_modify_remote (self, remote_state->remote_name, config, gpg_keys, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_update_remote_configuration (FlatpakDir *self,
|
||
const char *remote,
|
||
FlatpakRemoteState *optional_remote_state,
|
||
gboolean *updated_out,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
gboolean is_oci;
|
||
g_autoptr(FlatpakRemoteState) local_state = NULL;
|
||
FlatpakRemoteState *state;
|
||
gboolean has_changed = FALSE;
|
||
|
||
/* Initialize if we exit early */
|
||
if (updated_out)
|
||
*updated_out = FALSE;
|
||
|
||
if (flatpak_dir_get_remote_disabled (self, remote))
|
||
return TRUE;
|
||
|
||
is_oci = flatpak_dir_get_remote_oci (self, remote);
|
||
if (is_oci)
|
||
return TRUE;
|
||
|
||
if (optional_remote_state)
|
||
state = optional_remote_state;
|
||
else
|
||
{
|
||
local_state = flatpak_dir_get_remote_state (self, remote, FALSE, cancellable, error);
|
||
if (local_state == NULL)
|
||
return FALSE;
|
||
state = local_state;
|
||
}
|
||
|
||
if (flatpak_dir_use_system_helper (self, NULL))
|
||
{
|
||
gboolean gpg_verify_summary;
|
||
gboolean gpg_verify;
|
||
|
||
if (!ostree_repo_remote_get_gpg_verify_summary (self->repo, remote, &gpg_verify_summary, error))
|
||
return FALSE;
|
||
|
||
if (!ostree_repo_remote_get_gpg_verify (self->repo, remote, &gpg_verify, error))
|
||
return FALSE;
|
||
|
||
if (!gpg_verify_summary || !gpg_verify)
|
||
{
|
||
g_info ("Ignoring automatic updates for system-helper remotes without gpg signatures");
|
||
return TRUE;
|
||
}
|
||
|
||
if ((state->summary != NULL && state->summary_sig_bytes == NULL) ||
|
||
(state->index != NULL && state->index_sig_bytes == NULL))
|
||
{
|
||
g_info ("Can't update remote configuration as user, no GPG signature");
|
||
return TRUE;
|
||
}
|
||
|
||
if (!flatpak_dir_update_remote_configuration_for_state (self, state, TRUE, &has_changed, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (has_changed)
|
||
{
|
||
g_autoptr(GBytes) bytes = g_variant_get_data_as_bytes (state->index ? state->index : state->summary);
|
||
GBytes *sig_bytes = state->index ? state->index_sig_bytes : state->summary_sig_bytes;
|
||
glnx_autofd int summary_fd = -1;
|
||
g_autofree char *summary_path = NULL;
|
||
glnx_autofd int summary_sig_fd = -1;
|
||
g_autofree char *summary_sig_path = NULL;
|
||
const char *installation;
|
||
FlatpakHelperUpdateRemoteFlags flags = 0;
|
||
|
||
if (state->index)
|
||
flags |= FLATPAK_HELPER_UPDATE_REMOTE_FLAGS_SUMMARY_IS_INDEX;
|
||
|
||
summary_fd = g_file_open_tmp ("remote-summary.XXXXXX", &summary_path, error);
|
||
if (summary_fd == -1)
|
||
return FALSE;
|
||
if (glnx_loop_write (summary_fd, g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes)) < 0)
|
||
return glnx_throw_errno (error);
|
||
|
||
if (sig_bytes != NULL)
|
||
{
|
||
summary_sig_fd = g_file_open_tmp ("remote-summary-sig.XXXXXX", &summary_sig_path, error);
|
||
if (summary_sig_fd == -1)
|
||
return FALSE;
|
||
if (glnx_loop_write (summary_sig_fd, g_bytes_get_data (sig_bytes, NULL), g_bytes_get_size (sig_bytes)) < 0)
|
||
return glnx_throw_errno (error);
|
||
}
|
||
|
||
installation = flatpak_dir_get_id (self);
|
||
|
||
if (!flatpak_dir_system_helper_call_update_remote (self, flags, remote,
|
||
installation ? installation : "",
|
||
summary_path, summary_sig_path ? summary_sig_path : "",
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
unlink (summary_path);
|
||
if (summary_sig_path)
|
||
unlink (summary_sig_path);
|
||
|
||
|
||
if (!flatpak_dir_remote_clear_cached_summary (self, remote, cancellable, error))
|
||
return FALSE;
|
||
|
||
}
|
||
|
||
if (updated_out)
|
||
*updated_out = has_changed;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
if (!flatpak_dir_update_remote_configuration_for_state (self, state, FALSE, &has_changed, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (has_changed &&
|
||
!flatpak_dir_remote_clear_cached_summary (self, remote, cancellable, error))
|
||
return FALSE;
|
||
|
||
if (updated_out)
|
||
*updated_out = has_changed;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
void
|
||
flatpak_related_free (FlatpakRelated *self)
|
||
{
|
||
g_free (self->remote);
|
||
flatpak_decomposed_unref (self->ref);
|
||
g_free (self->commit);
|
||
g_strfreev (self->subpaths);
|
||
g_free (self);
|
||
}
|
||
|
||
static void
|
||
add_related (FlatpakDir *self,
|
||
GPtrArray *related,
|
||
const char *remote,
|
||
const char *extension,
|
||
FlatpakDecomposed *extension_ref,
|
||
const char *checksum,
|
||
gboolean no_autodownload,
|
||
const char *download_if,
|
||
const char *autoprune_unless,
|
||
gboolean autodelete,
|
||
gboolean locale_subset)
|
||
{
|
||
g_autoptr(GBytes) deploy_data = NULL;
|
||
g_autofree const char **old_subpaths = NULL;
|
||
g_autofree const char *id = NULL;
|
||
g_autofree const char *arch = NULL;
|
||
g_autofree const char *branch = NULL;
|
||
g_auto(GStrv) extra_subpaths = NULL;
|
||
g_auto(GStrv) subpaths = NULL;
|
||
FlatpakRelated *rel;
|
||
gboolean download;
|
||
gboolean delete = autodelete;
|
||
gboolean auto_prune = FALSE;
|
||
g_autoptr(GFile) unmaintained_path = NULL;
|
||
|
||
deploy_data = flatpak_dir_get_deploy_data (self, extension_ref, FLATPAK_DEPLOY_VERSION_ANY, NULL, NULL);
|
||
|
||
id = flatpak_decomposed_dup_id (extension_ref);
|
||
arch = flatpak_decomposed_dup_arch (extension_ref);
|
||
branch = flatpak_decomposed_dup_branch (extension_ref);
|
||
|
||
if (deploy_data)
|
||
{
|
||
old_subpaths = flatpak_deploy_data_get_subpaths (deploy_data);
|
||
/* If the extension is installed already, its origin overrides the remote
|
||
* that would otherwise be used */
|
||
remote = flatpak_deploy_data_get_origin (deploy_data);
|
||
}
|
||
|
||
/* Only respect no-autodownload/download-if for uninstalled refs, we
|
||
always want to update if you manually installed something */
|
||
download =
|
||
flatpak_extension_matches_reason (id, download_if, !no_autodownload) ||
|
||
deploy_data != NULL;
|
||
|
||
if (!flatpak_extension_matches_reason (id, autoprune_unless, TRUE))
|
||
auto_prune = TRUE;
|
||
|
||
/* Don't download if there is an unmaintained extension already installed */
|
||
/* TODO: This is a circular dependency between flatpak-dir (which
|
||
* deals with a single installation) and flatpak-dir-utils (which
|
||
* deals with all the installations on the system). */
|
||
unmaintained_path =
|
||
flatpak_find_unmaintained_extension_dir_if_exists (id, arch, branch, NULL);
|
||
if (unmaintained_path != NULL && deploy_data == NULL)
|
||
{
|
||
g_info ("Skipping related extension ‘%s’ because it is already "
|
||
"installed as an unmaintained extension in ‘%s’.",
|
||
id, flatpak_file_get_path_cached (unmaintained_path));
|
||
download = FALSE;
|
||
}
|
||
|
||
if (g_str_has_suffix (extension, ".Debug"))
|
||
{
|
||
/* debug files only updated if already installed */
|
||
if (deploy_data == NULL)
|
||
download = FALSE;
|
||
|
||
/* Always remove debug */
|
||
delete = TRUE;
|
||
}
|
||
|
||
if (g_str_has_suffix (extension, ".Locale"))
|
||
locale_subset = TRUE;
|
||
|
||
if (locale_subset)
|
||
{
|
||
extra_subpaths = flatpak_dir_get_locale_subpaths (self);
|
||
|
||
/* Always remove locale */
|
||
delete = TRUE;
|
||
}
|
||
|
||
subpaths = flatpak_subpaths_merge ((char **) old_subpaths, extra_subpaths);
|
||
|
||
rel = g_new0 (FlatpakRelated, 1);
|
||
rel->remote = g_strdup (remote);
|
||
rel->ref = flatpak_decomposed_ref (extension_ref);
|
||
rel->commit = g_strdup (checksum);
|
||
rel->subpaths = g_steal_pointer (&subpaths);
|
||
rel->download = download;
|
||
rel->delete = delete;
|
||
rel->auto_prune = auto_prune;
|
||
|
||
g_ptr_array_add (related, rel);
|
||
}
|
||
|
||
static GRegex *
|
||
flatpak_dir_get_mask_regexp (FlatpakDir *self)
|
||
{
|
||
GRegex *res = NULL;
|
||
|
||
G_LOCK (config_cache);
|
||
|
||
if (self->masked == NULL)
|
||
{
|
||
g_autofree char *masked = NULL;
|
||
|
||
masked = flatpak_dir_get_config (self, "masked", NULL);
|
||
if (masked)
|
||
{
|
||
g_auto(GStrv) patterns = g_strsplit (masked, ";", -1);
|
||
g_autoptr(GString) deny_regexp = g_string_new ("^(");
|
||
int i;
|
||
|
||
for (i = 0; patterns[i] != NULL; i++)
|
||
{
|
||
const char *pattern = patterns[i];
|
||
|
||
if (*pattern != 0)
|
||
{
|
||
g_autofree char *regexp = NULL;
|
||
|
||
regexp = flatpak_filter_glob_to_regexp (pattern, FALSE, NULL);
|
||
if (regexp)
|
||
{
|
||
if (i != 0)
|
||
g_string_append (deny_regexp, "|");
|
||
g_string_append (deny_regexp, regexp);
|
||
}
|
||
}
|
||
}
|
||
|
||
g_string_append (deny_regexp, ")$");
|
||
self->masked = g_regex_new (deny_regexp->str, G_REGEX_DOLLAR_ENDONLY|G_REGEX_RAW|G_REGEX_OPTIMIZE, G_REGEX_MATCH_ANCHORED, NULL);
|
||
}
|
||
}
|
||
|
||
if (self->masked)
|
||
res = g_regex_ref (self->masked);
|
||
|
||
G_UNLOCK (config_cache);
|
||
|
||
return res;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_ref_is_masked (FlatpakDir *self,
|
||
const char *ref)
|
||
{
|
||
g_autoptr(GRegex) masked = flatpak_dir_get_mask_regexp (self);
|
||
|
||
return !flatpak_filters_allow_ref (NULL, masked, ref);
|
||
}
|
||
|
||
static GRegex *
|
||
flatpak_dir_get_pin_regexp (FlatpakDir *self)
|
||
{
|
||
GRegex *res = NULL;
|
||
|
||
G_LOCK (config_cache);
|
||
|
||
if (self->pinned == NULL)
|
||
{
|
||
g_autofree char *pinned = NULL;
|
||
|
||
pinned = flatpak_dir_get_config (self, "pinned", NULL);
|
||
if (pinned)
|
||
{
|
||
g_auto(GStrv) patterns = g_strsplit (pinned, ";", -1);
|
||
g_autoptr(GString) deny_regexp = g_string_new ("^(");
|
||
int i;
|
||
|
||
for (i = 0; patterns[i] != NULL; i++)
|
||
{
|
||
const char *pattern = patterns[i];
|
||
|
||
if (*pattern != 0)
|
||
{
|
||
g_autofree char *regexp = NULL;
|
||
|
||
regexp = flatpak_filter_glob_to_regexp (pattern,
|
||
TRUE, /* only match runtimes */
|
||
NULL);
|
||
if (regexp)
|
||
{
|
||
if (i != 0)
|
||
g_string_append (deny_regexp, "|");
|
||
g_string_append (deny_regexp, regexp);
|
||
}
|
||
}
|
||
}
|
||
|
||
g_string_append (deny_regexp, ")$");
|
||
self->pinned = g_regex_new (deny_regexp->str, G_REGEX_DOLLAR_ENDONLY|G_REGEX_RAW|G_REGEX_OPTIMIZE, G_REGEX_MATCH_ANCHORED, NULL);
|
||
}
|
||
}
|
||
|
||
if (self->pinned)
|
||
res = g_regex_ref (self->pinned);
|
||
|
||
G_UNLOCK (config_cache);
|
||
|
||
return res;
|
||
}
|
||
|
||
gboolean
|
||
flatpak_dir_ref_is_pinned (FlatpakDir *self,
|
||
const char *ref)
|
||
{
|
||
g_autoptr(GRegex) pinned = flatpak_dir_get_pin_regexp (self);
|
||
|
||
return !flatpak_filters_allow_ref (NULL, pinned, ref);
|
||
}
|
||
|
||
GPtrArray *
|
||
flatpak_dir_find_remote_related_for_metadata (FlatpakDir *self,
|
||
FlatpakRemoteState *state,
|
||
FlatpakDecomposed *ref,
|
||
GKeyFile *metakey,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
int i;
|
||
g_autoptr(GPtrArray) related = g_ptr_array_new_with_free_func ((GDestroyNotify) flatpak_related_free);
|
||
g_autofree char *url = NULL;
|
||
g_auto(GStrv) groups = NULL;
|
||
g_autoptr(GRegex) masked = NULL;
|
||
g_autofree char *ref_arch = flatpak_decomposed_dup_arch (ref);
|
||
g_autofree char *ref_branch = flatpak_decomposed_dup_branch (ref);
|
||
|
||
if (!ostree_repo_remote_get_url (self->repo,
|
||
state->remote_name,
|
||
&url,
|
||
error))
|
||
return NULL;
|
||
|
||
if (*url == 0)
|
||
return g_steal_pointer (&related); /* Empty url, silently disables updates */
|
||
|
||
masked = flatpak_dir_get_mask_regexp (self);
|
||
|
||
groups = g_key_file_get_groups (metakey, NULL);
|
||
for (i = 0; groups[i] != NULL; i++)
|
||
{
|
||
char *tagged_extension;
|
||
|
||
if (g_str_has_prefix (groups[i], FLATPAK_METADATA_GROUP_PREFIX_EXTENSION) &&
|
||
*(tagged_extension = (groups[i] + strlen (FLATPAK_METADATA_GROUP_PREFIX_EXTENSION))) != 0)
|
||
{
|
||
g_autofree char *extension = NULL;
|
||
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);
|
||
gboolean subdirectories = g_key_file_get_boolean (metakey, groups[i],
|
||
FLATPAK_METADATA_KEY_SUBDIRECTORIES, NULL);
|
||
gboolean no_autodownload = g_key_file_get_boolean (metakey, groups[i],
|
||
FLATPAK_METADATA_KEY_NO_AUTODOWNLOAD, NULL);
|
||
g_autofree char *download_if = g_key_file_get_string (metakey, groups[i],
|
||
FLATPAK_METADATA_KEY_DOWNLOAD_IF, NULL);
|
||
g_autofree char *autoprune_unless = g_key_file_get_string (metakey, groups[i],
|
||
FLATPAK_METADATA_KEY_AUTOPRUNE_UNLESS, NULL);
|
||
gboolean autodelete = g_key_file_get_boolean (metakey, groups[i],
|
||
FLATPAK_METADATA_KEY_AUTODELETE, NULL);
|
||
gboolean locale_subset = g_key_file_get_boolean (metakey, groups[i],
|
||
FLATPAK_METADATA_KEY_LOCALE_SUBSET, NULL);
|
||
const char *default_branches[] = { NULL, NULL};
|
||
const char **branches;
|
||
int branch_i;
|
||
|
||
/* Parse actual extension name */
|
||
flatpak_parse_extension_with_tag (tagged_extension, &extension, NULL);
|
||
|
||
if (versions)
|
||
branches = (const char **) versions;
|
||
else
|
||
{
|
||
if (version)
|
||
default_branches[0] = version;
|
||
else
|
||
default_branches[0] = ref_branch;
|
||
branches = default_branches;
|
||
}
|
||
|
||
for (branch_i = 0; branches[branch_i] != NULL; branch_i++)
|
||
{
|
||
g_autoptr(FlatpakDecomposed) extension_ref = NULL;
|
||
g_autofree char *checksum = NULL;
|
||
const char *branch = branches[branch_i];
|
||
|
||
extension_ref = flatpak_decomposed_new_from_parts (FLATPAK_KINDS_RUNTIME,
|
||
extension, ref_arch, branch, NULL);
|
||
if (extension_ref == NULL)
|
||
continue;
|
||
|
||
if (flatpak_remote_state_lookup_ref (state, flatpak_decomposed_get_ref (extension_ref), &checksum, NULL, NULL, NULL, NULL))
|
||
{
|
||
if (flatpak_filters_allow_ref (NULL, masked, flatpak_decomposed_get_ref (extension_ref)))
|
||
add_related (self, related, state->remote_name, extension, extension_ref, checksum,
|
||
no_autodownload, download_if, autoprune_unless, autodelete, locale_subset);
|
||
}
|
||
else if (subdirectories)
|
||
{
|
||
g_autoptr(GPtrArray) subref_refs = flatpak_remote_state_match_subrefs (state, extension_ref);
|
||
for (int j = 0; j < subref_refs->len; j++)
|
||
{
|
||
FlatpakDecomposed *subref_ref = g_ptr_array_index (subref_refs, j);
|
||
g_autofree char *subref_checksum = NULL;
|
||
|
||
if (flatpak_remote_state_lookup_ref (state, flatpak_decomposed_get_ref (subref_ref),
|
||
&subref_checksum, NULL, NULL, NULL, NULL) &&
|
||
flatpak_filters_allow_ref (NULL, masked, flatpak_decomposed_get_ref (subref_ref)))
|
||
add_related (self, related, state->remote_name, extension, subref_ref, subref_checksum,
|
||
no_autodownload, download_if, autoprune_unless, autodelete, locale_subset);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return g_steal_pointer (&related);
|
||
}
|
||
|
||
GPtrArray *
|
||
flatpak_dir_find_remote_related (FlatpakDir *self,
|
||
FlatpakRemoteState *state,
|
||
FlatpakDecomposed *ref,
|
||
gboolean use_installed_metadata,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autofree char *metadata = NULL;
|
||
g_autoptr(GKeyFile) metakey = g_key_file_new ();
|
||
g_autoptr(GPtrArray) related = g_ptr_array_new_with_free_func ((GDestroyNotify) flatpak_related_free);
|
||
g_autofree char *url = NULL;
|
||
|
||
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
||
return NULL;
|
||
|
||
if (!ostree_repo_remote_get_url (self->repo,
|
||
state->remote_name,
|
||
&url,
|
||
error))
|
||
return NULL;
|
||
|
||
if (*url == 0)
|
||
return g_steal_pointer (&related); /* Empty url, silently disables updates */
|
||
|
||
if (use_installed_metadata)
|
||
{
|
||
g_autoptr(GFile) deploy_dir = NULL;
|
||
g_autoptr(GBytes) deploy_data = NULL;
|
||
g_autoptr(GFile) metadata_file = NULL;
|
||
|
||
deploy_dir = flatpak_dir_get_if_deployed (self, ref, NULL, cancellable);
|
||
if (deploy_dir == NULL)
|
||
{
|
||
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED,
|
||
_("%s not installed"), flatpak_decomposed_get_ref (ref));
|
||
return NULL;
|
||
}
|
||
|
||
deploy_data = flatpak_load_deploy_data (deploy_dir, ref, self->repo, FLATPAK_DEPLOY_VERSION_ANY, cancellable, error);
|
||
if (deploy_data == NULL)
|
||
return NULL;
|
||
|
||
metadata_file = g_file_get_child (deploy_dir, "metadata");
|
||
if (!g_file_load_contents (metadata_file, cancellable, &metadata, NULL, NULL, NULL))
|
||
{
|
||
g_info ("No metadata in local deploy");
|
||
/* No metadata => no related, but no error */
|
||
}
|
||
}
|
||
else
|
||
flatpak_remote_state_load_data (state, flatpak_decomposed_get_ref (ref),
|
||
NULL, NULL, &metadata,
|
||
NULL);
|
||
|
||
if (metadata != NULL &&
|
||
g_key_file_load_from_data (metakey, metadata, -1, 0, NULL))
|
||
{
|
||
g_ptr_array_unref (related);
|
||
related = flatpak_dir_find_remote_related_for_metadata (self, state, ref, metakey, cancellable, error);
|
||
}
|
||
|
||
return g_steal_pointer (&related);
|
||
}
|
||
|
||
static GHashTable *
|
||
local_match_prefix (FlatpakDir *self,
|
||
FlatpakDecomposed *extension_ref,
|
||
const char *remote,
|
||
GHashTable *decomposed_to_search)
|
||
{
|
||
GHashTable *matches = g_hash_table_new_full ((GHashFunc)flatpak_decomposed_hash,
|
||
(GEqualFunc)flatpak_decomposed_equal,
|
||
(GDestroyNotify)flatpak_decomposed_unref,
|
||
NULL);
|
||
g_autofree char *id = NULL;
|
||
g_autofree char *arch = NULL;
|
||
g_autofree char *branch = NULL;
|
||
g_autofree char *id_prefix = NULL;
|
||
|
||
id = flatpak_decomposed_dup_id (extension_ref);
|
||
arch = flatpak_decomposed_dup_arch (extension_ref);
|
||
branch = flatpak_decomposed_dup_branch (extension_ref);
|
||
|
||
id_prefix = g_strconcat (id, ".", NULL);
|
||
|
||
if (decomposed_to_search)
|
||
{
|
||
GHashTableIter hash_iter;
|
||
gpointer key;
|
||
|
||
g_hash_table_iter_init (&hash_iter, decomposed_to_search);
|
||
while (g_hash_table_iter_next (&hash_iter, &key, NULL))
|
||
{
|
||
FlatpakDecomposed *to_test = key;
|
||
|
||
if (flatpak_decomposed_get_kind (extension_ref) != flatpak_decomposed_get_kind (to_test))
|
||
continue;
|
||
|
||
/* Must match type, arch, branch */
|
||
if (!flatpak_decomposed_is_arch (to_test, arch) ||
|
||
!flatpak_decomposed_is_branch (to_test, branch))
|
||
continue;
|
||
|
||
/* But only prefix of id */
|
||
if (!flatpak_decomposed_id_has_prefix (to_test, id_prefix))
|
||
continue;
|
||
|
||
g_hash_table_add (matches, flatpak_decomposed_ref (to_test));
|
||
}
|
||
}
|
||
|
||
/* Also check deploys. In case remote-delete --force is run, we can end up
|
||
* with a deploy without a corresponding ref in the repo. */
|
||
flatpak_dir_collect_deployed_refs (self, flatpak_decomposed_get_kind_str (extension_ref),
|
||
id_prefix, arch, branch, matches, NULL, NULL);
|
||
|
||
return matches;
|
||
}
|
||
|
||
/* Finds all the locally installed ref related to ref, if remote_name is set it is limited to refs from that remote */
|
||
GPtrArray *
|
||
flatpak_dir_find_local_related_for_metadata (FlatpakDir *self,
|
||
FlatpakDecomposed *ref,
|
||
const char *remote_name, /* nullable */
|
||
GKeyFile *metakey,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
int i;
|
||
g_autoptr(GPtrArray) related = g_ptr_array_new_with_free_func ((GDestroyNotify) flatpak_related_free);
|
||
g_autoptr(GHashTable) all_decomposed_for_remote = NULL;
|
||
g_auto(GStrv) groups = NULL;
|
||
g_autofree char *ref_arch = flatpak_decomposed_dup_arch (ref);
|
||
g_autofree char *ref_branch = flatpak_decomposed_dup_branch (ref);
|
||
|
||
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
||
return NULL;
|
||
|
||
groups = g_key_file_get_groups (metakey, NULL);
|
||
for (i = 0; groups[i] != NULL; i++)
|
||
{
|
||
char *tagged_extension;
|
||
|
||
if (g_str_has_prefix (groups[i], FLATPAK_METADATA_GROUP_PREFIX_EXTENSION) &&
|
||
*(tagged_extension = (groups[i] + strlen (FLATPAK_METADATA_GROUP_PREFIX_EXTENSION))) != 0)
|
||
{
|
||
g_autofree char *extension = NULL;
|
||
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);
|
||
gboolean subdirectories = g_key_file_get_boolean (metakey, groups[i],
|
||
FLATPAK_METADATA_KEY_SUBDIRECTORIES, NULL);
|
||
gboolean no_autodownload = g_key_file_get_boolean (metakey, groups[i],
|
||
FLATPAK_METADATA_KEY_NO_AUTODOWNLOAD, NULL);
|
||
g_autofree char *download_if = g_key_file_get_string (metakey, groups[i],
|
||
FLATPAK_METADATA_KEY_DOWNLOAD_IF, NULL);
|
||
g_autofree char *autoprune_unless = g_key_file_get_string (metakey, groups[i],
|
||
FLATPAK_METADATA_KEY_AUTOPRUNE_UNLESS, NULL);
|
||
gboolean autodelete = g_key_file_get_boolean (metakey, groups[i],
|
||
FLATPAK_METADATA_KEY_AUTODELETE, NULL);
|
||
gboolean locale_subset = g_key_file_get_boolean (metakey, groups[i],
|
||
FLATPAK_METADATA_KEY_LOCALE_SUBSET, NULL);
|
||
const char *default_branches[] = { NULL, NULL};
|
||
const char **branches;
|
||
int branch_i;
|
||
|
||
/* Parse actual extension name */
|
||
flatpak_parse_extension_with_tag (tagged_extension, &extension, NULL);
|
||
|
||
if (versions)
|
||
branches = (const char **) versions;
|
||
else
|
||
{
|
||
if (version)
|
||
default_branches[0] = version;
|
||
else
|
||
default_branches[0] = ref_branch;
|
||
branches = default_branches;
|
||
}
|
||
|
||
for (branch_i = 0; branches[branch_i] != NULL; branch_i++)
|
||
{
|
||
g_autoptr(FlatpakDecomposed) extension_ref = NULL;
|
||
g_autofree char *checksum = NULL;
|
||
g_autoptr(GBytes) deploy_data = NULL;
|
||
const char *branch = branches[branch_i];
|
||
|
||
extension_ref = flatpak_decomposed_new_from_parts (FLATPAK_KINDS_RUNTIME,
|
||
extension, ref_arch, branch, NULL);
|
||
if (extension_ref == NULL)
|
||
continue;
|
||
|
||
if (remote_name != NULL &&
|
||
flatpak_repo_resolve_rev (self->repo,
|
||
NULL,
|
||
remote_name,
|
||
flatpak_decomposed_get_ref (extension_ref),
|
||
FALSE,
|
||
&checksum,
|
||
NULL,
|
||
NULL))
|
||
{
|
||
add_related (self, related, remote_name, extension, extension_ref,
|
||
checksum, no_autodownload, download_if, autoprune_unless, autodelete, locale_subset);
|
||
}
|
||
else if ((deploy_data = flatpak_dir_get_deploy_data (self, extension_ref,
|
||
FLATPAK_DEPLOY_VERSION_ANY,
|
||
NULL, NULL)) != NULL &&
|
||
(remote_name == NULL || g_strcmp0 (flatpak_deploy_data_get_origin (deploy_data), remote_name) == 0))
|
||
{
|
||
/* Here we're including extensions that are deployed but might
|
||
* not have a ref in the repo, as happens with remote-delete
|
||
* --force
|
||
*/
|
||
checksum = g_strdup (flatpak_deploy_data_get_commit (deploy_data));
|
||
add_related (self, related,
|
||
flatpak_deploy_data_get_origin (deploy_data),
|
||
extension, extension_ref, checksum,
|
||
no_autodownload, download_if, autoprune_unless, autodelete, locale_subset);
|
||
}
|
||
else if (subdirectories)
|
||
{
|
||
g_autoptr(GHashTable) matches = NULL;
|
||
|
||
if (!all_decomposed_for_remote)
|
||
{
|
||
g_autoptr(GHashTable) refs = NULL;
|
||
g_autofree char *list_prefix = NULL;
|
||
if (remote_name != NULL)
|
||
list_prefix = g_strdup_printf ("%s:", remote_name);
|
||
|
||
if (ostree_repo_list_refs (self->repo, list_prefix, &refs, NULL, NULL))
|
||
{
|
||
GHashTableIter iter;
|
||
gpointer key;
|
||
|
||
all_decomposed_for_remote = g_hash_table_new_full (
|
||
(GHashFunc)flatpak_decomposed_hash,
|
||
(GEqualFunc)flatpak_decomposed_equal,
|
||
(GDestroyNotify)flatpak_decomposed_unref,
|
||
NULL);
|
||
|
||
g_hash_table_iter_init (&iter, refs);
|
||
while (g_hash_table_iter_next (&iter, &key, NULL))
|
||
{
|
||
const char *refspec = key;
|
||
g_autoptr(FlatpakDecomposed) decomposed = NULL;
|
||
|
||
decomposed = flatpak_decomposed_new_from_refspec (refspec, NULL);
|
||
if (decomposed != NULL)
|
||
g_hash_table_add (all_decomposed_for_remote, g_steal_pointer (&decomposed));
|
||
}
|
||
}
|
||
}
|
||
|
||
matches = local_match_prefix (self, extension_ref, remote_name, all_decomposed_for_remote);
|
||
GLNX_HASH_TABLE_FOREACH (matches, FlatpakDecomposed *, match)
|
||
{
|
||
g_autofree char *match_checksum = NULL;
|
||
g_autoptr(GBytes) match_deploy_data = NULL;
|
||
|
||
if (remote_name != NULL &&
|
||
flatpak_repo_resolve_rev (self->repo,
|
||
NULL,
|
||
remote_name,
|
||
flatpak_decomposed_get_ref (match),
|
||
FALSE,
|
||
&match_checksum,
|
||
NULL,
|
||
NULL))
|
||
{
|
||
add_related (self, related, remote_name, extension, match, match_checksum,
|
||
no_autodownload, download_if, autoprune_unless, autodelete, locale_subset);
|
||
}
|
||
else if ((match_deploy_data = flatpak_dir_get_deploy_data (self, match,
|
||
FLATPAK_DEPLOY_VERSION_ANY,
|
||
NULL, NULL)) != NULL &&
|
||
(remote_name == NULL || g_strcmp0 (flatpak_deploy_data_get_origin (match_deploy_data), remote_name) == 0))
|
||
{
|
||
/* Here again we're including extensions that are deployed but might
|
||
* not have a ref in the repo
|
||
*/
|
||
match_checksum = g_strdup (flatpak_deploy_data_get_commit (match_deploy_data));
|
||
add_related (self, related,
|
||
flatpak_deploy_data_get_origin (match_deploy_data),
|
||
extension, match, match_checksum,
|
||
no_autodownload, download_if, autoprune_unless, autodelete, locale_subset);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return g_steal_pointer (&related);
|
||
}
|
||
|
||
|
||
GPtrArray *
|
||
flatpak_dir_find_local_related (FlatpakDir *self,
|
||
FlatpakDecomposed *ref,
|
||
const char *remote_name,
|
||
gboolean deployed,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GFile) deploy_dir = NULL;
|
||
g_autoptr(GBytes) deploy_data = NULL;
|
||
g_autoptr(GFile) metadata = NULL;
|
||
g_autofree char *metadata_contents = NULL;
|
||
g_autoptr(GKeyFile) metakey = g_key_file_new ();
|
||
g_autoptr(GPtrArray) related = NULL;
|
||
|
||
if (!flatpak_dir_ensure_repo (self, cancellable, error))
|
||
return NULL;
|
||
|
||
if (deployed)
|
||
{
|
||
deploy_dir = flatpak_dir_get_if_deployed (self, ref, NULL, cancellable);
|
||
if (deploy_dir == NULL)
|
||
{
|
||
g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED,
|
||
_("%s not installed"), flatpak_decomposed_get_ref (ref));
|
||
return NULL;
|
||
}
|
||
|
||
deploy_data = flatpak_load_deploy_data (deploy_dir, ref, self->repo, FLATPAK_DEPLOY_VERSION_ANY, cancellable, error);
|
||
if (deploy_data == NULL)
|
||
return NULL;
|
||
|
||
if (flatpak_deploy_data_get_extension_of (deploy_data) == NULL)
|
||
{
|
||
metadata = g_file_get_child (deploy_dir, "metadata");
|
||
if (!g_file_load_contents (metadata, cancellable, &metadata_contents, NULL, NULL, NULL))
|
||
{
|
||
g_info ("No metadata in local deploy");
|
||
/* No metadata => no related, but no error */
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
g_autofree char *checksum = NULL;
|
||
g_autoptr(GVariant) commit_data = flatpak_dir_read_latest_commit (self, remote_name, ref, &checksum, NULL, NULL);
|
||
if (commit_data)
|
||
{
|
||
g_autoptr(GVariant) commit_metadata = g_variant_get_child_value (commit_data, 0);
|
||
g_variant_lookup (commit_metadata, "xa.metadata", "s", &metadata_contents);
|
||
if (metadata_contents == NULL)
|
||
g_info ("No xa.metadata in local commit %s ref %s", checksum, flatpak_decomposed_get_ref (ref));
|
||
}
|
||
}
|
||
|
||
if (metadata_contents &&
|
||
g_key_file_load_from_data (metakey, metadata_contents, -1, 0, NULL))
|
||
related = flatpak_dir_find_local_related_for_metadata (self, ref, remote_name, metakey, cancellable, error);
|
||
else
|
||
related = g_ptr_array_new_with_free_func ((GDestroyNotify) flatpak_related_free);
|
||
|
||
return g_steal_pointer (&related);
|
||
}
|
||
|
||
FlatpakDecomposed *
|
||
flatpak_dir_get_remote_auto_install_authenticator_ref (FlatpakDir *self,
|
||
const char *remote_name)
|
||
{
|
||
g_autofree char *authenticator_name = NULL;
|
||
g_autoptr(FlatpakDecomposed) ref = NULL;
|
||
|
||
authenticator_name = flatpak_dir_get_remote_install_authenticator_name (self, remote_name);
|
||
if (authenticator_name != NULL)
|
||
{
|
||
g_autoptr(GError) local_error = NULL;
|
||
ref = flatpak_decomposed_new_from_parts (FLATPAK_KINDS_APP, authenticator_name, flatpak_get_arch (), "autoinstall", &local_error);
|
||
if (ref == NULL)
|
||
g_info ("Invalid authenticator ref: %s\n", local_error->message);
|
||
}
|
||
|
||
return g_steal_pointer (&ref);
|
||
}
|
||
|
||
static int
|
||
cmpstringp (const void *p1, const void *p2)
|
||
{
|
||
return strcmp (*(char * const *) p1, *(char * const *) p2);
|
||
}
|
||
|
||
static char **
|
||
sort_strv (char **strv)
|
||
{
|
||
qsort (strv, g_strv_length (strv), sizeof (const char *), cmpstringp);
|
||
return strv;
|
||
}
|
||
|
||
static char **
|
||
flatpak_dir_get_config_strv (FlatpakDir *self, char *key)
|
||
{
|
||
GKeyFile *config = flatpak_dir_get_repo_config (self);
|
||
g_auto(GStrv) lang = NULL;
|
||
|
||
if (config)
|
||
{
|
||
if (g_key_file_has_key (config, "core", key, NULL))
|
||
{
|
||
lang = g_key_file_get_string_list (config, "core", key, NULL, NULL);
|
||
return g_steal_pointer (&lang);
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
char **
|
||
flatpak_dir_get_default_locales (FlatpakDir *self)
|
||
{
|
||
g_auto(GStrv) extra_languages = NULL;
|
||
const GPtrArray *langs;
|
||
|
||
extra_languages = flatpak_dir_get_config_strv (self, "xa.extra-languages");
|
||
|
||
if (flatpak_dir_is_user (self))
|
||
{
|
||
g_auto(GStrv) locale_langs = flatpak_get_current_locale_langs ();
|
||
g_auto(GStrv) merged = NULL;
|
||
|
||
langs = flatpak_get_user_locales ();
|
||
merged = flatpak_strv_merge (extra_languages, (char **) langs->pdata);
|
||
|
||
return sort_strv (flatpak_strv_merge (merged, locale_langs));
|
||
}
|
||
|
||
/* Then get the system default locales */
|
||
langs = flatpak_get_system_locales ();
|
||
|
||
return sort_strv (flatpak_strv_merge (extra_languages, (char **) langs->pdata));
|
||
}
|
||
|
||
char **
|
||
flatpak_dir_get_default_locale_languages (FlatpakDir *self)
|
||
{
|
||
g_auto(GStrv) extra_languages = NULL;
|
||
const GPtrArray *langs;
|
||
int i;
|
||
|
||
extra_languages = flatpak_dir_get_config_strv (self, "xa.extra-languages");
|
||
for (i = 0; extra_languages != NULL && extra_languages[i] != NULL; i++)
|
||
{
|
||
/* Strip the locale, modifier or codeset, if present. */
|
||
gchar *match = strpbrk (extra_languages[i], "._@");
|
||
if (match != NULL)
|
||
*match = '\0';
|
||
}
|
||
|
||
if (flatpak_dir_is_user (self))
|
||
{
|
||
g_auto(GStrv) locale_langs = flatpak_get_current_locale_langs ();
|
||
g_auto(GStrv) merged = NULL;
|
||
|
||
langs = flatpak_get_user_locales ();
|
||
merged = flatpak_strv_merge (extra_languages, (char **) langs->pdata);
|
||
|
||
return sort_strv (flatpak_strv_merge (merged, locale_langs));
|
||
}
|
||
|
||
/* Then get the system default locales */
|
||
langs = flatpak_get_system_locales ();
|
||
|
||
return sort_strv (flatpak_strv_merge (extra_languages, (char **) langs->pdata));
|
||
}
|
||
|
||
char **
|
||
flatpak_dir_get_locales (FlatpakDir *self)
|
||
{
|
||
char **langs = NULL;
|
||
|
||
/* Fetch the list of languages specified by xa.languages - if this key is empty,
|
||
* this would mean that all languages are accepted. You can read the man for the
|
||
* flatpak-config section for more info.
|
||
*/
|
||
langs = flatpak_dir_get_config_strv (self, "xa.languages");
|
||
if (langs)
|
||
return sort_strv (langs);
|
||
|
||
return flatpak_dir_get_default_locales (self);
|
||
}
|
||
|
||
|
||
char **
|
||
flatpak_dir_get_locale_languages (FlatpakDir *self)
|
||
{
|
||
char **langs = NULL;
|
||
|
||
/* Fetch the list of languages specified by xa.languages - if this key is empty,
|
||
* this would mean that all languages are accepted. You can read the man for the
|
||
* flatpak-config section for more info.
|
||
*/
|
||
langs = flatpak_dir_get_config_strv (self, "xa.languages");
|
||
if (langs)
|
||
return sort_strv (langs);
|
||
|
||
return flatpak_dir_get_default_locale_languages (self);
|
||
}
|
||
|
||
char **
|
||
flatpak_dir_get_locale_subpaths (FlatpakDir *self)
|
||
{
|
||
char **subpaths = flatpak_dir_get_locale_languages (self);
|
||
int i;
|
||
|
||
/* Convert languages to paths */
|
||
for (i = 0; subpaths[i] != NULL; i++)
|
||
{
|
||
char *lang = subpaths[i];
|
||
/* For backwards compat with old xa.languages we support the configuration having slashes already */
|
||
if (*lang != '/')
|
||
{
|
||
subpaths[i] = g_strconcat ("/", lang, NULL);
|
||
g_free (lang);
|
||
}
|
||
}
|
||
return subpaths;
|
||
}
|
||
|
||
void
|
||
flatpak_dir_set_source_pid (FlatpakDir *self,
|
||
pid_t pid)
|
||
{
|
||
self->source_pid = pid;
|
||
}
|
||
|
||
pid_t
|
||
flatpak_dir_get_source_pid (FlatpakDir *self)
|
||
{
|
||
return self->source_pid;
|
||
}
|
||
|
||
static void
|
||
(flatpak_dir_log) (FlatpakDir * self,
|
||
const char *file,
|
||
int line,
|
||
const char *func,
|
||
const char *source, /* overrides self->name */
|
||
const char *change,
|
||
const char *remote,
|
||
const char *ref,
|
||
const char *commit,
|
||
const char *old_commit,
|
||
const char *url,
|
||
const char *format,
|
||
...)
|
||
{
|
||
#ifdef HAVE_LIBSYSTEMD
|
||
const char *installation = source ? source : flatpak_dir_get_name_cached (self);
|
||
pid_t source_pid = flatpak_dir_get_source_pid (self);
|
||
char message[1024];
|
||
int len;
|
||
va_list args;
|
||
|
||
len = g_snprintf (message, sizeof (message), "%s: ", installation);
|
||
|
||
va_start (args, format);
|
||
g_vsnprintf (message + len, sizeof (message) - len, format, args);
|
||
va_end (args);
|
||
|
||
/* See systemd.journal-fields(7) for the meaning of the
|
||
* standard fields we use, in particular OBJECT_PID
|
||
*/
|
||
sd_journal_send ("MESSAGE_ID=" FLATPAK_MESSAGE_ID,
|
||
"PRIORITY=5",
|
||
"OBJECT_PID=%d", source_pid,
|
||
"CODE_FILE=%s", file,
|
||
"CODE_LINE=%d", line,
|
||
"CODE_FUNC=%s", func,
|
||
"MESSAGE=%s", message,
|
||
/* custom fields below */
|
||
"FLATPAK_VERSION=" PACKAGE_VERSION,
|
||
"INSTALLATION=%s", installation,
|
||
"OPERATION=%s", change,
|
||
"REMOTE=%s", remote ? remote : "",
|
||
"REF=%s", ref ? ref : "",
|
||
"COMMIT=%s", commit ? commit : "",
|
||
"OLD_COMMIT=%s", old_commit ? old_commit : "",
|
||
"URL=%s", url ? url : "",
|
||
NULL);
|
||
#endif
|
||
}
|
||
|
||
/* Delete refs that are in refs/mirrors/ rather than refs/remotes/ to prevent
|
||
* disk space from leaking. See https://github.com/flatpak/flatpak/issues/3222
|
||
* The caller is responsible for ensuring that @dir has a repo, and for pruning
|
||
* the repo after calling this function to actually free the disk space.
|
||
*/
|
||
gboolean
|
||
flatpak_dir_delete_mirror_refs (FlatpakDir *self,
|
||
gboolean dry_run,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GHashTable) collection_refs = NULL; /* (element-type OstreeCollectionRef utf8) */
|
||
g_autoptr(GPtrArray) ignore_collections = g_ptr_array_new_with_free_func (g_free); /* (element-type utf8) */
|
||
g_auto(GStrv) remotes = NULL;
|
||
const char *repo_collection_id;
|
||
OstreeRepo *repo;
|
||
int i;
|
||
|
||
/* Generally a flatpak repo should not have its own collection ID set, but
|
||
* check just in case flatpak is being run on a server for some reason. When
|
||
* a repo has a collection ID set, its own refs from refs/heads/ will be
|
||
* listed by the ostree_repo_list_collection_refs() call below, and we need
|
||
* to be sure not to delete them. There would be no reason to install from a
|
||
* server to itself, so we don't expect refs matching repo_collection_id to
|
||
* be in refs/mirrors/.
|
||
*/
|
||
repo = flatpak_dir_get_repo (self);
|
||
repo_collection_id = ostree_repo_get_collection_id (repo);
|
||
if (repo_collection_id != NULL)
|
||
g_ptr_array_add (ignore_collections, g_strdup (repo_collection_id));
|
||
|
||
/* Check also for any disabled remotes and ignore any associated
|
||
* collection-refs; in the case of Endless this would be the remote used for
|
||
* OS updates which Flatpak shouldn't touch.
|
||
*/
|
||
remotes = ostree_repo_remote_list (repo, NULL);
|
||
for (i = 0; remotes != NULL && remotes[i] != NULL; i++)
|
||
{
|
||
g_autofree char *remote_collection_id = NULL;
|
||
|
||
if (!flatpak_dir_get_remote_disabled (self, remotes[i]))
|
||
continue;
|
||
remote_collection_id = flatpak_dir_get_remote_collection_id (self, remotes[i]);
|
||
if (remote_collection_id != NULL)
|
||
g_ptr_array_add (ignore_collections, g_steal_pointer (&remote_collection_id));
|
||
}
|
||
g_ptr_array_add (ignore_collections, NULL);
|
||
|
||
if (!ostree_repo_list_collection_refs (repo, NULL, &collection_refs,
|
||
OSTREE_REPO_LIST_REFS_EXT_EXCLUDE_REMOTES,
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
/* Now delete any collection-refs which are in refs/mirrors/, were created by
|
||
* Flatpak, and don't belong to a disabled remote.
|
||
*/
|
||
GLNX_HASH_TABLE_FOREACH (collection_refs, const OstreeCollectionRef *, c_r)
|
||
{
|
||
if (g_strv_contains ((const char * const *)ignore_collections->pdata, c_r->collection_id))
|
||
{
|
||
g_info ("Ignoring collection-ref (%s, %s) since its remote is disabled or it matches the repo collection ID",
|
||
c_r->collection_id, c_r->ref_name);
|
||
continue;
|
||
}
|
||
|
||
/* Only delete refs which Flatpak created; the repo may have other
|
||
* users. We could check only for refs that come from configured
|
||
* remotes, but that would not cover the case of if a remote was
|
||
* deleted.
|
||
*/
|
||
if (flatpak_is_app_runtime_or_appstream_ref (c_r->ref_name) ||
|
||
g_strcmp0 (c_r->ref_name, OSTREE_REPO_METADATA_REF) == 0)
|
||
{
|
||
if (dry_run)
|
||
g_print (_("Skipping deletion of mirror ref (%s, %s)…\n"), c_r->collection_id, c_r->ref_name);
|
||
else
|
||
{
|
||
if (!ostree_repo_set_collection_ref_immediate (repo, c_r, NULL, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
}
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
static gboolean
|
||
dir_get_metadata (FlatpakDir *dir,
|
||
FlatpakDecomposed *ref,
|
||
GKeyFile **out_metakey)
|
||
{
|
||
g_autoptr(GFile) deploy_dir = NULL;
|
||
g_autoptr(GKeyFile) metakey = NULL;
|
||
g_autoptr(GFile) metadata = NULL;
|
||
g_autofree char *metadata_contents = NULL;
|
||
gsize metadata_size;
|
||
|
||
deploy_dir = flatpak_dir_get_if_deployed (dir, ref, NULL, NULL);
|
||
if (deploy_dir == NULL)
|
||
return FALSE;
|
||
|
||
metadata = g_file_get_child (deploy_dir, "metadata");
|
||
if (!g_file_load_contents (metadata, NULL, &metadata_contents, &metadata_size, NULL, NULL))
|
||
return FALSE;
|
||
|
||
metakey = g_key_file_new ();
|
||
if (!g_key_file_load_from_data (metakey, metadata_contents, metadata_size, 0, NULL))
|
||
return FALSE;
|
||
|
||
*out_metakey = g_steal_pointer (&metakey);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
maybe_get_metakey (FlatpakDir *dir,
|
||
FlatpakDir *shadowing_dir,
|
||
FlatpakDecomposed *ref,
|
||
GHashTable *metadata_injection,
|
||
GKeyFile **out_metakey,
|
||
gboolean *out_ref_is_shadowed,
|
||
char **out_dir_name)
|
||
{
|
||
if (shadowing_dir &&
|
||
dir_get_metadata (shadowing_dir, ref, out_metakey))
|
||
{
|
||
*out_ref_is_shadowed = TRUE;
|
||
*out_dir_name = g_strdup_printf (" (%s)", flatpak_dir_get_name_cached (shadowing_dir));
|
||
return TRUE;
|
||
}
|
||
|
||
if (metadata_injection != NULL)
|
||
{
|
||
GKeyFile *injected_metakey = g_hash_table_lookup (metadata_injection, flatpak_decomposed_get_ref (ref));
|
||
if (injected_metakey != NULL)
|
||
{
|
||
*out_ref_is_shadowed = FALSE;
|
||
*out_metakey = g_key_file_ref (injected_metakey);
|
||
*out_dir_name = g_strdup ("");
|
||
return TRUE;
|
||
}
|
||
}
|
||
|
||
if (dir_get_metadata (dir, ref, out_metakey))
|
||
{
|
||
*out_ref_is_shadowed = FALSE;
|
||
*out_dir_name = g_strdup_printf (" (%s)", flatpak_dir_get_name_cached (dir));
|
||
return TRUE;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static void
|
||
queue_ref_for_analysis (FlatpakDecomposed *ref,
|
||
const char *arch,
|
||
GHashTable *analyzed_refs,
|
||
GQueue *refs_to_analyze)
|
||
{
|
||
if (arch != NULL && !flatpak_decomposed_is_arch (ref, arch))
|
||
return;
|
||
|
||
if (g_hash_table_lookup (analyzed_refs, ref) != NULL)
|
||
return;
|
||
|
||
g_hash_table_add (analyzed_refs, flatpak_decomposed_ref (ref));
|
||
g_queue_push_tail (refs_to_analyze, ref); /* owned by analyzed_refs */
|
||
}
|
||
|
||
/* This traverses from all the "root" refs and into for any recursive dependencies in @self
|
||
* that they use. In the regular case we just consider the @self installation,
|
||
* but we can also handle the case where another directory "shadows" self. For example
|
||
* we might be looking for used refs in the "system" dir, and the "user" dir is
|
||
* shadowing it, meaning that if a ref is installed in the user dir it is considered used
|
||
* from there instead of @self. So, analyzed refs from @shadowing_dir are *not* put
|
||
* in @used_ref (although their dependencies may).
|
||
*
|
||
* Notes:
|
||
* The "root" refs come from @shadowing_dir if not %NULL and @self otherwise.
|
||
* refs_to_exclude, and metadata_injection both only affect @self, not @shadowing_dir
|
||
*/
|
||
static GHashTable *
|
||
find_used_refs (FlatpakDir *self,
|
||
FlatpakDir *shadowing_dir, /* nullable */
|
||
const char *arch,
|
||
GHashTable *metadata_injection,
|
||
GHashTable *refs_to_exclude,
|
||
GHashTable *used_refs, /* This is filled in */
|
||
GHashTable *autopruned_refs, /* This is filled in */
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GPtrArray) root_app_refs = NULL;
|
||
g_autoptr(GPtrArray) root_runtime_refs = NULL;
|
||
g_autoptr(GHashTable) analyzed_refs = NULL;
|
||
g_autoptr(GQueue) refs_to_analyze = NULL;
|
||
FlatpakDir *root_ref_dir;
|
||
FlatpakDecomposed *ref_to_analyze;
|
||
|
||
refs_to_analyze = g_queue_new ();
|
||
analyzed_refs = g_hash_table_new_full ((GHashFunc)flatpak_decomposed_hash, (GEqualFunc)flatpak_decomposed_equal, (GDestroyNotify)flatpak_decomposed_unref, NULL);
|
||
|
||
if (shadowing_dir)
|
||
root_ref_dir = shadowing_dir;
|
||
else
|
||
root_ref_dir = self;
|
||
|
||
root_app_refs = flatpak_dir_list_refs (root_ref_dir, FLATPAK_KINDS_APP, cancellable, error);
|
||
if (root_app_refs == NULL)
|
||
return NULL;
|
||
|
||
for (int i = 0; i < root_app_refs->len; i++)
|
||
{
|
||
FlatpakDecomposed *root_app_ref = g_ptr_array_index (root_app_refs, i);
|
||
queue_ref_for_analysis (root_app_ref, arch, analyzed_refs, refs_to_analyze);
|
||
}
|
||
|
||
root_runtime_refs = flatpak_dir_list_refs (root_ref_dir, FLATPAK_KINDS_RUNTIME, cancellable, error);
|
||
if (root_runtime_refs == NULL)
|
||
return NULL;
|
||
|
||
for (int i = 0; i < root_runtime_refs->len; i++)
|
||
{
|
||
FlatpakDecomposed *root_runtime_ref = g_ptr_array_index (root_runtime_refs, i);
|
||
/* Consider all shadow dir runtimes as roots because we don't really do full analysis for shadowing_dir.
|
||
* For example a system installed app could end up using the user version of a runtime, which in turn
|
||
* uses a system gl extension.
|
||
*
|
||
* However, for non-shadowed runtime refs, only pinned ones are roots */
|
||
if (root_ref_dir == shadowing_dir)
|
||
{
|
||
queue_ref_for_analysis (root_runtime_ref, arch, analyzed_refs, refs_to_analyze);
|
||
}
|
||
else if (flatpak_dir_ref_is_pinned (root_ref_dir, flatpak_decomposed_get_ref (root_runtime_ref)))
|
||
{
|
||
g_debug ("%s: Treating %s as used since it's pinned",
|
||
G_STRFUNC, flatpak_decomposed_get_ref (root_runtime_ref));
|
||
queue_ref_for_analysis (root_runtime_ref, arch, analyzed_refs, refs_to_analyze);
|
||
}
|
||
}
|
||
|
||
/* Any injected refs are considered used, because this is used by transaction
|
||
* to emulate installing a new ref, and we never want the new ref:s dependencies
|
||
* seem ununsed. */
|
||
if (metadata_injection)
|
||
{
|
||
GLNX_HASH_TABLE_FOREACH (metadata_injection, const char *, injected_ref)
|
||
{
|
||
g_autoptr(FlatpakDecomposed) injected = flatpak_decomposed_new_from_ref (injected_ref, NULL);
|
||
if (injected)
|
||
{
|
||
g_debug ("%s: Treating %s as used during unused refs analysis",
|
||
G_STRFUNC, flatpak_decomposed_get_ref (injected));
|
||
queue_ref_for_analysis (injected, arch, analyzed_refs, refs_to_analyze);
|
||
}
|
||
}
|
||
}
|
||
|
||
while ((ref_to_analyze = g_queue_pop_head (refs_to_analyze)) != NULL)
|
||
{
|
||
g_autoptr(GKeyFile) metakey = NULL;
|
||
gboolean ref_is_shadowed;
|
||
gboolean is_app;
|
||
g_autoptr(GPtrArray) related = NULL;
|
||
g_autofree char *sdk = NULL;
|
||
g_autofree char *dir_name = NULL;
|
||
|
||
if (!maybe_get_metakey (self, shadowing_dir, ref_to_analyze, metadata_injection,
|
||
&metakey, &ref_is_shadowed, &dir_name))
|
||
continue; /* Something used something we could not find, that is fine and happens for instance with sdk dependencies */
|
||
|
||
if (!ref_is_shadowed)
|
||
{
|
||
/* Mark the analyzed ref used as it wasn't shadowed */
|
||
if (!g_hash_table_contains (used_refs, ref_to_analyze))
|
||
g_hash_table_add (used_refs, flatpak_decomposed_ref (ref_to_analyze));
|
||
|
||
/* For excluded refs we mark them as used (above) so that they don't get listed as
|
||
* unused, but we don't analyze them for any dependencies. Note that refs_to_exclude only
|
||
* affects the base dir, so does not affect shadowed refs */
|
||
if (refs_to_exclude != NULL && g_hash_table_contains (refs_to_exclude, ref_to_analyze))
|
||
{
|
||
g_debug ("%s: Treating %s as uninstalled during unused refs analysis",
|
||
G_STRFUNC, flatpak_decomposed_get_ref (ref_to_analyze));
|
||
continue;
|
||
}
|
||
}
|
||
|
||
/************************************************
|
||
* Find all dependencies and queue for analysis *
|
||
***********************************************/
|
||
|
||
is_app = flatpak_decomposed_is_app (ref_to_analyze);
|
||
|
||
/* App directly depends on its runtime */
|
||
if (is_app)
|
||
{
|
||
g_autofree char *runtime = g_key_file_get_string (metakey, "Application", "runtime", NULL);
|
||
if (runtime)
|
||
{
|
||
g_autoptr(FlatpakDecomposed) runtime_ref = flatpak_decomposed_new_from_pref (FLATPAK_KINDS_RUNTIME, runtime, NULL);
|
||
if (runtime_ref && !flatpak_decomposed_equal (runtime_ref, ref_to_analyze))
|
||
{
|
||
g_debug ("%s: Considering runtime %s used by app %s%s",
|
||
G_STRFUNC, flatpak_decomposed_get_ref (runtime_ref),
|
||
flatpak_decomposed_get_ref (ref_to_analyze), dir_name);
|
||
queue_ref_for_analysis (runtime_ref, arch, analyzed_refs, refs_to_analyze);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Both apps and runtimes directly depends on its sdk, to avoid suddenly
|
||
* uninstalling something you use to develop the app */
|
||
sdk = g_key_file_get_string (metakey, is_app ? "Application" : "Runtime", "sdk", NULL);
|
||
if (sdk)
|
||
{
|
||
g_autoptr(FlatpakDecomposed) sdk_ref = flatpak_decomposed_new_from_pref (FLATPAK_KINDS_RUNTIME, sdk, NULL);
|
||
if (sdk_ref && !flatpak_decomposed_equal (sdk_ref, ref_to_analyze))
|
||
{
|
||
g_debug ("%s: Considering sdk %s used by %s%s",
|
||
G_STRFUNC, flatpak_decomposed_get_ref (sdk_ref),
|
||
flatpak_decomposed_get_ref (ref_to_analyze), dir_name);
|
||
queue_ref_for_analysis (sdk_ref, arch, analyzed_refs, refs_to_analyze);
|
||
}
|
||
}
|
||
|
||
/* Extensions with extra data, that are not specially marked NoRuntime needs the runtime at install.
|
||
* Lets keep it around to not re-download it next update */
|
||
if (!is_app &&
|
||
g_key_file_has_group (metakey, "Extra Data") &&
|
||
!g_key_file_get_boolean (metakey, "Extra Data", "NoRuntime", NULL))
|
||
{
|
||
g_autofree char *extension_runtime_ref = g_key_file_get_string (metakey, "ExtensionOf", "runtime", NULL);
|
||
if (extension_runtime_ref != NULL)
|
||
{
|
||
g_autoptr(FlatpakDecomposed) d = flatpak_decomposed_new_from_ref (extension_runtime_ref, NULL);
|
||
if (d)
|
||
{
|
||
g_debug ("%s: Considering runtime %s used by extra-data %s%s",
|
||
G_STRFUNC, flatpak_decomposed_get_ref (d),
|
||
flatpak_decomposed_get_ref (ref_to_analyze), dir_name);
|
||
queue_ref_for_analysis (d, arch, analyzed_refs, refs_to_analyze);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* We pass NULL for remote-name here, because we want to consider related refs from all remotes */
|
||
related = flatpak_dir_find_local_related_for_metadata (self, ref_to_analyze,
|
||
NULL, metakey, NULL, NULL);
|
||
for (int i = 0; related != NULL && i < related->len; i++)
|
||
{
|
||
FlatpakRelated *rel = g_ptr_array_index (related, i);
|
||
|
||
if (!rel->auto_prune)
|
||
{
|
||
g_debug ("%s: Considering related ref %s used by %s%s",
|
||
G_STRFUNC, flatpak_decomposed_get_ref (rel->ref),
|
||
flatpak_decomposed_get_ref (ref_to_analyze), dir_name);
|
||
queue_ref_for_analysis (rel->ref, arch, analyzed_refs, refs_to_analyze);
|
||
}
|
||
else
|
||
{
|
||
g_hash_table_add (autopruned_refs, flatpak_decomposed_ref (rel->ref));
|
||
}
|
||
}
|
||
}
|
||
|
||
return g_steal_pointer (&used_refs);
|
||
}
|
||
|
||
/* See the documentation for
|
||
* flatpak_installation_list_unused_refs_with_options().
|
||
* The returned pointer array is transfer full. */
|
||
char **
|
||
flatpak_dir_list_unused_refs (FlatpakDir *self,
|
||
const char *arch,
|
||
GHashTable *metadata_injection,
|
||
GHashTable *eol_injection,
|
||
const char * const *refs_to_exclude,
|
||
FlatpakDirFilterFlags filter_flags,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GHashTable) used_refs = NULL;
|
||
g_autoptr(GHashTable) autoprune_refs = NULL;
|
||
g_autoptr(GHashTable) excluded_refs_ht = NULL;
|
||
g_autoptr(GPtrArray) refs = NULL;
|
||
g_autoptr(GPtrArray) runtime_refs = NULL;
|
||
gboolean filter_by_eol = (filter_flags & FLATPAK_DIR_FILTER_EOL) != 0;
|
||
gboolean filter_by_autoprune = (filter_flags & FLATPAK_DIR_FILTER_AUTOPRUNE) != 0;
|
||
|
||
/* Convert refs_to_exclude to hashtable for fast repeated lookups */
|
||
if (refs_to_exclude)
|
||
{
|
||
excluded_refs_ht = g_hash_table_new_full ((GHashFunc)flatpak_decomposed_hash, (GEqualFunc)flatpak_decomposed_equal, (GDestroyNotify)flatpak_decomposed_unref, NULL);
|
||
for (int i = 0; refs_to_exclude[i] != NULL; i++)
|
||
{
|
||
const char *ref_to_exclude = refs_to_exclude[i];
|
||
g_autoptr(FlatpakDecomposed) d = flatpak_decomposed_new_from_ref (ref_to_exclude, NULL);
|
||
if (d)
|
||
g_hash_table_add (excluded_refs_ht, flatpak_decomposed_ref (d));
|
||
}
|
||
}
|
||
|
||
used_refs = g_hash_table_new_full ((GHashFunc)flatpak_decomposed_hash, (GEqualFunc)flatpak_decomposed_equal, (GDestroyNotify)flatpak_decomposed_unref, NULL);
|
||
autoprune_refs = g_hash_table_new_full ((GHashFunc)flatpak_decomposed_hash, (GEqualFunc)flatpak_decomposed_equal, (GDestroyNotify)flatpak_decomposed_unref, NULL);
|
||
|
||
g_info ("Checking installation ‘%s’ %s%s",
|
||
flatpak_dir_get_name_cached (self),
|
||
filter_by_eol ? "for EOL unused refs" : "for unused refs",
|
||
filter_by_autoprune ? " and autoprunes" : "");
|
||
if (!find_used_refs (self, NULL, arch, metadata_injection, excluded_refs_ht,
|
||
used_refs, autoprune_refs, cancellable, error))
|
||
return NULL;
|
||
|
||
/* 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(FlatpakDir) user_dir = flatpak_dir_get_user ();
|
||
g_autoptr(GError) local_error = NULL;
|
||
|
||
g_info ("Checking installation ‘%s’ by checking for dependent refs in ‘%s’",
|
||
flatpak_dir_get_name_cached (self), flatpak_dir_get_name_cached (user_dir));
|
||
if (!find_used_refs (self, user_dir, arch, metadata_injection, excluded_refs_ht,
|
||
used_refs, autoprune_refs, cancellable, &local_error))
|
||
{
|
||
/* We may get permission denied if the process is sandboxed with
|
||
* systemd's ProtectHome=
|
||
*/
|
||
if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) &&
|
||
!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED))
|
||
{
|
||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||
return NULL;
|
||
}
|
||
}
|
||
}
|
||
|
||
runtime_refs = flatpak_dir_list_refs (self, FLATPAK_KINDS_RUNTIME, cancellable, error);
|
||
if (runtime_refs == NULL)
|
||
return NULL;
|
||
|
||
refs = g_ptr_array_new_with_free_func (g_free);
|
||
|
||
for (int i = 0; i < runtime_refs->len; i++)
|
||
{
|
||
FlatpakDecomposed *ref = g_ptr_array_index (runtime_refs, i);
|
||
|
||
if (g_hash_table_contains (used_refs, ref))
|
||
continue;
|
||
|
||
if (arch != NULL && !flatpak_decomposed_is_arch (ref, arch))
|
||
continue;
|
||
|
||
if (filter_flags)
|
||
{
|
||
gboolean is_eol = FALSE;
|
||
gboolean is_autopruned = g_hash_table_contains (autoprune_refs, ref);
|
||
|
||
if (eol_injection && g_hash_table_contains (eol_injection, flatpak_decomposed_get_ref (ref)))
|
||
{
|
||
is_eol = GPOINTER_TO_INT (g_hash_table_lookup (eol_injection, ref));
|
||
}
|
||
else
|
||
{
|
||
g_autoptr(GBytes) deploy_data = NULL;
|
||
|
||
/* deploy v4 guarantees eol/eolr info */
|
||
deploy_data = flatpak_dir_get_deploy_data (self, ref, 4,
|
||
cancellable, NULL);
|
||
is_eol = deploy_data != NULL &&
|
||
(flatpak_deploy_data_get_eol (deploy_data) != NULL ||
|
||
flatpak_deploy_data_get_eol_rebase (deploy_data));
|
||
}
|
||
|
||
if (!((is_autopruned && filter_by_autoprune) || (is_eol && filter_by_eol)))
|
||
{
|
||
g_debug ("%s: Ref %s (%s) not %s, so excluding from unused refs",
|
||
G_STRFUNC, flatpak_decomposed_get_ref (ref),
|
||
flatpak_dir_get_name_cached (self),
|
||
(!is_eol && filter_by_eol) ? "end-of-life" : "autopruned");
|
||
continue;
|
||
}
|
||
}
|
||
|
||
g_info ("%s: Ref %s (%s) is %s",
|
||
G_STRFUNC, flatpak_decomposed_get_ref (ref),
|
||
flatpak_dir_get_name_cached (self),
|
||
filter_by_eol ? "EOL and unused" : "unused");
|
||
g_ptr_array_add (refs, flatpak_decomposed_dup_ref (ref));
|
||
}
|
||
|
||
g_ptr_array_add (refs, NULL);
|
||
return (char **)g_ptr_array_free (g_steal_pointer (&refs), FALSE);
|
||
}
|