diff --git a/config.h.in b/config.h.in
index 70ad448897c36f38befe386e559e9a494aa52871..6760d62a3e2f41936c2584e82789a9c264e6a3f3 100644
--- a/config.h.in
+++ b/config.h.in
@@ -2,4 +2,12 @@
 #define G_LOG_DOMAIN "pressure-vessel"
 #mesondefine VERSION
 
+/* Allow using stuff from Flatpak with minimal modifications */
+#define FLATPAK_EXTERN extern
+#define _(s) s
+#define C_(context, s) s
+#define N_(s) s
+#define NC_(s) s
+#define Q_(s) g_strip_context (s, s)
+
 #include "subprojects/libglnx/config.h"
diff --git a/src/flatpak-common-types-private.h b/src/flatpak-common-types-private.h
new file mode 100644
index 0000000000000000000000000000000000000000..7f77b243556ba4345c7b80991b19339edecc5059
--- /dev/null
+++ b/src/flatpak-common-types-private.h
@@ -0,0 +1,60 @@
+/*
+ * Taken from Flatpak, last updated: 1.8.2
+ * Copyright © 2015 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *       Alexander Larsson <alexl@redhat.com>
+ */
+
+#ifndef __FLATPAK_COMMON_TYPES_H__
+#define __FLATPAK_COMMON_TYPES_H__
+
+typedef enum {
+  FLATPAK_KINDS_APP = 1 << 0,
+  FLATPAK_KINDS_RUNTIME = 1 << 1,
+} FlatpakKinds;
+
+typedef enum {
+  FLATPAK_RUN_FLAG_DEVEL              = (1 << 0),
+  FLATPAK_RUN_FLAG_BACKGROUND         = (1 << 1),
+  FLATPAK_RUN_FLAG_LOG_SESSION_BUS    = (1 << 2),
+  FLATPAK_RUN_FLAG_LOG_SYSTEM_BUS     = (1 << 3),
+  FLATPAK_RUN_FLAG_NO_SESSION_HELPER  = (1 << 4),
+  FLATPAK_RUN_FLAG_MULTIARCH          = (1 << 5),
+  FLATPAK_RUN_FLAG_WRITABLE_ETC       = (1 << 6),
+  FLATPAK_RUN_FLAG_NO_SESSION_BUS_PROXY = (1 << 7),
+  FLATPAK_RUN_FLAG_NO_SYSTEM_BUS_PROXY = (1 << 8),
+  FLATPAK_RUN_FLAG_SET_PERSONALITY    = (1 << 9),
+  FLATPAK_RUN_FLAG_FILE_FORWARDING    = (1 << 10),
+  FLATPAK_RUN_FLAG_DIE_WITH_PARENT    = (1 << 11),
+  FLATPAK_RUN_FLAG_LOG_A11Y_BUS       = (1 << 12),
+  FLATPAK_RUN_FLAG_NO_A11Y_BUS_PROXY  = (1 << 13),
+  FLATPAK_RUN_FLAG_SANDBOX            = (1 << 14),
+  FLATPAK_RUN_FLAG_NO_DOCUMENTS_PORTAL = (1 << 15),
+  FLATPAK_RUN_FLAG_BLUETOOTH          = (1 << 16),
+  FLATPAK_RUN_FLAG_CANBUS            = (1 << 17),
+  FLATPAK_RUN_FLAG_DO_NOT_REAP        = (1 << 18),
+  FLATPAK_RUN_FLAG_NO_PROC            = (1 << 19),
+  FLATPAK_RUN_FLAG_PARENT_EXPOSE_PIDS = (1 << 20),
+} FlatpakRunFlags;
+
+typedef struct FlatpakDir          FlatpakDir;
+typedef struct FlatpakDeploy       FlatpakDeploy;
+typedef struct FlatpakOciRegistry  FlatpakOciRegistry;
+typedef struct _FlatpakOciManifest FlatpakOciManifest;
+typedef struct _FlatpakOciImage    FlatpakOciImage;
+
+#endif /* __FLATPAK_COMMON_TYPES_H__ */
diff --git a/src/flatpak-context-private.h b/src/flatpak-context-private.h
new file mode 100644
index 0000000000000000000000000000000000000000..2773c979b200fc52bd0d49b77fea26c254887ec5
--- /dev/null
+++ b/src/flatpak-context-private.h
@@ -0,0 +1,165 @@
+/*
+ * Taken from Flatpak, last updated: 1.9.x commit 1.8.0-74-g354b9a22
+ * Modified to inline FlatpakPolicy instead of using the
+ * header file from xdg-dbus-proxy.
+ *
+ * Copyright © 2014-2018 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *       Alexander Larsson <alexl@redhat.com>
+ */
+
+#ifndef __FLATPAK_CONTEXT_H__
+#define __FLATPAK_CONTEXT_H__
+
+#include "libglnx/libglnx.h"
+
+typedef enum {
+  FLATPAK_POLICY_NONE,
+  FLATPAK_POLICY_SEE,
+  FLATPAK_POLICY_TALK,
+  FLATPAK_POLICY_OWN
+} FlatpakPolicy;
+
+#include <flatpak-common-types-private.h>
+#include "flatpak-exports-private.h"
+
+typedef struct FlatpakContext FlatpakContext;
+
+typedef enum {
+  FLATPAK_CONTEXT_SHARED_NETWORK   = 1 << 0,
+  FLATPAK_CONTEXT_SHARED_IPC       = 1 << 1,
+} FlatpakContextShares;
+
+typedef enum {
+  FLATPAK_CONTEXT_SOCKET_X11         = 1 << 0,
+  FLATPAK_CONTEXT_SOCKET_WAYLAND     = 1 << 1,
+  FLATPAK_CONTEXT_SOCKET_PULSEAUDIO  = 1 << 2,
+  FLATPAK_CONTEXT_SOCKET_SESSION_BUS = 1 << 3,
+  FLATPAK_CONTEXT_SOCKET_SYSTEM_BUS  = 1 << 4,
+  FLATPAK_CONTEXT_SOCKET_FALLBACK_X11 = 1 << 5, /* For backwards compat, also set SOCKET_X11 */
+  FLATPAK_CONTEXT_SOCKET_SSH_AUTH    = 1 << 6,
+  FLATPAK_CONTEXT_SOCKET_PCSC        = 1 << 7,
+  FLATPAK_CONTEXT_SOCKET_CUPS        = 1 << 8,
+} FlatpakContextSockets;
+
+typedef enum {
+  FLATPAK_CONTEXT_DEVICE_DRI         = 1 << 0,
+  FLATPAK_CONTEXT_DEVICE_ALL         = 1 << 1,
+  FLATPAK_CONTEXT_DEVICE_KVM         = 1 << 2,
+  FLATPAK_CONTEXT_DEVICE_SHM         = 1 << 3,
+} FlatpakContextDevices;
+
+typedef enum {
+  FLATPAK_CONTEXT_FEATURE_DEVEL        = 1 << 0,
+  FLATPAK_CONTEXT_FEATURE_MULTIARCH    = 1 << 1,
+  FLATPAK_CONTEXT_FEATURE_BLUETOOTH    = 1 << 2,
+  FLATPAK_CONTEXT_FEATURE_CANBUS       = 1 << 3,
+} FlatpakContextFeatures;
+
+struct FlatpakContext
+{
+  FlatpakContextShares   shares;
+  FlatpakContextShares   shares_valid;
+  FlatpakContextSockets  sockets;
+  FlatpakContextSockets  sockets_valid;
+  FlatpakContextDevices  devices;
+  FlatpakContextDevices  devices_valid;
+  FlatpakContextFeatures features;
+  FlatpakContextFeatures features_valid;
+  GHashTable            *env_vars;
+  GHashTable            *persistent;
+  GHashTable            *filesystems;
+  GHashTable            *session_bus_policy;
+  GHashTable            *system_bus_policy;
+  GHashTable            *generic_policy;
+};
+
+extern const char *flatpak_context_sockets[];
+extern const char *flatpak_context_devices[];
+extern const char *flatpak_context_features[];
+extern const char *flatpak_context_shares[];
+
+gboolean       flatpak_context_parse_filesystem (const char             *filesystem_and_mode,
+                                                 char                  **filesystem_out,
+                                                 FlatpakFilesystemMode  *mode_out,
+                                                 GError                **error);
+
+FlatpakContext *flatpak_context_new (void);
+void           flatpak_context_free (FlatpakContext *context);
+void           flatpak_context_merge (FlatpakContext *context,
+                                      FlatpakContext *other);
+
+#if 0
+
+GOptionEntry  *flatpak_context_get_option_entries (void);
+GOptionGroup  *flatpak_context_get_options (FlatpakContext *context);
+gboolean       flatpak_context_load_metadata (FlatpakContext *context,
+                                              GKeyFile       *metakey,
+                                              GError        **error);
+void           flatpak_context_save_metadata (FlatpakContext *context,
+                                              gboolean        flatten,
+                                              GKeyFile       *metakey);
+
+#endif
+
+void           flatpak_context_allow_host_fs (FlatpakContext *context);
+void           flatpak_context_set_session_bus_policy (FlatpakContext *context,
+                                                       const char     *name,
+                                                       FlatpakPolicy   policy);
+GStrv          flatpak_context_get_session_bus_policy_allowed_own_names (FlatpakContext *context);
+void           flatpak_context_set_system_bus_policy (FlatpakContext *context,
+                                                      const char     *name,
+                                                      FlatpakPolicy   policy);
+void           flatpak_context_to_args (FlatpakContext *context,
+                                        GPtrArray      *args);
+FlatpakRunFlags flatpak_context_get_run_flags (FlatpakContext *context);
+void           flatpak_context_add_bus_filters (FlatpakContext *context,
+                                                const char     *app_id,
+                                                gboolean        session_bus,
+                                                gboolean        sandboxed,
+                                                FlatpakBwrap   *bwrap);
+
+gboolean       flatpak_context_get_needs_session_bus_proxy (FlatpakContext *context);
+gboolean       flatpak_context_get_needs_system_bus_proxy (FlatpakContext *context);
+gboolean       flatpak_context_adds_permissions (FlatpakContext *old_context,
+                                                 FlatpakContext *new_context);
+
+void           flatpak_context_reset_permissions (FlatpakContext *context);
+void           flatpak_context_reset_non_permissions (FlatpakContext *context);
+void           flatpak_context_make_sandboxed (FlatpakContext *context);
+
+gboolean       flatpak_context_allows_features (FlatpakContext        *context,
+                                                FlatpakContextFeatures features);
+
+FlatpakContext *flatpak_context_load_for_deploy (FlatpakDeploy *deploy,
+                                                 GError       **error);
+
+FlatpakExports *flatpak_context_get_exports (FlatpakContext *context,
+                                             const char     *app_id);
+
+void flatpak_context_append_bwrap_filesystem (FlatpakContext  *context,
+                                              FlatpakBwrap    *bwrap,
+                                              const char      *app_id,
+                                              GFile           *app_id_dir,
+                                              GPtrArray       *extra_app_id_dirs,
+                                              FlatpakExports **exports_out);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakContext, flatpak_context_free)
+
+extern const char *dont_mount_in_root[];
+
+#endif /* __FLATPAK_CONTEXT_H__ */
diff --git a/src/flatpak-context.c b/src/flatpak-context.c
new file mode 100644
index 0000000000000000000000000000000000000000..5cddc0db5dd358308106ed4764c16cee1e47cb07
--- /dev/null
+++ b/src/flatpak-context.c
@@ -0,0 +1,2477 @@
+/*
+ * Taken from Flatpak, last updated: 1.8.0-74-g354b9a22
+ *
+ * Copyright © 2014-2018 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *       Alexander Larsson <alexl@redhat.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/utsname.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/personality.h>
+#include <grp.h>
+#include <unistd.h>
+#include <gio/gunixfdlist.h>
+
+#include <gio/gio.h>
+#include "libglnx/libglnx.h"
+
+#include "flatpak-run-private.h"
+#include "flatpak-utils-private.h"
+#include "flatpak-error.h"
+
+/* Same order as enum */
+const char *flatpak_context_shares[] = {
+  "network",
+  "ipc",
+  NULL
+};
+
+/* Same order as enum */
+const char *flatpak_context_sockets[] = {
+  "x11",
+  "wayland",
+  "pulseaudio",
+  "session-bus",
+  "system-bus",
+  "fallback-x11",
+  "ssh-auth",
+  "pcsc",
+  "cups",
+  NULL
+};
+
+const char *flatpak_context_devices[] = {
+  "dri",
+  "all",
+  "kvm",
+  "shm",
+  NULL
+};
+
+const char *flatpak_context_features[] = {
+  "devel",
+  "multiarch",
+  "bluetooth",
+  "canbus",
+  NULL
+};
+
+const char *flatpak_context_special_filesystems[] = {
+  "home",
+  "host",
+  "host-etc",
+  "host-os",
+  NULL
+};
+
+FlatpakContext *
+flatpak_context_new (void)
+{
+  FlatpakContext *context;
+
+  context = g_slice_new0 (FlatpakContext);
+  context->env_vars = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+  context->persistent = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+  /* filename or special filesystem name => FlatpakFilesystemMode */
+  context->filesystems = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+  context->session_bus_policy = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+  context->system_bus_policy = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+  context->generic_policy = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                   g_free, (GDestroyNotify) g_strfreev);
+
+  return context;
+}
+
+void
+flatpak_context_free (FlatpakContext *context)
+{
+  g_hash_table_destroy (context->env_vars);
+  g_hash_table_destroy (context->persistent);
+  g_hash_table_destroy (context->filesystems);
+  g_hash_table_destroy (context->session_bus_policy);
+  g_hash_table_destroy (context->system_bus_policy);
+  g_hash_table_destroy (context->generic_policy);
+  g_slice_free (FlatpakContext, context);
+}
+
+#if 0
+
+static guint32
+flatpak_context_bitmask_from_string (const char *name, const char **names)
+{
+  guint32 i;
+
+  for (i = 0; names[i] != NULL; i++)
+    {
+      if (strcmp (names[i], name) == 0)
+        return 1 << i;
+    }
+
+  return 0;
+}
+
+static char **
+flatpak_context_bitmask_to_string (guint32 enabled, guint32 valid, const char **names)
+{
+  guint32 i;
+  GPtrArray *array;
+
+  array = g_ptr_array_new ();
+
+  for (i = 0; names[i] != NULL; i++)
+    {
+      guint32 bitmask = 1 << i;
+      if (valid & bitmask)
+        {
+          if (enabled & bitmask)
+            g_ptr_array_add (array, g_strdup (names[i]));
+          else
+            g_ptr_array_add (array, g_strdup_printf ("!%s", names[i]));
+        }
+    }
+
+  g_ptr_array_add (array, NULL);
+  return (char **) g_ptr_array_free (array, FALSE);
+}
+
+#endif
+
+static void
+flatpak_context_bitmask_to_args (guint32 enabled, guint32 valid, const char **names,
+                                 const char *enable_arg, const char *disable_arg,
+                                 GPtrArray *args)
+{
+  guint32 i;
+
+  for (i = 0; names[i] != NULL; i++)
+    {
+      guint32 bitmask = 1 << i;
+      if (valid & bitmask)
+        {
+          if (enabled & bitmask)
+            g_ptr_array_add (args, g_strdup_printf ("%s=%s", enable_arg, names[i]));
+          else
+            g_ptr_array_add (args, g_strdup_printf ("%s=%s", disable_arg, names[i]));
+        }
+    }
+}
+
+#if 0
+
+static FlatpakContextShares
+flatpak_context_share_from_string (const char *string, GError **error)
+{
+  FlatpakContextShares shares = flatpak_context_bitmask_from_string (string, flatpak_context_shares);
+
+  if (shares == 0)
+    {
+      g_autofree char *values = g_strjoinv (", ", (char **) flatpak_context_shares);
+      g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
+                   _("Unknown share type %s, valid types are: %s"), string, values);
+    }
+
+  return shares;
+}
+
+static char **
+flatpak_context_shared_to_string (FlatpakContextShares shares, FlatpakContextShares valid)
+{
+  return flatpak_context_bitmask_to_string (shares, valid, flatpak_context_shares);
+}
+
+#endif
+
+static void
+flatpak_context_shared_to_args (FlatpakContextShares shares,
+                                FlatpakContextShares valid,
+                                GPtrArray           *args)
+{
+  return flatpak_context_bitmask_to_args (shares, valid, flatpak_context_shares, "--share", "--unshare", args);
+}
+
+#if 0
+
+static FlatpakPolicy
+flatpak_policy_from_string (const char *string, GError **error)
+{
+  const char *policies[] = { "none", "see", "talk", "own", NULL };
+  int i;
+  g_autofree char *values = NULL;
+
+  for (i = 0; policies[i]; i++)
+    {
+      if (strcmp (string, policies[i]) == 0)
+        return i;
+    }
+
+  values = g_strjoinv (", ", (char **) policies);
+  g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
+               _("Unknown policy type %s, valid types are: %s"), string, values);
+
+  return -1;
+}
+
+#endif
+
+static const char *
+flatpak_policy_to_string (FlatpakPolicy policy)
+{
+  if (policy == FLATPAK_POLICY_SEE)
+    return "see";
+  if (policy == FLATPAK_POLICY_TALK)
+    return "talk";
+  if (policy == FLATPAK_POLICY_OWN)
+    return "own";
+
+  return "none";
+}
+
+#if 0
+
+static gboolean
+flatpak_verify_dbus_name (const char *name, GError **error)
+{
+  const char *name_part;
+  g_autofree char *tmp = NULL;
+
+  if (g_str_has_suffix (name, ".*"))
+    {
+      tmp = g_strndup (name, strlen (name) - 2);
+      name_part = tmp;
+    }
+  else
+    {
+      name_part = name;
+    }
+
+  if (g_dbus_is_name (name_part) && !g_dbus_is_unique_name (name_part))
+    return TRUE;
+
+  g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
+               _("Invalid dbus name %s"), name);
+  return FALSE;
+}
+
+static FlatpakContextSockets
+flatpak_context_socket_from_string (const char *string, GError **error)
+{
+  FlatpakContextSockets sockets = flatpak_context_bitmask_from_string (string, flatpak_context_sockets);
+
+  if (sockets == 0)
+    {
+      g_autofree char *values = g_strjoinv (", ", (char **) flatpak_context_sockets);
+      g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
+                   _("Unknown socket type %s, valid types are: %s"), string, values);
+    }
+
+  return sockets;
+}
+
+static char **
+flatpak_context_sockets_to_string (FlatpakContextSockets sockets, FlatpakContextSockets valid)
+{
+  return flatpak_context_bitmask_to_string (sockets, valid, flatpak_context_sockets);
+}
+
+#endif
+
+static void
+flatpak_context_sockets_to_args (FlatpakContextSockets sockets,
+                                 FlatpakContextSockets valid,
+                                 GPtrArray            *args)
+{
+  return flatpak_context_bitmask_to_args (sockets, valid, flatpak_context_sockets, "--socket", "--nosocket", args);
+}
+
+#if 0
+
+static FlatpakContextDevices
+flatpak_context_device_from_string (const char *string, GError **error)
+{
+  FlatpakContextDevices devices = flatpak_context_bitmask_from_string (string, flatpak_context_devices);
+
+  if (devices == 0)
+    {
+      g_autofree char *values = g_strjoinv (", ", (char **) flatpak_context_devices);
+      g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
+                   _("Unknown device type %s, valid types are: %s"), string, values);
+    }
+  return devices;
+}
+
+static char **
+flatpak_context_devices_to_string (FlatpakContextDevices devices, FlatpakContextDevices valid)
+{
+  return flatpak_context_bitmask_to_string (devices, valid, flatpak_context_devices);
+}
+
+#endif
+
+static void
+flatpak_context_devices_to_args (FlatpakContextDevices devices,
+                                 FlatpakContextDevices valid,
+                                 GPtrArray            *args)
+{
+  return flatpak_context_bitmask_to_args (devices, valid, flatpak_context_devices, "--device", "--nodevice", args);
+}
+
+#if 0
+
+static FlatpakContextFeatures
+flatpak_context_feature_from_string (const char *string, GError **error)
+{
+  FlatpakContextFeatures feature = flatpak_context_bitmask_from_string (string, flatpak_context_features);
+
+  if (feature == 0)
+    {
+      g_autofree char *values = g_strjoinv (", ", (char **) flatpak_context_features);
+      g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
+                   _("Unknown feature type %s, valid types are: %s"), string, values);
+    }
+
+  return feature;
+}
+
+static char **
+flatpak_context_features_to_string (FlatpakContextFeatures features, FlatpakContextFeatures valid)
+{
+  return flatpak_context_bitmask_to_string (features, valid, flatpak_context_features);
+}
+
+#endif
+
+static void
+flatpak_context_features_to_args (FlatpakContextFeatures features,
+                                  FlatpakContextFeatures valid,
+                                  GPtrArray             *args)
+{
+  return flatpak_context_bitmask_to_args (features, valid, flatpak_context_features, "--allow", "--disallow", args);
+}
+
+#if 0
+
+static void
+flatpak_context_add_shares (FlatpakContext      *context,
+                            FlatpakContextShares shares)
+{
+  context->shares_valid |= shares;
+  context->shares |= shares;
+}
+
+static void
+flatpak_context_remove_shares (FlatpakContext      *context,
+                               FlatpakContextShares shares)
+{
+  context->shares_valid |= shares;
+  context->shares &= ~shares;
+}
+
+static void
+flatpak_context_add_sockets (FlatpakContext       *context,
+                             FlatpakContextSockets sockets)
+{
+  context->sockets_valid |= sockets;
+  context->sockets |= sockets;
+}
+
+static void
+flatpak_context_remove_sockets (FlatpakContext       *context,
+                                FlatpakContextSockets sockets)
+{
+  context->sockets_valid |= sockets;
+  context->sockets &= ~sockets;
+}
+
+static void
+flatpak_context_add_devices (FlatpakContext       *context,
+                             FlatpakContextDevices devices)
+{
+  context->devices_valid |= devices;
+  context->devices |= devices;
+}
+
+static void
+flatpak_context_remove_devices (FlatpakContext       *context,
+                                FlatpakContextDevices devices)
+{
+  context->devices_valid |= devices;
+  context->devices &= ~devices;
+}
+
+static void
+flatpak_context_add_features (FlatpakContext        *context,
+                              FlatpakContextFeatures features)
+{
+  context->features_valid |= features;
+  context->features |= features;
+}
+
+static void
+flatpak_context_remove_features (FlatpakContext        *context,
+                                 FlatpakContextFeatures features)
+{
+  context->features_valid |= features;
+  context->features &= ~features;
+}
+
+static void
+flatpak_context_set_env_var (FlatpakContext *context,
+                             const char     *name,
+                             const char     *value)
+{
+  g_hash_table_insert (context->env_vars, g_strdup (name), g_strdup (value));
+}
+
+#endif
+
+void
+flatpak_context_set_session_bus_policy (FlatpakContext *context,
+                                        const char     *name,
+                                        FlatpakPolicy   policy)
+{
+  g_hash_table_insert (context->session_bus_policy, g_strdup (name), GINT_TO_POINTER (policy));
+}
+
+GStrv
+flatpak_context_get_session_bus_policy_allowed_own_names (FlatpakContext *context)
+{
+  GHashTableIter iter;
+  gpointer key, value;
+  g_autoptr(GPtrArray) names = g_ptr_array_new_with_free_func (g_free);
+
+  g_hash_table_iter_init (&iter, context->session_bus_policy);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    if (GPOINTER_TO_INT (value) == FLATPAK_POLICY_OWN)
+      g_ptr_array_add (names, g_strdup (key));
+
+  g_ptr_array_add (names, NULL);
+  return (GStrv) g_ptr_array_free (g_steal_pointer (&names), FALSE);
+}
+
+void
+flatpak_context_set_system_bus_policy (FlatpakContext *context,
+                                       const char     *name,
+                                       FlatpakPolicy   policy)
+{
+  g_hash_table_insert (context->system_bus_policy, g_strdup (name), GINT_TO_POINTER (policy));
+}
+
+static void
+flatpak_context_apply_generic_policy (FlatpakContext *context,
+                                      const char     *key,
+                                      const char     *value)
+{
+  GPtrArray *new = g_ptr_array_new ();
+  const char **old_v;
+  int i;
+
+  g_assert (strchr (key, '.') != NULL);
+
+  old_v = g_hash_table_lookup (context->generic_policy, key);
+  for (i = 0; old_v != NULL && old_v[i] != NULL; i++)
+    {
+      const char *old = old_v[i];
+      const char *cmp1 = old;
+      const char *cmp2 = value;
+      if (*cmp1 == '!')
+        cmp1++;
+      if (*cmp2 == '!')
+        cmp2++;
+      if (strcmp (cmp1, cmp2) != 0)
+        g_ptr_array_add (new, g_strdup (old));
+    }
+
+  g_ptr_array_add (new, g_strdup (value));
+  g_ptr_array_add (new, NULL);
+
+  g_hash_table_insert (context->generic_policy, g_strdup (key),
+                       g_ptr_array_free (new, FALSE));
+}
+
+#if 0
+
+static void
+flatpak_context_set_persistent (FlatpakContext *context,
+                                const char     *path)
+{
+  g_hash_table_insert (context->persistent, g_strdup (path), GINT_TO_POINTER (1));
+}
+
+#endif
+
+static gboolean
+get_xdg_dir_from_prefix (const char  *prefix,
+                         const char **where,
+                         const char **dir)
+{
+  if (strcmp (prefix, "xdg-data") == 0)
+    {
+      if (where)
+        *where = "data";
+      if (dir)
+        *dir = g_get_user_data_dir ();
+      return TRUE;
+    }
+  if (strcmp (prefix, "xdg-cache") == 0)
+    {
+      if (where)
+        *where = "cache";
+      if (dir)
+        *dir = g_get_user_cache_dir ();
+      return TRUE;
+    }
+  if (strcmp (prefix, "xdg-config") == 0)
+    {
+      if (where)
+        *where = "config";
+      if (dir)
+        *dir = g_get_user_config_dir ();
+      return TRUE;
+    }
+  return FALSE;
+}
+
+/* This looks only in the xdg dirs (config, cache, data), not the user
+   definable ones */
+static char *
+get_xdg_dir_from_string (const char  *filesystem,
+                         const char **suffix,
+                         const char **where)
+{
+  char *slash;
+  const char *rest;
+  g_autofree char *prefix = NULL;
+  const char *dir = NULL;
+  gsize len;
+
+  slash = strchr (filesystem, '/');
+
+  if (slash)
+    len = slash - filesystem;
+  else
+    len = strlen (filesystem);
+
+  rest = filesystem + len;
+  while (*rest == '/')
+    rest++;
+
+  if (suffix != NULL)
+    *suffix = rest;
+
+  prefix = g_strndup (filesystem, len);
+
+  if (get_xdg_dir_from_prefix (prefix, where, &dir))
+    return g_build_filename (dir, rest, NULL);
+
+  return NULL;
+}
+
+static gboolean
+get_xdg_user_dir_from_string (const char  *filesystem,
+                              const char **config_key,
+                              const char **suffix,
+                              const char **dir)
+{
+  char *slash;
+  const char *rest;
+  g_autofree char *prefix = NULL;
+  gsize len;
+
+  slash = strchr (filesystem, '/');
+
+  if (slash)
+    len = slash - filesystem;
+  else
+    len = strlen (filesystem);
+
+  rest = filesystem + len;
+  while (*rest == '/')
+    rest++;
+
+  if (suffix)
+    *suffix = rest;
+
+  prefix = g_strndup (filesystem, len);
+
+  if (strcmp (prefix, "xdg-desktop") == 0)
+    {
+      if (config_key)
+        *config_key = "XDG_DESKTOP_DIR";
+      if (dir)
+        *dir = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP);
+      return TRUE;
+    }
+  if (strcmp (prefix, "xdg-documents") == 0)
+    {
+      if (config_key)
+        *config_key = "XDG_DOCUMENTS_DIR";
+      if (dir)
+        *dir = g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS);
+      return TRUE;
+    }
+  if (strcmp (prefix, "xdg-download") == 0)
+    {
+      if (config_key)
+        *config_key = "XDG_DOWNLOAD_DIR";
+      if (dir)
+        *dir = g_get_user_special_dir (G_USER_DIRECTORY_DOWNLOAD);
+      return TRUE;
+    }
+  if (strcmp (prefix, "xdg-music") == 0)
+    {
+      if (config_key)
+        *config_key = "XDG_MUSIC_DIR";
+      if (dir)
+        *dir = g_get_user_special_dir (G_USER_DIRECTORY_MUSIC);
+      return TRUE;
+    }
+  if (strcmp (prefix, "xdg-pictures") == 0)
+    {
+      if (config_key)
+        *config_key = "XDG_PICTURES_DIR";
+      if (dir)
+        *dir = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES);
+      return TRUE;
+    }
+  if (strcmp (prefix, "xdg-public-share") == 0)
+    {
+      if (config_key)
+        *config_key = "XDG_PUBLICSHARE_DIR";
+      if (dir)
+        *dir = g_get_user_special_dir (G_USER_DIRECTORY_PUBLIC_SHARE);
+      return TRUE;
+    }
+  if (strcmp (prefix, "xdg-templates") == 0)
+    {
+      if (config_key)
+        *config_key = "XDG_TEMPLATES_DIR";
+      if (dir)
+        *dir = g_get_user_special_dir (G_USER_DIRECTORY_TEMPLATES);
+      return TRUE;
+    }
+  if (strcmp (prefix, "xdg-videos") == 0)
+    {
+      if (config_key)
+        *config_key = "XDG_VIDEOS_DIR";
+      if (dir)
+        *dir = g_get_user_special_dir (G_USER_DIRECTORY_VIDEOS);
+      return TRUE;
+    }
+  if (get_xdg_dir_from_prefix (prefix, NULL, dir))
+    {
+      if (config_key)
+        *config_key = NULL;
+      return TRUE;
+    }
+  /* Don't support xdg-run without suffix, because that doesn't work */
+  if (strcmp (prefix, "xdg-run") == 0 &&
+      *rest != 0)
+    {
+      if (config_key)
+        *config_key = NULL;
+      if (dir)
+        *dir = flatpak_get_real_xdg_runtime_dir ();
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static char *
+unparse_filesystem_flags (const char           *path,
+                          FlatpakFilesystemMode mode)
+{
+  g_autoptr(GString) s = g_string_new ("");
+  const char *p;
+
+  for (p = path; *p != 0; p++)
+    {
+      if (*p == ':')
+        g_string_append (s, "\\:");
+      else if (*p == '\\')
+        g_string_append (s, "\\\\");
+      else
+        g_string_append_c (s, *p);
+    }
+
+  switch (mode)
+    {
+    case FLATPAK_FILESYSTEM_MODE_READ_ONLY:
+      g_string_append (s, ":ro");
+      break;
+
+    case FLATPAK_FILESYSTEM_MODE_CREATE:
+      g_string_append (s, ":create");
+      break;
+
+    case FLATPAK_FILESYSTEM_MODE_READ_WRITE:
+      break;
+
+    case FLATPAK_FILESYSTEM_MODE_NONE:
+    default:
+      g_warning ("Unexpected filesystem mode %d", mode);
+      break;
+    }
+
+  return g_string_free (g_steal_pointer (&s), FALSE);
+}
+
+static char *
+parse_filesystem_flags (const char            *filesystem,
+                        FlatpakFilesystemMode *mode_out)
+{
+  g_autoptr(GString) s = g_string_new ("");
+  const char *p, *suffix;
+  FlatpakFilesystemMode mode;
+
+  p = filesystem;
+  while (*p != 0 && *p != ':')
+    {
+      if (*p == '\\')
+        {
+          p++;
+          if (*p != 0)
+            g_string_append_c (s, *p++);
+        }
+      else
+        g_string_append_c (s, *p++);
+    }
+
+  mode = FLATPAK_FILESYSTEM_MODE_READ_WRITE;
+
+  if (*p == ':')
+    {
+      suffix = p + 1;
+
+      if (strcmp (suffix, "ro") == 0)
+        mode = FLATPAK_FILESYSTEM_MODE_READ_ONLY;
+      else if (strcmp (suffix, "rw") == 0)
+        mode = FLATPAK_FILESYSTEM_MODE_READ_WRITE;
+      else if (strcmp (suffix, "create") == 0)
+        mode = FLATPAK_FILESYSTEM_MODE_CREATE;
+      else if (*suffix != 0)
+        g_warning ("Unexpected filesystem suffix %s, ignoring", suffix);
+    }
+
+  if (mode_out)
+    *mode_out = mode;
+
+  return g_string_free (g_steal_pointer (&s), FALSE);
+}
+
+gboolean
+flatpak_context_parse_filesystem (const char             *filesystem_and_mode,
+                                  char                  **filesystem_out,
+                                  FlatpakFilesystemMode  *mode_out,
+                                  GError                **error)
+{
+  g_autofree char *filesystem = parse_filesystem_flags (filesystem_and_mode, mode_out);
+  char *slash;
+
+  slash = strchr (filesystem, '/');
+
+  /* Forbid /../ in paths */
+  if (slash != NULL)
+    {
+      if (g_str_has_prefix (slash + 1, "../") ||
+          g_str_has_suffix (slash + 1, "/..") ||
+          strstr (slash + 1, "/../") != NULL)
+        {
+          g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+                       _("Filesystem location \"%s\" contains \"..\""),
+                       filesystem);
+          return FALSE;
+        }
+
+      /* Convert "//" and "/./" to "/" */
+      for (; slash != NULL; slash = strchr (slash + 1, '/'))
+        {
+          while (TRUE)
+            {
+              if (slash[1] == '/')
+                memmove (slash + 1, slash + 2, strlen (slash + 2) + 1);
+              else if (slash[1] == '.' && slash[2] == '/')
+                memmove (slash + 1, slash + 3, strlen (slash + 3) + 1);
+              else
+                break;
+            }
+        }
+
+      /* Eliminate trailing "/." or "/". */
+      while (TRUE)
+        {
+          slash = strrchr (filesystem, '/');
+
+          if (slash != NULL &&
+              ((slash != filesystem && slash[1] == '\0') ||
+               (slash[1] == '.' && slash[2] == '\0')))
+            *slash = '\0';
+          else
+            break;
+        }
+
+      if (filesystem[0] == '/' && filesystem[1] == '\0')
+        {
+          /* We don't allow --filesystem=/ as equivalent to host, because
+           * it doesn't do what you'd think: --filesystem=host mounts some
+           * host directories in /run/host, not in the root. */
+          g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+                       _("--filesystem=/ is not available, "
+                         "use --filesystem=host for a similar result"));
+          return FALSE;
+        }
+    }
+
+  if (g_strv_contains (flatpak_context_special_filesystems, filesystem) ||
+      get_xdg_user_dir_from_string (filesystem, NULL, NULL, NULL) ||
+      g_str_has_prefix (filesystem, "~/") ||
+      g_str_has_prefix (filesystem, "/"))
+    {
+      if (filesystem_out != NULL)
+        *filesystem_out = g_steal_pointer (&filesystem);
+
+      return TRUE;
+    }
+
+  if (strcmp (filesystem, "~") == 0)
+    {
+      if (filesystem_out != NULL)
+        *filesystem_out = g_strdup ("home");
+
+      return TRUE;
+    }
+
+  if (g_str_has_prefix (filesystem, "home/"))
+    {
+      if (filesystem_out != NULL)
+        *filesystem_out = g_strconcat ("~/", filesystem + 5, NULL);
+
+      return TRUE;
+    }
+
+  g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
+               _("Unknown filesystem location %s, valid locations are: host, host-os, host-etc, home, xdg-*[/…], ~/dir, /dir"), filesystem);
+  return FALSE;
+}
+
+static void
+flatpak_context_take_filesystem (FlatpakContext        *context,
+                                 char                  *fs,
+                                 FlatpakFilesystemMode  mode)
+{
+  g_hash_table_insert (context->filesystems, fs, GINT_TO_POINTER (mode));
+}
+
+void
+flatpak_context_merge (FlatpakContext *context,
+                       FlatpakContext *other)
+{
+  GHashTableIter iter;
+  gpointer key, value;
+
+  context->shares &= ~other->shares_valid;
+  context->shares |= other->shares;
+  context->shares_valid |= other->shares_valid;
+  context->sockets &= ~other->sockets_valid;
+  context->sockets |= other->sockets;
+  context->sockets_valid |= other->sockets_valid;
+  context->devices &= ~other->devices_valid;
+  context->devices |= other->devices;
+  context->devices_valid |= other->devices_valid;
+  context->features &= ~other->features_valid;
+  context->features |= other->features;
+  context->features_valid |= other->features_valid;
+
+  g_hash_table_iter_init (&iter, other->env_vars);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    g_hash_table_insert (context->env_vars, g_strdup (key), g_strdup (value));
+
+  g_hash_table_iter_init (&iter, other->persistent);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    g_hash_table_insert (context->persistent, g_strdup (key), value);
+
+  g_hash_table_iter_init (&iter, other->filesystems);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    g_hash_table_insert (context->filesystems, g_strdup (key), value);
+
+  g_hash_table_iter_init (&iter, other->session_bus_policy);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    g_hash_table_insert (context->session_bus_policy, g_strdup (key), value);
+
+  g_hash_table_iter_init (&iter, other->system_bus_policy);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    g_hash_table_insert (context->system_bus_policy, g_strdup (key), value);
+
+  g_hash_table_iter_init (&iter, other->system_bus_policy);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    g_hash_table_insert (context->system_bus_policy, g_strdup (key), value);
+
+  g_hash_table_iter_init (&iter, other->generic_policy);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      const char **policy_values = (const char **) value;
+      int i;
+
+      for (i = 0; policy_values[i] != NULL; i++)
+        flatpak_context_apply_generic_policy (context, (char *) key, policy_values[i]);
+    }
+}
+
+#if 0
+
+static gboolean
+option_share_cb (const gchar *option_name,
+                 const gchar *value,
+                 gpointer     data,
+                 GError     **error)
+{
+  FlatpakContext *context = data;
+  FlatpakContextShares share;
+
+  share = flatpak_context_share_from_string (value, error);
+  if (share == 0)
+    return FALSE;
+
+  flatpak_context_add_shares (context, share);
+
+  return TRUE;
+}
+
+static gboolean
+option_unshare_cb (const gchar *option_name,
+                   const gchar *value,
+                   gpointer     data,
+                   GError     **error)
+{
+  FlatpakContext *context = data;
+  FlatpakContextShares share;
+
+  share = flatpak_context_share_from_string (value, error);
+  if (share == 0)
+    return FALSE;
+
+  flatpak_context_remove_shares (context, share);
+
+  return TRUE;
+}
+
+static gboolean
+option_socket_cb (const gchar *option_name,
+                  const gchar *value,
+                  gpointer     data,
+                  GError     **error)
+{
+  FlatpakContext *context = data;
+  FlatpakContextSockets socket;
+
+  socket = flatpak_context_socket_from_string (value, error);
+  if (socket == 0)
+    return FALSE;
+
+  if (socket == FLATPAK_CONTEXT_SOCKET_FALLBACK_X11)
+    socket |= FLATPAK_CONTEXT_SOCKET_X11;
+
+  flatpak_context_add_sockets (context, socket);
+
+  return TRUE;
+}
+
+static gboolean
+option_nosocket_cb (const gchar *option_name,
+                    const gchar *value,
+                    gpointer     data,
+                    GError     **error)
+{
+  FlatpakContext *context = data;
+  FlatpakContextSockets socket;
+
+  socket = flatpak_context_socket_from_string (value, error);
+  if (socket == 0)
+    return FALSE;
+
+  if (socket == FLATPAK_CONTEXT_SOCKET_FALLBACK_X11)
+    socket |= FLATPAK_CONTEXT_SOCKET_X11;
+
+  flatpak_context_remove_sockets (context, socket);
+
+  return TRUE;
+}
+
+static gboolean
+option_device_cb (const gchar *option_name,
+                  const gchar *value,
+                  gpointer     data,
+                  GError     **error)
+{
+  FlatpakContext *context = data;
+  FlatpakContextDevices device;
+
+  device = flatpak_context_device_from_string (value, error);
+  if (device == 0)
+    return FALSE;
+
+  flatpak_context_add_devices (context, device);
+
+  return TRUE;
+}
+
+static gboolean
+option_nodevice_cb (const gchar *option_name,
+                    const gchar *value,
+                    gpointer     data,
+                    GError     **error)
+{
+  FlatpakContext *context = data;
+  FlatpakContextDevices device;
+
+  device = flatpak_context_device_from_string (value, error);
+  if (device == 0)
+    return FALSE;
+
+  flatpak_context_remove_devices (context, device);
+
+  return TRUE;
+}
+
+static gboolean
+option_allow_cb (const gchar *option_name,
+                 const gchar *value,
+                 gpointer     data,
+                 GError     **error)
+{
+  FlatpakContext *context = data;
+  FlatpakContextFeatures feature;
+
+  feature = flatpak_context_feature_from_string (value, error);
+  if (feature == 0)
+    return FALSE;
+
+  flatpak_context_add_features (context, feature);
+
+  return TRUE;
+}
+
+static gboolean
+option_disallow_cb (const gchar *option_name,
+                    const gchar *value,
+                    gpointer     data,
+                    GError     **error)
+{
+  FlatpakContext *context = data;
+  FlatpakContextFeatures feature;
+
+  feature = flatpak_context_feature_from_string (value, error);
+  if (feature == 0)
+    return FALSE;
+
+  flatpak_context_remove_features (context, feature);
+
+  return TRUE;
+}
+
+static gboolean
+option_filesystem_cb (const gchar *option_name,
+                      const gchar *value,
+                      gpointer     data,
+                      GError     **error)
+{
+  FlatpakContext *context = data;
+  g_autofree char *fs = NULL;
+  FlatpakFilesystemMode mode;
+
+  if (!flatpak_context_parse_filesystem (value, &fs, &mode, error))
+    return FALSE;
+
+  flatpak_context_take_filesystem (context, g_steal_pointer (&fs), mode);
+  return TRUE;
+}
+
+static gboolean
+option_nofilesystem_cb (const gchar *option_name,
+                        const gchar *value,
+                        gpointer     data,
+                        GError     **error)
+{
+  FlatpakContext *context = data;
+  g_autofree char *fs = NULL;
+  FlatpakFilesystemMode mode;
+
+  if (!flatpak_context_parse_filesystem (value, &fs, &mode, error))
+    return FALSE;
+
+  flatpak_context_take_filesystem (context, g_steal_pointer (&fs),
+                                   FLATPAK_FILESYSTEM_MODE_NONE);
+  return TRUE;
+}
+
+static gboolean
+option_env_cb (const gchar *option_name,
+               const gchar *value,
+               gpointer     data,
+               GError     **error)
+{
+  FlatpakContext *context = data;
+  g_auto(GStrv) split = g_strsplit (value, "=", 2);
+
+  if (split == NULL || split[0] == NULL || split[0][0] == 0 || split[1] == NULL)
+    {
+      g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
+                   _("Invalid env format %s"), value);
+      return FALSE;
+    }
+
+  flatpak_context_set_env_var (context, split[0], split[1]);
+  return TRUE;
+}
+
+static gboolean
+option_own_name_cb (const gchar *option_name,
+                    const gchar *value,
+                    gpointer     data,
+                    GError     **error)
+{
+  FlatpakContext *context = data;
+
+  if (!flatpak_verify_dbus_name (value, error))
+    return FALSE;
+
+  flatpak_context_set_session_bus_policy (context, value, FLATPAK_POLICY_OWN);
+  return TRUE;
+}
+
+static gboolean
+option_talk_name_cb (const gchar *option_name,
+                     const gchar *value,
+                     gpointer     data,
+                     GError     **error)
+{
+  FlatpakContext *context = data;
+
+  if (!flatpak_verify_dbus_name (value, error))
+    return FALSE;
+
+  flatpak_context_set_session_bus_policy (context, value, FLATPAK_POLICY_TALK);
+  return TRUE;
+}
+
+static gboolean
+option_no_talk_name_cb (const gchar *option_name,
+                        const gchar *value,
+                        gpointer     data,
+                        GError     **error)
+{
+  FlatpakContext *context = data;
+
+  if (!flatpak_verify_dbus_name (value, error))
+    return FALSE;
+
+  flatpak_context_set_session_bus_policy (context, value, FLATPAK_POLICY_NONE);
+  return TRUE;
+}
+
+static gboolean
+option_system_own_name_cb (const gchar *option_name,
+                           const gchar *value,
+                           gpointer     data,
+                           GError     **error)
+{
+  FlatpakContext *context = data;
+
+  if (!flatpak_verify_dbus_name (value, error))
+    return FALSE;
+
+  flatpak_context_set_system_bus_policy (context, value, FLATPAK_POLICY_OWN);
+  return TRUE;
+}
+
+static gboolean
+option_system_talk_name_cb (const gchar *option_name,
+                            const gchar *value,
+                            gpointer     data,
+                            GError     **error)
+{
+  FlatpakContext *context = data;
+
+  if (!flatpak_verify_dbus_name (value, error))
+    return FALSE;
+
+  flatpak_context_set_system_bus_policy (context, value, FLATPAK_POLICY_TALK);
+  return TRUE;
+}
+
+static gboolean
+option_system_no_talk_name_cb (const gchar *option_name,
+                               const gchar *value,
+                               gpointer     data,
+                               GError     **error)
+{
+  FlatpakContext *context = data;
+
+  if (!flatpak_verify_dbus_name (value, error))
+    return FALSE;
+
+  flatpak_context_set_system_bus_policy (context, value, FLATPAK_POLICY_NONE);
+  return TRUE;
+}
+
+static gboolean
+option_add_generic_policy_cb (const gchar *option_name,
+                              const gchar *value,
+                              gpointer     data,
+                              GError     **error)
+{
+  FlatpakContext *context = data;
+  char *t;
+  g_autofree char *key = NULL;
+  const char *policy_value;
+
+  t = strchr (value, '=');
+  if (t == NULL)
+    {
+      g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+                   _("--add-policy arguments must be in the form SUBSYSTEM.KEY=VALUE"));
+      return FALSE;
+    }
+  policy_value = t + 1;
+  key = g_strndup (value, t - value);
+  if (strchr (key, '.') == NULL)
+    {
+      g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+                   _("--add-policy arguments must be in the form SUBSYSTEM.KEY=VALUE"));
+      return FALSE;
+    }
+
+  if (policy_value[0] == '!')
+    {
+      g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+                   _("--add-policy values can't start with \"!\""));
+      return FALSE;
+    }
+
+  flatpak_context_apply_generic_policy (context, key, policy_value);
+
+  return TRUE;
+}
+
+static gboolean
+option_remove_generic_policy_cb (const gchar *option_name,
+                                 const gchar *value,
+                                 gpointer     data,
+                                 GError     **error)
+{
+  FlatpakContext *context = data;
+  char *t;
+  g_autofree char *key = NULL;
+  const char *policy_value;
+  g_autofree char *extended_value = NULL;
+
+  t = strchr (value, '=');
+  if (t == NULL)
+    {
+      g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+                   _("--remove-policy arguments must be in the form SUBSYSTEM.KEY=VALUE"));
+      return FALSE;
+    }
+  policy_value = t + 1;
+  key = g_strndup (value, t - value);
+  if (strchr (key, '.') == NULL)
+    {
+      g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+                   _("--remove-policy arguments must be in the form SUBSYSTEM.KEY=VALUE"));
+      return FALSE;
+    }
+
+  if (policy_value[0] == '!')
+    {
+      g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+                   _("--remove-policy values can't start with \"!\""));
+      return FALSE;
+    }
+
+  extended_value = g_strconcat ("!", policy_value, NULL);
+
+  flatpak_context_apply_generic_policy (context, key, extended_value);
+
+  return TRUE;
+}
+
+static gboolean
+option_persist_cb (const gchar *option_name,
+                   const gchar *value,
+                   gpointer     data,
+                   GError     **error)
+{
+  FlatpakContext *context = data;
+
+  flatpak_context_set_persistent (context, value);
+  return TRUE;
+}
+
+static gboolean option_no_desktop_deprecated;
+
+static GOptionEntry context_options[] = {
+  { "share", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_share_cb, N_("Share with host"), N_("SHARE") },
+  { "unshare", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_unshare_cb, N_("Unshare with host"), N_("SHARE") },
+  { "socket", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_socket_cb, N_("Expose socket to app"), N_("SOCKET") },
+  { "nosocket", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_nosocket_cb, N_("Don't expose socket to app"), N_("SOCKET") },
+  { "device", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_device_cb, N_("Expose device to app"), N_("DEVICE") },
+  { "nodevice", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_nodevice_cb, N_("Don't expose device to app"), N_("DEVICE") },
+  { "allow", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_allow_cb, N_("Allow feature"), N_("FEATURE") },
+  { "disallow", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_disallow_cb, N_("Don't allow feature"), N_("FEATURE") },
+  { "filesystem", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_filesystem_cb, N_("Expose filesystem to app (:ro for read-only)"), N_("FILESYSTEM[:ro]") },
+  { "nofilesystem", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_nofilesystem_cb, N_("Don't expose filesystem to app"), N_("FILESYSTEM") },
+  { "env", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_env_cb, N_("Set environment variable"), N_("VAR=VALUE") },
+  { "own-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_own_name_cb, N_("Allow app to own name on the session bus"), N_("DBUS_NAME") },
+  { "talk-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_talk_name_cb, N_("Allow app to talk to name on the session bus"), N_("DBUS_NAME") },
+  { "no-talk-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_no_talk_name_cb, N_("Don't allow app to talk to name on the session bus"), N_("DBUS_NAME") },
+  { "system-own-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_system_own_name_cb, N_("Allow app to own name on the system bus"), N_("DBUS_NAME") },
+  { "system-talk-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_system_talk_name_cb, N_("Allow app to talk to name on the system bus"), N_("DBUS_NAME") },
+  { "system-no-talk-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_system_no_talk_name_cb, N_("Don't allow app to talk to name on the system bus"), N_("DBUS_NAME") },
+  { "add-policy", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_add_generic_policy_cb, N_("Add generic policy option"), N_("SUBSYSTEM.KEY=VALUE") },
+  { "remove-policy", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_remove_generic_policy_cb, N_("Remove generic policy option"), N_("SUBSYSTEM.KEY=VALUE") },
+  { "persist", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_persist_cb, N_("Persist home directory subpath"), N_("FILENAME") },
+  /* This is not needed/used anymore, so hidden, but we accept it for backwards compat */
+  { "no-desktop", 0, G_OPTION_FLAG_IN_MAIN |  G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &option_no_desktop_deprecated, N_("Don't require a running session (no cgroups creation)"), NULL },
+  { NULL }
+};
+
+GOptionEntry *
+flatpak_context_get_option_entries (void)
+{
+  return context_options;
+}
+
+GOptionGroup  *
+flatpak_context_get_options (FlatpakContext *context)
+{
+  GOptionGroup *group;
+
+  group = g_option_group_new ("environment",
+                              "Runtime Environment",
+                              "Runtime Environment",
+                              context,
+                              NULL);
+  g_option_group_set_translation_domain (group, GETTEXT_PACKAGE);
+
+  g_option_group_add_entries (group, context_options);
+
+  return group;
+}
+
+static const char *
+parse_negated (const char *option, gboolean *negated)
+{
+  if (option[0] == '!')
+    {
+      option++;
+      *negated = TRUE;
+    }
+  else
+    {
+      *negated = FALSE;
+    }
+  return option;
+}
+
+/*
+ * Merge the FLATPAK_METADATA_GROUP_CONTEXT,
+ * FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY,
+ * FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY and
+ * FLATPAK_METADATA_GROUP_ENVIRONMENT groups, and all groups starting
+ * with FLATPAK_METADATA_GROUP_PREFIX_POLICY, from metakey into context.
+ *
+ * This is a merge, not a replace!
+ */
+gboolean
+flatpak_context_load_metadata (FlatpakContext *context,
+                               GKeyFile       *metakey,
+                               GError        **error)
+{
+  gboolean remove;
+  g_auto(GStrv) groups = NULL;
+  int i;
+
+  if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT, FLATPAK_METADATA_KEY_SHARED, NULL))
+    {
+      g_auto(GStrv) shares = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT,
+                                                         FLATPAK_METADATA_KEY_SHARED, NULL, error);
+      if (shares == NULL)
+        return FALSE;
+
+      for (i = 0; shares[i] != NULL; i++)
+        {
+          FlatpakContextShares share;
+
+          share = flatpak_context_share_from_string (parse_negated (shares[i], &remove), NULL);
+          if (share == 0)
+            g_debug ("Unknown share type %s", shares[i]);
+          else
+            {
+              if (remove)
+                flatpak_context_remove_shares (context, share);
+              else
+                flatpak_context_add_shares (context, share);
+            }
+        }
+    }
+
+  if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT, FLATPAK_METADATA_KEY_SOCKETS, NULL))
+    {
+      g_auto(GStrv) sockets = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT,
+                                                          FLATPAK_METADATA_KEY_SOCKETS, NULL, error);
+      if (sockets == NULL)
+        return FALSE;
+
+      for (i = 0; sockets[i] != NULL; i++)
+        {
+          FlatpakContextSockets socket = flatpak_context_socket_from_string (parse_negated (sockets[i], &remove), NULL);
+          if (socket == 0)
+            g_debug ("Unknown socket type %s", sockets[i]);
+          else
+            {
+              if (remove)
+                flatpak_context_remove_sockets (context, socket);
+              else
+                flatpak_context_add_sockets (context, socket);
+            }
+        }
+    }
+
+  if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT, FLATPAK_METADATA_KEY_DEVICES, NULL))
+    {
+      g_auto(GStrv) devices = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT,
+                                                          FLATPAK_METADATA_KEY_DEVICES, NULL, error);
+      if (devices == NULL)
+        return FALSE;
+
+
+      for (i = 0; devices[i] != NULL; i++)
+        {
+          FlatpakContextDevices device = flatpak_context_device_from_string (parse_negated (devices[i], &remove), NULL);
+          if (device == 0)
+            g_debug ("Unknown device type %s", devices[i]);
+          else
+            {
+              if (remove)
+                flatpak_context_remove_devices (context, device);
+              else
+                flatpak_context_add_devices (context, device);
+            }
+        }
+    }
+
+  if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT, FLATPAK_METADATA_KEY_FEATURES, NULL))
+    {
+      g_auto(GStrv) features = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT,
+                                                           FLATPAK_METADATA_KEY_FEATURES, NULL, error);
+      if (features == NULL)
+        return FALSE;
+
+
+      for (i = 0; features[i] != NULL; i++)
+        {
+          FlatpakContextFeatures feature = flatpak_context_feature_from_string (parse_negated (features[i], &remove), NULL);
+          if (feature == 0)
+            g_debug ("Unknown feature type %s", features[i]);
+          else
+            {
+              if (remove)
+                flatpak_context_remove_features (context, feature);
+              else
+                flatpak_context_add_features (context, feature);
+            }
+        }
+    }
+
+  if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT, FLATPAK_METADATA_KEY_FILESYSTEMS, NULL))
+    {
+      g_auto(GStrv) filesystems = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT,
+                                                              FLATPAK_METADATA_KEY_FILESYSTEMS, NULL, error);
+      if (filesystems == NULL)
+        return FALSE;
+
+      for (i = 0; filesystems[i] != NULL; i++)
+        {
+          const char *fs = parse_negated (filesystems[i], &remove);
+          g_autofree char *filesystem = NULL;
+          FlatpakFilesystemMode mode;
+
+          if (!flatpak_context_parse_filesystem (fs, &filesystem, &mode, NULL))
+            g_debug ("Unknown filesystem type %s", filesystems[i]);
+          else
+            {
+              if (remove)
+                flatpak_context_take_filesystem (context, g_steal_pointer (&filesystem),
+                                                 FLATPAK_FILESYSTEM_MODE_NONE);
+              else
+                flatpak_context_take_filesystem (context, g_steal_pointer (&filesystem), mode);
+            }
+        }
+    }
+
+  if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT, FLATPAK_METADATA_KEY_PERSISTENT, NULL))
+    {
+      g_auto(GStrv) persistent = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT,
+                                                             FLATPAK_METADATA_KEY_PERSISTENT, NULL, error);
+      if (persistent == NULL)
+        return FALSE;
+
+      for (i = 0; persistent[i] != NULL; i++)
+        flatpak_context_set_persistent (context, persistent[i]);
+    }
+
+  if (g_key_file_has_group (metakey, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY))
+    {
+      g_auto(GStrv) keys = NULL;
+      gsize i, keys_count;
+
+      keys = g_key_file_get_keys (metakey, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY, &keys_count, NULL);
+      for (i = 0; i < keys_count; i++)
+        {
+          const char *key = keys[i];
+          g_autofree char *value = g_key_file_get_string (metakey, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY, key, NULL);
+          FlatpakPolicy policy;
+
+          if (!flatpak_verify_dbus_name (key, error))
+            return FALSE;
+
+          policy = flatpak_policy_from_string (value, NULL);
+          if ((int) policy != -1)
+            flatpak_context_set_session_bus_policy (context, key, policy);
+        }
+    }
+
+  if (g_key_file_has_group (metakey, FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY))
+    {
+      g_auto(GStrv) keys = NULL;
+      gsize i, keys_count;
+
+      keys = g_key_file_get_keys (metakey, FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY, &keys_count, NULL);
+      for (i = 0; i < keys_count; i++)
+        {
+          const char *key = keys[i];
+          g_autofree char *value = g_key_file_get_string (metakey, FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY, key, NULL);
+          FlatpakPolicy policy;
+
+          if (!flatpak_verify_dbus_name (key, error))
+            return FALSE;
+
+          policy = flatpak_policy_from_string (value, NULL);
+          if ((int) policy != -1)
+            flatpak_context_set_system_bus_policy (context, key, policy);
+        }
+    }
+
+  if (g_key_file_has_group (metakey, FLATPAK_METADATA_GROUP_ENVIRONMENT))
+    {
+      g_auto(GStrv) keys = NULL;
+      gsize i, keys_count;
+
+      keys = g_key_file_get_keys (metakey, FLATPAK_METADATA_GROUP_ENVIRONMENT, &keys_count, NULL);
+      for (i = 0; i < keys_count; i++)
+        {
+          const char *key = keys[i];
+          g_autofree char *value = g_key_file_get_string (metakey, FLATPAK_METADATA_GROUP_ENVIRONMENT, key, NULL);
+
+          flatpak_context_set_env_var (context, key, value);
+        }
+    }
+
+  groups = g_key_file_get_groups (metakey, NULL);
+  for (i = 0; groups[i] != NULL; i++)
+    {
+      const char *group = groups[i];
+      const char *subsystem;
+      int j;
+
+      if (g_str_has_prefix (group, FLATPAK_METADATA_GROUP_PREFIX_POLICY))
+        {
+          g_auto(GStrv) keys = NULL;
+          subsystem = group + strlen (FLATPAK_METADATA_GROUP_PREFIX_POLICY);
+          keys = g_key_file_get_keys (metakey, group, NULL, NULL);
+          for (j = 0; keys != NULL && keys[j] != NULL; j++)
+            {
+              const char *key = keys[j];
+              g_autofree char *policy_key = g_strdup_printf ("%s.%s", subsystem, key);
+              g_auto(GStrv) values = NULL;
+              int k;
+
+              values = g_key_file_get_string_list (metakey, group, key, NULL, NULL);
+              for (k = 0; values != NULL && values[k] != NULL; k++)
+                flatpak_context_apply_generic_policy (context, policy_key,
+                                                      values[k]);
+            }
+        }
+    }
+
+  return TRUE;
+}
+
+/*
+ * Save the FLATPAK_METADATA_GROUP_CONTEXT,
+ * FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY,
+ * FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY and
+ * FLATPAK_METADATA_GROUP_ENVIRONMENT groups, and all groups starting
+ * with FLATPAK_METADATA_GROUP_PREFIX_POLICY, into metakey
+ */
+void
+flatpak_context_save_metadata (FlatpakContext *context,
+                               gboolean        flatten,
+                               GKeyFile       *metakey)
+{
+  g_auto(GStrv) shared = NULL;
+  g_auto(GStrv) sockets = NULL;
+  g_auto(GStrv) devices = NULL;
+  g_auto(GStrv) features = NULL;
+  GHashTableIter iter;
+  gpointer key, value;
+  FlatpakContextShares shares_mask = context->shares;
+  FlatpakContextShares shares_valid = context->shares_valid;
+  FlatpakContextSockets sockets_mask = context->sockets;
+  FlatpakContextSockets sockets_valid = context->sockets_valid;
+  FlatpakContextDevices devices_mask = context->devices;
+  FlatpakContextDevices devices_valid = context->devices_valid;
+  FlatpakContextFeatures features_mask = context->features;
+  FlatpakContextFeatures features_valid = context->features_valid;
+  g_auto(GStrv) groups = NULL;
+  int i;
+
+  if (flatten)
+    {
+      /* A flattened format means we don't expect this to be merged on top of
+         another context. In that case we never need to negate any flags.
+         We calculate this by removing the zero parts of the mask from the valid set.
+       */
+      /* First we make sure only the valid parts of the mask are set, in case we
+         got some leftover */
+      shares_mask &= shares_valid;
+      sockets_mask &= sockets_valid;
+      devices_mask &= devices_valid;
+      features_mask &= features_valid;
+
+      /* Then just set the valid set to be the mask set */
+      shares_valid = shares_mask;
+      sockets_valid = sockets_mask;
+      devices_valid = devices_mask;
+      features_valid = features_mask;
+    }
+
+  shared = flatpak_context_shared_to_string (shares_mask, shares_valid);
+  sockets = flatpak_context_sockets_to_string (sockets_mask, sockets_valid);
+  devices = flatpak_context_devices_to_string (devices_mask, devices_valid);
+  features = flatpak_context_features_to_string (features_mask, features_valid);
+
+  if (shared[0] != NULL)
+    {
+      g_key_file_set_string_list (metakey,
+                                  FLATPAK_METADATA_GROUP_CONTEXT,
+                                  FLATPAK_METADATA_KEY_SHARED,
+                                  (const char * const *) shared, g_strv_length (shared));
+    }
+  else
+    {
+      g_key_file_remove_key (metakey,
+                             FLATPAK_METADATA_GROUP_CONTEXT,
+                             FLATPAK_METADATA_KEY_SHARED,
+                             NULL);
+    }
+
+  if (sockets[0] != NULL)
+    {
+      g_key_file_set_string_list (metakey,
+                                  FLATPAK_METADATA_GROUP_CONTEXT,
+                                  FLATPAK_METADATA_KEY_SOCKETS,
+                                  (const char * const *) sockets, g_strv_length (sockets));
+    }
+  else
+    {
+      g_key_file_remove_key (metakey,
+                             FLATPAK_METADATA_GROUP_CONTEXT,
+                             FLATPAK_METADATA_KEY_SOCKETS,
+                             NULL);
+    }
+
+  if (devices[0] != NULL)
+    {
+      g_key_file_set_string_list (metakey,
+                                  FLATPAK_METADATA_GROUP_CONTEXT,
+                                  FLATPAK_METADATA_KEY_DEVICES,
+                                  (const char * const *) devices, g_strv_length (devices));
+    }
+  else
+    {
+      g_key_file_remove_key (metakey,
+                             FLATPAK_METADATA_GROUP_CONTEXT,
+                             FLATPAK_METADATA_KEY_DEVICES,
+                             NULL);
+    }
+
+  if (features[0] != NULL)
+    {
+      g_key_file_set_string_list (metakey,
+                                  FLATPAK_METADATA_GROUP_CONTEXT,
+                                  FLATPAK_METADATA_KEY_FEATURES,
+                                  (const char * const *) features, g_strv_length (features));
+    }
+  else
+    {
+      g_key_file_remove_key (metakey,
+                             FLATPAK_METADATA_GROUP_CONTEXT,
+                             FLATPAK_METADATA_KEY_FEATURES,
+                             NULL);
+    }
+
+  if (g_hash_table_size (context->filesystems) > 0)
+    {
+      g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func (g_free);
+
+      g_hash_table_iter_init (&iter, context->filesystems);
+      while (g_hash_table_iter_next (&iter, &key, &value))
+        {
+          FlatpakFilesystemMode mode = GPOINTER_TO_INT (value);
+
+          if (mode != FLATPAK_FILESYSTEM_MODE_NONE)
+            g_ptr_array_add (array, unparse_filesystem_flags (key, mode));
+          else
+            g_ptr_array_add (array, g_strconcat ("!", key, NULL));
+        }
+
+      g_key_file_set_string_list (metakey,
+                                  FLATPAK_METADATA_GROUP_CONTEXT,
+                                  FLATPAK_METADATA_KEY_FILESYSTEMS,
+                                  (const char * const *) array->pdata, array->len);
+    }
+  else
+    {
+      g_key_file_remove_key (metakey,
+                             FLATPAK_METADATA_GROUP_CONTEXT,
+                             FLATPAK_METADATA_KEY_FILESYSTEMS,
+                             NULL);
+    }
+
+  if (g_hash_table_size (context->persistent) > 0)
+    {
+      g_autofree char **keys = (char **) g_hash_table_get_keys_as_array (context->persistent, NULL);
+
+      g_key_file_set_string_list (metakey,
+                                  FLATPAK_METADATA_GROUP_CONTEXT,
+                                  FLATPAK_METADATA_KEY_PERSISTENT,
+                                  (const char * const *) keys, g_strv_length (keys));
+    }
+  else
+    {
+      g_key_file_remove_key (metakey,
+                             FLATPAK_METADATA_GROUP_CONTEXT,
+                             FLATPAK_METADATA_KEY_PERSISTENT,
+                             NULL);
+    }
+
+  g_key_file_remove_group (metakey, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY, NULL);
+  g_hash_table_iter_init (&iter, context->session_bus_policy);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      FlatpakPolicy policy = GPOINTER_TO_INT (value);
+
+      if (flatten && (policy == 0))
+        continue;
+
+      g_key_file_set_string (metakey,
+                             FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY,
+                             (char *) key, flatpak_policy_to_string (policy));
+    }
+
+  g_key_file_remove_group (metakey, FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY, NULL);
+  g_hash_table_iter_init (&iter, context->system_bus_policy);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      FlatpakPolicy policy = GPOINTER_TO_INT (value);
+
+      if (flatten && (policy == 0))
+        continue;
+
+      g_key_file_set_string (metakey,
+                             FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY,
+                             (char *) key, flatpak_policy_to_string (policy));
+    }
+
+  g_key_file_remove_group (metakey, FLATPAK_METADATA_GROUP_ENVIRONMENT, NULL);
+  g_hash_table_iter_init (&iter, context->env_vars);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      g_key_file_set_string (metakey,
+                             FLATPAK_METADATA_GROUP_ENVIRONMENT,
+                             (char *) key, (char *) value);
+    }
+
+
+  groups = g_key_file_get_groups (metakey, NULL);
+  for (i = 0; groups[i] != NULL; i++)
+    {
+      const char *group = groups[i];
+      if (g_str_has_prefix (group, FLATPAK_METADATA_GROUP_PREFIX_POLICY))
+        g_key_file_remove_group (metakey, group, NULL);
+    }
+
+  g_hash_table_iter_init (&iter, context->generic_policy);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      g_auto(GStrv) parts = g_strsplit ((const char *) key, ".", 2);
+      g_autofree char *group = NULL;
+      g_assert (parts[1] != NULL);
+      const char **policy_values = (const char **) value;
+      g_autoptr(GPtrArray) new = g_ptr_array_new ();
+
+      for (i = 0; policy_values[i] != NULL; i++)
+        {
+          const char *policy_value = policy_values[i];
+
+          if (!flatten || policy_value[0] != '!')
+            g_ptr_array_add (new, (char *) policy_value);
+        }
+
+      if (new->len > 0)
+        {
+          group = g_strconcat (FLATPAK_METADATA_GROUP_PREFIX_POLICY,
+                               parts[0], NULL);
+          g_key_file_set_string_list (metakey, group, parts[1],
+                                      (const char * const *) new->pdata,
+                                      new->len);
+        }
+    }
+}
+
+#endif
+
+void
+flatpak_context_allow_host_fs (FlatpakContext *context)
+{
+  flatpak_context_take_filesystem (context, g_strdup ("host"), FLATPAK_FILESYSTEM_MODE_READ_WRITE);
+}
+
+gboolean
+flatpak_context_get_needs_session_bus_proxy (FlatpakContext *context)
+{
+  return g_hash_table_size (context->session_bus_policy) > 0;
+}
+
+gboolean
+flatpak_context_get_needs_system_bus_proxy (FlatpakContext *context)
+{
+  return g_hash_table_size (context->system_bus_policy) > 0;
+}
+
+static gboolean
+adds_flags (guint32 old_flags, guint32 new_flags)
+{
+  return (new_flags & ~old_flags) != 0;
+}
+
+static gboolean
+adds_bus_policy (GHashTable *old, GHashTable *new)
+{
+  GLNX_HASH_TABLE_FOREACH_KV (new, const char *, name, gpointer, _new_policy)
+    {
+      int new_policy = GPOINTER_TO_INT (_new_policy);
+      int old_policy = GPOINTER_TO_INT (g_hash_table_lookup (old, name));
+      if (new_policy > old_policy)
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+adds_generic_policy (GHashTable *old, GHashTable *new)
+{
+  GLNX_HASH_TABLE_FOREACH_KV (new, const char *, key, GPtrArray *, new_values)
+    {
+      GPtrArray *old_values = g_hash_table_lookup (old, key);
+      int i;
+
+      if (new_values == NULL || new_values->len == 0)
+        continue;
+
+      if (old_values == NULL || old_values->len == 0)
+        return TRUE;
+
+      for (i = 0; i < new_values->len; i++)
+        {
+          const char *new_value = g_ptr_array_index (new_values, i);
+
+          if (!flatpak_g_ptr_array_contains_string (old_values, new_value))
+            return TRUE;
+        }
+    }
+
+  return FALSE;
+}
+
+static gboolean
+adds_filesystem_access (GHashTable *old, GHashTable *new)
+{
+  FlatpakFilesystemMode old_host_mode = GPOINTER_TO_INT (g_hash_table_lookup (old, "host"));
+
+  GLNX_HASH_TABLE_FOREACH_KV (new, const char *, location, gpointer, _new_mode)
+    {
+      FlatpakFilesystemMode new_mode = GPOINTER_TO_INT (_new_mode);
+      FlatpakFilesystemMode old_mode = GPOINTER_TO_INT (g_hash_table_lookup (old, location));
+
+      /* Allow more limited access to the same thing */
+      if (new_mode <= old_mode)
+        continue;
+
+      /* Allow more limited access if we used to have access to everything */
+     if (new_mode <= old_host_mode)
+        continue;
+
+     /* For the remainder we have to be pessimistic, for instance even
+        if we have home access we can't allow adding access to ~/foo,
+        because foo might be a symlink outside home which didn't work
+        before but would work with an explicit access to that
+        particular file. */
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+
+gboolean
+flatpak_context_adds_permissions (FlatpakContext *old,
+                                  FlatpakContext *new)
+{
+  guint32 old_sockets;
+
+  if (adds_flags (old->shares & old->shares_valid,
+                  new->shares & new->shares_valid))
+    return TRUE;
+
+  old_sockets = old->sockets & old->sockets_valid;
+
+  /* If we used to allow X11, also allow new fallback X11,
+     as that is actually less permissions */
+  if (old_sockets & FLATPAK_CONTEXT_SOCKET_X11)
+    old_sockets |= FLATPAK_CONTEXT_SOCKET_FALLBACK_X11;
+
+  if (adds_flags (old_sockets,
+                  new->sockets & new->sockets_valid))
+    return TRUE;
+
+  if (adds_flags (old->devices & old->devices_valid,
+                  new->devices & new->devices_valid))
+    return TRUE;
+
+  /* We allow upgrade to multiarch, that is really not a huge problem */
+  if (adds_flags ((old->features & old->features_valid) | FLATPAK_CONTEXT_FEATURE_MULTIARCH,
+                  new->features & new->features_valid))
+    return TRUE;
+
+  if (adds_bus_policy (old->session_bus_policy, new->session_bus_policy))
+    return TRUE;
+
+  if (adds_bus_policy (old->system_bus_policy, new->system_bus_policy))
+    return TRUE;
+
+  if (adds_generic_policy (old->generic_policy, new->generic_policy))
+    return TRUE;
+
+  if (adds_filesystem_access (old->filesystems, new->filesystems))
+    return TRUE;
+
+  return FALSE;
+}
+
+gboolean
+flatpak_context_allows_features (FlatpakContext        *context,
+                                 FlatpakContextFeatures features)
+{
+  return (context->features & features) == features;
+}
+
+void
+flatpak_context_to_args (FlatpakContext *context,
+                         GPtrArray      *args)
+{
+  GHashTableIter iter;
+  gpointer key, value;
+
+  flatpak_context_shared_to_args (context->shares, context->shares_valid, args);
+  flatpak_context_sockets_to_args (context->sockets, context->sockets_valid, args);
+  flatpak_context_devices_to_args (context->devices, context->devices_valid, args);
+  flatpak_context_features_to_args (context->features, context->features_valid, args);
+
+  g_hash_table_iter_init (&iter, context->env_vars);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    g_ptr_array_add (args, g_strdup_printf ("--env=%s=%s", (char *) key, (char *) value));
+
+  g_hash_table_iter_init (&iter, context->persistent);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    g_ptr_array_add (args, g_strdup_printf ("--persist=%s", (char *) key));
+
+  g_hash_table_iter_init (&iter, context->session_bus_policy);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      const char *name = key;
+      FlatpakPolicy policy = GPOINTER_TO_INT (value);
+
+      g_ptr_array_add (args, g_strdup_printf ("--%s-name=%s", flatpak_policy_to_string (policy), name));
+    }
+
+  g_hash_table_iter_init (&iter, context->system_bus_policy);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      const char *name = key;
+      FlatpakPolicy policy = GPOINTER_TO_INT (value);
+
+      g_ptr_array_add (args, g_strdup_printf ("--system-%s-name=%s", flatpak_policy_to_string (policy), name));
+    }
+
+  g_hash_table_iter_init (&iter, context->filesystems);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      FlatpakFilesystemMode mode = GPOINTER_TO_INT (value);
+
+      if (mode != FLATPAK_FILESYSTEM_MODE_NONE)
+        {
+          g_autofree char *fs = unparse_filesystem_flags (key, mode);
+          g_ptr_array_add (args, g_strdup_printf ("--filesystem=%s", fs));
+        }
+      else
+        g_ptr_array_add (args, g_strdup_printf ("--nofilesystem=%s", (char *) key));
+    }
+}
+
+void
+flatpak_context_add_bus_filters (FlatpakContext *context,
+                                 const char     *app_id,
+                                 gboolean        session_bus,
+                                 gboolean        sandboxed,
+                                 FlatpakBwrap   *bwrap)
+{
+  GHashTable *ht;
+  GHashTableIter iter;
+  gpointer key, value;
+
+  flatpak_bwrap_add_arg (bwrap, "--filter");
+  if (app_id && session_bus)
+    {
+      if (!sandboxed)
+        {
+          flatpak_bwrap_add_arg_printf (bwrap, "--own=%s.*", app_id);
+          flatpak_bwrap_add_arg_printf (bwrap, "--own=org.mpris.MediaPlayer2.%s.*", app_id);
+        }
+      else
+        flatpak_bwrap_add_arg_printf (bwrap, "--own=%s.Sandboxed.*", app_id);
+    }
+
+  if (session_bus)
+    ht = context->session_bus_policy;
+  else
+    ht = context->system_bus_policy;
+
+  g_hash_table_iter_init (&iter, ht);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      FlatpakPolicy policy = GPOINTER_TO_INT (value);
+
+      if (policy > 0)
+        flatpak_bwrap_add_arg_printf (bwrap, "--%s=%s",
+                                      flatpak_policy_to_string (policy),
+                                      (char *) key);
+    }
+}
+
+void
+flatpak_context_reset_non_permissions (FlatpakContext *context)
+{
+  g_hash_table_remove_all (context->env_vars);
+}
+
+void
+flatpak_context_reset_permissions (FlatpakContext *context)
+{
+  context->shares_valid = 0;
+  context->sockets_valid = 0;
+  context->devices_valid = 0;
+  context->features_valid = 0;
+
+  context->shares = 0;
+  context->sockets = 0;
+  context->devices = 0;
+  context->features = 0;
+
+  g_hash_table_remove_all (context->persistent);
+  g_hash_table_remove_all (context->filesystems);
+  g_hash_table_remove_all (context->session_bus_policy);
+  g_hash_table_remove_all (context->system_bus_policy);
+  g_hash_table_remove_all (context->generic_policy);
+}
+
+void
+flatpak_context_make_sandboxed (FlatpakContext *context)
+{
+  /* We drop almost everything from the app permission, except
+   * multiarch which is inherited, to make sure app code keeps
+   * running. */
+  context->shares_valid &= 0;
+  context->sockets_valid &= 0;
+  context->devices_valid &= 0;
+  context->features_valid &= FLATPAK_CONTEXT_FEATURE_MULTIARCH;
+
+  context->shares &= context->shares_valid;
+  context->sockets &= context->sockets_valid;
+  context->devices &= context->devices_valid;
+  context->features &= context->features_valid;
+
+  g_hash_table_remove_all (context->persistent);
+  g_hash_table_remove_all (context->filesystems);
+  g_hash_table_remove_all (context->session_bus_policy);
+  g_hash_table_remove_all (context->system_bus_policy);
+  g_hash_table_remove_all (context->generic_policy);
+}
+
+const char *dont_mount_in_root[] = {
+  ".", "..", "lib", "lib32", "lib64", "bin", "sbin", "usr", "boot", "root",
+  "tmp", "etc", "app", "run", "proc", "sys", "dev", "var", NULL
+};
+
+static void
+flatpak_context_export (FlatpakContext *context,
+                        FlatpakExports *exports,
+                        GFile          *app_id_dir,
+                        GPtrArray       *extra_app_id_dirs,
+                        gboolean        do_create,
+                        GString        *xdg_dirs_conf,
+                        gboolean       *home_access_out)
+{
+  gboolean home_access = FALSE;
+  FlatpakFilesystemMode fs_mode, os_mode, etc_mode, home_mode;
+  GHashTableIter iter;
+  gpointer key, value;
+
+  fs_mode = (FlatpakFilesystemMode) g_hash_table_lookup (context->filesystems, "host");
+  if (fs_mode != FLATPAK_FILESYSTEM_MODE_NONE)
+    {
+      DIR *dir;
+      struct dirent *dirent;
+
+      g_debug ("Allowing host-fs access");
+      home_access = TRUE;
+
+      /* Bind mount most dirs in / into the new root */
+      dir = opendir ("/");
+      if (dir != NULL)
+        {
+          while ((dirent = readdir (dir)))
+            {
+              g_autofree char *path = NULL;
+
+              if (g_strv_contains (dont_mount_in_root, dirent->d_name))
+                continue;
+
+              path = g_build_filename ("/", dirent->d_name, NULL);
+              flatpak_exports_add_path_expose (exports, fs_mode, path);
+            }
+          closedir (dir);
+        }
+      flatpak_exports_add_path_expose (exports, fs_mode, "/run/media");
+    }
+
+  os_mode = MAX ((FlatpakFilesystemMode) g_hash_table_lookup (context->filesystems, "host-os"),
+                   fs_mode);
+
+  if (os_mode != FLATPAK_FILESYSTEM_MODE_NONE)
+    flatpak_exports_add_host_os_expose (exports, os_mode);
+
+  etc_mode = MAX ((FlatpakFilesystemMode) g_hash_table_lookup (context->filesystems, "host-etc"),
+                   fs_mode);
+
+  if (etc_mode != FLATPAK_FILESYSTEM_MODE_NONE)
+    flatpak_exports_add_host_etc_expose (exports, etc_mode);
+
+  home_mode = (FlatpakFilesystemMode) g_hash_table_lookup (context->filesystems, "home");
+  if (home_mode != FLATPAK_FILESYSTEM_MODE_NONE)
+    {
+      g_debug ("Allowing homedir access");
+      home_access = TRUE;
+
+      flatpak_exports_add_path_expose (exports, MAX (home_mode, fs_mode), g_get_home_dir ());
+    }
+
+  g_hash_table_iter_init (&iter, context->filesystems);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      const char *filesystem = key;
+      FlatpakFilesystemMode mode = GPOINTER_TO_INT (value);
+
+      if (g_strv_contains (flatpak_context_special_filesystems, filesystem))
+        continue;
+
+      if (g_str_has_prefix (filesystem, "xdg-"))
+        {
+          const char *path, *rest = NULL;
+          const char *config_key = NULL;
+          g_autofree char *subpath = NULL;
+
+          if (!get_xdg_user_dir_from_string (filesystem, &config_key, &rest, &path))
+            {
+              g_warning ("Unsupported xdg dir %s", filesystem);
+              continue;
+            }
+
+          if (path == NULL)
+            continue; /* Unconfigured, ignore */
+
+          if (strcmp (path, g_get_home_dir ()) == 0)
+            {
+              /* xdg-user-dirs sets disabled dirs to $HOME, and its in general not a good
+                 idea to set full access to $HOME other than explicitly, so we ignore
+                 these */
+              g_debug ("Xdg dir %s is $HOME (i.e. disabled), ignoring", filesystem);
+              continue;
+            }
+
+          subpath = g_build_filename (path, rest, NULL);
+
+          if (mode == FLATPAK_FILESYSTEM_MODE_CREATE && do_create)
+            g_mkdir_with_parents (subpath, 0755);
+
+          if (g_file_test (subpath, G_FILE_TEST_EXISTS))
+            {
+              if (config_key && xdg_dirs_conf)
+                g_string_append_printf (xdg_dirs_conf, "%s=\"%s\"\n",
+                                        config_key, path);
+
+              flatpak_exports_add_path_expose_or_hide (exports, mode, subpath);
+            }
+        }
+      else if (g_str_has_prefix (filesystem, "~/"))
+        {
+          g_autofree char *path = NULL;
+
+          path = g_build_filename (g_get_home_dir (), filesystem + 2, NULL);
+
+          if (mode == FLATPAK_FILESYSTEM_MODE_CREATE && do_create)
+            g_mkdir_with_parents (path, 0755);
+
+          if (g_file_test (path, G_FILE_TEST_EXISTS))
+            flatpak_exports_add_path_expose_or_hide (exports, mode, path);
+        }
+      else if (g_str_has_prefix (filesystem, "/"))
+        {
+          if (mode == FLATPAK_FILESYSTEM_MODE_CREATE && do_create)
+            g_mkdir_with_parents (filesystem, 0755);
+
+          if (g_file_test (filesystem, G_FILE_TEST_EXISTS))
+            flatpak_exports_add_path_expose_or_hide (exports, mode, filesystem);
+        }
+      else
+        {
+          g_warning ("Unexpected filesystem arg %s", filesystem);
+        }
+    }
+
+  if (app_id_dir)
+    {
+      g_autoptr(GFile) apps_dir = g_file_get_parent (app_id_dir);
+      int i;
+      /* Hide the .var/app dir by default (unless explicitly made visible) */
+      flatpak_exports_add_path_tmpfs (exports, flatpak_file_get_path_cached (apps_dir));
+      /* But let the app write to the per-app dir in it */
+      flatpak_exports_add_path_expose (exports, FLATPAK_FILESYSTEM_MODE_READ_WRITE,
+                                       flatpak_file_get_path_cached (app_id_dir));
+
+      if (extra_app_id_dirs != NULL)
+        {
+          for (i = 0; i < extra_app_id_dirs->len; i++)
+            {
+              GFile *extra_app_id_dir = g_ptr_array_index (extra_app_id_dirs, i);
+              flatpak_exports_add_path_expose (exports, FLATPAK_FILESYSTEM_MODE_READ_WRITE,
+                                               flatpak_file_get_path_cached (extra_app_id_dir));
+            }
+        }
+    }
+
+  if (home_access_out != NULL)
+    *home_access_out = home_access;
+}
+
+FlatpakExports *
+flatpak_context_get_exports (FlatpakContext *context,
+                             const char     *app_id)
+{
+  g_autoptr(FlatpakExports) exports = flatpak_exports_new ();
+  g_autoptr(GFile) app_id_dir = flatpak_get_data_dir (app_id);
+
+  flatpak_context_export (context, exports, app_id_dir, NULL, FALSE, NULL, NULL);
+  return g_steal_pointer (&exports);
+}
+
+FlatpakRunFlags
+flatpak_context_get_run_flags (FlatpakContext *context)
+{
+  FlatpakRunFlags flags = 0;
+
+  if (flatpak_context_allows_features (context, FLATPAK_CONTEXT_FEATURE_DEVEL))
+    flags |= FLATPAK_RUN_FLAG_DEVEL;
+
+  if (flatpak_context_allows_features (context, FLATPAK_CONTEXT_FEATURE_MULTIARCH))
+    flags |= FLATPAK_RUN_FLAG_MULTIARCH;
+
+  if (flatpak_context_allows_features (context, FLATPAK_CONTEXT_FEATURE_BLUETOOTH))
+    flags |= FLATPAK_RUN_FLAG_BLUETOOTH;
+
+  if (flatpak_context_allows_features (context, FLATPAK_CONTEXT_FEATURE_CANBUS))
+    flags |= FLATPAK_RUN_FLAG_CANBUS;
+
+  return flags;
+}
+
+void
+flatpak_context_append_bwrap_filesystem (FlatpakContext  *context,
+                                         FlatpakBwrap    *bwrap,
+                                         const char      *app_id,
+                                         GFile           *app_id_dir,
+                                         GPtrArray       *extra_app_id_dirs,
+                                         FlatpakExports **exports_out)
+{
+  g_autoptr(FlatpakExports) exports = flatpak_exports_new ();
+  g_autoptr(GString) xdg_dirs_conf = g_string_new ("");
+#if 0
+  g_autoptr(GFile) user_flatpak_dir = NULL;
+#endif
+  gboolean home_access = FALSE;
+  GHashTableIter iter;
+  gpointer key, value;
+
+  flatpak_context_export (context, exports, app_id_dir, extra_app_id_dirs, TRUE, xdg_dirs_conf, &home_access);
+  if (app_id_dir != NULL)
+    flatpak_run_apply_env_appid (bwrap, app_id_dir);
+
+  if (!home_access)
+    {
+      /* Enable persistent mapping only if no access to real home dir */
+
+      g_hash_table_iter_init (&iter, context->persistent);
+      while (g_hash_table_iter_next (&iter, &key, NULL))
+        {
+          const char *persist = key;
+          g_autofree char *src = g_build_filename (g_get_home_dir (), ".var/app", app_id, persist, NULL);
+          g_autofree char *dest = g_build_filename (g_get_home_dir (), persist, NULL);
+
+          g_mkdir_with_parents (src, 0755);
+
+          flatpak_bwrap_add_bind_arg (bwrap, "--bind", src, dest);
+        }
+    }
+
+  if (app_id_dir != NULL)
+    {
+      g_autofree char *user_runtime_dir = flatpak_get_real_xdg_runtime_dir ();
+      g_autofree char *run_user_app_dst = g_strdup_printf ("/run/user/%d/app/%s", getuid (), app_id);
+      g_autofree char *run_user_app_src = g_build_filename (user_runtime_dir, "app", app_id, NULL);
+
+      if (glnx_shutil_mkdir_p_at (AT_FDCWD,
+                                  run_user_app_src,
+                                  0700,
+                                  NULL,
+                                  NULL))
+        flatpak_bwrap_add_args (bwrap,
+                                "--bind", run_user_app_src, run_user_app_dst,
+                                NULL);
+    }
+
+#if 0
+  /* Hide the flatpak dir by default (unless explicitly made visible) */
+  user_flatpak_dir = flatpak_get_user_base_dir_location ();
+  flatpak_exports_add_path_tmpfs (exports, flatpak_file_get_path_cached (user_flatpak_dir));
+#endif
+
+  /* Ensure we always have a homedir */
+  flatpak_exports_add_path_dir (exports, g_get_home_dir ());
+
+  /* This actually outputs the args for the hide/expose operations above */
+  flatpak_exports_append_bwrap_args (exports, bwrap);
+
+  /* Special case subdirectories of the cache, config and data xdg
+   * dirs.  If these are accessible explicitly, then we bind-mount
+   * these in the app-id dir. This allows applications to explicitly
+   * opt out of keeping some config/cache/data in the app-specific
+   * directory.
+   */
+  if (app_id_dir)
+    {
+      g_hash_table_iter_init (&iter, context->filesystems);
+      while (g_hash_table_iter_next (&iter, &key, &value))
+        {
+          const char *filesystem = key;
+          FlatpakFilesystemMode mode = GPOINTER_TO_INT (value);
+          g_autofree char *xdg_path = NULL;
+          const char *rest, *where;
+
+          xdg_path = get_xdg_dir_from_string (filesystem, &rest, &where);
+
+          if (xdg_path != NULL && *rest != 0 &&
+              mode >= FLATPAK_FILESYSTEM_MODE_READ_ONLY)
+            {
+              g_autoptr(GFile) app_version = g_file_get_child (app_id_dir, where);
+              g_autoptr(GFile) app_version_subdir = g_file_resolve_relative_path (app_version, rest);
+
+              if (g_file_test (xdg_path, G_FILE_TEST_IS_DIR) ||
+                  g_file_test (xdg_path, G_FILE_TEST_IS_REGULAR))
+                {
+                  g_autofree char *xdg_path_in_app = g_file_get_path (app_version_subdir);
+                  flatpak_bwrap_add_bind_arg (bwrap,
+                                              mode == FLATPAK_FILESYSTEM_MODE_READ_ONLY ? "--ro-bind" : "--bind",
+                                              xdg_path, xdg_path_in_app);
+                }
+            }
+        }
+    }
+
+  if (home_access && app_id_dir != NULL)
+    {
+      g_autofree char *src_path = g_build_filename (g_get_user_config_dir (),
+                                                    "user-dirs.dirs",
+                                                    NULL);
+      g_autofree char *path = g_build_filename (flatpak_file_get_path_cached (app_id_dir),
+                                                "config/user-dirs.dirs", NULL);
+      if (g_file_test (src_path, G_FILE_TEST_EXISTS))
+        flatpak_bwrap_add_bind_arg (bwrap, "--ro-bind", src_path, path);
+    }
+  else if (xdg_dirs_conf->len > 0 && app_id_dir != NULL)
+    {
+      g_autofree char *path =
+        g_build_filename (flatpak_file_get_path_cached (app_id_dir),
+                          "config/user-dirs.dirs", NULL);
+
+      flatpak_bwrap_add_args_data (bwrap, "xdg-config-dirs",
+                                   xdg_dirs_conf->str, xdg_dirs_conf->len, path, NULL);
+    }
+
+  if (exports_out)
+    *exports_out = g_steal_pointer (&exports);
+}
diff --git a/src/flatpak-error.h b/src/flatpak-error.h
new file mode 100644
index 0000000000000000000000000000000000000000..d62bf8fa9c8811c54e2dec6c338315a544a537af
--- /dev/null
+++ b/src/flatpak-error.h
@@ -0,0 +1,108 @@
+/* flatpak-error.c
+ * Adapted from Flatpak, last update: 1.8.2
+ *
+ * Copyright (C) 2015 Red Hat, Inc
+ *
+ * This file 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 file 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 program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *       Alexander Larsson <alexl@redhat.com>
+ */
+
+#ifndef FLATPAK_ERROR_H
+#define FLATPAK_ERROR_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+/* NOTE: If you add an error code below, also update the list in common/flatpak-utils.c */
+/**
+ * FlatpakError:
+ * @FLATPAK_ERROR_ALREADY_INSTALLED: App/runtime is already installed
+ * @FLATPAK_ERROR_NOT_INSTALLED: App/runtime is not installed
+ * @FLATPAK_ERROR_ONLY_PULLED: App/runtime was only pulled into the local
+ *                             repository but not installed.
+ * @FLATPAK_ERROR_DIFFERENT_REMOTE: The App/Runtime is already installed, but from a different remote.
+ * @FLATPAK_ERROR_ABORTED: The transaction was aborted (returned %TRUE in operation-error signal).
+ * @FLATPAK_ERROR_SKIPPED: The App/Runtime install was skipped due to earlier errors.
+ * @FLATPAK_ERROR_NEED_NEW_FLATPAK: The App/Runtime needs a more recent version of flatpak.
+ * @FLATPAK_ERROR_REMOTE_NOT_FOUND: The specified remote was not found.
+ * @FLATPAK_ERROR_RUNTIME_NOT_FOUND: A runtime needed for the app was not found.
+ * @FLATPAK_ERROR_DOWNGRADE: The pulled commit is a downgrade, and a downgrade wasn't
+ *                           specifically allowed. (Since: 1.0)
+ * @FLATPAK_ERROR_INVALID_REF: A ref could not be parsed. (Since: 1.0.3)
+ * @FLATPAK_ERROR_INVALID_DATA: Invalid data. (Since: 1.0.3)
+ * @FLATPAK_ERROR_UNTRUSTED: Missing GPG key or signature. (Since: 1.0.3)
+ * @FLATPAK_ERROR_SETUP_FAILED: Sandbox setup failed. (Since: 1.0.3)
+ * @FLATPAK_ERROR_EXPORT_FAILED: Exporting data failed. (Since: 1.0.3)
+ * @FLATPAK_ERROR_REMOTE_USED: Remote can't be uninstalled. (Since: 1.0.3)
+ * @FLATPAK_ERROR_RUNTIME_USED: Runtime can't be uninstalled. (Since: 1.0.3)
+ * @FLATPAK_ERROR_INVALID_NAME: Application, runtime or remote name is invalid. (Since: 1.0.3)
+ * @FLATPAK_ERROR_OUT_OF_SPACE: More disk space needed. (Since: 1.2.0)
+ * @FLATPAK_ERROR_WRONG_USER: An operation is being attempted by the wrong user (such as
+ *                            root operating on a user installation). (Since: 1.2.0)
+ * @FLATPAK_ERROR_NOT_CACHED: Cached data was requested, but it was not available. (Since: 1.4.0)
+ * @FLATPAK_ERROR_REF_NOT_FOUND: The specified ref was not found. (Since: 1.4.0)
+ * @FLATPAK_ERROR_PERMISSION_DENIED: An operation was not allowed by the administrative policy.
+ *                                   For example, an app is not allowed to be installed due
+ *                                   to not complying with the parental controls policy. (Since: 1.5.1)
+ * @FLATPAK_ERROR_AUTHENTICATION_FAILED: An authentication operation failed, for example, no
+ *                                       correct password was supplied. (Since: 1.7.3)
+ * @FLATPAK_ERROR_NOT_AUTHORIZED: An operation tried to access a ref, or information about it that it
+ *                                was not authorized. For example, when succesfully authenticating with a
+ *                                server but the user doesn't have permissions for a private ref. (Since: 1.7.3)
+ *
+ * Error codes for library functions.
+ */
+typedef enum {
+  FLATPAK_ERROR_ALREADY_INSTALLED,
+  FLATPAK_ERROR_NOT_INSTALLED,
+  FLATPAK_ERROR_ONLY_PULLED,
+  FLATPAK_ERROR_DIFFERENT_REMOTE,
+  FLATPAK_ERROR_ABORTED,
+  FLATPAK_ERROR_SKIPPED,
+  FLATPAK_ERROR_NEED_NEW_FLATPAK,
+  FLATPAK_ERROR_REMOTE_NOT_FOUND,
+  FLATPAK_ERROR_RUNTIME_NOT_FOUND,
+  FLATPAK_ERROR_DOWNGRADE,
+  FLATPAK_ERROR_INVALID_REF,
+  FLATPAK_ERROR_INVALID_DATA,
+  FLATPAK_ERROR_UNTRUSTED,
+  FLATPAK_ERROR_SETUP_FAILED,
+  FLATPAK_ERROR_EXPORT_FAILED,
+  FLATPAK_ERROR_REMOTE_USED,
+  FLATPAK_ERROR_RUNTIME_USED,
+  FLATPAK_ERROR_INVALID_NAME,
+  FLATPAK_ERROR_OUT_OF_SPACE,
+  FLATPAK_ERROR_WRONG_USER,
+  FLATPAK_ERROR_NOT_CACHED,
+  FLATPAK_ERROR_REF_NOT_FOUND,
+  FLATPAK_ERROR_PERMISSION_DENIED,
+  FLATPAK_ERROR_AUTHENTICATION_FAILED,
+  FLATPAK_ERROR_NOT_AUTHORIZED,
+} FlatpakError;
+
+/**
+ * FLATPAK_ERROR:
+ *
+ * The error domain for #FlatpakError errors.
+ */
+#define FLATPAK_ERROR flatpak_error_quark ()
+
+FLATPAK_EXTERN GQuark  flatpak_error_quark (void);
+
+G_END_DECLS
+
+#endif /* FLATPAK_ERROR_H */
diff --git a/src/flatpak-exports-private.h b/src/flatpak-exports-private.h
new file mode 100644
index 0000000000000000000000000000000000000000..6da3bef4b0714874090e745154b7a117a903519e
--- /dev/null
+++ b/src/flatpak-exports-private.h
@@ -0,0 +1,66 @@
+/*
+ * Taken from Flatpak, last updated: 1.9.x commit 1.8.0-74-g354b9a22
+ * Copyright © 2014-2018 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *       Alexander Larsson <alexl@redhat.com>
+ */
+
+#ifndef __FLATPAK_EXPORTS_H__
+#define __FLATPAK_EXPORTS_H__
+
+#include "libglnx/libglnx.h"
+#include "flatpak-bwrap-private.h"
+
+/* In numerical order of more privs */
+typedef enum {
+  FLATPAK_FILESYSTEM_MODE_NONE         = 0,
+  FLATPAK_FILESYSTEM_MODE_READ_ONLY    = 1,
+  FLATPAK_FILESYSTEM_MODE_READ_WRITE   = 2,
+  FLATPAK_FILESYSTEM_MODE_CREATE       = 3,
+  FLATPAK_FILESYSTEM_MODE_LAST         = FLATPAK_FILESYSTEM_MODE_CREATE
+} FlatpakFilesystemMode;
+
+typedef struct _FlatpakExports FlatpakExports;
+
+void flatpak_exports_free (FlatpakExports *exports);
+FlatpakExports *flatpak_exports_new (void);
+void flatpak_exports_append_bwrap_args (FlatpakExports *exports,
+                                        FlatpakBwrap   *bwrap);
+void flatpak_exports_add_host_etc_expose (FlatpakExports       *exports,
+                                          FlatpakFilesystemMode mode);
+void flatpak_exports_add_host_os_expose (FlatpakExports       *exports,
+                                         FlatpakFilesystemMode mode);
+void flatpak_exports_add_path_expose (FlatpakExports       *exports,
+                                      FlatpakFilesystemMode mode,
+                                      const char           *path);
+void flatpak_exports_add_path_tmpfs (FlatpakExports *exports,
+                                     const char     *path);
+void flatpak_exports_add_path_expose_or_hide (FlatpakExports       *exports,
+                                              FlatpakFilesystemMode mode,
+                                              const char           *path);
+void flatpak_exports_add_path_dir (FlatpakExports *exports,
+                                   const char     *path);
+
+gboolean flatpak_exports_path_is_visible (FlatpakExports *exports,
+                                          const char     *path);
+FlatpakFilesystemMode flatpak_exports_path_get_mode (FlatpakExports *exports,
+                                                     const char     *path);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakExports, flatpak_exports_free);
+
+
+#endif /* __FLATPAK_EXPORTS_H__ */
diff --git a/src/flatpak-exports.c b/src/flatpak-exports.c
new file mode 100644
index 0000000000000000000000000000000000000000..eb072a21ed0928b92984f266727df5d6e4b3ca58
--- /dev/null
+++ b/src/flatpak-exports.c
@@ -0,0 +1,796 @@
+/*
+ * Taken from Flatpak, last updated: 1.9.x commit 1.8.0-74-g354b9a22
+ * Copyright © 2014-2019 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *       Alexander Larsson <alexl@redhat.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/utsname.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/vfs.h>
+#include <sys/personality.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <grp.h>
+#include <unistd.h>
+#include <gio/gunixfdlist.h>
+
+#include <gio/gio.h>
+#include "libglnx/libglnx.h"
+
+#include "flatpak-exports-private.h"
+#include "flatpak-run-private.h"
+#include "flatpak-utils-base-private.h"
+#include "flatpak-utils-private.h"
+#include "flatpak-error.h"
+
+/* To keep this more similar to the original file, we explicitly disable
+ * these warnings rather than fixing them */
+#if defined(__GNUC__) && __GNUC__ >= 8
+# pragma GCC diagnostic ignored "-Wcast-function-type"
+#endif
+#pragma GCC diagnostic ignored "-Wshadow"
+#pragma GCC diagnostic ignored "-Wtype-limits"
+
+/* We don't want to export paths pointing into these, because they are readonly
+   (so we can't create mountpoints there) and don't match what's on the host anyway.
+   flatpak_abs_usrmerged_dirs get the same treatment without having to be listed
+   here. */
+const char *dont_export_in[] = {
+  "/usr", "/etc", "/app", "/dev", "/proc", NULL
+};
+
+static char *
+make_relative (const char *base, const char *path)
+{
+  GString *s = g_string_new ("");
+
+  while (*base != 0)
+    {
+      while (*base == '/')
+        base++;
+
+      if (*base != 0)
+        g_string_append (s, "../");
+
+      while (*base != '/' && *base != 0)
+        base++;
+    }
+
+  while (*path == '/')
+    path++;
+
+  g_string_append (s, path);
+
+  return g_string_free (s, FALSE);
+}
+
+#define FAKE_MODE_DIR -1 /* Ensure a dir, either on tmpfs or mapped parent */
+#define FAKE_MODE_TMPFS FLATPAK_FILESYSTEM_MODE_NONE
+#define FAKE_MODE_SYMLINK G_MAXINT
+
+static inline gboolean
+is_export_mode (int mode)
+{
+  return ((mode >= FLATPAK_FILESYSTEM_MODE_NONE
+           && mode <= FLATPAK_FILESYSTEM_MODE_LAST)
+          || mode == FAKE_MODE_DIR
+          || mode == FAKE_MODE_SYMLINK);
+}
+
+typedef struct
+{
+  char *path;
+  gint  mode;
+} ExportedPath;
+
+struct _FlatpakExports
+{
+  GHashTable           *hash;
+  FlatpakFilesystemMode host_etc;
+  FlatpakFilesystemMode host_os;
+};
+
+static void
+exported_path_free (ExportedPath *exported_path)
+{
+  g_free (exported_path->path);
+  g_free (exported_path);
+}
+
+FlatpakExports *
+flatpak_exports_new (void)
+{
+  FlatpakExports *exports = g_new0 (FlatpakExports, 1);
+
+  exports->hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GFreeFunc) exported_path_free);
+  return exports;
+}
+
+void
+flatpak_exports_free (FlatpakExports *exports)
+{
+  g_hash_table_destroy (exports->hash);
+  g_free (exports);
+}
+
+/* Returns TRUE if the location of this export
+   is not visible due to parents being exported */
+static gboolean
+path_parent_is_mapped (const char **keys,
+                       guint        n_keys,
+                       GHashTable  *hash_table,
+                       const char  *path)
+{
+  guint i;
+  gboolean is_mapped = FALSE;
+
+  /* The keys are sorted so shorter (i.e. parents) are first */
+  for (i = 0; i < n_keys; i++)
+    {
+      const char *mounted_path = keys[i];
+      ExportedPath *ep = g_hash_table_lookup (hash_table, mounted_path);
+
+      g_assert (is_export_mode (ep->mode));
+
+      if (flatpak_has_path_prefix (path, mounted_path) &&
+          (strcmp (path, mounted_path) != 0))
+        {
+          /* FAKE_MODE_DIR has same mapped value as parent */
+          if (ep->mode == FAKE_MODE_DIR)
+            continue;
+
+          is_mapped = ep->mode != FAKE_MODE_TMPFS;
+        }
+    }
+
+  return is_mapped;
+}
+
+static gboolean
+path_is_mapped (const char **keys,
+                guint        n_keys,
+                GHashTable  *hash_table,
+                const char  *path,
+                gboolean    *is_readonly_out)
+{
+  guint i;
+  gboolean is_mapped = FALSE;
+  gboolean is_readonly = FALSE;
+
+  /* The keys are sorted so shorter (i.e. parents) are first */
+  for (i = 0; i < n_keys; i++)
+    {
+      const char *mounted_path = keys[i];
+      ExportedPath *ep = g_hash_table_lookup (hash_table, mounted_path);
+
+      g_assert (is_export_mode (ep->mode));
+
+      if (flatpak_has_path_prefix (path, mounted_path))
+        {
+          /* FAKE_MODE_DIR has same mapped value as parent */
+          if (ep->mode == FAKE_MODE_DIR)
+            continue;
+
+          if (ep->mode == FAKE_MODE_SYMLINK)
+            is_mapped = strcmp (path, mounted_path) == 0;
+          else
+            is_mapped = ep->mode != FAKE_MODE_TMPFS;
+
+          if (is_mapped)
+            is_readonly = ep->mode == FLATPAK_FILESYSTEM_MODE_READ_ONLY;
+          else
+            is_readonly = FALSE;
+        }
+    }
+
+  *is_readonly_out = is_readonly;
+  return is_mapped;
+}
+
+static gint
+compare_eps (const ExportedPath *a,
+             const ExportedPath *b)
+{
+  return g_strcmp0 (a->path, b->path);
+}
+
+/* This differs from g_file_test (path, G_FILE_TEST_IS_DIR) which
+   returns true if the path is a symlink to a dir */
+static gboolean
+path_is_dir (const char *path)
+{
+  struct stat s;
+
+  if (lstat (path, &s) != 0)
+    return FALSE;
+
+  return S_ISDIR (s.st_mode);
+}
+
+static gboolean
+path_is_symlink (const char *path)
+{
+  struct stat s;
+
+  if (lstat (path, &s) != 0)
+    return FALSE;
+
+  return S_ISLNK (s.st_mode);
+}
+
+/*
+ * @name: A file or directory below /etc
+ * @test: How we test whether it is suitable
+ *
+ * The paths in /etc that are required if we want to make use of the
+ * host /usr (and /lib, and so on).
+ */
+typedef struct
+{
+  const char *name;
+  GFileTest test;
+} LibsNeedEtc;
+
+static const LibsNeedEtc libs_need_etc[] =
+{
+  /* glibc */
+  { "ld.so.cache", G_FILE_TEST_IS_REGULAR },
+  /* Used for executables and a few libraries on e.g. Debian */
+  { "alternatives", G_FILE_TEST_IS_DIR }
+};
+
+void
+flatpak_exports_append_bwrap_args (FlatpakExports *exports,
+                                   FlatpakBwrap   *bwrap)
+{
+  guint n_keys;
+  g_autofree const char **keys = (const char **) g_hash_table_get_keys_as_array (exports->hash, &n_keys);
+  g_autoptr(GList) eps = NULL;
+  GList *l;
+
+  eps = g_hash_table_get_values (exports->hash);
+  eps = g_list_sort (eps, (GCompareFunc) compare_eps);
+
+  g_qsort_with_data (keys, n_keys, sizeof (char *), (GCompareDataFunc) flatpak_strcmp0_ptr, NULL);
+
+  for (l = eps; l != NULL; l = l->next)
+    {
+      ExportedPath *ep = l->data;
+      const char *path = ep->path;
+
+      g_assert (is_export_mode (ep->mode));
+
+      if (ep->mode == FAKE_MODE_SYMLINK)
+        {
+          if (!path_parent_is_mapped (keys, n_keys, exports->hash, path))
+            {
+              g_autofree char *resolved = flatpak_resolve_link (path, NULL);
+              if (resolved)
+                {
+                  g_autofree char *parent = g_path_get_dirname (path);
+                  g_autofree char *relative = make_relative (parent, resolved);
+                  flatpak_bwrap_add_args (bwrap, "--symlink", relative, path,  NULL);
+                }
+            }
+        }
+      else if (ep->mode == FAKE_MODE_TMPFS)
+        {
+          /* Mount a tmpfs to hide the subdirectory, but only if there
+             is a pre-existing dir we can mount the path on. */
+          if (path_is_dir (path))
+            {
+              if (!path_parent_is_mapped (keys, n_keys, exports->hash, path))
+                /* If the parent is not mapped, it will be a tmpfs, no need to mount another one */
+                flatpak_bwrap_add_args (bwrap, "--dir", path, NULL);
+              else
+                flatpak_bwrap_add_args (bwrap, "--tmpfs", path, NULL);
+            }
+        }
+      else if (ep->mode == FAKE_MODE_DIR)
+        {
+          if (path_is_dir (path))
+            flatpak_bwrap_add_args (bwrap, "--dir", path, NULL);
+        }
+      else
+        {
+          flatpak_bwrap_add_args (bwrap,
+                                  (ep->mode == FLATPAK_FILESYSTEM_MODE_READ_ONLY) ? "--ro-bind" : "--bind",
+                                  path, path, NULL);
+        }
+    }
+
+  g_assert (exports->host_os >= FLATPAK_FILESYSTEM_MODE_NONE);
+  g_assert (exports->host_os <= FLATPAK_FILESYSTEM_MODE_LAST);
+
+  if (exports->host_os != FLATPAK_FILESYSTEM_MODE_NONE)
+    {
+      const char *os_bind_mode = "--bind";
+      int i;
+
+      if (exports->host_os == FLATPAK_FILESYSTEM_MODE_READ_ONLY)
+        os_bind_mode = "--ro-bind";
+
+      if (g_file_test ("/usr", G_FILE_TEST_IS_DIR))
+        flatpak_bwrap_add_args (bwrap,
+                                os_bind_mode, "/usr", "/run/host/usr", NULL);
+
+      for (i = 0; flatpak_abs_usrmerged_dirs[i] != NULL; i++)
+        {
+          const char *subdir = flatpak_abs_usrmerged_dirs[i];
+          g_autofree char *target = NULL;
+          g_autofree char *run_host_subdir = NULL;
+
+          g_assert (subdir[0] == '/');
+          /* e.g. /run/host/lib32 */
+          run_host_subdir = g_strconcat ("/run/host", subdir, NULL);
+          target = glnx_readlinkat_malloc (-1, subdir, NULL, NULL);
+
+          if (target != NULL &&
+              g_str_has_prefix (target, "usr/"))
+            {
+              /* e.g. /lib32 is a relative symlink to usr/lib32, or
+               * on Arch Linux, /lib64 is a relative symlink to usr/lib;
+               * keep it relative */
+              flatpak_bwrap_add_args (bwrap,
+                                      "--symlink", target, run_host_subdir,
+                                      NULL);
+            }
+          else if (target != NULL &&
+                   g_str_has_prefix (target, "/usr/"))
+            {
+              /* e.g. /lib32 is an absolute symlink to /usr/lib32; make
+               * it a relative symlink to usr/lib32 instead by skipping
+               * the '/' */
+              flatpak_bwrap_add_args (bwrap,
+                                      "--symlink", target + 1, run_host_subdir,
+                                      NULL);
+            }
+          else if (g_file_test (subdir, G_FILE_TEST_IS_DIR))
+            {
+              /* e.g. /lib32 is a symlink to /opt/compat/ia32/lib,
+               * or is a plain directory because the host OS has not
+               * undergone the /usr merge; bind-mount the directory instead */
+              flatpak_bwrap_add_args (bwrap,
+                                      os_bind_mode, subdir, run_host_subdir,
+                                      NULL);
+            }
+        }
+
+      if (exports->host_etc == FLATPAK_FILESYSTEM_MODE_NONE)
+        {
+          guint i;
+
+          /* We are exposing the host /usr (and friends) but not the
+           * host /etc. Additionally expose just enough of /etc to make
+           * things that want to read /usr work as expected.
+           *
+           * (If exports->host_etc is nonzero, we'll do this as part of
+           * /etc instead.) */
+
+          for (i = 0; i < G_N_ELEMENTS (libs_need_etc); i++)
+            {
+              const LibsNeedEtc *item = &libs_need_etc[i];
+              g_autofree gchar *host_path = g_strconcat ("/etc/", item->name, NULL);
+
+              if (g_file_test (host_path, item->test))
+                {
+                  g_autofree gchar *run_host_path = g_strconcat ("/run/host/etc/", item->name, NULL);
+
+                  flatpak_bwrap_add_args (bwrap,
+                                          os_bind_mode, host_path, run_host_path,
+                                          NULL);
+                }
+            }
+        }
+    }
+
+  g_assert (exports->host_etc >= FLATPAK_FILESYSTEM_MODE_NONE);
+  g_assert (exports->host_etc <= FLATPAK_FILESYSTEM_MODE_LAST);
+
+  if (exports->host_etc != FLATPAK_FILESYSTEM_MODE_NONE)
+    {
+      const char *etc_bind_mode = "--bind";
+
+      if (exports->host_etc == FLATPAK_FILESYSTEM_MODE_READ_ONLY)
+        etc_bind_mode = "--ro-bind";
+
+      if (g_file_test ("/etc", G_FILE_TEST_IS_DIR))
+        flatpak_bwrap_add_args (bwrap,
+                                etc_bind_mode, "/etc", "/run/host/etc", NULL);
+    }
+
+  /* As per the os-release specification https://www.freedesktop.org/software/systemd/man/os-release.html
+   * always read-only bind-mount /etc/os-release if it exists, or /usr/lib/os-release as a fallback from
+   * the host into the application's /run/host */
+  if (g_file_test ("/etc/os-release", G_FILE_TEST_EXISTS))
+    flatpak_bwrap_add_args (bwrap, "--ro-bind", "/etc/os-release", "/run/host/os-release", NULL);
+  else if (g_file_test ("/usr/lib/os-release", G_FILE_TEST_EXISTS))
+    flatpak_bwrap_add_args (bwrap, "--ro-bind", "/usr/lib/os-release", "/run/host/os-release", NULL);
+}
+
+/* Returns FLATPAK_FILESYSTEM_MODE_NONE if not visible */
+FlatpakFilesystemMode
+flatpak_exports_path_get_mode (FlatpakExports *exports,
+                               const char     *path)
+{
+  guint n_keys;
+  g_autofree const char **keys = (const char **) g_hash_table_get_keys_as_array (exports->hash, &n_keys);
+  g_autofree char *canonical = NULL;
+  gboolean is_readonly = FALSE;
+  g_auto(GStrv) parts = NULL;
+  int i;
+  g_autoptr(GString) path_builder = g_string_new ("");
+  struct stat st;
+
+  g_qsort_with_data (keys, n_keys, sizeof (char *), (GCompareDataFunc) flatpak_strcmp0_ptr, NULL);
+
+  path = canonical = flatpak_canonicalize_filename (path);
+
+  parts = g_strsplit (path + 1, "/", -1);
+
+  /* A path is visible in the sandbox if no parent
+   * path element that is mapped in the sandbox is
+   * a symlink, and the final element is mapped.
+   * If any parent is a symlink we resolve that and
+   * continue with that instead.
+   */
+  for (i = 0; parts[i] != NULL; i++)
+    {
+      g_string_append (path_builder, "/");
+      g_string_append (path_builder, parts[i]);
+
+      if (path_is_mapped (keys, n_keys, exports->hash, path_builder->str, &is_readonly))
+        {
+          if (lstat (path_builder->str, &st) != 0)
+            {
+              if (errno == ENOENT && parts[i + 1] == NULL && !is_readonly)
+                {
+                  /* Last element was mapped but isn't there, this is
+                   * OK (used for the save case) if we the parent is
+                   * mapped and writable, as the app can then create
+                   * the file here.
+                   */
+                  break;
+                }
+
+              return FLATPAK_FILESYSTEM_MODE_NONE;
+            }
+
+          if (S_ISLNK (st.st_mode))
+            {
+              g_autofree char *resolved = flatpak_resolve_link (path_builder->str, NULL);
+              g_autoptr(GString) path2_builder = NULL;
+              int j;
+
+              if (resolved == NULL)
+                return FLATPAK_FILESYSTEM_MODE_NONE;
+
+              path2_builder = g_string_new (resolved);
+
+              for (j = i + 1; parts[j] != NULL; j++)
+                {
+                  g_string_append (path2_builder, "/");
+                  g_string_append (path2_builder, parts[j]);
+                }
+
+              return flatpak_exports_path_get_mode (exports, path2_builder->str);
+            }
+        }
+      else if (parts[i + 1] == NULL)
+        return FLATPAK_FILESYSTEM_MODE_NONE; /* Last part was not mapped */
+    }
+
+  if (is_readonly)
+    return FLATPAK_FILESYSTEM_MODE_READ_ONLY;
+
+  return FLATPAK_FILESYSTEM_MODE_READ_WRITE;
+}
+
+gboolean
+flatpak_exports_path_is_visible (FlatpakExports *exports,
+                                 const char     *path)
+{
+  return flatpak_exports_path_get_mode (exports, path) > FLATPAK_FILESYSTEM_MODE_NONE;
+}
+
+static gboolean
+never_export_as_symlink (const char *path)
+{
+  /* Don't export /tmp as a symlink even if it is on the host, because
+     that will fail with the pre-existing directory we created for /tmp,
+     and anyway, it being a symlink is not useful in the sandbox */
+  if (strcmp (path, "/tmp") == 0)
+    return TRUE;
+
+  return FALSE;
+}
+
+static void
+do_export_path (FlatpakExports *exports,
+                const char     *path,
+                gint            mode)
+{
+  ExportedPath *old_ep = g_hash_table_lookup (exports->hash, path);
+  ExportedPath *ep;
+
+  g_return_if_fail (is_export_mode (mode));
+
+  ep = g_new0 (ExportedPath, 1);
+  ep->path = g_strdup (path);
+
+  if (old_ep != NULL)
+    ep->mode = MAX (old_ep->mode, mode);
+  else
+    ep->mode = mode;
+
+  g_hash_table_replace (exports->hash, ep->path, ep);
+}
+
+/* AUTOFS mounts are tricky, as using them as a source in a bind mount
+ * causes the mount to trigger, which can take a long time (or forever)
+ * waiting for a device or network mount. We try to open the directory
+ * but time out after a while, ignoring the mount. Unfortunately we
+ * have to mess with forks and stuff to be able to handle the timeout.
+ */
+static gboolean
+check_if_autofs_works (const char *path)
+{
+  int selfpipe[2];
+  struct timeval timeout;
+  pid_t pid;
+  fd_set rfds;
+  int res;
+  int wstatus;
+
+  if (pipe2 (selfpipe, O_CLOEXEC) == -1)
+    return FALSE;
+
+  fcntl (selfpipe[0], F_SETFL, fcntl (selfpipe[0], F_GETFL) | O_NONBLOCK);
+  fcntl (selfpipe[1], F_SETFL, fcntl (selfpipe[1], F_GETFL) | O_NONBLOCK);
+
+  pid = fork ();
+  if (pid == -1)
+    {
+      close (selfpipe[0]);
+      close (selfpipe[1]);
+      return FALSE;
+    }
+
+  if (pid == 0)
+    {
+      /* Note: open, close and _exit are signal-async-safe, so it is ok to call in the child after fork */
+
+      close (selfpipe[0]); /* Close unused read end */
+      int dir_fd = open (path, O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_DIRECTORY);
+      _exit (dir_fd == -1 ? 1 : 0);
+    }
+
+  /* Parent */
+  close (selfpipe[1]);  /* Close unused write end */
+
+  /* 200 msec timeout*/
+  timeout.tv_sec = 0;
+  timeout.tv_usec = 200 * 1000;
+
+  FD_ZERO (&rfds);
+  FD_SET (selfpipe[0], &rfds);
+  res = select (selfpipe[0] + 1, &rfds, NULL, NULL, &timeout);
+
+  close (selfpipe[0]);
+
+  if (res == -1 /* Error */ || res == 0) /* Timeout */
+    {
+      /* Kill, but then waitpid to avoid zombie */
+      kill (pid, SIGKILL);
+    }
+
+  if (waitpid (pid, &wstatus, 0) != pid)
+    return FALSE;
+
+  if (res == -1 /* Error */ || res == 0) /* Timeout */
+    return FALSE;
+
+  if (!WIFEXITED (wstatus) || WEXITSTATUS (wstatus) != 0)
+    return FALSE;
+
+  return TRUE;
+}
+
+/* We use level to avoid infinite recursion */
+static gboolean
+_exports_path_expose (FlatpakExports *exports,
+                      int             mode,
+                      const char     *path,
+                      int             level)
+{
+  g_autofree char *canonical = NULL;
+  struct stat st;
+  struct statfs stfs;
+  char *slash;
+  int i;
+  glnx_autofd int o_path_fd = -1;
+
+  g_return_val_if_fail (is_export_mode (mode), FALSE);
+
+  if (level > 40) /* 40 is the current kernel ELOOP check */
+    {
+      g_debug ("Expose too deep, bail");
+      return FALSE;
+    }
+
+  if (!g_path_is_absolute (path))
+    {
+      g_debug ("Not exposing relative path %s", path);
+      return FALSE;
+    }
+
+  /* Check if it exists at all */
+  o_path_fd = open (path, O_PATH | O_NOFOLLOW | O_CLOEXEC);
+  if (o_path_fd == -1)
+    return FALSE;
+
+  if (fstat (o_path_fd, &st) != 0)
+    return FALSE;
+
+  /* Don't expose weird things */
+  if (!(S_ISDIR (st.st_mode) ||
+        S_ISREG (st.st_mode) ||
+        S_ISLNK (st.st_mode) ||
+        S_ISSOCK (st.st_mode)))
+    return FALSE;
+
+  /* O_PATH + fstatfs is the magic that we need to statfs without automounting the target */
+  if (fstatfs (o_path_fd, &stfs) != 0)
+    return FALSE;
+
+  if (stfs.f_type == AUTOFS_SUPER_MAGIC)
+    {
+      if (!check_if_autofs_works (path))
+        {
+          g_debug ("ignoring blocking autofs path %s", path);
+          return FALSE;
+        }
+    }
+
+  path = canonical = flatpak_canonicalize_filename (path);
+
+  for (i = 0; dont_export_in[i] != NULL; i++)
+    {
+      /* Don't expose files in non-mounted dirs like /app or /usr, as
+         they are not the same as on the host, and we generally can't
+         create the parents for them anyway */
+      if (flatpak_has_path_prefix (path, dont_export_in[i]))
+        {
+          g_debug ("skipping export for path %s", path);
+          return FALSE;
+        }
+    }
+
+  for (i = 0; flatpak_abs_usrmerged_dirs[i] != NULL; i++)
+    {
+      /* Same as /usr, but for the directories that get merged into /usr */
+      if (flatpak_has_path_prefix (path, flatpak_abs_usrmerged_dirs[i]))
+        {
+          g_debug ("skipping export for path %s", path);
+          return FALSE;
+        }
+    }
+
+  /* Handle any symlinks prior to the target itself. This includes path itself,
+     because we expose the target of the symlink. */
+  slash = canonical;
+  do
+    {
+      slash = strchr (slash + 1, '/');
+      if (slash)
+        *slash = 0;
+
+      if (path_is_symlink (path) && !never_export_as_symlink (path))
+        {
+          g_autofree char *resolved = flatpak_resolve_link (path, NULL);
+          g_autofree char *new_target = NULL;
+
+          if (resolved)
+            {
+              if (slash)
+                new_target = g_build_filename (resolved, slash + 1, NULL);
+              else
+                new_target = g_strdup (resolved);
+
+              if (_exports_path_expose (exports, mode, new_target, level + 1))
+                {
+                  do_export_path (exports, path, FAKE_MODE_SYMLINK);
+                  return TRUE;
+                }
+            }
+
+          return FALSE;
+        }
+      if (slash)
+        *slash = '/';
+    }
+  while (slash != NULL);
+
+  do_export_path (exports, path, mode);
+  return TRUE;
+}
+
+void
+flatpak_exports_add_path_expose (FlatpakExports       *exports,
+                                 FlatpakFilesystemMode mode,
+                                 const char           *path)
+{
+  g_return_if_fail (mode > FLATPAK_FILESYSTEM_MODE_NONE);
+  g_return_if_fail (mode <= FLATPAK_FILESYSTEM_MODE_LAST);
+  _exports_path_expose (exports, mode, path, 0);
+}
+
+void
+flatpak_exports_add_path_tmpfs (FlatpakExports *exports,
+                                const char     *path)
+{
+  _exports_path_expose (exports, FAKE_MODE_TMPFS, path, 0);
+}
+
+void
+flatpak_exports_add_path_expose_or_hide (FlatpakExports       *exports,
+                                         FlatpakFilesystemMode mode,
+                                         const char           *path)
+{
+  g_return_if_fail (mode >= FLATPAK_FILESYSTEM_MODE_NONE);
+  g_return_if_fail (mode <= FLATPAK_FILESYSTEM_MODE_LAST);
+
+  if (mode == FLATPAK_FILESYSTEM_MODE_NONE)
+    flatpak_exports_add_path_tmpfs (exports, path);
+  else
+    flatpak_exports_add_path_expose (exports, mode, path);
+}
+
+void
+flatpak_exports_add_path_dir (FlatpakExports *exports,
+                              const char     *path)
+{
+  _exports_path_expose (exports, FAKE_MODE_DIR, path, 0);
+}
+
+void
+flatpak_exports_add_host_etc_expose (FlatpakExports       *exports,
+                                     FlatpakFilesystemMode mode)
+{
+  g_return_if_fail (mode > FLATPAK_FILESYSTEM_MODE_NONE);
+  g_return_if_fail (mode <= FLATPAK_FILESYSTEM_MODE_LAST);
+
+  exports->host_etc = mode;
+}
+
+void
+flatpak_exports_add_host_os_expose (FlatpakExports       *exports,
+                                    FlatpakFilesystemMode mode)
+{
+  g_return_if_fail (mode > FLATPAK_FILESYSTEM_MODE_NONE);
+  g_return_if_fail (mode <= FLATPAK_FILESYSTEM_MODE_LAST);
+
+  exports->host_os = mode;
+}
diff --git a/src/flatpak-run-private.h b/src/flatpak-run-private.h
index 24f1a8fd77b06fed406050c2cba0c7bb7dc44868..ddc9a406c535cfa313b06b0ae386e96332b146a0 100644
--- a/src/flatpak-run-private.h
+++ b/src/flatpak-run-private.h
@@ -1,6 +1,6 @@
 /*
  * Cut-down version of common/flatpak-run-private.h from Flatpak
- * Last updated: Flatpak 1.6.1
+ * Last updated: Flatpak 1.8.2
  *
  * Copyright © 2017-2019 Collabora Ltd.
  * Copyright © 2014-2019 Red Hat, Inc
@@ -32,6 +32,8 @@
 
 #include "libglnx/libglnx.h"
 
+#include "flatpak-common-types-private.h"
+#include "flatpak-context-private.h"
 #include "flatpak-bwrap-private.h"
 #include "flatpak-utils-private.h"
 #include "glib-backports.h"
@@ -42,4 +44,9 @@ gboolean flatpak_run_add_wayland_args (FlatpakBwrap *bwrap);
 void flatpak_run_add_pulseaudio_args (FlatpakBwrap *bwrap);
 gboolean flatpak_run_add_system_dbus_args (FlatpakBwrap *app_bwrap);
 gboolean flatpak_run_add_session_dbus_args (FlatpakBwrap *app_bwrap);
+void     flatpak_run_apply_env_appid (FlatpakBwrap *bwrap,
+                                      GFile        *app_dir);
+GFile *flatpak_get_data_dir (const char *app_id);
+
+extern const char * const *flatpak_abs_usrmerged_dirs;
 #endif /* __FLATPAK_RUN_H__ */
diff --git a/src/flatpak-run.c b/src/flatpak-run.c
index 94a20e25ee64917ddfe8323f8fd7086aff9db868..addd6e70786e31eb6d95bc3a674615db5ae7a60c 100644
--- a/src/flatpak-run.c
+++ b/src/flatpak-run.c
@@ -40,6 +40,17 @@
 /* In Flatpak this is optional, in pressure-vessel not so much */
 #define ENABLE_XAUTH
 
+const char * const abs_usrmerged_dirs[] =
+{
+  "/bin",
+  "/lib",
+  "/lib32",
+  "/lib64",
+  "/sbin",
+  NULL
+};
+const char * const *flatpak_abs_usrmerged_dirs = abs_usrmerged_dirs;
+
 static char *
 extract_unix_path_from_dbus_address (const char *address)
 {
@@ -393,6 +404,14 @@ flatpak_run_add_pulseaudio_args (FlatpakBwrap *bwrap)
     }
   else
     g_debug ("Could not find pulseaudio socket");
+
+  /* Also allow ALSA access. This was added in 1.8, and is not ideally named. However,
+   * since the practical permission of ALSA and PulseAudio are essentially the same, and
+   * since we don't want to add more permissions for something we plan to replace with
+   * portals/pipewire going forward we reinterpret pulseaudio to also mean ALSA.
+   */
+  if (g_file_test ("/dev/snd", G_FILE_TEST_IS_DIR))
+    flatpak_bwrap_add_args (bwrap, "--dev-bind", "/dev/snd", "/dev/snd", NULL);
 }
 
 /* Simplified from Flatpak: we never restrict access to the D-Bus system bus */
@@ -458,3 +477,862 @@ flatpak_run_add_session_dbus_args (FlatpakBwrap   *app_bwrap)
 
   return FALSE;
 }
+
+#if 0
+
+static gboolean
+flatpak_run_add_a11y_dbus_args (FlatpakBwrap   *app_bwrap,
+                                FlatpakBwrap   *proxy_arg_bwrap,
+                                FlatpakContext *context,
+                                FlatpakRunFlags flags)
+{
+  g_autoptr(GDBusConnection) session_bus = NULL;
+  g_autofree char *a11y_address = NULL;
+  g_autoptr(GError) local_error = NULL;
+  g_autoptr(GDBusMessage) reply = NULL;
+  g_autoptr(GDBusMessage) msg = NULL;
+  g_autofree char *proxy_socket = NULL;
+
+  if ((flags & FLATPAK_RUN_FLAG_NO_A11Y_BUS_PROXY) != 0)
+    return FALSE;
+
+  session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+  if (session_bus == NULL)
+    return FALSE;
+
+  msg = g_dbus_message_new_method_call ("org.a11y.Bus", "/org/a11y/bus", "org.a11y.Bus", "GetAddress");
+  g_dbus_message_set_body (msg, g_variant_new ("()"));
+  reply =
+    g_dbus_connection_send_message_with_reply_sync (session_bus, msg,
+                                                    G_DBUS_SEND_MESSAGE_FLAGS_NONE,
+                                                    30000,
+                                                    NULL,
+                                                    NULL,
+                                                    NULL);
+  if (reply)
+    {
+      if (g_dbus_message_to_gerror (reply, &local_error))
+        {
+          if (!g_error_matches (local_error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN))
+            g_message ("Can't find a11y bus: %s", local_error->message);
+        }
+      else
+        {
+          g_variant_get (g_dbus_message_get_body (reply),
+                         "(s)", &a11y_address);
+        }
+    }
+
+  if (!a11y_address)
+    return FALSE;
+
+  proxy_socket = create_proxy_socket ("a11y-bus-proxy-XXXXXX");
+  if (proxy_socket == NULL)
+    return FALSE;
+
+  g_autofree char *sandbox_socket_path = g_strdup_printf ("/run/user/%d/at-spi-bus", getuid ());
+  g_autofree char *sandbox_dbus_address = g_strdup_printf ("unix:path=/run/user/%d/at-spi-bus", getuid ());
+
+  flatpak_bwrap_add_args (proxy_arg_bwrap,
+                          a11y_address,
+                          proxy_socket, "--filter", "--sloppy-names",
+                          "--call=org.a11y.atspi.Registry=org.a11y.atspi.Socket.Embed@/org/a11y/atspi/accessible/root",
+                          "--call=org.a11y.atspi.Registry=org.a11y.atspi.Socket.Unembed@/org/a11y/atspi/accessible/root",
+                          "--call=org.a11y.atspi.Registry=org.a11y.atspi.Registry.GetRegisteredEvents@/org/a11y/atspi/registry",
+                          "--call=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.GetKeystrokeListeners@/org/a11y/atspi/registry/deviceeventcontroller",
+                          "--call=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.GetDeviceEventListeners@/org/a11y/atspi/registry/deviceeventcontroller",
+                          "--call=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.NotifyListenersSync@/org/a11y/atspi/registry/deviceeventcontroller",
+                          "--call=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.NotifyListenersAsync@/org/a11y/atspi/registry/deviceeventcontroller",
+                          NULL);
+
+  if ((flags & FLATPAK_RUN_FLAG_LOG_A11Y_BUS) != 0)
+    flatpak_bwrap_add_args (proxy_arg_bwrap, "--log", NULL);
+
+  flatpak_bwrap_add_args (app_bwrap,
+                          "--ro-bind", proxy_socket, sandbox_socket_path,
+                          NULL);
+  flatpak_bwrap_set_env (app_bwrap, "AT_SPI_BUS_ADDRESS", sandbox_dbus_address, TRUE);
+
+  return TRUE;
+}
+
+/* This wraps the argv in a bwrap call, primary to allow the
+   command to be run with a proper /.flatpak-info with data
+   taken from app_info_path */
+static gboolean
+add_bwrap_wrapper (FlatpakBwrap *bwrap,
+                   const char   *app_info_path,
+                   GError      **error)
+{
+  glnx_autofd int app_info_fd = -1;
+  g_auto(GLnxDirFdIterator) dir_iter = { 0 };
+  struct dirent *dent;
+  g_autofree char *user_runtime_dir = flatpak_get_real_xdg_runtime_dir ();
+  g_autofree char *proxy_socket_dir = g_build_filename (user_runtime_dir, ".dbus-proxy/", NULL);
+
+  app_info_fd = open (app_info_path, O_RDONLY | O_CLOEXEC);
+  if (app_info_fd == -1)
+    return glnx_throw_errno_prefix (error, _("Failed to open app info file"));
+
+  if (!glnx_dirfd_iterator_init_at (AT_FDCWD, "/", FALSE, &dir_iter, error))
+    return FALSE;
+
+  flatpak_bwrap_add_arg (bwrap, flatpak_get_bwrap ());
+
+  while (TRUE)
+    {
+      glnx_autofd int o_path_fd = -1;
+      struct statfs stfs;
+
+      if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dir_iter, &dent, NULL, error))
+        return FALSE;
+
+      if (dent == NULL)
+        break;
+
+      if (strcmp (dent->d_name, ".flatpak-info") == 0)
+        continue;
+
+      /* O_PATH + fstatfs is the magic that we need to statfs without automounting the target */
+      o_path_fd = openat (dir_iter.fd, dent->d_name, O_PATH | O_NOFOLLOW | O_CLOEXEC);
+      if (o_path_fd == -1 || fstatfs (o_path_fd, &stfs) != 0 || stfs.f_type == AUTOFS_SUPER_MAGIC)
+        continue; /* AUTOFS mounts are risky and can cause us to block (see issue #1633), so ignore it. Its unlikely the proxy needs such a directory. */
+
+      if (dent->d_type == DT_DIR)
+        {
+          if (strcmp (dent->d_name, "tmp") == 0 ||
+              strcmp (dent->d_name, "var") == 0 ||
+              strcmp (dent->d_name, "run") == 0)
+            flatpak_bwrap_add_arg (bwrap, "--bind");
+          else
+            flatpak_bwrap_add_arg (bwrap, "--ro-bind");
+
+          flatpak_bwrap_add_arg_printf (bwrap, "/%s", dent->d_name);
+          flatpak_bwrap_add_arg_printf (bwrap, "/%s", dent->d_name);
+        }
+      else if (dent->d_type == DT_LNK)
+        {
+          g_autofree gchar *target = NULL;
+
+          target = glnx_readlinkat_malloc (dir_iter.fd, dent->d_name,
+                                           NULL, error);
+          if (target == NULL)
+            return FALSE;
+          flatpak_bwrap_add_args (bwrap, "--symlink", target, NULL);
+          flatpak_bwrap_add_arg_printf (bwrap, "/%s", dent->d_name);
+        }
+    }
+
+  flatpak_bwrap_add_args (bwrap, "--bind", proxy_socket_dir, proxy_socket_dir, NULL);
+
+  /* This is a file rather than a bind mount, because it will then
+     not be unmounted from the namespace when the namespace dies. */
+  flatpak_bwrap_add_args_data_fd (bwrap, "--file", glnx_steal_fd (&app_info_fd), "/.flatpak-info");
+
+  if (!flatpak_bwrap_bundle_args (bwrap, 1, -1, FALSE, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+static gboolean
+start_dbus_proxy (FlatpakBwrap *app_bwrap,
+                  FlatpakBwrap *proxy_arg_bwrap,
+                  const char   *app_info_path,
+                  GError      **error)
+{
+  char x = 'x';
+  const char *proxy;
+  g_autofree char *commandline = NULL;
+  g_autoptr(FlatpakBwrap) proxy_bwrap = NULL;
+  int sync_fds[2] = {-1, -1};
+  int proxy_start_index;
+  g_auto(GStrv) minimal_envp = NULL;
+
+  minimal_envp = flatpak_run_get_minimal_env (FALSE, FALSE);
+  proxy_bwrap = flatpak_bwrap_new (NULL);
+
+  if (!add_bwrap_wrapper (proxy_bwrap, app_info_path, error))
+    return FALSE;
+
+  proxy = g_getenv ("FLATPAK_DBUSPROXY");
+  if (proxy == NULL)
+    proxy = DBUSPROXY;
+
+  flatpak_bwrap_add_arg (proxy_bwrap, proxy);
+
+  proxy_start_index = proxy_bwrap->argv->len;
+
+  if (pipe2 (sync_fds, O_CLOEXEC) < 0)
+    {
+      g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errno),
+                           _("Unable to create sync pipe"));
+      return FALSE;
+    }
+
+  /* read end goes to app */
+  flatpak_bwrap_add_args_data_fd (app_bwrap, "--sync-fd", sync_fds[0], NULL);
+
+  /* write end goes to proxy */
+  flatpak_bwrap_add_fd (proxy_bwrap, sync_fds[1]);
+  flatpak_bwrap_add_arg_printf (proxy_bwrap, "--fd=%d", sync_fds[1]);
+
+  /* Note: This steals the fds from proxy_arg_bwrap */
+  flatpak_bwrap_append_bwrap (proxy_bwrap, proxy_arg_bwrap);
+
+  if (!flatpak_bwrap_bundle_args (proxy_bwrap, proxy_start_index, -1, TRUE, error))
+    return FALSE;
+
+  flatpak_bwrap_finish (proxy_bwrap);
+
+  commandline = flatpak_quote_argv ((const char **) proxy_bwrap->argv->pdata, -1);
+  g_debug ("Running '%s'", commandline);
+
+  /* We use LEAVE_DESCRIPTORS_OPEN to work around dead-lock, see flatpak_close_fds_workaround */
+  if (!g_spawn_async (NULL,
+                      (char **) proxy_bwrap->argv->pdata,
+                      NULL,
+                      G_SPAWN_SEARCH_PATH | G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
+                      flatpak_bwrap_child_setup_cb, proxy_bwrap->fds,
+                      NULL, error))
+    return FALSE;
+
+  /* The write end can be closed now, otherwise the read below will hang of xdg-dbus-proxy
+     fails to start. */
+  g_clear_pointer (&proxy_bwrap, flatpak_bwrap_free);
+
+  /* Sync with proxy, i.e. wait until its listening on the sockets */
+  if (read (sync_fds[0], &x, 1) != 1)
+    {
+      g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errno),
+                           _("Failed to sync with dbus proxy"));
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static int
+flatpak_extension_compare_by_path (gconstpointer _a,
+                                   gconstpointer _b)
+{
+  const FlatpakExtension *a = _a;
+  const FlatpakExtension *b = _b;
+
+  return g_strcmp0 (a->directory, b->directory);
+}
+
+gboolean
+flatpak_run_add_extension_args (FlatpakBwrap *bwrap,
+                                GKeyFile     *metakey,
+                                const char   *full_ref,
+                                gboolean      use_ld_so_cache,
+                                char        **extensions_out,
+                                GCancellable *cancellable,
+                                GError      **error)
+{
+  g_auto(GStrv) parts = NULL;
+  g_autoptr(GString) used_extensions = g_string_new ("");
+  gboolean is_app;
+  GList *extensions, *path_sorted_extensions, *l;
+  g_autoptr(GString) ld_library_path = g_string_new ("");
+  int count = 0;
+  g_autoptr(GHashTable) mounted_tmpfs =
+    g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+  g_autoptr(GHashTable) created_symlink =
+    g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+  parts = g_strsplit (full_ref, "/", 0);
+  if (g_strv_length (parts) != 4)
+    return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Failed to determine parts from ref: %s"), full_ref);
+
+  is_app = strcmp (parts[0], "app") == 0;
+
+  extensions = flatpak_list_extensions (metakey,
+                                        parts[2], parts[3]);
+
+  /* First we apply all the bindings, they are sorted alphabetically in order for parent directory
+     to be mounted before child directories */
+  path_sorted_extensions = g_list_copy (extensions);
+  path_sorted_extensions = g_list_sort (path_sorted_extensions, flatpak_extension_compare_by_path);
+
+  for (l = path_sorted_extensions; l != NULL; l = l->next)
+    {
+      FlatpakExtension *ext = l->data;
+      g_autofree char *directory = g_build_filename (is_app ? "/app" : "/usr", ext->directory, NULL);
+      g_autofree char *full_directory = g_build_filename (directory, ext->subdir_suffix, NULL);
+      g_autofree char *ref = g_build_filename (full_directory, ".ref", NULL);
+      g_autofree char *real_ref = g_build_filename (ext->files_path, ext->directory, ".ref", NULL);
+
+      if (ext->needs_tmpfs)
+        {
+          g_autofree char *parent = g_path_get_dirname (directory);
+
+          if (g_hash_table_lookup (mounted_tmpfs, parent) == NULL)
+            {
+              flatpak_bwrap_add_args (bwrap,
+                                      "--tmpfs", parent,
+                                      NULL);
+              g_hash_table_insert (mounted_tmpfs, g_steal_pointer (&parent), "mounted");
+            }
+        }
+
+      flatpak_bwrap_add_args (bwrap,
+                              "--ro-bind", ext->files_path, full_directory,
+                              NULL);
+
+      if (g_file_test (real_ref, G_FILE_TEST_EXISTS))
+        flatpak_bwrap_add_args (bwrap,
+                                "--lock-file", ref,
+                                NULL);
+    }
+
+  g_list_free (path_sorted_extensions);
+
+  /* Then apply library directories and file merging, in extension prio order */
+
+  for (l = extensions; l != NULL; l = l->next)
+    {
+      FlatpakExtension *ext = l->data;
+      g_autofree char *directory = g_build_filename (is_app ? "/app" : "/usr", ext->directory, NULL);
+      g_autofree char *full_directory = g_build_filename (directory, ext->subdir_suffix, NULL);
+      int i;
+
+      if (used_extensions->len > 0)
+        g_string_append (used_extensions, ";");
+      g_string_append (used_extensions, ext->installed_id);
+      g_string_append (used_extensions, "=");
+      if (ext->commit != NULL)
+        g_string_append (used_extensions, ext->commit);
+      else
+        g_string_append (used_extensions, "local");
+
+      if (ext->add_ld_path)
+        {
+          g_autofree char *ld_path = g_build_filename (full_directory, ext->add_ld_path, NULL);
+
+          if (use_ld_so_cache)
+            {
+              g_autofree char *contents = g_strconcat (ld_path, "\n", NULL);
+              /* We prepend app or runtime and a counter in order to get the include order correct for the conf files */
+              g_autofree char *ld_so_conf_file = g_strdup_printf ("%s-%03d-%s.conf", parts[0], ++count, ext->installed_id);
+              g_autofree char *ld_so_conf_file_path = g_build_filename ("/run/flatpak/ld.so.conf.d", ld_so_conf_file, NULL);
+
+              if (!flatpak_bwrap_add_args_data (bwrap, "ld-so-conf",
+                                                contents, -1, ld_so_conf_file_path, error))
+                return FALSE;
+            }
+          else
+            {
+              if (ld_library_path->len != 0)
+                g_string_append (ld_library_path, ":");
+              g_string_append (ld_library_path, ld_path);
+            }
+        }
+
+      for (i = 0; ext->merge_dirs != NULL && ext->merge_dirs[i] != NULL; i++)
+        {
+          g_autofree char *parent = g_path_get_dirname (directory);
+          g_autofree char *merge_dir = g_build_filename (parent, ext->merge_dirs[i], NULL);
+          g_autofree char *source_dir = g_build_filename (ext->files_path, ext->merge_dirs[i], NULL);
+          g_auto(GLnxDirFdIterator) source_iter = { 0 };
+          struct dirent *dent;
+
+          if (glnx_dirfd_iterator_init_at (AT_FDCWD, source_dir, TRUE, &source_iter, NULL))
+            {
+              while (glnx_dirfd_iterator_next_dent (&source_iter, &dent, NULL, NULL) && dent != NULL)
+                {
+                  g_autofree char *symlink_path = g_build_filename (merge_dir, dent->d_name, NULL);
+                  /* Only create the first, because extensions are listed in prio order */
+                  if (g_hash_table_lookup (created_symlink, symlink_path) == NULL)
+                    {
+                      g_autofree char *symlink = g_build_filename (directory, ext->merge_dirs[i], dent->d_name, NULL);
+                      flatpak_bwrap_add_args (bwrap,
+                                              "--symlink", symlink, symlink_path,
+                                              NULL);
+                      g_hash_table_insert (created_symlink, g_steal_pointer (&symlink_path), "created");
+                    }
+                }
+            }
+        }
+    }
+
+  g_list_free_full (extensions, (GDestroyNotify) flatpak_extension_free);
+
+  if (ld_library_path->len != 0)
+    {
+      const gchar *old_ld_path = g_environ_getenv (bwrap->envp, "LD_LIBRARY_PATH");
+
+      if (old_ld_path != NULL && *old_ld_path != 0)
+        {
+          if (is_app)
+            {
+              g_string_append (ld_library_path, ":");
+              g_string_append (ld_library_path, old_ld_path);
+            }
+          else
+            {
+              g_string_prepend (ld_library_path, ":");
+              g_string_prepend (ld_library_path, old_ld_path);
+            }
+        }
+
+      flatpak_bwrap_set_env (bwrap, "LD_LIBRARY_PATH", ld_library_path->str, TRUE);
+    }
+
+  if (extensions_out)
+    *extensions_out = g_string_free (g_steal_pointer (&used_extensions), FALSE);
+
+  return TRUE;
+}
+
+gboolean
+flatpak_run_add_environment_args (FlatpakBwrap    *bwrap,
+                                  const char      *app_info_path,
+                                  FlatpakRunFlags  flags,
+                                  const char      *app_id,
+                                  FlatpakContext  *context,
+                                  GFile           *app_id_dir,
+                                  GPtrArray       *previous_app_id_dirs,
+                                  FlatpakExports **exports_out,
+                                  GCancellable    *cancellable,
+                                  GError         **error)
+{
+  g_autoptr(GError) my_error = NULL;
+  g_autoptr(FlatpakExports) exports = NULL;
+  g_autoptr(FlatpakBwrap) proxy_arg_bwrap = flatpak_bwrap_new (flatpak_bwrap_empty_env);
+  gboolean has_wayland = FALSE;
+  gboolean allow_x11 = FALSE;
+
+  if ((context->shares & FLATPAK_CONTEXT_SHARED_IPC) == 0)
+    {
+      g_debug ("Disallowing ipc access");
+      flatpak_bwrap_add_args (bwrap, "--unshare-ipc", NULL);
+    }
+
+  if ((context->shares & FLATPAK_CONTEXT_SHARED_NETWORK) == 0)
+    {
+      g_debug ("Disallowing network access");
+      flatpak_bwrap_add_args (bwrap, "--unshare-net", NULL);
+    }
+
+  if (context->devices & FLATPAK_CONTEXT_DEVICE_ALL)
+    {
+      flatpak_bwrap_add_args (bwrap,
+                              "--dev-bind", "/dev", "/dev",
+                              NULL);
+      /* Don't expose the host /dev/shm, just the device nodes, unless explicitly allowed */
+      if (g_file_test ("/dev/shm", G_FILE_TEST_IS_DIR))
+        {
+          if ((context->devices & FLATPAK_CONTEXT_DEVICE_SHM) == 0)
+            flatpak_bwrap_add_args (bwrap,
+                                    "--tmpfs", "/dev/shm",
+                                    NULL);
+        }
+      else if (g_file_test ("/dev/shm", G_FILE_TEST_IS_SYMLINK))
+        {
+          g_autofree char *link = flatpak_readlink ("/dev/shm", NULL);
+
+          /* On debian (with sysv init) the host /dev/shm is a symlink to /run/shm, so we can't
+             mount on top of it. */
+          if (g_strcmp0 (link, "/run/shm") == 0)
+            {
+              if (context->devices & FLATPAK_CONTEXT_DEVICE_SHM &&
+                  g_file_test ("/run/shm", G_FILE_TEST_IS_DIR))
+                flatpak_bwrap_add_args (bwrap,
+                                        "--bind", "/run/shm", "/run/shm",
+                                        NULL);
+              else
+                flatpak_bwrap_add_args (bwrap,
+                                        "--dir", "/run/shm",
+                                        NULL);
+            }
+          else
+            g_warning ("Unexpected /dev/shm symlink %s", link);
+        }
+    }
+  else
+    {
+      flatpak_bwrap_add_args (bwrap,
+                              "--dev", "/dev",
+                              NULL);
+      if (context->devices & FLATPAK_CONTEXT_DEVICE_DRI)
+        {
+          g_debug ("Allowing dri access");
+          int i;
+          char *dri_devices[] = {
+            "/dev/dri",
+            /* mali */
+            "/dev/mali",
+            "/dev/mali0",
+            "/dev/umplock",
+            /* nvidia */
+            "/dev/nvidiactl",
+            "/dev/nvidia-modeset",
+            /* nvidia OpenCL/CUDA */
+            "/dev/nvidia-uvm",
+            "/dev/nvidia-uvm-tools",
+          };
+
+          for (i = 0; i < G_N_ELEMENTS (dri_devices); i++)
+            {
+              if (g_file_test (dri_devices[i], G_FILE_TEST_EXISTS))
+                flatpak_bwrap_add_args (bwrap, "--dev-bind", dri_devices[i], dri_devices[i], NULL);
+            }
+
+          /* Each Nvidia card gets its own device.
+             This is a fairly arbitrary limit but ASUS sells mining boards supporting 20 in theory. */
+          char nvidia_dev[14]; /* /dev/nvidia plus up to 2 digits */
+          for (i = 0; i < 20; i++)
+            {
+              g_snprintf (nvidia_dev, sizeof (nvidia_dev), "/dev/nvidia%d", i);
+              if (g_file_test (nvidia_dev, G_FILE_TEST_EXISTS))
+                flatpak_bwrap_add_args (bwrap, "--dev-bind", nvidia_dev, nvidia_dev, NULL);
+            }
+        }
+
+      if (context->devices & FLATPAK_CONTEXT_DEVICE_KVM)
+        {
+          g_debug ("Allowing kvm access");
+          if (g_file_test ("/dev/kvm", G_FILE_TEST_EXISTS))
+            flatpak_bwrap_add_args (bwrap, "--dev-bind", "/dev/kvm", "/dev/kvm", NULL);
+        }
+
+      if (context->devices & FLATPAK_CONTEXT_DEVICE_SHM)
+        {
+          /* This is a symlink to /run/shm on debian, so bind to real target */
+          g_autofree char *real_dev_shm = realpath ("/dev/shm", NULL);
+
+          g_debug ("Allowing /dev/shm access (as %s)", real_dev_shm);
+          if (real_dev_shm != NULL)
+              flatpak_bwrap_add_args (bwrap, "--bind", real_dev_shm, "/dev/shm", NULL);
+        }
+    }
+
+  flatpak_context_append_bwrap_filesystem (context, bwrap, app_id, app_id_dir, previous_app_id_dirs, &exports);
+
+  if (context->sockets & FLATPAK_CONTEXT_SOCKET_WAYLAND)
+    {
+      g_debug ("Allowing wayland access");
+      has_wayland = flatpak_run_add_wayland_args (bwrap);
+    }
+
+  if ((context->sockets & FLATPAK_CONTEXT_SOCKET_FALLBACK_X11) != 0)
+    allow_x11 = !has_wayland;
+  else
+    allow_x11 = (context->sockets & FLATPAK_CONTEXT_SOCKET_X11) != 0;
+
+  flatpak_run_add_x11_args (bwrap, allow_x11);
+
+  if (context->sockets & FLATPAK_CONTEXT_SOCKET_SSH_AUTH)
+    {
+      flatpak_run_add_ssh_args (bwrap);
+    }
+
+  if (context->sockets & FLATPAK_CONTEXT_SOCKET_PULSEAUDIO)
+    {
+      g_debug ("Allowing pulseaudio access");
+      flatpak_run_add_pulseaudio_args (bwrap);
+    }
+
+  if (context->sockets & FLATPAK_CONTEXT_SOCKET_PCSC)
+    {
+      flatpak_run_add_pcsc_args (bwrap);
+    }
+
+  if (context->sockets & FLATPAK_CONTEXT_SOCKET_CUPS)
+    {
+      flatpak_run_add_cups_args (bwrap);
+    }
+
+  flatpak_run_add_session_dbus_args (bwrap, proxy_arg_bwrap, context, flags, app_id);
+  flatpak_run_add_system_dbus_args (bwrap, proxy_arg_bwrap, context, flags);
+  flatpak_run_add_a11y_dbus_args (bwrap, proxy_arg_bwrap, context, flags);
+
+  if (g_environ_getenv (bwrap->envp, "LD_LIBRARY_PATH") != NULL)
+    {
+      /* LD_LIBRARY_PATH is overridden for setuid helper, so pass it as cmdline arg */
+      flatpak_bwrap_add_args (bwrap,
+                              "--setenv", "LD_LIBRARY_PATH", g_environ_getenv (bwrap->envp, "LD_LIBRARY_PATH"),
+                              NULL);
+      flatpak_bwrap_unset_env (bwrap, "LD_LIBRARY_PATH");
+    }
+
+  if (g_environ_getenv (bwrap->envp, "TMPDIR") != NULL)
+    {
+      /* TMPDIR is overridden for setuid helper, so pass it as cmdline arg */
+      flatpak_bwrap_add_args (bwrap,
+                              "--setenv", "TMPDIR", g_environ_getenv (bwrap->envp, "TMPDIR"),
+                              NULL);
+      flatpak_bwrap_unset_env (bwrap, "TMPDIR");
+    }
+
+  /* Must run this before spawning the dbus proxy, to ensure it
+     ends up in the app cgroup */
+  if (!flatpak_run_in_transient_unit (app_id, &my_error))
+    {
+      /* We still run along even if we don't get a cgroup, as nothing
+         really depends on it. Its just nice to have */
+      g_debug ("Failed to run in transient scope: %s", my_error->message);
+      g_clear_error (&my_error);
+    }
+
+  if (!flatpak_bwrap_is_empty (proxy_arg_bwrap) &&
+      !start_dbus_proxy (bwrap, proxy_arg_bwrap, app_info_path, error))
+    return FALSE;
+
+  if (exports_out)
+    *exports_out = g_steal_pointer (&exports);
+
+  return TRUE;
+}
+
+typedef struct
+{
+  const char *env;
+  const char *val;
+} ExportData;
+
+static const ExportData default_exports[] = {
+  {"PATH", "/app/bin:/usr/bin"},
+  /* We always want to unset LD_LIBRARY_PATH to avoid inheriting weird
+   * dependencies from the host. But if not using ld.so.cache this is
+   * later set. */
+  {"LD_LIBRARY_PATH", NULL},
+  {"XDG_CONFIG_DIRS", "/app/etc/xdg:/etc/xdg"},
+  {"XDG_DATA_DIRS", "/app/share:/usr/share"},
+  {"SHELL", "/bin/sh"},
+  {"TMPDIR", NULL}, /* Unset TMPDIR as it may not exist in the sandbox */
+
+  /* Some env vars are common enough and will affect the sandbox badly
+     if set on the host. We clear these always. */
+  {"PYTHONPATH", NULL},
+  {"PERLLIB", NULL},
+  {"PERL5LIB", NULL},
+  {"XCURSOR_PATH", NULL},
+};
+
+static const ExportData no_ld_so_cache_exports[] = {
+  {"LD_LIBRARY_PATH", "/app/lib"},
+};
+
+static const ExportData devel_exports[] = {
+  {"ACLOCAL_PATH", "/app/share/aclocal"},
+  {"C_INCLUDE_PATH", "/app/include"},
+  {"CPLUS_INCLUDE_PATH", "/app/include"},
+  {"LDFLAGS", "-L/app/lib "},
+  {"PKG_CONFIG_PATH", "/app/lib/pkgconfig:/app/share/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig"},
+  {"LC_ALL", "en_US.utf8"},
+};
+
+static void
+add_exports (GPtrArray        *env_array,
+             const ExportData *exports,
+             gsize             n_exports)
+{
+  int i;
+
+  for (i = 0; i < n_exports; i++)
+    {
+      if (exports[i].val)
+        g_ptr_array_add (env_array, g_strdup_printf ("%s=%s", exports[i].env, exports[i].val));
+    }
+}
+
+char **
+flatpak_run_get_minimal_env (gboolean devel, gboolean use_ld_so_cache)
+{
+  GPtrArray *env_array;
+  static const char * const copy[] = {
+    "PWD",
+    "GDMSESSION",
+    "XDG_CURRENT_DESKTOP",
+    "XDG_SESSION_DESKTOP",
+    "DESKTOP_SESSION",
+    "EMAIL_ADDRESS",
+    "HOME",
+    "HOSTNAME",
+    "LOGNAME",
+    "REAL_NAME",
+    "TERM",
+    "USER",
+    "USERNAME",
+  };
+  static const char * const copy_nodevel[] = {
+    "LANG",
+    "LANGUAGE",
+    "LC_ALL",
+    "LC_ADDRESS",
+    "LC_COLLATE",
+    "LC_CTYPE",
+    "LC_IDENTIFICATION",
+    "LC_MEASUREMENT",
+    "LC_MESSAGES",
+    "LC_MONETARY",
+    "LC_NAME",
+    "LC_NUMERIC",
+    "LC_PAPER",
+    "LC_TELEPHONE",
+    "LC_TIME",
+  };
+  int i;
+
+  env_array = g_ptr_array_new_with_free_func (g_free);
+
+  add_exports (env_array, default_exports, G_N_ELEMENTS (default_exports));
+
+  if (!use_ld_so_cache)
+    add_exports (env_array, no_ld_so_cache_exports, G_N_ELEMENTS (no_ld_so_cache_exports));
+
+  if (devel)
+    add_exports (env_array, devel_exports, G_N_ELEMENTS (devel_exports));
+
+  for (i = 0; i < G_N_ELEMENTS (copy); i++)
+    {
+      const char *current = g_getenv (copy[i]);
+      if (current)
+        g_ptr_array_add (env_array, g_strdup_printf ("%s=%s", copy[i], current));
+    }
+
+  if (!devel)
+    {
+      for (i = 0; i < G_N_ELEMENTS (copy_nodevel); i++)
+        {
+          const char *current = g_getenv (copy_nodevel[i]);
+          if (current)
+            g_ptr_array_add (env_array, g_strdup_printf ("%s=%s", copy_nodevel[i], current));
+        }
+    }
+
+  g_ptr_array_add (env_array, NULL);
+  return (char **) g_ptr_array_free (env_array, FALSE);
+}
+
+static char **
+apply_exports (char            **envp,
+               const ExportData *exports,
+               gsize             n_exports)
+{
+  int i;
+
+  for (i = 0; i < n_exports; i++)
+    {
+      const char *value = exports[i].val;
+
+      if (value)
+        envp = g_environ_setenv (envp, exports[i].env, value, TRUE);
+      else
+        envp = g_environ_unsetenv (envp, exports[i].env);
+    }
+
+  return envp;
+}
+
+void
+flatpak_run_apply_env_default (FlatpakBwrap *bwrap, gboolean use_ld_so_cache)
+{
+  bwrap->envp = apply_exports (bwrap->envp, default_exports, G_N_ELEMENTS (default_exports));
+
+  if (!use_ld_so_cache)
+    bwrap->envp = apply_exports (bwrap->envp, no_ld_so_cache_exports, G_N_ELEMENTS (no_ld_so_cache_exports));
+}
+
+static void
+flatpak_run_apply_env_prompt (FlatpakBwrap *bwrap, const char *app_id)
+{
+  /* A custom shell prompt. FLATPAK_ID is always set.
+   * PS1 can be overwritten by runtime metadata or by --env overrides
+   */
+  flatpak_bwrap_set_env (bwrap, "FLATPAK_ID", app_id, TRUE);
+  flatpak_bwrap_set_env (bwrap, "PS1", "[📦 $FLATPAK_ID \\W]\\$ ", FALSE);
+}
+
+#endif
+
+void
+flatpak_run_apply_env_appid (FlatpakBwrap *bwrap,
+                             GFile        *app_dir)
+{
+  g_autoptr(GFile) app_dir_data = NULL;
+  g_autoptr(GFile) app_dir_config = NULL;
+  g_autoptr(GFile) app_dir_cache = NULL;
+
+  app_dir_data = g_file_get_child (app_dir, "data");
+  app_dir_config = g_file_get_child (app_dir, "config");
+  app_dir_cache = g_file_get_child (app_dir, "cache");
+  flatpak_bwrap_set_env (bwrap, "XDG_DATA_HOME", flatpak_file_get_path_cached (app_dir_data), TRUE);
+  flatpak_bwrap_set_env (bwrap, "XDG_CONFIG_HOME", flatpak_file_get_path_cached (app_dir_config), TRUE);
+  flatpak_bwrap_set_env (bwrap, "XDG_CACHE_HOME", flatpak_file_get_path_cached (app_dir_cache), TRUE);
+
+  if (g_getenv ("XDG_DATA_HOME"))
+    flatpak_bwrap_set_env (bwrap, "HOST_XDG_DATA_HOME", g_getenv ("XDG_DATA_HOME"), TRUE);
+  if (g_getenv ("XDG_CONFIG_HOME"))
+    flatpak_bwrap_set_env (bwrap, "HOST_XDG_CONFIG_HOME", g_getenv ("XDG_CONFIG_HOME"), TRUE);
+  if (g_getenv ("XDG_CACHE_HOME"))
+    flatpak_bwrap_set_env (bwrap, "HOST_XDG_CACHE_HOME", g_getenv ("XDG_CACHE_HOME"), TRUE);
+}
+
+#if 0
+
+void
+flatpak_run_apply_env_vars (FlatpakBwrap *bwrap, FlatpakContext *context)
+{
+  GHashTableIter iter;
+  gpointer key, value;
+
+  g_hash_table_iter_init (&iter, context->env_vars);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      const char *var = key;
+      const char *val = value;
+
+      if (val && val[0] != 0)
+        flatpak_bwrap_set_env (bwrap, var, val, TRUE);
+      else
+        flatpak_bwrap_unset_env (bwrap, var);
+    }
+}
+
+#endif
+
+GFile *
+flatpak_get_data_dir (const char *app_id)
+{
+  g_autoptr(GFile) home = g_file_new_for_path (g_get_home_dir ());
+  g_autoptr(GFile) var_app = g_file_resolve_relative_path (home, ".var/app");
+
+  return g_file_get_child (var_app, app_id);
+}
+
+#if 0
+
+gboolean
+flatpak_ensure_data_dir (GFile        *app_id_dir,
+                         GCancellable *cancellable,
+                         GError      **error)
+{
+  g_autoptr(GFile) data_dir = g_file_get_child (app_id_dir, "data");
+  g_autoptr(GFile) cache_dir = g_file_get_child (app_id_dir, "cache");
+  g_autoptr(GFile) fontconfig_cache_dir = g_file_get_child (cache_dir, "fontconfig");
+  g_autoptr(GFile) tmp_dir = g_file_get_child (cache_dir, "tmp");
+  g_autoptr(GFile) config_dir = g_file_get_child (app_id_dir, "config");
+
+  if (!flatpak_mkdir_p (data_dir, cancellable, error))
+    return FALSE;
+
+  if (!flatpak_mkdir_p (cache_dir, cancellable, error))
+    return FALSE;
+
+  if (!flatpak_mkdir_p (fontconfig_cache_dir, cancellable, error))
+    return FALSE;
+
+  if (!flatpak_mkdir_p (tmp_dir, cancellable, error))
+    return FALSE;
+
+  if (!flatpak_mkdir_p (config_dir, cancellable, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+#endif
diff --git a/src/flatpak-utils-private.h b/src/flatpak-utils-private.h
index 612a71e357ed3a0c0b712e34894edeb9c7658324..9e418e5a0c5c5cd464ef8bb84c518a96d8804e95 100644
--- a/src/flatpak-utils-private.h
+++ b/src/flatpak-utils-private.h
@@ -24,6 +24,22 @@
 #define __FLATPAK_UTILS_H__
 
 #include "libglnx/libglnx.h"
+#include <flatpak-common-types-private.h>
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
+#include "flatpak-context-private.h"
+#include "flatpak-error.h"
+
+#define AUTOFS_SUPER_MAGIC 0x0187
+
+/* https://bugzilla.gnome.org/show_bug.cgi?id=766370 */
+#if !GLIB_CHECK_VERSION (2, 49, 3)
+#define FLATPAK_VARIANT_BUILDER_INITIALIZER {{0, }}
+#define FLATPAK_VARIANT_DICT_INITIALIZER {{0, }}
+#else
+#define FLATPAK_VARIANT_BUILDER_INITIALIZER {{{0, }}}
+#define FLATPAK_VARIANT_DICT_INITIALIZER {{{0, }}}
+#endif
 
 /* https://github.com/GNOME/libglnx/pull/38
  * Note by using #define rather than wrapping via a static inline, we
@@ -31,20 +47,49 @@
  */
 #define flatpak_fail glnx_throw
 
+gboolean flatpak_fail_error (GError     **error,
+                             FlatpakError code,
+                             const char  *fmt,
+                             ...) G_GNUC_PRINTF (3, 4);
+
 #define flatpak_debug2 g_debug
 
+gint flatpak_strcmp0_ptr (gconstpointer a,
+                          gconstpointer b);
+
 /* Sometimes this is /var/run which is a symlink, causing weird issues when we pass
  * it as a path into the sandbox */
 char * flatpak_get_real_xdg_runtime_dir (void);
 
+gboolean  flatpak_has_path_prefix (const char *str,
+                                   const char *prefix);
+
+
+gboolean flatpak_g_ptr_array_contains_string (GPtrArray  *array,
+                                              const char *str);
 
 char * flatpak_quote_argv (const char *argv[],
                            gssize      len);
 
+const char *flatpak_file_get_path_cached (GFile *file);
+
+
+gboolean flatpak_mkdir_p (GFile        *dir,
+                          GCancellable *cancellable,
+                          GError      **error);
 gboolean flatpak_buffer_to_sealed_memfd_or_tmpfile (GLnxTmpfile *tmpf,
                                                     const char  *name,
                                                     const char  *str,
                                                     size_t       len,
                                                     GError     **error);
 
+#if !GLIB_CHECK_VERSION (2, 43, 4)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (GUnixFDList, g_object_unref)
+#endif
+
+static inline void
+null_safe_g_ptr_array_unref (gpointer data)
+{
+  g_clear_pointer (&data, g_ptr_array_unref);
+}
 #endif /* __FLATPAK_UTILS_H__ */
diff --git a/src/flatpak-utils.c b/src/flatpak-utils.c
index d796878008461472273a2f325a7540905fd6fb3c..c3b913de261995fb7e83afea1a49b566c54fe68c 100644
--- a/src/flatpak-utils.c
+++ b/src/flatpak-utils.c
@@ -39,8 +39,10 @@
 #include <termios.h>
 
 #include <glib.h>
+#include "flatpak-error.h"
 #include "flatpak-utils-base-private.h"
 #include "flatpak-utils-private.h"
+#include "libglnx/libglnx.h"
 
 /* To keep this more similar to the original file, we explicitly disable
  * this warning rather than fixing it */
@@ -48,6 +50,95 @@
 
 #define RUNNING_ON_VALGRIND 0
 
+/* This is also here so the common code can report these errors to the lib */
+static const GDBusErrorEntry flatpak_error_entries[] = {
+  {FLATPAK_ERROR_ALREADY_INSTALLED,     "org.freedesktop.Flatpak.Error.AlreadyInstalled"},
+  {FLATPAK_ERROR_NOT_INSTALLED,         "org.freedesktop.Flatpak.Error.NotInstalled"},
+  {FLATPAK_ERROR_ONLY_PULLED,           "org.freedesktop.Flatpak.Error.OnlyPulled"}, /* Since: 1.0 */
+  {FLATPAK_ERROR_DIFFERENT_REMOTE,      "org.freedesktop.Flatpak.Error.DifferentRemote"}, /* Since: 1.0 */
+  {FLATPAK_ERROR_ABORTED,               "org.freedesktop.Flatpak.Error.Aborted"}, /* Since: 1.0 */
+  {FLATPAK_ERROR_SKIPPED,               "org.freedesktop.Flatpak.Error.Skipped"}, /* Since: 1.0 */
+  {FLATPAK_ERROR_NEED_NEW_FLATPAK,      "org.freedesktop.Flatpak.Error.NeedNewFlatpak"}, /* Since: 1.0 */
+  {FLATPAK_ERROR_REMOTE_NOT_FOUND,      "org.freedesktop.Flatpak.Error.RemoteNotFound"}, /* Since: 1.0 */
+  {FLATPAK_ERROR_RUNTIME_NOT_FOUND,     "org.freedesktop.Flatpak.Error.RuntimeNotFound"}, /* Since: 1.0 */
+  {FLATPAK_ERROR_DOWNGRADE,             "org.freedesktop.Flatpak.Error.Downgrade"}, /* Since: 1.0 */
+  {FLATPAK_ERROR_INVALID_REF,           "org.freedesktop.Flatpak.Error.InvalidRef"}, /* Since: 1.0.3 */
+  {FLATPAK_ERROR_INVALID_DATA,          "org.freedesktop.Flatpak.Error.InvalidData"}, /* Since: 1.0.3 */
+  {FLATPAK_ERROR_UNTRUSTED,             "org.freedesktop.Flatpak.Error.Untrusted"}, /* Since: 1.0.3 */
+  {FLATPAK_ERROR_SETUP_FAILED,          "org.freedesktop.Flatpak.Error.SetupFailed"}, /* Since: 1.0.3 */
+  {FLATPAK_ERROR_EXPORT_FAILED,         "org.freedesktop.Flatpak.Error.ExportFailed"}, /* Since: 1.0.3 */
+  {FLATPAK_ERROR_REMOTE_USED,           "org.freedesktop.Flatpak.Error.RemoteUsed"}, /* Since: 1.0.3 */
+  {FLATPAK_ERROR_RUNTIME_USED,          "org.freedesktop.Flatpak.Error.RuntimeUsed"}, /* Since: 1.0.3 */
+  {FLATPAK_ERROR_INVALID_NAME,          "org.freedesktop.Flatpak.Error.InvalidName"}, /* Since: 1.0.3 */
+  {FLATPAK_ERROR_OUT_OF_SPACE,          "org.freedesktop.Flatpak.Error.OutOfSpace"}, /* Since: 1.2.0 */
+  {FLATPAK_ERROR_WRONG_USER,            "org.freedesktop.Flatpak.Error.WrongUser"}, /* Since: 1.2.0 */
+  {FLATPAK_ERROR_NOT_CACHED,            "org.freedesktop.Flatpak.Error.NotCached"}, /* Since: 1.3.3 */
+  {FLATPAK_ERROR_REF_NOT_FOUND,         "org.freedesktop.Flatpak.Error.RefNotFound"}, /* Since: 1.4.0 */
+  {FLATPAK_ERROR_PERMISSION_DENIED,     "org.freedesktop.Flatpak.Error.PermissionDenied"}, /* Since: 1.5.1 */
+};
+
+GQuark
+flatpak_error_quark (void)
+{
+  static volatile gsize quark_volatile = 0;
+
+  g_dbus_error_register_error_domain ("flatpak-error-quark",
+                                      &quark_volatile,
+                                      flatpak_error_entries,
+                                      G_N_ELEMENTS (flatpak_error_entries));
+  return (GQuark) quark_volatile;
+}
+
+gboolean
+flatpak_fail_error (GError **error, FlatpakError code, const char *fmt, ...)
+{
+  if (error == NULL)
+    return FALSE;
+
+  va_list args;
+  va_start (args, fmt);
+  GError *new = g_error_new_valist (FLATPAK_ERROR, code, fmt, args);
+  va_end (args);
+  g_propagate_error (error, g_steal_pointer (&new));
+  return FALSE;
+}
+
+
+#if 0
+
+GBytes *
+flatpak_read_stream (GInputStream *in,
+                     gboolean      null_terminate,
+                     GError      **error)
+{
+  g_autoptr(GOutputStream) mem_stream = NULL;
+
+  mem_stream = g_memory_output_stream_new_resizable ();
+  if (g_output_stream_splice (mem_stream, in,
+                              0, NULL, error) < 0)
+    return NULL;
+
+  if (null_terminate)
+    {
+      if (!g_output_stream_write (G_OUTPUT_STREAM (mem_stream), "\0", 1, NULL, error))
+        return NULL;
+    }
+
+  if (!g_output_stream_close (G_OUTPUT_STREAM (mem_stream), NULL, error))
+    return NULL;
+
+  return g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (mem_stream));
+}
+
+#endif
+
+gint
+flatpak_strcmp0_ptr (gconstpointer a,
+                     gconstpointer b)
+{
+  return g_strcmp0 (*(char * const *) a, *(char * const *) b);
+}
+
 /* Sometimes this is /var/run which is a symlink, causing weird issues when we pass
  * it as a path into the sandbox */
 char *
@@ -56,95 +147,6963 @@ flatpak_get_real_xdg_runtime_dir (void)
   return realpath (g_get_user_runtime_dir (), NULL);
 }
 
-static gboolean
-needs_quoting (const char *arg)
+/* Compares if str has a specific path prefix. This differs
+   from a regular prefix in two ways. First of all there may
+   be multiple slashes separating the path elements, and
+   secondly, if a prefix is matched that has to be en entire
+   path element. For instance /a/prefix matches /a/prefix/foo/bar,
+   but not /a/prefixfoo/bar. */
+gboolean
+flatpak_has_path_prefix (const char *str,
+                         const char *prefix)
 {
-  while (*arg != 0)
+  while (TRUE)
     {
-      char c = *arg;
-      if (!g_ascii_isalnum (c) &&
-          !(c == '-' || c == '/' || c == '~' ||
-            c == ':' || c == '.' || c == '_' ||
-            c == '=' || c == '@'))
+      /* Skip consecutive slashes to reach next path
+         element */
+      while (*str == '/')
+        str++;
+      while (*prefix == '/')
+        prefix++;
+
+      /* No more prefix path elements? Done! */
+      if (*prefix == 0)
         return TRUE;
-      arg++;
+
+      /* Compare path element */
+      while (*prefix != 0 && *prefix != '/')
+        {
+          if (*str != *prefix)
+            return FALSE;
+          str++;
+          prefix++;
+        }
+
+      /* Matched prefix path element,
+         must be entire str path element */
+      if (*str != '/' && *str != 0)
+        return FALSE;
     }
-  return FALSE;
 }
 
-char *
-flatpak_quote_argv (const char *argv[],
-                    gssize      len)
+#if 0
+
+/* Returns end of matching path prefix, or NULL if no match */
+const char *
+flatpak_path_match_prefix (const char *pattern,
+                           const char *string)
 {
-  GString *res = g_string_new ("");
-  int i;
+  char c, test;
+  const char *tmp;
 
-  if (len == -1)
-    len = g_strv_length ((char **) argv);
+  while (*pattern == '/')
+    pattern++;
 
-  for (i = 0; i < len; i++)
-    {
-      if (i != 0)
-        g_string_append_c (res, ' ');
+  while (*string == '/')
+    string++;
 
-      if (needs_quoting (argv[i]))
+  while (TRUE)
+    {
+      switch (c = *pattern++)
         {
-          g_autofree char *quoted = g_shell_quote (argv[i]);
-          g_string_append (res, quoted);
+        case 0:
+          if (*string == '/' || *string == 0)
+            return string;
+          return NULL;
+
+        case '?':
+          if (*string == '/' || *string == 0)
+            return NULL;
+          string++;
+          break;
+
+        case '*':
+          c = *pattern;
+
+          while (c == '*')
+            c = *++pattern;
+
+          /* special case * at end */
+          if (c == 0)
+            {
+              char *tmp = strchr (string, '/');
+              if (tmp != NULL)
+                return tmp;
+              return string + strlen (string);
+            }
+          else if (c == '/')
+            {
+              string = strchr (string, '/');
+              if (string == NULL)
+                return NULL;
+              break;
+            }
+
+          while ((test = *string) != 0)
+            {
+              tmp = flatpak_path_match_prefix (pattern, string);
+              if (tmp != NULL)
+                return tmp;
+              if (test == '/')
+                break;
+              string++;
+            }
+          return NULL;
+
+        default:
+          if (c != *string)
+            return NULL;
+          string++;
+          break;
         }
-      else
-        g_string_append (res, argv[i]);
     }
+  return NULL; /* Should not be reached */
+}
 
-  return g_string_free (res, FALSE);
+const char *
+flatpak_get_bwrap (void)
+{
+  const char *e = g_getenv ("FLATPAK_BWRAP");
+
+  if (e != NULL)
+    return e;
+  return HELPER;
 }
 
-/* If memfd_create() is available, generate a sealed memfd with contents of
- * @str. Otherwise use an O_TMPFILE @tmpf in anonymous mode, write @str to
- * @tmpf, and lseek() back to the start. See also similar uses in e.g.
- * rpm-ostree for running dracut.
+static gboolean
+is_valid_initial_name_character (gint c, gboolean allow_dash)
+{
+  return
+    (c >= 'A' && c <= 'Z') ||
+    (c >= 'a' && c <= 'z') ||
+    (c == '_') || (allow_dash && c == '-');
+}
+
+static gboolean
+is_valid_name_character (gint c, gboolean allow_dash)
+{
+  return
+    is_valid_initial_name_character (c, allow_dash) ||
+    (c >= '0' && c <= '9');
+}
+
+/**
+ * flatpak_is_valid_name:
+ * @string: The string to check
+ * @error: Return location for an error
+ *
+ * Checks if @string is a valid application name.
+ *
+ * App names are composed of 3 or more elements separated by a period
+ * ('.') character. All elements must contain at least one character.
+ *
+ * Each element must only contain the ASCII characters
+ * "[A-Z][a-z][0-9]_-". Elements may not begin with a digit.
+ * Additionally "-" is only allowed in the last element.
+ *
+ * App names must not begin with a '.' (period) character.
+ *
+ * App names must not exceed 255 characters in length.
+ *
+ * The above means that any app name is also a valid DBus well known
+ * bus name, but not all DBus names are valid app names. The difference are:
+ * 1) DBus name elements may contain '-' in the non-last element.
+ * 2) DBus names require only two elements
+ *
+ * Returns: %TRUE if valid, %FALSE otherwise.
+ *
+ * Since: 2.26
  */
 gboolean
-flatpak_buffer_to_sealed_memfd_or_tmpfile (GLnxTmpfile *tmpf,
-                                           const char  *name,
-                                           const char  *str,
-                                           size_t       len,
-                                           GError     **error)
+flatpak_is_valid_name (const char *string,
+                       GError    **error)
 {
-  if (len == -1)
-    len = strlen (str);
-  glnx_autofd int memfd = memfd_create (name, MFD_CLOEXEC | MFD_ALLOW_SEALING);
-  int fd; /* Unowned */
-  if (memfd != -1)
+  guint len;
+  gboolean ret;
+  const gchar *s;
+  const gchar *end;
+  const gchar *last_dot;
+  int dot_count;
+  gboolean last_element;
+
+  g_return_val_if_fail (string != NULL, FALSE);
+
+  ret = FALSE;
+
+  len = strlen (string);
+  if (G_UNLIKELY (len == 0))
     {
-      fd = memfd;
+      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME,
+                          _("Name can't be empty"));
+      goto out;
     }
-  else
+
+  if (G_UNLIKELY (len > 255))
     {
-      /* We use an anonymous fd (i.e. O_EXCL) since we don't want
-       * the target container to potentially be able to re-link it.
-       */
-      if (!G_IN_SET (errno, ENOSYS, EOPNOTSUPP))
-        return glnx_throw_errno_prefix (error, "memfd_create");
-      if (!glnx_open_anonymous_tmpfile (O_RDWR | O_CLOEXEC, tmpf, error))
-        return FALSE;
-      fd = tmpf->fd;
+      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME,
+                          _("Name can't be longer than 255 characters"));
+      goto out;
     }
-  if (ftruncate (fd, len) < 0)
-    return glnx_throw_errno_prefix (error, "ftruncate");
-  if (glnx_loop_write (fd, str, len) < 0)
-    return glnx_throw_errno_prefix (error, "write");
-  if (lseek (fd, 0, SEEK_SET) < 0)
-    return glnx_throw_errno_prefix (error, "lseek");
-  if (memfd != -1)
+
+  end = string + len;
+
+  last_dot = strrchr (string, '.');
+  last_element = FALSE;
+
+  s = string;
+  if (G_UNLIKELY (*s == '.'))
     {
-      /* Valgrind doesn't currently handle G_ADD_SEALS, so lets not seal when debugging... */
-      if ((!RUNNING_ON_VALGRIND) &&
-          fcntl (memfd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL) < 0)
-        return glnx_throw_errno_prefix (error, "fcntl(F_ADD_SEALS)");
-      /* The other values can stay default */
-      tmpf->fd = glnx_steal_fd (&memfd);
-      tmpf->initialized = TRUE;
+      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME,
+                          _("Name can't start with a period"));
+      goto out;
+    }
+  else if (G_UNLIKELY (!is_valid_initial_name_character (*s, last_element)))
+    {
+      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME,
+                          _("Name can't start with %c"), *s);
+      goto out;
+    }
+
+  s += 1;
+  dot_count = 0;
+  while (s != end)
+    {
+      if (*s == '.')
+        {
+          if (s == last_dot)
+            last_element = TRUE;
+          s += 1;
+          if (G_UNLIKELY (s == end))
+            {
+              flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME,
+                                  _("Name can't end with a period"));
+              goto out;
+            }
+          if (!is_valid_initial_name_character (*s, last_element))
+            {
+              if (*s == '-')
+                flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME,
+                                    _("Only last name segment can contain -"));
+              else
+                flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME,
+                                    _("Name segment can't start with %c"), *s);
+              goto out;
+            }
+          dot_count++;
+        }
+      else if (G_UNLIKELY (!is_valid_name_character (*s, last_element)))
+        {
+          if (*s == '-')
+            flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME,
+                                _("Only last name segment can contain -"));
+          else
+            flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME,
+                                _("Name can't contain %c"), *s);
+          goto out;
+        }
+      s += 1;
+    }
+
+  if (G_UNLIKELY (dot_count < 2))
+    {
+      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME,
+                          _("Names must contain at least 2 periods"));
+      goto out;
+    }
+
+  ret = TRUE;
+
+out:
+  return ret;
+}
+
+gboolean
+flatpak_has_name_prefix (const char *string,
+                         const char *name)
+{
+  const char *rest;
+
+  if (!g_str_has_prefix (string, name))
+    return FALSE;
+
+  rest = string + strlen (name);
+  return
+    *rest == 0 ||
+    *rest == '.' ||
+    !is_valid_name_character (*rest, FALSE);
+}
+
+gboolean
+flatpak_name_matches_one_wildcard_prefix (const char         *name,
+                                          const char * const *wildcarded_prefixes,
+                                          gboolean            require_exact_match)
+{
+  const char * const *iter = wildcarded_prefixes;
+  const char *remainder;
+  gsize longest_match_len = 0;
+
+  /* Find longest valid match */
+  for (; *iter != NULL; ++iter)
+    {
+      const char *prefix = *iter;
+      gsize prefix_len = strlen (prefix);
+      gsize match_len = strlen (prefix);
+      gboolean has_wildcard = FALSE;
+      const char *end_of_match;
+
+      if (g_str_has_suffix (prefix, ".*"))
+        {
+          has_wildcard = TRUE;
+          prefix_len -= 2;
+        }
+
+      if (strncmp (name, prefix, prefix_len) != 0)
+        continue;
+
+      end_of_match = name + prefix_len;
+
+      if (has_wildcard &&
+          end_of_match[0] == '.' &&
+          is_valid_initial_name_character (end_of_match[1], TRUE))
+        {
+          end_of_match += 2;
+          while (*end_of_match != 0 &&
+                 (is_valid_name_character (*end_of_match, TRUE) ||
+                  (end_of_match[0] == '.' &&
+                   is_valid_initial_name_character (end_of_match[1], TRUE))))
+            end_of_match++;
+        }
+
+      match_len = end_of_match - name;
+
+      if (match_len > longest_match_len)
+        longest_match_len = match_len;
+    }
+
+  if (longest_match_len == 0)
+    return FALSE;
+
+  if (require_exact_match)
+    return name[longest_match_len] == 0;
+
+  /* non-exact matches can be exact, or can be followed by characters that would make
+   * not be part of the last element in the matched prefix, due to being invalid or
+   * a new element. As a special case we explicitly disallow dash here, even though
+   * it iss typically allowed in the final element of a name, this allows you too sloppily
+   * match org.the.App with org.the.App-symbolic[.png] or org.the.App-settings[.desktop].
+   */
+  remainder = name + longest_match_len;
+  return
+    *remainder == 0 ||
+    *remainder == '.' ||
+    !is_valid_name_character (*remainder, FALSE);
+}
+
+gboolean
+flatpak_get_allowed_exports (const char     *source_path,
+                             const char     *app_id,
+                             FlatpakContext *context,
+                             char         ***allowed_extensions_out,
+                             char         ***allowed_prefixes_out,
+                             gboolean       *require_exact_match_out)
+{
+  g_autoptr(GPtrArray) allowed_extensions = g_ptr_array_new_with_free_func (g_free);
+  g_autoptr(GPtrArray) allowed_prefixes = g_ptr_array_new_with_free_func (g_free);
+  gboolean require_exact_match = FALSE;
+
+  g_ptr_array_add (allowed_prefixes, g_strdup_printf ("%s.*", app_id));
+
+  if (strcmp (source_path, "share/applications") == 0)
+    {
+      g_ptr_array_add (allowed_extensions, g_strdup (".desktop"));
+    }
+  else if (flatpak_has_path_prefix (source_path, "share/icons"))
+    {
+      g_ptr_array_add (allowed_extensions, g_strdup (".svgz"));
+      g_ptr_array_add (allowed_extensions, g_strdup (".png"));
+      g_ptr_array_add (allowed_extensions, g_strdup (".svg"));
+      g_ptr_array_add (allowed_extensions, g_strdup (".ico"));
+    }
+  else if (strcmp (source_path, "share/dbus-1/services") == 0)
+    {
+      g_auto(GStrv) owned_dbus_names =  flatpak_context_get_session_bus_policy_allowed_own_names (context);
+
+      g_ptr_array_add (allowed_extensions, g_strdup (".service"));
+
+      for (GStrv iter = owned_dbus_names; *iter != NULL; ++iter)
+        g_ptr_array_add (allowed_prefixes, g_strdup (*iter));
+
+      /* We need an exact match with no extra garbage, because the filename refers to busnames
+       * and we can *only* match exactly these */
+      require_exact_match = TRUE;
+    }
+  else if (strcmp (source_path, "share/gnome-shell/search-providers") == 0)
+    {
+      g_ptr_array_add (allowed_extensions, g_strdup (".ini"));
+    }
+  else if (strcmp (source_path, "share/mime/packages") == 0)
+    {
+      g_ptr_array_add (allowed_extensions, g_strdup (".xml"));
     }
+  else
+    return FALSE;
+
+  g_ptr_array_add (allowed_extensions, NULL);
+  g_ptr_array_add (allowed_prefixes, NULL);
+
+  if (allowed_extensions_out)
+    *allowed_extensions_out = (char **) g_ptr_array_free (g_steal_pointer (&allowed_extensions), FALSE);
+
+  if (allowed_prefixes_out)
+    *allowed_prefixes_out = (char **) g_ptr_array_free (g_steal_pointer (&allowed_prefixes), FALSE);
+
+  if (require_exact_match_out)
+    *require_exact_match_out = require_exact_match;
+
   return TRUE;
 }
+
+static gboolean
+is_valid_initial_branch_character (gint c)
+{
+  return
+    (c >= '0' && c <= '9') ||
+    (c >= 'A' && c <= 'Z') ||
+    (c >= 'a' && c <= 'z') ||
+    (c == '_') ||
+    (c == '-');
+}
+
+static gboolean
+is_valid_branch_character (gint c)
+{
+  return
+    is_valid_initial_branch_character (c) ||
+    (c == '.');
+}
+
+/**
+ * flatpak_is_valid_branch:
+ * @string: The string to check
+ * @error: return location for an error
+ *
+ * Checks if @string is a valid branch name.
+ *
+ * Branch names must only contain the ASCII characters
+ * "[A-Z][a-z][0-9]_-.".
+ * Branch names may not begin with a period.
+ * Branch names must contain at least one character.
+ *
+ * Returns: %TRUE if valid, %FALSE otherwise.
+ *
+ * Since: 2.26
+ */
+gboolean
+flatpak_is_valid_branch (const char *string,
+                         GError    **error)
+{
+  guint len;
+  gboolean ret;
+  const gchar *s;
+  const gchar *end;
+
+  g_return_val_if_fail (string != NULL, FALSE);
+
+  ret = FALSE;
+
+  len = strlen (string);
+  if (G_UNLIKELY (len == 0))
+    {
+      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME,
+                          _("Branch can't be empty"));
+      goto out;
+    }
+
+  end = string + len;
+
+  s = string;
+  if (G_UNLIKELY (!is_valid_initial_branch_character (*s)))
+    {
+      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME,
+                          _("Branch can't start with %c"), *s);
+      goto out;
+    }
+
+  s += 1;
+  while (s != end)
+    {
+      if (G_UNLIKELY (!is_valid_branch_character (*s)))
+        {
+          flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME,
+                              _("Branch can't contain %c"), *s);
+          goto out;
+        }
+      s += 1;
+    }
+
+  ret = TRUE;
+
+out:
+  return ret;
+}
+
+/* Dashes are only valid in the last part of the app id, so
+   we replace them with underscore so we can suffix the id */
+char *
+flatpak_make_valid_id_prefix (const char *orig_id)
+{
+  char *id, *t;
+
+  id = g_strdup (orig_id);
+  t = id;
+  while (*t != 0 && *t != '/')
+    {
+      if (*t == '-')
+        *t = '_';
+
+      t++;
+    }
+
+  return id;
+}
+
+gboolean
+flatpak_id_has_subref_suffix (const char *id)
+{
+  return
+    g_str_has_suffix (id, ".Locale") ||
+    g_str_has_suffix (id, ".Debug") ||
+    g_str_has_suffix (id, ".Sources");
+}
+
+
+static const char *
+skip_segment (const char *s)
+{
+  const char *slash;
+
+  slash = strchr (s, '/');
+  if (slash)
+    return slash + 1;
+  return s + strlen (s);
+}
+
+static int
+compare_segment (const char *s1, const char *s2)
+{
+  gint c1, c2;
+
+  while (*s1 && *s1 != '/' &&
+         *s2 && *s2 != '/')
+    {
+      c1 = *s1;
+      c2 = *s2;
+      if (c1 != c2)
+        return c1 - c2;
+      s1++;
+      s2++;
+    }
+
+  c1 = *s1;
+  if (c1 == '/')
+    c1 = 0;
+  c2 = *s2;
+  if (c2 == '/')
+    c2 = 0;
+
+  return c1 - c2;
+}
+
+int
+flatpak_compare_ref (const char *ref1, const char *ref2)
+{
+  int res;
+  int i;
+
+  /* Skip first element and do per-segment compares for rest */
+  for (i = 0; i < 3; i++)
+    {
+      ref1 = skip_segment (ref1);
+      ref2 = skip_segment (ref2);
+
+      res = compare_segment (ref1, ref2);
+      if (res != 0)
+        return res;
+    }
+  return 0;
+}
+
+static char *
+line_get_word (char **line)
+{
+  char *word = NULL;
+
+  while (g_ascii_isspace (**line))
+    (*line)++;
+
+  if (**line == 0)
+    return NULL;
+
+  word = *line;
+
+  while (**line && !g_ascii_isspace (**line))
+    (*line)++;
+
+  if (**line)
+    {
+      **line = 0;
+      (*line)++;
+    }
+
+  return word;
+}
+
+char *
+flatpak_filter_glob_to_regexp (const char *glob, GError **error)
+{
+  g_autoptr(GString) regexp = g_string_new ("");
+  int parts = 1;
+  gboolean empty_part;
+
+  if (g_str_has_prefix (glob, "app/"))
+    {
+      glob += strlen ("app/");
+      g_string_append (regexp, "app/");
+    }
+  else if (g_str_has_prefix (glob, "runtime/"))
+    {
+      glob += strlen ("runtime/");
+      g_string_append (regexp, "runtime/");
+    }
+  else
+    g_string_append (regexp, "(app|runtime)/");
+
+  /* We really need an id part, the rest is optional */
+  if (*glob == 0)
+    {
+      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Empty glob"));
+      return NULL;
+    }
+
+  empty_part = TRUE;
+  while (*glob != 0)
+    {
+      char c = *glob;
+      glob++;
+
+      if (c == '/')
+        {
+          if (empty_part)
+            g_string_append (regexp, "[.\\-_a-zA-Z0-9]*");
+          empty_part = TRUE;
+          parts++;
+          g_string_append (regexp, "/");
+          if (parts > 3)
+            {
+              flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Too many segments in glob"));
+              return NULL;
+            }
+        }
+      else if (c == '*')
+        {
+          empty_part = FALSE;
+         g_string_append (regexp, "[.\\-_a-zA-Z0-9]*");
+        }
+      else if (c == '.')
+        {
+          empty_part = FALSE;
+          g_string_append (regexp, "\\.");
+        }
+      else if (g_ascii_isalnum (c) || c == '-' || c == '_')
+        {
+          empty_part = FALSE;
+          g_string_append_c (regexp, c);
+        }
+      else
+        {
+          flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid glob character '%c'"), c);
+          return NULL;
+        }
+    }
+
+  while (parts < 3)
+    {
+      parts++;
+      g_string_append (regexp, "/[.\\-_a-zA-Z0-9]*");
+    }
+
+  return g_string_free (g_steal_pointer (&regexp), FALSE);
+}
+
+gboolean
+flatpak_parse_filters (const char *data,
+                       GRegex **allow_refs_out,
+                       GRegex **deny_refs_out,
+                       GError **error)
+{
+  g_auto(GStrv) lines = NULL;
+  int i;
+  g_autoptr(GString) allow_regexp = g_string_new ("^(");
+  g_autoptr(GString) deny_regexp = g_string_new ("^(");
+  gboolean has_allow = FALSE;
+  gboolean has_deny = FALSE;
+  g_autoptr(GRegex) allow_refs = NULL;
+  g_autoptr(GRegex) deny_refs = NULL;
+
+  lines = g_strsplit (data, "\n", -1);
+  for (i = 0; lines[i] != NULL; i++)
+    {
+      char *line = lines[i];
+      char *comment, *command;
+
+      /* Ignore shell-style comments */
+      comment = strchr (line, '#');
+      if (comment != NULL)
+        *comment = 0;
+
+      command = line_get_word (&line);
+      /* Ignore empty lines */
+      if (command == NULL)
+        continue;
+
+      if (strcmp (command, "allow") == 0 || strcmp (command, "deny") == 0)
+        {
+          char *glob, *next;
+          g_autofree char *ref_regexp = NULL;
+          GString *command_regexp;
+          gboolean *has_type = NULL;
+
+          glob = line_get_word (&line);
+          if (glob == NULL)
+            return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Missing glob on line %d"), i + 1);
+
+          next = line_get_word (&line);
+          if (next != NULL)
+            return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Trailing text on line %d"), i + 1);
+
+          ref_regexp = flatpak_filter_glob_to_regexp (glob, error);
+          if (ref_regexp == NULL)
+            return glnx_prefix_error (error, _("on line %d"), i + 1);
+
+          if (strcmp (command, "allow") == 0)
+            {
+              command_regexp = allow_regexp;
+              has_type = &has_allow;
+            }
+          else
+            {
+              command_regexp = deny_regexp;
+              has_type = &has_deny;
+            }
+
+          if (*has_type)
+            g_string_append (command_regexp, "|");
+          else
+            *has_type = TRUE;
+
+          g_string_append (command_regexp, ref_regexp);
+        }
+      else
+        {
+          return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Unexpected word '%s' on line %d"), command, i + 1);
+        }
+    }
+
+  g_string_append (allow_regexp, ")$");
+  g_string_append (deny_regexp, ")$");
+
+  if (allow_regexp)
+    {
+      allow_refs = g_regex_new (allow_regexp->str, G_REGEX_DOLLAR_ENDONLY|G_REGEX_RAW|G_REGEX_OPTIMIZE, G_REGEX_MATCH_ANCHORED, error);
+      if (allow_refs == NULL)
+        return FALSE;
+    }
+
+  if (deny_regexp)
+    {
+      deny_refs = g_regex_new (deny_regexp->str, G_REGEX_DOLLAR_ENDONLY|G_REGEX_RAW|G_REGEX_OPTIMIZE, G_REGEX_MATCH_ANCHORED, error);
+      if (deny_refs == NULL)
+        return FALSE;
+    }
+
+  *allow_refs_out = g_steal_pointer (&allow_refs);
+  *deny_refs_out = g_steal_pointer (&deny_refs);
+
+  return TRUE;
+}
+
+gboolean
+flatpak_filters_allow_ref (GRegex *allow_refs,
+                           GRegex *deny_refs,
+                           const char *ref)
+{
+  if (deny_refs == NULL)
+    return TRUE; /* All refs are allowed by default */
+
+  if (!g_regex_match (deny_refs, ref, G_REGEX_MATCH_ANCHORED, NULL))
+    return TRUE; /* Not denied */
+
+  if (allow_refs &&  g_regex_match (allow_refs, ref, G_REGEX_MATCH_ANCHORED, NULL))
+    return TRUE; /* Explicitly allowed */
+
+  return FALSE;
+}
+
+char **
+flatpak_decompose_ref (const char *full_ref,
+                       GError    **error)
+{
+  g_auto(GStrv) parts = NULL;
+  g_autoptr(GError) local_error = NULL;
+
+  parts = g_strsplit (full_ref, "/", 0);
+  if (g_strv_length (parts) != 4)
+    {
+      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Wrong number of components in %s"), full_ref);
+      return NULL;
+    }
+
+  if (strcmp (parts[0], "app") != 0 && strcmp (parts[0], "runtime") != 0)
+    {
+      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("%s is not application or runtime"), full_ref);
+      return NULL;
+    }
+
+  if (!flatpak_is_valid_name (parts[1], &local_error))
+    {
+      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Invalid name %s: %s"), parts[1], local_error->message);
+      return NULL;
+    }
+
+  if (strlen (parts[2]) == 0)
+    {
+      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Invalid arch %s"), parts[2]);
+      return NULL;
+    }
+
+  if (!flatpak_is_valid_branch (parts[3], &local_error))
+    {
+      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Invalid branch %s: %s"), parts[3], local_error->message);
+      return NULL;
+    }
+
+  return g_steal_pointer (&parts);
+}
+
+static const char *
+next_element (const char **partial_ref)
+{
+  const char *slash;
+  const char *end;
+
+  slash = (const char *) strchr (*partial_ref, '/');
+  if (slash != NULL)
+    {
+      end = slash;
+      *partial_ref = slash + 1;
+    }
+  else
+    {
+      end = *partial_ref + strlen (*partial_ref);
+      *partial_ref = end;
+    }
+
+  return end;
+}
+
+FlatpakKinds
+flatpak_kinds_from_bools (gboolean app, gboolean runtime)
+{
+  FlatpakKinds kinds = 0;
+
+  if (app)
+    kinds |= FLATPAK_KINDS_APP;
+
+  if (runtime)
+    kinds |= FLATPAK_KINDS_RUNTIME;
+
+  if (kinds == 0)
+    kinds = FLATPAK_KINDS_APP | FLATPAK_KINDS_RUNTIME;
+
+  return kinds;
+}
+
+static gboolean
+_flatpak_split_partial_ref_arg (const char   *partial_ref,
+                                gboolean      validate,
+                                FlatpakKinds  default_kinds,
+                                const char   *default_arch,
+                                const char   *default_branch,
+                                FlatpakKinds *out_kinds,
+                                char        **out_id,
+                                char        **out_arch,
+                                char        **out_branch,
+                                GError      **error)
+{
+  const char *id_start = NULL;
+  const char *id_end = NULL;
+  g_autofree char *id = NULL;
+  const char *arch_start = NULL;
+  const char *arch_end = NULL;
+  g_autofree char *arch = NULL;
+  const char *branch_start = NULL;
+  const char *branch_end = NULL;
+  g_autofree char *branch = NULL;
+  g_autoptr(GError) local_error = NULL;
+  FlatpakKinds kinds = 0;
+
+  if (g_str_has_prefix (partial_ref, "app/"))
+    {
+      partial_ref += strlen ("app/");
+      kinds = FLATPAK_KINDS_APP;
+    }
+  else if (g_str_has_prefix (partial_ref, "runtime/"))
+    {
+      partial_ref += strlen ("runtime/");
+      kinds = FLATPAK_KINDS_RUNTIME;
+    }
+  else
+    kinds = default_kinds;
+
+  id_start = partial_ref;
+  id_end = next_element (&partial_ref);
+  id = g_strndup (id_start, id_end - id_start);
+
+  if (validate && !flatpak_is_valid_name (id, &local_error))
+    return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Invalid id %s: %s"), id, local_error->message);
+
+  arch_start = partial_ref;
+  arch_end = next_element (&partial_ref);
+  if (arch_end != arch_start)
+    arch = g_strndup (arch_start, arch_end - arch_start);
+  else
+    arch = g_strdup (default_arch);
+
+  branch_start = partial_ref;
+  branch_end = next_element (&partial_ref);
+  if (branch_end != branch_start)
+    branch = g_strndup (branch_start, branch_end - branch_start);
+  else
+    branch = g_strdup (default_branch);
+
+  if (validate && branch != NULL && !flatpak_is_valid_branch (branch, &local_error))
+    return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Invalid branch %s: %s"), branch, local_error->message);
+
+  if (out_kinds)
+    *out_kinds = kinds;
+  if (out_id != NULL)
+    *out_id = g_steal_pointer (&id);
+  if (out_arch != NULL)
+    *out_arch = g_steal_pointer (&arch);
+  if (out_branch != NULL)
+    *out_branch = g_steal_pointer (&branch);
+
+  return TRUE;
+}
+
+gboolean
+flatpak_split_partial_ref_arg (const char   *partial_ref,
+                               FlatpakKinds  default_kinds,
+                               const char   *default_arch,
+                               const char   *default_branch,
+                               FlatpakKinds *out_kinds,
+                               char        **out_id,
+                               char        **out_arch,
+                               char        **out_branch,
+                               GError      **error)
+{
+  return _flatpak_split_partial_ref_arg (partial_ref,
+                                         TRUE,
+                                         default_kinds,
+                                         default_arch,
+                                         default_branch,
+                                         out_kinds,
+                                         out_id,
+                                         out_arch,
+                                         out_branch,
+                                         error);
+}
+
+gboolean
+flatpak_split_partial_ref_arg_novalidate (const char   *partial_ref,
+                                          FlatpakKinds  default_kinds,
+                                          const char   *default_arch,
+                                          const char   *default_branch,
+                                          FlatpakKinds *out_kinds,
+                                          char        **out_id,
+                                          char        **out_arch,
+                                          char        **out_branch)
+{
+  return _flatpak_split_partial_ref_arg (partial_ref,
+                                         FALSE,
+                                         default_kinds,
+                                         default_arch,
+                                         default_branch,
+                                         out_kinds,
+                                         out_id,
+                                         out_arch,
+                                         out_branch,
+                                         NULL);
+}
+
+
+char *
+flatpak_compose_ref (gboolean    app,
+                     const char *name,
+                     const char *branch,
+                     const char *arch,
+                     GError    **error)
+{
+  g_autoptr(GError) local_error = NULL;
+
+  if (!flatpak_is_valid_name (name, &local_error))
+    {
+      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("'%s' is not a valid name: %s"), name, local_error->message);
+      return NULL;
+    }
+
+  if (branch && !flatpak_is_valid_branch (branch, &local_error))
+    {
+      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("'%s' is not a valid branch name: %s"), branch, local_error->message);
+      return NULL;
+    }
+
+  if (app)
+    return flatpak_build_app_ref (name, branch, arch);
+  else
+    return flatpak_build_runtime_ref (name, branch, arch);
+}
+
+char *
+flatpak_build_untyped_ref (const char *runtime,
+                           const char *branch,
+                           const char *arch)
+{
+  if (arch == NULL)
+    arch = flatpak_get_arch ();
+
+  return g_build_filename (runtime, arch, branch, NULL);
+}
+
+char *
+flatpak_build_runtime_ref (const char *runtime,
+                           const char *branch,
+                           const char *arch)
+{
+  if (branch == NULL)
+    branch = "master";
+
+  if (arch == NULL)
+    arch = flatpak_get_arch ();
+
+  return g_build_filename ("runtime", runtime, arch, branch, NULL);
+}
+
+char *
+flatpak_build_app_ref (const char *app,
+                       const char *branch,
+                       const char *arch)
+{
+  if (branch == NULL)
+    branch = "master";
+
+  if (arch == NULL)
+    arch = flatpak_get_arch ();
+
+  return g_build_filename ("app", app, arch, branch, NULL);
+}
+
+char **
+flatpak_list_deployed_refs (const char   *type,
+                            const char   *name_prefix,
+                            const char   *arch,
+                            const char   *branch,
+                            GCancellable *cancellable,
+                            GError      **error)
+{
+  gchar **ret = NULL;
+  g_autoptr(GPtrArray) names = NULL;
+  g_autoptr(GHashTable) hash = NULL;
+  g_autoptr(FlatpakDir) user_dir = NULL;
+  g_autoptr(GPtrArray) system_dirs = NULL;
+  const char *key;
+  GHashTableIter iter;
+  int i;
+
+  hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+  user_dir = flatpak_dir_get_user ();
+  system_dirs = flatpak_dir_get_system_list (cancellable, error);
+  if (system_dirs == NULL)
+    goto out;
+
+  if (!flatpak_dir_collect_deployed_refs (user_dir, type, name_prefix,
+                                          arch, branch, hash, cancellable,
+                                          error))
+    goto out;
+
+  for (i = 0; i < system_dirs->len; i++)
+    {
+      FlatpakDir *system_dir = g_ptr_array_index (system_dirs, i);
+      if (!flatpak_dir_collect_deployed_refs (system_dir, type, name_prefix,
+                                              arch, branch, hash, cancellable,
+                                              error))
+        goto out;
+    }
+
+  names = g_ptr_array_new ();
+  g_hash_table_iter_init (&iter, hash);
+  while (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL))
+    g_ptr_array_add (names, g_strdup (key));
+
+  g_ptr_array_sort (names, flatpak_strcmp0_ptr);
+  g_ptr_array_add (names, NULL);
+
+  ret = (char **) g_ptr_array_free (names, FALSE);
+  names = NULL;
+
+out:
+  return ret;
+}
+
+char **
+flatpak_list_unmaintained_refs (const char   *name_prefix,
+                                const char   *arch,
+                                const char   *branch,
+                                GCancellable *cancellable,
+                                GError      **error)
+{
+  gchar **ret = NULL;
+  g_autoptr(GPtrArray) names = NULL;
+  g_autoptr(GHashTable) hash = NULL;
+  g_autoptr(FlatpakDir) user_dir = NULL;
+  const char *key;
+  GHashTableIter iter;
+  g_autoptr(GPtrArray) system_dirs = NULL;
+  int i;
+
+  hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+  user_dir = flatpak_dir_get_user ();
+
+  if (!flatpak_dir_collect_unmaintained_refs (user_dir, name_prefix,
+                                              arch, branch, hash, cancellable,
+                                              error))
+    return NULL;
+
+  system_dirs = flatpak_dir_get_system_list (cancellable, error);
+  if (system_dirs == NULL)
+    return NULL;
+
+  for (i = 0; i < system_dirs->len; i++)
+    {
+      FlatpakDir *system_dir = g_ptr_array_index (system_dirs, i);
+
+      if (!flatpak_dir_collect_unmaintained_refs (system_dir, name_prefix,
+                                                  arch, branch, hash, cancellable,
+                                                  error))
+        return NULL;
+    }
+
+  names = g_ptr_array_new ();
+  g_hash_table_iter_init (&iter, hash);
+  while (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL))
+    g_ptr_array_add (names, g_strdup (key));
+
+  g_ptr_array_sort (names, flatpak_strcmp0_ptr);
+  g_ptr_array_add (names, NULL);
+
+  ret = (char **) g_ptr_array_free (names, FALSE);
+  names = NULL;
+
+  return ret;
+}
+
+GFile *
+flatpak_find_deploy_dir_for_ref (const char   *ref,
+                                 FlatpakDir  **dir_out,
+                                 GCancellable *cancellable,
+                                 GError      **error)
+{
+  g_autoptr(FlatpakDir) user_dir = NULL;
+  g_autoptr(GPtrArray) system_dirs = NULL;
+  FlatpakDir *dir = NULL;
+  g_autoptr(GFile) deploy = NULL;
+
+  user_dir = flatpak_dir_get_user ();
+  system_dirs = flatpak_dir_get_system_list (cancellable, error);
+  if (system_dirs == NULL)
+    return NULL;
+
+  dir = user_dir;
+  deploy = flatpak_dir_get_if_deployed (dir, ref, NULL, cancellable);
+  if (deploy == NULL)
+    {
+      int i;
+      for (i = 0; deploy == NULL && i < system_dirs->len; i++)
+        {
+          dir = g_ptr_array_index (system_dirs, i);
+          deploy = flatpak_dir_get_if_deployed (dir, ref, NULL, cancellable);
+          if (deploy != NULL)
+            break;
+        }
+    }
+
+  if (deploy == NULL)
+    {
+      flatpak_fail_error (error, FLATPAK_ERROR_NOT_INSTALLED, _("%s not installed"), ref);
+      return NULL;
+    }
+
+  if (dir_out)
+    *dir_out = g_object_ref (dir);
+  return g_steal_pointer (&deploy);
+}
+
+GFile *
+flatpak_find_files_dir_for_ref (const char   *ref,
+                                GCancellable *cancellable,
+                                GError      **error)
+{
+  g_autoptr(GFile) deploy = NULL;
+
+  deploy = flatpak_find_deploy_dir_for_ref (ref, NULL, cancellable, error);
+  if (deploy == NULL)
+    return NULL;
+
+  return g_file_get_child (deploy, "files");
+}
+
+GFile *
+flatpak_find_unmaintained_extension_dir_if_exists (const char   *name,
+                                                   const char   *arch,
+                                                   const char   *branch,
+                                                   GCancellable *cancellable)
+{
+  g_autoptr(FlatpakDir) user_dir = NULL;
+  g_autoptr(GFile) extension_dir = NULL;
+  g_autoptr(GError) local_error = NULL;
+
+  user_dir = flatpak_dir_get_user ();
+
+  extension_dir = flatpak_dir_get_unmaintained_extension_dir_if_exists (user_dir, name, arch, branch, cancellable);
+  if (extension_dir == NULL)
+    {
+      g_autoptr(GPtrArray) system_dirs = NULL;
+      int i;
+
+      system_dirs = flatpak_dir_get_system_list (cancellable, &local_error);
+      if (system_dirs == NULL)
+        {
+          g_warning ("Could not get the system installations: %s", local_error->message);
+          return NULL;
+        }
+
+      for (i = 0; i < system_dirs->len; i++)
+        {
+          FlatpakDir *system_dir = g_ptr_array_index (system_dirs, i);
+          extension_dir = flatpak_dir_get_unmaintained_extension_dir_if_exists (system_dir, name, arch, branch, cancellable);
+          if (extension_dir != NULL)
+            break;
+        }
+    }
+
+  if (extension_dir == NULL)
+    return NULL;
+
+  return g_steal_pointer (&extension_dir);
+}
+
+char *
+flatpak_find_current_ref (const char   *app_id,
+                          GCancellable *cancellable,
+                          GError      **error)
+{
+  g_autofree char *current_ref = NULL;
+  g_autoptr(FlatpakDir) user_dir = flatpak_dir_get_user ();
+  int i;
+
+  current_ref = flatpak_dir_current_ref (user_dir, app_id, NULL);
+  if (current_ref == NULL)
+    {
+      g_autoptr(GPtrArray) system_dirs = NULL;
+
+      system_dirs = flatpak_dir_get_system_list (cancellable, error);
+      if (system_dirs == NULL)
+        return FALSE;
+
+      for (i = 0; i < system_dirs->len; i++)
+        {
+          FlatpakDir *dir = g_ptr_array_index (system_dirs, i);
+          current_ref = flatpak_dir_current_ref (dir, app_id, cancellable);
+          if (current_ref != NULL)
+            break;
+        }
+    }
+
+  if (current_ref)
+    return g_steal_pointer (&current_ref);
+
+  flatpak_fail_error (error, FLATPAK_ERROR_NOT_INSTALLED, _("%s not installed"), app_id);
+  return NULL;
+}
+
+FlatpakDeploy *
+flatpak_find_deploy_for_ref_in (GPtrArray    *dirs,
+                                const char   *ref,
+                                const char   *commit,
+                                GCancellable *cancellable,
+                                GError      **error)
+{
+  FlatpakDeploy *deploy = NULL;
+  int i;
+  g_autoptr(GError) my_error = NULL;
+
+  for (i = 0; deploy == NULL && i < dirs->len; i++)
+    {
+      FlatpakDir *dir = g_ptr_array_index (dirs, i);
+
+      flatpak_log_dir_access (dir);
+      g_clear_error (&my_error);
+      deploy = flatpak_dir_load_deployed (dir, ref, commit, cancellable, &my_error);
+    }
+
+  if (deploy == NULL)
+    g_propagate_error (error, g_steal_pointer (&my_error));
+
+  return deploy;
+}
+
+FlatpakDeploy *
+flatpak_find_deploy_for_ref (const char   *ref,
+                             const char   *commit,
+                             GCancellable *cancellable,
+                             GError      **error)
+{
+  g_autoptr(GPtrArray) dirs = NULL;
+
+  dirs = flatpak_dir_get_system_list (cancellable, error);
+  if (dirs == NULL)
+    return NULL;
+
+  g_ptr_array_insert (dirs, 0, flatpak_dir_get_user ());
+
+  return flatpak_find_deploy_for_ref_in (dirs, ref, commit, cancellable, error);
+}
+
+static gboolean
+remove_dangling_symlinks (int           parent_fd,
+                          const char   *name,
+                          GCancellable *cancellable,
+                          GError      **error)
+{
+  gboolean ret = FALSE;
+  struct dirent *dent;
+  g_auto(GLnxDirFdIterator) iter = { 0 };
+
+  if (!glnx_dirfd_iterator_init_at (parent_fd, name, FALSE, &iter, error))
+    goto out;
+
+  while (TRUE)
+    {
+      if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&iter, &dent, cancellable, error))
+        goto out;
+
+      if (dent == NULL)
+        break;
+
+      if (dent->d_type == DT_DIR)
+        {
+          if (!remove_dangling_symlinks (iter.fd, dent->d_name, cancellable, error))
+            goto out;
+        }
+      else if (dent->d_type == DT_LNK)
+        {
+          struct stat stbuf;
+          if (fstatat (iter.fd, dent->d_name, &stbuf, 0) != 0 && errno == ENOENT)
+            {
+              if (unlinkat (iter.fd, dent->d_name, 0) != 0)
+                {
+                  glnx_set_error_from_errno (error);
+                  goto out;
+                }
+            }
+        }
+    }
+
+  ret = TRUE;
+out:
+
+  return ret;
+}
+
+gboolean
+flatpak_remove_dangling_symlinks (GFile        *dir,
+                                  GCancellable *cancellable,
+                                  GError      **error)
+{
+  gboolean ret = FALSE;
+
+  /* The fd is closed by this call */
+  if (!remove_dangling_symlinks (AT_FDCWD, flatpak_file_get_path_cached (dir),
+                                 cancellable, error))
+    goto out;
+
+  ret = TRUE;
+
+out:
+  return ret;
+}
+
+/* This atomically replaces a symlink with a new value, removing the
+ * existing symlink target, if it exstis and is different from
+ * @target. This is atomic in the sense that we're guaranteed to
+ * remove any existing symlink target (once), independent of how many
+ * processes do the same operation in parallele. However, it is still
+ * possible that we remove the old and then fail to create the new
+ * symlink for some reason, ending up with neither the old or the new
+ * target. That is fine if the reason for the symlink is keeping a
+ * cache though.
+ */
+gboolean
+flatpak_switch_symlink_and_remove (const char *symlink_path,
+                                   const char *target,
+                                   GError    **error)
+{
+  g_autofree char *symlink_dir = g_path_get_dirname (symlink_path);
+  int try;
+
+  for (try = 0; try < 100; try++)
+    {
+      g_autofree char *tmp_path = NULL;
+      int fd;
+
+      /* Try to atomically create the symlink */
+      if (TEMP_FAILURE_RETRY (symlink (target, symlink_path)) == 0)
+        return TRUE;
+
+      if (errno != EEXIST)
+        {
+          /* Unexpected failure, bail */
+          glnx_set_error_from_errno (error);
+          return FALSE;
+        }
+
+      /* The symlink existed, move it to a temporary name atomically, and remove target
+         if that succeeded. */
+      tmp_path = g_build_filename (symlink_dir, ".switched-symlink-XXXXXX", NULL);
+
+      fd = g_mkstemp_full (tmp_path, O_RDWR, 0644);
+      if (fd == -1)
+        {
+          glnx_set_error_from_errno (error);
+          return FALSE;
+        }
+      close (fd);
+
+      if (TEMP_FAILURE_RETRY (rename (symlink_path, tmp_path)) == 0)
+        {
+          /* The move succeeded, now we can remove the old target */
+          g_autofree char *old_target = flatpak_readlink (tmp_path, error);
+          if (old_target == NULL)
+            return FALSE;
+          if (strcmp (old_target, target) != 0) /* Don't remove old file if its the same as the new one */
+            {
+              g_autofree char *old_target_path = g_build_filename (symlink_dir, old_target, NULL);
+              unlink (old_target_path);
+            }
+        }
+      else if (errno != ENOENT)
+        {
+          glnx_set_error_from_errno (error);
+          unlink (tmp_path);
+          return -1;
+        }
+      unlink (tmp_path);
+
+      /* An old target was removed, try again */
+    }
+
+  return flatpak_fail (error, "flatpak_switch_symlink_and_remove looped too many times");
+}
+
+#endif
+
+static gboolean
+needs_quoting (const char *arg)
+{
+  while (*arg != 0)
+    {
+      char c = *arg;
+      if (!g_ascii_isalnum (c) &&
+          !(c == '-' || c == '/' || c == '~' ||
+            c == ':' || c == '.' || c == '_' ||
+            c == '=' || c == '@'))
+        return TRUE;
+      arg++;
+    }
+  return FALSE;
+}
+
+char *
+flatpak_quote_argv (const char *argv[],
+                    gssize      len)
+{
+  GString *res = g_string_new ("");
+  int i;
+
+  if (len == -1)
+    len = g_strv_length ((char **) argv);
+
+  for (i = 0; i < len; i++)
+    {
+      if (i != 0)
+        g_string_append_c (res, ' ');
+
+      if (needs_quoting (argv[i]))
+        {
+          g_autofree char *quoted = g_shell_quote (argv[i]);
+          g_string_append (res, quoted);
+        }
+      else
+        g_string_append (res, argv[i]);
+    }
+
+  return g_string_free (res, FALSE);
+}
+
+#if 0
+
+/* This is useful, because it handles escaped characters in uris, and ? arguments at the end of the uri */
+gboolean
+flatpak_file_arg_has_suffix (const char *arg, const char *suffix)
+{
+  g_autoptr(GFile) file = g_file_new_for_commandline_arg (arg);
+  g_autofree char *basename = g_file_get_basename (file);
+
+  return g_str_has_suffix (basename, suffix);
+}
+
+GFile *
+flatpak_build_file_va (GFile  *base,
+                       va_list args)
+{
+  g_autoptr(GFile) res = g_object_ref (base);
+  const gchar *arg;
+
+  while ((arg = va_arg (args, const gchar *)))
+    {
+      g_autoptr(GFile) child = g_file_resolve_relative_path (res, arg);
+      g_set_object (&res, child);
+    }
+
+  return g_steal_pointer (&res);
+}
+
+GFile *
+flatpak_build_file (GFile *base, ...)
+{
+  GFile *res;
+  va_list args;
+
+  va_start (args, base);
+  res = flatpak_build_file_va (base, args);
+  va_end (args);
+
+  return res;
+}
+
+#endif
+
+#if !GLIB_CHECK_VERSION(2, 34, 0)
+G_LOCK_DEFINE_STATIC (path_cached);
+#endif
+
+const char *
+flatpak_file_get_path_cached (GFile *file)
+{
+  const char *path;
+  static GQuark _file_path_quark = 0;
+
+  if (G_UNLIKELY (_file_path_quark == 0))
+    _file_path_quark = g_quark_from_static_string ("flatpak-file-path");
+
+#if GLIB_CHECK_VERSION(2, 34, 0)
+  do
+    {
+      path = g_object_get_qdata ((GObject *) file, _file_path_quark);
+      if (path == NULL)
+        {
+          g_autofree char *new_path = NULL;
+          new_path = g_file_get_path (file);
+          if (new_path == NULL)
+            return NULL;
+
+          if (g_object_replace_qdata ((GObject *) file, _file_path_quark,
+                                      NULL, new_path, g_free, NULL))
+            path = g_steal_pointer (&new_path);
+        }
+    }
+  while (path == NULL);
+#else
+  G_LOCK (path_cached);
+    {
+      path = g_object_get_qdata ((GObject *) file, _file_path_quark);
+
+      if (path == NULL)
+        {
+          g_autofree char *new_path = g_file_get_path (file);
+
+          path = new_path;
+
+          if (new_path != NULL)
+            g_object_set_qdata_full ((GObject *) file, _file_path_quark,
+                                     g_steal_pointer (&new_path), g_free);
+        }
+    }
+  G_UNLOCK (path_cached);
+#endif
+
+  return path;
+}
+
+#if 0
+
+gboolean
+flatpak_openat_noatime (int           dfd,
+                        const char   *name,
+                        int          *ret_fd,
+                        GCancellable *cancellable,
+                        GError      **error)
+{
+  int fd;
+  int flags = O_RDONLY | O_CLOEXEC;
+
+#ifdef O_NOATIME
+  do
+    fd = openat (dfd, name, flags | O_NOATIME, 0);
+  while (G_UNLIKELY (fd == -1 && errno == EINTR));
+  /* Only the owner or superuser may use O_NOATIME; so we may get
+   * EPERM.  EINVAL may happen if the kernel is really old...
+   */
+  if (fd == -1 && (errno == EPERM || errno == EINVAL))
+#endif
+  do
+    fd = openat (dfd, name, flags, 0);
+  while (G_UNLIKELY (fd == -1 && errno == EINTR));
+
+  if (fd == -1)
+    {
+      glnx_set_error_from_errno (error);
+      return FALSE;
+    }
+  else
+    {
+      *ret_fd = fd;
+      return TRUE;
+    }
+}
+
+gboolean
+flatpak_cp_a (GFile         *src,
+              GFile         *dest,
+              FlatpakCpFlags flags,
+              GCancellable  *cancellable,
+              GError       **error)
+{
+  gboolean ret = FALSE;
+  GFileEnumerator *enumerator = NULL;
+  GFileInfo *src_info = NULL;
+  GFile *dest_child = NULL;
+  int dest_dfd = -1;
+  gboolean merge = (flags & FLATPAK_CP_FLAGS_MERGE) != 0;
+  gboolean no_chown = (flags & FLATPAK_CP_FLAGS_NO_CHOWN) != 0;
+  gboolean move = (flags & FLATPAK_CP_FLAGS_MOVE) != 0;
+  g_autoptr(GFileInfo) child_info = NULL;
+  GError *temp_error = NULL;
+  int r;
+
+  enumerator = g_file_enumerate_children (src, "standard::type,standard::name,unix::uid,unix::gid,unix::mode",
+                                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                          cancellable, error);
+  if (!enumerator)
+    goto out;
+
+  src_info = g_file_query_info (src, "standard::name,unix::mode,unix::uid,unix::gid," \
+                                     "time::modified,time::modified-usec,time::access,time::access-usec",
+                                G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                cancellable, error);
+  if (!src_info)
+    goto out;
+
+  do
+    r = mkdir (flatpak_file_get_path_cached (dest), 0755);
+  while (G_UNLIKELY (r == -1 && errno == EINTR));
+  if (r == -1 &&
+      (!merge || errno != EEXIST))
+    {
+      glnx_set_error_from_errno (error);
+      goto out;
+    }
+
+  if (!glnx_opendirat (AT_FDCWD, flatpak_file_get_path_cached (dest), TRUE,
+                       &dest_dfd, error))
+    goto out;
+
+  if (!no_chown)
+    {
+      do
+        r = fchown (dest_dfd,
+                    g_file_info_get_attribute_uint32 (src_info, "unix::uid"),
+                    g_file_info_get_attribute_uint32 (src_info, "unix::gid"));
+      while (G_UNLIKELY (r == -1 && errno == EINTR));
+      if (r == -1)
+        {
+          glnx_set_error_from_errno (error);
+          goto out;
+        }
+    }
+
+  do
+    r = fchmod (dest_dfd, g_file_info_get_attribute_uint32 (src_info, "unix::mode"));
+  while (G_UNLIKELY (r == -1 && errno == EINTR));
+
+  if (dest_dfd != -1)
+    {
+      (void) close (dest_dfd);
+      dest_dfd = -1;
+    }
+
+  while ((child_info = g_file_enumerator_next_file (enumerator, cancellable, &temp_error)))
+    {
+      const char *name = g_file_info_get_name (child_info);
+      g_autoptr(GFile) src_child = g_file_get_child (src, name);
+
+      if (dest_child)
+        g_object_unref (dest_child);
+      dest_child = g_file_get_child (dest, name);
+
+      if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY)
+        {
+          if (!flatpak_cp_a (src_child, dest_child, flags,
+                             cancellable, error))
+            goto out;
+        }
+      else
+        {
+          (void) unlink (flatpak_file_get_path_cached (dest_child));
+          GFileCopyFlags copyflags = G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS;
+          if (!no_chown)
+            copyflags |= G_FILE_COPY_ALL_METADATA;
+          if (move)
+            {
+              if (!g_file_move (src_child, dest_child, copyflags,
+                                cancellable, NULL, NULL, error))
+                goto out;
+            }
+          else
+            {
+              if (!g_file_copy (src_child, dest_child, copyflags,
+                                cancellable, NULL, NULL, error))
+                goto out;
+            }
+        }
+
+      g_clear_object (&child_info);
+    }
+
+  if (temp_error != NULL)
+    {
+      g_propagate_error (error, temp_error);
+      goto out;
+    }
+
+  if (move &&
+      !g_file_delete (src, NULL, error))
+    goto out;
+
+  ret = TRUE;
+out:
+  if (dest_dfd != -1)
+    (void) close (dest_dfd);
+  g_clear_object (&src_info);
+  g_clear_object (&enumerator);
+  g_clear_object (&dest_child);
+  return ret;
+}
+
+static gboolean
+_flatpak_canonicalize_permissions (int         parent_dfd,
+                                   const char *rel_path,
+                                   gboolean    toplevel,
+                                   int         uid,
+                                   int         gid,
+                                   GError    **error)
+{
+  struct stat stbuf;
+  gboolean res = TRUE;
+
+  /* Note, in order to not leave non-canonical things around in case
+   * of error, this continues after errors, but returns the first
+   * error. */
+
+  if (TEMP_FAILURE_RETRY (fstatat (parent_dfd, rel_path, &stbuf, AT_SYMLINK_NOFOLLOW)) != 0)
+    {
+      glnx_set_error_from_errno (error);
+      return FALSE;
+    }
+
+  if ((uid != -1 && uid != stbuf.st_uid) || (gid != -1 && gid != stbuf.st_gid))
+    {
+      if (TEMP_FAILURE_RETRY (fchownat (parent_dfd, rel_path, uid, gid, AT_SYMLINK_NOFOLLOW)) != 0)
+        {
+          glnx_set_error_from_errno (error);
+          return FALSE;
+        }
+
+      /* Re-read st_mode for new owner */
+      if (TEMP_FAILURE_RETRY (fstatat (parent_dfd, rel_path, &stbuf, AT_SYMLINK_NOFOLLOW)) != 0)
+        {
+          glnx_set_error_from_errno (error);
+          return FALSE;
+        }
+    }
+
+  if (S_ISDIR (stbuf.st_mode))
+    {
+      g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
+
+      /* For the toplevel we set to 0700 so we can modify it, but not
+         expose any non-canonical files to any other user, then we set
+         it to 0755 afterwards. */
+      if (fchmodat (parent_dfd, rel_path, toplevel ? 0700 : 0755, 0) != 0)
+        {
+          glnx_set_error_from_errno (error);
+          error = NULL;
+          res = FALSE;
+        }
+
+      if (glnx_dirfd_iterator_init_at (parent_dfd, rel_path, FALSE, &dfd_iter, NULL))
+        {
+          while (TRUE)
+            {
+              struct dirent *dent;
+
+              if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, NULL, NULL) || dent == NULL)
+                break;
+
+              if (!_flatpak_canonicalize_permissions (dfd_iter.fd, dent->d_name, FALSE, uid, gid, error))
+                {
+                  error = NULL;
+                  res = FALSE;
+                }
+            }
+        }
+
+      if (toplevel &&
+          fchmodat (parent_dfd, rel_path, 0755, 0) != 0)
+        {
+          glnx_set_error_from_errno (error);
+          error = NULL;
+          res = FALSE;
+        }
+
+      return res;
+    }
+  else if (S_ISREG (stbuf.st_mode))
+    {
+      mode_t mode;
+
+      /* If use can execute, make executable by all */
+      if (stbuf.st_mode & S_IXUSR)
+        mode = 0755;
+      else /* otherwise executable by none */
+        mode = 0644;
+
+      if (fchmodat (parent_dfd, rel_path, mode, 0) != 0)
+        {
+          glnx_set_error_from_errno (error);
+          res = FALSE;
+        }
+    }
+  else if (S_ISLNK (stbuf.st_mode))
+    {
+      /* symlinks have no permissions */
+    }
+  else
+    {
+      /* some weird non-canonical type, lets delete it */
+      if (unlinkat (parent_dfd, rel_path, 0) != 0)
+        {
+          glnx_set_error_from_errno (error);
+          res = FALSE;
+        }
+    }
+
+  return res;
+}
+
+/* Canonicalizes files to the same permissions as bare-user-only checkouts */
+gboolean
+flatpak_canonicalize_permissions (int         parent_dfd,
+                                  const char *rel_path,
+                                  int         uid,
+                                  int         gid,
+                                  GError    **error)
+{
+  return _flatpak_canonicalize_permissions (parent_dfd, rel_path, TRUE, uid, gid, error);
+}
+
+#endif
+
+/* Make a directory, and its parent. Don't error if it already exists.
+ * If you want a failure mode with EEXIST, use g_file_make_directory_with_parents. */
+gboolean
+flatpak_mkdir_p (GFile        *dir,
+                 GCancellable *cancellable,
+                 GError      **error)
+{
+  return glnx_shutil_mkdir_p_at (AT_FDCWD,
+                                 flatpak_file_get_path_cached (dir),
+                                 0777,
+                                 cancellable,
+                                 error);
+}
+
+#if 0
+
+gboolean
+flatpak_rm_rf (GFile        *dir,
+               GCancellable *cancellable,
+               GError      **error)
+{
+  return glnx_shutil_rm_rf_at (AT_FDCWD,
+                               flatpak_file_get_path_cached (dir),
+                               cancellable, error);
+}
+
+gboolean
+flatpak_file_rename (GFile        *from,
+                     GFile        *to,
+                     GCancellable *cancellable,
+                     GError      **error)
+{
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    return FALSE;
+
+  if (rename (flatpak_file_get_path_cached (from),
+              flatpak_file_get_path_cached (to)) < 0)
+    {
+      glnx_set_error_from_errno (error);
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+#endif
+
+/* If memfd_create() is available, generate a sealed memfd with contents of
+ * @str. Otherwise use an O_TMPFILE @tmpf in anonymous mode, write @str to
+ * @tmpf, and lseek() back to the start. See also similar uses in e.g.
+ * rpm-ostree for running dracut.
+ */
+gboolean
+flatpak_buffer_to_sealed_memfd_or_tmpfile (GLnxTmpfile *tmpf,
+                                           const char  *name,
+                                           const char  *str,
+                                           size_t       len,
+                                           GError     **error)
+{
+  if (len == -1)
+    len = strlen (str);
+  glnx_autofd int memfd = memfd_create (name, MFD_CLOEXEC | MFD_ALLOW_SEALING);
+  int fd; /* Unowned */
+  if (memfd != -1)
+    {
+      fd = memfd;
+    }
+  else
+    {
+      /* We use an anonymous fd (i.e. O_EXCL) since we don't want
+       * the target container to potentially be able to re-link it.
+       */
+      if (!G_IN_SET (errno, ENOSYS, EOPNOTSUPP))
+        return glnx_throw_errno_prefix (error, "memfd_create");
+      if (!glnx_open_anonymous_tmpfile (O_RDWR | O_CLOEXEC, tmpf, error))
+        return FALSE;
+      fd = tmpf->fd;
+    }
+  if (ftruncate (fd, len) < 0)
+    return glnx_throw_errno_prefix (error, "ftruncate");
+  if (glnx_loop_write (fd, str, len) < 0)
+    return glnx_throw_errno_prefix (error, "write");
+  if (lseek (fd, 0, SEEK_SET) < 0)
+    return glnx_throw_errno_prefix (error, "lseek");
+  if (memfd != -1)
+    {
+      /* Valgrind doesn't currently handle G_ADD_SEALS, so lets not seal when debugging... */
+      if ((!RUNNING_ON_VALGRIND) &&
+          fcntl (memfd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL) < 0)
+        return glnx_throw_errno_prefix (error, "fcntl(F_ADD_SEALS)");
+      /* The other values can stay default */
+      tmpf->fd = glnx_steal_fd (&memfd);
+      tmpf->initialized = TRUE;
+    }
+  return TRUE;
+}
+
+#if 0
+
+gboolean
+flatpak_open_in_tmpdir_at (int             tmpdir_fd,
+                           int             mode,
+                           char           *tmpl,
+                           GOutputStream **out_stream,
+                           GCancellable   *cancellable,
+                           GError        **error)
+{
+  const int max_attempts = 128;
+  int i;
+  int fd;
+
+  /* 128 attempts seems reasonable... */
+  for (i = 0; i < max_attempts; i++)
+    {
+      glnx_gen_temp_name (tmpl);
+
+      do
+        fd = openat (tmpdir_fd, tmpl, O_WRONLY | O_CREAT | O_EXCL, mode);
+      while (fd == -1 && errno == EINTR);
+      if (fd < 0 && errno != EEXIST)
+        {
+          glnx_set_error_from_errno (error);
+          return FALSE;
+        }
+      else if (fd != -1)
+        break;
+    }
+  if (i == max_attempts)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Exhausted attempts to open temporary file");
+      return FALSE;
+    }
+
+  if (out_stream)
+    *out_stream = g_unix_output_stream_new (fd, TRUE);
+  else
+    (void) close (fd);
+
+  return TRUE;
+}
+
+gboolean
+flatpak_bytes_save (GFile        *dest,
+                    GBytes       *bytes,
+                    GCancellable *cancellable,
+                    GError      **error)
+{
+  g_autoptr(GOutputStream) out = NULL;
+
+  out = (GOutputStream *) g_file_replace (dest, NULL, FALSE,
+                                          G_FILE_CREATE_REPLACE_DESTINATION,
+                                          cancellable, error);
+  if (out == NULL)
+    return FALSE;
+
+  if (!g_output_stream_write_all (out,
+                                  g_bytes_get_data (bytes, NULL),
+                                  g_bytes_get_size (bytes),
+                                  NULL,
+                                  cancellable,
+                                  error))
+    return FALSE;
+
+  if (!g_output_stream_close (out, cancellable, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+gboolean
+flatpak_variant_save (GFile        *dest,
+                      GVariant     *variant,
+                      GCancellable *cancellable,
+                      GError      **error)
+{
+  g_autoptr(GOutputStream) out = NULL;
+  gsize bytes_written;
+
+  out = (GOutputStream *) g_file_replace (dest, NULL, FALSE,
+                                          G_FILE_CREATE_REPLACE_DESTINATION,
+                                          cancellable, error);
+  if (out == NULL)
+    return FALSE;
+
+  if (!g_output_stream_write_all (out,
+                                  g_variant_get_data (variant),
+                                  g_variant_get_size (variant),
+                                  &bytes_written,
+                                  cancellable,
+                                  error))
+    return FALSE;
+
+  if (!g_output_stream_close (out, cancellable, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+/* This special cases the ref lookup which by doing a
+   bsearch since the array is sorted */
+static gboolean
+flatpak_var_ref_map_lookup_ref (VarRefMapRef   ref_map,
+                                const char    *ref,
+                                VarRefInfoRef *out_info)
+{
+  gsize imax, imin;
+  gsize imid;
+  gsize n;
+
+  g_return_val_if_fail (out_info != NULL, FALSE);
+
+  n = var_ref_map_get_length (ref_map);
+  if (n == 0)
+    return FALSE;
+
+  imax = n - 1;
+  imin = 0;
+  while (imax >= imin)
+    {
+      VarRefMapEntryRef entry;
+      const char *cur;
+      int cmp;
+
+      imid = (imin + imax) / 2;
+
+      entry = var_ref_map_get_at (ref_map, imid);
+      cur = var_ref_map_entry_get_ref (entry);
+
+      cmp = strcmp (cur, ref);
+      if (cmp < 0)
+        {
+          imin = imid + 1;
+        }
+      else if (cmp > 0)
+        {
+          if (imid == 0)
+            break;
+          imax = imid - 1;
+        }
+      else
+        {
+          *out_info = var_ref_map_entry_get_info (entry);
+          return TRUE;
+        }
+    }
+
+  return FALSE;
+}
+
+/* Find the list of refs which belong to the given @collection_id in @summary.
+ * If @collection_id is %NULL, the main refs list from the summary will be
+ * returned. If @collection_id doesn’t match any collection IDs in the summary
+ * file, %FALSE will be returned. */
+gboolean
+flatpak_summary_find_ref_map (VarSummaryRef summary,
+                              const char *collection_id,
+                              VarRefMapRef *refs_out)
+{
+  VarMetadataRef metadata = var_summary_get_metadata (summary);
+  const char *summary_collection_id;
+
+  summary_collection_id = var_metadata_lookup_string (metadata, "ostree.summary.collection-id", NULL);
+
+  if (collection_id == NULL || g_strcmp0 (collection_id, summary_collection_id) == 0)
+    {
+      if (refs_out)
+        *refs_out = var_summary_get_ref_map (summary);
+      return TRUE;
+    }
+  else if (collection_id != NULL)
+    {
+      VarVariantRef collection_map_v;
+      if (var_metadata_lookup (metadata, "ostree.summary.collection-map", NULL, &collection_map_v))
+        {
+          VarCollectionMapRef collection_map = var_collection_map_from_variant (collection_map_v);
+          return var_collection_map_lookup (collection_map, collection_id, NULL, refs_out);
+        }
+    }
+
+  return FALSE;
+}
+
+/* This matches all refs from @collection_id that have ref, followed by '.'  as prefix */
+char **
+flatpak_summary_match_subrefs (GVariant   *summary_v,
+                               const char *collection_id,
+                               const char *ref)
+{
+  GPtrArray *res = g_ptr_array_new ();
+  gsize n, i;
+  g_auto(GStrv) parts = NULL;
+  g_autofree char *parts_prefix = NULL;
+  g_autofree char *ref_prefix = NULL;
+  g_autofree char *ref_suffix = NULL;
+  VarSummaryRef summary;
+  VarRefMapRef ref_map;
+
+  summary = var_summary_from_gvariant (summary_v);
+
+  /* Work out which refs list to use, based on the @collection_id. */
+  if (flatpak_summary_find_ref_map (summary, collection_id, &ref_map))
+    {
+      /* Match against the refs. */
+      parts = g_strsplit (ref, "/", 0);
+      parts_prefix = g_strconcat (parts[1], ".", NULL);
+
+      ref_prefix = g_strconcat (parts[0], "/", NULL);
+      ref_suffix = g_strconcat ("/", parts[2], "/", parts[3], NULL);
+
+      n = var_ref_map_get_length (ref_map);
+      for (i = 0; i < n; i++)
+        {
+          VarRefMapEntryRef entry = var_ref_map_get_at (ref_map, i);
+          const char *cur;
+          const char *id_start;
+
+          cur = var_ref_map_entry_get_ref (entry);
+
+          /* Must match type */
+          if (!g_str_has_prefix (cur, ref_prefix))
+            continue;
+
+          /* Must match arch & branch */
+          if (!g_str_has_suffix (cur, ref_suffix))
+            continue;
+
+          id_start = strchr (cur, '/');
+          if (id_start == NULL)
+            continue;
+
+          /* But only prefix of id */
+          if (!g_str_has_prefix (id_start + 1, parts_prefix))
+            continue;
+
+          g_ptr_array_add (res, g_strdup (cur));
+        }
+    }
+
+  g_ptr_array_add (res, NULL);
+  return (char **) g_ptr_array_free (res, FALSE);
+}
+
+gboolean
+flatpak_summary_lookup_ref (GVariant      *summary_v,
+                            const char    *collection_id,
+                            const char    *ref,
+                            char         **out_checksum,
+                            VarRefInfoRef *out_info)
+{
+  VarSummaryRef summary;
+  VarRefMapRef ref_map;
+  VarRefInfoRef info;
+  const guchar *checksum_bytes;
+  gsize checksum_bytes_len;
+
+  summary = var_summary_from_gvariant (summary_v);
+
+  /* Work out which refs list to use, based on the @collection_id. */
+  if (!flatpak_summary_find_ref_map (summary, collection_id, &ref_map))
+    return FALSE;
+
+  if (!flatpak_var_ref_map_lookup_ref (ref_map, ref, &info))
+    return FALSE;
+
+  checksum_bytes = var_ref_info_peek_checksum (info, &checksum_bytes_len);
+  if (G_UNLIKELY (checksum_bytes_len != OSTREE_SHA256_DIGEST_LEN))
+    return FALSE;
+
+  if (out_checksum)
+    *out_checksum = ostree_checksum_from_bytes (checksum_bytes);
+
+  if (out_info)
+    *out_info = info;
+
+  return TRUE;
+}
+
+GKeyFile *
+flatpak_parse_repofile (const char   *remote_name,
+                        gboolean      from_ref,
+                        GKeyFile     *keyfile,
+                        GBytes      **gpg_data_out,
+                        GCancellable *cancellable,
+                        GError      **error)
+{
+  g_autoptr(GBytes) gpg_data = NULL;
+  g_autofree char *uri = NULL;
+  g_autofree char *title = NULL;
+  g_autofree char *gpg_key = NULL;
+  g_autofree char *collection_id = NULL;
+  g_autofree char *default_branch = NULL;
+  g_autofree char *comment = NULL;
+  g_autofree char *description = NULL;
+  g_autofree char *icon = NULL;
+  g_autofree char *homepage = NULL;
+  g_autofree char *filter = NULL;
+  g_autofree char *authenticator_name = NULL;
+  gboolean nodeps;
+  const char *source_group;
+  g_autofree char *version = NULL;
+
+  if (from_ref)
+    source_group = FLATPAK_REF_GROUP;
+  else
+    source_group = FLATPAK_REPO_GROUP;
+
+  GKeyFile *config = g_key_file_new ();
+  g_autofree char *group = g_strdup_printf ("remote \"%s\"", remote_name);
+
+  if (!g_key_file_has_group (keyfile, source_group))
+    {
+      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid %s: Missing group ‘%s’"),
+                          from_ref ? ".flatpakref" : ".flatpakrepo", source_group);
+      return NULL;
+    }
+
+  uri = g_key_file_get_string (keyfile, source_group,
+                               FLATPAK_REPO_URL_KEY, NULL);
+  if (uri == NULL)
+    {
+      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid %s: Missing key ‘%s’"),
+                          from_ref ? ".flatpakref" : ".flatpakrepo", FLATPAK_REPO_URL_KEY);
+      return NULL;
+    }
+
+  version = g_key_file_get_string (keyfile, FLATPAK_REPO_GROUP,
+                                   FLATPAK_REPO_VERSION_KEY, NULL);
+  if (version != NULL && strcmp (version, "1") != 0)
+    {
+      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA,
+                          _("Invalid version %s, only 1 supported"), version);
+      return NULL;
+    }
+
+  g_key_file_set_string (config, group, "url", uri);
+
+  title = g_key_file_get_locale_string (keyfile, source_group,
+                                        FLATPAK_REPO_TITLE_KEY, NULL, NULL);
+  if (title != NULL)
+    g_key_file_set_string (config, group, "xa.title", title);
+
+  default_branch = g_key_file_get_locale_string (keyfile, source_group,
+                                                 FLATPAK_REPO_DEFAULT_BRANCH_KEY, NULL, NULL);
+  if (default_branch != NULL)
+    g_key_file_set_string (config, group, "xa.default-branch", default_branch);
+
+  nodeps = g_key_file_get_boolean (keyfile, source_group,
+                                   FLATPAK_REPO_NODEPS_KEY, NULL);
+  if (nodeps)
+    g_key_file_set_boolean (config, group, "xa.nodeps", TRUE);
+
+  gpg_key = g_key_file_get_string (keyfile, source_group,
+                                   FLATPAK_REPO_GPGKEY_KEY, NULL);
+  if (gpg_key != NULL)
+    {
+      guchar *decoded;
+      gsize decoded_len;
+
+      gpg_key = g_strstrip (gpg_key);
+      decoded = g_base64_decode (gpg_key, &decoded_len);
+      if (decoded_len < 10) /* Check some minimal size so we don't get crap */
+        {
+          flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid gpg key"));
+          return NULL;
+        }
+
+      gpg_data = g_bytes_new_take (decoded, decoded_len);
+      g_key_file_set_boolean (config, group, "gpg-verify", TRUE);
+    }
+  else
+    {
+      g_key_file_set_boolean (config, group, "gpg-verify", FALSE);
+    }
+
+  collection_id = g_key_file_get_string (keyfile, source_group,
+                                         FLATPAK_REPO_DEPLOY_COLLECTION_ID_KEY, NULL);
+  if (collection_id == NULL || *collection_id == '\0')
+    collection_id = g_key_file_get_string (keyfile, source_group,
+                                           FLATPAK_REPO_COLLECTION_ID_KEY, NULL);
+  if (collection_id != NULL)
+    {
+      if (gpg_key == NULL)
+        {
+          flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Collection ID requires GPG key to be provided"));
+          return NULL;
+        }
+
+      g_key_file_set_string (config, group, "collection-id", collection_id);
+    }
+
+  g_key_file_set_boolean (config, group, "gpg-verify-summary",
+                          (gpg_key != NULL));
+
+  authenticator_name = g_key_file_get_string (keyfile, FLATPAK_REPO_GROUP,
+                                              FLATPAK_REPO_AUTHENTICATOR_NAME_KEY, NULL);
+  if (authenticator_name)
+    g_key_file_set_string (config, group, "xa.authenticator-name", authenticator_name);
+
+  if (g_key_file_has_key (keyfile, FLATPAK_REPO_GROUP, FLATPAK_REPO_AUTHENTICATOR_INSTALL_KEY, NULL))
+    {
+      gboolean authenticator_install = g_key_file_get_boolean (keyfile, FLATPAK_REPO_GROUP,
+                                                               FLATPAK_REPO_AUTHENTICATOR_INSTALL_KEY, NULL);
+      g_key_file_set_boolean (config, group, "xa.authenticator-install", authenticator_install);
+    }
+
+  comment = g_key_file_get_string (keyfile, FLATPAK_REPO_GROUP,
+                                   FLATPAK_REPO_COMMENT_KEY, NULL);
+  if (comment)
+    g_key_file_set_string (config, group, "xa.comment", comment);
+
+  description = g_key_file_get_string (keyfile, FLATPAK_REPO_GROUP,
+                                       FLATPAK_REPO_DESCRIPTION_KEY, NULL);
+  if (description)
+    g_key_file_set_string (config, group, "xa.description", description);
+
+  icon = g_key_file_get_string (keyfile, FLATPAK_REPO_GROUP,
+                                FLATPAK_REPO_ICON_KEY, NULL);
+  if (icon)
+    g_key_file_set_string (config, group, "xa.icon", icon);
+
+  homepage  = g_key_file_get_string (keyfile, FLATPAK_REPO_GROUP,
+                                     FLATPAK_REPO_HOMEPAGE_KEY, NULL);
+  if (homepage)
+    g_key_file_set_string (config, group, "xa.homepage", homepage);
+
+  filter = g_key_file_get_string (keyfile, FLATPAK_REPO_GROUP,
+                                   FLATPAK_REPO_FILTER_KEY, NULL);
+  if (filter)
+    g_key_file_set_string (config, group, "xa.filter", filter);
+  else
+    g_key_file_set_string (config, group, "xa.filter", ""); /* Default to override any pre-existing filters */
+
+  *gpg_data_out = g_steal_pointer (&gpg_data);
+
+  return g_steal_pointer (&config);
+}
+
+gboolean
+flatpak_repo_set_title (OstreeRepo *repo,
+                        const char *title,
+                        GError    **error)
+{
+  g_autoptr(GKeyFile) config = NULL;
+
+  config = ostree_repo_copy_config (repo);
+
+  if (title)
+    g_key_file_set_string (config, "flatpak", "title", title);
+  else
+    g_key_file_remove_key (config, "flatpak", "title", NULL);
+
+  if (!ostree_repo_write_config (repo, config, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+gboolean
+flatpak_repo_set_comment (OstreeRepo *repo,
+                          const char *comment,
+                          GError    **error)
+{
+  g_autoptr(GKeyFile) config = NULL;
+
+  config = ostree_repo_copy_config (repo);
+
+  if (comment)
+    g_key_file_set_string (config, "flatpak", "comment", comment);
+  else
+    g_key_file_remove_key (config, "flatpak", "comment", NULL);
+
+  if (!ostree_repo_write_config (repo, config, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+gboolean
+flatpak_repo_set_description (OstreeRepo *repo,
+                              const char *description,
+                              GError    **error)
+{
+  g_autoptr(GKeyFile) config = NULL;
+
+  config = ostree_repo_copy_config (repo);
+
+  if (description)
+    g_key_file_set_string (config, "flatpak", "description", description);
+  else
+    g_key_file_remove_key (config, "flatpak", "description", NULL);
+
+  if (!ostree_repo_write_config (repo, config, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+
+gboolean
+flatpak_repo_set_icon (OstreeRepo *repo,
+                       const char *icon,
+                       GError    **error)
+{
+  g_autoptr(GKeyFile) config = NULL;
+
+  config = ostree_repo_copy_config (repo);
+
+  if (icon)
+    g_key_file_set_string (config, "flatpak", "icon", icon);
+  else
+    g_key_file_remove_key (config, "flatpak", "icon", NULL);
+
+  if (!ostree_repo_write_config (repo, config, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+gboolean
+flatpak_repo_set_homepage (OstreeRepo *repo,
+                           const char *homepage,
+                           GError    **error)
+{
+  g_autoptr(GKeyFile) config = NULL;
+
+  config = ostree_repo_copy_config (repo);
+
+  if (homepage)
+    g_key_file_set_string (config, "flatpak", "homepage", homepage);
+  else
+    g_key_file_remove_key (config, "flatpak", "homepage", NULL);
+
+  if (!ostree_repo_write_config (repo, config, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+gboolean
+flatpak_repo_set_redirect_url (OstreeRepo *repo,
+                               const char *redirect_url,
+                               GError    **error)
+{
+  g_autoptr(GKeyFile) config = NULL;
+
+  config = ostree_repo_copy_config (repo);
+
+  if (redirect_url)
+    g_key_file_set_string (config, "flatpak", "redirect-url", redirect_url);
+  else
+    g_key_file_remove_key (config, "flatpak", "redirect-url", NULL);
+
+  if (!ostree_repo_write_config (repo, config, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+gboolean
+flatpak_repo_set_authenticator_name (OstreeRepo *repo,
+                                     const char *authenticator_name,
+                                     GError    **error)
+{
+  g_autoptr(GKeyFile) config = NULL;
+
+  config = ostree_repo_copy_config (repo);
+
+  if (authenticator_name)
+    g_key_file_set_string (config, "flatpak", "authenticator-name", authenticator_name);
+  else
+    g_key_file_remove_key (config, "flatpak", "authenticator-name", NULL);
+
+  if (!ostree_repo_write_config (repo, config, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+gboolean
+flatpak_repo_set_authenticator_install (OstreeRepo *repo,
+                                        gboolean authenticator_install,
+                                        GError    **error)
+{
+  g_autoptr(GKeyFile) config = NULL;
+
+  config = ostree_repo_copy_config (repo);
+
+  g_key_file_set_boolean (config, "flatpak", "authenticator-install", authenticator_install);
+
+  if (!ostree_repo_write_config (repo, config, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+gboolean
+flatpak_repo_set_authenticator_option (OstreeRepo *repo,
+                                       const char *key,
+                                       const char *value,
+                                       GError    **error)
+{
+  g_autoptr(GKeyFile) config = NULL;
+  g_autofree char *full_key = g_strdup_printf ("authenticator-options.%s", key);
+
+  config = ostree_repo_copy_config (repo);
+
+  if (value)
+    g_key_file_set_string (config, "flatpak", full_key, value);
+  else
+    g_key_file_remove_key (config, "flatpak", full_key, NULL);
+
+  if (!ostree_repo_write_config (repo, config, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+gboolean
+flatpak_repo_set_deploy_collection_id (OstreeRepo *repo,
+                                       gboolean    deploy_collection_id,
+                                       GError    **error)
+{
+  g_autoptr(GKeyFile) config = NULL;
+
+  config = ostree_repo_copy_config (repo);
+  g_key_file_set_boolean (config, "flatpak", "deploy-collection-id", deploy_collection_id);
+  return ostree_repo_write_config (repo, config, error);
+}
+
+gboolean
+flatpak_repo_set_deploy_sideload_collection_id (OstreeRepo *repo,
+                                           gboolean    deploy_collection_id,
+                                           GError    **error)
+{
+  g_autoptr(GKeyFile) config = NULL;
+
+  config = ostree_repo_copy_config (repo);
+  g_key_file_set_boolean (config, "flatpak", "deploy-sideload-collection-id", deploy_collection_id);
+  return ostree_repo_write_config (repo, config, error);
+}
+
+gboolean
+flatpak_repo_set_gpg_keys (OstreeRepo *repo,
+                           GBytes     *bytes,
+                           GError    **error)
+{
+  g_autoptr(GKeyFile) config = NULL;
+  g_autofree char *value_base64 = NULL;
+
+  config = ostree_repo_copy_config (repo);
+
+  value_base64 = g_base64_encode (g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes));
+
+  g_key_file_set_string (config, "flatpak", "gpg-keys", value_base64);
+
+  if (!ostree_repo_write_config (repo, config, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+gboolean
+flatpak_repo_set_default_branch (OstreeRepo *repo,
+                                 const char *branch,
+                                 GError    **error)
+{
+  g_autoptr(GKeyFile) config = NULL;
+
+  config = ostree_repo_copy_config (repo);
+
+  if (branch)
+    g_key_file_set_string (config, "flatpak", "default-branch", branch);
+  else
+    g_key_file_remove_key (config, "flatpak", "default-branch", NULL);
+
+  if (!ostree_repo_write_config (repo, config, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+gboolean
+flatpak_repo_set_collection_id (OstreeRepo *repo,
+                                const char *collection_id,
+                                GError    **error)
+{
+  g_autoptr(GKeyFile) config = NULL;
+
+  if (!ostree_repo_set_collection_id (repo, collection_id, error))
+    return FALSE;
+
+  config = ostree_repo_copy_config (repo);
+  if (!ostree_repo_write_config (repo, config, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+GVariant *
+flatpak_commit_get_extra_data_sources (GVariant *commitv,
+                                       GError  **error)
+{
+  g_autoptr(GVariant) commit_metadata = NULL;
+  g_autoptr(GVariant) extra_data_sources = NULL;
+
+  commit_metadata = g_variant_get_child_value (commitv, 0);
+  extra_data_sources = g_variant_lookup_value (commit_metadata,
+                                               "xa.extra-data-sources",
+                                               G_VARIANT_TYPE ("a(ayttays)"));
+
+  if (extra_data_sources == NULL)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+                   _("No extra data sources"));
+      return NULL;
+    }
+
+  return g_steal_pointer (&extra_data_sources);
+}
+
+
+GVariant *
+flatpak_repo_get_extra_data_sources (OstreeRepo   *repo,
+                                     const char   *rev,
+                                     GCancellable *cancellable,
+                                     GError      **error)
+{
+  g_autoptr(GVariant) commitv = NULL;
+
+  if (!ostree_repo_load_variant (repo,
+                                 OSTREE_OBJECT_TYPE_COMMIT,
+                                 rev, &commitv, error))
+    return NULL;
+
+  return flatpak_commit_get_extra_data_sources (commitv, error);
+}
+
+void
+flatpak_repo_parse_extra_data_sources (GVariant      *extra_data_sources,
+                                       int            index,
+                                       const char   **name,
+                                       guint64       *download_size,
+                                       guint64       *installed_size,
+                                       const guchar **sha256,
+                                       const char   **uri)
+{
+  g_autoptr(GVariant) sha256_v = NULL;
+  g_variant_get_child (extra_data_sources, index, "(^aytt@ay&s)",
+                       name,
+                       download_size,
+                       installed_size,
+                       &sha256_v,
+                       uri);
+
+  if (download_size)
+    *download_size = GUINT64_FROM_BE (*download_size);
+
+  if (installed_size)
+    *installed_size = GUINT64_FROM_BE (*installed_size);
+
+  if (sha256)
+    *sha256 = ostree_checksum_bytes_peek (sha256_v);
+}
+
+#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 gboolean
+_flatpak_repo_collect_sizes (OstreeRepo   *repo,
+                             GFile        *file,
+                             GFileInfo    *file_info,
+                             guint64      *installed_size,
+                             guint64      *download_size,
+                             GCancellable *cancellable,
+                             GError      **error)
+{
+  g_autoptr(GFileEnumerator) dir_enum = NULL;
+  GFileInfo *child_info_tmp;
+  g_autoptr(GError) temp_error = NULL;
+
+  if (file_info != NULL && g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR)
+    {
+      const char *checksum = ostree_repo_file_get_checksum (OSTREE_REPO_FILE (file));
+      guint64 obj_size;
+      guint64 file_size = g_file_info_get_size (file_info);
+
+      if (installed_size)
+        *installed_size += ((file_size + 511) / 512) * 512;
+
+      if (download_size)
+        {
+          g_autoptr(GInputStream) input = NULL;
+          GInputStream *base_input;
+          g_autoptr(GError) local_error = NULL;
+
+          if (!ostree_repo_query_object_storage_size (repo,
+                                                      OSTREE_OBJECT_TYPE_FILE, checksum,
+                                                      &obj_size, cancellable, &local_error))
+            {
+              int fd;
+              struct stat stbuf;
+
+              /* Ostree does not look at the staging directory when querying storage
+                 size, so may return a NOT_FOUND error here. We work around this
+                 by loading the object and walking back until we find the original
+                 fd which we can fstat(). */
+              if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+                return FALSE;
+
+              if (!ostree_repo_load_file (repo, checksum,  &input, NULL, NULL, NULL, error))
+                return FALSE;
+
+              base_input = input;
+              while (G_IS_FILTER_INPUT_STREAM (base_input))
+                base_input = g_filter_input_stream_get_base_stream (G_FILTER_INPUT_STREAM (base_input));
+
+              if (!G_IS_UNIX_INPUT_STREAM (base_input))
+                return flatpak_fail (error, "Unable to find size of commit %s, not an unix stream", checksum);
+
+              fd = g_unix_input_stream_get_fd (G_UNIX_INPUT_STREAM (base_input));
+
+              if (fstat (fd, &stbuf) != 0)
+                return glnx_throw_errno_prefix (error, "Can't find commit size: ");
+
+              obj_size = stbuf.st_size;
+            }
+
+          *download_size += obj_size;
+        }
+    }
+
+  if (file_info == NULL || g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY)
+    {
+      dir_enum = g_file_enumerate_children (file, OSTREE_GIO_FAST_QUERYINFO,
+                                            G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                            cancellable, error);
+      if (!dir_enum)
+        return FALSE;
+
+
+      while ((child_info_tmp = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)))
+        {
+          g_autoptr(GFileInfo) child_info = child_info_tmp;
+          const char *name = g_file_info_get_name (child_info);
+          g_autoptr(GFile) child = g_file_get_child (file, name);
+
+          if (!_flatpak_repo_collect_sizes (repo, child, child_info, installed_size, download_size, cancellable, error))
+            return FALSE;
+        }
+    }
+
+  return TRUE;
+}
+
+gboolean
+flatpak_repo_collect_sizes (OstreeRepo   *repo,
+                            GFile        *root,
+                            guint64      *installed_size,
+                            guint64      *download_size,
+                            GCancellable *cancellable,
+                            GError      **error)
+{
+  /* Initialize the sums */
+  if (installed_size)
+    *installed_size = 0;
+  if (download_size)
+    *download_size = 0;
+  return _flatpak_repo_collect_sizes (repo, root, NULL, installed_size, download_size, cancellable, error);
+}
+
+
+static void
+flatpak_repo_collect_extra_data_sizes (OstreeRepo *repo,
+                                       const char *rev,
+                                       guint64    *installed_size,
+                                       guint64    *download_size)
+{
+  g_autoptr(GVariant) extra_data_sources = NULL;
+  gsize n_extra_data;
+  int i;
+
+  extra_data_sources = flatpak_repo_get_extra_data_sources (repo, rev, NULL, NULL);
+  if (extra_data_sources == NULL)
+    return;
+
+  n_extra_data = g_variant_n_children (extra_data_sources);
+  if (n_extra_data == 0)
+    return;
+
+  for (i = 0; i < n_extra_data; i++)
+    {
+      guint64 extra_download_size;
+      guint64 extra_installed_size;
+
+      flatpak_repo_parse_extra_data_sources (extra_data_sources, i,
+                                             NULL,
+                                             &extra_download_size,
+                                             &extra_installed_size,
+                                             NULL, NULL);
+      if (installed_size)
+        *installed_size += extra_installed_size;
+      if (download_size)
+        *download_size += extra_download_size;
+    }
+}
+
+/* Loads a summary file from a local repo */
+GVariant *
+flatpak_repo_load_summary (OstreeRepo *repo,
+                           GError    **error)
+{
+  glnx_autofd int fd = -1;
+  g_autoptr(GMappedFile) mfile = NULL;
+  g_autoptr(GBytes) bytes = NULL;
+
+  fd = openat (ostree_repo_get_dfd (repo), "summary", O_RDONLY | O_CLOEXEC);
+  if (fd < 0)
+    {
+      glnx_set_error_from_errno (error);
+      return NULL;
+    }
+
+  mfile = g_mapped_file_new_from_fd (fd, FALSE, error);
+  if (!mfile)
+    return NULL;
+
+  bytes = g_mapped_file_get_bytes (mfile);
+
+  return g_variant_ref_sink (g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, bytes, TRUE));
+}
+
+typedef struct
+{
+  guint64   installed_size;
+  guint64   download_size;
+  char     *metadata_contents;
+  GVariant *sparse_data;
+} CommitData;
+
+static void
+commit_data_free (gpointer data)
+{
+  CommitData *rev_data = data;
+
+  g_free (rev_data->metadata_contents);
+  if (rev_data->sparse_data)
+    g_variant_unref (rev_data->sparse_data);
+  g_free (rev_data);
+}
+
+/* For all the refs listed in @cache_v (an xa.cache value) which exist in the
+ * @summary, insert their data into @commit_data_cache if it isn’t already there. */
+static void
+populate_commit_data_cache (GVariant   *metadata,
+                            GVariant   *summary,
+                            GHashTable *commit_data_cache /* (element-type utf8 CommitData) */)
+{
+  g_autoptr(GVariant) cache_v = NULL;
+  g_autoptr(GVariant) cache = NULL;
+  g_autoptr(GVariant) sparse_cache = NULL;
+  gsize n, i;
+  guint32 cache_version = 0;
+  const char *old_collection_id;
+
+  if (!g_variant_lookup (metadata, "ostree.summary.collection-id", "&s", &old_collection_id))
+    old_collection_id = NULL;
+
+  cache_v = g_variant_lookup_value (metadata, "xa.cache", NULL);
+  if (cache_v == NULL)
+    return;
+
+  if (g_variant_lookup (metadata, "xa.cache-version", "u", &cache_version))
+    cache_version = GUINT32_FROM_LE (cache_version);
+  else
+    cache_version = 0;
+
+  if (cache_version < FLATPAK_XA_CACHE_VERSION)
+    return; /* We need to rebuild the cache with the current version */
+
+  cache = g_variant_get_child_value (cache_v, 0);
+
+  sparse_cache = g_variant_lookup_value (metadata, "xa.sparse-cache", NULL);
+
+  n = g_variant_n_children (cache);
+  for (i = 0; i < n; i++)
+    {
+      g_autoptr(GVariant) old_element = g_variant_get_child_value (cache, i);
+      g_autoptr(GVariant) old_ref_v = g_variant_get_child_value (old_element, 0);
+      const char *old_ref = g_variant_get_string (old_ref_v, NULL);
+      g_autofree char *old_rev = NULL;
+      g_autoptr(GVariant) old_commit_data_v = g_variant_get_child_value (old_element, 1);
+      CommitData *old_rev_data;
+
+      if (flatpak_summary_lookup_ref (summary, old_collection_id, old_ref, &old_rev, NULL))
+        {
+          guint64 old_installed_size, old_download_size;
+          g_autofree char *old_metadata = NULL;
+
+          /* See if we already have the info on this revision */
+          if (g_hash_table_lookup (commit_data_cache, old_rev))
+            continue;
+
+          g_variant_get_child (old_commit_data_v, 0, "t", &old_installed_size);
+          old_installed_size = GUINT64_FROM_BE (old_installed_size);
+          g_variant_get_child (old_commit_data_v, 1, "t", &old_download_size);
+          old_download_size = GUINT64_FROM_BE (old_download_size);
+          g_variant_get_child (old_commit_data_v, 2, "s", &old_metadata);
+
+          old_rev_data = g_new0 (CommitData, 1);
+          old_rev_data->installed_size = old_installed_size;
+          old_rev_data->download_size = old_download_size;
+          old_rev_data->metadata_contents = g_steal_pointer (&old_metadata);
+
+          if (sparse_cache)
+            old_rev_data->sparse_data = g_variant_lookup_value (sparse_cache, old_ref, G_VARIANT_TYPE_VARDICT);
+
+          g_hash_table_insert (commit_data_cache, g_steal_pointer (&old_rev), old_rev_data);
+        }
+    }
+}
+
+/* Update the metadata in the summary file for @repo, and then re-sign the file.
+ * If the repo has a collection ID set, additionally store the metadata on a
+ * contentless commit in a well-known branch, which is the preferred way of
+ * broadcasting per-repo metadata (putting it in the summary file is deprecated,
+ * but kept for backwards compatibility).
+ *
+ * Note that there are two keys for the collection ID: collection-id, and
+ * ostree.deploy-collection-id. If a client does not currently have a
+ * collection ID configured for this remote, it will *only* update its
+ * configuration from ostree.deploy-collection-id.  This allows phased
+ * deployment of collection-based repositories. Clients will only update their
+ * configuration from an unset to a set collection ID once (otherwise the
+ * security properties of collection IDs are broken). */
+gboolean
+flatpak_repo_update (OstreeRepo   *repo,
+                     const char  **gpg_key_ids,
+                     const char   *gpg_homedir,
+                     GCancellable *cancellable,
+                     GError      **error)
+{
+  g_autoptr(GVariantBuilder) builder = g_variant_builder_new (G_VARIANT_TYPE_VARDICT);
+  g_autoptr(GVariantBuilder) commits_builder = g_variant_builder_new (G_VARIANT_TYPE ("aay"));
+  g_autoptr(GVariantBuilder) ref_data_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{s(tts)}"));
+  g_autoptr(GVariantBuilder) ref_sparse_data_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sa{sv}}"));
+  GKeyFile *config;
+  g_autofree char *title = NULL;
+  g_autofree char *comment = NULL;
+  g_autofree char *description = NULL;
+  g_autofree char *homepage = NULL;
+  g_autofree char *icon = NULL;
+  g_autofree char *redirect_url = NULL;
+  g_autofree char *default_branch = NULL;
+  g_autofree char *authenticator_name = NULL;
+  g_autofree char *gpg_keys = NULL;
+  g_auto(GStrv) config_keys = NULL;
+  int authenticator_install = -1;
+  g_autoptr(GVariant) old_summary = NULL;
+  g_autoptr(GVariant) new_summary = NULL;
+  g_autoptr(GHashTable) refs = NULL;
+  const char *prefixes[] = { "appstream", "appstream2", "app", "runtime", NULL };
+  const char **prefix;
+  g_autoptr(GList) ordered_keys = NULL;
+  GList *l = NULL;
+  g_autoptr(GHashTable) commit_data_cache = NULL;
+  const char *collection_id;
+  g_autofree char *old_ostree_metadata_checksum = NULL;
+  g_autoptr(GVariant) old_ostree_metadata_v = NULL;
+  gboolean deploy_collection_id = FALSE;
+  gboolean deploy_sideload_collection_id = FALSE;
+
+  config = ostree_repo_get_config (repo);
+
+  if (config)
+    {
+      title = g_key_file_get_string (config, "flatpak", "title", NULL);
+      comment = g_key_file_get_string (config, "flatpak", "comment", NULL);
+      description = g_key_file_get_string (config, "flatpak", "description", NULL);
+      homepage = g_key_file_get_string (config, "flatpak", "homepage", NULL);
+      icon = g_key_file_get_string (config, "flatpak", "icon", NULL);
+      default_branch = g_key_file_get_string (config, "flatpak", "default-branch", NULL);
+      gpg_keys = g_key_file_get_string (config, "flatpak", "gpg-keys", NULL);
+      redirect_url = g_key_file_get_string (config, "flatpak", "redirect-url", NULL);
+      deploy_sideload_collection_id = g_key_file_get_boolean (config, "flatpak", "deploy-sideload-collection-id", NULL);
+      deploy_collection_id = g_key_file_get_boolean (config, "flatpak", "deploy-collection-id", NULL);
+      authenticator_name = g_key_file_get_string (config, "flatpak", "authenticator-name", NULL);
+      if (g_key_file_has_key (config, "flatpak", "authenticator-install", NULL))
+        authenticator_install = g_key_file_get_boolean (config, "flatpak", "authenticator-install", NULL);
+
+      config_keys = g_key_file_get_keys (config, "flatpak", NULL, NULL);
+    }
+
+  collection_id = ostree_repo_get_collection_id (repo);
+
+  if (title)
+    g_variant_builder_add (builder, "{sv}", "xa.title",
+                           g_variant_new_string (title));
+
+  if (comment)
+    g_variant_builder_add (builder, "{sv}", "xa.comment",
+                           g_variant_new_string (comment));
+
+  if (description)
+    g_variant_builder_add (builder, "{sv}", "xa.description",
+                           g_variant_new_string (description));
+
+  if (homepage)
+    g_variant_builder_add (builder, "{sv}", "xa.homepage",
+                           g_variant_new_string (homepage));
+
+  if (icon)
+    g_variant_builder_add (builder, "{sv}", "xa.icon",
+                           g_variant_new_string (icon));
+
+  if (redirect_url)
+    g_variant_builder_add (builder, "{sv}", "xa.redirect-url",
+                           g_variant_new_string (redirect_url));
+
+  if (default_branch)
+    g_variant_builder_add (builder, "{sv}", "xa.default-branch",
+                           g_variant_new_string (default_branch));
+
+  if (deploy_collection_id && collection_id != NULL)
+    g_variant_builder_add (builder, "{sv}", OSTREE_META_KEY_DEPLOY_COLLECTION_ID,
+                           g_variant_new_string (collection_id));
+  else if (deploy_sideload_collection_id && collection_id != NULL)
+    g_variant_builder_add (builder, "{sv}", "xa.deploy-collection-id",
+                           g_variant_new_string (collection_id));
+  else if (deploy_collection_id)
+    g_debug ("Ignoring deploy-collection-id=true because no collection ID is set.");
+
+  if (authenticator_name)
+    g_variant_builder_add (builder, "{sv}", "xa.authenticator-name",
+                           g_variant_new_string (authenticator_name));
+
+  if (authenticator_install != -1)
+    g_variant_builder_add (builder, "{sv}", "xa.authenticator-install",
+                           g_variant_new_boolean (authenticator_install));
+
+  if (config_keys != NULL)
+    {
+      for (int i = 0; config_keys[i] != NULL; i++)
+        {
+          const char *key = config_keys[i];
+          g_autofree char *xa_key = NULL;
+          g_autofree char *value = NULL;
+
+          if (!g_str_has_prefix (key, "authenticator-options."))
+            continue;
+
+          value = g_key_file_get_string (config, "flatpak", key, NULL);
+          if (value == NULL)
+            continue;
+
+          xa_key = g_strconcat ("xa.", key, NULL);
+          g_variant_builder_add (builder, "{sv}", xa_key,
+                                 g_variant_new_string (value));
+        }
+    }
+
+  if (gpg_keys)
+    {
+      guchar *decoded;
+      gsize decoded_len;
+
+      gpg_keys = g_strstrip (gpg_keys);
+      decoded = g_base64_decode (gpg_keys, &decoded_len);
+
+      g_variant_builder_add (builder, "{sv}", "xa.gpg-keys",
+                             g_variant_new_from_data (G_VARIANT_TYPE ("ay"), decoded, decoded_len,
+                                                      TRUE, (GDestroyNotify) g_free, decoded));
+    }
+
+  /* Only operate on flatpak relevant refs */
+  refs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+  for (prefix = prefixes; *prefix != NULL; prefix++)
+    {
+      g_autoptr(GHashTable) prefix_refs = NULL;
+      GHashTableIter hashiter;
+      gpointer key, value;
+
+      if (!ostree_repo_list_refs_ext (repo, *prefix, &prefix_refs,
+                                      OSTREE_REPO_LIST_REFS_EXT_NONE,
+                                      cancellable, error))
+        return FALSE;
+
+      /* Merge the prefix refs to the full refs table */
+      g_hash_table_iter_init (&hashiter, prefix_refs);
+      while (g_hash_table_iter_next (&hashiter, &key, &value))
+        {
+          char *ref = g_strdup (key);
+          char *rev = g_strdup (value);
+          g_hash_table_replace (refs, ref, rev);
+        }
+    }
+
+  commit_data_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                             g_free, commit_data_free);
+
+  old_summary = flatpak_repo_load_summary (repo, NULL);
+
+  if (!flatpak_repo_resolve_rev (repo, collection_id, NULL, OSTREE_REPO_METADATA_REF,
+                                 TRUE, &old_ostree_metadata_checksum, cancellable, error))
+    return FALSE;
+
+  if (old_summary != NULL &&
+      old_ostree_metadata_checksum != NULL &&
+      ostree_repo_load_commit (repo, old_ostree_metadata_checksum, &old_ostree_metadata_v, NULL, NULL))
+    {
+      g_autoptr(GVariant) metadata = g_variant_get_child_value (old_ostree_metadata_v, 0);
+
+      populate_commit_data_cache (metadata, old_summary, commit_data_cache);
+    }
+  else if (old_summary != NULL)
+    {
+      g_autoptr(GVariant) extensions = g_variant_get_child_value (old_summary, 1);
+
+      populate_commit_data_cache (extensions, old_summary, commit_data_cache);
+    }
+
+  ordered_keys = g_hash_table_get_keys (refs);
+  ordered_keys = g_list_sort (ordered_keys, (GCompareFunc) strcmp);
+  for (l = ordered_keys; l; l = l->next)
+    {
+      const char *ref = l->data;
+      const char *rev = g_hash_table_lookup (refs, ref);
+      g_autoptr(GFile) root = NULL;
+      g_autoptr(GFile) metadata = NULL;
+      guint64 installed_size = 0;
+      guint64 download_size = 0;
+      g_autofree char *metadata_contents = NULL;
+      g_autofree char *commit = NULL;
+      g_autoptr(GVariant) commit_v = NULL;
+      g_autoptr(GVariant) commit_metadata = NULL;
+      CommitData *rev_data;
+      const char *eol = NULL;
+      const char *eol_rebase = NULL;
+      int token_type = -1;
+      g_autoptr(GVariant) extra_data_sources = NULL;
+      guint32 n_extra_data = 0;
+      guint64 total_extra_data_download_size = 0;
+
+      /* See if we already have the info on this revision */
+      if (g_hash_table_lookup (commit_data_cache, rev))
+        continue;
+
+      if (!ostree_repo_read_commit (repo, rev, &root, &commit, NULL, error))
+        return FALSE;
+
+      if (!ostree_repo_load_commit (repo, commit, &commit_v, NULL, error))
+        return FALSE;
+
+      commit_metadata = g_variant_get_child_value (commit_v, 0);
+      if (!g_variant_lookup (commit_metadata, "xa.metadata", "s", &metadata_contents))
+        {
+          metadata = g_file_get_child (root, "metadata");
+          if (!g_file_load_contents (metadata, cancellable, &metadata_contents, NULL, NULL, NULL))
+            metadata_contents = g_strdup ("");
+        }
+
+      if (g_variant_lookup (commit_metadata, "xa.installed-size", "t", &installed_size) &&
+          g_variant_lookup (commit_metadata, "xa.download-size", "t", &download_size))
+        {
+          installed_size = GUINT64_FROM_BE (installed_size);
+          download_size = GUINT64_FROM_BE (download_size);
+        }
+      else
+        {
+          if (!flatpak_repo_collect_sizes (repo, root, &installed_size, &download_size, cancellable, error))
+            return FALSE;
+        }
+
+      flatpak_repo_collect_extra_data_sizes (repo, rev, &installed_size, &download_size);
+
+      rev_data = g_new0 (CommitData, 1);
+      rev_data->installed_size = installed_size;
+      rev_data->download_size = download_size;
+      rev_data->metadata_contents = g_steal_pointer (&metadata_contents);
+
+      g_variant_lookup (commit_metadata, OSTREE_COMMIT_META_KEY_ENDOFLIFE, "&s", &eol);
+      g_variant_lookup (commit_metadata, OSTREE_COMMIT_META_KEY_ENDOFLIFE_REBASE, "&s", &eol_rebase);
+      if (g_variant_lookup (commit_metadata, "xa.token-type", "i", &token_type))
+        token_type = GINT32_FROM_LE(token_type);
+
+      extra_data_sources = flatpak_commit_get_extra_data_sources (commit_v, NULL);
+      if (extra_data_sources)
+        {
+          n_extra_data = g_variant_n_children (extra_data_sources);
+          for (int i = 0; i < n_extra_data; i++)
+            {
+              guint64 download_size;
+              flatpak_repo_parse_extra_data_sources (extra_data_sources, i,
+                                                     NULL,
+                                                     &download_size,
+                                                     NULL,
+                                                     NULL,
+                                                     NULL);
+              total_extra_data_download_size += download_size;
+            }
+        }
+
+      if (eol || eol_rebase || token_type >= 0 || n_extra_data > 0)
+        {
+          g_auto(GVariantBuilder) sparse_builder = FLATPAK_VARIANT_BUILDER_INITIALIZER;
+          g_variant_builder_init (&sparse_builder, G_VARIANT_TYPE_VARDICT);
+          if (eol)
+            g_variant_builder_add (&sparse_builder, "{sv}", FLATPAK_SPARSE_CACHE_KEY_ENDOFLINE, g_variant_new_string (eol));
+          if (eol_rebase)
+            g_variant_builder_add (&sparse_builder, "{sv}", FLATPAK_SPARSE_CACHE_KEY_ENDOFLINE_REBASE, g_variant_new_string (eol_rebase));
+          if (token_type >= 0)
+            g_variant_builder_add (&sparse_builder, "{sv}", FLATPAK_SPARSE_CACHE_KEY_TOKEN_TYPE, g_variant_new_int32 (GINT32_TO_LE(token_type)));
+          if (n_extra_data >= 0)
+            g_variant_builder_add (&sparse_builder, "{sv}", FLATPAK_SPARSE_CACHE_KEY_EXTRA_DATA_SIZE,
+                                   g_variant_new ("(ut)", GUINT32_TO_LE(n_extra_data), GUINT64_TO_LE(total_extra_data_download_size)));
+
+          rev_data->sparse_data = g_variant_ref_sink (g_variant_builder_end (&sparse_builder));
+        }
+
+      g_hash_table_insert (commit_data_cache, g_strdup (rev), rev_data);
+    }
+
+  for (l = ordered_keys; l; l = l->next)
+    {
+      const char *ref = l->data;
+      const char *rev = g_hash_table_lookup (refs, ref);
+      const CommitData *rev_data = g_hash_table_lookup (commit_data_cache,
+                                                        rev);
+
+      g_variant_builder_add (ref_data_builder, "{s(tts)}",
+                             ref,
+                             GUINT64_TO_BE (rev_data->installed_size),
+                             GUINT64_TO_BE (rev_data->download_size),
+                             rev_data->metadata_contents);
+      if (rev_data->sparse_data)
+        g_variant_builder_add (ref_sparse_data_builder, "{s@a{sv}}",
+                               ref, rev_data->sparse_data);
+      g_variant_builder_add (commits_builder, "@ay", ostree_checksum_to_bytes_v (rev));
+    }
+
+  /* Note: xa.cache doesn’t need to support collection IDs for the refs listed
+   * in it, because the xa.cache metadata is stored on the ostree-metadata ref,
+   * which is itself strongly bound to a collection ID — so that collection ID
+   * is bound to all the refs in xa.cache. If a client is using the xa.cache
+   * data from a summary file (rather than an ostree-metadata branch), they are
+   * too old to care about collection IDs anyway. */
+  g_variant_builder_add (builder, "{sv}", "xa.cache",
+                         g_variant_new_variant (g_variant_builder_end (ref_data_builder)));
+  g_variant_builder_add (builder, "{sv}", "xa.cache-version",
+                         g_variant_new_uint32 (GUINT32_TO_LE (FLATPAK_XA_CACHE_VERSION)));
+
+  g_variant_builder_add (builder, "{sv}", "xa.sparse-cache",
+                         g_variant_builder_end (ref_sparse_data_builder));
+
+  new_summary = g_variant_ref_sink (g_variant_builder_end (builder));
+
+  /* Write out a new metadata commit for the repository. */
+  if (collection_id != NULL)
+    {
+      OstreeCollectionRef collection_ref = { (gchar *) collection_id, (gchar *) OSTREE_REPO_METADATA_REF };
+      g_autofree gchar *new_ostree_metadata_checksum = NULL;
+      g_autoptr(OstreeMutableTree) mtree = NULL;
+      g_autoptr(OstreeRepoFile) repo_file = NULL;
+      g_autoptr(GVariantDict) new_summary_commit_dict = NULL;
+      g_autoptr(GVariant) new_summary_commit = NULL;
+
+      /* Add bindings to the metadata. */
+      new_summary_commit_dict = g_variant_dict_new (new_summary);
+      g_variant_dict_insert_value (new_summary_commit_dict, "xa.commits",
+                                   g_variant_builder_end (commits_builder));
+      g_variant_dict_insert (new_summary_commit_dict, "ostree.collection-binding",
+                             "s", collection_ref.collection_id);
+      g_variant_dict_insert_value (new_summary_commit_dict, "ostree.ref-binding",
+                                   g_variant_new_strv ((const gchar * const *) &collection_ref.ref_name, 1));
+      new_summary_commit = g_variant_ref_sink (g_variant_dict_end (new_summary_commit_dict));
+
+      if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error))
+        goto out;
+
+      /* Set up an empty mtree. */
+      mtree = ostree_mutable_tree_new ();
+      if (!flatpak_mtree_ensure_dir_metadata (repo, mtree, cancellable, error))
+        goto out;
+      if (!ostree_repo_write_mtree (repo, mtree, (GFile **) &repo_file, NULL, error))
+        goto out;
+
+      if (!ostree_repo_write_commit (repo, old_ostree_metadata_checksum,
+                                     NULL /* subject */, NULL /* body */,
+                                     new_summary_commit, repo_file, &new_ostree_metadata_checksum,
+                                     NULL, error))
+        goto out;
+
+      if (gpg_key_ids != NULL)
+        {
+          const char * const *iter;
+
+          for (iter = gpg_key_ids; iter != NULL && *iter != NULL; iter++)
+            {
+              const char *key_id = *iter;
+
+              if (!ostree_repo_sign_commit (repo,
+                                            new_ostree_metadata_checksum,
+                                            key_id,
+                                            gpg_homedir,
+                                            cancellable,
+                                            error))
+                goto out;
+            }
+        }
+
+      ostree_repo_transaction_set_collection_ref (repo, &collection_ref,
+                                                  new_ostree_metadata_checksum);
+
+      if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error))
+        goto out;
+    }
+
+  /* Regenerate and re-sign the summary file. */
+  if (!ostree_repo_regenerate_summary (repo, new_summary, cancellable, error))
+    return FALSE;
+
+  if (gpg_key_ids)
+    {
+      if (!ostree_repo_add_gpg_signature_summary (repo,
+                                                  gpg_key_ids,
+                                                  gpg_homedir,
+                                                  cancellable,
+                                                  error))
+        return FALSE;
+    }
+
+  return TRUE;
+
+out:
+  if (repo != NULL)
+    ostree_repo_abort_transaction (repo, cancellable, NULL);
+  return FALSE;
+}
+
+gboolean
+flatpak_mtree_create_dir (OstreeRepo         *repo,
+                          OstreeMutableTree  *parent,
+                          const char         *name,
+                          OstreeMutableTree **dir_out,
+                          GError            **error)
+{
+  g_autoptr(OstreeMutableTree) dir = NULL;
+
+  if (!ostree_mutable_tree_ensure_dir (parent, name, &dir, error))
+    return FALSE;
+
+  if (!flatpak_mtree_ensure_dir_metadata (repo, dir, NULL, error))
+    return FALSE;
+
+  *dir_out = g_steal_pointer (&dir);
+  return TRUE;
+}
+
+gboolean
+flatpak_mtree_create_symlink (OstreeRepo         *repo,
+                              OstreeMutableTree  *parent,
+                              const char         *filename,
+                              const char         *target,
+                              GError            **error)
+{
+  g_autoptr(GFileInfo) file_info = g_file_info_new ();
+  g_autoptr(GInputStream) content_stream = NULL;
+  g_autofree guchar *raw_checksum = NULL;
+  g_autofree char *checksum = NULL;
+  guint64 length;
+
+  g_file_info_set_name (file_info, filename);
+  g_file_info_set_file_type (file_info, G_FILE_TYPE_SYMBOLIC_LINK);
+  g_file_info_set_attribute_uint32 (file_info, "unix::uid", 0);
+  g_file_info_set_attribute_uint32 (file_info, "unix::gid", 0);
+  g_file_info_set_attribute_uint32 (file_info, "unix::mode", S_IFLNK | 0777);
+
+  g_file_info_set_attribute_boolean (file_info, "standard::is-symlink", TRUE);
+  g_file_info_set_attribute_byte_string (file_info, "standard::symlink-target", target);
+
+  if (!ostree_raw_file_to_content_stream (NULL, file_info, NULL,
+                                          &content_stream, &length,
+                                          NULL, error))
+    return FALSE;
+
+  if (!ostree_repo_write_content (repo, NULL, content_stream, length,
+                                  &raw_checksum, NULL, error))
+    return FALSE;
+
+  checksum = ostree_checksum_from_bytes (raw_checksum);
+
+  if (!ostree_mutable_tree_replace_file (parent, filename, checksum, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+gboolean
+flatpak_mtree_add_file_from_bytes (OstreeRepo *repo,
+                                   GBytes *bytes,
+                                   OstreeMutableTree *parent,
+                                   const char *filename,
+                                   GCancellable *cancellable,
+                                   GError      **error)
+{
+  g_autoptr(GFileInfo) info = g_file_info_new ();
+  g_autoptr(GInputStream) memstream = NULL;
+  g_autoptr(GInputStream) content_stream = NULL;
+  g_autofree guchar *raw_checksum = NULL;
+  g_autofree char *checksum = NULL;
+  guint64 length;
+
+  g_file_info_set_attribute_uint32 (info, "standard::type", G_FILE_TYPE_REGULAR);
+  g_file_info_set_attribute_uint64 (info, "standard::size", g_bytes_get_size (bytes));
+  g_file_info_set_attribute_uint32 (info, "unix::uid", 0);
+  g_file_info_set_attribute_uint32 (info, "unix::gid", 0);
+  g_file_info_set_attribute_uint32 (info, "unix::mode", S_IFREG | 0644);
+
+  memstream = g_memory_input_stream_new_from_bytes (bytes);
+
+  if (!ostree_raw_file_to_content_stream (memstream, info, NULL,
+                                          &content_stream, &length,
+                                          cancellable, error))
+    return FALSE;
+
+  if (!ostree_repo_write_content (repo, NULL, content_stream, length,
+                                  &raw_checksum, cancellable, error))
+    return FALSE;
+
+  checksum = ostree_checksum_from_bytes (raw_checksum);
+
+  if (!ostree_mutable_tree_replace_file (parent, filename, checksum, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+gboolean
+flatpak_mtree_ensure_dir_metadata (OstreeRepo        *repo,
+                                   OstreeMutableTree *mtree,
+                                   GCancellable      *cancellable,
+                                   GError           **error)
+{
+  g_autoptr(GVariant) dirmeta = NULL;
+  g_autoptr(GFileInfo) file_info = g_file_info_new ();
+  g_autofree guchar *csum = NULL;
+  g_autofree char *checksum = NULL;
+
+  g_file_info_set_name (file_info, "/");
+  g_file_info_set_file_type (file_info, G_FILE_TYPE_DIRECTORY);
+  g_file_info_set_attribute_uint32 (file_info, "unix::uid", 0);
+  g_file_info_set_attribute_uint32 (file_info, "unix::gid", 0);
+  g_file_info_set_attribute_uint32 (file_info, "unix::mode", 040755);
+
+  dirmeta = ostree_create_directory_metadata (file_info, NULL);
+  if (!ostree_repo_write_metadata (repo, OSTREE_OBJECT_TYPE_DIR_META, NULL,
+                                   dirmeta, &csum, cancellable, error))
+    return FALSE;
+
+  checksum = ostree_checksum_from_bytes (csum);
+  ostree_mutable_tree_set_metadata_checksum (mtree, checksum);
+
+  return TRUE;
+}
+
+static gboolean
+validate_component (FlatpakXml *component,
+                    const char *ref,
+                    const char *id,
+                    char      **tags,
+                    const char *runtime,
+                    const char *sdk)
+{
+  FlatpakXml *bundle, *text, *prev, *id_node, *id_text_node, *metadata, *value;
+  g_autofree char *id_text = NULL;
+  int i;
+
+  if (g_strcmp0 (component->element_name, "component") != 0)
+    return FALSE;
+
+  id_node = flatpak_xml_find (component, "id", NULL);
+  if (id_node == NULL)
+    return FALSE;
+
+  id_text_node = flatpak_xml_find (id_node, NULL, NULL);
+  if (id_text_node == NULL || id_text_node->text == NULL)
+    return FALSE;
+
+  id_text = g_strstrip (g_strdup (id_text_node->text));
+
+  /* Drop .desktop file suffix (unless the actual app id ends with .desktop) */
+  if (g_str_has_suffix (id_text, ".desktop") &&
+      !g_str_has_suffix (id, ".desktop"))
+    id_text[strlen (id_text) - strlen (".desktop")] = 0;
+
+  if (!g_str_has_prefix (id_text, id))
+    {
+      g_warning ("Invalid id %s", id_text);
+      return FALSE;
+    }
+
+  while ((bundle = flatpak_xml_find (component, "bundle", &prev)) != NULL)
+    flatpak_xml_free (flatpak_xml_unlink (component, bundle));
+
+  bundle = flatpak_xml_new ("bundle");
+  bundle->attribute_names = g_new0 (char *, 2 * 4);
+  bundle->attribute_values = g_new0 (char *, 2 * 4);
+  bundle->attribute_names[0] = g_strdup ("type");
+  bundle->attribute_values[0] = g_strdup ("flatpak");
+
+  i = 1;
+  if (runtime && !g_str_has_prefix (runtime, "runtime/"))
+    {
+      bundle->attribute_names[i] = g_strdup ("runtime");
+      bundle->attribute_values[i] = g_strdup (runtime);
+      i++;
+    }
+
+  if (sdk)
+    {
+      bundle->attribute_names[i] = g_strdup ("sdk");
+      bundle->attribute_values[i] = g_strdup (sdk);
+      i++;
+    }
+
+  text = flatpak_xml_new (NULL);
+  text->text = g_strdup (ref);
+  flatpak_xml_add (bundle, text);
+
+  flatpak_xml_add (component, flatpak_xml_new_text ("  "));
+  flatpak_xml_add (component, bundle);
+  flatpak_xml_add (component, flatpak_xml_new_text ("\n  "));
+
+  if (tags != NULL && tags[0] != NULL)
+    {
+      metadata = flatpak_xml_find (component, "metadata", NULL);
+      if (metadata == NULL)
+        {
+          metadata = flatpak_xml_new ("metadata");
+          metadata->attribute_names = g_new0 (char *, 1);
+          metadata->attribute_values = g_new0 (char *, 1);
+
+          flatpak_xml_add (component, flatpak_xml_new_text ("  "));
+          flatpak_xml_add (component, metadata);
+          flatpak_xml_add (component, flatpak_xml_new_text ("\n  "));
+        }
+
+      value = flatpak_xml_new ("value");
+      value->attribute_names = g_new0 (char *, 2);
+      value->attribute_values = g_new0 (char *, 2);
+      value->attribute_names[0] = g_strdup ("key");
+      value->attribute_values[0] = g_strdup ("X-Flatpak-Tags");
+      flatpak_xml_add (metadata, flatpak_xml_new_text ("\n       "));
+      flatpak_xml_add (metadata, value);
+      flatpak_xml_add (metadata, flatpak_xml_new_text ("\n    "));
+
+      text = flatpak_xml_new (NULL);
+      text->text = g_strjoinv (",", tags);
+      flatpak_xml_add (value, text);
+    }
+
+  return TRUE;
+}
+
+gboolean
+flatpak_appstream_xml_migrate (FlatpakXml *source,
+                               FlatpakXml *dest,
+                               const char *ref,
+                               const char *id,
+                               GKeyFile   *metadata)
+{
+  FlatpakXml *source_components;
+  FlatpakXml *dest_components;
+  FlatpakXml *component;
+  FlatpakXml *prev_component;
+  gboolean migrated = FALSE;
+  g_auto(GStrv) tags = NULL;
+  g_autofree const char *runtime = NULL;
+  g_autofree const char *sdk = NULL;
+  const char *group;
+
+  if (source->first_child == NULL ||
+      source->first_child->next_sibling != NULL ||
+      g_strcmp0 (source->first_child->element_name, "components") != 0)
+    return FALSE;
+
+  if (g_str_has_prefix (ref, "app/"))
+    group = FLATPAK_METADATA_GROUP_APPLICATION;
+  else
+    group = FLATPAK_METADATA_GROUP_RUNTIME;
+
+  tags = g_key_file_get_string_list (metadata, group, FLATPAK_METADATA_KEY_TAGS,
+                                     NULL, NULL);
+  runtime = g_key_file_get_string (metadata, group,
+                                   FLATPAK_METADATA_KEY_RUNTIME, NULL);
+  sdk = g_key_file_get_string (metadata, group, FLATPAK_METADATA_KEY_SDK, NULL);
+
+  source_components = source->first_child;
+  dest_components = dest->first_child;
+
+  component = source_components->first_child;
+  prev_component = NULL;
+  while (component != NULL)
+    {
+      FlatpakXml *next = component->next_sibling;
+
+      if (validate_component (component, ref, id, tags, runtime, sdk))
+        {
+          flatpak_xml_add (dest_components,
+                           flatpak_xml_unlink (component, prev_component));
+          migrated = TRUE;
+        }
+      else
+        {
+          prev_component = component;
+        }
+
+      component = next;
+    }
+
+  return migrated;
+}
+
+static gboolean
+copy_icon (const char        *id,
+           GFile             *icons_dir,
+           OstreeRepo        *repo,
+           OstreeMutableTree *size_mtree,
+           const char        *size,
+           GError           **error)
+{
+  g_autofree char *icon_name = g_strconcat (id, ".png", NULL);
+  g_autoptr(GFile) size_dir = g_file_get_child (icons_dir, size);
+  g_autoptr(GFile) icon_file = g_file_get_child (size_dir, icon_name);
+  const char *checksum;
+
+  if (!ostree_repo_file_ensure_resolved (OSTREE_REPO_FILE(icon_file), NULL))
+    {
+      g_debug ("No icon at size %s for %s", size, id);
+      return TRUE;
+    }
+
+  checksum = ostree_repo_file_get_checksum (OSTREE_REPO_FILE(icon_file));
+  if (!ostree_mutable_tree_replace_file (size_mtree, icon_name, checksum, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+static gboolean
+extract_appstream (OstreeRepo   *repo,
+                   FlatpakXml   *appstream_root,
+                   const char   *ref,
+                   const char   *id,
+                   OstreeMutableTree *size1_mtree,
+                   OstreeMutableTree *size2_mtree,
+                   GCancellable *cancellable,
+                   GError      **error)
+{
+  g_autoptr(GFile) root = NULL;
+  g_autoptr(GFile) app_info_dir = NULL;
+  g_autoptr(GFile) xmls_dir = NULL;
+  g_autoptr(GFile) icons_dir = NULL;
+  g_autoptr(GFile) appstream_file = NULL;
+  g_autoptr(GFile) metadata = NULL;
+  g_autofree char *appstream_basename = NULL;
+  g_autoptr(GInputStream) in = NULL;
+  g_autoptr(FlatpakXml) xml_root = NULL;
+  g_autoptr(GKeyFile) keyfile = NULL;
+
+  if (!ostree_repo_read_commit (repo, ref, &root, NULL, NULL, error))
+    return FALSE;
+
+  keyfile = g_key_file_new ();
+  metadata = g_file_get_child (root, "metadata");
+  if (g_file_query_exists (metadata, cancellable))
+    {
+      g_autofree char *content = NULL;
+      gsize len;
+
+      if (!g_file_load_contents (metadata, cancellable, &content, &len, NULL, error))
+        return FALSE;
+
+      if (!g_key_file_load_from_data (keyfile, content, len, G_KEY_FILE_NONE, error))
+        return FALSE;
+    }
+
+  app_info_dir = g_file_resolve_relative_path (root, "files/share/app-info");
+
+  xmls_dir = g_file_resolve_relative_path (app_info_dir, "xmls");
+  icons_dir = g_file_resolve_relative_path (app_info_dir, "icons/flatpak");
+
+  appstream_basename = g_strconcat (id, ".xml.gz", NULL);
+  appstream_file = g_file_get_child (xmls_dir, appstream_basename);
+
+  in = (GInputStream *) g_file_read (appstream_file, cancellable, error);
+  if (!in)
+    return FALSE;
+
+  xml_root = flatpak_xml_parse (in, TRUE, cancellable, error);
+  if (xml_root == NULL)
+    return FALSE;
+
+  if (flatpak_appstream_xml_migrate (xml_root, appstream_root,
+                                     ref, id, keyfile))
+    {
+      g_autoptr(GError) my_error = NULL;
+      FlatpakXml *components = appstream_root->first_child;
+      FlatpakXml *component = components->first_child;
+
+      while (component != NULL)
+        {
+          FlatpakXml *component_id, *component_id_text_node;
+          g_autofree char *component_id_text = NULL;
+          char *component_id_suffix;
+
+          if (g_strcmp0 (component->element_name, "component") != 0)
+            {
+              component = component->next_sibling;
+              continue;
+            }
+
+          component_id = flatpak_xml_find (component, "id", NULL);
+          component_id_text_node = flatpak_xml_find (component_id, NULL, NULL);
+
+          component_id_text = g_strstrip (g_strdup (component_id_text_node->text));
+
+          /* We're looking for a component that matches the app-id (id), but it
+             may have some further elements (separated by dot) and can also have
+             ".desktop" at the end which we need to strip out. Further complicating
+             things, some actual app ids ends in .desktop, such as org.telegram.desktop. */
+
+          component_id_suffix = component_id_text + strlen (id); /* Don't deref before we check for prefix match! */
+          if (!g_str_has_prefix (component_id_text, id) ||
+              (component_id_suffix[0] != 0 && component_id_suffix[0] != '.'))
+            {
+              component = component->next_sibling;
+              continue;
+            }
+
+          if (g_str_has_suffix (component_id_suffix, ".desktop"))
+            component_id_suffix[strlen (component_id_suffix) - strlen (".desktop")] = 0;
+
+          if (!copy_icon (component_id_text, icons_dir, repo, size1_mtree, "64x64", &my_error))
+            {
+              g_print (_("Error copying 64x64 icon for component %s: %s\n"), component_id_text, my_error->message);
+              g_clear_error (&my_error);
+            }
+
+          if (!copy_icon (component_id_text, icons_dir, repo, size2_mtree, "128x128", &my_error))
+             {
+               g_print (_("Error copying 128x128 icon for component %s: %s\n"), component_id_text, my_error->message);
+               g_clear_error (&my_error);
+             }
+
+
+          /* We might match other prefixes, so keep on going */
+          component = component->next_sibling;
+        }
+    }
+
+  return TRUE;
+}
+
+FlatpakXml *
+flatpak_appstream_xml_new (void)
+{
+  FlatpakXml *appstream_root = NULL;
+  FlatpakXml *appstream_components;
+
+  appstream_root = flatpak_xml_new ("root");
+  appstream_components = flatpak_xml_new ("components");
+  flatpak_xml_add (appstream_root, appstream_components);
+  flatpak_xml_add (appstream_components, flatpak_xml_new_text ("\n  "));
+
+  appstream_components->attribute_names = g_new0 (char *, 3);
+  appstream_components->attribute_values = g_new0 (char *, 3);
+  appstream_components->attribute_names[0] = g_strdup ("version");
+  appstream_components->attribute_values[0] = g_strdup ("0.8");
+  appstream_components->attribute_names[1] = g_strdup ("origin");
+  appstream_components->attribute_values[1] = g_strdup ("flatpak");
+
+  return appstream_root;
+}
+
+gboolean
+flatpak_appstream_xml_root_to_data (FlatpakXml *appstream_root,
+                                    GBytes    **uncompressed,
+                                    GBytes    **compressed,
+                                    GError    **error)
+{
+  g_autoptr(GString) xml = NULL;
+  g_autoptr(GZlibCompressor) compressor = NULL;
+  g_autoptr(GOutputStream) out2 = NULL;
+  g_autoptr(GOutputStream) out = NULL;
+
+  flatpak_xml_add (appstream_root->first_child, flatpak_xml_new_text ("\n"));
+
+  xml = g_string_new ("");
+  flatpak_xml_to_string (appstream_root, xml);
+
+  if (compressed)
+    {
+      compressor = g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP, -1);
+      out = g_memory_output_stream_new_resizable ();
+      out2 = g_converter_output_stream_new (out, G_CONVERTER (compressor));
+      if (!g_output_stream_write_all (out2, xml->str, xml->len,
+                                      NULL, NULL, error))
+        return FALSE;
+      if (!g_output_stream_close (out2, NULL, error))
+        return FALSE;
+    }
+
+  if (uncompressed)
+    *uncompressed = g_string_free_to_bytes (g_steal_pointer (&xml));
+
+  if (compressed)
+    *compressed = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (out));
+
+  return TRUE;
+}
+
+void
+flatpak_appstream_xml_filter (FlatpakXml *appstream,
+                              GRegex *allow_refs,
+                              GRegex *deny_refs)
+{
+  FlatpakXml *components;
+  FlatpakXml *component;
+  FlatpakXml *prev_component, *old;
+
+  for (components = appstream->first_child;
+       components != NULL;
+       components = components->next_sibling)
+    {
+      if (g_strcmp0 (components->element_name, "components") != 0)
+        continue;
+
+
+      prev_component = NULL;
+      component = components->first_child;
+      while (component != NULL)
+        {
+          FlatpakXml *bundle;
+          gboolean allow = FALSE;
+
+          if (g_strcmp0 (component->element_name, "component") == 0)
+            {
+              bundle = flatpak_xml_find (component, "bundle", NULL);
+              if (bundle && bundle->first_child && bundle->first_child->text)
+                allow = flatpak_filters_allow_ref (allow_refs, deny_refs, bundle->first_child->text);
+            }
+
+          if (allow)
+            {
+              prev_component = component;
+              component = component->next_sibling;
+            }
+          else
+            {
+              old = component;
+
+              /* prev_component is same as before */
+              component = component->next_sibling;
+
+              flatpak_xml_unlink (old, prev_component);
+              flatpak_xml_free (old);
+            }
+        }
+    }
+}
+
+
+gboolean
+flatpak_repo_generate_appstream (OstreeRepo   *repo,
+                                 const char  **gpg_key_ids,
+                                 const char   *gpg_homedir,
+                                 guint64       timestamp,
+                                 GCancellable *cancellable,
+                                 GError      **error)
+{
+  g_autoptr(GHashTable) all_refs = NULL;
+  g_autofree const char **all_refs_keys = NULL;
+  guint n_keys;
+  gsize i;
+  g_autoptr(GHashTable) arches = NULL;  /* (element-type utf8 utf8) */
+  const char *collection_id;
+
+  arches = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+  collection_id = ostree_repo_get_collection_id (repo);
+
+  if (!ostree_repo_list_refs (repo,
+                              NULL,
+                              &all_refs,
+                              cancellable,
+                              error))
+    return FALSE;
+
+  all_refs_keys = (const char **) g_hash_table_get_keys_as_array (all_refs, &n_keys);
+
+  /* Sort refs so that appdata order is stable for e.g. deltas */
+  g_qsort_with_data (all_refs_keys, n_keys, sizeof (char *), (GCompareDataFunc) flatpak_strcmp0_ptr, NULL);
+
+  for (i = 0; i < n_keys; i++)
+    {
+      const char *ref = all_refs_keys[i];
+      g_auto(GStrv) split = NULL;
+      const char *arch;
+
+      split = flatpak_decompose_ref (ref, NULL);
+      if (!split)
+        continue;
+
+      arch = split[2];
+      if (!g_hash_table_contains (arches, arch))
+        {
+          const char *reverse_compat_arch;
+          g_hash_table_add (arches, g_strdup (arch));
+
+          /* If repo contains e.g. i386, also generated x86-64 appdata */
+          reverse_compat_arch = flatpak_get_compat_arch_reverse (arch);
+          if (reverse_compat_arch)
+            g_hash_table_add (arches, g_strdup (reverse_compat_arch));
+        }
+    }
+
+  GLNX_HASH_TABLE_FOREACH (arches, const char *, arch)
+  {
+    OstreeRepoTransactionStats stats;
+    g_autoptr(FlatpakXml) appstream_root = NULL;
+    g_autoptr(GBytes) xml_data = NULL;
+    g_autoptr(GBytes) xml_gz_data = NULL;
+    g_autoptr(OstreeMutableTree) mtree = ostree_mutable_tree_new ();
+    g_autoptr(OstreeMutableTree) icons_mtree = NULL;
+    g_autoptr(OstreeMutableTree) icons_flatpak_mtree = NULL;
+    g_autoptr(OstreeMutableTree) size1_mtree = NULL;
+    g_autoptr(OstreeMutableTree) size2_mtree = NULL;
+    const char *compat_arch;
+    g_autoptr(FlatpakRepoTransaction) transaction = NULL;
+    compat_arch = flatpak_get_compat_arch (arch);
+    const char *branch_names[] = { "appstream", "appstream2" };
+
+    if (!flatpak_mtree_ensure_dir_metadata (repo, mtree, cancellable, error))
+      return FALSE;
+
+    if (!flatpak_mtree_create_dir (repo, mtree, "icons", &icons_mtree, error))
+      return FALSE;
+
+    if (!flatpak_mtree_create_dir (repo, icons_mtree, "64x64", &size1_mtree, error))
+      return FALSE;
+
+    if (!flatpak_mtree_create_dir (repo, icons_mtree, "128x128", &size2_mtree, error))
+      return FALSE;
+
+    /* For compatibility with libappstream we create a $origin ("flatpak") subdirectory with symlinks
+     * to the size directories thus matching the standard merged appstream layout if we assume the
+     * appstream has origin=flatpak, which flatpak-builder creates.
+     *
+     * See https://github.com/ximion/appstream/pull/224 for details.
+     */
+    if (!flatpak_mtree_create_dir (repo, icons_mtree, "flatpak", &icons_flatpak_mtree, error))
+      return FALSE;
+    if (!flatpak_mtree_create_symlink (repo, icons_flatpak_mtree, "64x64", "../64x64", error))
+      return FALSE;
+    if (!flatpak_mtree_create_symlink (repo, icons_flatpak_mtree, "128x128", "../128x128", error))
+      return FALSE;
+
+    appstream_root = flatpak_appstream_xml_new ();
+
+    for (i = 0; i < n_keys; i++)
+      {
+        const char *ref = all_refs_keys[i];
+        const char *commit;
+        g_autoptr(GVariant) commit_v = NULL;
+        g_autoptr(GVariant) commit_metadata = NULL;
+        g_auto(GStrv) split = NULL;
+        g_autoptr(GError) my_error = NULL;
+        const char *eol = NULL;
+        const char *eol_rebase = NULL;
+
+        split = flatpak_decompose_ref (ref, NULL);
+        if (!split)
+          continue;
+
+        if (strcmp (split[2], arch) != 0)
+          {
+            g_autofree char *main_ref = NULL;
+            /* Include refs that don't match the main arch (e.g. x86_64), if they match
+               the compat arch (e.g. i386) and the main arch version is not in the repo */
+            if (g_strcmp0 (split[2], compat_arch) == 0)
+              main_ref = g_strdup_printf ("%s/%s/%s/%s",
+                                          split[0], split[1], arch, split[3]);
+            if (main_ref == NULL ||
+                g_hash_table_lookup (all_refs, main_ref))
+              continue;
+          }
+
+        commit = g_hash_table_lookup (all_refs, ref);
+
+        if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, commit,
+                                       &commit_v, NULL))
+          {
+            g_warning ("Couldn't load commit %s (ref %s)", commit, ref);
+            continue;
+          }
+
+        commit_metadata = g_variant_get_child_value (commit_v, 0);
+        g_variant_lookup (commit_metadata, OSTREE_COMMIT_META_KEY_ENDOFLIFE, "&s", &eol);
+        g_variant_lookup (commit_metadata, OSTREE_COMMIT_META_KEY_ENDOFLIFE_REBASE, "&s", &eol_rebase);
+        if (eol || eol_rebase)
+          {
+            g_print (_("%s is end-of-life, ignoring\n"), ref);
+            continue;
+          }
+
+        if (!extract_appstream (repo, appstream_root,
+                                ref, split[1], size1_mtree, size2_mtree,
+                                cancellable, &my_error))
+          {
+            if (g_str_has_prefix (ref, "app/"))
+              g_print (_("No appstream data for %s: %s\n"), ref, my_error->message);
+            continue;
+          }
+      }
+
+    if (!flatpak_appstream_xml_root_to_data (appstream_root, &xml_data, &xml_gz_data, error))
+      return FALSE;
+
+    transaction = flatpak_repo_transaction_start (repo, cancellable, error);
+    if (transaction == NULL)
+      return FALSE;
+
+    for (i = 0; i < G_N_ELEMENTS (branch_names); i++)
+      {
+        gboolean skip_commit = FALSE;
+        const char *branch_prefix = branch_names[i];
+        g_autoptr(GFile) root = NULL;
+        g_autofree char *branch = NULL;
+        g_autofree char *parent = NULL;
+        g_autofree char *commit_checksum = NULL;
+
+        branch = g_strdup_printf ("%s/%s", branch_prefix, arch);
+        if (!flatpak_repo_resolve_rev (repo, collection_id, NULL, branch, TRUE,
+                                       &parent, cancellable, error))
+          return FALSE;
+
+        if (i == 0)
+          {
+            if (!flatpak_mtree_add_file_from_bytes (repo, xml_gz_data, mtree, "appstream.xml.gz", cancellable, error))
+              return FALSE;
+          }
+        else
+          {
+            if (!ostree_mutable_tree_remove (mtree, "appstream.xml.gz", TRUE, error))
+              return FALSE;
+
+            if (!flatpak_mtree_add_file_from_bytes (repo, xml_data, mtree, "appstream.xml", cancellable, error))
+              return FALSE;
+          }
+
+        if (!ostree_repo_write_mtree (repo, mtree, &root, cancellable, error))
+          return FALSE;
+
+        /* No need to commit if nothing changed */
+        if (parent)
+          {
+            g_autoptr(GFile) parent_root = NULL;
+
+            if (!ostree_repo_read_commit (repo, parent, &parent_root, NULL, cancellable, error))
+              return FALSE;
+
+            if (g_file_equal (root, parent_root))
+              {
+                skip_commit = TRUE;
+                g_debug ("Not updating %s, no change", branch);
+              }
+          }
+
+        if (!skip_commit)
+          {
+            g_autoptr(GVariantDict) metadata_dict = NULL;
+            g_autoptr(GVariant) metadata = NULL;
+
+            /* Add bindings to the metadata. Do this even if P2P support is not
+             * enabled, as it might be enable for other flatpak builds. */
+            metadata_dict = g_variant_dict_new (NULL);
+            g_variant_dict_insert (metadata_dict, "ostree.collection-binding",
+                                   "s", (collection_id != NULL) ? collection_id : "");
+            g_variant_dict_insert_value (metadata_dict, "ostree.ref-binding",
+                                         g_variant_new_strv ((const gchar * const *) &branch, 1));
+            metadata = g_variant_ref_sink (g_variant_dict_end (metadata_dict));
+
+            if (timestamp > 0)
+              {
+                if (!ostree_repo_write_commit_with_time (repo, parent, "Update", NULL, metadata,
+                                                         OSTREE_REPO_FILE (root),
+                                                         timestamp,
+                                                         &commit_checksum,
+                                                         cancellable, error))
+                  return FALSE;
+              }
+            else
+              {
+                if (!ostree_repo_write_commit (repo, parent, "Update", NULL, metadata,
+                                               OSTREE_REPO_FILE (root),
+                                               &commit_checksum, cancellable, error))
+                  return FALSE;
+              }
+
+            if (gpg_key_ids)
+              {
+                int i;
+
+                for (i = 0; gpg_key_ids[i] != NULL; i++)
+                  {
+                    const char *keyid = gpg_key_ids[i];
+
+                    if (!ostree_repo_sign_commit (repo,
+                                                  commit_checksum,
+                                                  keyid,
+                                                  gpg_homedir,
+                                                  cancellable,
+                                                  error))
+                      return FALSE;
+                  }
+              }
+
+            if (collection_id != NULL)
+              {
+                const OstreeCollectionRef collection_ref = { (char *) collection_id, branch };
+                ostree_repo_transaction_set_collection_ref (repo, &collection_ref, commit_checksum);
+              }
+            else
+              {
+                ostree_repo_transaction_set_ref (repo, NULL, branch, commit_checksum);
+              }
+          }
+      }
+
+    if (!ostree_repo_commit_transaction (repo, &stats, cancellable, error))
+      return FALSE;
+  }
+
+  return TRUE;
+}
+
+void
+flatpak_extension_free (FlatpakExtension *extension)
+{
+  g_free (extension->id);
+  g_free (extension->installed_id);
+  g_free (extension->commit);
+  g_free (extension->ref);
+  g_free (extension->directory);
+  g_free (extension->files_path);
+  g_free (extension->add_ld_path);
+  g_free (extension->subdir_suffix);
+  g_strfreev (extension->merge_dirs);
+  g_free (extension);
+}
+
+static int
+flatpak_extension_compare (gconstpointer _a,
+                           gconstpointer _b)
+{
+  const FlatpakExtension *a = _a;
+  const FlatpakExtension *b = _b;
+
+  return b->priority - a->priority;
+}
+
+static FlatpakExtension *
+flatpak_extension_new (const char *id,
+                       const char *extension,
+                       const char *ref,
+                       const char *directory,
+                       const char *add_ld_path,
+                       const char *subdir_suffix,
+                       char      **merge_dirs,
+                       GFile      *files,
+                       GFile      *deploy_dir,
+                       gboolean    is_unmaintained)
+{
+  FlatpakExtension *ext = g_new0 (FlatpakExtension, 1);
+  g_autoptr(GBytes) deploy_data = NULL;
+
+  ext->id = g_strdup (id);
+  ext->installed_id = g_strdup (extension);
+  ext->ref = g_strdup (ref);
+  ext->directory = g_strdup (directory);
+  ext->files_path = g_file_get_path (files);
+  ext->add_ld_path = g_strdup (add_ld_path);
+  ext->subdir_suffix = g_strdup (subdir_suffix);
+  ext->merge_dirs = g_strdupv (merge_dirs);
+  ext->is_unmaintained = is_unmaintained;
+
+  if (deploy_dir)
+    {
+      deploy_data = flatpak_load_deploy_data (deploy_dir, ref, FLATPAK_DEPLOY_VERSION_ANY, NULL, NULL);
+      if (deploy_data)
+        ext->commit = g_strdup (flatpak_deploy_data_get_commit (deploy_data));
+    }
+
+  if (is_unmaintained)
+    ext->priority = 1000;
+  else
+    {
+      g_autoptr(GKeyFile) keyfile = g_key_file_new ();
+      g_autofree char *metadata_path = g_build_filename (ext->files_path, "../metadata", NULL);
+
+      if (g_key_file_load_from_file (keyfile, metadata_path, G_KEY_FILE_NONE, NULL))
+        ext->priority = g_key_file_get_integer (keyfile,
+                                                FLATPAK_METADATA_GROUP_EXTENSION_OF,
+                                                FLATPAK_METADATA_KEY_PRIORITY,
+                                                NULL);
+    }
+
+  return ext;
+}
+
+gboolean
+flatpak_extension_matches_reason (const char *extension_id,
+                                  const char *reasons,
+                                  gboolean    default_value)
+{
+  const char *extension_basename;
+  g_auto(GStrv) reason_list = NULL;
+  size_t i;
+
+  if (reasons == NULL || *reasons == 0)
+    return default_value;
+
+  extension_basename = strrchr (extension_id, '.');
+  if (extension_basename == NULL)
+    return FALSE;
+  extension_basename += 1;
+
+  reason_list = g_strsplit (reasons, ";", -1);
+
+  for (i = 0; reason_list[i]; ++i)
+    {
+      const char *reason = reason_list[i];
+
+      if (strcmp (reason, "active-gl-driver") == 0)
+        {
+          /* handled below */
+          const char **gl_drivers = flatpak_get_gl_drivers ();
+          size_t j;
+
+          for (j = 0; gl_drivers[j]; j++)
+            {
+              if (strcmp (gl_drivers[j], extension_basename) == 0)
+                return TRUE;
+            }
+        }
+      else if (strcmp (reason, "active-gtk-theme") == 0)
+        {
+          const char *gtk_theme = flatpak_get_gtk_theme ();
+          if (strcmp (gtk_theme, extension_basename) == 0)
+            return TRUE;
+        }
+      else if (strcmp (reason, "have-intel-gpu") == 0)
+        {
+          /* Used for Intel VAAPI driver extension */
+          if (flatpak_get_have_intel_gpu ())
+            return TRUE;
+        }
+      else if (g_str_has_prefix (reason, "on-xdg-desktop-"))
+        {
+          const char *desktop_name = reason + strlen ("on-xdg-desktop-");
+          const char *current_desktop_var = g_getenv ("XDG_CURRENT_DESKTOP");
+          g_auto(GStrv) current_desktop_names = NULL;
+          size_t j;
+
+          if (!current_desktop_var)
+            continue;
+
+          current_desktop_names = g_strsplit (current_desktop_var, ":", -1);
+
+          for (j = 0; current_desktop_names[j]; ++j)
+            {
+              if (g_ascii_strcasecmp (desktop_name, current_desktop_names[j]) == 0)
+                return TRUE;
+            }
+        }
+    }
+
+  return FALSE;
+}
+
+static GList *
+add_extension (GKeyFile   *metakey,
+               const char *group,
+               const char *extension,
+               const char *arch,
+               const char *branch,
+               GList      *res)
+{
+  FlatpakExtension *ext;
+  g_autofree char *directory = g_key_file_get_string (metakey, group,
+                                                      FLATPAK_METADATA_KEY_DIRECTORY,
+                                                      NULL);
+  g_autofree char *add_ld_path = g_key_file_get_string (metakey, group,
+                                                        FLATPAK_METADATA_KEY_ADD_LD_PATH,
+                                                        NULL);
+  g_auto(GStrv) merge_dirs = g_key_file_get_string_list (metakey, group,
+                                                         FLATPAK_METADATA_KEY_MERGE_DIRS,
+                                                         NULL, NULL);
+  g_autofree char *enable_if = g_key_file_get_string (metakey, group,
+                                                      FLATPAK_METADATA_KEY_ENABLE_IF,
+                                                      NULL);
+  g_autofree char *subdir_suffix = g_key_file_get_string (metakey, group,
+                                                          FLATPAK_METADATA_KEY_SUBDIRECTORY_SUFFIX,
+                                                          NULL);
+  g_autofree char *ref = NULL;
+  gboolean is_unmaintained = FALSE;
+  g_autoptr(GFile) files = NULL;
+  g_autoptr(GFile) deploy_dir = NULL;
+
+  if (directory == NULL)
+    return res;
+
+  ref = g_build_filename ("runtime", extension, arch, branch, NULL);
+
+  files = flatpak_find_unmaintained_extension_dir_if_exists (extension, arch, branch, NULL);
+
+  if (files == NULL)
+    {
+      deploy_dir = flatpak_find_deploy_dir_for_ref (ref, NULL, NULL, NULL);
+      if (deploy_dir)
+        files = g_file_get_child (deploy_dir, "files");
+    }
+  else
+    is_unmaintained = TRUE;
+
+  /* Prefer a full extension (org.freedesktop.Locale) over subdirectory ones (org.freedesktop.Locale.sv) */
+  if (files != NULL)
+    {
+      if (flatpak_extension_matches_reason (extension, enable_if, TRUE))
+        {
+          ext = flatpak_extension_new (extension, extension, ref, directory, add_ld_path, subdir_suffix, merge_dirs, files, deploy_dir, is_unmaintained);
+          res = g_list_prepend (res, ext);
+        }
+    }
+  else if (g_key_file_get_boolean (metakey, group,
+                                   FLATPAK_METADATA_KEY_SUBDIRECTORIES, NULL))
+    {
+      g_autofree char *prefix = g_strconcat (extension, ".", NULL);
+      g_auto(GStrv) refs = NULL;
+      g_auto(GStrv) unmaintained_refs = NULL;
+      int j;
+
+      refs = flatpak_list_deployed_refs ("runtime", prefix, arch, branch,
+                                         NULL, NULL);
+      for (j = 0; refs != NULL && refs[j] != NULL; j++)
+        {
+          g_autofree char *extended_dir = g_build_filename (directory, refs[j] + strlen (prefix), NULL);
+          g_autofree char *dir_ref = g_build_filename ("runtime", refs[j], arch, branch, NULL);
+          g_autoptr(GFile) subdir_deploy_dir = NULL;
+          g_autoptr(GFile) subdir_files = NULL;
+          subdir_deploy_dir = flatpak_find_deploy_dir_for_ref (dir_ref, NULL, NULL, NULL);
+          if (subdir_deploy_dir)
+            subdir_files = g_file_get_child (subdir_deploy_dir, "files");
+
+          if (subdir_files && flatpak_extension_matches_reason (refs[j], enable_if, TRUE))
+            {
+              ext = flatpak_extension_new (extension, refs[j], dir_ref, extended_dir, add_ld_path, subdir_suffix, merge_dirs, subdir_files, subdir_deploy_dir, FALSE);
+              ext->needs_tmpfs = TRUE;
+              res = g_list_prepend (res, ext);
+            }
+        }
+
+      unmaintained_refs = flatpak_list_unmaintained_refs (prefix, arch, branch,
+                                                          NULL, NULL);
+      for (j = 0; unmaintained_refs != NULL && unmaintained_refs[j] != NULL; j++)
+        {
+          g_autofree char *extended_dir = g_build_filename (directory, unmaintained_refs[j] + strlen (prefix), NULL);
+          g_autofree char *dir_ref = g_build_filename ("runtime", unmaintained_refs[j], arch, branch, NULL);
+          g_autoptr(GFile) subdir_files = flatpak_find_unmaintained_extension_dir_if_exists (unmaintained_refs[j], arch, branch, NULL);
+
+          if (subdir_files && flatpak_extension_matches_reason (unmaintained_refs[j], enable_if, TRUE))
+            {
+              ext = flatpak_extension_new (extension, unmaintained_refs[j], dir_ref, extended_dir, add_ld_path, subdir_suffix, merge_dirs, subdir_files, NULL, TRUE);
+              ext->needs_tmpfs = TRUE;
+              res = g_list_prepend (res, ext);
+            }
+        }
+    }
+
+  return res;
+}
+
+void
+flatpak_parse_extension_with_tag (const char *extension,
+                                  char      **name,
+                                  char      **tag)
+{
+  const char *tag_chr = strchr (extension, '@');
+
+  if (tag_chr)
+    {
+      if (name != NULL)
+        *name = g_strndup (extension, tag_chr - extension);
+
+      /* Everything after the @ */
+      if (tag != NULL)
+        *tag = g_strdup (tag_chr + 1);
+
+      return;
+    }
+
+  if (name != NULL)
+    *name = g_strdup (extension);
+
+  if (tag != NULL)
+    *tag = NULL;
+}
+
+GList *
+flatpak_list_extensions (GKeyFile   *metakey,
+                         const char *arch,
+                         const char *default_branch)
+{
+  g_auto(GStrv) groups = NULL;
+  int i, j;
+  GList *res;
+
+  res = NULL;
+
+  if (arch == NULL)
+    arch = flatpak_get_arch ();
+
+  groups = g_key_file_get_groups (metakey, NULL);
+  for (i = 0; groups[i] != NULL; i++)
+    {
+      char *extension;
+
+      if (g_str_has_prefix (groups[i], FLATPAK_METADATA_GROUP_PREFIX_EXTENSION) &&
+          *(extension = (groups[i] + strlen (FLATPAK_METADATA_GROUP_PREFIX_EXTENSION))) != 0)
+        {
+          g_autofree char *version = g_key_file_get_string (metakey, groups[i],
+                                                            FLATPAK_METADATA_KEY_VERSION,
+                                                            NULL);
+          g_auto(GStrv) versions = g_key_file_get_string_list (metakey, groups[i],
+                                                               FLATPAK_METADATA_KEY_VERSIONS,
+                                                               NULL, NULL);
+          g_autofree char *name = NULL;
+          const char *default_branches[] = { default_branch, NULL};
+          const char **branches;
+
+          flatpak_parse_extension_with_tag (extension, &name, NULL);
+
+          if (versions)
+            branches = (const char **) versions;
+          else
+            {
+              if (version)
+                default_branches[0] = version;
+              branches = default_branches;
+            }
+
+          for (j = 0; branches[j] != NULL; j++)
+            res = add_extension (metakey, groups[i], name, arch, branches[j], res);
+        }
+    }
+
+  return g_list_sort (g_list_reverse (res), flatpak_extension_compare);
+}
+
+typedef struct
+{
+  FlatpakXml *current;
+} XmlData;
+
+FlatpakXml *
+flatpak_xml_new (const gchar *element_name)
+{
+  FlatpakXml *node = g_new0 (FlatpakXml, 1);
+
+  node->element_name = g_strdup (element_name);
+  return node;
+}
+
+FlatpakXml *
+flatpak_xml_new_text (const gchar *text)
+{
+  FlatpakXml *node = g_new0 (FlatpakXml, 1);
+
+  node->text = g_strdup (text);
+  return node;
+}
+
+void
+flatpak_xml_add (FlatpakXml *parent, FlatpakXml *node)
+{
+  node->parent = parent;
+
+  if (parent->first_child == NULL)
+    parent->first_child = node;
+  else
+    parent->last_child->next_sibling = node;
+  parent->last_child = node;
+}
+
+static void
+xml_start_element (GMarkupParseContext *context,
+                   const gchar         *element_name,
+                   const gchar        **attribute_names,
+                   const gchar        **attribute_values,
+                   gpointer             user_data,
+                   GError             **error)
+{
+  XmlData *data = user_data;
+  FlatpakXml *node;
+
+  node = flatpak_xml_new (element_name);
+  node->attribute_names = g_strdupv ((char **) attribute_names);
+  node->attribute_values = g_strdupv ((char **) attribute_values);
+
+  flatpak_xml_add (data->current, node);
+  data->current = node;
+}
+
+static void
+xml_end_element (GMarkupParseContext *context,
+                 const gchar         *element_name,
+                 gpointer             user_data,
+                 GError             **error)
+{
+  XmlData *data = user_data;
+
+  data->current = data->current->parent;
+}
+
+static void
+xml_text (GMarkupParseContext *context,
+          const gchar         *text,
+          gsize                text_len,
+          gpointer             user_data,
+          GError             **error)
+{
+  XmlData *data = user_data;
+  FlatpakXml *node;
+
+  node = flatpak_xml_new (NULL);
+  node->text = g_strndup (text, text_len);
+  flatpak_xml_add (data->current, node);
+}
+
+static void
+xml_passthrough (GMarkupParseContext *context,
+                 const gchar         *passthrough_text,
+                 gsize                text_len,
+                 gpointer             user_data,
+                 GError             **error)
+{
+}
+
+static GMarkupParser xml_parser = {
+  xml_start_element,
+  xml_end_element,
+  xml_text,
+  xml_passthrough,
+  NULL
+};
+
+void
+flatpak_xml_free (FlatpakXml *node)
+{
+  FlatpakXml *child;
+
+  if (node == NULL)
+    return;
+
+  child = node->first_child;
+  while (child != NULL)
+    {
+      FlatpakXml *next = child->next_sibling;
+      flatpak_xml_free (child);
+      child = next;
+    }
+
+  g_free (node->element_name);
+  g_free (node->text);
+  g_strfreev (node->attribute_names);
+  g_strfreev (node->attribute_values);
+  g_free (node);
+}
+
+
+void
+flatpak_xml_to_string (FlatpakXml *node, GString *res)
+{
+  int i;
+  FlatpakXml *child;
+
+  if (node->parent == NULL)
+    g_string_append (res, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+
+  if (node->element_name)
+    {
+      if (node->parent != NULL)
+        {
+          g_string_append (res, "<");
+          g_string_append (res, node->element_name);
+          if (node->attribute_names)
+            {
+              for (i = 0; node->attribute_names[i] != NULL; i++)
+                {
+                  g_string_append_printf (res, " %s=\"%s\"",
+                                          node->attribute_names[i],
+                                          node->attribute_values[i]);
+                }
+            }
+          if (node->first_child == NULL)
+            g_string_append (res, "/>");
+          else
+            g_string_append (res, ">");
+        }
+
+      child = node->first_child;
+      while (child != NULL)
+        {
+          flatpak_xml_to_string (child, res);
+          child = child->next_sibling;
+        }
+      if (node->parent != NULL)
+        {
+          if (node->first_child != NULL)
+            g_string_append_printf (res, "</%s>", node->element_name);
+        }
+    }
+  else if (node->text)
+    {
+      g_autofree char *escaped = g_markup_escape_text (node->text, -1);
+      g_string_append (res, escaped);
+    }
+}
+
+FlatpakXml *
+flatpak_xml_unlink (FlatpakXml *node,
+                    FlatpakXml *prev_sibling)
+{
+  FlatpakXml *parent = node->parent;
+
+  if (parent == NULL)
+    return node;
+
+  if (parent->first_child == node)
+    parent->first_child = node->next_sibling;
+
+  if (parent->last_child == node)
+    parent->last_child = prev_sibling;
+
+  if (prev_sibling)
+    prev_sibling->next_sibling = node->next_sibling;
+
+  node->parent = NULL;
+  node->next_sibling = NULL;
+
+  return node;
+}
+
+FlatpakXml *
+flatpak_xml_find (FlatpakXml  *node,
+                  const char  *type,
+                  FlatpakXml **prev_child_out)
+{
+  FlatpakXml *child = NULL;
+  FlatpakXml *prev_child = NULL;
+
+  child = node->first_child;
+  prev_child = NULL;
+  while (child != NULL)
+    {
+      FlatpakXml *next = child->next_sibling;
+
+      if (g_strcmp0 (child->element_name, type) == 0)
+        {
+          if (prev_child_out)
+            *prev_child_out = prev_child;
+          return child;
+        }
+
+      prev_child = child;
+      child = next;
+    }
+
+  return NULL;
+}
+
+
+FlatpakXml *
+flatpak_xml_parse (GInputStream *in,
+                   gboolean      compressed,
+                   GCancellable *cancellable,
+                   GError      **error)
+{
+  g_autoptr(GInputStream) real_in = NULL;
+  g_autoptr(FlatpakXml) xml_root = NULL;
+  XmlData data = { 0 };
+  char buffer[32 * 1024];
+  gssize len;
+  g_autoptr(GMarkupParseContext) ctx = NULL;
+
+  if (compressed)
+    {
+      g_autoptr(GZlibDecompressor) decompressor = NULL;
+      decompressor = g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP);
+      real_in = g_converter_input_stream_new (in, G_CONVERTER (decompressor));
+    }
+  else
+    {
+      real_in = g_object_ref (in);
+    }
+
+  xml_root = flatpak_xml_new ("root");
+  data.current = xml_root;
+
+  ctx = g_markup_parse_context_new (&xml_parser,
+                                    G_MARKUP_PREFIX_ERROR_POSITION,
+                                    &data,
+                                    NULL);
+
+  while ((len = g_input_stream_read (real_in, buffer, sizeof (buffer),
+                                     cancellable, error)) > 0)
+    {
+      if (!g_markup_parse_context_parse (ctx, buffer, len, error))
+        return NULL;
+    }
+
+  if (len < 0)
+    return NULL;
+
+  return g_steal_pointer (&xml_root);
+}
+
+#define OSTREE_STATIC_DELTA_META_ENTRY_FORMAT "(uayttay)"
+#define OSTREE_STATIC_DELTA_FALLBACK_FORMAT "(yaytt)"
+#define OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT "(a{sv}tayay" OSTREE_COMMIT_GVARIANT_STRING "aya" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT "a" OSTREE_STATIC_DELTA_FALLBACK_FORMAT ")"
+
+static inline guint64
+maybe_swap_endian_u64 (gboolean swap,
+                       guint64  v)
+{
+  if (!swap)
+    return v;
+  return GUINT64_SWAP_LE_BE (v);
+}
+
+static guint64
+flatpak_bundle_get_installed_size (GVariant *bundle, gboolean byte_swap)
+{
+  guint64 total_usize = 0;
+  g_autoptr(GVariant) meta_entries = NULL;
+  guint i, n_parts;
+
+  g_variant_get_child (bundle, 6, "@a" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT, &meta_entries);
+  n_parts = g_variant_n_children (meta_entries);
+
+  for (i = 0; i < n_parts; i++)
+    {
+      guint32 version;
+      guint64 size, usize;
+      g_autoptr(GVariant) objects = NULL;
+
+      g_variant_get_child (meta_entries, i, "(u@aytt@ay)",
+                           &version, NULL, &size, &usize, &objects);
+
+      total_usize += maybe_swap_endian_u64 (byte_swap, usize);
+    }
+
+  return total_usize;
+}
+
+GVariant *
+flatpak_bundle_load (GFile   *file,
+                     char   **commit,
+                     char   **ref,
+                     char   **origin,
+                     char   **runtime_repo,
+                     char   **app_metadata,
+                     guint64 *installed_size,
+                     GBytes **gpg_keys,
+                     char   **collection_id,
+                     GError **error)
+{
+  g_autoptr(GVariant) delta = NULL;
+  g_autoptr(GVariant) metadata = NULL;
+  g_autoptr(GBytes) bytes = NULL;
+  g_autoptr(GBytes) copy = NULL;
+  g_autoptr(GVariant) to_csum_v = NULL;
+  guint8 endianness_char;
+  gboolean byte_swap = FALSE;
+
+  GMappedFile *mfile = g_mapped_file_new (flatpak_file_get_path_cached (file), FALSE, error);
+
+  if (mfile == NULL)
+    return NULL;
+
+  bytes = g_mapped_file_get_bytes (mfile);
+  g_mapped_file_unref (mfile);
+
+  delta = g_variant_new_from_bytes (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT), bytes, FALSE);
+  g_variant_ref_sink (delta);
+
+  to_csum_v = g_variant_get_child_value (delta, 3);
+  if (!ostree_validate_structureof_csum_v (to_csum_v, error))
+    return NULL;
+
+  metadata = g_variant_get_child_value (delta, 0);
+
+  if (g_variant_lookup (metadata, "ostree.endianness", "y", &endianness_char))
+    {
+      int file_byte_order = G_BYTE_ORDER;
+      switch (endianness_char)
+        {
+        case 'l':
+          file_byte_order = G_LITTLE_ENDIAN;
+          break;
+
+        case 'B':
+          file_byte_order = G_BIG_ENDIAN;
+          break;
+
+        default:
+          break;
+        }
+      byte_swap = (G_BYTE_ORDER != file_byte_order);
+    }
+
+  if (commit)
+    *commit = ostree_checksum_from_bytes_v (to_csum_v);
+
+  if (installed_size)
+    *installed_size = flatpak_bundle_get_installed_size (delta, byte_swap);
+
+  if (ref != NULL)
+    {
+      if (!g_variant_lookup (metadata, "ref", "s", ref))
+        {
+          flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid bundle, no ref in metadata"));
+          return NULL;
+        }
+    }
+
+  if (origin != NULL)
+    {
+      if (!g_variant_lookup (metadata, "origin", "s", origin))
+        *origin = NULL;
+    }
+
+  if (runtime_repo != NULL)
+    {
+      if (!g_variant_lookup (metadata, "runtime-repo", "s", runtime_repo))
+        *runtime_repo = NULL;
+    }
+
+  if (collection_id != NULL)
+    {
+      if (!g_variant_lookup (metadata, "collection-id", "s", collection_id))
+        {
+          *collection_id = NULL;
+        }
+      else if (**collection_id == '\0')
+        {
+          g_free (*collection_id);
+          *collection_id = NULL;
+        }
+    }
+
+  if (app_metadata != NULL)
+    {
+      if (!g_variant_lookup (metadata, "metadata", "s", app_metadata))
+        *app_metadata = NULL;
+    }
+
+  if (gpg_keys != NULL)
+    {
+      g_autoptr(GVariant) gpg_value = g_variant_lookup_value (metadata, "gpg-keys",
+                                                              G_VARIANT_TYPE ("ay"));
+      if (gpg_value)
+        {
+          gsize n_elements;
+          const char *data = g_variant_get_fixed_array (gpg_value, &n_elements, 1);
+          *gpg_keys = g_bytes_new (data, n_elements);
+        }
+      else
+        {
+          *gpg_keys = NULL;
+        }
+    }
+
+  /* Make a copy of the data so we can return it after freeing the file */
+  copy = g_bytes_new (g_variant_get_data (metadata),
+                      g_variant_get_size (metadata));
+  return g_variant_ref_sink (g_variant_new_from_bytes (g_variant_get_type (metadata),
+                                                       copy,
+                                                       FALSE));
+}
+
+gboolean
+flatpak_pull_from_bundle (OstreeRepo   *repo,
+                          GFile        *file,
+                          const char   *remote,
+                          const char   *ref,
+                          gboolean      require_gpg_signature,
+                          GCancellable *cancellable,
+                          GError      **error)
+{
+  g_autofree char *metadata_contents = NULL;
+  g_autofree char *to_checksum = NULL;
+  g_autoptr(GFile) root = NULL;
+  g_autoptr(GFile) metadata_file = NULL;
+  g_autoptr(GInputStream) in = NULL;
+  g_autoptr(OstreeGpgVerifyResult) gpg_result = NULL;
+  g_autoptr(GError) my_error = NULL;
+  g_autoptr(GVariant) metadata = NULL;
+  gboolean metadata_valid;
+  g_autofree char *remote_collection_id = NULL;
+  g_autofree char *collection_id = NULL;
+
+  metadata = flatpak_bundle_load (file, &to_checksum, NULL, NULL, NULL, &metadata_contents, NULL, NULL, &collection_id, error);
+  if (metadata == NULL)
+    return FALSE;
+
+  if (!ostree_repo_get_remote_option (repo, remote, "collection-id", NULL,
+                                      &remote_collection_id, NULL))
+    remote_collection_id = NULL;
+
+  if (remote_collection_id != NULL && collection_id != NULL &&
+      strcmp (remote_collection_id, collection_id) != 0)
+    return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Collection ‘%s’ of bundle doesn’t match collection ‘%s’ of remote"),
+                               collection_id, remote_collection_id);
+
+  if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error))
+    return FALSE;
+
+  /* Don’t need to set the collection ID here, since the remote binds this ref to the collection. */
+  ostree_repo_transaction_set_ref (repo, remote, ref, to_checksum);
+
+  if (!ostree_repo_static_delta_execute_offline (repo,
+                                                 file,
+                                                 FALSE,
+                                                 cancellable,
+                                                 error))
+    return FALSE;
+
+  gpg_result = ostree_repo_verify_commit_ext (repo, to_checksum,
+                                              NULL, NULL, cancellable, &my_error);
+  if (gpg_result == NULL)
+    {
+      /* no gpg signature, we ignore this *if* there is no gpg key
+       * specified in the bundle or by the user */
+      if (g_error_matches (my_error, OSTREE_GPG_ERROR, OSTREE_GPG_ERROR_NO_SIGNATURE) &&
+          !require_gpg_signature)
+        {
+          g_clear_error (&my_error);
+        }
+      else
+        {
+          g_propagate_error (error, g_steal_pointer (&my_error));
+          return FALSE;
+        }
+    }
+  else
+    {
+      /* If there is no valid gpg signature we fail, unless there is no gpg
+         key specified (on the command line or in the file) because then we
+         trust the source bundle. */
+      if (ostree_gpg_verify_result_count_valid (gpg_result) == 0  &&
+          require_gpg_signature)
+        return flatpak_fail_error (error, FLATPAK_ERROR_UNTRUSTED, _("GPG signatures found, but none are in trusted keyring"));
+    }
+
+  if (!ostree_repo_read_commit (repo, to_checksum, &root, NULL, NULL, error))
+    return FALSE;
+
+  if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error))
+    return FALSE;
+
+  /* We ensure that the actual installed metadata matches the one in the
+     header, because you may have made decisions on whether to install it or not
+     based on that data. */
+  metadata_file = g_file_resolve_relative_path (root, "metadata");
+  in = (GInputStream *) g_file_read (metadata_file, cancellable, NULL);
+  if (in != NULL)
+    {
+      g_autoptr(GMemoryOutputStream) data_stream = (GMemoryOutputStream *) g_memory_output_stream_new_resizable ();
+
+      if (g_output_stream_splice (G_OUTPUT_STREAM (data_stream), in,
+                                  G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE,
+                                  cancellable, error) < 0)
+        return FALSE;
+
+      /* Null terminate */
+      g_output_stream_write (G_OUTPUT_STREAM (data_stream), "\0", 1, NULL, NULL);
+
+      metadata_valid =
+        metadata_contents != NULL &&
+        strcmp (metadata_contents, g_memory_output_stream_get_data (data_stream)) == 0;
+    }
+  else
+    {
+      metadata_valid = (metadata_contents == NULL);
+    }
+
+  if (!metadata_valid)
+    {
+      /* Immediately remove this broken commit */
+      ostree_repo_set_ref_immediate (repo, remote, ref, NULL, cancellable, error);
+      return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Metadata in header and app are inconsistent"));
+    }
+
+  return TRUE;
+}
+
+typedef struct
+{
+  FlatpakOciPullProgress progress_cb;
+  gpointer               progress_user_data;
+  guint64                total_size;
+  guint64                previous_layers_size;
+  guint32                n_layers;
+  guint32                pulled_layers;
+} FlatpakOciPullProgressData;
+
+static void
+oci_layer_progress (guint64  downloaded_bytes,
+                    gpointer user_data)
+{
+  FlatpakOciPullProgressData *progress_data = user_data;
+
+  if (progress_data->progress_cb)
+    progress_data->progress_cb (progress_data->total_size, progress_data->previous_layers_size + downloaded_bytes,
+                                progress_data->n_layers, progress_data->pulled_layers,
+                                progress_data->progress_user_data);
+}
+
+gboolean
+flatpak_mirror_image_from_oci (FlatpakOciRegistry    *dst_registry,
+                               FlatpakOciRegistry    *registry,
+                               const char            *oci_repository,
+                               const char            *digest,
+                               const char            *remote,
+                               const char            *ref,
+                               const char            *delta_url,
+                               OstreeRepo            *repo,
+                               FlatpakOciPullProgress progress_cb,
+                               gpointer               progress_user_data,
+                               GCancellable          *cancellable,
+                               GError               **error)
+{
+  FlatpakOciPullProgressData progress_data = { progress_cb, progress_user_data };
+  g_autoptr(FlatpakOciVersioned) versioned = NULL;
+  FlatpakOciManifest *manifest = NULL;
+  g_autoptr(FlatpakOciDescriptor) manifest_desc = NULL;
+  g_autoptr(FlatpakOciManifest) delta_manifest = NULL;
+  g_autofree char *old_checksum = NULL;
+  g_autoptr(GVariant) old_commit = NULL;
+  g_autoptr(GFile) old_root = NULL;
+  OstreeRepoCommitState old_state = 0;
+  g_autofree char *old_diffid = NULL;
+  gsize versioned_size;
+  g_autoptr(FlatpakOciIndex) index = NULL;
+  g_autoptr(FlatpakOciImage) image_config = NULL;
+  int n_layers;
+  int i;
+
+  if (!flatpak_oci_registry_mirror_blob (dst_registry, registry, oci_repository, TRUE, digest, NULL, NULL, NULL, cancellable, error))
+    return FALSE;
+
+  versioned = flatpak_oci_registry_load_versioned (dst_registry, NULL, digest, NULL, &versioned_size, cancellable, error);
+  if (versioned == NULL)
+    return FALSE;
+
+  if (!FLATPAK_IS_OCI_MANIFEST (versioned))
+    return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Image is not a manifest"));
+
+  manifest = FLATPAK_OCI_MANIFEST (versioned);
+
+  if (manifest->config.digest == NULL)
+    return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Image is not a manifest"));
+
+  if (!flatpak_oci_registry_mirror_blob (dst_registry, registry, oci_repository, FALSE, manifest->config.digest, (const char **)manifest->config.urls, NULL, NULL, cancellable, error))
+    return FALSE;
+
+  image_config = flatpak_oci_registry_load_image_config (dst_registry, NULL,
+                                                         manifest->config.digest, NULL,
+                                                         NULL, cancellable, error);
+  if (image_config == NULL)
+    return FALSE;
+
+  /* For deltas we ensure that the diffid and regular layers exists and match up */
+  n_layers = flatpak_oci_manifest_get_n_layers (manifest);
+  if (n_layers == 0 || n_layers != flatpak_oci_image_get_n_layers (image_config))
+    return flatpak_fail (error, _("Invalid OCI image config"));
+
+  /* Look for delta manifest, and if it exists, the current (old) commit and its recorded diffid */
+  if (flatpak_repo_resolve_rev (repo, NULL, remote, ref, FALSE, &old_checksum, NULL, NULL) &&
+      ostree_repo_load_commit (repo, old_checksum, &old_commit, &old_state, NULL) &&
+      (old_state == OSTREE_REPO_COMMIT_STATE_NORMAL) &&
+      ostree_repo_read_commit (repo, old_checksum, &old_root, NULL, NULL, NULL))
+    {
+      delta_manifest = flatpak_oci_registry_find_delta_manifest (registry, oci_repository, digest, delta_url, cancellable);
+      if (delta_manifest)
+        {
+          VarMetadataRef commit_metadata = var_commit_get_metadata (var_commit_from_gvariant (old_commit));
+          const char *raw_old_diffid = var_metadata_lookup_string (commit_metadata, "xa.diff-id", NULL);
+          if (raw_old_diffid != NULL)
+            old_diffid = g_strconcat ("sha256:", raw_old_diffid, NULL);
+        }
+    }
+
+  for (i = 0; manifest->layers[i] != NULL; i++)
+    {
+      FlatpakOciDescriptor *layer = manifest->layers[i];
+      FlatpakOciDescriptor *delta_layer = NULL;
+
+      if (delta_manifest)
+        delta_layer = flatpak_oci_manifest_find_delta_for (delta_manifest, old_diffid, image_config->rootfs.diff_ids[i]);
+
+      if (delta_layer)
+        progress_data.total_size += delta_layer->size;
+      else
+        progress_data.total_size += layer->size;
+      progress_data.n_layers++;
+    }
+
+  if (progress_cb)
+    progress_cb (progress_data.total_size, 0,
+                 progress_data.n_layers, progress_data.pulled_layers,
+                 progress_user_data);
+
+  for (i = 0; manifest->layers[i] != NULL; i++)
+    {
+      FlatpakOciDescriptor *layer = manifest->layers[i];
+      FlatpakOciDescriptor *delta_layer = NULL;
+
+      if (delta_manifest)
+        delta_layer = flatpak_oci_manifest_find_delta_for (delta_manifest, old_diffid, image_config->rootfs.diff_ids[i]);
+
+      if (delta_layer)
+        {
+          g_debug ("Using OCI delta %s for layer %s", delta_layer->digest, layer->digest);
+          g_autofree char *delta_digest = NULL;
+          glnx_autofd int delta_fd = flatpak_oci_registry_download_blob (registry, oci_repository, FALSE,
+                                                                         delta_layer->digest, (const char **)delta_layer->urls,
+                                                                         oci_layer_progress, &progress_data,
+                                                                         cancellable, error);
+          if (delta_fd == -1)
+            return FALSE;
+
+          delta_digest = flatpak_oci_registry_apply_delta_to_blob (dst_registry, delta_fd, old_root, cancellable, error);
+          if (delta_digest == NULL)
+            return FALSE;
+
+          if (g_strcmp0 (delta_digest, image_config->rootfs.diff_ids[i]) != 0)
+            return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Wrong layer checksum, expected %s, was %s"), image_config->rootfs.diff_ids[i], delta_digest);
+        }
+      else
+        {
+          if (!flatpak_oci_registry_mirror_blob (dst_registry, registry, oci_repository, FALSE, layer->digest, (const char **)layer->urls,
+                                                 oci_layer_progress, &progress_data,
+                                                 cancellable, error))
+            return FALSE;
+        }
+
+      progress_data.pulled_layers++;
+      progress_data.previous_layers_size += delta_layer ? delta_layer->size : layer->size;
+    }
+
+  index = flatpak_oci_registry_load_index (dst_registry, NULL, NULL);
+  if (index == NULL)
+    index = flatpak_oci_index_new ();
+
+  manifest_desc = flatpak_oci_descriptor_new (versioned->mediatype, digest, versioned_size);
+
+  flatpak_oci_index_add_manifest (index, ref, manifest_desc);
+
+  if (!flatpak_oci_registry_save_index (dst_registry, index, cancellable, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+char *
+flatpak_pull_from_oci (OstreeRepo            *repo,
+                       FlatpakOciRegistry    *registry,
+                       const char            *oci_repository,
+                       const char            *digest,
+                       const char            *delta_url,
+                       FlatpakOciManifest    *manifest,
+                       FlatpakOciImage       *image_config,
+                       const char            *remote,
+                       const char            *ref,
+                       FlatpakPullFlags       flags,
+                       FlatpakOciPullProgress progress_cb,
+                       gpointer               progress_user_data,
+                       GCancellable          *cancellable,
+                       GError               **error)
+{
+  gboolean force_disable_deltas = (flags & FLATPAK_PULL_FLAGS_NO_STATIC_DELTAS) != 0;
+  g_autoptr(OstreeMutableTree) archive_mtree = NULL;
+  g_autoptr(GFile) archive_root = NULL;
+  g_autoptr(FlatpakOciManifest) delta_manifest = NULL;
+  g_autofree char *old_checksum = NULL;
+  g_autoptr(GVariant) old_commit = NULL;
+  g_autoptr(GFile) old_root = NULL;
+  OstreeRepoCommitState old_state = 0;
+  g_autofree char *old_diffid = NULL;
+  g_autofree char *commit_checksum = NULL;
+  const char *parent = NULL;
+  g_autofree char *subject = NULL;
+  g_autofree char *body = NULL;
+  g_autofree char *manifest_ref = NULL;
+  g_autofree char *full_ref = NULL;
+  const char *diffid;
+  guint64 timestamp = 0;
+  FlatpakOciPullProgressData progress_data = { progress_cb, progress_user_data };
+  g_autoptr(GVariantBuilder) metadata_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
+  g_autoptr(GVariant) metadata = NULL;
+  GHashTable *labels;
+  int n_layers;
+  int i;
+
+  g_assert (ref != NULL);
+  g_assert (g_str_has_prefix (digest, "sha256:"));
+
+  labels = flatpak_oci_image_get_labels (image_config);
+  if (labels)
+    flatpak_oci_parse_commit_labels (labels, &timestamp,
+                                     &subject, &body,
+                                     &manifest_ref, NULL, NULL,
+                                     metadata_builder);
+
+  if (manifest_ref == NULL)
+    {
+      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("No ref specified for OCI image %s"), digest);
+      return NULL;
+    }
+
+  if (strcmp (manifest_ref, ref) != 0)
+    {
+      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Wrong ref (%s) specified for OCI image %s, expected %s"), manifest_ref, digest, ref);
+      return NULL;
+    }
+
+  g_variant_builder_add (metadata_builder, "{s@v}", "xa.alt-id",
+                         g_variant_new_variant (g_variant_new_string (digest + strlen ("sha256:"))));
+
+  /* For deltas we ensure that the diffid and regular layers exists and match up */
+  n_layers = flatpak_oci_manifest_get_n_layers (manifest);
+  if (n_layers == 0 || n_layers != flatpak_oci_image_get_n_layers (image_config))
+    {
+      flatpak_fail (error, _("Invalid OCI image config"));
+      return NULL;
+    }
+
+  /* Assuming everyting looks good, we record the uncompressed checksum (the diff-id) of the last layer,
+     because that is what we can read back easily from the deploy dir, and thus is easy to use for applying deltas */
+  diffid = image_config->rootfs.diff_ids[n_layers-1];
+  if (diffid != NULL && g_str_has_prefix (diffid, "sha256:"))
+    g_variant_builder_add (metadata_builder, "{s@v}", "xa.diff-id",
+                           g_variant_new_variant (g_variant_new_string (diffid + strlen ("sha256:"))));
+
+  /* Look for delta manifest, and if it exists, the current (old) commit and its recorded diffid */
+  if (!force_disable_deltas &&
+      !flatpak_oci_registry_is_local (registry) &&
+      flatpak_repo_resolve_rev (repo, NULL, remote, ref, FALSE, &old_checksum, NULL, NULL) &&
+      ostree_repo_load_commit (repo, old_checksum, &old_commit, &old_state, NULL) &&
+      (old_state == OSTREE_REPO_COMMIT_STATE_NORMAL) &&
+      ostree_repo_read_commit (repo, old_checksum, &old_root, NULL, NULL, NULL))
+    {
+      delta_manifest = flatpak_oci_registry_find_delta_manifest (registry, oci_repository, digest, delta_url, cancellable);
+      if (delta_manifest)
+        {
+          VarMetadataRef commit_metadata = var_commit_get_metadata (var_commit_from_gvariant (old_commit));
+          const char *raw_old_diffid = var_metadata_lookup_string (commit_metadata, "xa.diff-id", NULL);
+          if (raw_old_diffid != NULL)
+            old_diffid = g_strconcat ("sha256:", raw_old_diffid, NULL);
+        }
+    }
+
+  if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error))
+    return NULL;
+
+  /* There is no way to write a subset of the archive to a mtree, so instead
+     we write all of it and then build a new mtree with the subset */
+  archive_mtree = ostree_mutable_tree_new ();
+
+  for (i = 0; manifest->layers[i] != NULL; i++)
+    {
+      FlatpakOciDescriptor *layer = manifest->layers[i];
+      FlatpakOciDescriptor *delta_layer = NULL;
+
+      if (delta_manifest)
+        delta_layer = flatpak_oci_manifest_find_delta_for (delta_manifest, old_diffid, image_config->rootfs.diff_ids[i]);
+
+      if (delta_layer)
+        progress_data.total_size += delta_layer->size;
+      else
+        progress_data.total_size += layer->size;
+
+      progress_data.n_layers++;
+    }
+
+  if (progress_cb)
+    progress_cb (progress_data.total_size, 0,
+                 progress_data.n_layers, progress_data.pulled_layers,
+                 progress_user_data);
+
+  for (i = 0; manifest->layers[i] != NULL; i++)
+    {
+      FlatpakOciDescriptor *layer = manifest->layers[i];
+      FlatpakOciDescriptor *delta_layer = NULL;
+      OstreeRepoImportArchiveOptions opts = { 0, };
+      g_autoptr(FlatpakAutoArchiveRead) a = NULL;
+      glnx_autofd int layer_fd = -1;
+      glnx_autofd int blob_fd = -1;
+      g_autoptr(GChecksum) checksum = g_checksum_new (G_CHECKSUM_SHA256);
+      g_autoptr(GError) local_error = NULL;
+      const char *layer_checksum;
+      const char *expected_digest;
+
+      if (delta_manifest)
+        delta_layer = flatpak_oci_manifest_find_delta_for (delta_manifest, old_diffid, image_config->rootfs.diff_ids[i]);
+
+      opts.autocreate_parents = TRUE;
+      opts.ignore_unsupported_content = TRUE;
+
+      if (delta_layer)
+        {
+          g_debug ("Using OCI delta %s for layer %s", delta_layer->digest, layer->digest);
+          expected_digest = image_config->rootfs.diff_ids[i]; /* The delta recreates the uncompressed tar so use that digest */
+        }
+      else
+        {
+          layer_fd = glnx_steal_fd (&blob_fd);
+          expected_digest = layer->digest;
+        }
+
+      blob_fd = flatpak_oci_registry_download_blob (registry, oci_repository, FALSE,
+                                                    delta_layer ? delta_layer->digest : layer->digest,
+                                                    (const char **)(delta_layer ? delta_layer->urls : layer->urls),
+                                                    oci_layer_progress, &progress_data,
+                                                    cancellable, &local_error);
+
+      if (blob_fd == -1 && delta_layer == NULL &&
+          flatpak_oci_registry_is_local (registry) &&
+          g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+        {
+          /* Pulling regular layer from local repo and its not there, try the uncompressed version.
+           * This happens when we deploy via system helper using oci deltas */
+          expected_digest = image_config->rootfs.diff_ids[i];
+          blob_fd = flatpak_oci_registry_download_blob (registry, oci_repository, FALSE,
+                                                        image_config->rootfs.diff_ids[i], NULL,
+                                                        oci_layer_progress, &progress_data,
+                                                        cancellable, NULL); /* No error here, we report the first error if this failes */
+        }
+
+      if (blob_fd == -1)
+        {
+          g_propagate_error (error, g_steal_pointer (&local_error));
+          goto error;
+        }
+
+      g_clear_error (&local_error);
+
+      if (delta_layer)
+        {
+          layer_fd = flatpak_oci_registry_apply_delta (registry, blob_fd, old_root, cancellable, error);
+          if (layer_fd == -1)
+            goto error;
+        }
+      else
+        {
+          layer_fd = glnx_steal_fd (&blob_fd);
+        }
+
+      a = archive_read_new ();
+#ifdef HAVE_ARCHIVE_READ_SUPPORT_FILTER_ALL
+      archive_read_support_filter_all (a);
+#else
+      archive_read_support_compression_all (a);
+#endif
+      archive_read_support_format_all (a);
+
+      if (!flatpak_archive_read_open_fd_with_checksum (a, layer_fd, checksum, error))
+        goto error;
+
+      if (!ostree_repo_import_archive_to_mtree (repo, &opts, a, archive_mtree, NULL, cancellable, error))
+        goto error;
+
+      if (archive_read_close (a) != ARCHIVE_OK)
+        {
+          propagate_libarchive_error (error, a);
+          goto error;
+        }
+
+      layer_checksum = g_checksum_get_string (checksum);
+      if (!g_str_has_prefix (expected_digest, "sha256:") ||
+          strcmp (expected_digest + strlen ("sha256:"), layer_checksum) != 0)
+        {
+          flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Wrong layer checksum, expected %s, was %s"), expected_digest, layer_checksum);
+          goto error;
+        }
+
+      progress_data.pulled_layers++;
+      progress_data.previous_layers_size += delta_layer ? delta_layer->size : layer->size;
+    }
+
+  if (!ostree_repo_write_mtree (repo, archive_mtree, &archive_root, cancellable, error))
+    goto error;
+
+  if (!ostree_repo_file_ensure_resolved ((OstreeRepoFile *) archive_root, error))
+    goto error;
+
+  metadata = g_variant_ref_sink (g_variant_builder_end (metadata_builder));
+  if (!ostree_repo_write_commit_with_time (repo,
+                                           parent,
+                                           subject,
+                                           body,
+                                           metadata,
+                                           OSTREE_REPO_FILE (archive_root),
+                                           timestamp,
+                                           &commit_checksum,
+                                           cancellable, error))
+    goto error;
+
+  if (remote)
+    full_ref = g_strdup_printf ("%s:%s", remote, ref);
+  else
+    full_ref = g_strdup (ref);
+
+  /* Don’t need to set the collection ID here, since the ref is bound to a
+   * collection via its remote. */
+  ostree_repo_transaction_set_ref (repo, NULL, full_ref, commit_checksum);
+
+  if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error))
+    return NULL;
+
+  return g_steal_pointer (&commit_checksum);
+
+error:
+
+  ostree_repo_abort_transaction (repo, cancellable, NULL);
+  return NULL;
+}
+
+/* This allocates and locks a subdir of the tmp dir, using an existing
+ * one with the same prefix if it is not in use already. */
+gboolean
+flatpak_allocate_tmpdir (int           tmpdir_dfd,
+                         const char   *tmpdir_relpath,
+                         const char   *tmpdir_prefix,
+                         char        **tmpdir_name_out,
+                         int          *tmpdir_fd_out,
+                         GLnxLockFile *file_lock_out,
+                         gboolean     *reusing_dir_out,
+                         GCancellable *cancellable,
+                         GError      **error)
+{
+  gboolean reusing_dir = FALSE;
+  g_autofree char *tmpdir_name = NULL;
+  glnx_autofd int tmpdir_fd = -1;
+  g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
+
+  /* Look for existing tmpdir (with same prefix) to reuse */
+  if (!glnx_dirfd_iterator_init_at (tmpdir_dfd, tmpdir_relpath ? tmpdir_relpath : ".", FALSE, &dfd_iter, error))
+    return FALSE;
+
+  while (tmpdir_name == NULL)
+    {
+      struct dirent *dent;
+      glnx_autofd int existing_tmpdir_fd = -1;
+      g_autoptr(GError) local_error = NULL;
+      g_autofree char *lock_name = NULL;
+
+      if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, cancellable, error))
+        return FALSE;
+
+      if (dent == NULL)
+        break;
+
+      if (!g_str_has_prefix (dent->d_name, tmpdir_prefix))
+        continue;
+
+      /* Quickly skip non-dirs, if unknown we ignore ENOTDIR when opening instead */
+      if (dent->d_type != DT_UNKNOWN &&
+          dent->d_type != DT_DIR)
+        continue;
+
+      if (!glnx_opendirat (dfd_iter.fd, dent->d_name, FALSE,
+                           &existing_tmpdir_fd, &local_error))
+        {
+          if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY))
+            {
+              continue;
+            }
+          else
+            {
+              g_propagate_error (error, g_steal_pointer (&local_error));
+              return FALSE;
+            }
+        }
+
+      lock_name = g_strconcat (dent->d_name, "-lock", NULL);
+
+      /* We put the lock outside the dir, so we can hold the lock
+       * until the directory is fully removed */
+      if (!glnx_make_lock_file (dfd_iter.fd, lock_name, LOCK_EX | LOCK_NB,
+                                file_lock_out, &local_error))
+        {
+          if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+            {
+              continue;
+            }
+          else
+            {
+              g_propagate_error (error, g_steal_pointer (&local_error));
+              return FALSE;
+            }
+        }
+
+      /* Touch the reused directory so that we don't accidentally
+       *   remove it due to being old when cleaning up the tmpdir
+       */
+      (void) futimens (existing_tmpdir_fd, NULL);
+
+      /* We found an existing tmpdir which we managed to lock */
+      tmpdir_name = g_strdup (dent->d_name);
+      tmpdir_fd = glnx_steal_fd (&existing_tmpdir_fd);
+      reusing_dir = TRUE;
+    }
+
+  while (tmpdir_name == NULL)
+    {
+      g_autofree char *tmpdir_name_template = g_strconcat (tmpdir_prefix, "XXXXXX", NULL);
+      g_autoptr(GError) local_error = NULL;
+      g_autofree char *lock_name = NULL;
+      g_auto(GLnxTmpDir) new_tmpdir = { 0, };
+      /* No existing tmpdir found, create a new */
+
+      if (!glnx_mkdtempat (dfd_iter.fd, tmpdir_name_template, 0777,
+                           &new_tmpdir, error))
+        return FALSE;
+
+      lock_name = g_strconcat (new_tmpdir.path, "-lock", NULL);
+
+      /* Note, at this point we can race with another process that picks up this
+       * new directory. If that happens we need to retry, making a new directory. */
+      if (!glnx_make_lock_file (dfd_iter.fd, lock_name, LOCK_EX | LOCK_NB,
+                                file_lock_out, &local_error))
+        {
+          if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+            {
+              glnx_tmpdir_unset (&new_tmpdir); /* Don't delete */
+              continue;
+            }
+          else
+            {
+              g_propagate_error (error, g_steal_pointer (&local_error));
+              return FALSE;
+            }
+        }
+
+      tmpdir_name = g_strdup (new_tmpdir.path);
+      tmpdir_fd = dup (new_tmpdir.fd);
+      glnx_tmpdir_unset (&new_tmpdir); /* Don't delete */
+    }
+
+  if (tmpdir_name_out)
+    *tmpdir_name_out = g_steal_pointer (&tmpdir_name);
+
+  if (tmpdir_fd_out)
+    *tmpdir_fd_out = glnx_steal_fd (&tmpdir_fd);
+
+  if (reusing_dir_out)
+    *reusing_dir_out = reusing_dir;
+
+  return TRUE;
+}
+
+char *
+flatpak_prompt (gboolean allow_empty,
+                const char *prompt, ...)
+{
+  char buf[512];
+  va_list var_args;
+  g_autofree char *s = NULL;
+
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+  va_start (var_args, prompt);
+  s = g_strdup_vprintf (prompt, var_args);
+  va_end (var_args);
+#pragma GCC diagnostic pop
+
+  while (TRUE)
+    {
+      g_print ("%s: ", s);
+
+      if (!isatty (STDIN_FILENO) || !isatty (STDOUT_FILENO))
+        {
+          g_print ("n\n");
+          return NULL;
+        }
+
+      if (fgets (buf, sizeof (buf), stdin) == NULL)
+        return NULL;
+
+      g_strstrip (buf);
+
+      if (buf[0] != 0 || allow_empty)
+        return g_strdup (buf);
+    }
+}
+
+char *
+flatpak_password_prompt (const char *prompt, ...)
+{
+  char buf[512];
+  va_list var_args;
+  g_autofree char *s = NULL;
+  gboolean was_echo;
+
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+  va_start (var_args, prompt);
+  s = g_strdup_vprintf (prompt, var_args);
+  va_end (var_args);
+#pragma GCC diagnostic pop
+
+  while (TRUE)
+    {
+      g_print ("%s: ", s);
+
+      if (!isatty (STDIN_FILENO) || !isatty (STDOUT_FILENO))
+        return NULL;
+
+      was_echo = flatpak_set_tty_echo (FALSE);
+
+      if (fgets (buf, sizeof (buf), stdin) == NULL)
+        return NULL;
+
+      flatpak_set_tty_echo (was_echo);
+
+      g_strstrip (buf);
+
+      /* We stole the return, so manual new line */
+      g_print ("\n");
+      return g_strdup (buf);
+    }
+}
+
+
+gboolean
+flatpak_yes_no_prompt (gboolean default_yes, const char *prompt, ...)
+{
+  char buf[512];
+  va_list var_args;
+  g_autofree char *s = NULL;
+
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+  va_start (var_args, prompt);
+  s = g_strdup_vprintf (prompt, var_args);
+  va_end (var_args);
+#pragma GCC diagnostic pop
+
+  while (TRUE)
+    {
+      g_print ("%s %s: ", s, default_yes ? "[Y/n]" : "[y/n]");
+
+      if (!isatty (STDIN_FILENO) || !isatty (STDOUT_FILENO))
+        {
+          g_print ("n\n");
+          return FALSE;
+        }
+
+      if (fgets (buf, sizeof (buf), stdin) == NULL)
+        return FALSE;
+
+      g_strstrip (buf);
+
+      if (default_yes && strlen (buf) == 0)
+        return TRUE;
+
+      if (g_ascii_strcasecmp (buf, "y") == 0 ||
+          g_ascii_strcasecmp (buf, "yes") == 0)
+        return TRUE;
+
+      if (g_ascii_strcasecmp (buf, "n") == 0 ||
+          g_ascii_strcasecmp (buf, "no") == 0)
+        return FALSE;
+    }
+}
+
+static gboolean
+is_number (const char *s)
+{
+  if (*s == '\0')
+    return FALSE;
+
+  while (*s != 0)
+    {
+      if (!g_ascii_isdigit (*s))
+        return FALSE;
+      s++;
+    }
+
+  return TRUE;
+}
+
+long
+flatpak_number_prompt (gboolean default_yes, int min, int max, const char *prompt, ...)
+{
+  char buf[512];
+  va_list var_args;
+  g_autofree char *s = NULL;
+
+  va_start (var_args, prompt);
+  s = g_strdup_vprintf (prompt, var_args);
+  va_end (var_args);
+
+  while (TRUE)
+    {
+      g_print ("%s [%d-%d]: ", s, min, max);
+
+      if (!isatty (STDIN_FILENO) || !isatty (STDOUT_FILENO))
+        {
+          g_print ("0\n");
+          return 0;
+        }
+
+      if (fgets (buf, sizeof (buf), stdin) == NULL)
+        return 0;
+
+      g_strstrip (buf);
+
+      if (default_yes && strlen (buf) == 0 &&
+          max - min == 1 && min == 0)
+        return 1;
+
+      if (is_number (buf))
+        {
+          long res = strtol (buf, NULL, 10);
+
+          if (res >= min && res <= max)
+            return res;
+        }
+    }
+}
+
+static gboolean
+parse_range (const char *s, int *a, int *b)
+{
+  char *p;
+
+  p = strchr (s, '-');
+  if (!p)
+    return FALSE;
+
+  p++;
+  p[-1] = '\0';
+
+  if (is_number (s) && is_number (p))
+    {
+      *a = (int) strtol (s, NULL, 10);
+      *b = (int) strtol (p, NULL, 10);
+      p[-1] = '-';
+      return TRUE;
+    }
+
+  p[-1] = '-';
+  return FALSE;
+}
+
+static void
+add_number (GArray *numbers,
+            int     num)
+{
+  int i;
+
+  for (i = 0; i < numbers->len; i++)
+    {
+      if (g_array_index (numbers, int, i) == num)
+        return;
+    }
+
+  g_array_append_val (numbers, num);
+}
+
+int *
+flatpak_parse_numbers (const char *buf,
+                       int         min,
+                       int         max)
+{
+  g_autoptr(GArray) numbers = g_array_new (FALSE, FALSE, sizeof (int));
+  g_auto(GStrv) parts = g_strsplit_set (buf, " ,", 0);
+  int i, j;
+
+  for (i = 0; parts[i]; i++)
+    {
+      int a, b;
+
+      g_strstrip (parts[i]);
+
+      if (parse_range (parts[i], &a, &b) &&
+          min <= a && a <= max &&
+          min <= b && b <= max)
+        {
+          for (j = a; j <= b; j++)
+            add_number (numbers, j);
+        }
+      else if (is_number (parts[i]))
+        {
+          int res = (int) strtol (parts[i], NULL, 10);
+          if (min <= res && res <= max)
+            add_number (numbers, res);
+          else
+            return NULL;
+        }
+      else
+        return NULL;
+    }
+
+  j = 0;
+  g_array_append_val (numbers, j);
+
+  return (int *) g_array_free (g_steal_pointer (&numbers), FALSE);
+}
+
+/* Returns a 0-terminated array of ints. Free with g_free */
+int *
+flatpak_numbers_prompt (gboolean default_yes, int min, int max, const char *prompt, ...)
+{
+  char buf[512];
+  va_list var_args;
+  g_autofree char *s = NULL;
+  g_autofree int *choice = g_new0 (int, 2);
+  int *numbers;
+
+  va_start (var_args, prompt);
+  s = g_strdup_vprintf (prompt, var_args);
+  va_end (var_args);
+
+  while (TRUE)
+    {
+      g_print ("%s [%d-%d]: ", s, min, max);
+
+      if (!isatty (STDIN_FILENO) || !isatty (STDOUT_FILENO))
+        {
+          g_print ("0\n");
+          choice[0] = 0;
+          return g_steal_pointer (&choice);
+        }
+
+      if (fgets (buf, sizeof (buf), stdin) == NULL)
+        {
+          choice[0] = 0;
+          return g_steal_pointer (&choice);
+        }
+
+      g_strstrip (buf);
+
+      if (default_yes && strlen (buf) == 0 &&
+          max - min == 1 && min == 0)
+        {
+          choice[0] = 0;
+          return g_steal_pointer (&choice);
+        }
+
+      numbers = flatpak_parse_numbers (buf, min, max);
+      if (numbers)
+        return numbers;
+    }
+}
+
+void
+flatpak_format_choices (const char **choices,
+                        const char  *prompt,
+                        ...)
+{
+  va_list var_args;
+  g_autofree char *s = NULL;
+  int i;
+
+  va_start (var_args, prompt);
+  s = g_strdup_vprintf (prompt, var_args);
+  va_end (var_args);
+
+  g_print ("%s\n\n", s);
+  for (i = 0; choices[i]; i++)
+    g_print ("  %2d) %s\n", i + 1, choices[i]);
+  g_print ("\n");
+}
+
+char **
+flatpak_strv_merge (char   **strv1,
+                    char   **strv2)
+{
+  GPtrArray *array;
+  int i;
+
+  /* Maybe either (or both) is unspecified */
+  if (strv1 == NULL)
+    return g_strdupv (strv2);
+  if (strv2 == NULL)
+    return g_strdupv (strv1);
+
+  /* Combine both */
+  array = g_ptr_array_new ();
+
+  for (i = 0; strv1[i] != NULL; i++)
+    {
+      if (!flatpak_g_ptr_array_contains_string (array, strv1[i]))
+        g_ptr_array_add (array, g_strdup (strv1[i]));
+    }
+
+  for (i = 0; strv2[i] != NULL; i++)
+    {
+      if (!flatpak_g_ptr_array_contains_string (array, strv2[i]))
+        g_ptr_array_add (array, g_strdup (strv2[i]));
+    }
+
+  g_ptr_array_add (array, NULL);
+  return (char **) g_ptr_array_free (array, FALSE);
+}
+
+/* In this NULL means don't care about these paths, while
+   an empty array means match anything */
+char **
+flatpak_subpaths_merge (char **subpaths1,
+                        char **subpaths2)
+{
+  char **res;
+
+  if (subpaths1 != NULL && subpaths1[0] == NULL)
+    return g_strdupv (subpaths1);
+  if (subpaths2 != NULL && subpaths2[0] == NULL)
+    return g_strdupv (subpaths2);
+
+  res = flatpak_strv_merge (subpaths1, subpaths2);
+  if (res)
+    qsort (res, g_strv_length (res), sizeof (const char *), flatpak_strcmp0_ptr);
+
+  return res;
+}
+
+char *
+flatpak_get_lang_from_locale (const char *locale)
+{
+  g_autofree char *lang = g_strdup (locale);
+  char *c;
+
+  c = strchr (lang, '@');
+  if (c != NULL)
+    *c = 0;
+  c = strchr (lang, '_');
+  if (c != NULL)
+    *c = 0;
+  c = strchr (lang, '.');
+  if (c != NULL)
+    *c = 0;
+
+  if (strcmp (lang, "C") == 0)
+    return NULL;
+
+  return g_steal_pointer (&lang);
+}
+
+#endif
+
+gboolean
+flatpak_g_ptr_array_contains_string (GPtrArray *array, const char *str)
+{
+  int i;
+
+  for (i = 0; i < array->len; i++)
+    {
+      if (strcmp (g_ptr_array_index (array, i), str) == 0)
+        return TRUE;
+    }
+  return FALSE;
+}
+
+#if 0
+
+char **
+flatpak_get_current_locale_langs (void)
+{
+  const gchar * const *locales = g_get_language_names ();
+  GPtrArray *langs = g_ptr_array_new ();
+  int i;
+
+  for (i = 0; locales[i] != NULL; i++)
+    {
+      g_autofree char *lang = flatpak_get_lang_from_locale (locales[i]);
+      if (lang != NULL && !flatpak_g_ptr_array_contains_string (langs, lang))
+        g_ptr_array_add (langs, g_steal_pointer (&lang));
+    }
+
+  g_ptr_array_sort (langs, flatpak_strcmp0_ptr);
+  g_ptr_array_add (langs, NULL);
+
+  return (char **) g_ptr_array_free (langs, FALSE);
+}
+
+void
+flatpak_log_dir_access (FlatpakDir *dir)
+{
+  if (dir != NULL)
+    {
+      GFile *dir_path = NULL;
+      g_autofree char *dir_path_str = NULL;
+      g_autofree char *dir_name = NULL;
+
+      dir_path = flatpak_dir_get_path (dir);
+      if (dir_path != NULL)
+        dir_path_str = g_file_get_path (dir_path);
+      dir_name = flatpak_dir_get_name (dir);
+      g_debug ("Opening %s flatpak installation at path %s", dir_name, dir_path_str);
+    }
+}
+
+gboolean
+flatpak_check_required_version (const char *ref,
+                                GKeyFile   *metakey,
+                                GError    **error)
+{
+  g_auto(GStrv) required_versions = NULL;
+  const char *group;
+  int max_required_major = 0, max_required_minor = 0;
+  const char *max_required_version = "0.0";
+  int i;
+
+  if (g_str_has_prefix (ref, "app/"))
+    group = "Application";
+  else
+    group = "Runtime";
+
+  /* We handle handle multiple version requirements here. Each requirement must
+   * be in the form major.minor.micro, and if the flatpak version matches the
+   * major.minor part, t must be equal or later in the micro. If the major.minor part
+   * doesn't exactly match any of the specified requirements it must be larger
+   * than the maximum specified requirement.
+   *
+   * For example, specifying
+   *   required-flatpak=1.6.2;1.4.2;1.0.2;
+   * would allow flatpak versions:
+   *  1.7.0, 1.6.2, 1.6.3, 1.4.2, 1.4.3, 1.0.2, 1.0.3
+   * but not:
+   *  1.6.1, 1.4.1 or 1.2.100.
+   *
+   * The goal here is to be able to specify a version (like 1.6.2 above) where a feature
+   * was introduced, but also allow backports of said feature to earlier version series.
+   *
+   * Earlier versions that only support specifying one version will only look at the first
+   * element in the list, so put the largest version first.
+   */
+  required_versions = g_key_file_get_string_list (metakey, group, "required-flatpak", NULL, NULL);
+  if (required_versions == 0 || required_versions[0] == NULL)
+    return TRUE;
+
+  for (i = 0; required_versions[i] != NULL; i++)
+    {
+      int required_major, required_minor, required_micro;
+      const char *required_version = required_versions[i];
+
+      if (sscanf (required_version, "%d.%d.%d", &required_major, &required_minor, &required_micro) != 3)
+        return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA,
+                                   _("Invalid require-flatpak argument %s"), required_version);
+      else
+        {
+          /* If flatpak is in the same major.minor series as the requirement, do a micro check */
+          if (required_major == PACKAGE_MAJOR_VERSION && required_minor == PACKAGE_MINOR_VERSION)
+            {
+              if (required_micro <= PACKAGE_MICRO_VERSION)
+                return TRUE;
+              else
+                return flatpak_fail_error (error, FLATPAK_ERROR_NEED_NEW_FLATPAK,
+                                           _("%s needs a later flatpak version (%s)"),
+                                           ref, required_version);
+            }
+
+          /* Otherwise, keep track of the largest major.minor that is required */
+          if ((required_major > max_required_major) ||
+              (required_major == max_required_major &&
+               required_minor > max_required_minor))
+            {
+              max_required_major = required_major;
+              max_required_minor = required_minor;
+              max_required_version = required_version;
+            }
+        }
+    }
+
+  if (max_required_major > PACKAGE_MAJOR_VERSION ||
+      (max_required_major == PACKAGE_MAJOR_VERSION && max_required_minor > PACKAGE_MINOR_VERSION))
+    return flatpak_fail_error (error, FLATPAK_ERROR_NEED_NEW_FLATPAK,
+                               _("%s needs a later flatpak version (%s)"),
+                               ref, max_required_version);
+
+  return TRUE;
+}
+
+static gboolean
+str_has_sign (const gchar *str)
+{
+  return str[0] == '-' || str[0] == '+';
+}
+
+static gboolean
+str_has_hex_prefix (const gchar *str)
+{
+  return str[0] == '0' && g_ascii_tolower (str[1]) == 'x';
+}
+
+/* Copied from glib-2.54.0 to avoid the Glib's version bump.
+ * Function name in glib: g_ascii_string_to_unsigned
+ * If this is being dropped(migration to g_ascii_string_to_unsigned)
+ * make sure to remove str_has_hex_prefix and str_has_sign helpers too.
+ */
+gboolean
+flatpak_utils_ascii_string_to_unsigned (const gchar *str,
+                                        guint        base,
+                                        guint64      min,
+                                        guint64      max,
+                                        guint64     *out_num,
+                                        GError     **error)
+{
+  guint64 number;
+  const gchar *end_ptr = NULL;
+  gint saved_errno = 0;
+
+  g_return_val_if_fail (str != NULL, FALSE);
+  g_return_val_if_fail (base >= 2 && base <= 36, FALSE);
+  g_return_val_if_fail (min <= max, FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  if (str[0] == '\0')
+    {
+      g_set_error_literal (error,
+                           G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+                           _("Empty string is not a number"));
+      return FALSE;
+    }
+
+  errno = 0;
+  number = g_ascii_strtoull (str, (gchar **) &end_ptr, base);
+  saved_errno = errno;
+
+  if (/* We do not allow leading whitespace, but g_ascii_strtoull
+       * accepts it and just skips it, so we need to check for it
+       * ourselves.
+       */
+    g_ascii_isspace (str[0]) ||
+    /* Unsigned number should have no sign.
+     */
+    str_has_sign (str) ||
+    /* We don't support hexadecimal numbers prefixed with 0x or
+     * 0X.
+     */
+    (base == 16 && str_has_hex_prefix (str)) ||
+    (saved_errno != 0 && saved_errno != ERANGE) ||
+    end_ptr == NULL ||
+    *end_ptr != '\0')
+    {
+      g_set_error (error,
+                   G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+                   _("“%s” is not an unsigned number"), str);
+      return FALSE;
+    }
+  if (saved_errno == ERANGE || number < min || number > max)
+    {
+      gchar *min_str = g_strdup_printf ("%" G_GUINT64_FORMAT, min);
+      gchar *max_str = g_strdup_printf ("%" G_GUINT64_FORMAT, max);
+
+      g_set_error (error,
+                   G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+                   _("Number “%s” is out of bounds [%s, %s]"),
+                   str, min_str, max_str);
+      g_free (min_str);
+      g_free (max_str);
+      return FALSE;
+    }
+  if (out_num != NULL)
+    *out_num = number;
+  return TRUE;
+}
+
+static int
+dist (const char *s, int ls, const char *t, int lt, int i, int j, int *d)
+{
+  int x, y;
+
+  if (d[i * (lt + 1) + j] >= 0)
+    return d[i * (lt + 1) + j];
+
+  if (i == ls)
+    x = lt - j;
+  else if (j == lt)
+    x = ls - i;
+  else if (s[i] == t[j])
+    x = dist (s, ls, t, lt, i + 1, j + 1, d);
+  else
+    {
+      x = dist (s, ls, t, lt, i + 1, j + 1, d);
+      y = dist (s, ls, t, lt, i, j + 1, d);
+      if (y < x)
+        x = y;
+      y = dist (s, ls, t, lt, i + 1, j, d);
+      if (y < x)
+        x = y;
+      x++;
+    }
+
+  d[i * (lt + 1) + j] = x;
+
+  return x;
+}
+
+int
+flatpak_levenshtein_distance (const char *s, const char *t)
+{
+  int ls = strlen (s);
+  int lt = strlen (t);
+  int i, j;
+  int *d;
+
+  d = alloca (sizeof (int) * (ls + 1) * (lt + 1));
+
+  for (i = 0; i <= ls; i++)
+    for (j = 0; j <= lt; j++)
+      d[i * (lt + 1) + j] = -1;
+
+  return dist (s, ls, t, lt, 0, 0, d);
+}
+
+void
+flatpak_get_window_size (int *rows, int *cols)
+{
+  struct winsize w;
+
+  if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &w) == 0)
+    {
+      /* For whatever reason, in buildbot this returns 0, 0 so add a fallback */
+      if (w.ws_row == 0)
+        w.ws_row = 24;
+      if (w.ws_col == 0)
+        w.ws_col = 80;
+      *rows = w.ws_row;
+      *cols = w.ws_col;
+    }
+  else
+    {
+      *rows = 24;
+      *cols = 80;
+    }
+}
+
+gboolean
+flatpak_set_tty_echo (gboolean echo)
+{
+  struct termios term;
+  gboolean was;
+
+  tcgetattr (STDIN_FILENO, &term);
+  was = (term.c_lflag & ECHO) != 0;
+
+  if (echo)
+    term.c_lflag |= ECHO;
+  else
+    term.c_lflag &= ~ECHO;
+  tcsetattr (STDIN_FILENO, TCSANOW, &term);
+
+  return was;
+}
+
+gboolean
+flatpak_get_cursor_pos (int * row, int *col)
+{
+  fd_set readset;
+  struct timeval time;
+  struct termios term, initial_term;
+  int res = 0;
+
+  tcgetattr (STDIN_FILENO, &initial_term);
+  term = initial_term;
+  term.c_lflag &= ~ICANON;
+  term.c_lflag &= ~ECHO;
+  tcsetattr (STDIN_FILENO, TCSANOW, &term);
+
+  printf ("\033[6n");
+  fflush (stdout);
+
+  FD_ZERO (&readset);
+  FD_SET (STDIN_FILENO, &readset);
+  time.tv_sec = 0;
+  time.tv_usec = 100000;
+
+  if (select (STDIN_FILENO + 1, &readset, NULL, NULL, &time) == 1)
+    res = scanf ("\033[%d;%dR", row, col);
+
+  tcsetattr (STDIN_FILENO, TCSADRAIN, &initial_term);
+
+  return res == 2;
+}
+
+void
+flatpak_hide_cursor (void)
+{
+  write (STDOUT_FILENO, FLATPAK_ANSI_HIDE_CURSOR, strlen (FLATPAK_ANSI_HIDE_CURSOR));
+}
+
+void
+flatpak_show_cursor (void)
+{
+  write (STDOUT_FILENO, FLATPAK_ANSI_SHOW_CURSOR, strlen (FLATPAK_ANSI_SHOW_CURSOR));
+}
+
+void
+flatpak_enable_raw_mode (void)
+{
+  struct termios raw;
+
+  tcgetattr (STDIN_FILENO, &raw);
+
+  raw.c_lflag &= ~(ECHO | ICANON);
+
+  tcsetattr (STDIN_FILENO, TCSAFLUSH, &raw);
+}
+
+void
+flatpak_disable_raw_mode (void)
+{
+  struct termios raw;
+
+  tcgetattr (STDIN_FILENO, &raw);
+
+  raw.c_lflag |= (ECHO | ICANON);
+
+  tcsetattr (STDIN_FILENO, TCSAFLUSH, &raw);
+}
+
+/* Wrapper that uses ostree_repo_resolve_collection_ref() and on failure falls
+ * back to using ostree_repo_resolve_rev() for backwards compatibility. This
+ * means we support refs/heads/, refs/remotes/, and refs/mirrors/. */
+gboolean
+flatpak_repo_resolve_rev (OstreeRepo    *repo,
+                          const char    *collection_id, /* nullable */
+                          const char    *remote_name, /* nullable */
+                          const char    *ref_name,
+                          gboolean       allow_noent,
+                          char         **out_rev,
+                          GCancellable  *cancellable,
+                          GError       **error)
+{
+  g_autoptr(GError) local_error = NULL;
+
+  if (collection_id != NULL)
+    {
+      /* Do a version check to ensure we have these:
+       * https://github.com/ostreedev/ostree/pull/1821
+       * https://github.com/ostreedev/ostree/pull/1825 */
+#if OSTREE_CHECK_VERSION (2019, 2)
+      const OstreeCollectionRef c_r =
+        {
+          .collection_id = (char *) collection_id,
+          .ref_name = (char *) ref_name,
+        };
+      OstreeRepoResolveRevExtFlags flags = remote_name == NULL ?
+                                           OSTREE_REPO_RESOLVE_REV_EXT_LOCAL_ONLY :
+                                           OSTREE_REPO_RESOLVE_REV_EXT_NONE;
+      if (ostree_repo_resolve_collection_ref (repo, &c_r,
+                                              allow_noent,
+                                              flags,
+                                              out_rev,
+                                              cancellable, NULL))
+        return TRUE;
+#endif
+    }
+
+  /* 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_name != NULL)
+    {
+      g_autofree char *refspec = g_strdup_printf ("%s:%s", remote_name, ref_name);
+      ostree_repo_resolve_rev (repo, refspec, allow_noent, out_rev, &local_error);
+    }
+  else
+    ostree_repo_resolve_rev_ext (repo, ref_name, allow_noent,
+                                 OSTREE_REPO_RESOLVE_REV_EXT_NONE, out_rev, &local_error);
+
+  if (local_error != NULL)
+    {
+      if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+        flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND, "%s", local_error->message);
+      else
+        g_propagate_error (error, g_steal_pointer (&local_error));
+
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+
+#if !GLIB_CHECK_VERSION (2, 56, 0)
+/* All this code is backported directly from glib */
+
+static void
+g_date_time_get_week_number (GDateTime *datetime,
+                             gint      *week_number,
+                             gint      *day_of_week,
+                             gint      *day_of_year)
+{
+  gint a, b, c, d, e, f, g, n, s, month, day, year;
+
+  g_date_time_get_ymd (datetime, &year, &month, &day);
+
+  if (month <= 2)
+    {
+      a = g_date_time_get_year (datetime) - 1;
+      b = (a / 4) - (a / 100) + (a / 400);
+      c = ((a - 1) / 4) - ((a - 1) / 100) + ((a - 1) / 400);
+      s = b - c;
+      e = 0;
+      f = day - 1 + (31 * (month - 1));
+    }
+  else
+    {
+      a = year;
+      b = (a / 4) - (a / 100) + (a / 400);
+      c = ((a - 1) / 4) - ((a - 1) / 100) + ((a - 1) / 400);
+      s = b - c;
+      e = s + 1;
+      f = day + (((153 * (month - 3)) + 2) / 5) + 58 + s;
+    }
+
+  g = (a + b) % 7;
+  d = (f + g - e) % 7;
+  n = f + 3 - d;
+
+  if (week_number)
+    {
+      if (n < 0)
+        *week_number = 53 - ((g - s) / 5);
+      else if (n > 364 + s)
+        *week_number = 1;
+      else
+        *week_number = (n / 7) + 1;
+    }
+
+  if (day_of_week)
+    *day_of_week = d + 1;
+
+  if (day_of_year)
+    *day_of_year = f + 1;
+}
+
+#define GREGORIAN_LEAP(y)    ((((y) % 4) == 0) && (!((((y) % 100) == 0) && (((y) % 400) != 0))))
+
+/* Parse integers in the form d (week days), dd (hours etc), ddd (ordinal days) or dddd (years) */
+static gboolean
+get_iso8601_int (const gchar *text, gsize length, gint *value)
+{
+  gint i, v = 0;
+
+  if (length < 1 || length > 4)
+    return FALSE;
+
+  for (i = 0; i < length; i++)
+    {
+      const gchar c = text[i];
+      if (c < '0' || c > '9')
+        return FALSE;
+      v = v * 10 + (c - '0');
+    }
+
+  *value = v;
+  return TRUE;
+}
+
+/* Parse seconds in the form ss or ss.sss (variable length decimal) */
+static gboolean
+get_iso8601_seconds (const gchar *text, gsize length, gdouble *value)
+{
+  gint i;
+  gdouble divisor = 1, v = 0;
+
+  if (length < 2)
+    return FALSE;
+
+  for (i = 0; i < 2; i++)
+    {
+      const gchar c = text[i];
+      if (c < '0' || c > '9')
+        return FALSE;
+      v = v * 10 + (c - '0');
+    }
+
+  if (length > 2 && !(text[i] == '.' || text[i] == ','))
+    return FALSE;
+  i++;
+  if (i == length)
+    return FALSE;
+
+  for (; i < length; i++)
+    {
+      const gchar c = text[i];
+      if (c < '0' || c > '9')
+        return FALSE;
+      v = v * 10 + (c - '0');
+      divisor *= 10;
+    }
+
+  *value = v / divisor;
+  return TRUE;
+}
+
+static GDateTime *
+g_date_time_new_ordinal (GTimeZone *tz, gint year, gint ordinal_day, gint hour, gint minute, gdouble seconds)
+{
+  GDateTime *dt, *dt2;
+
+  if (ordinal_day < 1 || ordinal_day > (GREGORIAN_LEAP (year) ? 366 : 365))
+    return NULL;
+
+  dt = g_date_time_new (tz, year, 1, 1, hour, minute, seconds);
+  dt2 = g_date_time_add_days (dt, ordinal_day - 1);
+  g_date_time_unref (dt);
+
+  return dt2;
+}
+
+static GDateTime *
+g_date_time_new_week (GTimeZone *tz, gint year, gint week, gint week_day, gint hour, gint minute, gdouble seconds)
+{
+  gint64 p;
+  gint max_week, jan4_week_day, ordinal_day;
+  GDateTime *dt;
+
+  p = (year * 365 + (year / 4) - (year / 100) + (year / 400)) % 7;
+  max_week = p == 4 ? 53 : 52;
+
+  if (week < 1 || week > max_week || week_day < 1 || week_day > 7)
+    return NULL;
+
+  dt = g_date_time_new (tz, year, 1, 4, 0, 0, 0);
+  g_date_time_get_week_number (dt, NULL, &jan4_week_day, NULL);
+  g_date_time_unref (dt);
+
+  ordinal_day = (week * 7) + week_day - (jan4_week_day + 3);
+  if (ordinal_day < 0)
+    {
+      year--;
+      ordinal_day += GREGORIAN_LEAP (year) ? 366 : 365;
+    }
+  else if (ordinal_day > (GREGORIAN_LEAP (year) ? 366 : 365))
+    {
+      ordinal_day -= (GREGORIAN_LEAP (year) ? 366 : 365);
+      year++;
+    }
+
+  return g_date_time_new_ordinal (tz, year, ordinal_day, hour, minute, seconds);
+}
+
+static GDateTime *
+parse_iso8601_date (const gchar *text, gsize length,
+                    gint hour, gint minute, gdouble seconds, GTimeZone *tz)
+{
+  /* YYYY-MM-DD */
+  if (length == 10 && text[4] == '-' && text[7] == '-')
+    {
+      int year, month, day;
+      if (!get_iso8601_int (text, 4, &year) ||
+          !get_iso8601_int (text + 5, 2, &month) ||
+          !get_iso8601_int (text + 8, 2, &day))
+        return NULL;
+      return g_date_time_new (tz, year, month, day, hour, minute, seconds);
+    }
+  /* YYYY-DDD */
+  else if (length == 8 && text[4] == '-')
+    {
+      gint year, ordinal_day;
+      if (!get_iso8601_int (text, 4, &year) ||
+          !get_iso8601_int (text + 5, 3, &ordinal_day))
+        return NULL;
+      return g_date_time_new_ordinal (tz, year, ordinal_day, hour, minute, seconds);
+    }
+  /* YYYY-Www-D */
+  else if (length == 10 && text[4] == '-' && text[5] == 'W' && text[8] == '-')
+    {
+      gint year, week, week_day;
+      if (!get_iso8601_int (text, 4, &year) ||
+          !get_iso8601_int (text + 6, 2, &week) ||
+          !get_iso8601_int (text + 9, 1, &week_day))
+        return NULL;
+      return g_date_time_new_week (tz, year, week, week_day, hour, minute, seconds);
+    }
+  /* YYYYWwwD */
+  else if (length == 8 && text[4] == 'W')
+    {
+      gint year, week, week_day;
+      if (!get_iso8601_int (text, 4, &year) ||
+          !get_iso8601_int (text + 5, 2, &week) ||
+          !get_iso8601_int (text + 7, 1, &week_day))
+        return NULL;
+      return g_date_time_new_week (tz, year, week, week_day, hour, minute, seconds);
+    }
+  /* YYYYMMDD */
+  else if (length == 8)
+    {
+      int year, month, day;
+      if (!get_iso8601_int (text, 4, &year) ||
+          !get_iso8601_int (text + 4, 2, &month) ||
+          !get_iso8601_int (text + 6, 2, &day))
+        return NULL;
+      return g_date_time_new (tz, year, month, day, hour, minute, seconds);
+    }
+  /* YYYYDDD */
+  else if (length == 7)
+    {
+      gint year, ordinal_day;
+      if (!get_iso8601_int (text, 4, &year) ||
+          !get_iso8601_int (text + 4, 3, &ordinal_day))
+        return NULL;
+      return g_date_time_new_ordinal (tz, year, ordinal_day, hour, minute, seconds);
+    }
+  else
+    return FALSE;
+}
+
+static GTimeZone *
+parse_iso8601_timezone (const gchar *text, gsize length, gssize *tz_offset)
+{
+  gint i, tz_length, offset_sign = 1, offset_hours, offset_minutes;
+  GTimeZone *tz;
+
+  /* UTC uses Z suffix  */
+  if (length > 0 && text[length - 1] == 'Z')
+    {
+      *tz_offset = length - 1;
+      return g_time_zone_new_utc ();
+    }
+
+  /* Look for '+' or '-' of offset */
+  for (i = length - 1; i >= 0; i--)
+    if (text[i] == '+' || text[i] == '-')
+      {
+        offset_sign = text[i] == '-' ? -1 : 1;
+        break;
+      }
+  if (i < 0)
+    return NULL;
+  tz_length = length - i;
+
+  /* +hh:mm or -hh:mm */
+  if (tz_length == 6 && text[i + 3] == ':')
+    {
+      if (!get_iso8601_int (text + i + 1, 2, &offset_hours) ||
+          !get_iso8601_int (text + i + 4, 2, &offset_minutes))
+        return NULL;
+    }
+  /* +hhmm or -hhmm */
+  else if (tz_length == 5)
+    {
+      if (!get_iso8601_int (text + i + 1, 2, &offset_hours) ||
+          !get_iso8601_int (text + i + 3, 2, &offset_minutes))
+        return NULL;
+    }
+  /* +hh or -hh */
+  else if (tz_length == 3)
+    {
+      if (!get_iso8601_int (text + i + 1, 2, &offset_hours))
+        return NULL;
+      offset_minutes = 0;
+    }
+  else
+    return NULL;
+
+  *tz_offset = i;
+  tz = g_time_zone_new (text + i);
+
+  /* Double-check that the GTimeZone matches our interpretation of the timezone.
+   * Failure would indicate a bug either here of in the GTimeZone code. */
+  g_assert (g_time_zone_get_offset (tz, 0) == offset_sign * (offset_hours * 3600 + offset_minutes * 60));
+
+  return tz;
+}
+
+static gboolean
+parse_iso8601_time (const gchar *text, gsize length,
+                    gint *hour, gint *minute, gdouble *seconds, GTimeZone **tz)
+{
+  gssize tz_offset = -1;
+
+  /* Check for timezone suffix */
+  *tz = parse_iso8601_timezone (text, length, &tz_offset);
+  if (tz_offset >= 0)
+    length = tz_offset;
+
+  /* hh:mm:ss(.sss) */
+  if (length >= 8 && text[2] == ':' && text[5] == ':')
+    {
+      return get_iso8601_int (text, 2, hour) &&
+             get_iso8601_int (text + 3, 2, minute) &&
+             get_iso8601_seconds (text + 6, length - 6, seconds);
+    }
+  /* hhmmss(.sss) */
+  else if (length >= 6)
+    {
+      return get_iso8601_int (text, 2, hour) &&
+             get_iso8601_int (text + 2, 2, minute) &&
+             get_iso8601_seconds (text + 4, length - 4, seconds);
+    }
+  else
+    return FALSE;
+}
+
+
+GDateTime *
+flatpak_g_date_time_new_from_iso8601 (const gchar *text, GTimeZone *default_tz)
+{
+  gint length, date_length = -1;
+  gint hour = 0, minute = 0;
+  gdouble seconds = 0.0;
+  GTimeZone *tz = NULL;
+  GDateTime *datetime = NULL;
+
+  g_return_val_if_fail (text != NULL, NULL);
+
+  /* Count length of string and find date / time separator ('T', 't', or ' ') */
+  for (length = 0; text[length] != '\0'; length++)
+    {
+      if (date_length < 0 && (text[length] == 'T' || text[length] == 't' || text[length] == ' '))
+        date_length = length;
+    }
+
+  if (date_length < 0)
+    return NULL;
+
+  if (!parse_iso8601_time (text + date_length + 1, length - (date_length + 1),
+                           &hour, &minute, &seconds, &tz))
+    goto out;
+  if (tz == NULL && default_tz == NULL)
+    return NULL;
+
+  datetime = parse_iso8601_date (text, date_length, hour, minute, seconds, tz ? tz : default_tz);
+
+out:
+  if (tz != NULL)
+    g_time_zone_unref (tz);
+  return datetime;
+}
+#endif
+
+/* Convert an app id to a dconf path in the obvious way.
+ */
+char *
+flatpak_dconf_path_for_app_id (const char *app_id)
+{
+  GString *s;
+  const char *p;
+
+  s = g_string_new ("");
+
+  g_string_append_c (s, '/');
+  for (p = app_id; *p; p++)
+    {
+      if (*p == '.')
+        g_string_append_c (s, '/');
+      else
+        g_string_append_c (s, *p);
+    }
+  g_string_append_c (s, '/');
+
+  return g_string_free (s, FALSE);
+}
+
+/* Check if two dconf paths are 'similar enough', which
+ * for now is defined as equal except case differences
+ * and -/_
+ */
+gboolean
+flatpak_dconf_path_is_similar (const char *path1,
+                               const char *path2)
+{
+  int i, i1, i2;
+  int num_components = -1;
+
+  for (i = 0; path1[i] != '\0'; i++)
+    {
+      if (path2[i] == '\0')
+        break;
+
+      if (tolower (path1[i]) == tolower (path2[i]))
+        {
+          if (path1[i] == '/')
+            num_components++;
+          continue;
+        }
+
+      if ((path1[i] == '-' || path1[i] == '_') &&
+          (path2[i] == '-' || path2[i] == '_'))
+        continue;
+
+      break;
+    }
+
+  /* Skip over any versioning if we have at least a TLD and
+   * domain name, so 2 components */
+  /* We need at least TLD, and domain name, so 2 components */
+  i1 = i2 = i;
+  if (num_components >= 2)
+    {
+      while (isdigit (path1[i1]))
+        i1++;
+      while (isdigit (path2[i2]))
+        i2++;
+    }
+
+  if (path1[i1] != path2[i2])
+    return FALSE;
+
+  /* Both strings finished? */
+  if (path1[i1] == '\0')
+    return TRUE;
+
+  /* Maybe a trailing slash in both strings */
+  if (path1[i1] == '/')
+    {
+      i1++;
+      i2++;
+    }
+
+  if (path1[i1] != path2[i2])
+    return FALSE;
+
+  return (path1[i1] == '\0');
+}
+
+#endif
diff --git a/src/glib-backports.c b/src/glib-backports.c
index ce4990ee15acbd95fbe213285a2e28c35370ea39..3a4694733a39a729ba1634674b036f148aa4ecfb 100644
--- a/src/glib-backports.c
+++ b/src/glib-backports.c
@@ -33,9 +33,6 @@
 
 #include <glib-object.h>
 
-/* We have no internationalization */
-#define _(x) x
-
 #if !GLIB_CHECK_VERSION (2, 34, 0)
 G_DEFINE_QUARK (g-spawn-exit-error-quark, my_g_spawn_exit_error)
 #endif
@@ -490,3 +487,26 @@ my_g_canonicalize_filename (const gchar *filename,
   return canon;
 }
 #endif
+
+#if !GLIB_CHECK_VERSION(2, 40, 0)
+gpointer *
+my_g_hash_table_get_keys_as_array (GHashTable *hash,
+                                   guint *len)
+{
+  GPtrArray *arr = g_ptr_array_sized_new (g_hash_table_size (hash));
+  GHashTableIter iter;
+  gpointer k;
+
+  g_hash_table_iter_init (&iter, hash);
+
+  while (g_hash_table_iter_next (&iter, &k, NULL))
+    g_ptr_array_add (arr, k);
+
+  if (len != NULL)
+    *len = arr->len;
+
+  g_ptr_array_add (arr, NULL);
+
+  return g_ptr_array_free (arr, FALSE);
+}
+#endif
diff --git a/src/glib-backports.h b/src/glib-backports.h
index 78f7fe164f4b3566c4a3b69ee79255144234b766..73a587bce98758cfdeb7e0ab4322810c57b16af6 100644
--- a/src/glib-backports.h
+++ b/src/glib-backports.h
@@ -100,3 +100,10 @@ GSource *my_g_unix_fd_source_new (int fd,
 gchar *my_g_canonicalize_filename (const gchar *filename,
                                    const gchar *relative_to);
 #endif
+
+#if !GLIB_CHECK_VERSION(2, 40, 0)
+#define g_hash_table_get_keys_as_array(h, l) \
+  my_g_hash_table_get_keys_as_array (h, l)
+gpointer *my_g_hash_table_get_keys_as_array (GHashTable *hash,
+                                             guint *len);
+#endif
diff --git a/src/meson.build b/src/meson.build
index 27242f101364c88817a4708058217a7ca2f4bd72..2529a228306c95e01ff30000e661c0efb762c600 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -148,6 +148,11 @@ executable(
     'bwrap.h',
     'flatpak-bwrap.c',
     'flatpak-bwrap-private.h',
+    'flatpak-common-types-private.h',
+    'flatpak-context.c',
+    'flatpak-context-private.h',
+    'flatpak-exports.c',
+    'flatpak-exports-private.h',
     'flatpak-run.c',
     'flatpak-run-private.h',
     'runtime.c',