Skip to content
Snippets Groups Projects
  • Simon McVittie's avatar
    66d41ccd
    runtime: Optionally copy the runtime, and bind-mount the copy · 66d41ccd
    Simon McVittie authored
    
    This is good for two reasons. In a Flatpak runtime, we will not be able
    to do tricks with arbitrary bind-mounts; if we want the host library
    stack, we will have to rewrite the mutable copy of the runtime to
    replace container libraries with symlinks into /run/host. Also, when
    not in a Flatpak runtime, having a mutable copy of the immutable runtime
    gives developers a chance to edit the runtime to inject workarounds or
    things they're testing.
    
    Signed-off-by: default avatarSimon McVittie <smcv@collabora.com>
    66d41ccd
    History
    runtime: Optionally copy the runtime, and bind-mount the copy
    Simon McVittie authored
    
    This is good for two reasons. In a Flatpak runtime, we will not be able
    to do tricks with arbitrary bind-mounts; if we want the host library
    stack, we will have to rewrite the mutable copy of the runtime to
    replace container libraries with symlinks into /run/host. Also, when
    not in a Flatpak runtime, having a mutable copy of the immutable runtime
    gives developers a chance to edit the runtime to inject workarounds or
    things they're testing.
    
    Signed-off-by: default avatarSimon McVittie <smcv@collabora.com>
runtime.c 88.40 KiB
/*
 * Copyright © 2020 Collabora Ltd.
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */

#include "src/runtime.h"

#include <sysexits.h>

#include <gio/gio.h>

/* Include these before steam-runtime-tools.h so that their backport of
 * G_DEFINE_AUTOPTR_CLEANUP_FUNC will be visible to it */
#include "glib-backports.h"
#include "libglnx/libglnx.h"

#include <steam-runtime-tools/steam-runtime-tools.h>

#include "bwrap.h"
#include "bwrap-lock.h"
#include "enumtypes.h"
#include "flatpak-run-private.h"
#include "utils.h"

/*
 * PvRuntime:
 *
 * Object representing a runtime to be used as the /usr for a game.
 */

struct _PvRuntime
{
  GObject parent;

  gchar *bubblewrap;
  gchar *source_files;
  gchar *tools_dir;
  PvBwrapLock *runtime_lock;

  gchar *mutable_parent;
  gchar *mutable_sysroot;
  gchar *tmpdir;
  gchar *overrides;
  gchar *overrides_bin;
  gchar *container_access;
  FlatpakBwrap *container_access_adverb;
  const gchar *runtime_files;   /* either source_files or mutable_sysroot */
  gchar *runtime_usr;           /* either runtime_files or that + "/usr" */

  PvRuntimeFlags flags;
  int mutable_parent_fd;
  int mutable_sysroot_fd;
  gboolean any_libc_from_host;
  gboolean all_libc_from_host;
  gboolean runtime_is_just_usr;
};

struct _PvRuntimeClass
{
  GObjectClass parent;
};

enum {
  PROP_0,
  PROP_BUBBLEWRAP,
  PROP_FLAGS,
  PROP_MUTABLE_PARENT,
  PROP_SOURCE_FILES,
  PROP_TOOLS_DIRECTORY,
  N_PROPERTIES
};

static GParamSpec *properties[N_PROPERTIES] = { NULL };

static void pv_runtime_initable_iface_init (GInitableIface *iface,
                                            gpointer unused);

G_DEFINE_TYPE_WITH_CODE (PvRuntime, pv_runtime, G_TYPE_OBJECT,
                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
                                                pv_runtime_initable_iface_init))

/*
 * Supported Debian-style multiarch tuples
 */
static const char * const multiarch_tuples[] =
{
  "x86_64-linux-gnu",
  "i386-linux-gnu",
  NULL
};

/*
 * Directories other than /usr/lib that we must search for loadable
 * modules, in the same order as multiarch_tuples
 */
static const char * const libquals[] =
{
  "lib64",
  "lib32"
};
G_STATIC_ASSERT (G_N_ELEMENTS (libquals)
                 == G_N_ELEMENTS (multiarch_tuples) - 1);

typedef struct
{
  gsize multiarch_index;
  const char *tuple;
  gchar *capsule_capture_libs_basename;
  gchar *capsule_capture_libs;
  gchar *libdir_on_host;
  gchar *libdir_in_container;
  const char *libqual;
  gchar *ld_so;
} RuntimeArchitecture;

static gboolean
runtime_architecture_init (RuntimeArchitecture *self,
                           PvRuntime *runtime)
{
  const gchar *argv[] = { NULL, "--print-ld.so", NULL };

  g_return_val_if_fail (self->multiarch_index < G_N_ELEMENTS (libquals), FALSE);
  g_return_val_if_fail (self->multiarch_index < G_N_ELEMENTS (multiarch_tuples) - 1,
                        FALSE);
  g_return_val_if_fail (self->tuple == NULL, FALSE);
  self->tuple = multiarch_tuples[self->multiarch_index];
  g_return_val_if_fail (self->tuple != NULL, FALSE);
  self->libqual = libquals[self->multiarch_index];

  self->capsule_capture_libs_basename = g_strdup_printf ("%s-capsule-capture-libs",
                                                         self->tuple);
  self->capsule_capture_libs = g_build_filename (runtime->tools_dir,
                                                 self->capsule_capture_libs_basename,
                                                 NULL);
  self->libdir_on_host = g_build_filename (runtime->overrides, "lib",
                                           self->tuple, NULL);
  self->libdir_in_container = g_build_filename ("/overrides", "lib",
                                                self->tuple, NULL);

  /* This has the side-effect of testing whether we can run binaries
   * for this architecture on the host system. */
  argv[0] = self->capsule_capture_libs;
  self->ld_so = pv_capture_output (argv, NULL);

  if (self->ld_so == NULL)
    {
      g_debug ("Cannot determine ld.so for %s", self->tuple);
      return FALSE;
    }

  return TRUE;
}

static gboolean
runtime_architecture_check_valid (RuntimeArchitecture *self)
{
  g_return_val_if_fail (self->multiarch_index < G_N_ELEMENTS (libquals), FALSE);
  g_return_val_if_fail (self->tuple == multiarch_tuples[self->multiarch_index], FALSE);
  g_return_val_if_fail (self->capsule_capture_libs_basename != NULL, FALSE);
  g_return_val_if_fail (self->capsule_capture_libs != NULL, FALSE);
  g_return_val_if_fail (self->libdir_on_host != NULL, FALSE);
  g_return_val_if_fail (self->libdir_in_container != NULL, FALSE);
  g_return_val_if_fail (self->libqual == libquals[self->multiarch_index], FALSE);
  g_return_val_if_fail (self->ld_so != NULL, FALSE);
  return TRUE;
}

static void
runtime_architecture_clear (RuntimeArchitecture *self)
{
  self->multiarch_index = G_MAXSIZE;
  self->tuple = NULL;
  self->libqual = NULL;
  g_clear_pointer (&self->capsule_capture_libs_basename, g_free);
  g_clear_pointer (&self->capsule_capture_libs, g_free);
  g_clear_pointer (&self->libdir_on_host, g_free);
  g_clear_pointer (&self->libdir_in_container, g_free);
  g_clear_pointer (&self->ld_so, g_free);
}

G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (RuntimeArchitecture,
                                  runtime_architecture_clear)

static gboolean pv_runtime_use_host_graphics_stack (PvRuntime *self,
                                                    FlatpakBwrap *bwrap,
                                                    GError **error);
static void pv_runtime_set_search_paths (PvRuntime *self,
                                         FlatpakBwrap *bwrap);

static void
pv_runtime_init (PvRuntime *self)
{
  self->any_libc_from_host = FALSE;
  self->all_libc_from_host = FALSE;
  self->mutable_parent_fd = -1;
  self->mutable_sysroot_fd = -1;
}

static void
pv_runtime_get_property (GObject *object,
                         guint prop_id,
                         GValue *value,
                         GParamSpec *pspec)
{
  PvRuntime *self = PV_RUNTIME (object);

  switch (prop_id)
    {
      case PROP_BUBBLEWRAP:
        g_value_set_string (value, self->bubblewrap);
        break;

      case PROP_FLAGS:
        g_value_set_flags (value, self->flags);
        break;

      case PROP_MUTABLE_PARENT:
        g_value_set_string (value, self->mutable_parent);
        break;

      case PROP_SOURCE_FILES:
        g_value_set_string (value, self->source_files);
        break;

      case PROP_TOOLS_DIRECTORY:
        g_value_set_string (value, self->tools_dir);
        break;

      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
pv_runtime_set_property (GObject *object,
                         guint prop_id,
                         const GValue *value,
                         GParamSpec *pspec)
{
  PvRuntime *self = PV_RUNTIME (object);
  const char *path;

  switch (prop_id)
    {
      case PROP_BUBBLEWRAP:
        /* Construct-only */
        g_return_if_fail (self->bubblewrap == NULL);
        self->bubblewrap = g_value_dup_string (value);
        break;

      case PROP_FLAGS:
        self->flags = g_value_get_flags (value);
        break;

      case PROP_MUTABLE_PARENT:
        /* Construct-only */
        g_return_if_fail (self->mutable_parent == NULL);
        path = g_value_get_string (value);

        if (path != NULL)
          self->mutable_parent = flatpak_canonicalize_filename (path);
        break;

      case PROP_SOURCE_FILES:
        /* Construct-only */
        g_return_if_fail (self->source_files == NULL);
        self->source_files = g_value_dup_string (value);
        break;

      case PROP_TOOLS_DIRECTORY:
        /* Construct-only */
        g_return_if_fail (self->tools_dir == NULL);
        self->tools_dir = g_value_dup_string (value);
        break;

      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
pv_runtime_constructed (GObject *object)
{
  PvRuntime *self = PV_RUNTIME (object);

  G_OBJECT_CLASS (pv_runtime_parent_class)->constructed (object);

  g_return_if_fail (self->bubblewrap != NULL);
  g_return_if_fail (self->source_files != NULL);
  g_return_if_fail (self->tools_dir != NULL);
}

static gboolean
pv_runtime_init_mutable (PvRuntime *self,
                         GError **error)
{
  g_autofree gchar *dest_usr = NULL;
  g_autofree gchar *source_usr_subdir = NULL;
  g_autofree gchar *temp_dir = NULL;
  g_autoptr(GDir) dir = NULL;
  g_autoptr(PvBwrapLock) copy_lock = NULL;
  g_autoptr(PvBwrapLock) mutable_lock = NULL;
  g_autoptr(PvBwrapLock) source_lock = NULL;
  const char *member;
  const char *source_usr;
  glnx_autofd int temp_dir_fd = -1;
  gboolean is_just_usr;

  /* Nothing to do in this case */
  if (self->mutable_parent == NULL)
    return TRUE;

  if (g_mkdir_with_parents (self->mutable_parent, 0700) != 0)
    return glnx_throw_errno_prefix (error, "Unable to create %s",
                                    self->mutable_parent);

  if (!glnx_opendirat (AT_FDCWD, self->mutable_parent, TRUE,
                       &self->mutable_parent_fd, error))
    return FALSE;

  /* Lock the parent directory. Anything that directly manipulates the
   * temporary runtimes (notably the deploy-runtime and run-in-steamrt
   * scripts in SteamLinuxRuntime) is expected to do the same, so that
   * it cannot be deleting temporary runtimes at the same time we're
   * creating them.
   *
   * This is a read-mode lock: it's OK to create more than one temporary
   * runtime in parallel, as long as nothing is deleting them
   * concurrently. */
  mutable_lock = pv_bwrap_lock_new (self->mutable_parent_fd, ".ref",
                                    PV_BWRAP_LOCK_FLAGS_CREATE,
                                    error);

  if (mutable_lock == NULL)
    return glnx_prefix_error (error, "Unable to lock \"%s/%s\"",
                              self->mutable_parent, ".ref");

  temp_dir = g_build_filename (self->mutable_parent, "tmp-XXXXXX", NULL);

  if (g_mkdtemp (temp_dir) == NULL)
    return glnx_throw_errno_prefix (error,
                                    "Cannot create temporary directory \"%s\"",
                                    temp_dir);

  source_usr_subdir = g_build_filename (self->source_files, "usr", NULL);
  dest_usr = g_build_filename (temp_dir, "usr", NULL);

  is_just_usr = !g_file_test (source_usr_subdir, G_FILE_TEST_IS_DIR);

  if (is_just_usr)
    {
      /* ${source_files}/usr does not exist, so assume it's a merged /usr,
       * for example ./scout/files. Copy ${source_files}/bin to
       * ${temp_dir}/usr/bin, etc. */
      source_usr = self->source_files;

      if (!pv_cheap_tree_copy (self->source_files, dest_usr, error))
        return FALSE;
    }
  else
    {
      /* ${source_files}/usr exists, so assume it's a complete sysroot.
       * Copy ${source_files}/bin to ${temp_dir}/bin, etc. */
      source_usr = source_usr_subdir;

      if (!pv_cheap_tree_copy (self->source_files, temp_dir, error))
        return FALSE;
    }

  if (!glnx_opendirat (-1, temp_dir, FALSE, &temp_dir_fd, error))
    return FALSE;

  /* Create the copy in a pre-locked state. After the lock on the parent
   * directory is released, the copy continues to have a read lock,
   * preventing it from being modified or deleted while in use (even if
   * a cleanup process successfully obtains a write lock on the parent).
   *
   * Because we control the structure of the runtime in this case, we
   * actually lock /usr/.ref instead of /.ref, and ensure that /.ref
   * is a symlink to it. This might become important if we pass the
   * runtime's /usr to Flatpak, which normally takes out a lock on
   * /usr/.ref (obviously this will only work if the runtime happens
   * to be merged-/usr). */
  copy_lock = pv_bwrap_lock_new (temp_dir_fd, "usr/.ref",
                                 PV_BWRAP_LOCK_FLAGS_CREATE,
                                 error);

  if (copy_lock == NULL)
    return glnx_prefix_error (error,
                              "Unable to lock \"%s/.ref\" in temporary runtime",
                              dest_usr);

  if (is_just_usr)
    {
      g_autofree gchar *in_root = g_build_filename (temp_dir, ".ref", NULL);

      if (unlink (in_root) != 0 && errno != ENOENT)
        return glnx_throw_errno_prefix (error,
                                        "Cannot remove \"%s\"", in_root);

      if (symlink ("usr/.ref", in_root) != 0)
        return glnx_throw_errno_prefix (error,
                                        "Cannot create symlink \"%s\" -> usr/.ref",
                                        in_root);
    }

  dir = g_dir_open (source_usr, 0, error);

  if (dir == NULL)
    return FALSE;

  for (member = g_dir_read_name (dir);
       member != NULL;
       member = g_dir_read_name (dir))
    {
      /* Create symlinks ${temp_dir}/bin -> usr/bin, etc. if missing.
       *
       * Also make ${temp_dir}/etc, ${temp_dir}/var symlinks to etc
       * and var, for the benefit of tools like capsule-capture-libs
       * accessing /etc/ld.so.cache in the incomplete container (for the
       * final container command-line they get merged by bind_runtime()
       * instead). */
      if (g_str_equal (member, "bin") ||
          g_str_equal (member, "etc") ||
          (g_str_has_prefix (member, "lib") &&
           !g_str_equal (member, "libexec")) ||
          g_str_equal (member, "sbin") ||
          g_str_equal (member, "var"))
        {
          g_autofree gchar *dest = g_build_filename (temp_dir, member, NULL);
          g_autofree gchar *target = g_build_filename ("usr", member, NULL);

          if (symlink (target, dest) != 0)
            {
              /* Ignore EEXIST in the case where it was not just /usr:
               * it's fine if the runtime we copied from source_files
               * already had either directories or symlinks in its root
               * directory */
              if (is_just_usr || errno != EEXIST)
                return glnx_throw_errno_prefix (error,
                                                "Cannot create symlink \"%s\" -> %s",
                                                dest, target);
            }
        }
    }

  /* Hand over from holding a lock on the source to just holding a lock
   * on the copy. We'll release source_lock when we leave this scope */
  source_lock = g_steal_pointer (&self->runtime_lock);
  self->runtime_lock = g_steal_pointer (&copy_lock);
  self->mutable_sysroot = g_steal_pointer (&temp_dir);
  self->mutable_sysroot_fd = glnx_steal_fd (&temp_dir_fd);

  return TRUE;
}

static gboolean
pv_runtime_initable_init (GInitable *initable,
                          GCancellable *cancellable G_GNUC_UNUSED,
                          GError **error)
{
  PvRuntime *self = PV_RUNTIME (initable);
  g_autofree gchar *files_ref = NULL;

  g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  if (!g_file_test (self->bubblewrap, G_FILE_TEST_IS_EXECUTABLE))
    {
      return glnx_throw (error, "\"%s\" is not executable",
                         self->bubblewrap);
    }

  if (self->mutable_parent != NULL
      && !g_file_test (self->mutable_parent, G_FILE_TEST_IS_DIR))
    {
      return glnx_throw (error, "\"%s\" is not a directory",
                         self->mutable_parent);
    }

  if (!g_file_test (self->source_files, G_FILE_TEST_IS_DIR))
    {
      return glnx_throw (error, "\"%s\" is not a directory",
                         self->source_files);
    }

  if (!g_file_test (self->tools_dir, G_FILE_TEST_IS_DIR))
    {
      return glnx_throw (error, "\"%s\" is not a directory",
                         self->tools_dir);
    }

  /* Take a lock on the runtime until we're finished with setup,
   * to make sure it doesn't get deleted.
   *
   * If the runtime is mounted read-only in the container, it will
   * continue to be locked until all processes in the container exit.
   * If we make a temporary mutable copy, we only hold this lock until
   * setup has finished. */
  files_ref = g_build_filename (self->source_files, ".ref", NULL);
  self->runtime_lock = pv_bwrap_lock_new (AT_FDCWD, files_ref,
                                          PV_BWRAP_LOCK_FLAGS_CREATE,
                                          error);

  /* If the runtime is being deleted, ... don't use it, I suppose? */
  if (self->runtime_lock == NULL)
    return FALSE;

  g_debug ("Creating temporary directories...");

  /* Using a runtime requires a temporary directory */
  self->tmpdir = g_dir_make_tmp ("pressure-vessel-wrap.XXXXXX", error);

  if (self->tmpdir == NULL)
    return FALSE;

  if (!pv_runtime_init_mutable (self, error))
    return FALSE;

  if (self->mutable_sysroot != NULL)
    self->runtime_files = self->mutable_sysroot;
  else
    self->runtime_files = self->source_files;

  self->overrides = g_build_filename (self->tmpdir, "overrides", NULL);
  g_mkdir (self->overrides, 0700);
  self->overrides_bin = g_build_filename (self->overrides, "bin", NULL);
  g_mkdir (self->overrides_bin, 0700);

  self->runtime_usr = g_build_filename (self->runtime_files, "usr", NULL);

  if (g_file_test (self->runtime_usr, G_FILE_TEST_IS_DIR))
    {
      self->runtime_is_just_usr = FALSE;
    }
  else
    {
      /* runtime_files is just a merged /usr. */
      self->runtime_is_just_usr = TRUE;
      g_free (self->runtime_usr);
      self->runtime_usr = g_strdup (self->runtime_files);
    }

  return TRUE;
}

void
pv_runtime_cleanup (PvRuntime *self)
{
  g_autoptr(GError) local_error = NULL;

  g_return_if_fail (PV_IS_RUNTIME (self));

  if (self->tmpdir != NULL &&
      !glnx_shutil_rm_rf_at (-1, self->tmpdir, NULL, &local_error))
    {
      g_warning ("Unable to delete temporary directory: %s",
                 local_error->message);
    }

  g_clear_pointer (&self->overrides_bin, g_free);
  g_clear_pointer (&self->overrides, g_free);
  g_clear_pointer (&self->container_access, g_free);
  g_clear_pointer (&self->container_access_adverb, flatpak_bwrap_free);
  g_clear_pointer (&self->tmpdir, g_free);
}

static void
pv_runtime_finalize (GObject *object)
{
  PvRuntime *self = PV_RUNTIME (object);

  pv_runtime_cleanup (self);
  g_free (self->bubblewrap);
  glnx_close_fd (&self->mutable_parent_fd);
  g_free (self->mutable_parent);
  glnx_close_fd (&self->mutable_sysroot_fd);
  g_free (self->mutable_sysroot);
  g_free (self->runtime_usr);
  g_free (self->source_files);
  g_free (self->tools_dir);

  if (self->runtime_lock != NULL)
    pv_bwrap_lock_free (self->runtime_lock);

  G_OBJECT_CLASS (pv_runtime_parent_class)->finalize (object);
}

static void
pv_runtime_class_init (PvRuntimeClass *cls)
{
  GObjectClass *object_class = G_OBJECT_CLASS (cls);

  object_class->get_property = pv_runtime_get_property;
  object_class->set_property = pv_runtime_set_property;
  object_class->constructed = pv_runtime_constructed;
  object_class->finalize = pv_runtime_finalize;

  properties[PROP_BUBBLEWRAP] =
    g_param_spec_string ("bubblewrap", "Bubblewrap",
                         "Bubblewrap executable",
                         NULL,
                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
                          G_PARAM_STATIC_STRINGS));

  properties[PROP_FLAGS] =
    g_param_spec_flags ("flags", "Flags",
                        "Flags affecting how we set up the runtime",
                        PV_TYPE_RUNTIME_FLAGS, PV_RUNTIME_FLAGS_NONE,
                        (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
                         G_PARAM_STATIC_STRINGS));

  properties[PROP_MUTABLE_PARENT] =
    g_param_spec_string ("mutable-parent", "Mutable parent",
                         ("Path to a directory in which to create a "
                          "mutable copy of source-files, or NULL"),
                         NULL,
                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
                          G_PARAM_STATIC_STRINGS));

  properties[PROP_SOURCE_FILES] =
    g_param_spec_string ("source-files", "Source files",
                         ("Path to read-only runtime files (merged-/usr "
                          "or sysroot) on host system"),
                         NULL,
                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
                          G_PARAM_STATIC_STRINGS));

  properties[PROP_TOOLS_DIRECTORY] =
    g_param_spec_string ("tools-directory", "Tools directory",
                         "Path to pressure-vessel/bin on host system",
                         NULL,
                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
                          G_PARAM_STATIC_STRINGS));

  g_object_class_install_properties (object_class, N_PROPERTIES, properties);
}

PvRuntime *
pv_runtime_new (const char *source_files,
                const char *mutable_parent,
                const char *bubblewrap,
                const char *tools_dir,
                PvRuntimeFlags flags,
                GError **error)
{
  g_return_val_if_fail (source_files != NULL, NULL);
  g_return_val_if_fail (bubblewrap != NULL, NULL);
  g_return_val_if_fail (tools_dir != NULL, NULL);
  g_return_val_if_fail ((flags & ~(PV_RUNTIME_FLAGS_MASK)) == 0, NULL);

  return g_initable_new (PV_TYPE_RUNTIME,
                         NULL,
                         error,
                         "bubblewrap", bubblewrap,
                         "mutable-parent", mutable_parent,
                         "source-files", source_files,
                         "tools-directory", tools_dir,
                         "flags", flags,
                         NULL);
}

/* If we are using a runtime, pass the lock fd to the executed process,
 * and make it act as a subreaper for the game itself.
 *
 * If we were using --unshare-pid then we could use bwrap --sync-fd
 * and rely on bubblewrap's init process for this, but we currently
 * can't do that without breaking gameoverlayrender.so's assumptions. */
void
pv_runtime_append_lock_adverb (PvRuntime *self,
                               FlatpakBwrap *bwrap)
{
  g_return_if_fail (PV_IS_RUNTIME (self));
  g_return_if_fail (!pv_bwrap_was_finished (bwrap));

  flatpak_bwrap_add_args (bwrap,
                          "/run/pressure-vessel/bin/pressure-vessel-with-lock",
                          "--subreaper",
                          NULL);

  if (pv_bwrap_lock_is_ofd (self->runtime_lock))
    {
      int fd = pv_bwrap_lock_steal_fd (self->runtime_lock);
      g_autofree gchar *fd_str = NULL;
      g_debug ("Passing lock fd %d down to with-lock", fd);
      flatpak_bwrap_add_fd (bwrap, fd);
      fd_str = g_strdup_printf ("%d", fd);
      flatpak_bwrap_add_args (bwrap,
                              "--fd", fd_str,
                              NULL);
    }
  else
    {
      /*
       * We were unable to take out an open file descriptor lock,
       * so it will be released on fork(). Tell the with-lock process
       * to take out its own compatible lock instead. There will be
       * a short window during which we have lost our lock but the
       * with-lock process has not taken its lock - that's unavoidable
       * if we want to use exec() to replace ourselves with the
       * container.
       *
       * pv_bwrap_bind_usr() arranges for /.ref to either be a
       * symbolic link to /usr/.ref which is the runtime_lock
       * (if opt_runtime is a merged /usr), or the runtime_lock
       * itself (otherwise).
       */
      g_debug ("Telling process in container to lock /.ref");
      flatpak_bwrap_add_args (bwrap,
                              "--lock-file", "/.ref",
                              NULL);
    }

  flatpak_bwrap_add_args (bwrap,
                          "--",
                          NULL);
}

/*
 * Set self->container_access_adverb to a (possibly empty) command prefix
 * that will result in the container being available at
 * self->container_access, with write access to self->overrides, and
 * read-only access to everything else.
 */
static gboolean
pv_runtime_provide_container_access (PvRuntime *self,
                                     GError **error)
{
  if (self->container_access_adverb != NULL)
    return TRUE;

  if (!self->runtime_is_just_usr)
    {
      static const char * const need_top_level[] =
      {
        "bin",
        "etc",
        "lib",
        "sbin",
      };
      gsize i;

      /* If we are working with a runtime that has a root directory containing
       * /etc and /usr, we can just access it via its path - that's "the same
       * shape" that the final system is going to be.
       *
       * In particular, if we are working with a writeable copy of a runtime
       * that we are editing in-place, it's always like that. */
      g_debug ("%s: Setting up runtime without using bwrap",
               G_STRFUNC);
      self->container_access_adverb = flatpak_bwrap_new (NULL);
      self->container_access = g_strdup (self->runtime_files);

      /* This is going to go poorly for us if the runtime is not complete.
       * !self->runtime_is_just_usr means we know it has a /usr subdirectory,
       * but that doesn't guarantee that it has /bin, /lib, /sbin (either
       * in the form of real directories or symlinks into /usr) and /etc
       * (for at least /etc/alternatives and /etc/ld.so.cache).
       *
       * This check is not intended to be exhaustive, merely something
       * that will catch obvious mistakes like completely forgetting to
       * add the merged-/usr symlinks.
       *
       * In practice we also need /lib64 for 64-bit-capable runtimes,
       * but a pure 32-bit runtime would legitimately not have that,
       * so we don't check for it. */
      for (i = 0; i < G_N_ELEMENTS (need_top_level); i++)
        {
          g_autofree gchar *path = g_build_filename (self->runtime_files,
                                                     need_top_level[i],
                                                     NULL);

          if (!g_file_test (path, G_FILE_TEST_IS_DIR))
            g_warning ("%s does not exist, this probably won't work",
                       path);
        }
    }
  else
    {
      /* Otherwise, will we need to use bwrap to build a directory hierarchy
       * that is the same shape as the final system. */
      g_debug ("%s: Using bwrap to set up runtime that is just /usr",
               G_STRFUNC);

      /* By design, writeable copies of the runtime never need this:
       * the writeable copy is a complete sysroot, not just a merged /usr. */
      g_assert (self->mutable_sysroot == NULL);

      self->container_access = g_build_filename (self->tmpdir, "mnt", NULL);
      g_mkdir (self->container_access, 0700);

      self->container_access_adverb = flatpak_bwrap_new (NULL);
      flatpak_bwrap_add_args (self->container_access_adverb,
                              self->bubblewrap,
                              "--ro-bind", "/", "/",
                              "--bind", self->overrides, self->overrides,
                              "--tmpfs", self->container_access,
                              NULL);
      if (!pv_bwrap_bind_usr (self->container_access_adverb,
                              self->runtime_files,
                              self->container_access,
                              error))
        return FALSE;
    }

  return TRUE;
}

static gboolean
try_bind_dri (PvRuntime *self,
              RuntimeArchitecture *arch,
              FlatpakBwrap *bwrap,
              const char *libdir,
              GError **error)
{
  g_autofree gchar *dri = g_build_filename (libdir, "dri", NULL);
  g_autofree gchar *s2tc = g_build_filename (libdir, "libtxc_dxtn.so", NULL);

  if (g_file_test (dri, G_FILE_TEST_IS_DIR))
    {
      g_autoptr(FlatpakBwrap) temp_bwrap = NULL;
      g_autofree gchar *expr = NULL;
      g_autoptr(GDir) dir = NULL;
      const char *member;

      g_debug ("Collecting dependencies of DRI drivers in \"%s\"...", dri);
      expr = g_strdup_printf ("only-dependencies:if-exists:path-match:%s/dri/*.so",
                              libdir);

      if (!pv_runtime_provide_container_access (self, error))
        return FALSE;

      temp_bwrap = pv_bwrap_copy (self->container_access_adverb);
      flatpak_bwrap_add_args (temp_bwrap,
                              arch->capsule_capture_libs,
                              "--container", self->container_access,
                              "--link-target", "/run/host",
                              "--dest", arch->libdir_on_host,
                              "--provider", "/",
                              expr,
                              NULL);
      flatpak_bwrap_finish (temp_bwrap);

      if (!pv_bwrap_run_sync (temp_bwrap, NULL, error))
        return FALSE;

      g_clear_pointer (&temp_bwrap, flatpak_bwrap_free);

      dir = g_dir_open (dri, 0, error);

      if (dir == NULL)
        return FALSE;

      for (member = g_dir_read_name (dir);
           member != NULL;
           member = g_dir_read_name (dir))
        {
          g_autofree gchar *target = g_build_filename ("/run/host", dri,
                                                      member, NULL);
          g_autofree gchar *dest = g_build_filename (arch->libdir_on_host,
                                                     "dri", member, NULL);

          g_debug ("Creating symbolic link \"%s\" -> \"%s\" for \"%s\" DRI driver",
                   dest, target, arch->tuple);

          /* Delete an existing symlink if any, like ln -f */
          if (unlink (dest) != 0 && errno != ENOENT)
            return glnx_throw_errno_prefix (error,
                                            "Unable to remove \"%s\"",
                                            dest);

          if (symlink (target, dest) != 0)
            return glnx_throw_errno_prefix (error,
                                            "Unable to create symlink \"%s\" -> \"%s\"",
                                            dest, target);
        }
    }

  if (g_file_test (s2tc, G_FILE_TEST_EXISTS))
    {
      g_autoptr(FlatpakBwrap) temp_bwrap = NULL;
      g_autofree gchar *expr = NULL;

      g_debug ("Collecting s2tc \"%s\" and its dependencies...", s2tc);
      expr = g_strdup_printf ("path-match:%s", s2tc);

      if (!pv_runtime_provide_container_access (self, error))
        return FALSE;

      temp_bwrap = pv_bwrap_copy (self->container_access_adverb);
      flatpak_bwrap_add_args (temp_bwrap,
                              arch->capsule_capture_libs,
                              "--container", self->container_access,
                              "--link-target", "/run/host",
                              "--dest", arch->libdir_on_host,
                              "--provider", "/",
                              expr,
                              NULL);
      flatpak_bwrap_finish (temp_bwrap);

      if (!pv_bwrap_run_sync (temp_bwrap, NULL, error))
        return FALSE;
    }

  return TRUE;
}

/*
 * Try to make sure we have all the locales we need, by running
 * the helper from steam-runtime-tools in the container. If this
 * fails, it isn't fatal - carry on anyway.
 *
 * @bwrap must be set up to have the same libc that we will be using
 * for the container.
 */
static void
ensure_locales (PvRuntime *self,
                gboolean on_host,
                FlatpakBwrap *bwrap)
{
  g_autoptr(GError) local_error = NULL;
  g_autoptr(FlatpakBwrap) run_locale_gen = NULL;
  g_autofree gchar *locale_gen = NULL;
  g_autofree gchar *locales = g_build_filename (self->overrides, "locales", NULL);
  g_autoptr(GDir) dir = NULL;
  int exit_status;

  /* bwrap can't own any fds yet, because if it did,
   * flatpak_bwrap_append_bwrap() would steal them. */
  g_return_if_fail (bwrap->fds == NULL || bwrap->fds->len == 0);

  g_mkdir (locales, 0700);

  run_locale_gen = flatpak_bwrap_new (NULL);

  if (on_host)
    {
      locale_gen = g_build_filename (self->tools_dir,
                                     "pressure-vessel-locale-gen",
                                     NULL);
      /* We don't actually need to use bwrap when we're just running on
       * the host system. */
      flatpak_bwrap_add_args (run_locale_gen,
                              locale_gen,
                              "--output-dir", locales,
                              "--verbose",
                              NULL);
    }
  else
    {
      locale_gen = g_build_filename ("/run/host/tools",
                                     "pressure-vessel-locale-gen",
                                     NULL);

      flatpak_bwrap_append_bwrap (run_locale_gen, bwrap);
      pv_bwrap_copy_tree (run_locale_gen, self->overrides, "/overrides");

      if (!flatpak_bwrap_bundle_args (run_locale_gen, 1, -1, FALSE,
                                      &local_error))
        {
          g_warning ("Unable to set up locale-gen command: %s",
                     local_error->message);
          g_clear_error (&local_error);
        }

      flatpak_bwrap_add_args (run_locale_gen,
                              "--ro-bind", self->tools_dir, "/run/host/tools",
                              "--bind", locales, "/overrides/locales",
                              locale_gen,
                              "--output-dir", "/overrides/locales",
                              "--verbose",
                              NULL);
    }

  flatpak_bwrap_finish (run_locale_gen);

  /* locale-gen exits 72 (EX_OSFILE) if it had to correct for
   * missing locales at OS level. This is not an error. */
  if (!pv_bwrap_run_sync (run_locale_gen, &exit_status, &local_error))
    {
      if (exit_status == EX_OSFILE)
        g_debug ("pressure-vessel-locale-gen created missing locales");
      else
        g_warning ("Unable to generate locales: %s", local_error->message);

      g_clear_error (&local_error);
    }
  else
    {
      g_debug ("No locales generated");
    }

  dir = g_dir_open (locales, 0, NULL);

  /* If the directory is not empty, make it the container's LOCPATH */
  if (dir != NULL && g_dir_read_name (dir) != NULL)
    {
      g_autoptr(GString) locpath = NULL;

      g_debug ("%s is non-empty", locales);

      locpath = g_string_new ("/overrides/locales");
      pv_search_path_append (locpath, g_getenv ("LOCPATH"));
      flatpak_bwrap_add_args (bwrap,
                              "--setenv", "LOCPATH", locpath->str,
                              NULL);
    }
  else
    {
      g_debug ("%s is empty", locales);
    }
}

typedef enum
{
  ICD_KIND_NONEXISTENT,
  ICD_KIND_ABSOLUTE,
  ICD_KIND_SONAME
} IcdKind;

typedef struct
{
  /* (type SrtEglIcd) or (type SrtVulkanIcd) or (type SrtVdpauDriver)
   * or (type SrtVaApiDriver) */
  gpointer icd;
  gchar *resolved_library;
  /* Last entry is always NONEXISTENT.
   * For VA-API, we use [0] and ignore the other elements.
   * For the rest, this is keyed by the index of a multiarch tuple
   * in multiarch_tuples. */
  IcdKind kinds[G_N_ELEMENTS (multiarch_tuples)];
  /* Last entry is always NULL */
  gchar *paths_in_container[G_N_ELEMENTS (multiarch_tuples)];
} IcdDetails;

static IcdDetails *
icd_details_new (gpointer icd)
{
  IcdDetails *self;
  gsize i;

  g_return_val_if_fail (G_IS_OBJECT (icd), NULL);
  g_return_val_if_fail (SRT_IS_EGL_ICD (icd) ||
                        SRT_IS_VULKAN_ICD (icd) ||
                        SRT_IS_VDPAU_DRIVER (icd) ||
                        SRT_IS_VA_API_DRIVER (icd),
                        NULL);

  self = g_slice_new0 (IcdDetails);
  self->icd = g_object_ref (icd);
  self->resolved_library = NULL;

  for (i = 0; i < G_N_ELEMENTS (multiarch_tuples); i++)
    {
      self->kinds[i] = ICD_KIND_NONEXISTENT;
      self->paths_in_container[i] = NULL;
    }

  return self;
}

static void
icd_details_free (IcdDetails *self)
{
  gsize i;

  g_object_unref (self->icd);
  g_free (self->resolved_library);

  for (i = 0; i < G_N_ELEMENTS (multiarch_tuples); i++)
    g_free (self->paths_in_container[i]);

  g_slice_free (IcdDetails, self);
}

G_DEFINE_AUTOPTR_CLEANUP_FUNC (IcdDetails, icd_details_free)

/*
 * @sequence_number: numbered directory to use. Set to G_MAXSIZE to
 *  use just @subdir without a numbered sub directory
 */
static gboolean
bind_icd (PvRuntime *self,
          RuntimeArchitecture *arch,
          gsize sequence_number,
          const char *subdir,
          IcdDetails *details,
          GError **error)
{
  static const char options[] = "if-exists:if-same-abi";
  g_autofree gchar *on_host = NULL;
  g_autofree gchar *pattern = NULL;
  g_autofree gchar *dependency_pattern = NULL;
  g_autofree gchar *seq_str = NULL;
  const char *mode;
  g_autoptr(FlatpakBwrap) temp_bwrap = NULL;
  gsize multiarch_index;

  g_return_val_if_fail (runtime_architecture_check_valid (arch), FALSE);
  g_return_val_if_fail (subdir != NULL, FALSE);
  g_return_val_if_fail (details != NULL, FALSE);
  g_return_val_if_fail (details->resolved_library != NULL, FALSE);
  multiarch_index = arch->multiarch_index;
  g_return_val_if_fail (details->kinds[multiarch_index] == ICD_KIND_NONEXISTENT,
                        FALSE);
  g_return_val_if_fail (details->paths_in_container[multiarch_index] == NULL,
                        FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  if (g_path_is_absolute (details->resolved_library))
    {
      details->kinds[multiarch_index] = ICD_KIND_ABSOLUTE;
      mode = "path";

      /* Because the ICDs might have collisions among their
       * basenames (might differ only by directory), we put each
       * in its own numbered directory. */
      if (sequence_number != G_MAXSIZE)
        {
          seq_str = g_strdup_printf ("%" G_GSIZE_FORMAT, sequence_number);
          on_host = g_build_filename (arch->libdir_on_host, subdir, seq_str, NULL);
        }
      else
        {
          on_host = g_build_filename (arch->libdir_on_host, subdir, NULL);
        }

      g_debug ("Ensuring %s exists", on_host);

      if (g_mkdir_with_parents (on_host, 0700) != 0)
        return glnx_throw_errno_prefix (error, "Unable to create %s", on_host);
    }
  else
    {
      /* ICDs in the default search path by definition can't collide:
       * one of them is the first one we find, and we use that one. */
      details->kinds[multiarch_index] = ICD_KIND_SONAME;
      mode = "soname";
    }

  pattern = g_strdup_printf ("no-dependencies:even-if-older:%s:%s:%s",
                             options, mode, details->resolved_library);
  dependency_pattern = g_strdup_printf ("only-dependencies:%s:%s:%s",
                                        options, mode, details->resolved_library);

  if (!pv_runtime_provide_container_access (self, error))
    return FALSE;

  temp_bwrap = pv_bwrap_copy (self->container_access_adverb);
  flatpak_bwrap_add_args (temp_bwrap,
                          arch->capsule_capture_libs,
                          "--container", self->container_access,
                          "--link-target", "/run/host",
                          "--dest",
                            on_host == NULL ? arch->libdir_on_host : on_host,
                          "--provider", "/",
                          pattern,
                          NULL);
  flatpak_bwrap_finish (temp_bwrap);

  if (!pv_bwrap_run_sync (temp_bwrap, NULL, error))
    return FALSE;

  g_clear_pointer (&temp_bwrap, flatpak_bwrap_free);

  if (on_host != NULL)
    {
      /* Try to remove the directory we created. If it succeeds, then we
       * can optimize slightly by not capturing the dependencies: there's
       * no point, because we know we didn't create a symlink to the ICD
       * itself. (It must have been nonexistent or for a different ABI.) */
      if (g_rmdir (on_host) == 0)
        {
          details->kinds[multiarch_index] = ICD_KIND_NONEXISTENT;
          return TRUE;
        }
    }

  temp_bwrap = pv_bwrap_copy (self->container_access_adverb);
  flatpak_bwrap_add_args (temp_bwrap,
                          arch->capsule_capture_libs,
                          "--container", self->container_access,
                          "--link-target", "/run/host",
                          "--dest", arch->libdir_on_host,
                          "--provider", "/",
                          dependency_pattern,
                          NULL);
  flatpak_bwrap_finish (temp_bwrap);

  if (!pv_bwrap_run_sync (temp_bwrap, NULL, error))
    return FALSE;

  g_clear_pointer (&temp_bwrap, flatpak_bwrap_free);

  if (details->kinds[multiarch_index] == ICD_KIND_ABSOLUTE)
    {
      g_assert (on_host != NULL);
      details->paths_in_container[multiarch_index] = g_build_filename (arch->libdir_in_container,
                                                                       subdir,
                                                                       seq_str ? seq_str : "",
                                                                       glnx_basename (details->resolved_library),
                                                                       NULL);
    }

  return TRUE;
}

static gboolean
bind_runtime (PvRuntime *self,
              FlatpakBwrap *bwrap,
              GError **error)
{
  static const char * const bind_mutable[] =
  {
    "etc",
    "var/cache",
    "var/lib"
  };
  static const char * const dont_bind[] =
  {
    "/etc/group",
    "/etc/passwd",
    "/etc/host.conf",
    "/etc/hosts",
    "/etc/localtime",
    "/etc/machine-id",
    "/etc/resolv.conf",
    "/var/lib/dbus",
    "/var/lib/dhcp",
    "/var/lib/sudo",
    "/var/lib/urandom",
    NULL
  };
  g_autofree gchar *xrd = g_strdup_printf ("/run/user/%ld", (long) geteuid ());
  gsize i;
  const gchar *member;

  g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE);
  g_return_val_if_fail (!pv_bwrap_was_finished (bwrap), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  if (!pv_bwrap_bind_usr (bwrap, self->runtime_files, "/", error))
    return FALSE;
  flatpak_bwrap_add_args (bwrap,
                          "--setenv", "XDG_RUNTIME_DIR", xrd,
                          "--tmpfs", "/run",
                          "--tmpfs", "/tmp",
                          "--tmpfs", "/var",
                          "--symlink", "../run", "/var/run",
                          NULL);

  if (!pv_bwrap_bind_usr (bwrap, "/", "/run/host", error))
    return FALSE;

  for (i = 0; i < G_N_ELEMENTS (bind_mutable); i++)
    {
      g_autofree gchar *path = g_build_filename (self->runtime_files,
                                                 bind_mutable[i],
                                                 NULL);
      g_autoptr(GDir) dir = NULL;

      dir = g_dir_open (path, 0, NULL);

      if (dir == NULL)
        continue;

      for (member = g_dir_read_name (dir);
           member != NULL;
           member = g_dir_read_name (dir))
        {
          g_autofree gchar *dest = g_build_filename ("/", bind_mutable[i],
                                                     member, NULL);
          g_autofree gchar *full = NULL;
          g_autofree gchar *target = NULL;

          if (g_strv_contains (dont_bind, dest))
            continue;

          full = g_build_filename (self->runtime_files,
                                   bind_mutable[i],
                                   member,
                                   NULL);
          target = glnx_readlinkat_malloc (-1, full, NULL, NULL);

          if (target != NULL)
            flatpak_bwrap_add_args (bwrap, "--symlink", target, dest, NULL);
          else
            flatpak_bwrap_add_args (bwrap, "--ro-bind", full, dest, NULL);
        }
    }

  if (g_file_test ("/etc/machine-id", G_FILE_TEST_EXISTS))
    {
      flatpak_bwrap_add_args (bwrap,
                              "--ro-bind", "/etc/machine-id", "/etc/machine-id",
                              "--symlink", "/etc/machine-id",
                              "/var/lib/dbus/machine-id",
                              NULL);
    }
  else if (g_file_test ("/var/lib/dbus/machine-id", G_FILE_TEST_EXISTS))
    {
      flatpak_bwrap_add_args (bwrap,
                              "--ro-bind", "/var/lib/dbus/machine-id",
                              "/etc/machine-id",
                              "--symlink", "/etc/machine-id",
                              "/var/lib/dbus/machine-id",
                              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",
                            NULL);
  if (g_file_test ("/etc/host.conf", G_FILE_TEST_EXISTS))
    flatpak_bwrap_add_args (bwrap,
                            "--ro-bind", "/etc/host.conf", "/etc/host.conf",
                            NULL);
  if (g_file_test ("/etc/hosts", G_FILE_TEST_EXISTS))
    flatpak_bwrap_add_args (bwrap,
                            "--ro-bind", "/etc/hosts", "/etc/hosts",
                            NULL);

  /* TODO: Synthesize a passwd with only the user and nobody,
   * like Flatpak does? */
  if (g_file_test ("/etc/passwd", G_FILE_TEST_EXISTS))
    flatpak_bwrap_add_args (bwrap,
                            "--ro-bind", "/etc/passwd", "/etc/passwd",
                            NULL);
  if (g_file_test ("/etc/group", G_FILE_TEST_EXISTS))
    flatpak_bwrap_add_args (bwrap,
                            "--ro-bind", "/etc/group", "/etc/group",
                            NULL);

  if (self->flags & PV_RUNTIME_FLAGS_HOST_GRAPHICS_STACK)
    {
      if (!pv_runtime_use_host_graphics_stack (self, bwrap, error))
        return FALSE;
    }

  /* This needs to be done after pv_runtime_use_host_graphics_stack()
   * has decided whether to bring in the host system's libc. */
  if (self->flags & PV_RUNTIME_FLAGS_GENERATE_LOCALES)
    ensure_locales (self, self->any_libc_from_host, bwrap);

  /* 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. */

  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, self->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 *target = NULL;
      gboolean is_reachable = FALSE;
      g_autofree char *tz = flatpak_get_timezone ();
      g_autofree char *timezone_content = g_strdup_printf ("%s\n", tz);

      target = glnx_readlinkat_malloc (-1, "/etc/localtime", NULL, NULL);

      if (target != 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, target);
          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", target, "/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;
}

static gboolean
pv_runtime_use_host_graphics_stack (PvRuntime *self,
                                    FlatpakBwrap *bwrap,
                                    GError **error)
{
  gsize i, j;
  g_autoptr(GString) dri_path = g_string_new ("");
  g_autoptr(GString) egl_path = g_string_new ("");
  g_autoptr(GString) vulkan_path = g_string_new ("");
  g_autoptr(GString) va_api_path = g_string_new ("");
  gboolean any_architecture_works = FALSE;
  g_autofree gchar *localedef = NULL;
  g_autofree gchar *ldconfig = NULL;
  g_autofree gchar *locale = NULL;
  g_autofree gchar *dir_on_host = NULL;
  g_autoptr(SrtSystemInfo) system_info = srt_system_info_new (NULL);
  g_autoptr(SrtObjectList) egl_icds = NULL;
  g_autoptr(SrtObjectList) vulkan_icds = NULL;
  g_autoptr(GPtrArray) egl_icd_details = NULL;      /* (element-type IcdDetails) */
  g_autoptr(GPtrArray) vulkan_icd_details = NULL;   /* (element-type IcdDetails) */
  g_autoptr(GPtrArray) va_api_icd_details = NULL;   /* (element-type IcdDetails) */
  guint n_egl_icds;
  guint n_vulkan_icds;
  const GList *icd_iter;
  gboolean all_libdrm_from_host = TRUE;
  g_autoptr(GHashTable) libdrm_data_from_host = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                                       g_free, NULL);
  g_autoptr(GHashTable) gconv_from_host = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                                 g_free, NULL);
  g_autofree gchar *libdrm_data_in_runtime = NULL;
  g_autofree gchar *best_libdrm_data_from_host = NULL;
  GHashTableIter iter;
  const gchar *gconv_path;

  g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE);
  g_return_val_if_fail (!pv_bwrap_was_finished (bwrap), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  if (!pv_runtime_provide_container_access (self, error))
    return FALSE;

  g_debug ("Enumerating EGL ICDs on host system...");
  egl_icds = srt_system_info_list_egl_icds (system_info, multiarch_tuples);
  n_egl_icds = g_list_length (egl_icds);

  egl_icd_details = g_ptr_array_new_full (n_egl_icds,
                                          (GDestroyNotify) G_CALLBACK (icd_details_free));
  for (icd_iter = egl_icds, j = 0;
       icd_iter != NULL;
       icd_iter = icd_iter->next, j++)
    {
      SrtEglIcd *icd = icd_iter->data;
      const gchar *path = srt_egl_icd_get_json_path (icd);
      GError *local_error = NULL;

      if (!srt_egl_icd_check_error (icd, &local_error))
        {
          g_debug ("Failed to load EGL ICD #%" G_GSIZE_FORMAT  " from %s: %s",
                   j, path, local_error->message);
          g_clear_error (&local_error);
          continue;
        }

      g_debug ("EGL ICD #%" G_GSIZE_FORMAT " at %s: %s",
               j, path, srt_egl_icd_get_library_path (icd));

      g_ptr_array_add (egl_icd_details, icd_details_new (icd));
    }

  g_debug ("Enumerating Vulkan ICDs on host system...");
  vulkan_icds = srt_system_info_list_vulkan_icds (system_info,
                                                  multiarch_tuples);
  n_vulkan_icds = g_list_length (vulkan_icds);

  vulkan_icd_details = g_ptr_array_new_full (n_vulkan_icds,
                                             (GDestroyNotify) G_CALLBACK (icd_details_free));

  for (icd_iter = vulkan_icds, j = 0;
       icd_iter != NULL;
       icd_iter = icd_iter->next, j++)
    {
      SrtVulkanIcd *icd = icd_iter->data;
      const gchar *path = srt_vulkan_icd_get_json_path (icd);
      GError *local_error = NULL;

      if (!srt_vulkan_icd_check_error (icd, &local_error))
        {
          g_debug ("Failed to load Vulkan ICD #%" G_GSIZE_FORMAT " from %s: %s",
                   j, path, local_error->message);
          g_clear_error (&local_error);
          continue;
        }

      g_debug ("Vulkan ICD #%" G_GSIZE_FORMAT " at %s: %s",
               j, path, srt_vulkan_icd_get_library_path (icd));

      g_ptr_array_add (vulkan_icd_details, icd_details_new (icd));
    }

  /* We set this FALSE later if we decide not to use the host libc for
   * some architecture. */
  self->all_libc_from_host = TRUE;

  g_assert (multiarch_tuples[G_N_ELEMENTS (multiarch_tuples) - 1] == NULL);

  for (i = 0; i < G_N_ELEMENTS (multiarch_tuples) - 1; i++)
    {
      g_auto (RuntimeArchitecture) arch_on_stack = { i };
      RuntimeArchitecture *arch = &arch_on_stack;

      g_debug ("Checking for %s libraries...", multiarch_tuples[i]);

      if (runtime_architecture_init (arch, self))
        {
          g_auto(GStrv) dirs = NULL;
          g_autoptr(FlatpakBwrap) temp_bwrap = NULL;
          g_autofree gchar *this_dri_path_on_host = g_build_filename (arch->libdir_on_host,
                                                                      "dri", NULL);
          g_autofree gchar *this_dri_path_in_container = g_build_filename (arch->libdir_in_container,
                                                                           "dri", NULL);
          g_autofree gchar *libc = NULL;
          g_autofree gchar *ld_so_in_runtime = NULL;
          g_autofree gchar *libdrm = NULL;
          g_autoptr(SrtObjectList) vdpau_drivers = NULL;
          g_autoptr(SrtObjectList) va_api_drivers = NULL;

          temp_bwrap = flatpak_bwrap_new (NULL);
          flatpak_bwrap_add_args (temp_bwrap,
                                  self->bubblewrap,
                                  NULL);

          if (!pv_bwrap_bind_usr (temp_bwrap,
                                  self->runtime_files,
                                  "/",
                                  error))
            return FALSE;

          flatpak_bwrap_add_args (temp_bwrap,
                                  "env", "PATH=/usr/bin:/bin",
                                  "readlink", "-e", arch->ld_so,
                                  NULL);
          flatpak_bwrap_finish (temp_bwrap);

          ld_so_in_runtime = pv_capture_output ((const char * const *) temp_bwrap->argv->pdata,
                                                NULL);

          g_clear_pointer (&temp_bwrap, flatpak_bwrap_free);

          if (ld_so_in_runtime == NULL)
            {
              g_debug ("Container does not have %s so it cannot run "
                       "%s binaries",
                       arch->ld_so, arch->tuple);
              continue;
            }

          any_architecture_works = TRUE;
          g_debug ("Container path: %s -> %s",
                   arch->ld_so, ld_so_in_runtime);

          pv_search_path_append (dri_path, this_dri_path_in_container);

          g_mkdir_with_parents (arch->libdir_on_host, 0755);
          g_mkdir_with_parents (this_dri_path_on_host, 0755);

          g_debug ("Collecting graphics drivers from host system...");

          temp_bwrap = pv_bwrap_copy (self->container_access_adverb);
          flatpak_bwrap_add_args (temp_bwrap,
                                  arch->capsule_capture_libs,
                                  "--container", self->container_access,
                                  "--link-target", "/run/host",
                                  "--dest", arch->libdir_on_host,
                                  "--provider", "/",
                                  /* Mesa GLX, etc. */
                                  "gl:",
                                  /* Vulkan */
                                  "if-exists:if-same-abi:soname:libvulkan.so.1",
                                  /* VDPAU */
                                  "if-exists:if-same-abi:soname:libvdpau.so.1",
                                  /* VA-API */
                                  "if-exists:if-same-abi:soname:libva.so.1",
                                  "if-exists:if-same-abi:soname:libva-drm.so.1",
                                  "if-exists:if-same-abi:soname:libva-glx.so.1",
                                  "if-exists:if-same-abi:soname:libva-x11.so.1",
                                  "if-exists:if-same-abi:soname:libva.so.2",
                                  "if-exists:if-same-abi:soname:libva-drm.so.2",
                                  "if-exists:if-same-abi:soname:libva-glx.so.2",
                                  "if-exists:if-same-abi:soname:libva-x11.so.2",
                                  /* NVIDIA proprietary stack */
                                  "if-exists:even-if-older:soname-match:libEGL.so.*",
                                  "if-exists:even-if-older:soname-match:libEGL_nvidia.so.*",
                                  "if-exists:even-if-older:soname-match:libGL.so.*",
                                  "if-exists:even-if-older:soname-match:libGLESv1_CM.so.*",
                                  "if-exists:even-if-older:soname-match:libGLESv1_CM_nvidia.so.*",
                                  "if-exists:even-if-older:soname-match:libGLESv2.so.*",
                                  "if-exists:even-if-older:soname-match:libGLESv2_nvidia.so.*",
                                  "if-exists:even-if-older:soname-match:libGLX.so.*",
                                  "if-exists:even-if-older:soname-match:libGLX_nvidia.so.*",
                                  "if-exists:even-if-older:soname-match:libGLX_indirect.so.*",
                                  "if-exists:even-if-older:soname-match:libGLdispatch.so.*",
                                  "if-exists:even-if-older:soname-match:libOpenGL.so.*",
                                  "if-exists:even-if-older:soname-match:libcuda.so.*",
                                  "if-exists:even-if-older:soname-match:libglx.so.*",
                                  "if-exists:even-if-older:soname-match:libnvidia-cbl.so.*",
                                  "if-exists:even-if-older:soname-match:libnvidia-cfg.so.*",
                                  "if-exists:even-if-older:soname-match:libnvidia-compiler.so.*",
                                  "if-exists:even-if-older:soname-match:libnvidia-egl-wayland.so.*",
                                  "if-exists:even-if-older:soname-match:libnvidia-eglcore.so.*",
                                  "if-exists:even-if-older:soname-match:libnvidia-encode.so.*",
                                  "if-exists:even-if-older:soname-match:libnvidia-fatbinaryloader.so.*",
                                  "if-exists:even-if-older:soname-match:libnvidia-fbc.so.*",
                                  "if-exists:even-if-older:soname-match:libnvidia-glcore.so.*",
                                  "if-exists:even-if-older:soname-match:libnvidia-glsi.so.*",
                                  "if-exists:even-if-older:soname-match:libnvidia-glvkspirv.so.*",
                                  "if-exists:even-if-older:soname-match:libnvidia-ifr.so.*",
                                  "if-exists:even-if-older:soname-match:libnvidia-ml.so.*",
                                  "if-exists:even-if-older:soname-match:libnvidia-opencl.so.*",
                                  "if-exists:even-if-older:soname-match:libnvidia-opticalflow.so.*",
                                  "if-exists:even-if-older:soname-match:libnvidia-ptxjitcompiler.so.*",
                                  "if-exists:even-if-older:soname-match:libnvidia-rtcore.so.*",
                                  "if-exists:even-if-older:soname-match:libnvidia-tls.so.*",
                                  "if-exists:even-if-older:soname-match:libOpenCL.so.*",
                                  "if-exists:even-if-older:soname-match:libvdpau_nvidia.so.*",
                                  NULL);
          flatpak_bwrap_finish (temp_bwrap);

          if (!pv_bwrap_run_sync (temp_bwrap, NULL, error))
            return FALSE;

          g_clear_pointer (&temp_bwrap, flatpak_bwrap_free);

          g_debug ("Collecting %s EGL drivers from host system...",
                   arch->tuple);

          for (j = 0; j < egl_icd_details->len; j++)
            {
              IcdDetails *details = g_ptr_array_index (egl_icd_details, j);
              SrtEglIcd *icd = SRT_EGL_ICD (details->icd);

              if (!srt_egl_icd_check_error (icd, NULL))
                continue;

              details->resolved_library = srt_egl_icd_resolve_library_path (icd);
              g_assert (details->resolved_library != NULL);

              if (!bind_icd (self, arch, j, "glvnd", details, error))
                return FALSE;
            }

          g_debug ("Collecting %s Vulkan drivers from host system...",
                   arch->tuple);

          for (j = 0; j < vulkan_icd_details->len; j++)
            {
              IcdDetails *details = g_ptr_array_index (vulkan_icd_details, j);
              SrtVulkanIcd *icd = SRT_VULKAN_ICD (details->icd);

              if (!srt_vulkan_icd_check_error (icd, NULL))
                continue;

              details->resolved_library = srt_vulkan_icd_resolve_library_path (icd);
              g_assert (details->resolved_library != NULL);

              if (!bind_icd (self, arch, j, "vulkan", details, error))
                return FALSE;
            }

          g_debug ("Enumerating %s VDPAU ICDs on host system...", arch->tuple);
          vdpau_drivers = srt_system_info_list_vdpau_drivers (system_info,
                                                              arch->tuple,
                                                              SRT_DRIVER_FLAGS_NONE);

          for (icd_iter = vdpau_drivers; icd_iter != NULL; icd_iter = icd_iter->next)
            {
              g_autoptr(IcdDetails) details = icd_details_new (icd_iter->data);
              details->resolved_library = srt_vdpau_driver_resolve_library_path (details->icd);
              g_assert (details->resolved_library != NULL);
              g_assert (g_path_is_absolute (details->resolved_library));

              /* We avoid using the sequence number for VDPAU because they can only
               * be located in a single directory, so by definition we can't have
               * collisions */
              if (!bind_icd (self, arch, G_MAXSIZE, "vdpau", details, error))
                return FALSE;
            }

          g_debug ("Enumerating %s VA-API drivers on host system...",
                   arch->tuple);
          va_api_drivers = srt_system_info_list_va_api_drivers (system_info,
                                                                arch->tuple,
                                                                SRT_DRIVER_FLAGS_NONE);

          /* Guess that there will be about the same number of VA-API ICDs
           * for each word size. This only needs to be approximately right:
           * g_ptr_array_add() will resize the allocated buffer if needed. */
          if (va_api_icd_details == NULL)
            va_api_icd_details = g_ptr_array_new_full (g_list_length (va_api_drivers) * (G_N_ELEMENTS (multiarch_tuples) - 1),
                                                       (GDestroyNotify) G_CALLBACK (icd_details_free));

          for (icd_iter = va_api_drivers, j = 0; icd_iter != NULL; icd_iter = icd_iter->next, j++)
            {
              g_autoptr(IcdDetails) details = icd_details_new (icd_iter->data);
              details->resolved_library = srt_va_api_driver_resolve_library_path (details->icd);
              g_assert (details->resolved_library != NULL);
              g_assert (g_path_is_absolute (details->resolved_library));

              if (!bind_icd (self, arch, j, "dri", details, error))
                return FALSE;

              g_ptr_array_add (va_api_icd_details, g_steal_pointer (&details));
            }

          libc = g_build_filename (arch->libdir_on_host, "libc.so.6", NULL);

          /* If we are going to use the host system's libc6 (likely)
           * then we have to use its ld.so too. */
          if (g_file_test (libc, G_FILE_TEST_IS_SYMLINK))
            {
              g_autofree gchar *ld_so_in_host = NULL;
              g_autofree char *libc_target = NULL;

              g_debug ("Making host ld.so visible in container");

              ld_so_in_host = realpath (arch->ld_so, NULL);

              if (ld_so_in_host == NULL)
                return glnx_throw_errno_prefix (error,
                                                "Unable to determine host path to %s",
                                                arch->ld_so);

              g_debug ("Host path: %s -> %s", arch->ld_so, ld_so_in_host);
              g_debug ("Container path: %s -> %s", arch->ld_so, ld_so_in_runtime);
              flatpak_bwrap_add_args (bwrap,
                                      "--ro-bind", ld_so_in_host,
                                      ld_so_in_runtime,
                                      NULL);

              /* Collect miscellaneous libraries that libc might dlopen.
               * At the moment this is just libidn2. */
              g_assert (temp_bwrap == NULL);
              temp_bwrap = pv_bwrap_copy (self->container_access_adverb);
              flatpak_bwrap_add_args (temp_bwrap,
                                      arch->capsule_capture_libs,
                                      "--container", self->container_access,
                                      "--link-target", "/run/host",
                                      "--dest", arch->libdir_on_host,
                                      "--provider", "/",
                                      "if-exists:libidn2.so.0",
                                      NULL);
              flatpak_bwrap_finish (temp_bwrap);

              if (!pv_bwrap_run_sync (temp_bwrap, NULL, error))
                return FALSE;

              g_clear_pointer (&temp_bwrap, flatpak_bwrap_free);

              libc_target = glnx_readlinkat_malloc (-1, libc, NULL, NULL);
              if (libc_target != NULL)
                {
                  g_autofree gchar *dir = NULL;
                  g_autofree gchar *gconv_dir_in_host = NULL;
                  gboolean found = FALSE;

                  dir = g_path_get_dirname (libc_target);

                  if (g_str_has_prefix (dir, "/run/host/"))
                    memmove (dir, dir + strlen ("/run/host"), strlen (dir) - strlen ("/run/host") + 1);

                  /* We are assuming that in the glibc "Makeconfig", $(libdir) was the same as
                   * $(slibdir) (this is the upstream default) or the same as "/usr$(slibdir)"
                   * (like in Debian without the mergerd /usr). We also assume that $(gconvdir)
                   * had its default value "$(libdir)/gconv". */
                  gconv_dir_in_host = g_build_filename (dir, "gconv", NULL);

                  if (g_file_test (gconv_dir_in_host, G_FILE_TEST_IS_DIR))
                    {
                      g_hash_table_add (gconv_from_host, g_steal_pointer (&gconv_dir_in_host));
                      found = TRUE;
                    }
                  else if (!g_str_has_prefix (dir, "/usr/"))
                    {
                      g_free (gconv_dir_in_host);
                      gconv_dir_in_host = g_build_filename ("/usr", dir, "gconv", NULL);
                      if (g_file_test (gconv_dir_in_host, G_FILE_TEST_IS_DIR))
                        {
                          g_hash_table_add (gconv_from_host, g_steal_pointer (&gconv_dir_in_host));
                          found = TRUE;
                        }
                    }

                  if (!found)
                    {
                      g_debug ("We were expecting to have the gconv modules directory in the "
                               "host to be located in \"%s\", but instead it is missing",
                               gconv_dir_in_host);
                    }
                }

              self->any_libc_from_host = TRUE;
            }
          else
            {
              self->all_libc_from_host = FALSE;
            }

          libdrm = g_build_filename (arch->libdir_on_host, "libdrm.so.2", NULL);

          /* If we have libdrm.so.2 in overrides we also want to mount
           * ${prefix}/share/libdrm from the host. ${prefix} is derived from
           * the absolute path of libdrm.so.2 */
          if (g_file_test (libdrm, G_FILE_TEST_IS_SYMLINK))
            {
              g_autofree char *target = NULL;
              target = glnx_readlinkat_malloc (-1, libdrm, NULL, NULL);

              if (target != NULL)
                {
                  g_autofree gchar *dir = NULL;
                  g_autofree gchar *lib_multiarch = NULL;
                  g_autofree gchar *libdrm_dir_in_host = NULL;

                  dir = g_path_get_dirname (target);

                  lib_multiarch = g_build_filename ("/lib", arch->tuple, NULL);
                  if (g_str_has_suffix (dir, lib_multiarch))
                    dir[strlen (dir) - strlen (lib_multiarch)] = '\0';
                  else if (g_str_has_suffix (dir, "/lib64"))
                    dir[strlen (dir) - strlen ("/lib64")] = '\0';
                  else if (g_str_has_suffix (dir, "/lib32"))
                    dir[strlen (dir) - strlen ("/lib32")] = '\0';
                  else if (g_str_has_suffix (dir, "/lib"))
                    dir[strlen (dir) - strlen ("/lib")] = '\0';

                  if (g_str_has_prefix (dir, "/run/host"))
                    memmove (dir, dir + strlen ("/run/host"), strlen (dir) - strlen ("/run/host") + 1);

                  libdrm_dir_in_host = g_build_filename (dir, "share", "libdrm", NULL);

                  if (g_file_test (libdrm_dir_in_host, G_FILE_TEST_IS_DIR))
                    {
                      g_hash_table_add (libdrm_data_from_host, g_steal_pointer (&libdrm_dir_in_host));
                    }
                  else
                    {
                      g_debug ("We were expecting to have the libdrm directory in the host "
                               "to be located in \"%s\", but instead it is missing",
                               libdrm_dir_in_host);
                    }
                }
            }
          else
            {
              /* For at least a single architecture, libdrm is newer in the container */
              all_libdrm_from_host = FALSE;
            }

          /* Order matters: drivers from a later entry will overwrite
           * drivers from an earlier entry. Because we don't know whether
           * /lib and /usr/lib are 32- or 64-bit, we need to prioritize
           * libQUAL higher. Prioritize Debian-style multiarch higher
           * still, because it's completely unambiguous. */
          dirs = g_new0 (gchar *, 7);
          dirs[0] = g_strdup ("/lib");
          dirs[1] = g_strdup ("/usr/lib");
          dirs[2] = g_build_filename ("/", arch->libqual, NULL);
          dirs[3] = g_build_filename ("/usr", arch->libqual, NULL);
          dirs[4] = g_build_filename ("/lib", arch->tuple, NULL);
          dirs[5] = g_build_filename ("/usr", "lib", arch->tuple, NULL);

          for (j = 0; j < 6; j++)
            {
              if (!try_bind_dri (self, arch, bwrap, dirs[j], error))
                return FALSE;
            }
        }
    }

  if (!any_architecture_works)
    {
      GString *archs = g_string_new ("");

      g_assert (multiarch_tuples[G_N_ELEMENTS (multiarch_tuples) - 1] == NULL);

      for (i = 0; i < G_N_ELEMENTS (multiarch_tuples) - 1; 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 (self->any_libc_from_host && !self->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 (self->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);

      localedef = g_find_program_in_path ("localedef");

      if (localedef == NULL)
        {
          g_warning ("Cannot find localedef in PATH");
        }
      else
        {
          g_autofree gchar *target = g_build_filename ("/run/host",
                                                       localedef, NULL);

          flatpak_bwrap_add_args (bwrap,
                                  "--symlink", target,
                                  "/overrides/bin/localedef",
                                  NULL);
        }

      locale = g_find_program_in_path ("locale");

      if (locale == NULL)
        {
          g_warning ("Cannot find locale in PATH");
        }
      else
        {
          g_autofree gchar *target = g_build_filename ("/run/host",
                                                       locale, NULL);

          flatpak_bwrap_add_args (bwrap,
                                  "--symlink", target,
                                  "/overrides/bin/locale",
                                  NULL);
        }

      ldconfig = g_find_program_in_path ("ldconfig");

      if (ldconfig == NULL
          && g_file_test ("/sbin/ldconfig", G_FILE_TEST_IS_EXECUTABLE))
        ldconfig = g_strdup ("/sbin/ldconfig");

      if (ldconfig == NULL
          && g_file_test ("/usr/sbin/ldconfig", G_FILE_TEST_IS_EXECUTABLE))
        ldconfig = g_strdup ("/usr/sbin/ldconfig");

      if (ldconfig == NULL)
        {
          g_warning ("Cannot find ldconfig in PATH, /sbin or /usr/sbin");
        }
      else
        {
          flatpak_bwrap_add_args (bwrap,
                                  "--ro-bind", ldconfig, "/sbin/ldconfig",
                                  NULL);
        }

      g_debug ("Making host gconv modules visible in container");

      g_hash_table_iter_init (&iter, gconv_from_host);
      while (g_hash_table_iter_next (&iter, (gpointer *)&gconv_path, NULL))
        {
          g_autofree gchar *gconv_in_runtime = NULL;

          if (g_str_has_prefix (gconv_path, "/usr/"))
            gconv_in_runtime = g_build_filename (self->runtime_usr, gconv_path + strlen ("/usr"), NULL);
          else
            gconv_in_runtime = g_build_filename (self->runtime_usr, gconv_path, NULL);

          if (g_file_test (gconv_in_runtime, G_FILE_TEST_IS_DIR))
            {
              flatpak_bwrap_add_args (bwrap,
                                      "--ro-bind", gconv_path, gconv_path,
                                      NULL);
            }
        }
    }
  else
    {
      g_debug ("Using included locale data from container");
      g_debug ("Using included gconv modules from container");
    }

  if (g_hash_table_size (libdrm_data_from_host) > 0 && !all_libdrm_from_host)
    {
      /* See the explanation in the similar "any_libc_from_host && !all_libc_from_host"
       * case, above */
      g_warning ("Using libdrm.so.2 from host system for some but not all "
                 "architectures! Will take /usr/share/libdrm from host.");
    }

  if (g_hash_table_size (libdrm_data_from_host) == 1)
    {
      best_libdrm_data_from_host = g_strdup (pv_hash_table_get_arbitrary_key (libdrm_data_from_host));
    }
  else if (g_hash_table_size (libdrm_data_from_host) > 1)
    {
      g_warning ("Found more than one possible libdrm data directory from host");
      /* Prioritize "/usr/share/libdrm" if available. Otherwise randomly pick
       * the first directory in the hash table */
      if (g_hash_table_contains (libdrm_data_from_host, "/usr/share/libdrm"))
        best_libdrm_data_from_host = g_strdup ("/usr/share/libdrm");
      else
        best_libdrm_data_from_host = g_strdup (pv_hash_table_get_arbitrary_key (libdrm_data_from_host));
    }

  libdrm_data_in_runtime = g_build_filename (self->runtime_usr, "share", "libdrm", NULL);

  if (best_libdrm_data_from_host != NULL &&
      g_file_test (libdrm_data_in_runtime, G_FILE_TEST_IS_DIR))
    {
      flatpak_bwrap_add_args (bwrap,
                              "--ro-bind", best_libdrm_data_from_host, "/usr/share/libdrm",
                              NULL);
    }

  g_debug ("Setting up EGL ICD JSON...");

  dir_on_host = g_build_filename (self->overrides,
                                  "share", "glvnd", "egl_vendor.d", NULL);

  if (g_mkdir_with_parents (dir_on_host, 0700) != 0)
    {
      glnx_throw_errno_prefix (error, "Unable to create %s", dir_on_host);
      return FALSE;
    }

  for (j = 0; j < egl_icd_details->len; j++)
    {
      IcdDetails *details = g_ptr_array_index (egl_icd_details, j);
      SrtEglIcd *icd = SRT_EGL_ICD (details->icd);
      gboolean need_host_json = FALSE;

      if (!srt_egl_icd_check_error (icd, NULL))
        continue;

      for (i = 0; i < G_N_ELEMENTS (multiarch_tuples) - 1; i++)
        {
          g_assert (i < G_N_ELEMENTS (details->kinds));
          g_assert (i < G_N_ELEMENTS (details->paths_in_container));

          if (details->kinds[i] == ICD_KIND_ABSOLUTE)
            {
              g_autoptr(SrtEglIcd) replacement = NULL;
              g_autofree gchar *json_on_host = NULL;
              g_autofree gchar *json_in_container = NULL;
              g_autofree gchar *json_base = NULL;

              g_assert (details->paths_in_container[i] != NULL);

              json_base = g_strdup_printf ("%" G_GSIZE_FORMAT "-%s.json",
                                           j, multiarch_tuples[i]);
              json_on_host = g_build_filename (dir_on_host, json_base, NULL);
              json_in_container = g_build_filename ("/overrides", "share",
                                                    "glvnd", "egl_vendor.d",
                                                    json_base, NULL);

              replacement = srt_egl_icd_new_replace_library_path (icd,
                                                                  details->paths_in_container[i]);

              if (!srt_egl_icd_write_to_file (replacement, json_on_host,
                                                 error))
                return FALSE;

              pv_search_path_append (egl_path, json_in_container);
            }
          else if (details->kinds[i] == ICD_KIND_SONAME)
            {
              need_host_json = TRUE;
            }
        }

      if (need_host_json)
        {
          g_autofree gchar *json_in_container = NULL;
          g_autofree gchar *json_base = NULL;
          const char *json_on_host = srt_egl_icd_get_json_path (icd);

          json_base = g_strdup_printf ("%" G_GSIZE_FORMAT ".json", j);
          json_in_container = g_build_filename ("/overrides", "share",
                                                "glvnd", "egl_vendor.d",
                                                json_base, NULL);

          flatpak_bwrap_add_args (bwrap,
                                  "--ro-bind", json_on_host, json_in_container,
                                  NULL);
          pv_search_path_append (egl_path, json_in_container);
        }
    }

  g_debug ("Setting up Vulkan ICD JSON...");

  dir_on_host = g_build_filename (self->overrides,
                                  "share", "vulkan", "icd.d", NULL);

  if (g_mkdir_with_parents (dir_on_host, 0700) != 0)
    {
      glnx_throw_errno_prefix (error, "Unable to create %s", dir_on_host);
      return FALSE;
    }

  for (j = 0; j < vulkan_icd_details->len; j++)
    {
      IcdDetails *details = g_ptr_array_index (vulkan_icd_details, j);
      SrtVulkanIcd *icd = SRT_VULKAN_ICD (details->icd);
      gboolean need_host_json = FALSE;

      if (!srt_vulkan_icd_check_error (icd, NULL))
        continue;

      for (i = 0; i < G_N_ELEMENTS (multiarch_tuples) - 1; i++)
        {
          g_assert (i < G_N_ELEMENTS (details->kinds));
          g_assert (i < G_N_ELEMENTS (details->paths_in_container));
          if (details->kinds[i] == ICD_KIND_ABSOLUTE)
            {
              g_autoptr(SrtVulkanIcd) replacement = NULL;
              g_autofree gchar *json_on_host = NULL;
              g_autofree gchar *json_in_container = NULL;
              g_autofree gchar *json_base = NULL;

              g_assert (details->paths_in_container[i] != NULL);

              json_base = g_strdup_printf ("%" G_GSIZE_FORMAT "-%s.json",
                                           j, multiarch_tuples[i]);
              json_on_host = g_build_filename (dir_on_host, json_base, NULL);
              json_in_container = g_build_filename ("/overrides", "share",
                                                    "vulkan", "icd.d",
                                                    json_base, NULL);

              replacement = srt_vulkan_icd_new_replace_library_path (icd,
                                                                     details->paths_in_container[i]);

              if (!srt_vulkan_icd_write_to_file (replacement, json_on_host,
                                                 error))
                return FALSE;

              pv_search_path_append (vulkan_path, json_in_container);
            }
          else if (details->kinds[i] == ICD_KIND_SONAME)
            {
              need_host_json = TRUE;
            }
        }

      if (need_host_json)
        {
          g_autofree gchar *json_in_container = NULL;
          g_autofree gchar *json_base = NULL;
          const char *json_on_host = srt_vulkan_icd_get_json_path (icd);

          json_base = g_strdup_printf ("%" G_GSIZE_FORMAT ".json", j);
          json_in_container = g_build_filename ("/overrides", "share",
                                                "vulkan", "icd.d",
                                                json_base, NULL);

          flatpak_bwrap_add_args (bwrap,
                                  "--ro-bind", json_on_host, json_in_container,
                                  NULL);
          pv_search_path_append (vulkan_path, json_in_container);
        }
    }

  for (j = 0; j < va_api_icd_details->len; j++)
    {
      IcdDetails *details = g_ptr_array_index (va_api_icd_details, j);

      for (i = 0; i < G_N_ELEMENTS (multiarch_tuples) - 1; i++)
        {
          g_assert (i < G_N_ELEMENTS (details->kinds));
          g_assert (i < G_N_ELEMENTS (details->paths_in_container));

          if (details->kinds[i] == ICD_KIND_NONEXISTENT)
            {
              continue;
            }
          else
            {
              g_autofree gchar *parent = NULL;

              g_assert (details->kinds[i] == ICD_KIND_ABSOLUTE);

              parent = g_path_get_dirname (details->paths_in_container[i]);
              pv_search_path_append (va_api_path, parent);
            }
        }
    }

  if (dri_path->len != 0)
      flatpak_bwrap_add_args (bwrap,
                              "--setenv", "LIBGL_DRIVERS_PATH", dri_path->str,
                              NULL);
  else
      flatpak_bwrap_add_args (bwrap,
                              "--unsetenv", "LIBGL_DRIVERS_PATH",
                              NULL);

  if (egl_path->len != 0)
      flatpak_bwrap_add_args (bwrap,
                              "--setenv", "__EGL_VENDOR_LIBRARY_FILENAMES",
                              egl_path->str, NULL);
  else
      flatpak_bwrap_add_args (bwrap,
                              "--unsetenv", "__EGL_VENDOR_LIBRARY_FILENAMES",
                              NULL);

  flatpak_bwrap_add_args (bwrap,
                          "--unsetenv", "__EGL_VENDOR_LIBRARY_DIRS",
                          NULL);

  if (vulkan_path->len != 0)
      flatpak_bwrap_add_args (bwrap,
                              "--setenv", "VK_ICD_FILENAMES",
                              vulkan_path->str, NULL);
  else
      flatpak_bwrap_add_args (bwrap,
                              "--unsetenv", "VK_ICD_FILENAMES",
                              NULL);

  if (va_api_path->len != 0)
      flatpak_bwrap_add_args (bwrap,
                              "--setenv", "LIBVA_DRIVERS_PATH",
                              va_api_path->str, NULL);
  else
      flatpak_bwrap_add_args (bwrap,
                              "--unsetenv", "LIBVA_DRIVERS_PATH",
                              NULL);

  /* We binded the VDPAU drivers in "%{libdir}/vdpau".
   * Unfortunately VDPAU_DRIVER_PATH can hold just a single path, so we can't
   * easily list both x86_64 and i386 drivers path.
   * As a workaround we set VDPAU_DRIVER_PATH to
   * "/overrides/lib/${PLATFORM}-linux-gnu/vdpau". And because we can't control
   * the ${PLATFORM} placeholder value we also create symlinks from `i486`, up
   * to `i686`, to the library directory `i386` that we expect to have
   * already. */
  flatpak_bwrap_add_args (bwrap,
                          "--setenv", "VDPAU_DRIVER_PATH",
                          "/overrides/lib/${PLATFORM}-linux-gnu/vdpau",
                          NULL);

  static const char * const extra_multiarch_tuples[] =
  {
    "i486-linux-gnu",
    "i586-linux-gnu",
    "i686-linux-gnu",
    NULL
  };

  g_autofree gchar *i386_libdir_on_host = g_build_filename (self->overrides, "lib",
                                                            "i386-linux-gnu",
                                                            NULL);

  for (i = 0; i < G_N_ELEMENTS (extra_multiarch_tuples) - 1; i++)
    {
      g_autofree gchar *extra_libdir_on_host = g_build_filename (self->overrides, "lib",
                                                                 extra_multiarch_tuples[i],
                                                                 NULL);

      if (!g_file_test (extra_libdir_on_host, G_FILE_TEST_EXISTS) &&
          g_file_test (i386_libdir_on_host, G_FILE_TEST_IS_DIR))
        {
          g_unlink (extra_libdir_on_host);

          if (symlink ("i386-linux-gnu", extra_libdir_on_host) != 0)
            return glnx_throw_errno_prefix (error,
                                            "Unable to create symlink %s -> i386-linux-gnu",
                                            extra_libdir_on_host);
        }
    }

  return TRUE;
}

gboolean
pv_runtime_bind (PvRuntime *self,
                 FlatpakBwrap *bwrap,
                 GError **error)
{
  g_autofree gchar *pressure_vessel_prefix = NULL;

  g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE);
  g_return_val_if_fail (!pv_bwrap_was_finished (bwrap), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  /* Start with just the root tmpfs (which appears automatically)
   * and the standard API filesystems */
  pv_bwrap_add_api_filesystems (bwrap);

  if (!bind_runtime (self, bwrap, error))
    return FALSE;

  pressure_vessel_prefix = g_path_get_dirname (self->tools_dir);

  /* Make sure pressure-vessel itself is visible there. */
  flatpak_bwrap_add_args (bwrap,
                          "--ro-bind",
                          pressure_vessel_prefix,
                          "/run/pressure-vessel",
                          NULL);

  pv_runtime_set_search_paths (self, bwrap);

  return TRUE;
}

void
pv_runtime_set_search_paths (PvRuntime *self,
                             FlatpakBwrap *bwrap)
{
  g_autoptr(GString) ld_library_path = g_string_new ("");
  gsize i;

  /* TODO: Adapt the use_ld_so_cache code from Flatpak instead
   * of setting LD_LIBRARY_PATH, for better robustness against
   * games that set their own LD_LIBRARY_PATH ignoring what they
   * got from the environment */
  g_assert (multiarch_tuples[G_N_ELEMENTS (multiarch_tuples) - 1] == NULL);

    for (i = 0; i < G_N_ELEMENTS (multiarch_tuples) - 1; i++)
      {
        g_autofree gchar *ld_path = NULL;

        ld_path = g_build_filename ("/overrides", "lib",
                                   multiarch_tuples[i], NULL);

        pv_search_path_append (ld_library_path, ld_path);
      }

  /* This would be filtered out by a setuid bwrap, so we have to go
   * via --setenv. */
  flatpak_bwrap_add_args (bwrap,
                          /* The PATH from outside the container doesn't
                           * really make sense inside the container:
                           * in principle the layout could be totally
                           * different. */
                          "--setenv", "PATH", "/overrides/bin:/usr/bin:/bin",
                          "--setenv", "LD_LIBRARY_PATH",
                          ld_library_path->str,
                          NULL);
}

static void
pv_runtime_initable_iface_init (GInitableIface *iface,
                                gpointer unused G_GNUC_UNUSED)
{
  iface->init = pv_runtime_initable_init;
}