diff --git a/debian/copyright b/debian/copyright
index a8314ce2e38d53b7089fc27baed213de21d49400..126f0c7fce746dbb0534be66c5fb68da5e4f88c3 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -18,14 +18,6 @@ Copyright:
  2017-2019 Collabora Ltd.
 License: LGPL-2.1+
 
-Files:
- src/*
- tests/shellcheck.sh
-Copyright:
- 2014-2019 Red Hat, Inc
- 2017-2019 Collabora Ltd.
-License: LGPL-2.1+
-
 Files:
  build-aux/git-version-gen
 Copyright:
diff --git a/meson.build b/meson.build
index d9dd40928f99a6f96f93a3b70b80c150ed76bfe6..6276537a4e93ab6cf2077d81b9b91a17bff8d17a 100644
--- a/meson.build
+++ b/meson.build
@@ -155,15 +155,21 @@ configure_file(
 executable(
   'pressure-vessel-wrap',
   sources : [
+    'src/bwrap.c',
+    'src/bwrap.h',
     'src/glib-backports.c',
     'src/glib-backports.h',
     'src/flatpak-bwrap.c',
     'src/flatpak-bwrap-private.h',
+    'src/flatpak-run.c',
+    'src/flatpak-run-private.h',
     'src/flatpak-utils.c',
     'src/flatpak-utils-private.h',
     'src/utils.c',
     'src/utils.h',
     'src/wrap.c',
+    'src/wrap-interactive.c',
+    'src/wrap-interactive.h',
   ],
   c_args : [
     '-Wno-unused-local-typedefs',
diff --git a/src/bwrap.c b/src/bwrap.c
new file mode 100644
index 0000000000000000000000000000000000000000..c882decec4fb09568c65267e7383160309840353
--- /dev/null
+++ b/src/bwrap.c
@@ -0,0 +1,407 @@
+/*
+ * Copyright © 2017-2019 Collabora Ltd.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "subprojects/libglnx/config.h"
+
+#include <ftw.h>
+
+#include <gio/gio.h>
+
+#include "bwrap.h"
+
+/**
+ * pv_bwrap_run_sync:
+ * @bwrap: A #FlatpakBwrap on which flatpak_bwrap_finish() has been called
+ * @exit_status_out: (out) (optional): Used to return the exit status,
+ *  or -1 if it could not be launched or was killed by a signal
+ * @error: Used to raise an error on failure
+ *
+ * Try to run a command. Its standard output and standard error go to
+ * pressure-vessel's own stdout and stderr.
+ *
+ * Returns: %TRUE if the subprocess runs to completion
+ */
+gboolean
+pv_bwrap_run_sync (FlatpakBwrap *bwrap,
+                   int *exit_status_out,
+                   GError **error)
+{
+  gint exit_status;
+  g_autofree gchar *output = NULL;
+  g_autofree gchar *errors = NULL;
+  guint i;
+  g_autoptr(GString) command = g_string_new ("");
+  void (*child_setup) (gpointer) = NULL;
+  gpointer child_setup_data = NULL;
+
+  g_return_val_if_fail (bwrap != NULL, FALSE);
+  g_return_val_if_fail (bwrap->argv->len >= 2, FALSE);
+  g_return_val_if_fail (pv_bwrap_was_finished (bwrap), FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  if (exit_status_out != NULL)
+    *exit_status_out = -1;
+
+  for (i = 0; i < bwrap->argv->len; i++)
+    {
+      g_autofree gchar *quoted = NULL;
+      char *unquoted = g_ptr_array_index (bwrap->argv, i);
+
+      if (unquoted == NULL)
+        break;
+
+      quoted = g_shell_quote (unquoted);
+      g_string_append_printf (command, " %s", quoted);
+    }
+
+  if (bwrap->fds != NULL && bwrap->fds->len > 0)
+    {
+      child_setup = flatpak_bwrap_child_setup_cb;
+      child_setup_data = bwrap->fds;
+    }
+
+  g_debug ("run:%s", command->str);
+
+  if (!g_spawn_sync (NULL,  /* cwd */
+                     (char **) bwrap->argv->pdata,
+                     bwrap->envp,
+                     G_SPAWN_SEARCH_PATH,
+                     child_setup, child_setup_data,
+                     &output,
+                     &errors,
+                     &exit_status,
+                     error))
+    return FALSE;
+
+  g_print ("%s", output);
+  g_printerr ("%s", errors);
+
+  if (exit_status_out != NULL)
+    {
+      if (WIFEXITED (exit_status))
+        *exit_status_out = WEXITSTATUS (exit_status);
+    }
+
+  if (!g_spawn_check_exit_status (exit_status, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+/**
+ * pv_bwrap_execve:
+ * @bwrap: A #FlatpakBwrap on which flatpak_bwrap_finish() has been called
+ * @error: Used to raise an error on failure
+ *
+ * Attempt to replace the current process with the given bwrap command.
+ * If unable to do so, raise an error.
+ *
+ * Returns: %FALSE
+ */
+gboolean
+pv_bwrap_execve (FlatpakBwrap *bwrap,
+                 GError **error)
+{
+  int saved_errno;
+
+  g_return_val_if_fail (bwrap != NULL, FALSE);
+  g_return_val_if_fail (bwrap->argv->len >= 2, FALSE);
+  g_return_val_if_fail (pv_bwrap_was_finished (bwrap), FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  g_debug ("Replacing self with bwrap...");
+
+  if (bwrap->fds != NULL && bwrap->fds->len > 0)
+    flatpak_bwrap_child_setup_cb (bwrap->fds);
+
+  execve (bwrap->argv->pdata[0],
+          (char * const *) bwrap->argv->pdata,
+          bwrap->envp);
+  saved_errno = errno;
+
+  /* If we are still here then execve failed */
+  g_set_error (error,
+               G_IO_ERROR,
+               g_io_error_from_errno (saved_errno),
+               "Error replacing self with bwrap: %s",
+               g_strerror (saved_errno));
+  return FALSE;
+}
+
+/**
+ * pv_bwrap_bind_usr:
+ * @bwrap: The #FlatpakBwrap
+ * @host_path: Absolute path to a directory in the host filesystem
+ * @mount_point: Absolute path of the location at which to mount @host_path
+ *  in the container
+ * @error: Used to raise an error on failure
+ *
+ * Append arguments to @bwrap that will bind-mount `/usr` and associated
+ * directories from @host_path into @mount_point.
+ *
+ * If @host_path contains a `usr` directory, it is assumed to be a
+ * system root. Its `usr` directory is mounted on `${mount_point}/usr`
+ * in the container. Its `lib*`, `bin` and `sbin` directories are
+ * created as symbolic links in @mount_point, or mounted on subdirectories
+ * of @mount_point, as appropriate.
+ *
+ * If @host_path does not contain a `usr` directory, it is assumed to be
+ * a merged `/usr`. It is mounted on `${mount_point}/usr`, and `lib*`,
+ * `bin` and `sbin` symbolic links are created in @mount_point.
+ *
+ * In either case, if @host_path` contains `etc/alternatives` and/or
+ * `etc/ld.so.cache`, they are mounted on corresponding paths under
+ * @mount_point.
+ *
+ * Returns: %TRUE on success
+ */
+gboolean
+pv_bwrap_bind_usr (FlatpakBwrap *bwrap,
+                   const char *host_path,
+                   const char *mount_point,
+                   GError **error)
+{
+  g_autofree gchar *usr = NULL;
+  g_autofree gchar *dest = NULL;
+  gboolean host_path_is_usr = FALSE;
+  g_autoptr(GDir) dir = NULL;
+  const gchar *member = NULL;
+  static const char * const bind_etc[] =
+  {
+    "alternatives",
+    "ld.so.cache"
+  };
+  gsize i;
+
+  g_return_val_if_fail (bwrap != NULL, FALSE);
+  g_return_val_if_fail (!pv_bwrap_was_finished (bwrap), FALSE);
+  g_return_val_if_fail (host_path != NULL, FALSE);
+  g_return_val_if_fail (host_path[0] == '/', FALSE);
+  g_return_val_if_fail (mount_point != NULL, FALSE);
+  g_return_val_if_fail (mount_point[0] == '/', FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  usr = g_build_filename (host_path, "usr", NULL);
+  dest = g_build_filename (mount_point, "usr", NULL);
+
+  if (g_file_test (usr, G_FILE_TEST_IS_DIR))
+    {
+      flatpak_bwrap_add_args (bwrap,
+                              "--ro-bind", usr, dest,
+                              NULL);
+    }
+  else
+    {
+      /* host_path is assumed to be a merged /usr */
+      host_path_is_usr = TRUE;
+      flatpak_bwrap_add_args (bwrap,
+                              "--ro-bind", host_path, dest,
+                              NULL);
+    }
+
+  g_clear_pointer (&dest, g_free);
+
+  dir = g_dir_open (host_path, 0, error);
+
+  if (dir == NULL)
+    return FALSE;
+
+  for (member = g_dir_read_name (dir);
+       member != NULL;
+       member = g_dir_read_name (dir))
+    {
+      if (g_str_has_prefix (member, "lib")
+          || g_str_equal (member, "bin")
+          || g_str_equal (member, "sbin"))
+        {
+          dest = g_build_filename (mount_point, member, NULL);
+
+          if (host_path_is_usr)
+            {
+              g_autofree gchar *target = g_build_filename ("usr",
+                                                           member, NULL);
+
+              flatpak_bwrap_add_args (bwrap,
+                                      "--symlink", target, dest,
+                                      NULL);
+            }
+          else
+            {
+              g_autofree gchar *path = g_build_filename (host_path,
+                                                         member, NULL);
+              g_autofree gchar *target = glnx_readlinkat_malloc (-1, path, NULL, NULL);
+
+              if (target != NULL)
+                {
+                  flatpak_bwrap_add_args (bwrap,
+                                          "--symlink", target, dest,
+                                          NULL);
+                }
+              else
+                {
+                  flatpak_bwrap_add_args (bwrap,
+                                          "--ro-bind", path, dest,
+                                          NULL);
+                }
+            }
+
+          g_clear_pointer (&dest, g_free);
+        }
+    }
+
+  for (i = 0; i < G_N_ELEMENTS (bind_etc); i++)
+    {
+      g_autofree gchar *path = g_build_filename (host_path, "etc",
+                                                 bind_etc[i], NULL);
+
+      if (g_file_test (path, G_FILE_TEST_EXISTS))
+        {
+          dest = g_build_filename (mount_point, "etc", bind_etc[i], NULL);
+
+          flatpak_bwrap_add_args (bwrap,
+                                  "--ro-bind", path, dest,
+                                  NULL);
+
+          g_clear_pointer (&dest, g_free);
+        }
+    }
+
+  return TRUE;
+}
+
+/* nftw() doesn't have a user_data argument so we need to use a global
+ * variable :-( */
+static struct
+{
+  FlatpakBwrap *bwrap;
+  const char *source;
+  const char *dest;
+} nftw_data;
+
+static int
+copy_tree_helper (const char *fpath,
+                  const struct stat *sb,
+                  int typeflag,
+                  struct FTW *ftwbuf)
+{
+  const char *path_in_container;
+  g_autofree gchar *target = NULL;
+  gsize prefix_len;
+  int fd = -1;
+  GError *error = NULL;
+
+  g_return_val_if_fail (g_str_has_prefix (fpath, nftw_data.source), 1);
+  g_return_val_if_fail (g_str_has_suffix (nftw_data.source, nftw_data.dest), 1);
+
+  prefix_len = strlen (nftw_data.source) - strlen (nftw_data.dest);
+  path_in_container = fpath + prefix_len;
+
+  switch (typeflag)
+    {
+      case FTW_D:
+        flatpak_bwrap_add_args (nftw_data.bwrap,
+                                "--dir", path_in_container,
+                                NULL);
+        break;
+
+      case FTW_SL:
+        target = glnx_readlinkat_malloc (-1, fpath, NULL, NULL);
+        flatpak_bwrap_add_args (nftw_data.bwrap,
+                                "--symlink", target, path_in_container,
+                                NULL);
+        break;
+
+      case FTW_F:
+        if (!glnx_openat_rdonly (AT_FDCWD, fpath, FALSE, &fd, &error))
+          {
+            g_warning ("Unable to copy file into container: %s",
+                       error->message);
+            g_clear_error (&error);
+          }
+
+        flatpak_bwrap_add_args_data_fd (nftw_data.bwrap,
+                                        "--ro-bind-data", glnx_steal_fd (&fd),
+                                        path_in_container);
+        break;
+
+      default:
+        g_warning ("Don't know how to handle ftw type flag %d at %s",
+                   typeflag, fpath);
+    }
+
+  return 0;
+}
+
+/**
+ * pv_bwrap_copy_tree:
+ * @bwrap: The #FlatpakBwrap
+ * @source: A copy of the desired @dest in a temporary directory,
+ *  for example `/tmp/tmp12345678/overrides/lib`. The path must end
+ *  with @dest.
+ * @dest: The destination path in the container, which must be absolute.
+ *
+ * For every file, directory or symbolic link in @source, add a
+ * corresponding read-only file, directory or symbolic link via the bwrap
+ * command-line, so that the files, directories and symbolic links in the
+ * container will persist even after @source has been deleted.
+ */
+void
+pv_bwrap_copy_tree (FlatpakBwrap *bwrap,
+                    const char *source,
+                    const char *dest)
+{
+  g_return_if_fail (nftw_data.bwrap == NULL);
+  g_return_if_fail (dest[0] == '/');
+  g_return_if_fail (g_str_has_suffix (source, dest));
+
+  nftw_data.bwrap = bwrap;
+  nftw_data.source = source;
+  nftw_data.dest = dest;
+  nftw (source, copy_tree_helper, 100, FTW_PHYS);
+  nftw_data.bwrap = NULL;
+  nftw_data.source = NULL;
+  nftw_data.dest = NULL;
+}
+
+/**
+ * pv_bwrap_add_api_filesystems:
+ * @bwrap: The #FlatpakBwrap
+ *
+ * Make basic API filesystems available.
+ */
+void
+pv_bwrap_add_api_filesystems (FlatpakBwrap *bwrap)
+{
+  flatpak_bwrap_add_args (bwrap,
+                          "--dev-bind", "/dev", "/dev",
+                          "--proc", "/proc",
+                          "--ro-bind", "/sys", "/sys",
+                          NULL);
+
+  if (g_file_test ("/dev/pts", G_FILE_TEST_EXISTS))
+    flatpak_bwrap_add_args (bwrap,
+                            "--dev-bind", "/dev/pts", "/dev/pts",
+                            NULL);
+
+  if (g_file_test ("/dev/shm", G_FILE_TEST_EXISTS))
+    flatpak_bwrap_add_args (bwrap,
+                            "--dev-bind", "/dev/shm", "/dev/shm",
+                            NULL);
+}
diff --git a/src/bwrap.h b/src/bwrap.h
new file mode 100644
index 0000000000000000000000000000000000000000..492d89670267ff3d3869f4faf8fbc3352c05162d
--- /dev/null
+++ b/src/bwrap.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright © 2017-2019 Collabora Ltd.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library 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/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "libglnx.h"
+
+#include "flatpak-bwrap-private.h"
+#include "glib-backports.h"
+
+gboolean pv_bwrap_run_sync (FlatpakBwrap *bwrap,
+                            int *exit_status_out,
+                            GError **error);
+gboolean pv_bwrap_execve (FlatpakBwrap *bwrap,
+                          GError **error);
+gboolean pv_bwrap_bind_usr (FlatpakBwrap *bwrap,
+                            const char *host_path,
+                            const char *mount_point,
+                            GError **error);
+void pv_bwrap_copy_tree (FlatpakBwrap *bwrap,
+                         const char *source,
+                         const char *dest);
+void pv_bwrap_add_api_filesystems (FlatpakBwrap *bwrap);
+
+static inline gboolean
+pv_bwrap_was_finished (FlatpakBwrap *bwrap)
+{
+  g_return_val_if_fail (bwrap != NULL, FALSE);
+
+  return (bwrap->argv->len >= 1 &&
+          bwrap->argv->pdata[bwrap->argv->len - 1] == NULL);
+}
diff --git a/src/flatpak-run-private.h b/src/flatpak-run-private.h
new file mode 100644
index 0000000000000000000000000000000000000000..78b5e372efd52fbf838e156bc4fa7894fc141078
--- /dev/null
+++ b/src/flatpak-run-private.h
@@ -0,0 +1,38 @@
+/*
+ * Cut-down version of common/flatpak-run-private.h from Flatpak
+ *
+ * Copyright © 2017-2019 Collabora Ltd.
+ * Copyright © 2014-2019 Red Hat, Inc
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library 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/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "libglnx.h"
+
+#include "flatpak-bwrap-private.h"
+#include "glib-backports.h"
+
+void flatpak_run_add_x11_args (FlatpakBwrap *bwrap,
+                               gboolean      allowed);
+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);
diff --git a/src/flatpak-run.c b/src/flatpak-run.c
new file mode 100644
index 0000000000000000000000000000000000000000..a07380b9db75d5581558be649bfdda9eab9e42d8
--- /dev/null
+++ b/src/flatpak-run.c
@@ -0,0 +1,458 @@
+/*
+ * Cut-down version of common/flatpak-run.c from Flatpak
+ *
+ * Copyright © 2017-2019 Collabora Ltd.
+ * Copyright © 2014-2019 Red Hat, Inc
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "subprojects/libglnx/config.h"
+
+#include "flatpak-run-private.h"
+
+#include <sys/utsname.h>
+
+#include <X11/Xauth.h>
+#include <gio/gio.h>
+
+#include "flatpak-utils-private.h"
+
+/* In Flatpak this is optional, in pressure-vessel not so much */
+#define ENABLE_XAUTH
+
+static char *
+extract_unix_path_from_dbus_address (const char *address)
+{
+  const char *path, *path_end;
+
+  if (address == NULL)
+    return NULL;
+
+  if (!g_str_has_prefix (address, "unix:"))
+    return NULL;
+
+  path = strstr (address, "path=");
+  if (path == NULL)
+    return NULL;
+  path += strlen ("path=");
+  path_end = path;
+  while (*path_end != 0 && *path_end != ',')
+    path_end++;
+
+  return g_strndup (path, path_end - path);
+}
+
+#ifdef ENABLE_XAUTH
+static gboolean
+auth_streq (char *str,
+            char *au_str,
+            int   au_len)
+{
+  return au_len == strlen (str) && memcmp (str, au_str, au_len) == 0;
+}
+
+static gboolean
+xauth_entry_should_propagate (Xauth *xa,
+                              char  *hostname,
+                              char  *number)
+{
+  /* ensure entry isn't for remote access */
+  if (xa->family != FamilyLocal && xa->family != FamilyWild)
+    return FALSE;
+
+  /* ensure entry is for this machine */
+  if (xa->family == FamilyLocal && !auth_streq (hostname, xa->address, xa->address_length))
+    return FALSE;
+
+  /* ensure entry is for this session */
+  if (xa->number != NULL && !auth_streq (number, xa->number, xa->number_length))
+    return FALSE;
+
+  return TRUE;
+}
+
+static void
+write_xauth (char *number, FILE *output)
+{
+  Xauth *xa, local_xa;
+  char *filename;
+  FILE *f;
+  struct utsname unames;
+
+  if (uname (&unames))
+    {
+      g_warning ("uname failed");
+      return;
+    }
+
+  filename = XauFileName ();
+  f = fopen (filename, "rb");
+  if (f == NULL)
+    return;
+
+  while (TRUE)
+    {
+      xa = XauReadAuth (f);
+      if (xa == NULL)
+        break;
+      if (xauth_entry_should_propagate (xa, unames.nodename, number))
+        {
+          local_xa = *xa;
+          if (local_xa.number)
+            {
+              local_xa.number = (char *) "99";
+              local_xa.number_length = 2;
+            }
+
+          if (!XauWriteAuth (output, &local_xa))
+            g_warning ("xauth write error");
+        }
+
+      XauDisposeAuth (xa);
+    }
+
+  fclose (f);
+}
+#endif
+
+void
+flatpak_run_add_x11_args (FlatpakBwrap *bwrap,
+                          gboolean      allowed)
+{
+  g_autofree char *x11_socket = NULL;
+  const char *display;
+
+  /* Always cover /tmp/.X11-unix, that way we never see the host one in case
+   * we have access to the host /tmp. If you request X access we'll put the right
+   * thing in this anyway.
+   */
+  flatpak_bwrap_add_args (bwrap,
+                          "--tmpfs", "/tmp/.X11-unix",
+                          NULL);
+
+  if (!allowed)
+    {
+      flatpak_bwrap_unset_env (bwrap, "DISPLAY");
+      return;
+    }
+
+  g_debug ("Allowing x11 access");
+
+  display = g_getenv ("DISPLAY");
+  if (display && display[0] == ':' && g_ascii_isdigit (display[1]))
+    {
+      const char *display_nr = &display[1];
+      const char *display_nr_end = display_nr;
+      g_autofree char *d = NULL;
+
+      while (g_ascii_isdigit (*display_nr_end))
+        display_nr_end++;
+
+      d = g_strndup (display_nr, display_nr_end - display_nr);
+      x11_socket = g_strdup_printf ("/tmp/.X11-unix/X%s", d);
+
+      flatpak_bwrap_add_args (bwrap,
+                              "--ro-bind", x11_socket, "/tmp/.X11-unix/X99",
+                              NULL);
+      flatpak_bwrap_set_env (bwrap, "DISPLAY", ":99.0", TRUE);
+
+#ifdef ENABLE_XAUTH
+      g_auto(GLnxTmpfile) xauth_tmpf  = { 0, };
+
+      if (glnx_open_anonymous_tmpfile (O_RDWR | O_CLOEXEC, &xauth_tmpf, NULL))
+        {
+          FILE *output = fdopen (xauth_tmpf.fd, "wb");
+          if (output != NULL)
+            {
+              /* fd is now owned by output, steal it from the tmpfile */
+              int tmp_fd = dup (glnx_steal_fd (&xauth_tmpf.fd));
+              if (tmp_fd != -1)
+                {
+                  g_autofree char *dest = g_strdup_printf ("/run/user/%d/Xauthority", getuid ());
+
+                  write_xauth (d, output);
+                  flatpak_bwrap_add_args_data_fd (bwrap, "--ro-bind-data", tmp_fd, dest);
+
+                  flatpak_bwrap_set_env (bwrap, "XAUTHORITY", dest, TRUE);
+                }
+
+              fclose (output);
+
+              if (tmp_fd != -1)
+                lseek (tmp_fd, 0, SEEK_SET);
+            }
+        }
+#endif
+    }
+  else
+    {
+      flatpak_bwrap_unset_env (bwrap, "DISPLAY");
+    }
+}
+
+gboolean
+flatpak_run_add_wayland_args (FlatpakBwrap *bwrap)
+{
+  const char *wayland_display;
+  g_autofree char *user_runtime_dir = flatpak_get_real_xdg_runtime_dir ();
+  g_autofree char *wayland_socket = NULL;
+  g_autofree char *sandbox_wayland_socket = NULL;
+  gboolean res = FALSE;
+  struct stat statbuf;
+
+  wayland_display = g_getenv ("WAYLAND_DISPLAY");
+  if (!wayland_display)
+    wayland_display = "wayland-0";
+
+  wayland_socket = g_build_filename (user_runtime_dir, wayland_display, NULL);
+  sandbox_wayland_socket = g_strdup_printf ("/run/user/%d/%s", getuid (), wayland_display);
+
+  if (stat (wayland_socket, &statbuf) == 0 &&
+      (statbuf.st_mode & S_IFMT) == S_IFSOCK)
+    {
+      res = TRUE;
+      flatpak_bwrap_add_args (bwrap,
+                              "--ro-bind", wayland_socket, sandbox_wayland_socket,
+                              NULL);
+    }
+  return res;
+}
+
+/* Try to find a default server from a pulseaudio confguration file */
+static char *
+flatpak_run_get_pulseaudio_server_user_config (const char *path)
+{
+  g_autoptr(GFile) file = g_file_new_for_path (path);
+  g_autoptr(GError) my_error = NULL;
+  g_autoptr(GFileInputStream) input_stream = NULL;
+  g_autoptr(GDataInputStream) data_stream = NULL;
+  size_t len;
+
+  input_stream = g_file_read (file, NULL, &my_error);
+  if (my_error)
+    {
+      g_debug ("Pulseaudio user configuration file '%s': %s", path, my_error->message);
+      return NULL;
+    }
+
+  data_stream = g_data_input_stream_new (G_INPUT_STREAM (input_stream));
+
+  while (TRUE)
+    {
+      g_autofree char *line = g_data_input_stream_read_line (data_stream, &len, NULL, NULL);
+      if (line == NULL)
+        break;
+
+      g_strchug (line);
+
+      if ((*line  == '\0') || (*line == ';') || (*line == '#'))
+        continue;
+
+      if (g_str_has_prefix (line, ".include "))
+        {
+          g_autofree char *rec_path = g_strdup (line + 9);
+          g_strstrip (rec_path);
+          char *found = flatpak_run_get_pulseaudio_server_user_config (rec_path);
+          if (found)
+            return found;
+        }
+      else if (g_str_has_prefix (line, "["))
+        {
+          return NULL;
+        }
+      else
+        {
+          g_auto(GStrv) tokens = g_strsplit (line, "=", 2);
+
+          if ((tokens[0] != NULL) && (tokens[1] != NULL))
+            {
+              g_strchomp (tokens[0]);
+              if (strcmp ("default-server", tokens[0]) == 0)
+                {
+                  g_strstrip (tokens[1]);
+                  g_debug ("Found pulseaudio socket from configuration file '%s': %s", path, tokens[1]);
+                  return g_strdup (tokens[1]);
+                }
+            }
+        }
+    }
+
+  return NULL;
+}
+
+static char *
+flatpak_run_get_pulseaudio_server (void)
+{
+  const char * pulse_clientconfig;
+  char *pulse_server;
+  g_autofree char *pulse_user_config = NULL;
+
+  pulse_server = g_strdup (g_getenv ("PULSE_SERVER"));
+  if (pulse_server)
+    return pulse_server;
+
+  pulse_clientconfig = g_getenv ("PULSE_CLIENTCONFIG");
+  if (pulse_clientconfig)
+    return flatpak_run_get_pulseaudio_server_user_config (pulse_clientconfig);
+
+  pulse_user_config = g_build_filename (g_get_user_config_dir (), "pulse/client.conf", NULL);
+  pulse_server = flatpak_run_get_pulseaudio_server_user_config (pulse_user_config);
+  if (pulse_server)
+    return pulse_server;
+
+  pulse_server = flatpak_run_get_pulseaudio_server_user_config ("/etc/pulse/client.conf");
+  if (pulse_server)
+    return pulse_server;
+
+  return NULL;
+}
+
+static char *
+flatpak_run_parse_pulse_server (const char *value)
+{
+  g_auto(GStrv) servers = g_strsplit (value, " ", 0);
+  gsize i;
+
+  for (i = 0; servers[i] != NULL; i++)
+    {
+      const char *server = servers[i];
+      if (g_str_has_prefix (server, "{"))
+        {
+          const char * closing = strstr (server, "}");
+          if (closing == NULL)
+            continue;
+          server = closing + 1;
+        }
+      if (g_str_has_prefix (server, "unix:"))
+        return g_strdup (server + 5);
+    }
+
+  return NULL;
+}
+
+void
+flatpak_run_add_pulseaudio_args (FlatpakBwrap *bwrap)
+{
+  g_autofree char *pulseaudio_server = flatpak_run_get_pulseaudio_server ();
+  g_autofree char *pulseaudio_socket = NULL;
+  g_autofree char *user_runtime_dir = flatpak_get_real_xdg_runtime_dir ();
+
+  if (pulseaudio_server)
+    pulseaudio_socket = flatpak_run_parse_pulse_server (pulseaudio_server);
+
+  if (!pulseaudio_socket)
+    pulseaudio_socket = g_build_filename (user_runtime_dir, "pulse/native", NULL);
+
+  flatpak_bwrap_unset_env (bwrap, "PULSE_SERVER");
+
+  /* SteamOS system-wide PulseAudio instance */
+  if (!g_file_test (pulseaudio_socket, G_FILE_TEST_EXISTS))
+    {
+      g_clear_pointer (&pulseaudio_socket, g_free);
+      pulseaudio_socket = g_strdup ("/var/run/pulse/native");
+    }
+
+  if (g_file_test (pulseaudio_socket, G_FILE_TEST_EXISTS))
+    {
+      gboolean share_shm = FALSE; /* TODO: When do we add this? */
+      g_autofree char *client_config = g_strdup_printf ("enable-shm=%s\n", share_shm ? "yes" : "no");
+      g_autofree char *sandbox_socket_path = g_strdup_printf ("/run/user/%d/pulse/native", getuid ());
+      g_autofree char *pulse_server = g_strdup_printf ("unix:/run/user/%d/pulse/native", getuid ());
+      g_autofree char *config_path = g_strdup_printf ("/run/user/%d/pulse/config", getuid ());
+
+      /* FIXME - error handling */
+      if (!flatpak_bwrap_add_args_data (bwrap, "pulseaudio", client_config, -1, config_path, NULL))
+        return;
+
+      flatpak_bwrap_add_args (bwrap,
+                              "--ro-bind", pulseaudio_socket, sandbox_socket_path,
+                              NULL);
+
+      flatpak_bwrap_set_env (bwrap, "PULSE_SERVER", pulse_server, TRUE);
+      flatpak_bwrap_set_env (bwrap, "PULSE_CLIENTCONFIG", config_path, TRUE);
+    }
+  else
+    g_debug ("Could not find pulseaudio socket");
+}
+
+/* Simplified from Flatpak: we never restrict access to the D-Bus system bus */
+gboolean
+flatpak_run_add_system_dbus_args (FlatpakBwrap   *app_bwrap)
+{
+  const char *dbus_address = g_getenv ("DBUS_SYSTEM_BUS_ADDRESS");
+  g_autofree char *real_dbus_address = NULL;
+  g_autofree char *dbus_system_socket = NULL;
+
+  if (dbus_address != NULL)
+    dbus_system_socket = extract_unix_path_from_dbus_address (dbus_address);
+  else if (g_file_test ("/var/run/dbus/system_bus_socket", G_FILE_TEST_EXISTS))
+    dbus_system_socket = g_strdup ("/var/run/dbus/system_bus_socket");
+
+  if (dbus_system_socket != NULL)
+    {
+      flatpak_bwrap_add_args (app_bwrap,
+                              "--ro-bind", dbus_system_socket, "/run/dbus/system_bus_socket",
+                              NULL);
+      flatpak_bwrap_set_env (app_bwrap, "DBUS_SYSTEM_BUS_ADDRESS", "unix:path=/run/dbus/system_bus_socket", TRUE);
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+/* Simplified from Flatpak: we never restrict access to the D-Bus session bus */
+gboolean
+flatpak_run_add_session_dbus_args (FlatpakBwrap   *app_bwrap)
+{
+  const char *dbus_address = g_getenv ("DBUS_SESSION_BUS_ADDRESS");
+  char *dbus_session_socket = NULL;
+  g_autofree char *sandbox_socket_path = g_strdup_printf ("/run/user/%d/bus", getuid ());
+  g_autofree char *sandbox_dbus_address = g_strdup_printf ("unix:path=/run/user/%d/bus", getuid ());
+  g_autofree char *user_runtime_dir = flatpak_get_real_xdg_runtime_dir ();
+
+  /* FIXME: upstream the use of user_runtime_dir to Flatpak */
+
+  if (dbus_address != NULL)
+    {
+      dbus_session_socket = extract_unix_path_from_dbus_address (dbus_address);
+    }
+  else
+    {
+      struct stat statbuf;
+
+      dbus_session_socket = g_build_filename (user_runtime_dir, "bus", NULL);
+
+      if (stat (dbus_session_socket, &statbuf) < 0
+          || (statbuf.st_mode & S_IFMT) != S_IFSOCK
+          || statbuf.st_uid != getuid ())
+        return FALSE;
+    }
+
+  if (dbus_session_socket != NULL)
+    {
+      flatpak_bwrap_add_args (app_bwrap,
+                              "--ro-bind", dbus_session_socket, sandbox_socket_path,
+                              NULL);
+      flatpak_bwrap_set_env (app_bwrap, "DBUS_SESSION_BUS_ADDRESS", sandbox_dbus_address, TRUE);
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
diff --git a/src/wrap-interactive.c b/src/wrap-interactive.c
new file mode 100644
index 0000000000000000000000000000000000000000..9aa931a841983d889e221bbc058ebd1a290a87c9
--- /dev/null
+++ b/src/wrap-interactive.c
@@ -0,0 +1,154 @@
+/*
+ * Copyright © 2017-2019 Collabora Ltd.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "subprojects/libglnx/config.h"
+
+#include "wrap-interactive.h"
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <glib/gstdio.h>
+
+void
+pv_bwrap_wrap_in_xterm (FlatpakBwrap *wrapped_command)
+{
+  g_return_if_fail (wrapped_command != NULL);
+
+  flatpak_bwrap_add_args (wrapped_command,
+                          "xterm", "-e",
+                          /* Original command will go here and become
+                           * the argv of xterm -e */
+                          NULL);
+}
+
+void
+pv_bwrap_wrap_interactive (FlatpakBwrap *wrapped_command,
+                           PvShell shell)
+{
+  static const char preamble[] =
+    {
+      "prgname=\"$1\"\n"
+      "shift\n"
+    };
+  const char *script = "";
+  static const char start_shell[] =
+    {
+      "echo\n"
+      "echo\n"
+      "echo\n"
+      "echo \"$1: Starting interactive shell (original command is in "
+      "\\\"\\$@\\\")\"\n"
+      "echo\n"
+      "echo\n"
+      "echo\n"
+      "exec bash -i -s \"$@\"\n"
+    };
+  gchar *command;
+
+  g_return_if_fail (wrapped_command != NULL);
+
+  switch (shell)
+    {
+      case PV_SHELL_NONE:
+        script =
+          "e=0\n"
+          "\"$@\" || e=$?\n"
+          "echo\n"
+          "echo \"Press Enter or ^D to continue...\"\n"
+          "read reply || true\n"
+          "exit \"$e\"\n";
+        break;
+
+      case PV_SHELL_AFTER:
+        script =
+          "e=0\n"
+          "\"$@\" || e=$?\n";
+        break;
+
+      case PV_SHELL_FAIL:
+        script =
+          "if \"$@\"; then exit 0; else e=\"$?\"; fi\n"
+          "echo \"$1: command exit status $e\"\n";
+        break;
+
+      case PV_SHELL_INSTEAD:
+        break;
+
+      default:
+        g_return_if_reached ();
+    }
+
+  command = g_strdup_printf ("%s%s%s",
+                             preamble,
+                             script,
+                             shell == PV_SHELL_NONE ? "" : start_shell);
+
+  flatpak_bwrap_add_args (wrapped_command,
+                          "sh", "-euc",
+                          command,
+                          "sh",   /* $0 for sh */
+                          g_get_prgname (),   /* $1 for sh */
+                          /* Original command will go here and become
+                           * the argv of command, and eventually
+                           * the argv of bash -i -s */
+                          NULL);
+}
+
+gboolean
+pv_bwrap_wrap_tty (FlatpakBwrap *wrapped_command,
+                   GError **error)
+{
+  int fd;
+
+  g_return_val_if_fail (wrapped_command != NULL, FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  fflush (stdout);
+  fflush (stderr);
+
+  fd = open ("/dev/tty", O_RDONLY);
+
+  if (fd < 0)
+    return glnx_throw_errno_prefix (error,
+                                    "Cannot open /dev/tty for reading");
+
+  if (dup2 (fd, 0) < 0)
+    return glnx_throw_errno_prefix (error, "Cannot use /dev/tty as stdin");
+
+  g_close (fd, NULL);
+
+  fd = open ("/dev/tty", O_WRONLY);
+
+  if (fd < 0)
+    return glnx_throw_errno_prefix (error, "Cannot open /dev/tty for writing");
+
+  if (dup2 (fd, 1) < 0)
+    return glnx_throw_errno_prefix (error, "Cannot use /dev/tty as stdout");
+
+  if (dup2 (fd, 2) < 0)
+    return glnx_throw_errno_prefix (error, "Cannot use /dev/tty as stderr");
+
+  g_close (fd, NULL);
+  return TRUE;
+}
diff --git a/src/wrap-interactive.h b/src/wrap-interactive.h
new file mode 100644
index 0000000000000000000000000000000000000000..ad01ce1fd5b9bcacf31f734422ebc06d5dc71f34
--- /dev/null
+++ b/src/wrap-interactive.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright © 2017-2019 Collabora Ltd.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library 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/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "libglnx.h"
+
+#include "flatpak-bwrap-private.h"
+#include "glib-backports.h"
+
+typedef enum
+{
+  PV_SHELL_NONE = 0,
+  PV_SHELL_AFTER,
+  PV_SHELL_FAIL,
+  PV_SHELL_INSTEAD
+} PvShell;
+
+typedef enum
+{
+  PV_TERMINAL_NONE = 0,
+  PV_TERMINAL_AUTO,
+  PV_TERMINAL_TTY,
+  PV_TERMINAL_XTERM,
+} PvTerminal;
+
+void pv_bwrap_wrap_in_xterm (FlatpakBwrap *wrapped_command);
+void pv_bwrap_wrap_interactive (FlatpakBwrap *wrapped_command,
+                                PvShell shell);
+gboolean pv_bwrap_wrap_tty (FlatpakBwrap *wrapped_command,
+                            GError **error);
diff --git a/src/wrap.c b/src/wrap.c
index 21d537749b0c8939425b48b4abc9922f02d9b65a..109d31faf059198d96a9b5139253c6f73b18c725 100644
--- a/src/wrap.c
+++ b/src/wrap.c
@@ -29,19 +29,18 @@
 #include <glib/gstdio.h>
 #include <gio/gio.h>
 
-#include <X11/Xauth.h>
-
-#include <ftw.h>
 #include <locale.h>
 #include <stdlib.h>
-#include <sys/utsname.h>
 
 #include "libglnx.h"
 
+#include "bwrap.h"
 #include "glib-backports.h"
 #include "flatpak-bwrap-private.h"
+#include "flatpak-run-private.h"
 #include "flatpak-utils-private.h"
 #include "utils.h"
+#include "wrap-interactive.h"
 
 /*
  * Supported Debian-style multiarch tuples
@@ -62,9 +61,6 @@ static const char * const libquals[] =
   "lib32"
 };
 
-/* In Flatpak this is optional, in pressure-vessel not so much */
-#define ENABLE_XAUTH
-
 static gchar *
 find_executable_dir (GError **error)
 {
@@ -194,542 +190,6 @@ check_bwrap (const char *tools_dir)
   return NULL;
 }
 
-static gboolean
-bind_usr (FlatpakBwrap *bwrap,
-          const char *host_path,
-          const char *mount_point,
-          GError **error)
-{
-  g_autofree gchar *usr = NULL;
-  g_autofree gchar *dest = NULL;
-  gboolean host_path_is_usr = FALSE;
-  g_autoptr(GDir) dir = NULL;
-  const gchar *member = NULL;
-  static const char * const bind_etc[] =
-  {
-    "alternatives",
-    "ld.so.cache"
-  };
-  gsize i;
-
-  g_return_val_if_fail (host_path != NULL, FALSE);
-  g_return_val_if_fail (host_path[0] == '/', FALSE);
-  g_return_val_if_fail (mount_point != NULL, FALSE);
-  g_return_val_if_fail (mount_point[0] == '/', FALSE);
-
-  usr = g_build_filename (host_path, "usr", NULL);
-  dest = g_build_filename (mount_point, "usr", NULL);
-
-  if (g_file_test (usr, G_FILE_TEST_IS_DIR))
-    {
-      flatpak_bwrap_add_args (bwrap,
-                              "--ro-bind", usr, dest,
-                              NULL);
-    }
-  else
-    {
-      /* host_path is assumed to be a merged /usr */
-      host_path_is_usr = TRUE;
-      flatpak_bwrap_add_args (bwrap,
-                              "--ro-bind", host_path, dest,
-                              NULL);
-    }
-
-  g_clear_pointer (&dest, g_free);
-
-  dir = g_dir_open (host_path, 0, error);
-
-  if (dir == NULL)
-    return FALSE;
-
-  for (member = g_dir_read_name (dir);
-       member != NULL;
-       member = g_dir_read_name (dir))
-    {
-      if (g_str_has_prefix (member, "lib")
-          || g_str_equal (member, "bin")
-          || g_str_equal (member, "sbin"))
-        {
-          dest = g_build_filename (mount_point, member, NULL);
-
-          if (host_path_is_usr)
-            {
-              g_autofree gchar *target = g_build_filename ("usr",
-                                                           member, NULL);
-
-              flatpak_bwrap_add_args (bwrap,
-                                      "--symlink", target, dest,
-                                      NULL);
-            }
-          else
-            {
-              g_autofree gchar *path = g_build_filename (host_path,
-                                                         member, NULL);
-              g_autofree gchar *target = glnx_readlinkat_malloc (-1, path, NULL, NULL);
-
-              if (target != NULL)
-                {
-                  flatpak_bwrap_add_args (bwrap,
-                                          "--symlink", target, dest,
-                                          NULL);
-                }
-              else
-                {
-                  flatpak_bwrap_add_args (bwrap,
-                                          "--ro-bind", path, dest,
-                                          NULL);
-                }
-            }
-
-          g_clear_pointer (&dest, g_free);
-        }
-    }
-
-  for (i = 0; i < G_N_ELEMENTS (bind_etc); i++)
-    {
-      g_autofree gchar *path = g_build_filename (host_path, "etc",
-                                                 bind_etc[i], NULL);
-
-      if (g_file_test (path, G_FILE_TEST_EXISTS))
-        {
-          dest = g_build_filename (mount_point, "etc", bind_etc[i], NULL);
-
-          flatpak_bwrap_add_args (bwrap,
-                                  "--ro-bind", path, dest,
-                                  NULL);
-
-          g_clear_pointer (&dest, g_free);
-        }
-    }
-
-  return TRUE;
-}
-
-/* From Flatpak */
-static char *
-extract_unix_path_from_dbus_address (const char *address)
-{
-  const char *path, *path_end;
-
-  if (address == NULL)
-    return NULL;
-
-  if (!g_str_has_prefix (address, "unix:"))
-    return NULL;
-
-  path = strstr (address, "path=");
-  if (path == NULL)
-    return NULL;
-  path += strlen ("path=");
-  path_end = path;
-  while (*path_end != 0 && *path_end != ',')
-    path_end++;
-
-  return g_strndup (path, path_end - path);
-}
-
-/* From Flatpak */
-#ifdef ENABLE_XAUTH
-static gboolean
-auth_streq (char *str,
-            char *au_str,
-            int   au_len)
-{
-  return au_len == strlen (str) && memcmp (str, au_str, au_len) == 0;
-}
-
-static gboolean
-xauth_entry_should_propagate (Xauth *xa,
-                              char  *hostname,
-                              char  *number)
-{
-  /* ensure entry isn't for remote access */
-  if (xa->family != FamilyLocal && xa->family != FamilyWild)
-    return FALSE;
-
-  /* ensure entry is for this machine */
-  if (xa->family == FamilyLocal && !auth_streq (hostname, xa->address, xa->address_length))
-    return FALSE;
-
-  /* ensure entry is for this session */
-  if (xa->number != NULL && !auth_streq (number, xa->number, xa->number_length))
-    return FALSE;
-
-  return TRUE;
-}
-
-static void
-write_xauth (char *number, FILE *output)
-{
-  Xauth *xa, local_xa;
-  char *filename;
-  FILE *f;
-  struct utsname unames;
-
-  if (uname (&unames))
-    {
-      g_warning ("uname failed");
-      return;
-    }
-
-  filename = XauFileName ();
-  f = fopen (filename, "rb");
-  if (f == NULL)
-    return;
-
-  while (TRUE)
-    {
-      xa = XauReadAuth (f);
-      if (xa == NULL)
-        break;
-      if (xauth_entry_should_propagate (xa, unames.nodename, number))
-        {
-          local_xa = *xa;
-          if (local_xa.number)
-            {
-              local_xa.number = (char *) "99";
-              local_xa.number_length = 2;
-            }
-
-          if (!XauWriteAuth (output, &local_xa))
-            g_warning ("xauth write error");
-        }
-
-      XauDisposeAuth (xa);
-    }
-
-  fclose (f);
-}
-#endif
-
-/* Adapted from Flatpak */
-static void
-flatpak_run_add_x11_args (FlatpakBwrap *bwrap,
-                          gboolean      allowed)
-{
-  g_autofree char *x11_socket = NULL;
-  const char *display;
-
-  /* Always cover /tmp/.X11-unix, that way we never see the host one in case
-   * we have access to the host /tmp. If you request X access we'll put the right
-   * thing in this anyway.
-   */
-  flatpak_bwrap_add_args (bwrap,
-                          "--tmpfs", "/tmp/.X11-unix",
-                          NULL);
-
-  if (!allowed)
-    {
-      flatpak_bwrap_unset_env (bwrap, "DISPLAY");
-      return;
-    }
-
-  g_debug ("Allowing x11 access");
-
-  display = g_getenv ("DISPLAY");
-  if (display && display[0] == ':' && g_ascii_isdigit (display[1]))
-    {
-      const char *display_nr = &display[1];
-      const char *display_nr_end = display_nr;
-      g_autofree char *d = NULL;
-
-      while (g_ascii_isdigit (*display_nr_end))
-        display_nr_end++;
-
-      d = g_strndup (display_nr, display_nr_end - display_nr);
-      x11_socket = g_strdup_printf ("/tmp/.X11-unix/X%s", d);
-
-      flatpak_bwrap_add_args (bwrap,
-                              "--ro-bind", x11_socket, "/tmp/.X11-unix/X99",
-                              NULL);
-      flatpak_bwrap_set_env (bwrap, "DISPLAY", ":99.0", TRUE);
-
-#ifdef ENABLE_XAUTH
-      g_auto(GLnxTmpfile) xauth_tmpf  = { 0, };
-
-      if (glnx_open_anonymous_tmpfile (O_RDWR | O_CLOEXEC, &xauth_tmpf, NULL))
-        {
-          FILE *output = fdopen (xauth_tmpf.fd, "wb");
-          if (output != NULL)
-            {
-              /* fd is now owned by output, steal it from the tmpfile */
-              int tmp_fd = dup (glnx_steal_fd (&xauth_tmpf.fd));
-              if (tmp_fd != -1)
-                {
-                  g_autofree char *dest = g_strdup_printf ("/run/user/%d/Xauthority", getuid ());
-
-                  write_xauth (d, output);
-                  flatpak_bwrap_add_args_data_fd (bwrap, "--ro-bind-data", tmp_fd, dest);
-
-                  flatpak_bwrap_set_env (bwrap, "XAUTHORITY", dest, TRUE);
-                }
-
-              fclose (output);
-
-              if (tmp_fd != -1)
-                lseek (tmp_fd, 0, SEEK_SET);
-            }
-        }
-#endif
-    }
-  else
-    {
-      flatpak_bwrap_unset_env (bwrap, "DISPLAY");
-    }
-}
-
-static gboolean
-flatpak_run_add_wayland_args (FlatpakBwrap *bwrap)
-{
-  const char *wayland_display;
-  g_autofree char *user_runtime_dir = flatpak_get_real_xdg_runtime_dir ();
-  g_autofree char *wayland_socket = NULL;
-  g_autofree char *sandbox_wayland_socket = NULL;
-  gboolean res = FALSE;
-  struct stat statbuf;
-
-  wayland_display = g_getenv ("WAYLAND_DISPLAY");
-  if (!wayland_display)
-    wayland_display = "wayland-0";
-
-  wayland_socket = g_build_filename (user_runtime_dir, wayland_display, NULL);
-  sandbox_wayland_socket = g_strdup_printf ("/run/user/%d/%s", getuid (), wayland_display);
-
-  if (stat (wayland_socket, &statbuf) == 0 &&
-      (statbuf.st_mode & S_IFMT) == S_IFSOCK)
-    {
-      res = TRUE;
-      flatpak_bwrap_add_args (bwrap,
-                              "--ro-bind", wayland_socket, sandbox_wayland_socket,
-                              NULL);
-    }
-  return res;
-}
-
-/* Try to find a default server from a pulseaudio confguration file */
-static char *
-flatpak_run_get_pulseaudio_server_user_config (const char *path)
-{
-  g_autoptr(GFile) file = g_file_new_for_path (path);
-  g_autoptr(GError) my_error = NULL;
-  g_autoptr(GFileInputStream) input_stream = NULL;
-  g_autoptr(GDataInputStream) data_stream = NULL;
-  size_t len;
-
-  input_stream = g_file_read (file, NULL, &my_error);
-  if (my_error)
-    {
-      g_debug ("Pulseaudio user configuration file '%s': %s", path, my_error->message);
-      return NULL;
-    }
-
-  data_stream = g_data_input_stream_new (G_INPUT_STREAM (input_stream));
-
-  while (TRUE)
-    {
-      g_autofree char *line = g_data_input_stream_read_line (data_stream, &len, NULL, NULL);
-      if (line == NULL)
-        break;
-
-      g_strchug (line);
-
-      if ((*line  == '\0') || (*line == ';') || (*line == '#'))
-        continue;
-
-      if (g_str_has_prefix (line, ".include "))
-        {
-          g_autofree char *rec_path = g_strdup (line + 9);
-          g_strstrip (rec_path);
-          char *found = flatpak_run_get_pulseaudio_server_user_config (rec_path);
-          if (found)
-            return found;
-        }
-      else if (g_str_has_prefix (line, "["))
-        {
-          return NULL;
-        }
-      else
-        {
-          g_auto(GStrv) tokens = g_strsplit (line, "=", 2);
-
-          if ((tokens[0] != NULL) && (tokens[1] != NULL))
-            {
-              g_strchomp (tokens[0]);
-              if (strcmp ("default-server", tokens[0]) == 0)
-                {
-                  g_strstrip (tokens[1]);
-                  g_debug ("Found pulseaudio socket from configuration file '%s': %s", path, tokens[1]);
-                  return g_strdup (tokens[1]);
-                }
-            }
-        }
-    }
-
-  return NULL;
-}
-
-static char *
-flatpak_run_get_pulseaudio_server (void)
-{
-  const char * pulse_clientconfig;
-  char *pulse_server;
-  g_autofree char *pulse_user_config = NULL;
-
-  pulse_server = g_strdup (g_getenv ("PULSE_SERVER"));
-  if (pulse_server)
-    return pulse_server;
-
-  pulse_clientconfig = g_getenv ("PULSE_CLIENTCONFIG");
-  if (pulse_clientconfig)
-    return flatpak_run_get_pulseaudio_server_user_config (pulse_clientconfig);
-
-  pulse_user_config = g_build_filename (g_get_user_config_dir (), "pulse/client.conf", NULL);
-  pulse_server = flatpak_run_get_pulseaudio_server_user_config (pulse_user_config);
-  if (pulse_server)
-    return pulse_server;
-
-  pulse_server = flatpak_run_get_pulseaudio_server_user_config ("/etc/pulse/client.conf");
-  if (pulse_server)
-    return pulse_server;
-
-  return NULL;
-}
-
-static char *
-flatpak_run_parse_pulse_server (const char *value)
-{
-  g_auto(GStrv) servers = g_strsplit (value, " ", 0);
-  gsize i;
-
-  for (i = 0; servers[i] != NULL; i++)
-    {
-      const char *server = servers[i];
-      if (g_str_has_prefix (server, "{"))
-        {
-          const char * closing = strstr (server, "}");
-          if (closing == NULL)
-            continue;
-          server = closing + 1;
-        }
-      if (g_str_has_prefix (server, "unix:"))
-        return g_strdup (server + 5);
-    }
-
-  return NULL;
-}
-
-static void
-flatpak_run_add_pulseaudio_args (FlatpakBwrap *bwrap)
-{
-  g_autofree char *pulseaudio_server = flatpak_run_get_pulseaudio_server ();
-  g_autofree char *pulseaudio_socket = NULL;
-  g_autofree char *user_runtime_dir = flatpak_get_real_xdg_runtime_dir ();
-
-  if (pulseaudio_server)
-    pulseaudio_socket = flatpak_run_parse_pulse_server (pulseaudio_server);
-
-  if (!pulseaudio_socket)
-    pulseaudio_socket = g_build_filename (user_runtime_dir, "pulse/native", NULL);
-
-  flatpak_bwrap_unset_env (bwrap, "PULSE_SERVER");
-
-  /* SteamOS system-wide PulseAudio instance */
-  if (!g_file_test (pulseaudio_socket, G_FILE_TEST_EXISTS))
-    {
-      g_clear_pointer (&pulseaudio_socket, g_free);
-      pulseaudio_socket = g_strdup ("/var/run/pulse/native");
-    }
-
-  if (g_file_test (pulseaudio_socket, G_FILE_TEST_EXISTS))
-    {
-      gboolean share_shm = FALSE; /* TODO: When do we add this? */
-      g_autofree char *client_config = g_strdup_printf ("enable-shm=%s\n", share_shm ? "yes" : "no");
-      g_autofree char *sandbox_socket_path = g_strdup_printf ("/run/user/%d/pulse/native", getuid ());
-      g_autofree char *pulse_server = g_strdup_printf ("unix:/run/user/%d/pulse/native", getuid ());
-      g_autofree char *config_path = g_strdup_printf ("/run/user/%d/pulse/config", getuid ());
-
-      /* FIXME - error handling */
-      if (!flatpak_bwrap_add_args_data (bwrap, "pulseaudio", client_config, -1, config_path, NULL))
-        return;
-
-      flatpak_bwrap_add_args (bwrap,
-                              "--ro-bind", pulseaudio_socket, sandbox_socket_path,
-                              NULL);
-
-      flatpak_bwrap_set_env (bwrap, "PULSE_SERVER", pulse_server, TRUE);
-      flatpak_bwrap_set_env (bwrap, "PULSE_CLIENTCONFIG", config_path, TRUE);
-    }
-  else
-    g_debug ("Could not find pulseaudio socket");
-}
-
-/* Simplified from Flatpak: we never restrict access to the D-Bus system bus */
-static gboolean
-flatpak_run_add_system_dbus_args (FlatpakBwrap   *app_bwrap)
-{
-  const char *dbus_address = g_getenv ("DBUS_SYSTEM_BUS_ADDRESS");
-  g_autofree char *real_dbus_address = NULL;
-  g_autofree char *dbus_system_socket = NULL;
-
-  if (dbus_address != NULL)
-    dbus_system_socket = extract_unix_path_from_dbus_address (dbus_address);
-  else if (g_file_test ("/var/run/dbus/system_bus_socket", G_FILE_TEST_EXISTS))
-    dbus_system_socket = g_strdup ("/var/run/dbus/system_bus_socket");
-
-  if (dbus_system_socket != NULL)
-    {
-      flatpak_bwrap_add_args (app_bwrap,
-                              "--ro-bind", dbus_system_socket, "/run/dbus/system_bus_socket",
-                              NULL);
-      flatpak_bwrap_set_env (app_bwrap, "DBUS_SYSTEM_BUS_ADDRESS", "unix:path=/run/dbus/system_bus_socket", TRUE);
-
-      return TRUE;
-    }
-
-  return FALSE;
-}
-
-/* Simplified from Flatpak: we never restrict access to the D-Bus session bus */
-static gboolean
-flatpak_run_add_session_dbus_args (FlatpakBwrap   *app_bwrap)
-{
-  const char *dbus_address = g_getenv ("DBUS_SESSION_BUS_ADDRESS");
-  char *dbus_session_socket = NULL;
-  g_autofree char *sandbox_socket_path = g_strdup_printf ("/run/user/%d/bus", getuid ());
-  g_autofree char *sandbox_dbus_address = g_strdup_printf ("unix:path=/run/user/%d/bus", getuid ());
-  g_autofree char *user_runtime_dir = flatpak_get_real_xdg_runtime_dir ();
-
-  /* FIXME: upstream the use of user_runtime_dir to Flatpak */
-
-  if (dbus_address != NULL)
-    {
-      dbus_session_socket = extract_unix_path_from_dbus_address (dbus_address);
-    }
-  else
-    {
-      struct stat statbuf;
-
-      dbus_session_socket = g_build_filename (user_runtime_dir, "bus", NULL);
-
-      if (stat (dbus_session_socket, &statbuf) < 0
-          || (statbuf.st_mode & S_IFMT) != S_IFSOCK
-          || statbuf.st_uid != getuid ())
-        return FALSE;
-    }
-
-  if (dbus_session_socket != NULL)
-    {
-      flatpak_bwrap_add_args (app_bwrap,
-                              "--ro-bind", dbus_session_socket, sandbox_socket_path,
-                              NULL);
-      flatpak_bwrap_set_env (app_bwrap, "DBUS_SESSION_BUS_ADDRESS", sandbox_dbus_address, TRUE);
-
-      return TRUE;
-    }
-
-  return FALSE;
-}
-
 static gchar *
 capture_output (const char * const * argv,
                 GError **error)
@@ -777,50 +237,6 @@ capture_output (const char * const * argv,
   return g_steal_pointer (&output);
 }
 
-static gboolean
-run_bwrap (FlatpakBwrap *bwrap,
-           GError **error)
-{
-  gint exit_status;
-  g_autofree gchar *output = NULL;
-  g_autofree gchar *errors = NULL;
-  guint i;
-  g_autoptr(GString) command = g_string_new ("");
-
-  for (i = 0; i < bwrap->argv->len; i++)
-    {
-      g_autofree gchar *quoted = NULL;
-      char *unquoted = g_ptr_array_index (bwrap->argv, i);
-
-      if (unquoted == NULL)
-        break;
-
-      quoted = g_shell_quote (unquoted);
-      g_string_append_printf (command, " %s", quoted);
-    }
-
-  g_debug ("run:%s", command->str);
-
-  if (!g_spawn_sync (NULL,  /* cwd */
-                     (char **) bwrap->argv->pdata,
-                     NULL,  /* env */
-                     G_SPAWN_SEARCH_PATH,
-                     NULL, NULL,    /* child setup */
-                     &output,
-                     &errors,
-                     &exit_status,
-                     error))
-    return FALSE;
-
-  g_print ("%s", output);
-  g_printerr ("%s", errors);
-
-  if (!g_spawn_check_exit_status (exit_status, error))
-    return FALSE;
-
-  return TRUE;
-}
-
 static gboolean
 try_bind_dri (FlatpakBwrap *bwrap,
               FlatpakBwrap *mount_runtime_on_scratch,
@@ -834,6 +250,12 @@ try_bind_dri (FlatpakBwrap *bwrap,
   g_autofree gchar *dri = g_build_filename (libdir, "dri", NULL);
   g_autofree gchar *s2tc = g_build_filename (libdir, "libtxc_dxtn.so", NULL);
 
+  /* mount_runtime_on_scratch can't own any fds, because if it did,
+   * flatpak_bwrap_append_bwrap() would steal them. */
+  g_return_val_if_fail (mount_runtime_on_scratch->fds == NULL
+                        || mount_runtime_on_scratch->fds->len == 0,
+                        FALSE);
+
   if (g_file_test (dri, G_FILE_TEST_IS_DIR))
     {
       g_autoptr(FlatpakBwrap) temp_bwrap = NULL;
@@ -845,6 +267,8 @@ try_bind_dri (FlatpakBwrap *bwrap,
                               libdir);
 
       temp_bwrap = flatpak_bwrap_new (NULL);
+      g_warn_if_fail (mount_runtime_on_scratch->fds == NULL
+                      || mount_runtime_on_scratch->fds->len == 0);
       flatpak_bwrap_append_bwrap (temp_bwrap, mount_runtime_on_scratch);
       flatpak_bwrap_add_args (temp_bwrap,
                               tool_path,
@@ -856,7 +280,7 @@ try_bind_dri (FlatpakBwrap *bwrap,
                               NULL);
       flatpak_bwrap_finish (temp_bwrap);
 
-      if (!run_bwrap (temp_bwrap, error))
+      if (!pv_bwrap_run_sync (temp_bwrap, NULL, error))
         return FALSE;
 
       g_clear_pointer (&temp_bwrap, flatpak_bwrap_free);
@@ -878,7 +302,7 @@ try_bind_dri (FlatpakBwrap *bwrap,
                               NULL);
       flatpak_bwrap_finish (temp_bwrap);
 
-      if (!run_bwrap (temp_bwrap, error))
+      if (!pv_bwrap_run_sync (temp_bwrap, NULL, error))
         return FALSE;
     }
 
@@ -889,6 +313,8 @@ try_bind_dri (FlatpakBwrap *bwrap,
 
       expr = g_strdup_printf ("path-match:%s", s2tc);
       temp_bwrap = flatpak_bwrap_new (NULL);
+      g_warn_if_fail (mount_runtime_on_scratch->fds == NULL
+                      || mount_runtime_on_scratch->fds->len == 0);
       flatpak_bwrap_append_bwrap (temp_bwrap, mount_runtime_on_scratch);
       flatpak_bwrap_add_args (temp_bwrap,
                               tool_path,
@@ -900,83 +326,13 @@ try_bind_dri (FlatpakBwrap *bwrap,
                               NULL);
       flatpak_bwrap_finish (temp_bwrap);
 
-      if (!run_bwrap (temp_bwrap, error))
+      if (!pv_bwrap_run_sync (temp_bwrap, NULL, error))
         return FALSE;
     }
 
   return TRUE;
 }
 
-/* nftw() doesn't have a user_data argument so we need to use a global
- * variable :-( */
-static struct
-{
-  FlatpakBwrap *bwrap;
-  const char *symlink_farm;
-  const char *dest;
-} nftw_data;
-
-static int
-add_symlinks_to_bwrap (const char *fpath,
-                       const struct stat *sb,
-                       int typeflag,
-                       struct FTW *ftwbuf)
-{
-  const char *path_in_container;
-  g_autofree gchar *target = NULL;
-  gsize prefix_len;
-
-  g_return_val_if_fail (g_str_has_prefix (fpath, nftw_data.symlink_farm), 1);
-  g_return_val_if_fail (g_str_has_suffix (nftw_data.symlink_farm, nftw_data.dest), 1);
-
-  prefix_len = strlen (nftw_data.symlink_farm) - strlen (nftw_data.dest);
-  path_in_container = fpath + prefix_len;
-
-  switch (typeflag)
-    {
-      case FTW_D:
-        flatpak_bwrap_add_args (nftw_data.bwrap,
-                                "--dir", path_in_container,
-                                NULL);
-        break;
-
-      case FTW_SL:
-        target = glnx_readlinkat_malloc (-1, fpath, NULL, NULL);
-        flatpak_bwrap_add_args (nftw_data.bwrap,
-                                "--symlink", target, path_in_container,
-                                NULL);
-        break;
-
-      default:
-        g_warning ("Don't know how to handle ftw type flag %d at %s",
-                   typeflag, fpath);
-    }
-
-  return 0;
-}
-
-/*
- * For every symbolic link in @farm, add a corresponding symbolic link
- * via the bwrap command-line.
- */
-static void
-add_symlink_farm (FlatpakBwrap *bwrap,
-                  const char *farm,
-                  const char *dest)
-{
-  g_return_if_fail (nftw_data.bwrap == NULL);
-  g_return_if_fail (dest[0] == '/');
-  g_return_if_fail (g_str_has_suffix (farm, dest));
-
-  nftw_data.bwrap = bwrap;
-  nftw_data.symlink_farm = farm;
-  nftw_data.dest = dest;
-  nftw (farm, add_symlinks_to_bwrap, 100, FTW_PHYS);
-  nftw_data.bwrap = NULL;
-  nftw_data.symlink_farm = NULL;
-  nftw_data.dest = NULL;
-}
-
 static gboolean
 bind_runtime (FlatpakBwrap *bwrap,
               const char *tools_dir,
@@ -1011,6 +367,9 @@ bind_runtime (FlatpakBwrap *bwrap,
   gsize i, j;
   const gchar *member;
   g_autoptr(GString) dri_path = g_string_new ("");
+  gboolean any_architecture_works = FALSE;
+  gboolean any_libc_from_host = FALSE;
+  gboolean all_libc_from_host = TRUE;
 
   g_return_val_if_fail (tools_dir != NULL, FALSE);
   g_return_val_if_fail (runtime != NULL, FALSE);
@@ -1020,13 +379,11 @@ bind_runtime (FlatpakBwrap *bwrap,
 
   usr = g_build_filename (runtime, "usr", NULL);
 
-  if (!bind_usr (bwrap, runtime, "/", error))
+  if (!pv_bwrap_bind_usr (bwrap, runtime, "/", error))
     return FALSE;
 
   flatpak_bwrap_add_args (bwrap,
                           "--setenv", "XDG_RUNTIME_DIR", xrd,
-                          "--proc", "/proc",
-                          "--ro-bind", "/sys", "/sys",
                           "--tmpfs", "/run",
                           "--tmpfs", "/tmp",
                           "--tmpfs", "/var",
@@ -1084,50 +441,6 @@ bind_runtime (FlatpakBwrap *bwrap,
                               NULL);
     }
 
-  /* /etc/localtime and /etc/resolv.conf can not exist (or be symlinks to
-   * non-existing targets), in which case we don't want to attempt to create
-   * bogus symlinks or bind mounts, as that will cause flatpak run to fail.
-   */
-  if (g_file_test ("/etc/localtime", G_FILE_TEST_EXISTS))
-    {
-      g_autofree char *localtime = NULL;
-      gboolean is_reachable = FALSE;
-      g_autofree char *timezone = flatpak_get_timezone ();
-      g_autofree char *timezone_content = g_strdup_printf ("%s\n", timezone);
-
-      localtime = glnx_readlinkat_malloc (-1, "/etc/localtime", NULL, NULL);
-
-      if (localtime != NULL)
-        {
-          g_autoptr(GFile) base_file = NULL;
-          g_autoptr(GFile) target_file = NULL;
-          g_autofree char *target_canonical = NULL;
-
-          base_file = g_file_new_for_path ("/etc");
-          target_file = g_file_resolve_relative_path (base_file, localtime);
-          target_canonical = g_file_get_path (target_file);
-
-          is_reachable = g_str_has_prefix (target_canonical, "/usr/");
-        }
-
-      if (is_reachable)
-        {
-          flatpak_bwrap_add_args (bwrap,
-                                  "--symlink", localtime, "/etc/localtime",
-                                  NULL);
-        }
-      else
-        {
-          flatpak_bwrap_add_args (bwrap,
-                                  "--ro-bind", "/etc/localtime", "/etc/localtime",
-                                  NULL);
-        }
-
-      flatpak_bwrap_add_args_data (bwrap, "timezone",
-                                   timezone_content, -1, "/etc/timezone",
-                                   NULL);
-    }
-
   if (g_file_test ("/etc/resolv.conf", G_FILE_TEST_EXISTS))
     flatpak_bwrap_add_args (bwrap,
                             "--ro-bind", "/etc/resolv.conf", "/etc/resolv.conf",
@@ -1152,12 +465,6 @@ bind_runtime (FlatpakBwrap *bwrap,
                             "--ro-bind", "/etc/group", "/etc/group",
                             NULL);
 
-  flatpak_run_add_wayland_args (bwrap);
-  flatpak_run_add_x11_args (bwrap, TRUE);
-  flatpak_run_add_pulseaudio_args (bwrap);
-  flatpak_run_add_session_dbus_args (bwrap);
-  flatpak_run_add_system_dbus_args (bwrap);
-
   for (i = 0; i < G_N_ELEMENTS (multiarch_tuples); i++)
     {
       g_autofree gchar *tool = g_strdup_printf ("%s-capsule-capture-libs",
@@ -1200,7 +507,7 @@ bind_runtime (FlatpakBwrap *bwrap,
                                   bwrap->argv->pdata[0],
                                   NULL);
 
-          if (!bind_usr (temp_bwrap, runtime, "/", error))
+          if (!pv_bwrap_bind_usr (temp_bwrap, runtime, "/", error))
             return FALSE;
 
           flatpak_bwrap_add_args (temp_bwrap,
@@ -1220,6 +527,7 @@ bind_runtime (FlatpakBwrap *bwrap,
               continue;
             }
 
+          any_architecture_works = TRUE;
           g_debug ("Container path: %s -> %s", ld_so, ld_so_in_runtime);
 
           search_path_append (dri_path, this_dri_path_in_container);
@@ -1234,11 +542,15 @@ bind_runtime (FlatpakBwrap *bwrap,
                                   "--bind", overrides, overrides,
                                   "--tmpfs", scratch,
                                   NULL);
-          if (!bind_usr (mount_runtime_on_scratch, runtime, scratch,
+          if (!pv_bwrap_bind_usr (mount_runtime_on_scratch, runtime, scratch,
                          error))
             return FALSE;
 
           temp_bwrap = flatpak_bwrap_new (NULL);
+          /* mount_runtime_on_scratch can't own any fds, because if it did,
+           * flatpak_bwrap_append_bwrap() would steal them. */
+          g_warn_if_fail (mount_runtime_on_scratch->fds == NULL
+                          || mount_runtime_on_scratch->fds->len == 0);
           flatpak_bwrap_append_bwrap (temp_bwrap, mount_runtime_on_scratch);
           flatpak_bwrap_add_args (temp_bwrap,
                                   tool_path,
@@ -1250,7 +562,7 @@ bind_runtime (FlatpakBwrap *bwrap,
                                   NULL);
           flatpak_bwrap_finish (temp_bwrap);
 
-          if (!run_bwrap (temp_bwrap, error))
+          if (!pv_bwrap_run_sync (temp_bwrap, NULL, error))
             return FALSE;
 
           g_clear_pointer (&temp_bwrap, flatpak_bwrap_free);
@@ -1275,7 +587,7 @@ bind_runtime (FlatpakBwrap *bwrap,
                                       bwrap->argv->pdata[0],
                                       NULL);
 
-              if (!bind_usr (temp_bwrap, runtime, "/", error))
+              if (!pv_bwrap_bind_usr (temp_bwrap, runtime, "/", error))
                 return FALSE;
 
               flatpak_bwrap_add_args (temp_bwrap,
@@ -1289,19 +601,32 @@ bind_runtime (FlatpakBwrap *bwrap,
                                       ld_so_in_runtime,
                                       NULL);
 
-              g_debug ("Making host locale data visible in container");
+              /* Collect miscellaneous libraries that libc might dlopen.
+               * At the moment this is just libidn2. */
+              temp_bwrap = flatpak_bwrap_new (NULL);
+              g_warn_if_fail (mount_runtime_on_scratch->fds == NULL
+                              || mount_runtime_on_scratch->fds->len == 0);
+              flatpak_bwrap_append_bwrap (temp_bwrap, mount_runtime_on_scratch);
+              flatpak_bwrap_add_args (temp_bwrap,
+                                      tool_path,
+                                      "--container", scratch,
+                                      "--link-target", "/run/host",
+                                      "--dest", libdir_on_host,
+                                      "--provider", "/",
+                                      "if-exists:libidn2.so.0",
+                                      NULL);
+              flatpak_bwrap_finish (temp_bwrap);
 
-              if (g_file_test ("/usr/lib/locale", G_FILE_TEST_EXISTS))
-                flatpak_bwrap_add_args (bwrap,
-                                        "--ro-bind", "/usr/lib/locale",
-                                        "/usr/lib/locale",
-                                        NULL);
+              if (!pv_bwrap_run_sync (temp_bwrap, NULL, error))
+                return FALSE;
 
-              if (g_file_test ("/usr/share/i18n", G_FILE_TEST_EXISTS))
-                flatpak_bwrap_add_args (bwrap,
-                                        "--ro-bind", "/usr/share/i18n",
-                                        "/usr/share/i18n",
-                                        NULL);
+              g_clear_pointer (&temp_bwrap, flatpak_bwrap_free);
+
+              any_libc_from_host = TRUE;
+            }
+          else
+            {
+              all_libc_from_host = FALSE;
             }
 
           /* /lib32 or /lib64 */
@@ -1329,6 +654,68 @@ bind_runtime (FlatpakBwrap *bwrap,
         }
     }
 
+  if (!any_architecture_works)
+    {
+      GString *archs = g_string_new ("");
+
+      for (i = 0; i < G_N_ELEMENTS (multiarch_tuples); i++)
+        {
+          if (archs->len > 0)
+            g_string_append (archs, ", ");
+
+          g_string_append (archs, multiarch_tuples[i]);
+        }
+
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "None of the supported CPU architectures are common to "
+                   "the host system and the container (tried: %s)",
+                   archs->str);
+      g_string_free (archs, TRUE);
+      return FALSE;
+    }
+
+  if (any_libc_from_host && !all_libc_from_host)
+    {
+      /*
+       * This shouldn't happen. It would mean that there exist at least
+       * two architectures (let's say aaa and bbb) for which we have:
+       * host libc6:aaa < container libc6 < host libc6:bbb
+       * (we know that the container's libc6:aaa and libc6:bbb are
+       * constrained to be the same version because that's how multiarch
+       * works).
+       *
+       * If the host system locales work OK with both the aaa and bbb
+       * versions, let's assume they will also work with the intermediate
+       * version from the container...
+       */
+      g_warning ("Using glibc from host system for some but not all "
+                 "architectures! Arbitrarily using host locales.");
+    }
+
+  if (any_libc_from_host)
+    {
+      g_debug ("Making host locale data visible in container");
+
+      if (g_file_test ("/usr/lib/locale", G_FILE_TEST_EXISTS))
+        flatpak_bwrap_add_args (bwrap,
+                                "--ro-bind", "/usr/lib/locale",
+                                "/usr/lib/locale",
+                                NULL);
+
+      if (g_file_test ("/usr/share/i18n", G_FILE_TEST_EXISTS))
+        flatpak_bwrap_add_args (bwrap,
+                                "--ro-bind", "/usr/share/i18n",
+                                "/usr/share/i18n",
+                                NULL);
+    }
+  else
+    {
+      g_debug ("Using included locale data from container");
+    }
+
+  if (!pv_bwrap_bind_usr (bwrap, "/", "/run/host", error))
+    return FALSE;
+
   if (dri_path->len != 0)
       flatpak_bwrap_add_args (bwrap,
                               "--setenv", "LIBGL_DRIVERS_PATH", dri_path->str,
@@ -1338,10 +725,63 @@ bind_runtime (FlatpakBwrap *bwrap,
                               "--unsetenv", "LIBGL_DRIVERS_PATH",
                               NULL);
 
-  add_symlink_farm (bwrap, overrides, "/overrides");
+  /* These can add data fds to @bwrap, so they must come last - after
+   * other functions stop using @bwrap as a basis for their own bwrap
+   * invocations with flatpak_bwrap_append_bwrap().
+   * Otherwise, when flatpak_bwrap_append_bwrap() calls
+   * flatpak_bwrap_steal_fds(), it will make the original FlatpakBwrap
+   * unusable. */
 
-  if (!bind_usr (bwrap, "/", "/run/host", error))
-    return FALSE;
+  flatpak_run_add_wayland_args (bwrap);
+  flatpak_run_add_x11_args (bwrap, TRUE);
+  flatpak_run_add_pulseaudio_args (bwrap);
+  flatpak_run_add_session_dbus_args (bwrap);
+  flatpak_run_add_system_dbus_args (bwrap);
+  pv_bwrap_copy_tree (bwrap, overrides, "/overrides");
+
+  /* /etc/localtime and /etc/resolv.conf can not exist (or be symlinks to
+   * non-existing targets), in which case we don't want to attempt to create
+   * bogus symlinks or bind mounts, as that will cause flatpak run to fail.
+   */
+  if (g_file_test ("/etc/localtime", G_FILE_TEST_EXISTS))
+    {
+      g_autofree char *localtime = NULL;
+      gboolean is_reachable = FALSE;
+      g_autofree char *timezone = flatpak_get_timezone ();
+      g_autofree char *timezone_content = g_strdup_printf ("%s\n", timezone);
+
+      localtime = glnx_readlinkat_malloc (-1, "/etc/localtime", NULL, NULL);
+
+      if (localtime != NULL)
+        {
+          g_autoptr(GFile) base_file = NULL;
+          g_autoptr(GFile) target_file = NULL;
+          g_autofree char *target_canonical = NULL;
+
+          base_file = g_file_new_for_path ("/etc");
+          target_file = g_file_resolve_relative_path (base_file, localtime);
+          target_canonical = g_file_get_path (target_file);
+
+          is_reachable = g_str_has_prefix (target_canonical, "/usr/");
+        }
+
+      if (is_reachable)
+        {
+          flatpak_bwrap_add_args (bwrap,
+                                  "--symlink", localtime, "/etc/localtime",
+                                  NULL);
+        }
+      else
+        {
+          flatpak_bwrap_add_args (bwrap,
+                                  "--ro-bind", "/etc/localtime", "/etc/localtime",
+                                  NULL);
+        }
+
+      flatpak_bwrap_add_args_data (bwrap, "timezone",
+                                   timezone_content, -1, "/etc/timezone",
+                                   NULL);
+    }
 
   return TRUE;
 }
@@ -1495,145 +935,6 @@ use_fake_home (FlatpakBwrap *bwrap,
   return TRUE;
 }
 
-static void
-wrap_in_xterm (FlatpakBwrap *wrapped_command)
-{
-  g_return_if_fail (wrapped_command != NULL);
-
-  flatpak_bwrap_add_args (wrapped_command,
-                          "xterm", "-e",
-                          /* Original command will go here and become
-                           * the argv of xterm -e */
-                          NULL);
-}
-
-typedef enum
-{
-  SHELL_NONE = 0,
-  SHELL_AFTER,
-  SHELL_FAIL,
-  SHELL_INSTEAD
-} Shell;
-
-static void
-wrap_interactive (FlatpakBwrap *wrapped_command,
-                  Shell shell)
-{
-  static const char preamble[] =
-    {
-      "prgname=\"$1\"\n"
-      "shift\n"
-    };
-  const char *script = "";
-  static const char start_shell[] =
-    {
-      "echo\n"
-      "echo\n"
-      "echo\n"
-      "echo \"$1: Starting interactive shell (original command is in "
-      "\\\"\\$@\\\")\"\n"
-      "echo\n"
-      "echo\n"
-      "echo\n"
-      "exec bash -i -s \"$@\"\n"
-    };
-  gchar *command;
-
-  g_return_if_fail (wrapped_command != NULL);
-
-  switch (shell)
-    {
-      case SHELL_NONE:
-        script =
-          "e=0\n"
-          "\"$@\" || e=$?\n"
-          "echo\n"
-          "echo \"Press Enter or ^D to continue...\"\n"
-          "read reply || true\n"
-          "exit \"$e\"\n";
-        break;
-
-      case SHELL_AFTER:
-        script =
-          "e=0\n"
-          "\"$@\" || e=$?\n";
-        break;
-
-      case SHELL_FAIL:
-        script =
-          "if \"$@\"; then exit 0; else e=\"$?\"; fi\n"
-          "echo \"$1: command exit status $e\"\n";
-        break;
-
-      case SHELL_INSTEAD:
-        break;
-
-      default:
-        g_return_if_reached ();
-    }
-
-  command = g_strdup_printf ("%s%s%s",
-                             preamble,
-                             script,
-                             shell == SHELL_NONE ? "" : start_shell);
-
-  flatpak_bwrap_add_args (wrapped_command,
-                          "sh", "-euc",
-                          command,
-                          "sh",   /* $0 for sh */
-                          g_get_prgname (),   /* $1 for sh */
-                          /* Original command will go here and become
-                           * the argv of command, and eventually
-                           * the argv of bash -i -s */
-                          NULL);
-}
-
-static gboolean
-wrap_tty (FlatpakBwrap *wrapped_command,
-          GError **error)
-{
-  int fd;
-
-  g_return_val_if_fail (wrapped_command != NULL, FALSE);
-  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-
-  fflush (stdout);
-  fflush (stderr);
-
-  fd = open ("/dev/tty", O_RDONLY);
-
-  if (fd < 0)
-    return glnx_throw_errno_prefix (error,
-                                    "Cannot open /dev/tty for reading");
-
-  if (dup2 (fd, 0) < 0)
-    return glnx_throw_errno_prefix (error, "Cannot use /dev/tty as stdin");
-
-  g_close (fd, NULL);
-
-  fd = open ("/dev/tty", O_WRONLY);
-
-  if (fd < 0)
-    return glnx_throw_errno_prefix (error, "Cannot open /dev/tty for writing");
-
-  if (dup2 (fd, 1) < 0)
-    return glnx_throw_errno_prefix (error, "Cannot use /dev/tty as stdout");
-
-  if (dup2 (fd, 2) < 0)
-    return glnx_throw_errno_prefix (error, "Cannot use /dev/tty as stderr");
-
-  g_close (fd, NULL);
-  return TRUE;
-}
-
-typedef enum
-{
-  TERMINAL_NONE = 0,
-  TERMINAL_AUTO,
-  TERMINAL_TTY,
-  TERMINAL_XTERM,
-} Terminal;
-
 typedef enum
 {
   TRISTATE_NO = 0,
@@ -1647,14 +948,14 @@ static char *opt_freedesktop_app_id = NULL;
 static char *opt_steam_app_id = NULL;
 static char *opt_home = NULL;
 static gboolean opt_host_fallback = FALSE;
-static Shell opt_shell = SHELL_NONE;
+static PvShell opt_shell = PV_SHELL_NONE;
 static GPtrArray *opt_ld_preload = NULL;
 static char *opt_runtime_base = NULL;
 static char *opt_runtime = NULL;
 static Tristate opt_share_home = TRISTATE_MAYBE;
 static gboolean opt_verbose = FALSE;
 static gboolean opt_version = FALSE;
-static Terminal opt_terminal = TERMINAL_AUTO;
+static PvTerminal opt_terminal = PV_TERMINAL_AUTO;
 
 static gboolean
 opt_host_ld_preload_cb (const gchar *option_name,
@@ -1687,7 +988,7 @@ opt_shell_cb (const gchar *option_name,
 
   if (value == NULL || *value == '\0')
     {
-      opt_shell = SHELL_NONE;
+      opt_shell = PV_SHELL_NONE;
       return TRUE;
     }
 
@@ -1696,7 +997,7 @@ opt_shell_cb (const gchar *option_name,
       case 'a':
         if (g_strcmp0 (value, "after") == 0)
           {
-            opt_shell = SHELL_AFTER;
+            opt_shell = PV_SHELL_AFTER;
             return TRUE;
           }
         break;
@@ -1704,7 +1005,7 @@ opt_shell_cb (const gchar *option_name,
       case 'f':
         if (g_strcmp0 (value, "fail") == 0)
           {
-            opt_shell = SHELL_FAIL;
+            opt_shell = PV_SHELL_FAIL;
             return TRUE;
           }
         break;
@@ -1712,7 +1013,7 @@ opt_shell_cb (const gchar *option_name,
       case 'i':
         if (g_strcmp0 (value, "instead") == 0)
           {
-            opt_shell = SHELL_INSTEAD;
+            opt_shell = PV_SHELL_INSTEAD;
             return TRUE;
           }
         break;
@@ -1720,7 +1021,7 @@ opt_shell_cb (const gchar *option_name,
       case 'n':
         if (g_strcmp0 (value, "none") == 0 || g_strcmp0 (value, "no") == 0)
           {
-            opt_shell = SHELL_NONE;
+            opt_shell = PV_SHELL_NONE;
             return TRUE;
           }
         break;
@@ -1748,7 +1049,7 @@ opt_terminal_cb (const gchar *option_name,
 
   if (value == NULL || *value == '\0')
     {
-      opt_terminal = TERMINAL_AUTO;
+      opt_terminal = PV_TERMINAL_AUTO;
       return TRUE;
     }
 
@@ -1757,7 +1058,7 @@ opt_terminal_cb (const gchar *option_name,
       case 'a':
         if (g_strcmp0 (value, "auto") == 0)
           {
-            opt_terminal = TERMINAL_AUTO;
+            opt_terminal = PV_TERMINAL_AUTO;
             return TRUE;
           }
         break;
@@ -1765,7 +1066,7 @@ opt_terminal_cb (const gchar *option_name,
       case 'n':
         if (g_strcmp0 (value, "none") == 0 || g_strcmp0 (value, "no") == 0)
           {
-            opt_terminal = TERMINAL_NONE;
+            opt_terminal = PV_TERMINAL_NONE;
             return TRUE;
           }
         break;
@@ -1773,7 +1074,7 @@ opt_terminal_cb (const gchar *option_name,
       case 't':
         if (g_strcmp0 (value, "tty") == 0)
           {
-            opt_terminal = TERMINAL_TTY;
+            opt_terminal = PV_TERMINAL_TTY;
             return TRUE;
           }
         break;
@@ -1781,7 +1082,7 @@ opt_terminal_cb (const gchar *option_name,
       case 'x':
         if (g_strcmp0 (value, "xterm") == 0)
           {
-            opt_terminal = TERMINAL_XTERM;
+            opt_terminal = PV_TERMINAL_XTERM;
             return TRUE;
           }
         break;
@@ -2042,15 +1343,15 @@ main (int argc,
       goto out;
     }
 
-  if (opt_terminal == TERMINAL_AUTO)
+  if (opt_terminal == PV_TERMINAL_AUTO)
     {
-      if (opt_shell != SHELL_NONE)
-        opt_terminal = TERMINAL_XTERM;
+      if (opt_shell != PV_SHELL_NONE)
+        opt_terminal = PV_TERMINAL_XTERM;
       else
-        opt_terminal = TERMINAL_NONE;
+        opt_terminal = PV_TERMINAL_NONE;
     }
 
-  if (opt_terminal == TERMINAL_NONE && opt_shell != SHELL_NONE)
+  if (opt_terminal == PV_TERMINAL_NONE && opt_shell != PV_SHELL_NONE)
     {
       g_printerr ("%s: --terminal=none is incompatible with --shell\n",
                   g_get_prgname ());
@@ -2121,7 +1422,7 @@ main (int argc,
    * us exit 1. */
   ret = 1;
 
-  if (opt_terminal != TERMINAL_TTY)
+  if (opt_terminal != PV_TERMINAL_TTY)
     {
       int fd;
 
@@ -2191,31 +1492,31 @@ main (int argc,
 
   switch (opt_terminal)
     {
-      case TERMINAL_TTY:
+      case PV_TERMINAL_TTY:
         g_debug ("Wrapping command to use tty");
 
-        if (!wrap_tty (wrapped_command, error))
+        if (!pv_bwrap_wrap_tty (wrapped_command, error))
           goto out;
 
         break;
 
-      case TERMINAL_XTERM:
+      case PV_TERMINAL_XTERM:
         g_debug ("Wrapping command with xterm");
-        wrap_in_xterm (wrapped_command);
+        pv_bwrap_wrap_in_xterm (wrapped_command);
         break;
 
-      case TERMINAL_AUTO:
-      case TERMINAL_NONE:
+      case PV_TERMINAL_AUTO:
+      case PV_TERMINAL_NONE:
       default:
         /* do nothing */
         break;
     }
 
-  if (opt_shell != SHELL_NONE || opt_terminal == TERMINAL_XTERM)
+  if (opt_shell != PV_SHELL_NONE || opt_terminal == PV_TERMINAL_XTERM)
     {
-      /* In the (SHELL_NONE, TERMINAL_XTERM) case, just don't let the xterm
-       * close before the user has had a chance to see the output */
-      wrap_interactive (wrapped_command, opt_shell);
+      /* In the (PV_SHELL_NONE, PV_TERMINAL_XTERM) case, just don't let the
+       * xterm close before the user has had a chance to see the output */
+      pv_bwrap_wrap_interactive (wrapped_command, opt_shell);
     }
 
   if (argv[1][0] == '-')
@@ -2301,9 +1602,11 @@ main (int argc,
   /* Protect the controlling terminal from the app/game, unless we are
    * running an interactive shell in which case that would break its
    * job control. */
-  if (opt_terminal != TERMINAL_TTY)
+  if (opt_terminal != PV_TERMINAL_TTY)
     flatpak_bwrap_add_arg (bwrap, "--new-session");
 
+  pv_bwrap_add_api_filesystems (bwrap);
+
   if (opt_runtime != NULL && opt_runtime[0] != '\0')
     {
       g_debug ("Configuring runtime...");
@@ -2340,21 +1643,6 @@ main (int argc,
                               NULL);
     }
 
-  /* Make /dev available */
-  flatpak_bwrap_add_args (bwrap,
-                          "--dev-bind", "/dev", "/dev",
-                          NULL);
-
-  if (g_file_test ("/dev/pts", G_FILE_TEST_EXISTS))
-    flatpak_bwrap_add_args (bwrap,
-                            "--dev-bind", "/dev/pts", "/dev/pts",
-                            NULL);
-
-  if (g_file_test ("/dev/shm", G_FILE_TEST_EXISTS))
-    flatpak_bwrap_add_args (bwrap,
-                            "--dev-bind", "/dev/shm", "/dev/shm",
-                            NULL);
-
   /* Protect other users' homes (but guard against the unlikely
    * situation that they don't exist) */
   if (g_file_test ("/home", G_FILE_TEST_EXISTS))
@@ -2544,8 +1832,6 @@ main (int argc,
         }
     }
 
-  flatpak_bwrap_finish (bwrap);
-
   /* Clean up temporary directory before running our long-running process */
   if (tmpdir != NULL &&
       !glnx_shutil_rm_rf_at (-1, tmpdir, NULL, error))
@@ -2557,18 +1843,8 @@ main (int argc,
 
   g_clear_pointer (&tmpdir, g_free);
 
-  /* flatpak_bwrap_finish did this */
-  g_assert (g_ptr_array_index (bwrap->argv, bwrap->argv->len - 1) == NULL);
-
-  g_debug ("Replacing self with bwrap...");
-
-  flatpak_bwrap_child_setup_cb (bwrap->fds);
-  execve (bwrap->argv->pdata[0],
-          (char * const *) bwrap->argv->pdata,
-          bwrap->envp);
-
-  /* If we are still here then execve failed */
-  g_warning ("execve failed: %s", g_strerror (errno));
+  flatpak_bwrap_finish (bwrap);
+  pv_bwrap_execve (bwrap, error);
 
 out:
   if (local_error != NULL)