summaryrefslogtreecommitdiff
path: root/common/flatpak-dir.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/flatpak-dir.c')
-rw-r--r--common/flatpak-dir.c4265
1 files changed, 4265 insertions, 0 deletions
diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c
new file mode 100644
index 0000000..1aac10c
--- /dev/null
+++ b/common/flatpak-dir.c
@@ -0,0 +1,4265 @@
+/*
+ * Copyright © 2014 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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>
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <sys/file.h>
+#include <sys/types.h>
+#include <utime.h>
+
+#include <gio/gio.h>
+#include "libgsystem.h"
+#include "libglnx/libglnx.h"
+
+#include "flatpak-dir.h"
+#include "flatpak-utils.h"
+#include "flatpak-run.h"
+
+#include "errno.h"
+
+#define NO_SYSTEM_HELPER ((XdgAppSystemHelper *) (gpointer) 1)
+
+struct FlatpakDir
+{
+ GObject parent;
+
+ gboolean user;
+ GFile *basedir;
+ OstreeRepo *repo;
+
+ XdgAppSystemHelper *system_helper;
+
+ SoupSession *soup_session;
+};
+
+typedef struct
+{
+ GObjectClass parent_class;
+} FlatpakDirClass;
+
+struct FlatpakDeploy
+{
+ GObject parent;
+
+ GFile *dir;
+ GKeyFile *metadata;
+ FlatpakContext *system_overrides;
+ FlatpakContext *user_overrides;
+};
+
+typedef struct
+{
+ GObjectClass parent_class;
+} FlatpakDeployClass;
+
+G_DEFINE_TYPE (FlatpakDir, flatpak_dir, G_TYPE_OBJECT)
+G_DEFINE_TYPE (FlatpakDeploy, flatpak_deploy, G_TYPE_OBJECT)
+
+G_DEFINE_QUARK (xdg - app - dir - error - quark, flatpak_dir_error)
+
+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 void
+flatpak_deploy_finalize (GObject *object)
+{
+ FlatpakDeploy *self = FLATPAK_DEPLOY (object);
+
+ g_clear_object (&self->dir);
+ g_clear_pointer (&self->metadata, g_key_file_unref);
+ g_clear_pointer (&self->system_overrides, g_key_file_unref);
+ g_clear_pointer (&self->user_overrides, g_key_file_unref);
+
+ 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);
+}
+
+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_merge (overrides, deploy->system_overrides);
+
+ if (deploy->user_overrides)
+ flatpak_context_merge (overrides, deploy->user_overrides);
+
+ return overrides;
+}
+
+GKeyFile *
+flatpak_deploy_get_metadata (FlatpakDeploy *deploy)
+{
+ return g_key_file_ref (deploy->metadata);
+}
+
+static FlatpakDeploy *
+flatpak_deploy_new (GFile *dir, GKeyFile *metadata)
+{
+ FlatpakDeploy *deploy;
+
+ deploy = g_object_new (FLATPAK_TYPE_DEPLOY, NULL);
+ deploy->dir = g_object_ref (dir);
+ deploy->metadata = g_key_file_ref (metadata);
+
+ return deploy;
+}
+
+GFile *
+flatpak_get_system_base_dir_location (void)
+{
+ return g_file_new_for_path (FLATPAK_SYSTEMDIR);
+}
+
+GFile *
+flatpak_get_user_base_dir_location (void)
+{
+ g_autofree char *base = g_build_filename (g_get_user_data_dir (), "xdg-app", NULL);
+
+ return g_file_new_for_path (base);
+}
+
+GFile *
+flatpak_get_user_cache_dir_location (void)
+{
+ g_autoptr(GFile) base_dir = NULL;
+
+ base_dir = flatpak_get_user_base_dir_location ();
+ return g_file_get_child (base_dir, "system-cache");
+}
+
+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 XdgAppSystemHelper *
+flatpak_dir_get_system_helper (FlatpakDir *self)
+{
+ g_autoptr(GError) error = NULL;
+
+ if (g_once_init_enter (&self->system_helper))
+ {
+ XdgAppSystemHelper *system_helper;
+ system_helper =
+ xdg_app_system_helper_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+ "org.freedesktop.Flatpak.SystemHelper",
+ "/org/freedesktop/Flatpak/SystemHelper",
+ NULL, &error);
+ if (error != NULL)
+ {
+ g_warning ("Can't find org.freedesktop.Flatpak.SystemHelper: %s\n", error->message);
+ system_helper = NO_SYSTEM_HELPER;
+ }
+
+ g_once_init_leave (&self->system_helper, system_helper);
+ }
+
+ if (self->system_helper != NO_SYSTEM_HELPER)
+ return self->system_helper;
+ return NULL;
+}
+
+gboolean
+flatpak_dir_use_child_repo (FlatpakDir *self)
+{
+ XdgAppSystemHelper *system_helper;
+
+ if (self->user || getuid () == 0)
+ return FALSE;
+
+ system_helper = flatpak_dir_get_system_helper (self);
+
+ return system_helper != NULL;
+}
+
+static OstreeRepo *
+system_ostree_repo_new (GFile *repodir)
+{
+ return g_object_new (OSTREE_TYPE_REPO, "path", repodir,
+ "remotes-config-dir", FLATPAK_CONFIGDIR "/remotes.d",
+ NULL);
+}
+
+static void
+flatpak_dir_finalize (GObject *object)
+{
+ FlatpakDir *self = FLATPAK_DIR (object);
+
+ g_clear_object (&self->repo);
+ g_clear_object (&self->basedir);
+
+ g_clear_object (&self->system_helper);
+
+ g_clear_object (&self->soup_session);
+
+ 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 (gs_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)
+{
+}
+
+gboolean
+flatpak_dir_is_user (FlatpakDir *self)
+{
+ return self->user;
+}
+
+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");
+}
+
+char *
+flatpak_dir_load_override (FlatpakDir *self,
+ const char *app_id,
+ gsize *length,
+ 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");
+ file = g_file_get_child (override_dir, app_id);
+
+ 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;
+ }
+
+ 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(GKeyFile) metakey = g_key_file_new ();
+ g_autoptr(FlatpakDir) dir = NULL;
+
+ dir = flatpak_dir_get (user);
+
+ metadata_contents = flatpak_dir_load_override (dir, app_id, &metadata_size, error);
+ if (metadata_contents == NULL)
+ return NULL;
+
+ if (!g_key_file_load_from_data (metakey,
+ metadata_contents,
+ metadata_size,
+ 0, error))
+ return NULL;
+
+ return g_steal_pointer (&metakey);
+}
+
+FlatpakContext *
+flatpak_load_override_file (const char *app_id, gboolean user, GError **error)
+{
+ 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_base_dir_location ();
+
+ override_dir = g_file_get_child (base_dir, "overrides");
+ file = g_file_get_child (override_dir, app_id);
+
+ 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);
+}
+
+FlatpakDeploy *
+flatpak_dir_load_deployed (FlatpakDir *self,
+ const char *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_auto(GStrv) ref_parts = 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)
+ {
+ g_set_error (error, FLATPAK_DIR_ERROR, FLATPAK_DIR_ERROR_NOT_DEPLOYED, "%s not installed", ref);
+ 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 NULL;
+
+ deploy = flatpak_deploy_new (deploy_dir, metakey);
+
+ ref_parts = g_strsplit (ref, "/", -1);
+ g_assert (g_strv_length (ref_parts) == 4);
+
+ /* Only apps have overrides */
+ if (strcmp (ref_parts[0], "app") == 0)
+ {
+ /* Only load system overrides for system installed apps */
+ if (!self->user)
+ {
+ deploy->system_overrides = flatpak_load_override_file (ref_parts[1], FALSE, error);
+ if (deploy->system_overrides == NULL)
+ return NULL;
+ }
+
+ /* Always load user overrides */
+ deploy->user_overrides = flatpak_load_override_file (ref_parts[1], TRUE, error);
+ if (deploy->user_overrides == NULL)
+ return NULL;
+ }
+
+ return deploy;
+}
+
+GFile *
+flatpak_dir_get_deploy_dir (FlatpakDir *self,
+ const char *ref)
+{
+ return g_file_resolve_relative_path (self->basedir, 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");
+}
+
+OstreeRepo *
+flatpak_dir_get_repo (FlatpakDir *self)
+{
+ return self->repo;
+}
+
+
+/* This is an exclusive per xdg-app installation file lock that is taken
+ * whenever any config in the directory outside the repo is to be changed. For
+ * instance deployements, 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);
+}
+
+const char *
+flatpak_deploy_data_get_origin (GVariant *deploy_data)
+{
+ const char *origin;
+
+ g_variant_get_child (deploy_data, 0, "&s", &origin);
+ return origin;
+}
+
+const char *
+flatpak_deploy_data_get_commit (GVariant *deploy_data)
+{
+ const char *commit;
+
+ g_variant_get_child (deploy_data, 1, "&s", &commit);
+ return commit;
+}
+
+/**
+ * flatpak_deploy_data_get_subpaths:
+ *
+ * Returns: (array length=length zero-terminated=1) (transfer container): an array of constant strings
+ **/
+const char **
+flatpak_deploy_data_get_subpaths (GVariant *deploy_data)
+{
+ const char **subpaths;
+
+ g_variant_get_child (deploy_data, 2, "^as", &subpaths);
+ return subpaths;
+}
+
+guint64
+flatpak_deploy_data_get_installed_size (GVariant *deploy_data)
+{
+ guint64 size;
+
+ g_variant_get_child (deploy_data, 3, "t", &size);
+ return GUINT64_FROM_BE (size);
+}
+
+static GVariant *
+flatpak_dir_new_deploy_data (const char *origin,
+ const char *commit,
+ char **subpaths,
+ guint64 installed_size,
+ GVariant *metadata)
+{
+ char *empty_subpaths[] = {NULL};
+ GVariantBuilder builder;
+
+ if (metadata == NULL)
+ {
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+ metadata = g_variant_builder_end (&builder);
+ }
+
+ return g_variant_ref_sink (g_variant_new ("(ss^ast@a{sv})",
+ origin,
+ commit,
+ subpaths ? subpaths : empty_subpaths,
+ GUINT64_TO_BE (installed_size),
+ metadata));
+}
+
+static char **
+get_old_subpaths (GFile *deploy_base,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GFile) file = NULL;
+ g_autofree char *data = NULL;
+ g_autoptr(GError) my_error = NULL;
+ g_autoptr(GPtrArray) subpaths = NULL;
+ g_auto(GStrv) lines = NULL;
+ int i;
+
+ file = g_file_get_child (deploy_base, "subpaths");
+ if (!g_file_load_contents (file, cancellable, &data, NULL, NULL, &my_error))
+ {
+ if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+ {
+ data = g_strdup ("");
+ }
+ else
+ {
+ g_propagate_error (error, g_steal_pointer (&my_error));
+ return NULL;
+ }
+ }
+
+ lines = g_strsplit (data, "\n", 0);
+
+ subpaths = g_ptr_array_new ();
+ for (i = 0; lines[i] != NULL; i++)
+ {
+ lines[i] = g_strstrip (lines[i]);
+ if (lines[i][0] == '/')
+ g_ptr_array_add (subpaths, g_strdup (lines[i]));
+ }
+
+ g_ptr_array_add (subpaths, NULL);
+ return (char **) g_ptr_array_free (subpaths, FALSE);
+}
+
+static GVariant *
+flatpak_create_deploy_data_from_old (FlatpakDir *self,
+ GFile *deploy_dir,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GFile) deploy_base = NULL;
+ g_autofree char *old_origin = NULL;
+ g_autofree char *commit = NULL;
+ g_auto(GStrv) old_subpaths = NULL;
+ g_autoptr(GFile) root = NULL;
+ g_autoptr(GFile) origin = NULL;
+ guint64 installed_size;
+
+ deploy_base = g_file_get_parent (deploy_dir);
+ commit = g_file_get_basename (deploy_dir);
+
+ origin = g_file_get_child (deploy_base, "origin");
+ if (!g_file_load_contents (origin, cancellable, &old_origin, NULL, NULL, error))
+ return NULL;
+
+ old_subpaths = get_old_subpaths (deploy_base, cancellable, error);
+ if (old_subpaths == NULL)
+ return NULL;
+
+ /* For backwards compat we return a 0 installed size, its to slow to regenerate */
+ installed_size = 0;
+
+ return flatpak_dir_new_deploy_data (old_origin, commit, old_subpaths,
+ installed_size, NULL);
+}
+
+GVariant *
+flatpak_dir_get_deploy_data (FlatpakDir *self,
+ const char *ref,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GFile) deploy_dir = NULL;
+ g_autoptr(GFile) data_file = NULL;
+ g_autoptr(GError) my_error = NULL;
+ char *data = NULL;
+ gsize data_size;
+ g_autofree char *active = NULL;
+
+ deploy_dir = flatpak_dir_get_if_deployed (self, ref, NULL, cancellable);
+ if (deploy_dir == NULL)
+ {
+ flatpak_fail (error, "%s is not installed", ref);
+ return NULL;
+ }
+
+ data_file = g_file_get_child (deploy_dir, "deploy");
+ if (!g_file_load_contents (data_file, cancellable, &data, &data_size, NULL, &my_error))
+ {
+ 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;
+ }
+
+ return flatpak_create_deploy_data_from_old (self, deploy_dir,
+ cancellable, error);
+ }
+
+ return g_variant_ref_sink (g_variant_new_from_data (FLATPAK_DEPLOY_DATA_GVARIANT_FORMAT,
+ data, data_size,
+ FALSE, g_free, data));
+}
+
+
+char *
+flatpak_dir_get_origin (FlatpakDir *self,
+ const char *ref,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GVariant) deploy_data = NULL;
+
+ deploy_data = flatpak_dir_get_deploy_data (self, ref,
+ cancellable, error);
+ if (deploy_data == NULL)
+ {
+ flatpak_fail (error, "%s is not installed", ref);
+ return NULL;
+ }
+
+ return g_strdup (flatpak_deploy_data_get_origin (deploy_data));
+}
+
+char **
+flatpak_dir_get_subpaths (FlatpakDir *self,
+ const char *ref,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GVariant) deploy_data = NULL;
+ char **subpaths;
+ int i;
+
+ deploy_data = flatpak_dir_get_deploy_data (self, ref,
+ cancellable, error);
+ if (deploy_data == NULL)
+ {
+ flatpak_fail (error, "%s is not installed", ref);
+ return NULL;
+ }
+
+ subpaths = (char **) flatpak_deploy_data_get_subpaths (deploy_data);
+ for (i = 0; subpaths[i] != NULL; i++)
+ subpaths[i] = g_strdup (subpaths[i]);
+
+ return subpaths;
+}
+
+gboolean
+flatpak_dir_ensure_path (FlatpakDir *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return gs_file_ensure_directory (self->basedir, TRUE, cancellable, error);
+}
+
+gboolean
+flatpak_dir_ensure_repo (FlatpakDir *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+
+ g_autoptr(GFile) repodir = NULL;
+ g_autoptr(OstreeRepo) repo = NULL;
+
+ if (self->repo == NULL)
+ {
+ if (!flatpak_dir_ensure_path (self, cancellable, error))
+ goto out;
+
+ repodir = g_file_get_child (self->basedir, "repo");
+ if (self->user)
+ {
+ repo = ostree_repo_new (repodir);
+ }
+ else
+ {
+ g_autoptr(GFile) cache_dir = NULL;
+ g_autofree char *cache_path = NULL;
+
+ repo = system_ostree_repo_new (repodir);
+
+ cache_dir = flatpak_ensure_user_cache_dir_location (error);
+ if (cache_dir == NULL)
+ goto out;
+
+ cache_path = g_file_get_path (cache_dir);
+ if (!ostree_repo_set_cache_dir (repo,
+ AT_FDCWD, cache_path,
+ cancellable, error))
+ goto out;
+ }
+
+ if (!g_file_query_exists (repodir, cancellable))
+ {
+ if (!ostree_repo_create (repo,
+ OSTREE_REPO_MODE_BARE_USER,
+ cancellable, error))
+ {
+ gs_shutil_rm_rf (repodir, cancellable, NULL);
+ goto out;
+ }
+
+ /* Create .changes file early to avoid polling non-existing file in monitor */
+ flatpak_dir_mark_changed (self, NULL);
+ }
+ else
+ {
+ if (!ostree_repo_open (repo, cancellable, error))
+ {
+ g_autofree char *repopath = NULL;
+
+ repopath = g_file_get_path (repodir);
+ g_prefix_error (error, "While opening repository %s: ", repopath);
+ goto out;
+ }
+ }
+
+ self->repo = g_object_ref (repo);
+ }
+
+ ret = TRUE;
+out:
+ return ret;
+}
+
+gboolean
+flatpak_dir_mark_changed (FlatpakDir *self,
+ GError **error)
+{
+ g_autoptr(GFile) changed_file = NULL;
+
+ changed_file = flatpak_dir_get_changed_path (self);
+ if (!g_file_replace_contents (changed_file, "", 0, NULL, FALSE,
+ G_FILE_CREATE_REPLACE_DESTINATION, 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) &&
+ !gs_shutil_rm_rf (remote_dir, cancellable, error))
+ return FALSE;
+
+ return TRUE;
+}
+
+gboolean
+flatpak_dir_remove_all_refs (FlatpakDir *self,
+ const char *remote,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autofree char *prefix = NULL;
+
+ g_autoptr(GHashTable) refs = NULL;
+ GHashTableIter hash_iter;
+ gpointer key;
+
+ if (!flatpak_dir_ensure_repo (self, cancellable, error))
+ return FALSE;
+
+ prefix = g_strdup_printf ("%s:", remote);
+
+ if (!ostree_repo_list_refs (self->repo,
+ NULL,
+ &refs,
+ cancellable, error))
+ return FALSE;
+
+ 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, refspec + strlen (prefix), cancellable, error))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+flatpak_dir_update_appstream (FlatpakDir *self,
+ const char *remote,
+ const char *arch,
+ gboolean *out_changed,
+ OstreeAsyncProgress *progress,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autofree char *branch = NULL;
+ g_autofree char *remote_and_branch = NULL;
+ g_autofree char *old_checksum = NULL;
+ g_autofree char *new_checksum = NULL;
+
+ g_autoptr(GFile) root = NULL;
+ 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) old_checkout_dir = NULL;
+ g_autoptr(GFileInfo) file_info = NULL;
+ g_autofree char *arch_path = NULL;
+ g_autofree char *tmpname = NULL;
+ g_autoptr(GFile) active_tmp_link = NULL;
+ g_autoptr(GFile) active_link = NULL;
+ g_autoptr(GFile) timestamp_file = NULL;
+ g_autoptr(GError) tmp_error = NULL;
+ gboolean checkout_exists;
+
+ if (!flatpak_dir_ensure_repo (self, cancellable, error))
+ return FALSE;
+
+ if (arch == NULL)
+ arch = flatpak_get_arch ();
+
+ branch = g_strdup_printf ("appstream/%s", arch);
+ remote_and_branch = g_strdup_printf ("%s:%s", remote, branch);
+
+ if (!ostree_repo_resolve_rev (self->repo, remote_and_branch, TRUE, &old_checksum, error))
+ return FALSE;
+
+ if (!flatpak_dir_pull (self, remote, branch, NULL, NULL, OSTREE_REPO_PULL_FLAGS_NONE, progress,
+ cancellable, error))
+ return FALSE;
+
+ if (!ostree_repo_resolve_rev (self->repo, remote_and_branch, TRUE, &new_checksum, error))
+ return FALSE;
+
+ if (new_checksum == NULL)
+ {
+ g_warning ("No appstream branch in remote %s\n", remote);
+ return TRUE;
+ }
+
+ 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);
+ checkout_dir = g_file_get_child (arch_dir, new_checksum);
+ 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;
+ }
+
+ checkout_exists = g_file_query_exists (checkout_dir, NULL);
+
+ if (old_checksum != NULL && new_checksum != NULL &&
+ strcmp (old_checksum, new_checksum) == 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 */
+ }
+
+ if (!ostree_repo_read_commit (self->repo, new_checksum, &root, NULL, cancellable, error))
+ return FALSE;
+
+ file_info = g_file_query_info (root, OSTREE_GIO_FAST_QUERYINFO,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ cancellable, error);
+ if (file_info == NULL)
+ return FALSE;
+
+ if (!ostree_repo_checkout_tree (self->repo,
+ OSTREE_REPO_CHECKOUT_MODE_USER,
+ OSTREE_REPO_CHECKOUT_OVERWRITE_NONE,
+ checkout_dir,
+ OSTREE_REPO_FILE (root), file_info,
+ cancellable, error))
+ return FALSE;
+
+ tmpname = gs_fileutil_gen_tmp_name (".active-", NULL);
+ active_tmp_link = g_file_get_child (arch_dir, tmpname);
+ active_link = g_file_get_child (arch_dir, "active");
+
+ if (!g_file_make_symbolic_link (active_tmp_link, new_checksum, cancellable, error))
+ return FALSE;
+
+ if (!gs_file_rename (active_tmp_link,
+ active_link,
+ cancellable, error))
+ return FALSE;
+
+ if (old_checksum != NULL &&
+ g_strcmp0 (old_checksum, new_checksum) != 0)
+ {
+ old_checkout_dir = g_file_get_child (arch_dir, old_checksum);
+ if (!gs_shutil_rm_rf (old_checkout_dir, cancellable, &tmp_error))
+ g_warning ("Unable to remove old appstream checkout: %s\n", 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);
+ }
+
+ if (out_changed)
+ *out_changed = TRUE;
+ return TRUE;
+}
+
+/* This is a copy of ostree_repo_pull_one_dir that always disables
+ static deltas if subdir is used */
+static gboolean
+repo_pull_one_dir (OstreeRepo *self,
+ const char *remote_name,
+ const char *dir_to_pull,
+ char **refs_to_fetch,
+ OstreeRepoPullFlags flags,
+ OstreeAsyncProgress *progress,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GVariantBuilder builder;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+
+ if (dir_to_pull)
+ {
+ g_variant_builder_add (&builder, "{s@v}", "subdir",
+ g_variant_new_variant (g_variant_new_string (dir_to_pull)));
+ 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}", "flags",
+ g_variant_new_variant (g_variant_new_int32 (flags)));
+ if (refs_to_fetch)
+ g_variant_builder_add (&builder, "{s@v}", "refs",
+ g_variant_new_variant (g_variant_new_strv ((const char * const *) refs_to_fetch, -1)));
+
+ return ostree_repo_pull_with_options (self, remote_name, g_variant_builder_end (&builder),
+ progress, cancellable, error);
+}
+
+
+gboolean
+flatpak_dir_pull (FlatpakDir *self,
+ const char *repository,
+ const char *ref,
+ char **subpaths,
+ OstreeRepo *repo,
+ OstreeRepoPullFlags flags,
+ OstreeAsyncProgress *progress,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GSConsole *console = NULL;
+
+ g_autoptr(OstreeAsyncProgress) console_progress = NULL;
+ const char *refs[2];
+ g_autofree char *url = NULL;
+
+ if (!flatpak_dir_ensure_repo (self, cancellable, error))
+ goto out;
+
+ if (!ostree_repo_remote_get_url (self->repo,
+ repository,
+ &url,
+ error))
+ goto out;
+
+ if (*url == 0)
+ return TRUE; /* Empty url, silently disables updates */
+
+ if (repo == NULL)
+ repo = self->repo;
+
+ if (progress == NULL)
+ {
+ console = gs_console_get ();
+ if (console)
+ {
+ gs_console_begin_status_line (console, "", NULL, NULL);
+ console_progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, console);
+ progress = console_progress;
+ }
+ }
+
+
+ refs[0] = ref;
+ refs[1] = NULL;
+
+ if (subpaths == NULL || subpaths[0] == NULL)
+ {
+ if (!ostree_repo_pull (repo, repository,
+ (char **) refs, flags,
+ progress,
+ cancellable, error))
+ {
+ g_prefix_error (error, "While pulling %s from remote %s: ", ref, repository);
+ goto out;
+ }
+ }
+ else
+ {
+ int i;
+
+ if (!repo_pull_one_dir (repo, repository,
+ "/metadata",
+ (char **) refs, flags,
+ progress,
+ cancellable, error))
+ {
+ g_prefix_error (error, "While pulling %s from remote %s, metadata: ",
+ ref, repository);
+ goto out;
+ }
+
+ for (i = 0; subpaths[i] != NULL; i++)
+ {
+ g_autofree char *subpath = g_build_filename ("/files", subpaths[i], NULL);
+ if (!repo_pull_one_dir (repo, repository,
+ subpath,
+ (char **) refs, flags,
+ progress,
+ cancellable, error))
+ {
+ g_prefix_error (error, "While pulling %s from remote %s, subpath %s: ",
+ ref, repository, subpaths[i]);
+ goto out;
+ }
+ }
+ }
+
+ ret = TRUE;
+
+out:
+ if (console)
+ {
+ ostree_async_progress_finish (progress);
+ gs_console_end_status_line (console, NULL, NULL);
+ }
+
+ return ret;
+}
+
+static gboolean
+repo_pull_one_untrusted (OstreeRepo *self,
+ const char *remote_name,
+ const char *url,
+ const char *dir_to_pull,
+ const char *ref,
+ const char *checksum,
+ OstreeAsyncProgress *progress,
+ GCancellable *cancellable,
+ GError **error)
+{
+ OstreeRepoPullFlags flags = OSTREE_REPO_PULL_FLAGS_UNTRUSTED;
+ GVariantBuilder builder;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+ const char *refs[2] = { NULL, NULL };
+ const char *commits[2] = { NULL, NULL };
+
+ refs[0] = ref;
+ commits[0] = checksum;
+
+ g_variant_builder_add (&builder, "{s@v}", "flags",
+ g_variant_new_variant (g_variant_new_int32 (flags)));
+ 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}", "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 (TRUE)));
+
+ if (dir_to_pull)
+ {
+ g_variant_builder_add (&builder, "{s@v}", "subdir",
+ g_variant_new_variant (g_variant_new_string (dir_to_pull)));
+ g_variant_builder_add (&builder, "{s@v}", "disable-static-deltas",
+ g_variant_new_variant (g_variant_new_boolean (TRUE)));
+ }
+
+ return ostree_repo_pull_with_options (self, url, g_variant_builder_end (&builder),
+ progress, cancellable, error);
+}
+
+gboolean
+flatpak_dir_pull_untrusted_local (FlatpakDir *self,
+ const char *src_path,
+ const char *remote_name,
+ const char *ref,
+ char **subpaths,
+ OstreeAsyncProgress *progress,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GSConsole *console = NULL;
+
+ g_autoptr(OstreeAsyncProgress) console_progress = NULL;
+ g_autoptr(GFile) path_file = g_file_new_for_path (src_path);
+ g_autoptr(GFile) summary_file = g_file_get_child (path_file, "summary");
+ g_autoptr(GFile) summary_sig_file = g_file_get_child (path_file, "summary.sig");
+ g_autofree char *url = g_file_get_uri (path_file);
+ g_autofree char *checksum = NULL;
+ gboolean gpg_verify_summary;
+ gboolean gpg_verify;
+ char *summary_data = NULL;
+ char *summary_sig_data = NULL;
+ gsize summary_data_size, summary_sig_data_size;
+ g_autoptr(GBytes) summary_bytes = NULL;
+ g_autoptr(GBytes) summary_sig_bytes = NULL;
+ g_autoptr(OstreeGpgVerifyResult) gpg_result = NULL;
+ g_autoptr(GVariant) summary = NULL;
+ g_autoptr(GVariant) old_commit = NULL;
+
+ if (!flatpak_dir_ensure_repo (self, 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;
+
+ if (!gpg_verify_summary || !gpg_verify)
+ return flatpak_fail (error, "Can't pull from untrusted non-gpg verified remote");
+
+ /* We verify the summary manually before anything else to make sure
+ we've got something right before looking too hard at the repo and
+ so we can check for a downgrade before pulling and updating the
+ ref */
+
+ if (!g_file_load_contents (summary_sig_file, cancellable,
+ &summary_sig_data, &summary_sig_data_size, NULL, NULL))
+ return flatpak_fail (error, "GPG verification enabled, but no summary signatures found");
+
+ summary_sig_bytes = g_bytes_new_take (summary_sig_data, summary_sig_data_size);
+
+ if (!g_file_load_contents (summary_file, cancellable,
+ &summary_data, &summary_data_size, NULL, NULL))
+ return flatpak_fail (error, "No summary found");
+ summary_bytes = g_bytes_new_take (summary_data, summary_data_size);
+
+ gpg_result = ostree_repo_verify_summary (self->repo,
+ remote_name,
+ summary_bytes,
+ summary_sig_bytes,
+ cancellable, error);
+ if (gpg_result == NULL)
+ return FALSE;
+
+ if (ostree_gpg_verify_result_count_valid (gpg_result) == 0)
+ return flatpak_fail (error, "GPG signatures found, but none are in trusted keyring");
+
+ summary = g_variant_ref_sink (g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, summary_bytes, FALSE));
+ if (!flatpak_summary_lookup_ref (summary,
+ ref,
+ &checksum))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Can't find %sin remote %s", ref, remote_name);
+ return FALSE;
+ }
+
+ (void) ostree_repo_load_commit (self->repo, checksum, &old_commit, NULL, NULL);
+
+ if (old_commit)
+ {
+ g_autoptr(OstreeRepo) src_repo = ostree_repo_new (path_file);
+ g_autoptr(GVariant) new_commit = NULL;
+ guint64 old_timestamp;
+ guint64 new_timestamp;
+
+ if (!ostree_repo_open (src_repo, cancellable, error))
+ return FALSE;
+
+ if (!ostree_repo_load_commit (src_repo, checksum, &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, "Not allowed to downgrade %s", ref);
+ }
+
+
+ if (progress == NULL)
+ {
+ console = gs_console_get ();
+ if (console)
+ {
+ gs_console_begin_status_line (console, "", NULL, NULL);
+ console_progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, console);
+ progress = console_progress;
+ }
+ }
+
+ if (subpaths == NULL || subpaths[0] == NULL)
+ {
+ if (!repo_pull_one_untrusted (self->repo, remote_name, url,
+ NULL, ref, checksum, progress,
+ cancellable, error))
+ {
+ g_prefix_error (error, "While pulling %s from remote %s: ", ref, remote_name);
+ goto out;
+ }
+ }
+ else
+ {
+ int i;
+
+ if (!repo_pull_one_untrusted (self->repo, remote_name, url,
+ "/metadata", ref, checksum, progress,
+ cancellable, error))
+ {
+ g_prefix_error (error, "While pulling %s from remote %s, metadata: ",
+ ref, remote_name);
+ goto out;
+ }
+
+ for (i = 0; subpaths[i] != NULL; i++)
+ {
+ g_autofree char *subpath = g_build_filename ("/files", subpaths[i], NULL);
+ if (!repo_pull_one_untrusted (self->repo, remote_name, url,
+ subpath, ref, checksum, progress,
+ cancellable, error))
+ {
+ g_prefix_error (error, "While pulling %s from remote %s, subpath %s: ",
+ ref, remote_name, subpaths[i]);
+ goto out;
+ }
+ }
+ }
+
+ ret = TRUE;
+
+out:
+ if (console)
+ {
+ ostree_async_progress_finish (progress);
+ gs_console_end_status_line (console, NULL, NULL);
+ }
+
+ return ret;
+}
+
+
+char *
+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;
+
+ 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;
+
+ return g_strconcat ("app/", name, "/", g_file_info_get_symlink_target (file_info), NULL);
+}
+
+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;
+
+ 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");
+
+ return g_file_delete (current_link, cancellable, error);
+}
+
+gboolean
+flatpak_dir_make_current_ref (FlatpakDir *self,
+ const char *ref,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GFile) base = NULL;
+ g_autoptr(GFile) dir = NULL;
+ g_autoptr(GFile) current_link = NULL;
+ g_auto(GStrv) ref_parts = NULL;
+ g_autofree char *rest = NULL;
+ gboolean ret = FALSE;
+
+ ref_parts = g_strsplit (ref, "/", -1);
+
+ g_assert (g_strv_length (ref_parts) == 4);
+ g_assert (strcmp (ref_parts[0], "app") == 0);
+
+ base = g_file_get_child (flatpak_dir_get_path (self), ref_parts[0]);
+ dir = g_file_get_child (base, ref_parts[1]);
+
+ current_link = g_file_get_child (dir, "current");
+
+ g_file_delete (current_link, cancellable, NULL);
+
+ if (*ref_parts[3] != 0)
+ {
+ rest = g_strdup_printf ("%s/%s", ref_parts[2], ref_parts[3]);
+ if (!g_file_make_symbolic_link (current_link, rest, cancellable, error))
+ goto out;
+ }
+
+ ret = TRUE;
+
+out:
+ return ret;
+}
+
+gboolean
+flatpak_dir_list_refs_for_name (FlatpakDir *self,
+ const char *kind,
+ const char *name,
+ char ***refs_out,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+
+ g_autoptr(GFile) base = NULL;
+ g_autoptr(GFile) dir = NULL;
+ g_autoptr(GFileEnumerator) dir_enum = NULL;
+ g_autoptr(GFileInfo) child_info = NULL;
+ GError *temp_error = NULL;
+ g_autoptr(GPtrArray) refs = NULL;
+
+ base = g_file_get_child (flatpak_dir_get_path (self), kind);
+ dir = g_file_get_child (base, name);
+
+ refs = g_ptr_array_new ();
+
+ if (!g_file_query_exists (dir, cancellable))
+ {
+ ret = TRUE;
+ goto out;
+ }
+
+ 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)))
+ {
+ 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)
+ goto out;
+
+ while ((child_info2 = g_file_enumerator_next_file (dir_enum2, cancellable, &temp_error)))
+ {
+ const char *branch;
+
+ if (g_file_info_get_file_type (child_info2) == G_FILE_TYPE_DIRECTORY)
+ {
+ branch = g_file_info_get_name (child_info2);
+ g_ptr_array_add (refs,
+ g_strdup_printf ("%s/%s/%s/%s", kind, name, arch, branch));
+ }
+
+ g_clear_object (&child_info2);
+ }
+
+
+ if (temp_error != NULL)
+ goto out;
+
+ g_clear_object (&child_info);
+ }
+
+ if (temp_error != NULL)
+ goto out;
+
+ g_ptr_array_sort (refs, flatpak_strcmp0_ptr);
+
+ ret = TRUE;
+
+out:
+ if (ret)
+ {
+ g_ptr_array_add (refs, NULL);
+ *refs_out = (char **) g_ptr_array_free (refs, FALSE);
+ refs = NULL;
+ }
+
+ if (temp_error != NULL)
+ g_propagate_error (error, temp_error);
+
+ return ret;
+}
+
+gboolean
+flatpak_dir_list_refs (FlatpakDir *self,
+ const char *kind,
+ char ***refs_out,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+
+ g_autoptr(GFile) base;
+ g_autoptr(GFileEnumerator) dir_enum = NULL;
+ g_autoptr(GFileInfo) child_info = NULL;
+ GError *temp_error = NULL;
+ g_autoptr(GPtrArray) refs = NULL;
+
+ refs = g_ptr_array_new ();
+
+ base = g_file_get_child (flatpak_dir_get_path (self), kind);
+
+ if (!g_file_query_exists (base, cancellable))
+ {
+ ret = TRUE;
+ goto out;
+ }
+
+ dir_enum = g_file_enumerate_children (base, 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)))
+ {
+ gchar **sub_refs = NULL;
+ const char *name;
+ int i;
+
+ if (g_file_info_get_file_type (child_info) != G_FILE_TYPE_DIRECTORY)
+ {
+ g_clear_object (&child_info);
+ continue;
+ }
+
+ name = g_file_info_get_name (child_info);
+
+ if (!flatpak_dir_list_refs_for_name (self, kind, name, &sub_refs, cancellable, error))
+ goto out;
+
+ for (i = 0; sub_refs[i] != NULL; i++)
+ g_ptr_array_add (refs, sub_refs[i]);
+ g_free (sub_refs);
+
+ g_clear_object (&child_info);
+ }
+
+ if (temp_error != NULL)
+ goto out;
+
+ ret = TRUE;
+
+ g_ptr_array_sort (refs, flatpak_strcmp0_ptr);
+
+out:
+ if (ret)
+ {
+ g_ptr_array_add (refs, NULL);
+ *refs_out = (char **) g_ptr_array_free (refs, FALSE);
+ refs = NULL;
+ }
+
+ if (temp_error != NULL)
+ g_propagate_error (error, temp_error);
+
+ return ret;
+}
+
+char *
+flatpak_dir_read_latest (FlatpakDir *self,
+ const char *remote,
+ const char *ref,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autofree char *remote_and_ref = NULL;
+ char *res = NULL;
+
+ /* There may be several remotes with the same branch (if we for
+ * instance changed the origin, so prepend the current origin to
+ * make sure we get the right one */
+
+ if (remote)
+ remote_and_ref = g_strdup_printf ("%s:%s", remote, ref);
+ else
+ remote_and_ref = g_strdup (ref);
+
+ if (!ostree_repo_resolve_rev (self->repo, remote_and_ref, FALSE, &res, error))
+ return NULL;
+
+ return res;
+}
+
+
+char *
+flatpak_dir_read_active (FlatpakDir *self,
+ const char *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,
+ const char *ref,
+ const char *checksum,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+
+ g_autoptr(GFile) deploy_base = NULL;
+ g_autofree char *tmpname = NULL;
+ g_autoptr(GFile) active_tmp_link = NULL;
+ g_autoptr(GFile) active_link = NULL;
+ g_autoptr(GError) my_error = NULL;
+
+ deploy_base = flatpak_dir_get_deploy_dir (self, ref);
+ active_link = g_file_get_child (deploy_base, "active");
+
+ if (checksum != NULL)
+ {
+ tmpname = gs_fileutil_gen_tmp_name (".active-", NULL);
+ active_tmp_link = g_file_get_child (deploy_base, tmpname);
+ if (!g_file_make_symbolic_link (active_tmp_link, checksum, cancellable, error))
+ goto out;
+
+ if (!gs_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;
+}
+
+
+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;
+
+ triggerspath = g_getenv ("FLATPAK_TRIGGERSDIR");
+ if (triggerspath == NULL)
+ triggerspath = FLATPAK_TRIGGERDIR;
+
+ g_debug ("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"))
+ {
+ g_autoptr(GPtrArray) argv_array = NULL;
+
+ g_debug ("running trigger %s", name);
+
+ argv_array = g_ptr_array_new_with_free_func (g_free);
+#ifdef DISABLE_SANDBOXED_TRIGGERS
+ g_ptr_array_add (argv_array, g_file_get_path (child));
+ g_ptr_array_add (argv_array, g_file_get_path (self->basedir));
+#else
+ g_ptr_array_add (argv_array, g_strdup (flatpak_get_bwrap ()));
+ g_ptr_array_add (argv_array, g_strdup ("--unshare-ipc"));
+ g_ptr_array_add (argv_array, g_strdup ("--unshare-net"));
+ g_ptr_array_add (argv_array, g_strdup ("--unshare-pid"));
+ g_ptr_array_add (argv_array, g_strdup ("--ro-bind"));
+ g_ptr_array_add (argv_array, g_strdup ("/"));
+ g_ptr_array_add (argv_array, g_strdup ("/"));
+ g_ptr_array_add (argv_array, g_strdup ("--proc"));
+ g_ptr_array_add (argv_array, g_strdup ("/proc"));
+ g_ptr_array_add (argv_array, g_strdup ("--dev"));
+ g_ptr_array_add (argv_array, g_strdup ("/dev"));
+ g_ptr_array_add (argv_array, g_strdup ("--bind"));
+ g_ptr_array_add (argv_array, g_file_get_path (self->basedir));
+ g_ptr_array_add (argv_array, g_file_get_path (self->basedir));
+#endif
+ g_ptr_array_add (argv_array, g_file_get_path (child));
+ g_ptr_array_add (argv_array, g_file_get_path (self->basedir));
+ g_ptr_array_add (argv_array, NULL);
+
+ if (!g_spawn_sync ("/",
+ (char **) argv_array->pdata,
+ NULL,
+ G_SPAWN_DEFAULT,
+ NULL, NULL,
+ 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 (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 (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);
+}
+
+static gboolean
+export_desktop_file (const char *app,
+ const char *branch,
+ const char *arch,
+ GKeyFile *metadata,
+ int parent_fd,
+ const char *name,
+ struct stat *stat_buf,
+ char **target,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ glnx_fd_close int desktop_fd = -1;
+ g_autofree char *tmpfile_name = NULL;
+
+ 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_autofree gchar *old_exec = NULL;
+ gint old_argc;
+ g_auto(GStrv) old_argv = NULL;
+ g_auto(GStrv) groups = NULL;
+ GString *new_exec = 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;
+
+ if (!gs_file_openat_noatime (parent_fd, name, &desktop_fd, cancellable, error))
+ goto out;
+
+ if (!read_fd (desktop_fd, stat_buf, &data, &data_len, error))
+ goto out;
+
+ keyfile = g_key_file_new ();
+ if (!g_key_file_load_from_data (keyfile, data, data_len, G_KEY_FILE_KEEP_TRANSLATIONS, error))
+ goto out;
+
+ 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)
+ {
+ flatpak_fail (error, "dbus service file %s has wrong name", name);
+ return FALSE;
+ }
+ }
+
+ 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,
+ "Desktop Entry",
+ "X-Flatpak-Tags",
+ (const char * const *) tags, length);
+ }
+ }
+
+ groups = g_key_file_get_groups (keyfile, NULL);
+
+ for (i = 0; groups[i] != NULL; i++)
+ {
+ 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 ("");
+ g_string_append_printf (new_exec, FLATPAK_BINDIR "/xdg-app run --branch=%s --arch=%s", escaped_branch, escaped_arch);
+
+ 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 i;
+ g_autofree char *command = maybe_quote (old_argv[0]);
+
+ g_string_append_printf (new_exec, " --command=%s", command);
+
+ g_string_append (new_exec, " ");
+ g_string_append (new_exec, escaped_app);
+
+ for (i = 1; i < old_argc; i++)
+ {
+ g_autofree char *arg = maybe_quote (old_argv[i]);
+ g_string_append (new_exec, " ");
+ g_string_append (new_exec, 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)
+ goto out;
+
+ if (!gs_file_open_in_tmpdir_at (parent_fd, 0755, &tmpfile_name, &out_stream, cancellable, error))
+ goto out;
+
+ if (!g_output_stream_write_all (out_stream, new_data, new_data_len, NULL, cancellable, error))
+ goto out;
+
+ if (!g_output_stream_close (out_stream, cancellable, error))
+ goto out;
+
+ if (target)
+ *target = g_steal_pointer (&tmpfile_name);
+
+ ret = TRUE;
+out:
+
+ if (new_exec != NULL)
+ g_string_free (new_exec, TRUE);
+
+ return ret;
+}
+
+static gboolean
+rewrite_export_dir (const char *app,
+ const char *branch,
+ const char *arch,
+ GKeyFile *metadata,
+ int source_parent_fd,
+ const char *source_name,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+
+ g_auto(GLnxDirFdIterator) source_iter = {0};
+ g_autoptr(GHashTable) visited_children = NULL;
+ struct dirent *dent;
+
+ if (!glnx_dirfd_iterator_init_at (source_parent_fd, source_name, FALSE, &source_iter, error))
+ goto out;
+
+ 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))
+ {
+ if (!rewrite_export_dir (app, branch, arch, metadata,
+ source_iter.fd, dent->d_name,
+ cancellable, error))
+ goto out;
+ }
+ else if (S_ISREG (stbuf.st_mode))
+ {
+ if (!flatpak_has_name_prefix (dent->d_name, app))
+ {
+ g_warning ("Non-prefixed filename %s in app %s, removing.\n", 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"))
+ {
+ g_autofree gchar *new_name = NULL;
+
+ if (!export_desktop_file (app, branch, arch, metadata,
+ source_iter.fd, dent->d_name, &stbuf, &new_name, cancellable, error))
+ goto out;
+
+ 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\n", 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;
+}
+
+gboolean
+flatpak_rewrite_export_dir (const char *app,
+ const char *branch,
+ const char *arch,
+ GKeyFile *metadata,
+ GFile *source,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+
+ /* The fds are closed by this call */
+ if (!rewrite_export_dir (app, branch, arch, metadata,
+ AT_FDCWD, gs_file_get_path_cached (source),
+ 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_fd_close 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 (!gs_file_open_dir_fd_at (destination_parent_fd, destination_name,
+ &destination_dfd,
+ cancellable, 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 gchar *target = NULL;
+
+ target = g_build_filename (source_symlink_prefix, dent->d_name, NULL);
+
+ if (unlinkat (destination_dfd, dent->d_name, 0) != 0 && errno != ENOENT)
+ {
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+
+ if (symlinkat (target, destination_dfd, dent->d_name) != 0)
+ {
+ glnx_set_error_from_errno (error);
+ goto out;
+ }
+ }
+ }
+
+ ret = TRUE;
+out:
+
+ return ret;
+}
+
+gboolean
+flatpak_export_dir (GFile *source,
+ GFile *destination,
+ const char *symlink_prefix,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+
+ if (!gs_file_ensure_directory (destination, TRUE, cancellable, error))
+ goto out;
+
+ /* The fds are closed by this call */
+ if (!export_dir (AT_FDCWD, gs_file_get_path_cached (source), symlink_prefix, "",
+ AT_FDCWD, gs_file_get_path_cached (destination),
+ cancellable, error))
+ goto out;
+
+ ret = TRUE;
+
+out:
+ return ret;
+}
+
+gboolean
+flatpak_dir_update_exports (FlatpakDir *self,
+ const char *changed_app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+
+ g_autoptr(GFile) exports = NULL;
+ g_autofree char *current_ref = NULL;
+ g_autofree char *active_id = NULL;
+ g_autofree char *symlink_prefix = NULL;
+
+ exports = flatpak_dir_get_exports_dir (self);
+
+ if (!gs_file_ensure_directory (exports, TRUE, 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;
+
+ if (!flatpak_dir_run_triggers (self, cancellable, error))
+ goto out;
+
+ ret = TRUE;
+
+out:
+ return ret;
+}
+
+gboolean
+flatpak_dir_deploy (FlatpakDir *self,
+ const char *origin,
+ const char *ref,
+ const char *checksum_or_latest,
+ const char * const * subpaths,
+ GVariant *old_deploy_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autofree char *resolved_ref = NULL;
+
+ g_autoptr(GFile) root = NULL;
+ g_autoptr(GFileInfo) file_info = NULL;
+ g_autoptr(GFile) deploy_base = NULL;
+ g_autoptr(GFile) checkoutdir = NULL;
+ g_autoptr(GFile) real_checkoutdir = NULL;
+ g_autoptr(GFile) dotref = NULL;
+ g_autoptr(GFile) files_etc = NULL;
+ g_autoptr(GFile) metadata = NULL;
+ g_autoptr(GFile) deploy_data_file = NULL;
+ g_autoptr(GVariant) deploy_data = NULL;
+ g_autoptr(GFile) export = NULL;
+ g_autoptr(GKeyFile) keyfile = NULL;
+ guint64 installed_size = 0;
+ const char *checksum;
+ g_autoptr(GFile) tmp_dir_template = NULL;
+ g_autofree char *tmp_dir_path = NULL;
+
+ if (!flatpak_dir_ensure_repo (self, cancellable, error))
+ return FALSE;
+
+ deploy_base = flatpak_dir_get_deploy_dir (self, ref);
+
+ if (checksum_or_latest == NULL)
+ {
+ g_debug ("No checksum specified, getting tip of %s", ref);
+
+ resolved_ref = flatpak_dir_read_latest (self, origin, ref, cancellable, error);
+ if (resolved_ref == NULL)
+ {
+ g_prefix_error (error, "While trying to resolve ref %s: ", ref);
+ return FALSE;
+ }
+
+ checksum = resolved_ref;
+ g_debug ("tip resolved to: %s", checksum);
+ }
+ else
+ {
+ g_autoptr(GFile) root = NULL;
+ g_autofree char *commit = NULL;
+
+ checksum = checksum_or_latest;
+ g_debug ("Looking for checksum %s in local repo", checksum);
+ if (!ostree_repo_read_commit (self->repo, checksum, &root, &commit, cancellable, NULL))
+ return flatpak_fail (error, "%s is not available", ref);
+ }
+
+ real_checkoutdir = g_file_get_child (deploy_base, checksum);
+ if (g_file_query_exists (real_checkoutdir, cancellable))
+ {
+ g_set_error (error, FLATPAK_DIR_ERROR,
+ FLATPAK_DIR_ERROR_ALREADY_DEPLOYED,
+ "%s branch %s already deployed", ref, checksum);
+ return FALSE;
+ }
+
+ g_autofree char *template = g_strdup_printf (".%s-XXXXXX", checksum);
+ tmp_dir_template = g_file_get_child (deploy_base, template);
+ tmp_dir_path = g_file_get_path (tmp_dir_template);
+
+ if (g_mkdtemp (tmp_dir_path) == NULL)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Can't create deploy directory");
+ return FALSE;
+ }
+
+ checkoutdir = g_file_new_for_path (tmp_dir_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;
+
+ file_info = g_file_query_info (root, OSTREE_GIO_FAST_QUERYINFO,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ cancellable, error);
+ if (file_info == NULL)
+ return FALSE;
+
+ if (subpaths == NULL || *subpaths == NULL)
+ {
+ if (!ostree_repo_checkout_tree (self->repo,
+ OSTREE_REPO_CHECKOUT_MODE_USER,
+ OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES,
+ checkoutdir,
+ OSTREE_REPO_FILE (root), file_info,
+ cancellable, error))
+ {
+ g_autofree char *rootpath = NULL;
+ g_autofree char *checkoutpath = NULL;
+
+ rootpath = g_file_get_path (root);
+ checkoutpath = g_file_get_path (checkoutdir);
+ g_prefix_error (error, "While trying to checkout %s into %s: ", rootpath, checkoutpath);
+ return FALSE;
+ }
+ }
+ else
+ {
+ OstreeRepoCheckoutOptions options = { 0, };
+ g_autofree char *checkoutdirpath = g_file_get_path (checkoutdir);
+ 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.mode = OSTREE_REPO_CHECKOUT_MODE_USER;
+ options.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES;
+ options.subpath = "/metadata";
+
+ access ("checkout metadata", 0);
+ if (!ostree_repo_checkout_tree_at (self->repo, &options,
+ AT_FDCWD, checkoutdirpath,
+ 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);
+ if (g_mkdir_with_parents (dstpath_parent, 0755))
+ {
+ glnx_set_error_from_errno (error);
+ return FALSE;
+ }
+
+ options.subpath = subpath;
+ if (!ostree_repo_checkout_tree_at (self->repo, &options,
+ AT_FDCWD, dstpath,
+ checksum,
+ cancellable, error))
+ {
+ g_prefix_error (error, "While trying to checkout metadata subpath: ");
+ 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 TRUE;
+
+ /* Ensure that various files exists 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))
+ {
+ char *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 TRUE;
+
+ if (!g_file_make_symbolic_link (etc_resolve_conf,
+ "/run/host/monitor/resolv.conf",
+ cancellable, error))
+ return FALSE;
+ }
+
+ keyfile = g_key_file_new ();
+ metadata = g_file_get_child (checkoutdir, "metadata");
+ if (g_file_query_exists (metadata, cancellable))
+ {
+ g_autofree char *path = g_file_get_path (metadata);
+
+ if (!g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, error))
+ return FALSE;
+ }
+
+ export = g_file_get_child (checkoutdir, "export");
+ if (g_file_query_exists (export, cancellable))
+ {
+ g_auto(GStrv) ref_parts = NULL;
+
+ ref_parts = g_strsplit (ref, "/", -1);
+
+ if (!flatpak_rewrite_export_dir (ref_parts[1], ref_parts[3], ref_parts[2],
+ keyfile, export,
+ cancellable,
+ error))
+ return FALSE;
+ }
+
+ deploy_data = flatpak_dir_new_deploy_data (origin,
+ checksum,
+ (char **) subpaths,
+ installed_size,
+ NULL);
+
+ deploy_data_file = g_file_get_child (checkoutdir, "deploy");
+ if (!flatpak_variant_save (deploy_data_file, deploy_data, cancellable, error))
+ return FALSE;
+
+ if (!g_file_move (checkoutdir, real_checkoutdir, G_FILE_COPY_NO_FALLBACK_FOR_MOVE,
+ cancellable, NULL, NULL, error))
+ return FALSE;
+
+ if (!flatpak_dir_set_active (self, ref, checksum, cancellable, error))
+ return FALSE;
+
+ return TRUE;
+}
+
+gboolean
+flatpak_dir_deploy_install (FlatpakDir *self,
+ const char *ref,
+ const char *origin,
+ char **subpaths,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_auto(GLnxLockFile) lock = GLNX_LOCK_FILE_INIT;
+ g_autoptr(GFile) deploy_base = NULL;
+ gboolean created_deploy_base = FALSE;
+ gboolean ret = FALSE;
+ g_autoptr(GError) local_error = NULL;
+ g_auto(GStrv) ref_parts = g_strsplit (ref, "/", -1);
+
+ if (!flatpak_dir_lock (self, &lock,
+ cancellable, error))
+ 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_set_error (error,
+ G_IO_ERROR, G_IO_ERROR_EXISTS,
+ "%s branch %s already installed",
+ ref_parts[1], ref_parts[3]);
+ }
+ else
+ {
+ 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, NULL, cancellable, error))
+ goto out;
+
+ if (g_str_has_prefix (ref, "app/"))
+ {
+
+ if (!flatpak_dir_make_current_ref (self, ref, cancellable, error))
+ goto out;
+
+ if (!flatpak_dir_update_exports (self, ref_parts[1], cancellable, error))
+ goto out;
+ }
+
+ /* 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;
+
+ ret = TRUE;
+
+out:
+ if (created_deploy_base && !ret)
+ gs_shutil_rm_rf (deploy_base, cancellable, NULL);
+
+ return ret;
+}
+
+
+gboolean
+flatpak_dir_deploy_update (FlatpakDir *self,
+ const char *ref,
+ const char *checksum_or_latest,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autofree char *previous_deployment = NULL;
+
+ g_autoptr(GError) my_error = NULL;
+ g_autoptr(GVariant) old_deploy_data = NULL;
+ g_auto(GLnxLockFile) lock = GLNX_LOCK_FILE_INIT;
+ g_autofree const char **old_subpaths = NULL;
+ const char *old_active;
+ const char *old_origin;
+
+ if (!flatpak_dir_lock (self, &lock,
+ cancellable, error))
+ return FALSE;
+
+ old_deploy_data = flatpak_dir_get_deploy_data (self, ref,
+ cancellable, error);
+ if (old_deploy_data == NULL)
+ return FALSE;
+
+ old_origin = flatpak_deploy_data_get_origin (old_deploy_data);
+ old_active = flatpak_deploy_data_get_commit (old_deploy_data);
+ old_subpaths = flatpak_deploy_data_get_subpaths (old_deploy_data);
+ if (!flatpak_dir_deploy (self,
+ old_origin,
+ ref,
+ checksum_or_latest,
+ old_subpaths,
+ old_deploy_data,
+ cancellable, &my_error))
+ {
+ if (g_error_matches (my_error, FLATPAK_DIR_ERROR,
+ FLATPAK_DIR_ERROR_ALREADY_DEPLOYED))
+ return TRUE;
+
+ g_propagate_error (error, my_error);
+ return FALSE;
+ }
+
+ if (!flatpak_dir_undeploy (self, ref, old_active,
+ FALSE,
+ cancellable, error))
+ return FALSE;
+
+ if (g_str_has_prefix (ref, "app/"))
+ {
+ g_auto(GStrv) ref_parts = g_strsplit (ref, "/", -1);
+
+ if (!flatpak_dir_update_exports (self, ref_parts[1], cancellable, error))
+ return FALSE;
+ }
+
+ /* Release lock before doing possibly slow prune */
+ glnx_release_lock_file (&lock);
+
+ if (!flatpak_dir_prune (self, cancellable, error))
+ return FALSE;
+
+ if (!flatpak_dir_mark_changed (self, error))
+ return FALSE;
+
+ flatpak_dir_cleanup_removed (self, cancellable, NULL);
+
+ return TRUE;
+}
+
+static OstreeRepo *
+flatpak_dir_create_system_child_repo (FlatpakDir *self,
+ GLnxLockFile *file_lock,
+ GError **error)
+{
+ g_autoptr(GFile) cache_dir = NULL;
+ 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_assert (!self->user);
+
+ if (!flatpak_dir_ensure_repo (self, NULL, error))
+ return NULL;
+
+ cache_dir = flatpak_ensure_user_cache_dir_location (error);
+ if (cache_dir == NULL)
+ return NULL;
+
+ if (!flatpak_allocate_tmpdir (AT_FDCWD,
+ gs_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,
+ OSTREE_REPO_MODE_BARE_USER,
+ NULL, error))
+ return NULL;
+ }
+ else
+ {
+ if (!ostree_repo_open (new_repo, NULL, error))
+ return NULL;
+ }
+
+ /* Ensure the config is updated */
+ config = ostree_repo_copy_config (new_repo);
+ g_key_file_set_string (config, "core", "parent",
+ gs_file_get_path_cached (ostree_repo_get_path (self->repo)));
+
+ if (!ostree_repo_write_config (new_repo, config, error))
+ return NULL;
+
+ /* We need to reopen to apply the parent config */
+ repo = system_ostree_repo_new (repo_dir);
+ if (!ostree_repo_open (repo, NULL, error))
+ return NULL;
+
+ return g_steal_pointer (&repo);
+}
+
+gboolean
+flatpak_dir_install (FlatpakDir *self,
+ gboolean no_pull,
+ gboolean no_deploy,
+ const char *ref,
+ const char *remote_name,
+ char **subpaths,
+ OstreeAsyncProgress *progress,
+ GCancellable *cancellable,
+ GError **error)
+{
+ if (flatpak_dir_use_child_repo (self))
+ {
+ g_autoptr(OstreeRepo) child_repo = NULL;
+ g_auto(GLnxLockFile) child_repo_lock = GLNX_LOCK_FILE_INIT;
+ char *empty_subpaths[] = {NULL};
+ XdgAppSystemHelper *system_helper;
+
+ if (no_pull)
+ return flatpak_fail (error, "No-pull install not supported without root permissions");
+
+ if (no_deploy)
+ return flatpak_fail (error, "No-deploy install not supported without root permissions");
+
+ child_repo = flatpak_dir_create_system_child_repo (self, &child_repo_lock, error);
+ if (child_repo == NULL)
+ return FALSE;
+
+ system_helper = flatpak_dir_get_system_helper (self);
+
+ g_assert (system_helper != NULL);
+
+ if (!flatpak_dir_pull (self, remote_name, ref, subpaths,
+ child_repo, OSTREE_REPO_PULL_FLAGS_MIRROR,
+ progress, cancellable, error))
+ return FALSE;
+
+ if (!xdg_app_system_helper_call_deploy_sync (system_helper,
+ gs_file_get_path_cached (ostree_repo_get_path (child_repo)),
+ FLATPAK_HELPER_DEPLOY_FLAGS_NONE,
+ ref,
+ remote_name,
+ (const char * const *) (subpaths ? subpaths : empty_subpaths),
+ cancellable,
+ error))
+ return FALSE;
+
+ (void) glnx_shutil_rm_rf_at (AT_FDCWD,
+ gs_file_get_path_cached (ostree_repo_get_path (child_repo)),
+ NULL, NULL);
+
+ return TRUE;
+ }
+
+
+ if (!no_pull)
+ {
+ if (!flatpak_dir_pull (self, remote_name, ref, subpaths, NULL, OSTREE_REPO_PULL_FLAGS_NONE, progress,
+ cancellable, error))
+ return FALSE;
+ }
+
+ if (!no_deploy)
+ {
+ if (!flatpak_dir_deploy_install (self, ref, remote_name, subpaths,
+ cancellable, error))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+flatpak_dir_update (FlatpakDir *self,
+ gboolean no_pull,
+ gboolean no_deploy,
+ const char *ref,
+ const char *remote_name,
+ const char *checksum_or_latest,
+ char **subpaths,
+ OstreeAsyncProgress *progress,
+ GCancellable *cancellable,
+ GError **error)
+{
+ if (flatpak_dir_use_child_repo (self))
+ {
+ g_autoptr(OstreeRepo) child_repo = NULL;
+ g_auto(GLnxLockFile) child_repo_lock = GLNX_LOCK_FILE_INIT;
+ char *empty_subpaths[] = {NULL};
+ g_autofree char *pulled_checksum = NULL;
+ g_autofree char *active_checksum = NULL;
+ XdgAppSystemHelper *system_helper;
+
+ if (no_pull)
+ return flatpak_fail (error, "No-pull update not supported without root permissions");
+
+ if (no_deploy)
+ return flatpak_fail (error, "No-deploy update not supported without root permissions");
+
+ if (checksum_or_latest != NULL)
+ return flatpak_fail (error, "Can't update to a specific commit without root permissions");
+
+ child_repo = flatpak_dir_create_system_child_repo (self, &child_repo_lock, error);
+ if (child_repo == NULL)
+ return FALSE;
+
+ system_helper = flatpak_dir_get_system_helper (self);
+
+ g_assert (system_helper != NULL);
+
+ if (!flatpak_dir_pull (self, remote_name, ref, subpaths,
+ child_repo, OSTREE_REPO_PULL_FLAGS_MIRROR,
+ progress, cancellable, error))
+ return FALSE;
+
+ if (!ostree_repo_resolve_rev (child_repo, ref, FALSE, &pulled_checksum, error))
+ return FALSE;
+
+ active_checksum = flatpak_dir_read_active (self, ref, NULL);
+ if (g_strcmp0 (active_checksum, pulled_checksum) != 0)
+ {
+
+ if (!xdg_app_system_helper_call_deploy_sync (system_helper,
+ gs_file_get_path_cached (ostree_repo_get_path (child_repo)),
+ FLATPAK_HELPER_DEPLOY_FLAGS_UPDATE,
+ ref,
+ remote_name,
+ (const char * const *) empty_subpaths,
+ cancellable,
+ error))
+ return FALSE;
+ }
+
+ (void) glnx_shutil_rm_rf_at (AT_FDCWD,
+ gs_file_get_path_cached (ostree_repo_get_path (child_repo)),
+ NULL, NULL);
+
+ return TRUE;
+ }
+
+
+ if (!no_pull)
+ {
+ if (!flatpak_dir_pull (self, remote_name, ref, subpaths,
+ NULL, OSTREE_REPO_PULL_FLAGS_NONE, progress,
+ cancellable, error))
+ return FALSE;
+ }
+
+ if (!no_deploy)
+ {
+ if (!flatpak_dir_deploy_update (self, ref, checksum_or_latest,
+ cancellable, error))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+
+gboolean
+flatpak_dir_collect_deployed_refs (FlatpakDir *self,
+ const char *type,
+ const char *name_prefix,
+ const char *branch,
+ const char *arch,
+ 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;
+
+ 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, branch);
+ g_autoptr(GFile) child3 = g_file_get_child (child2, arch);
+ g_autoptr(GFile) active = g_file_get_child (child3, "active");
+
+ if (g_file_query_exists (active, 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,
+ const char *ref,
+ char ***deployed_checksums,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+
+ g_autoptr(GFile) deploy_base = NULL;
+ g_autoptr(GPtrArray) checksums = 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);
+
+ checksums = 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 (checksums, 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 (checksums, NULL);
+ *deployed_checksums = (char **) g_ptr_array_free (g_steal_pointer (&checksums), FALSE);
+ }
+
+ return ret;
+
+}
+
+static gboolean
+dir_is_locked (GFile *dir)
+{
+ glnx_fd_close 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 (gs_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,
+ const char *ref,
+ const char *checksum,
+ gboolean force_remove,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+
+ 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 *tmpname = NULL;
+ g_autofree char *active = NULL;
+ int i;
+
+ g_assert (ref != NULL);
+ g_assert (checksum != NULL);
+
+ deploy_base = flatpak_dir_get_deploy_dir (self, ref);
+
+ checkoutdir = g_file_get_child (deploy_base, checksum);
+ if (!g_file_query_exists (checkoutdir, cancellable))
+ {
+ g_set_error (error, FLATPAK_DIR_ERROR,
+ FLATPAK_DIR_ERROR_ALREADY_UNDEPLOYED,
+ "%s branch %s already undeployed", ref, checksum);
+ goto out;
+ }
+
+ if (!flatpak_dir_ensure_repo (self, cancellable, error))
+ goto out;
+
+ active = flatpak_dir_read_active (self, ref, cancellable);
+ if (active != NULL && strcmp (active, checksum) == 0)
+ {
+ g_auto(GStrv) deployed_checksums = 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_checksums,
+ cancellable, error))
+ goto out;
+
+ some_deployment = NULL;
+ for (i = 0; deployed_checksums[i] != NULL; i++)
+ {
+ if (strcmp (deployed_checksums[i], checksum) == 0)
+ continue;
+
+ some_deployment = deployed_checksums[i];
+ break;
+ }
+
+ if (!flatpak_dir_set_active (self, ref, some_deployment, cancellable, error))
+ goto out;
+ }
+
+ removed_dir = flatpak_dir_get_removed_dir (self);
+ if (!gs_file_ensure_directory (removed_dir, TRUE, cancellable, error))
+ goto out;
+
+ tmpname = gs_fileutil_gen_tmp_name ("", checksum);
+ removed_subdir = g_file_get_child (removed_dir, tmpname);
+
+ if (!gs_file_rename (checkoutdir,
+ removed_subdir,
+ cancellable, error))
+ goto out;
+
+ if (force_remove || !dir_is_locked (removed_subdir))
+ {
+ GError *tmp_error = NULL;
+
+ if (!gs_shutil_rm_rf (removed_subdir, cancellable, &tmp_error))
+ {
+ g_warning ("Unable to remove old checkout: %s\n", tmp_error->message);
+ g_error_free (tmp_error);
+ }
+ }
+
+ ret = TRUE;
+out:
+ return ret;
+}
+
+gboolean
+flatpak_dir_undeploy_all (FlatpakDir *self,
+ const char *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_debug ("undeploying %s", deployed[i]);
+ if (!flatpak_dir_undeploy (self, ref, deployed[i], 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_debug ("removing deploy base");
+ if (!gs_shutil_rm_rf (deploy_base, cancellable, error))
+ return FALSE;
+ }
+
+ g_debug ("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;
+}
+
+gboolean
+flatpak_dir_remove_ref (FlatpakDir *self,
+ const char *remote_name,
+ const char *ref,
+ GCancellable *cancellable,
+ GError **error)
+{
+ 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))
+ {
+ GError *tmp_error = NULL;
+ if (!gs_shutil_rm_rf (child, cancellable, &tmp_error))
+ {
+ g_warning ("Unable to remove old checkout: %s\n", tmp_error->message);
+ g_error_free (tmp_error);
+ }
+ }
+
+ 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;
+
+ if (!flatpak_dir_ensure_repo (self, cancellable, error))
+ goto out;
+
+ 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_debug ("Pruned %d/%d objects, size %s", objects_total, objects_pruned, formatted_freed_size);
+
+ ret = TRUE;
+out:
+ return ret;
+
+}
+
+GFile *
+flatpak_dir_get_if_deployed (FlatpakDir *self,
+ const char *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_object_ref (deploy_dir);
+ return NULL;
+}
+
+static gboolean
+flatpak_dir_remote_fetch_summary (FlatpakDir *self,
+ const char *name,
+ GBytes **out_summary,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* TODO: Add in-memory cache here, also use for ostree_repo_list_refs */
+ if (!ostree_repo_remote_fetch_summary (self->repo, name,
+ out_summary, NULL,
+ cancellable,
+ error))
+ return FALSE;
+
+ return TRUE;
+}
+
+char *
+flatpak_dir_find_remote_ref (FlatpakDir *self,
+ const char *remote,
+ const char *name,
+ const char *opt_branch,
+ const char *opt_arch,
+ gboolean app,
+ gboolean runtime,
+ gboolean *is_app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autofree char *app_ref = NULL;
+ g_autofree char *runtime_ref = NULL;
+ g_autofree char *app_ref_with_remote = NULL;
+ g_autofree char *runtime_ref_with_remote = NULL;
+
+ g_autoptr(GVariant) summary = NULL;
+ g_autoptr(GVariant) refs = NULL;
+ g_autoptr(GBytes) summary_bytes = NULL;
+
+ if (!flatpak_dir_ensure_repo (self, NULL, error))
+ return NULL;
+
+ if (app)
+ {
+ app_ref = flatpak_compose_ref (TRUE, name, opt_branch, opt_arch, error);
+ if (app_ref == NULL)
+ return NULL;
+ app_ref_with_remote = g_strconcat (remote, ":", app_ref, NULL);
+ }
+
+ if (runtime)
+ {
+ runtime_ref = flatpak_compose_ref (FALSE, name, opt_branch, opt_arch, error);
+ if (runtime_ref == NULL)
+ return NULL;
+ runtime_ref_with_remote = g_strconcat (remote, ":", app_ref, NULL);
+ }
+
+ /* First look for a local ref */
+
+ if (app_ref &&
+ ostree_repo_resolve_rev (self->repo, app_ref_with_remote,
+ FALSE, NULL, NULL))
+ {
+ if (is_app)
+ *is_app = TRUE;
+ return g_steal_pointer (&app_ref);
+ }
+
+ if (runtime_ref &&
+ ostree_repo_resolve_rev (self->repo, runtime_ref_with_remote,
+ FALSE, NULL, NULL))
+ {
+ if (is_app)
+ *is_app = FALSE;
+ return g_steal_pointer (&runtime_ref);
+ }
+
+ if (!flatpak_dir_remote_fetch_summary (self, remote,
+ &summary_bytes,
+ cancellable, error))
+ return NULL;
+
+ if (summary_bytes == NULL)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Can't find %s in remote %s; server has no summary file", name, remote);
+ return NULL;
+ }
+
+ summary = g_variant_ref_sink (g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, summary_bytes, FALSE));
+ refs = g_variant_get_child_value (summary, 0);
+
+ if (app_ref && flatpak_summary_lookup_ref (summary, app_ref, NULL))
+ {
+ if (is_app)
+ *is_app = TRUE;
+ return g_steal_pointer (&app_ref);
+ }
+
+ if (runtime_ref && flatpak_summary_lookup_ref (summary, runtime_ref, NULL))
+ {
+ if (is_app)
+ *is_app = FALSE;
+ return g_steal_pointer (&runtime_ref);
+ }
+
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Can't find %s %s in remote %s", name, opt_branch ? opt_branch : "master", remote);
+
+ return NULL;
+}
+
+char *
+flatpak_dir_find_installed_ref (FlatpakDir *self,
+ const char *name,
+ const char *opt_branch,
+ const char *opt_arch,
+ gboolean app,
+ gboolean runtime,
+ gboolean *is_app,
+ GError **error)
+{
+ if (app)
+ {
+ g_autofree char *app_ref = NULL;
+ g_autoptr(GFile) deploy_base = NULL;
+
+ app_ref = flatpak_compose_ref (TRUE, name, opt_branch, opt_arch, error);
+ if (app_ref == NULL)
+ return NULL;
+
+
+ deploy_base = flatpak_dir_get_deploy_dir (self, app_ref);
+ if (g_file_query_exists (deploy_base, NULL))
+ {
+ if (is_app)
+ *is_app = TRUE;
+ return g_steal_pointer (&app_ref);
+ }
+ }
+
+ if (runtime)
+ {
+ g_autofree char *runtime_ref = NULL;
+ g_autoptr(GFile) deploy_base = NULL;
+
+ runtime_ref = flatpak_compose_ref (FALSE, name, opt_branch, opt_arch, error);
+ if (runtime_ref == NULL)
+ return NULL;
+
+ deploy_base = flatpak_dir_get_deploy_dir (self, runtime_ref);
+ if (g_file_query_exists (deploy_base, NULL))
+ {
+ if (is_app)
+ *is_app = FALSE;
+ return g_steal_pointer (&runtime_ref);
+ }
+ }
+
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "%s %s not installed", name, opt_branch ? opt_branch : "master");
+ return NULL;
+}
+
+FlatpakDir *
+flatpak_dir_new (GFile *path, gboolean user)
+{
+ return g_object_new (FLATPAK_TYPE_DIR, "path", path, "user", user, NULL);
+}
+
+FlatpakDir *
+flatpak_dir_clone (FlatpakDir *self)
+{
+ return flatpak_dir_new (self->basedir, self->user);
+}
+
+FlatpakDir *
+flatpak_dir_get_system (void)
+{
+ g_autoptr(GFile) path = flatpak_get_system_base_dir_location ();
+ return flatpak_dir_new (path, FALSE);
+}
+
+FlatpakDir *
+flatpak_dir_get_user (void)
+{
+ g_autoptr(GFile) path = flatpak_get_user_base_dir_location ();
+ return flatpak_dir_new (path, TRUE);
+}
+
+FlatpakDir *
+flatpak_dir_get (gboolean user)
+{
+ if (user)
+ return flatpak_dir_get_user ();
+ else
+ return flatpak_dir_get_system ();
+}
+
+static char *
+get_group (const char *remote_name)
+{
+ return g_strdup_printf ("remote \"%s\"", remote_name);
+}
+
+char *
+flatpak_dir_get_remote_title (FlatpakDir *self,
+ const char *remote_name)
+{
+ GKeyFile *config = ostree_repo_get_config (self->repo);
+ g_autofree char *group = get_group (remote_name);
+
+ if (config)
+ return g_key_file_get_string (config, group, "xa.title", NULL);
+
+ return NULL;
+}
+
+int
+flatpak_dir_get_remote_prio (FlatpakDir *self,
+ const char *remote_name)
+{
+ GKeyFile *config = ostree_repo_get_config (self->repo);
+ 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 = ostree_repo_get_config (self->repo);
+ 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_disabled (FlatpakDir *self,
+ const char *remote_name)
+{
+ GKeyFile *config = ostree_repo_get_config (self->repo);
+ g_autofree char *group = get_group (remote_name);
+
+ if (config)
+ return g_key_file_get_boolean (config, group, "xa.disable", NULL);
+
+ return TRUE;
+}
+
+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);
+
+ return prio_b - prio_a;
+}
+
+char *
+flatpak_dir_create_origin_remote (FlatpakDir *self,
+ const char *url,
+ const char *id,
+ const char *title,
+ GBytes *gpg_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autofree char *remote = NULL;
+
+ g_auto(GStrv) remotes = NULL;
+ int version = 0;
+ g_autoptr(GVariantBuilder) optbuilder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
+
+ if (!flatpak_dir_ensure_repo (self, cancellable, error))
+ return FALSE;
+
+ remotes = ostree_repo_remote_list (self->repo, NULL);
+
+ do
+ {
+ g_autofree char *name = NULL;
+ if (version == 0)
+ name = g_strdup_printf ("%s-origin", id);
+ else
+ name = g_strdup_printf ("%s-%d-origin", id, version);
+ version++;
+
+ if (remotes == NULL ||
+ !g_strv_contains ((const char * const *) remotes, name))
+ remote = g_steal_pointer (&name);
+ }
+ while (remote == NULL);
+
+ g_variant_builder_add (optbuilder, "{s@v}",
+ "xa.title",
+ g_variant_new_variant (g_variant_new_string (title)));
+
+ g_variant_builder_add (optbuilder, "{s@v}",
+ "xa.noenumerate",
+ g_variant_new_variant (g_variant_new_boolean (TRUE)));
+
+ g_variant_builder_add (optbuilder, "{s@v}",
+ "xa.prio",
+ g_variant_new_variant (g_variant_new_string ("0")));
+
+ if (!ostree_repo_remote_add (self->repo,
+ remote, url ? url : "", g_variant_builder_end (optbuilder), cancellable, error))
+ return NULL;
+
+ if (gpg_data)
+ {
+ g_autoptr(GInputStream) gpg_data_as_stream = g_memory_input_stream_new_from_bytes (gpg_data);
+
+ if (!ostree_repo_remote_gpg_import (self->repo, remote, gpg_data_as_stream,
+ NULL, NULL, cancellable, error))
+ {
+ ostree_repo_remote_delete (self->repo, remote,
+ NULL, NULL);
+ return NULL;
+ }
+ }
+
+ return g_steal_pointer (&remote);
+}
+
+
+char **
+flatpak_dir_list_remotes (FlatpakDir *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ char **res;
+
+ if (!flatpak_dir_ensure_repo (self, cancellable, error))
+ return NULL;
+
+ res = ostree_repo_remote_list (self->repo, NULL);
+ if (res == NULL)
+ res = g_new0 (char *, 1); /* Return empty array, not error */
+
+ g_qsort_with_data (res, g_strv_length (res), sizeof (char *),
+ cmp_remote, self);
+
+ return res;
+}
+
+static gboolean
+remove_unless_in_hash (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ GHashTable *table = user_data;
+
+ return !g_hash_table_contains (table, key);
+}
+
+gboolean
+flatpak_dir_list_remote_refs (FlatpakDir *self,
+ const char *remote,
+ GHashTable **refs,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GError) my_error = NULL;
+
+ if (error == NULL)
+ error = &my_error;
+
+ if (!flatpak_dir_ensure_repo (self, cancellable, error))
+ return FALSE;
+
+ if (!ostree_repo_remote_list_refs (self->repo, remote,
+ refs, cancellable, error))
+ return FALSE;
+
+ if (flatpak_dir_get_remote_noenumerate (self, remote))
+ {
+ g_autoptr(GHashTable) unprefixed_local_refs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+ g_autoptr(GHashTable) local_refs = NULL;
+ GHashTableIter hash_iter;
+ gpointer key;
+ g_autofree char *refspec_prefix = g_strconcat (remote, ":.", NULL);
+
+ /* For noenumerate remotes, only return data for already locally
+ * available refs */
+
+ if (!ostree_repo_list_refs (self->repo, refspec_prefix, &local_refs,
+ cancellable, error))
+ return FALSE;
+
+ /* First we need to unprefix the remote name from the local refs */
+ g_hash_table_iter_init (&hash_iter, local_refs);
+ while (g_hash_table_iter_next (&hash_iter, &key, NULL))
+ {
+ char *ref = NULL;
+ ostree_parse_refspec (key, NULL, &ref, NULL);
+
+ if (ref)
+ g_hash_table_insert (unprefixed_local_refs, ref, NULL);
+ }
+
+ /* Then we remove all remote refs not in the local refs set */
+ g_hash_table_foreach_remove (*refs,
+ remove_unless_in_hash,
+ unprefixed_local_refs);
+ }
+
+ return TRUE;
+}
+
+char *
+flatpak_dir_fetch_remote_title (FlatpakDir *self,
+ const char *remote,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GError) my_error = NULL;
+ g_autoptr(GBytes) summary_bytes = NULL;
+ g_autoptr(GVariant) summary = NULL;
+ g_autoptr(GVariant) extensions = NULL;
+ GVariantDict dict;
+ g_autofree char *title = NULL;
+
+ if (error == NULL)
+ error = &my_error;
+
+ if (!flatpak_dir_ensure_repo (self, cancellable, error))
+ return NULL;
+
+ if (!flatpak_dir_remote_fetch_summary (self, remote,
+ &summary_bytes,
+ cancellable, error))
+ return FALSE;
+
+ if (summary_bytes == NULL)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Remote title not available; server has no summary file");
+ return FALSE;
+ }
+
+ summary = g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT,
+ summary_bytes, FALSE);
+ extensions = g_variant_get_child_value (summary, 1);
+
+ g_variant_dict_init (&dict, extensions);
+ g_variant_dict_lookup (&dict, "xa.title", "s", &title);
+ g_variant_dict_end (&dict);
+
+ if (title == NULL)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Remote title not set");
+ return FALSE;
+ }
+
+ return g_steal_pointer (&title);
+}
+
+static void
+ensure_soup_session (FlatpakDir *self)
+{
+ const char *http_proxy;
+
+ if (g_once_init_enter (&self->soup_session))
+ {
+ SoupSession *soup_session;
+
+ soup_session =
+ soup_session_new_with_options (SOUP_SESSION_USER_AGENT, "ostree ",
+ SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE,
+ SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
+ SOUP_SESSION_TIMEOUT, 60,
+ SOUP_SESSION_IDLE_TIMEOUT, 60,
+ NULL);
+ http_proxy = g_getenv ("http_proxy");
+ if (http_proxy)
+ {
+ g_autoptr(SoupURI) proxy_uri = soup_uri_new (http_proxy);
+
+ if (!proxy_uri)
+ g_warning ("Invalid proxy URI '%s'", http_proxy);
+ else
+ g_object_set (soup_session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL);
+ }
+
+ if (g_getenv ("OSTREE_DEBUG_HTTP"))
+ soup_session_add_feature (soup_session, (SoupSessionFeature *) soup_logger_new (SOUP_LOGGER_LOG_BODY, 500));
+
+ g_once_init_leave (&self->soup_session, soup_session);
+ }
+}
+
+static GBytes *
+flatpak_dir_load_uri (FlatpakDir *self,
+ const char *uri,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autofree char *scheme = NULL;
+
+ g_autoptr(GBytes) bytes = NULL;
+
+ scheme = g_uri_parse_scheme (uri);
+ if (strcmp (scheme, "file") == 0)
+ {
+ char *buffer;
+ gsize length;
+ g_autoptr(GFile) file = NULL;
+
+ g_debug ("Loading %s using GIO", uri);
+
+ file = g_file_new_for_uri (uri);
+ if (!g_file_load_contents (file, cancellable, &buffer, &length, NULL, NULL))
+ return NULL;
+
+ bytes = g_bytes_new_take (buffer, length);
+ }
+ else if (strcmp (scheme, "http") == 0 ||
+ strcmp (scheme, "https") == 0)
+ {
+ g_autoptr(SoupMessage) msg = NULL;
+
+ ensure_soup_session (self);
+
+ g_debug ("Loading %s using libsoup", uri);
+ msg = soup_message_new ("GET", uri);
+ soup_session_send_message (self->soup_session, msg);
+
+ if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code))
+ {
+ GIOErrorEnum code;
+
+ switch (msg->status_code)
+ {
+ case 404:
+ case 410:
+ code = G_IO_ERROR_NOT_FOUND;
+ break;
+
+ default:
+ code = G_IO_ERROR_FAILED;
+ }
+
+ g_set_error (error, G_IO_ERROR, code,
+ "Server returned status %u: %s",
+ msg->status_code,
+ soup_status_get_phrase (msg->status_code));
+ return NULL;
+ }
+
+ bytes = g_bytes_new (msg->response_body->data, msg->response_body->length);
+ }
+ else
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Unsupported uri scheme %s", scheme);
+ return FALSE;
+ }
+
+ g_debug ("Received %" G_GSIZE_FORMAT " bytes", g_bytes_get_size (bytes));
+
+ return g_steal_pointer (&bytes);
+}
+
+GBytes *
+flatpak_dir_fetch_remote_object (FlatpakDir *self,
+ const char *remote_name,
+ const char *checksum,
+ const char *type,
+ 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;
+
+ if (!ostree_repo_remote_get_url (self->repo, remote_name, &base_url, error))
+ return NULL;
+
+ part1 = g_strndup (checksum, 2);
+ part2 = g_strdup_printf ("%s.%s", checksum + 2, type);
+
+ object_url = g_build_filename (base_url, "objects", part1, part2, NULL);
+
+ bytes = flatpak_dir_load_uri (self, object_url, cancellable, error);
+ if (bytes == NULL)
+ return NULL;
+
+ return g_steal_pointer (&bytes);
+}
+
+gboolean
+flatpak_dir_fetch_ref_cache (FlatpakDir *self,
+ const char *remote_name,
+ const char *ref,
+ guint64 *download_size,
+ guint64 *installed_size,
+ char **metadata,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GBytes) summary_bytes = NULL;
+ g_autoptr(GVariant) extensions = NULL;
+ g_autoptr(GVariant) summary = NULL;
+ g_autoptr(GVariant) cache_v = NULL;
+ g_autoptr(GVariant) cache = NULL;
+ g_autoptr(GVariant) res = NULL;
+
+ if (!flatpak_dir_ensure_repo (self, cancellable, error))
+ return FALSE;
+
+ if (!flatpak_dir_remote_fetch_summary (self, remote_name,
+ &summary_bytes,
+ cancellable, error))
+ return FALSE;
+
+ if (summary_bytes == NULL)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Data not available; server has no summary file");
+ return FALSE;
+ }
+
+ summary = g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT,
+ summary_bytes, FALSE);
+ extensions = g_variant_get_child_value (summary, 1);
+
+ cache_v = g_variant_lookup_value (extensions, "xa.cache", NULL);
+ if (cache_v == NULL)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Data not found");
+ return FALSE;
+ }
+
+ cache = g_variant_get_child_value (cache_v, 0);
+ res = g_variant_lookup_value (cache, ref, NULL);
+ if (res == NULL)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Data not found for ref %s", ref);
+ return FALSE;
+ }
+
+ if (installed_size)
+ {
+ guint64 v;
+ g_variant_get_child (res, 0, "t", &v);
+ *installed_size = GUINT64_FROM_BE (v);
+ }
+
+ if (download_size)
+ {
+ guint64 v;
+ g_variant_get_child (res, 1, "t", &v);
+ *download_size = GUINT64_FROM_BE (v);
+ }
+
+ if (metadata)
+ g_variant_get_child (res, 2, "s", metadata);
+
+ return TRUE;
+}
+
+GBytes *
+flatpak_dir_fetch_metadata (FlatpakDir *self,
+ const char *remote_name,
+ const char *commit,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GBytes) commit_bytes = NULL;
+ g_autoptr(GBytes) root_bytes = NULL;
+ g_autoptr(GBytes) filez_bytes = NULL;
+ g_autoptr(GVariant) commit_variant = NULL;
+ g_autoptr(GVariant) root_variant = NULL;
+ g_autoptr(GVariant) root_csum = NULL;
+ g_autoptr(GVariant) files_variant = NULL;
+ g_autofree char *file_checksum = NULL;
+ g_autofree char *root_checksum = NULL;
+ g_autoptr(GConverter) zlib_decomp = NULL;
+ g_autoptr(GInputStream) zlib_input = NULL;
+ g_autoptr(GMemoryOutputStream) data_stream = NULL;
+ g_autoptr(GMemoryInputStream) dataz_stream = NULL;
+ gsize filez_size;
+ const guchar *filez_data;
+ guint32 archive_header_size;
+ int i, n;
+
+ commit_bytes = flatpak_dir_fetch_remote_object (self, remote_name,
+ commit, "commit",
+ cancellable, error);
+ if (commit_bytes == NULL)
+ return NULL;
+
+ commit_variant = g_variant_new_from_bytes (OSTREE_COMMIT_GVARIANT_FORMAT,
+ commit_bytes, FALSE);
+
+ if (!ostree_validate_structureof_commit (commit_variant, error))
+ return NULL;
+
+ g_variant_get_child (commit_variant, 6, "@ay", &root_csum);
+ root_checksum = ostree_checksum_from_bytes_v (root_csum);
+
+ root_bytes = flatpak_dir_fetch_remote_object (self, remote_name,
+ root_checksum, "dirtree",
+ cancellable, error);
+ if (root_bytes == NULL)
+ return NULL;
+
+ root_variant = g_variant_new_from_bytes (OSTREE_TREE_GVARIANT_FORMAT,
+ root_bytes, FALSE);
+
+ if (!ostree_validate_structureof_dirtree (root_variant, error))
+ return NULL;
+
+ files_variant = g_variant_get_child_value (root_variant, 0);
+
+ n = g_variant_n_children (files_variant);
+ for (i = 0; i < n; i++)
+ {
+ const char *filename;
+ g_autoptr(GVariant) csum = NULL;
+
+ g_variant_get_child (files_variant, i, "(&s@ay)", &filename, &csum);
+
+ if (strcmp (filename, "metadata") != 0)
+ continue;
+
+ file_checksum = ostree_checksum_from_bytes_v (csum);
+ break;
+ }
+
+ if (file_checksum == NULL)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Can't find metadata file");
+ return NULL;
+ }
+
+ filez_bytes = flatpak_dir_fetch_remote_object (self, remote_name,
+ file_checksum, "filez",
+ cancellable, error);
+ if (filez_bytes == NULL)
+ return NULL;
+
+ filez_data = g_bytes_get_data (filez_bytes, &filez_size);
+
+ if (filez_size < 8)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid header");
+ return NULL;
+ }
+
+ archive_header_size = GUINT32_FROM_BE (*(guint32 *) filez_data);
+
+ archive_header_size += 4 + 4; /* Include header-size and padding */
+
+ if (archive_header_size > filez_size)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "File header size %u exceeds file size",
+ (guint) archive_header_size);
+ return NULL;
+ }
+
+ dataz_stream = (GMemoryInputStream *) g_memory_input_stream_new_from_data (filez_data + archive_header_size,
+ g_bytes_get_size (filez_bytes) - archive_header_size,
+ NULL);
+
+ zlib_decomp = (GConverter *) g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_RAW);
+ zlib_input = g_converter_input_stream_new (G_INPUT_STREAM (dataz_stream), zlib_decomp);
+
+ data_stream = (GMemoryOutputStream *) g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
+
+ if (g_output_stream_splice (G_OUTPUT_STREAM (data_stream), zlib_input,
+ G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+ cancellable, error) < 0)
+ return NULL;
+
+ return g_memory_output_stream_steal_as_bytes (data_stream);
+}