/*
 * 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 "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 "steam-runtime-tools/glib-backports-internal.h"
#include "steam-runtime-tools/json-glib-backports-internal.h"
#include "libglnx/libglnx.h"

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

#include "steam-runtime-tools/graphics-internal.h"
#include "steam-runtime-tools/profiling-internal.h"
#include "steam-runtime-tools/resolve-in-sysroot-internal.h"
#include "steam-runtime-tools/system-info-internal.h"
#include "steam-runtime-tools/utils-internal.h"

#include "bwrap.h"
#include "bwrap-lock.h"
#include "enumtypes.h"
#include "exports.h"
#include "flatpak-run-private.h"
#include "mtree.h"
#include "tree-copy.h"
#include "utils.h"

typedef struct
{
  GCancellable *cancellable;
  GThread *thread;
  SrtSystemInfo *system_info;
} EnumerationThread;

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

struct _PvRuntime
{
  GObject parent;

  gchar *bubblewrap;
  gchar *source;
  gchar *id;
  gchar *deployment;
  gchar *source_files;          /* either deployment or that + "/files" */
  gchar *tools_dir;
  PvBwrapLock *runtime_lock;
  GStrv original_environ;

  gchar *libcapsule_knowledge;
  gchar *runtime_abi_json;
  gchar *variable_dir;
  gchar *mutable_sysroot;
  gchar *tmpdir;
  gchar *overrides;
  const gchar *overrides_in_container;
  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" */
  gchar *runtime_app;           /* runtime_files + "/app" */
  gchar *runtime_files_on_host;
  const gchar *adverb_in_container;
  PvGraphicsProvider *provider;
  const gchar *host_in_current_namespace;
  EnumerationThread indep_thread;
  EnumerationThread *arch_threads;

  PvRuntimeFlags flags;
  int variable_dir_fd;
  int mutable_sysroot_fd;
  gboolean any_libc_from_provider;
  gboolean all_libc_from_provider;
  gboolean runtime_is_just_usr;
  gboolean is_steamrt;
  gboolean is_scout;
  gboolean is_flatpak_env;
};

struct _PvRuntimeClass
{
  GObjectClass parent;
};

enum {
  PROP_0,
  PROP_BUBBLEWRAP,
  PROP_GRAPHICS_PROVIDER,
  PROP_SOURCE,
  PROP_ORIGINAL_ENVIRON,
  PROP_FLAGS,
  PROP_ID,
  PROP_VARIABLE_DIR,
  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))

/*
 * Return whether @path is likely to be visible in the provider mount point
 * (e.g. /run/host).
 * This needs to be kept approximately in sync with pv_bwrap_bind_usr()
 * and Flatpak's --filesystem=host-os special keyword.
 *
 * This doesn't currently handle /etc: we make the pessimistic assumption
 * that /etc/ld.so.cache, etc., are not shared.
 */
static gboolean
path_visible_in_provider_namespace (PvRuntimeFlags flags,
                                    const char *path)
{
  while (path[0] == '/')
    path++;

  if ((flags & PV_RUNTIME_FLAGS_FLATPAK_SUBSANDBOX)
      && g_str_has_prefix (path, "app")
      && (path[3] == '\0' || path[3] == '/'))
    return TRUE;

  if (g_str_has_prefix (path, "usr") &&
      (path[3] == '\0' || path[3] == '/'))
    return TRUE;

  if (g_str_has_prefix (path, "lib"))
    return TRUE;

  if (g_str_has_prefix (path, "bin") &&
      (path[3] == '\0' || path[3] == '/'))
    return TRUE;

  if (g_str_has_prefix (path, "sbin") ||
      (path[4] == '\0' || path[4] == '/'))
    return TRUE;

  return FALSE;
}

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

typedef struct
{
  const char *tuple;

  /* Directories other than /usr/lib that we must search for loadable
   * modules, least-ambiguous first, most-ambiguous last, not including
   * Debian-style multiarch directories which are automatically derived
   * from @tuple.
   * - Exherbo <GNU-tuple>/lib
   * - Red-Hat- or Arch-style lib<QUAL>
   * - etc.
   * Size is completely arbitrary, expand as needed */
  const char *multilib[3];

  /* Alternative paths for ld.so.cache, other than ld.so.cache itself.
   * Size is completely arbitrary, expand as needed */
  const char *other_ld_so_cache[2];

  /* Known values that ${PLATFORM} can expand to.
   * Refer to sysdeps/x86/cpu-features.c and sysdeps/x86/dl-procinfo.c
   * in glibc.
   * Size is completely arbitrary, expand as needed */
  const char *platforms[5];
} MultiarchDetails;

/*
 * More details, in the same order as multiarch_tuples
 */
static const MultiarchDetails multiarch_details[] =
{
  {
    .tuple = "x86_64-linux-gnu",
    .multilib = { "x86_64-pc-linux-gnu/lib", "lib64", NULL },
    .other_ld_so_cache = { "ld-x86_64-pc-linux-gnu.cache", NULL },
    .platforms = { "xeon_phi", "haswell", "x86_64", NULL },
  },
  {
    .tuple = "i386-linux-gnu",
    .multilib = { "i686-pc-linux-gnu/lib", "lib32", NULL },
    .other_ld_so_cache = { "ld-i686-pc-linux-gnu.cache", NULL },
    .platforms = { "i686", "i586", "i486", "i386", NULL },
  },
};
G_STATIC_ASSERT (G_N_ELEMENTS (multiarch_details)
                 == G_N_ELEMENTS (multiarch_tuples) - 1);

/* Architecture-independent ld.so.cache filenames, other than the
 * conventional filename /etc/ld.so.cache used upstream and in Debian
 * (we assume this is also what's used in our runtimes). */
static const char * const other_ld_so_cache[] =
{
  /* Clear Linux */
  "/var/cache/ldconfig/ld.so.cache",
};

/*
 * @MULTIARCH_LIBDIRS_FLAGS_REMOVE_OVERRIDDEN:
 *  Return all library directories from which we might need to delete
 *  overridden libraries shipped in the runtime.
 */
typedef enum
{
  MULTIARCH_LIBDIRS_FLAGS_REMOVE_OVERRIDDEN = (1 << 0),
  MULTIARCH_LIBDIRS_FLAGS_NONE = 0
} MultiarchLibdirsFlags;

/*
 * Get the library directories associated with @self, most important or
 * unambiguous first.
 *
 * Returns: (transfer container) (element-type filename):
 */
static GPtrArray *
multiarch_details_get_libdirs (const MultiarchDetails *self,
                               MultiarchLibdirsFlags flags)
{
  g_autoptr(GPtrArray) dirs = g_ptr_array_new_with_free_func (g_free);
  gsize j;

  /* Multiarch is the least ambiguous so we put it first.
   *
   * We historically searched /usr/lib before /lib, but Debian actually
   * does the opposite, and we follow that here.
   *
   * Arguably we should search /usr/local/lib before /lib before /usr/lib,
   * but we don't currently try /usr/local/lib. We could add a flag
   * for that if we don't want to do it unconditionally. */
  g_ptr_array_add (dirs,
                   g_build_filename ("/lib", self->tuple, NULL));
  g_ptr_array_add (dirs,
                   g_build_filename ("/usr", "lib", self->tuple, NULL));

  if (flags & MULTIARCH_LIBDIRS_FLAGS_REMOVE_OVERRIDDEN)
    g_ptr_array_add (dirs,
                     g_build_filename ("/usr", "lib", "mesa",
                                       self->tuple, NULL));

  /* Try other multilib variants next. This includes
   * Exherbo/cross-compilation-style per-architecture prefixes,
   * Red-Hat-style lib64 and Arch-style lib32. */
  for (j = 0; j < G_N_ELEMENTS (self->multilib); j++)
    {
      if (self->multilib[j] == NULL)
        break;

      g_ptr_array_add (dirs,
                       g_build_filename ("/", self->multilib[j], NULL));
      g_ptr_array_add (dirs,
                       g_build_filename ("/usr", self->multilib[j], NULL));
    }

  /* /lib and /usr/lib are lowest priority because they're the most
   * ambiguous: we don't know whether they're meant to contain 32- or
   * 64-bit libraries. */
  g_ptr_array_add (dirs, g_strdup ("/lib"));
  g_ptr_array_add (dirs, g_strdup ("/usr/lib"));

  return g_steal_pointer (&dirs);
}

typedef struct
{
  gsize multiarch_index;
  const MultiarchDetails *details;
  gchar *aliases_in_current_namespace;
  gchar *capsule_capture_libs_basename;
  gchar *capsule_capture_libs;
  gchar *libdir_in_current_namespace;
  gchar *libdir_in_container;
  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 (multiarch_details),
                        FALSE);
  g_return_val_if_fail (self->multiarch_index < G_N_ELEMENTS (multiarch_tuples) - 1,
                        FALSE);
  g_return_val_if_fail (self->details == NULL, FALSE);

  self->details = &multiarch_details[self->multiarch_index];
  g_return_val_if_fail (self->details != NULL, FALSE);
  g_return_val_if_fail (self->details->tuple != NULL, FALSE);
  g_return_val_if_fail (g_strcmp0 (multiarch_tuples[self->multiarch_index],
                                   self->details->tuple) == 0, FALSE);

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

  self->aliases_in_current_namespace = g_build_filename (self->libdir_in_current_namespace,
                                                         "aliases", NULL);

  /* This has the side-effect of testing whether we can run binaries
   * for this architecture on the current environment. We
   * assume that this is the same as whether we can run them
   * on the host, if different. */
  argv[0] = self->capsule_capture_libs;
  pv_run_sync (argv, NULL, NULL, &self->ld_so, NULL);

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

  return TRUE;
}

static gboolean
runtime_architecture_check_valid (RuntimeArchitecture *self)
{
  g_return_val_if_fail (self->multiarch_index < G_N_ELEMENTS (multiarch_details), FALSE);
  g_return_val_if_fail (self->details == &multiarch_details[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_in_current_namespace != NULL, FALSE);
  g_return_val_if_fail (self->libdir_in_container != NULL, FALSE);
  g_return_val_if_fail (self->aliases_in_current_namespace != NULL, 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->details = 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_in_current_namespace, g_free);
  g_clear_pointer (&self->libdir_in_container, g_free);
  g_clear_pointer (&self->aliases_in_current_namespace, 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_provider_graphics_stack (PvRuntime *self,
                                                        FlatpakBwrap *bwrap,
                                                        PvEnviron *container_env,
                                                        GError **error);
static void pv_runtime_set_search_paths (PvRuntime *self,
                                         PvEnviron *container_env);

static void
pv_runtime_init (PvRuntime *self)
{
  self->any_libc_from_provider = FALSE;
  self->all_libc_from_provider = FALSE;
  self->variable_dir_fd = -1;
  self->mutable_sysroot_fd = -1;
  self->is_flatpak_env = g_file_test ("/.flatpak-info",
                                      G_FILE_TEST_IS_REGULAR);
}

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_GRAPHICS_PROVIDER:
        g_value_set_object (value, self->provider);
        break;

      case PROP_ORIGINAL_ENVIRON:
        g_value_set_boxed (value, self->original_environ);
        break;

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

      case PROP_ID:
        g_value_set_string (value, self->id);
        break;

      case PROP_VARIABLE_DIR:
        g_value_set_string (value, self->variable_dir);
        break;

      case PROP_SOURCE:
        g_value_set_string (value, self->source);
        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_GRAPHICS_PROVIDER:
        /* Construct-only */
        g_return_if_fail (self->provider == NULL);
        self->provider = g_value_dup_object (value);
        break;

      case PROP_ORIGINAL_ENVIRON:
        /* Construct-only */
        g_return_if_fail (self->original_environ == NULL);

        self->original_environ = g_value_dup_boxed (value);
        break;

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

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

        if (path != NULL)
          {
            self->variable_dir = realpath (path, NULL);

            if (self->variable_dir == NULL)
              {
                /* It doesn't exist. Keep the non-canonical path so we
                 * can warn about it later */
                self->variable_dir = g_strdup (path);
              }
          }

        break;

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

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

        if (path != NULL)
          {
            self->source = realpath (path, NULL);

            if (self->source == NULL)
              {
                /* It doesn't exist. Keep the non-canonical path so we
                 * can warn about it later */
                self->source = g_strdup (path);
              }
          }

        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->original_environ != NULL);
  g_return_if_fail (self->source != NULL);
  g_return_if_fail (self->tools_dir != NULL);
}

static void
pv_runtime_maybe_garbage_collect_subdir (const char *description,
                                         const char *parent,
                                         int parent_fd,
                                         const char *member)
{
  g_autoptr(GError) local_error = NULL;
  g_autoptr(PvBwrapLock) temp_lock = NULL;
  g_autofree gchar *keep = NULL;
  g_autofree gchar *ref = NULL;
  struct stat ignore;

  g_return_if_fail (parent != NULL);
  g_return_if_fail (parent_fd >= 0);
  g_return_if_fail (member != NULL);

  g_debug ("Found %s %s/%s, considering whether to delete it...",
           description, parent, member);

  keep = g_build_filename (member, "keep", NULL);

  if (glnx_fstatat (parent_fd, keep, &ignore,
                    AT_SYMLINK_NOFOLLOW, &local_error))
    {
      g_debug ("Not deleting \"%s/%s\": ./keep exists",
               parent, member);
      return;
    }
  else if (!g_error_matches (local_error, G_IO_ERROR,
                             G_IO_ERROR_NOT_FOUND))
    {
      /* EACCES or something? Give it the benefit of the doubt */
      g_warning ("Not deleting \"%s/%s\": unable to stat ./keep: %s",
               parent, member, local_error->message);
      return;
    }

  g_clear_error (&local_error);

  ref = g_build_filename (member, ".ref", NULL);
  temp_lock = pv_bwrap_lock_new (parent_fd, ref,
                                 (PV_BWRAP_LOCK_FLAGS_CREATE |
                                  PV_BWRAP_LOCK_FLAGS_WRITE),
                                 &local_error);

  if (temp_lock == NULL)
    {
      g_info ("Not deleting \"%s/%s\": unable to get lock: %s",
              parent, member, local_error->message);
      return;
    }

  g_debug ("Deleting \"%s/%s\"...", parent, member);

  /* We have the lock, which would not have happened if someone was
   * still using the runtime, so we can safely delete it. */
  if (!glnx_shutil_rm_rf_at (parent_fd, member, NULL, &local_error))
    {
      g_debug ("Unable to delete %s/%s: %s",
               parent, member, local_error->message);
    }
}

static gboolean
is_old_runtime_deployment (const char *name)
{
  if (g_str_has_prefix (name, "scout_before_"))
    return TRUE;

  if (g_str_has_prefix (name, "soldier_before_"))
    return TRUE;

  if (g_str_has_prefix (name, "scout_0."))
    return TRUE;

  if (g_str_has_prefix (name, "soldier_0."))
    return TRUE;

  if (g_str_has_prefix (name, ".scout_")
      && g_str_has_suffix (name, "_unpack-temp"))
    return TRUE;

  if (g_str_has_prefix (name, ".soldier_")
      && g_str_has_suffix (name, "_unpack-temp"))
    return TRUE;

  return FALSE;
}

gboolean
pv_runtime_garbage_collect_legacy (const char *variable_dir,
                                   const char *runtime_base,
                                   GError **error)
{
  g_autoptr(GError) local_error = NULL;
  g_autoptr(PvBwrapLock) variable_lock = NULL;
  g_autoptr(PvBwrapLock) base_lock = NULL;
  g_autoptr(SrtProfilingTimer) timer = NULL;
  g_auto(GLnxDirFdIterator) variable_dir_iter = { FALSE };
  g_auto(GLnxDirFdIterator) runtime_base_iter = { FALSE };
  glnx_autofd int variable_dir_fd = -1;
  glnx_autofd int runtime_base_fd = -1;
  struct
  {
    const char *path;
    GLnxDirFdIterator *iter;
  } iters[] = {
    { variable_dir, &variable_dir_iter },
    { runtime_base, &runtime_base_iter },
  };
  gsize i;

  g_return_val_if_fail (variable_dir != NULL, FALSE);
  g_return_val_if_fail (runtime_base != NULL, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  timer = _srt_profiling_start ("Cleaning up legacy runtimes in %s and %s",
                                variable_dir, runtime_base);

  if (g_mkdir_with_parents (variable_dir, 0700) != 0)
    return glnx_throw_errno_prefix (error, "Unable to create %s",
                                    variable_dir);

  if (!glnx_opendirat (AT_FDCWD, variable_dir, TRUE,
                       &variable_dir_fd, error))
    return FALSE;

  if (!glnx_opendirat (AT_FDCWD, runtime_base, TRUE,
                       &runtime_base_fd, error))
    return FALSE;

  variable_lock = pv_bwrap_lock_new (variable_dir_fd, ".ref",
                                     (PV_BWRAP_LOCK_FLAGS_CREATE
                                      | PV_BWRAP_LOCK_FLAGS_WRITE),
                                     &local_error);

  /* If we can't take the lock immediately, just don't do GC */
  if (variable_lock == NULL)
    return TRUE;

  /* We take out locks on both the variable directory and the base
   * directory, because historically in the shell scripts we only
   * locked the base directory, and we later moved to locking only the
   * variable directory. Now that we're in C code it seems safest to
   * lock both. */
  base_lock = pv_bwrap_lock_new (runtime_base_fd, ".ref",
                                 (PV_BWRAP_LOCK_FLAGS_CREATE
                                  | PV_BWRAP_LOCK_FLAGS_WRITE),
                                 &local_error);

  /* Same here */
  if (base_lock == NULL)
    return TRUE;

  for (i = 0; i < G_N_ELEMENTS (iters); i++)
    {
      const char * const symlinks[] = { "scout", "soldier" };
      gsize j;

      if (!glnx_dirfd_iterator_init_at (AT_FDCWD, iters[i].path,
                                        TRUE, iters[i].iter, error))
        return FALSE;

      g_debug ("Cleaning up old subdirectories in %s...",
               iters[i].path);

      while (TRUE)
        {
          struct dirent *dent;

          if (!glnx_dirfd_iterator_next_dent_ensure_dtype (iters[i].iter,
                                                           &dent, NULL, error))
            return FALSE;

          if (dent == NULL)
            break;

          switch (dent->d_type)
            {
              case DT_DIR:
                break;

              case DT_BLK:
              case DT_CHR:
              case DT_FIFO:
              case DT_LNK:
              case DT_REG:
              case DT_SOCK:
              case DT_UNKNOWN:
              default:
                g_debug ("Ignoring %s/%s: not a directory",
                         iters[i].path, dent->d_name);
                continue;
            }

          if (!is_old_runtime_deployment (dent->d_name))
            continue;

          pv_runtime_maybe_garbage_collect_subdir ("legacy runtime",
                                                   iters[i].path,
                                                   iters[i].iter->fd,
                                                   dent->d_name);
        }

      g_debug ("Cleaning up old symlinks in %s...",
               iters[i].path);

      for (j = 0; j < G_N_ELEMENTS (symlinks); j++)
        pv_delete_dangling_symlink (iters[i].iter->fd, iters[i].path,
                                    symlinks[j]);
    }

  return TRUE;
}

static gboolean
pv_runtime_garbage_collect (PvRuntime *self,
                            PvBwrapLock *variable_dir_lock,
                            GError **error)
{
  g_auto(GLnxDirFdIterator) iter = { FALSE };
  g_autoptr(SrtProfilingTimer) timer = NULL;

  g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE);
  g_return_val_if_fail (self->variable_dir != NULL, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
  /* We don't actually *use* this: it just acts as an assertion that
   * we are holding the lock on the parent directory. */
  g_return_val_if_fail (variable_dir_lock != NULL, FALSE);

  timer = _srt_profiling_start ("Cleaning up temporary runtimes in %s",
                                self->variable_dir);

  if (!glnx_dirfd_iterator_init_at (AT_FDCWD, self->variable_dir,
                                    TRUE, &iter, error))
    return FALSE;

  while (TRUE)
    {
      struct dirent *dent;

      if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&iter, &dent,
                                                       NULL, error))
        return FALSE;

      if (dent == NULL)
        break;

      switch (dent->d_type)
        {
          case DT_DIR:
            break;

          case DT_BLK:
          case DT_CHR:
          case DT_FIFO:
          case DT_LNK:
          case DT_REG:
          case DT_SOCK:
          case DT_UNKNOWN:
          default:
            g_debug ("Ignoring %s/%s: not a directory",
                     self->variable_dir, dent->d_name);
            continue;
        }

      if (g_str_has_prefix (dent->d_name, "deploy-"))
        {
          if (_srt_fstatat_is_same_file (self->variable_dir_fd,
                                         dent->d_name,
                                         AT_FDCWD,
                                         self->deployment))
            {
              g_debug ("Ignoring %s/%s: is the current version",
                       self->variable_dir, dent->d_name);
              continue;
            }
        }
      else if (!g_str_has_prefix (dent->d_name, "tmp-"))
        {
          g_debug ("Ignoring %s/%s: not tmp-*",
                   self->variable_dir, dent->d_name);
          continue;
        }

      pv_runtime_maybe_garbage_collect_subdir ("temporary runtime",
                                               self->variable_dir,
                                               self->variable_dir_fd,
                                               dent->d_name);
    }

  return TRUE;
}

static gboolean
pv_runtime_init_variable_dir (PvRuntime *self,
                              GError **error)
{
  /* Nothing to do in this case */
  if (self->variable_dir == NULL)
    return TRUE;

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

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

  return TRUE;
}

static gboolean
pv_runtime_create_copy (PvRuntime *self,
                        PvBwrapLock *variable_dir_lock,
                        const char *usr_mtree,
                        PvMtreeApplyFlags mtree_flags,
                        GError **error)
{
  g_autofree gchar *dest_usr = NULL;
  g_autofree gchar *temp_dir = NULL;
  g_autoptr(GDir) dir = NULL;
  g_autoptr(PvBwrapLock) copy_lock = NULL;
  g_autoptr(PvBwrapLock) source_lock = NULL;
  g_autoptr(SrtProfilingTimer) timer = NULL;
  const char *member;
  glnx_autofd int temp_dir_fd = -1;
  gboolean is_just_usr;

  g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE);
  g_return_val_if_fail (self->variable_dir != NULL, FALSE);
  g_return_val_if_fail (self->flags & PV_RUNTIME_FLAGS_COPY_RUNTIME, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
  /* We don't actually *use* this: it just acts as an assertion that
   * we are holding the lock on the parent directory. */
  g_return_val_if_fail (variable_dir_lock != NULL, FALSE);

  timer = _srt_profiling_start ("Temporary runtime copy");

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

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

  dest_usr = g_build_filename (temp_dir, "usr", NULL);

  if (usr_mtree != NULL)
    {
      is_just_usr = TRUE;
    }
  else
    {
      g_autofree gchar *source_usr_subdir = g_build_filename (self->source_files,
                                                              "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. */
      if (usr_mtree != NULL)
        {
          /* If there's a manifest available, it's actually quicker to iterate
           * through the manifest and use that to populate a new copy of the
           * runtime that it would be to do the equivalent of `cp -al` -
           * presumably because the mtree is probably contiguous on disk,
           * and the nested directories are probably not. */
          glnx_autofd int dest_usr_fd = -1;

          if (!glnx_ensure_dir (AT_FDCWD, dest_usr, 0755, error))
            return FALSE;

          if (!glnx_opendirat (AT_FDCWD, dest_usr, FALSE, &dest_usr_fd, error))
            {
              g_prefix_error (error, "Unable to open \"%s\": ", dest_usr);
              return FALSE;
            }

          if (!pv_mtree_apply (usr_mtree, dest_usr, dest_usr_fd,
                               self->source_files, mtree_flags,
                               error))
            return FALSE;
        }
      else
        {
          /* Fall back to assuming that what's on-disk is correct. */
          if (!pv_cheap_tree_copy (self->source_files, dest_usr,
                                   PV_COPY_FLAGS_NONE, error))
            return FALSE;
        }
    }
  else
    {
      /* ${source_files}/usr exists, so assume it's a complete sysroot.
       * Merge ${source_files}/bin and ${source_files}/usr/bin into
       * ${temp_dir}/usr/bin, etc. */
      g_assert (usr_mtree == NULL);

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

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

  /* We need to break the hard link for the lock file, otherwise the
   * temporary copy will share its locked/unlocked state with the
   * original. */
  if (TEMP_FAILURE_RETRY (unlinkat (temp_dir_fd, ".ref", 0)) != 0
      && errno != ENOENT)
    return glnx_throw_errno_prefix (error,
                                    "Cannot remove \"%s/.ref\"",
                                    temp_dir);

  if (TEMP_FAILURE_RETRY (unlinkat (temp_dir_fd, "usr/.ref", 0)) != 0
      && errno != ENOENT)
    return glnx_throw_errno_prefix (error,
                                    "Cannot remove \"%s/usr/.ref\"",
                                    temp_dir);

  /* 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)
    {
      if (TEMP_FAILURE_RETRY (symlinkat ("usr/.ref",
                                         temp_dir_fd,
                                         ".ref")) != 0)
        return glnx_throw_errno_prefix (error,
                                        "Cannot create symlink \"%s/.ref\" -> usr/.ref",
                                        temp_dir);
    }

  dir = g_dir_open (dest_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
gstring_replace_suffix (GString *s,
                        const char *suffix,
                        const char *replacement)
{
  gsize len = strlen (suffix);

  if (s->len >= len
      && strcmp (&s->str[s->len - len], suffix) == 0)
    {
      g_string_truncate (s, s->len - len);
      g_string_append (s, replacement);
      return TRUE;
    }

  return FALSE;
}

/*
 * mutable_lock: (out) (not optional):
 */
static gboolean
pv_runtime_unpack (PvRuntime *self,
                   PvBwrapLock **mutable_lock,
                   GError **error)
{
  g_autoptr(GString) debug_tarball = NULL;
  g_autoptr(SrtProfilingTimer) timer = NULL;
  g_autofree gchar *deploy_basename = NULL;
  g_autofree gchar *unpack_dir = NULL;

  g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE);
  g_return_val_if_fail (mutable_lock != NULL, FALSE);
  g_return_val_if_fail (*mutable_lock == NULL, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
  g_return_val_if_fail (self->source != NULL, FALSE);
  g_return_val_if_fail (self->variable_dir != NULL, FALSE);
  g_return_val_if_fail (self->variable_dir_fd >= 0, FALSE);
  g_return_val_if_fail (self->deployment == NULL, FALSE);

  if (!g_file_test (self->source, G_FILE_TEST_IS_REGULAR))
    return glnx_throw (error, "\"%s\" is not a regular file", self->source);

  if (!g_str_has_suffix (self->source, ".tar.gz"))
    return glnx_throw (error, "\"%s\" is not a .tar.gz file", self->source);

  if (self->id == NULL)
    {
      g_autoptr(GString) build_id_file = g_string_new (self->source);
      g_autofree char *id = NULL;
      gsize len;
      gsize i;

      if (gstring_replace_suffix (build_id_file, "-runtime.tar.gz",
                                  "-buildid.txt")
          || gstring_replace_suffix (build_id_file, "-sysroot.tar.gz",
                                     "-buildid.txt"))
        {
          if (!g_file_get_contents (build_id_file->str, &id, &len, error))
            {
              g_prefix_error (error, "Unable to determine build ID from \"%s\": ",
                              build_id_file->str);
              return FALSE;
            }

          if (len == 0)
            return glnx_throw (error, "Build ID in \"%s\" is empty",
                               build_id_file->str);

          for (i = 0; i < len; i++)
            {
              /* Ignore a trailing newline */
              if (i + 1 == len && id[i] == '\n')
                {
                  id[i] = '\0';
                  break;
                }

              /* Allow dot, dash or underscore, but not at the beginning */
              if (i > 0 && strchr (".-_", id[i]) != NULL)
                continue;

              if (!g_ascii_isalnum (id[i]))
                return glnx_throw (error, "Build ID in \"%s\" is invalid",
                                   build_id_file->str);
            }

          self->id = g_steal_pointer (&id);
        }
    }

  if (self->id == NULL)
    return glnx_throw (error, "Cannot unpack archive without unique ID");

  deploy_basename = g_strdup_printf ("deploy-%s", self->id);
  self->deployment = g_build_filename (self->variable_dir,
                                       deploy_basename, NULL);

  /* Fast path: if we already unpacked it, nothing more to do! */
  if (g_file_test (self->deployment, G_FILE_TEST_IS_DIR))
    return TRUE;

  /* Lock the parent directory. Anything that directly manipulates the
   * unpacked runtimes is expected to do the same, so that
   * it cannot be deleting unpacked runtimes at the same time we're
   * creating them.
   *
   * This is an exclusive lock, to avoid two concurrent processes trying
   * to unpack the same runtime. */
  *mutable_lock = pv_bwrap_lock_new (self->variable_dir_fd, ".ref",
                                     (PV_BWRAP_LOCK_FLAGS_CREATE
                                      | PV_BWRAP_LOCK_FLAGS_WAIT),
                                     error);

  if (*mutable_lock == NULL)
    return FALSE;

  /* Slow path: we need to do this the hard way. */
  timer = _srt_profiling_start ("Unpacking %s", self->source);
  unpack_dir = g_build_filename (self->variable_dir, "tmp-XXXXXX", NULL);

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

  g_info ("Unpacking \"%s\" into \"%s\"...", self->source, unpack_dir);

    {
      g_autoptr(FlatpakBwrap) tar = flatpak_bwrap_new (NULL);

      flatpak_bwrap_add_args (tar,
                              "tar",
                              "--force-local",
                              "-C", unpack_dir,
                              NULL);

      if (self->flags & PV_RUNTIME_FLAGS_VERBOSE)
        flatpak_bwrap_add_arg (tar, "-v");

      flatpak_bwrap_add_args (tar,
                              "-xf", self->source,
                              NULL);
      flatpak_bwrap_finish (tar);

      if (!pv_bwrap_run_sync (tar, NULL, error))
        {
          glnx_shutil_rm_rf_at (-1, unpack_dir, NULL, NULL);
          return FALSE;
        }
    }

  debug_tarball = g_string_new (self->source);

  if (gstring_replace_suffix (debug_tarball, "-runtime.tar.gz",
                              "-debug.tar.gz")
      && g_file_test (debug_tarball->str, G_FILE_TEST_EXISTS))
    {
      g_autoptr(FlatpakBwrap) tar = flatpak_bwrap_new (NULL);
      g_autoptr(GError) local_error = NULL;
      g_autofree char *files_lib_debug = NULL;

      files_lib_debug = g_build_filename (unpack_dir, "files", "lib",
                                          "debug", NULL);

      flatpak_bwrap_add_args (tar,
                              "tar",
                              "--force-local",
                              "-C", files_lib_debug,
                              NULL);

      if (self->flags & PV_RUNTIME_FLAGS_VERBOSE)
        flatpak_bwrap_add_arg (tar, "-v");

      flatpak_bwrap_add_args (tar,
                              "-xf", debug_tarball->str,
                              "files/",
                              NULL);
      flatpak_bwrap_finish (tar);

      if (!pv_bwrap_run_sync (tar, NULL, &local_error))
        g_debug ("Ignoring error unpacking detached debug symbols: %s",
                 local_error->message);
    }

  g_info ("Renaming \"%s\" to \"%s\"...", unpack_dir, deploy_basename);

  if (!glnx_renameat (self->variable_dir_fd, unpack_dir,
                      self->variable_dir_fd, deploy_basename,
                      error))
    {
      glnx_shutil_rm_rf_at (-1, unpack_dir, NULL, NULL);
      return FALSE;
    }

  return TRUE;
}

typedef struct
{
  const MultiarchDetails *details;
  PvRuntimeFlags flags;
  PvGraphicsProvider *provider;
  GCancellable *cancellable;
} EnumerationThreadInputs;

/* Called in main thread */
static EnumerationThreadInputs *
enumeration_thread_inputs_new (const MultiarchDetails *details,
                               PvRuntimeFlags flags,
                               PvGraphicsProvider *provider,
                               GCancellable *cancellable)
{
  EnumerationThreadInputs *self = g_new0 (EnumerationThreadInputs, 1);

  self->details = details;
  self->flags = self->flags;
  self->provider = g_object_ref (provider);
  self->cancellable = g_object_ref (cancellable);
  return self;
}

/* Called in enumeration thread */
static void
enumeration_thread_inputs_free (EnumerationThreadInputs *self)
{
  g_object_unref (self->cancellable);
  g_object_unref (self->provider);
  g_free (self);
}

/* Called in enumeration thread */
static gpointer
enumerate_arch (gpointer data)
{
  EnumerationThreadInputs *inputs = data;
  G_GNUC_UNUSED g_autoptr(SrtProfilingTimer) timer =
    _srt_profiling_start ("Enumerating %s drivers in thread",
                          inputs->details->tuple);
  g_autoptr(SrtSystemInfo) system_info =
    pv_graphics_provider_create_system_info (inputs->provider);

  if (g_cancellable_is_cancelled (inputs->cancellable))
    goto out;

  if (TRUE)
    {
      G_GNUC_UNUSED g_autoptr(SrtProfilingTimer) part_timer =
        _srt_profiling_start ("Enumerating %s VDPAU drivers in thread",
                              inputs->details->tuple);

      /* We ignore the results. system_info will cache them for later
       * calls, so when we're doing the actual work, redoing this call
       * will just retrieve them */
      srt_system_info_list_vdpau_drivers (system_info,
                                          inputs->details->tuple,
                                          SRT_DRIVER_FLAGS_NONE);
    }

  if (g_cancellable_is_cancelled (inputs->cancellable))
    goto out;

  if (TRUE)
    {
      G_GNUC_UNUSED g_autoptr(SrtProfilingTimer) part_timer =
        _srt_profiling_start ("Enumerating %s DRI drivers in thread",
                              inputs->details->tuple);

      srt_system_info_list_dri_drivers (system_info,
                                        inputs->details->tuple,
                                        SRT_DRIVER_FLAGS_NONE);
    }

  if (g_cancellable_is_cancelled (inputs->cancellable))
    goto out;

  if (TRUE)
    {
      G_GNUC_UNUSED g_autoptr(SrtProfilingTimer) part_timer =
        _srt_profiling_start ("Enumerating %s VA-API drivers in thread",
                              inputs->details->tuple);

      srt_system_info_list_va_api_drivers (system_info,
                                           inputs->details->tuple,
                                           SRT_DRIVER_FLAGS_NONE);
    }

  if (g_cancellable_is_cancelled (inputs->cancellable))
    goto out;

  g_free (srt_system_info_dup_libdl_platform (system_info,
                                              inputs->details->tuple, NULL));

out:
  enumeration_thread_inputs_free (inputs);
  return g_steal_pointer (&system_info);
}

/* Called in enumeration thread */
static gpointer
enumerate_indep (gpointer data)
{
  EnumerationThreadInputs *inputs = data;
  G_GNUC_UNUSED g_autoptr(SrtProfilingTimer) timer =
    _srt_profiling_start ("Enumerating cross-architecture ICDs in thread");
  g_autoptr(SrtSystemInfo) system_info = srt_system_info_new (NULL);

  srt_system_info_set_sysroot (system_info,
                               inputs->provider->path_in_current_ns);
  _srt_system_info_set_check_flags (system_info,
                                    (SRT_CHECK_FLAGS_SKIP_SLOW_CHECKS
                                     | SRT_CHECK_FLAGS_SKIP_EXTRAS));

  if (g_cancellable_is_cancelled (inputs->cancellable))
    goto out;

  if (TRUE)
    {
      G_GNUC_UNUSED g_autoptr(SrtProfilingTimer) part_timer =
        _srt_profiling_start ("Enumerating EGL ICDs in thread");

      srt_system_info_list_egl_icds (system_info, multiarch_tuples);
    }

  if (g_cancellable_is_cancelled (inputs->cancellable))
    goto out;

  if (TRUE)
    {
      G_GNUC_UNUSED g_autoptr(SrtProfilingTimer) part_timer =
        _srt_profiling_start ("Enumerating Vulkan ICDs in thread");

      srt_system_info_list_vulkan_icds (system_info, multiarch_tuples);
    }

  if (g_cancellable_is_cancelled (inputs->cancellable))
    goto out;

  if (inputs->flags & PV_RUNTIME_FLAGS_IMPORT_VULKAN_LAYERS)
    {
      G_GNUC_UNUSED g_autoptr(SrtProfilingTimer) part_timer =
        _srt_profiling_start ("Enumerating Vulkan layers in thread");

      srt_system_info_list_explicit_vulkan_layers (system_info);
      srt_system_info_list_implicit_vulkan_layers (system_info);
    }

out:
  enumeration_thread_inputs_free (inputs);
  return g_steal_pointer (&system_info);
}

/*
 * Must be called from same thread as enumeration_thread_start_arch()
 * or enumeration_thread_start_indep().
 *
 * Returns: (transfer none):
 */
static SrtSystemInfo *
enumeration_thread_join (EnumerationThread *self)
{
  if (self->thread != NULL)
    {
      g_assert (self->system_info == NULL);
      g_cancellable_cancel (self->cancellable);
      self->system_info = g_thread_join (g_steal_pointer (&self->thread));
    }

  return self->system_info;
}

static void
enumeration_thread_clear (EnumerationThread *self)
{
  enumeration_thread_join (self);
  g_clear_object (&self->system_info);
  g_clear_object (&self->cancellable);
}

static void
enumeration_threads_clear (EnumerationThread **arr,
                           gsize n)
{
  EnumerationThread *threads = *arr;
  gsize i;

  *arr = NULL;

  if (threads == NULL)
    return;

  for (i = 0; i < n; i++)
    enumeration_thread_clear (threads + i);
}

/* Must be called in main thread */
static void
enumeration_thread_start_arch (EnumerationThread *self,
                               const MultiarchDetails *details,
                               PvRuntimeFlags flags,
                               PvGraphicsProvider *provider)
{
  g_return_if_fail (self->cancellable == NULL);
  g_return_if_fail (self->system_info == NULL);
  g_return_if_fail (self->thread == NULL);

  self->cancellable = g_cancellable_new ();
  self->thread = g_thread_new (details->tuple, enumerate_arch,
                               enumeration_thread_inputs_new (details, flags,
                                                              provider,
                                                              self->cancellable));
}

/* Must be called in main thread */
static void
enumeration_thread_start_indep (EnumerationThread *self,
                                PvRuntimeFlags flags,
                                PvGraphicsProvider *provider)
{
  g_return_if_fail (self->cancellable == NULL);
  g_return_if_fail (self->system_info == NULL);
  g_return_if_fail (self->thread == NULL);

  self->cancellable = g_cancellable_new ();
  self->thread = g_thread_new ("cross-architecture", enumerate_indep,
                               enumeration_thread_inputs_new (NULL, flags,
                                                              provider,
                                                              self->cancellable));
}

static gboolean
pv_runtime_initable_init (GInitable *initable,
                          GCancellable *cancellable G_GNUC_UNUSED,
                          GError **error)
{
  PvRuntime *self = PV_RUNTIME (initable);
  g_autoptr(PvBwrapLock) mutable_lock = NULL;
  g_autofree gchar *contents = NULL;
  g_autofree gchar *os_release = NULL;
  g_autofree gchar *usr_mtree = NULL;
  gsize len;
  PvMtreeApplyFlags mtree_flags = PV_MTREE_APPLY_FLAGS_NONE;

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

  /* Enumerating the graphics provider's drivers only requires things
   * we already know, so start this first, and let it run in parallel
   * with other setup. The results go in the SrtSystemInfo's cache
   * for future use. */
  if (self->provider != NULL
      && !(self->flags & PV_RUNTIME_FLAGS_SINGLE_THREAD))
    {
      gsize i;

      enumeration_thread_start_indep (&self->indep_thread,
                                      self->flags,
                                      self->provider);

      self->arch_threads = g_new0 (EnumerationThread,
                                   G_N_ELEMENTS (multiarch_details));

      for (i = 0; i < G_N_ELEMENTS (multiarch_details); i++)
        enumeration_thread_start_arch (&self->arch_threads[i],
                                       &multiarch_details[i],
                                       self->flags,
                                       self->provider);
    }

  /* If we are in Flatpak container we don't expect to have a working bwrap */
  if (self->bubblewrap != NULL
      && !g_file_test (self->bubblewrap, G_FILE_TEST_IS_EXECUTABLE))
    {
      return glnx_throw (error, "\"%s\" is not executable",
                         self->bubblewrap);
    }

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

  if (self->flags & PV_RUNTIME_FLAGS_UNPACK_ARCHIVE)
    {
      if (self->variable_dir_fd < 0)
        return glnx_throw (error,
                           "Cannot unpack archive without variable directory");

      if (!pv_runtime_unpack (self, &mutable_lock, error))
        return FALSE;

      /* Set by pv_runtime_unpack */
      g_assert (self->deployment != NULL);
    }
  else
    {
      self->deployment = g_strdup (self->source);
    }

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

  /* If the deployment contains usr-mtree.txt, assume that it's a
   * Flatpak-style merged-/usr runtime, and usr-mtree.txt describes
   * what's in the runtime. The content is taken from the files/
   * directory, but files not listed in the mtree are not included.
   *
   * The manifest compresses well (about 3:1 if sha256sums are included)
   * so try to read a compressed version first, falling back to
   * uncompressed. */
  usr_mtree = g_build_filename (self->deployment, "usr-mtree.txt.gz", NULL);

  if (g_file_test (usr_mtree, G_FILE_TEST_IS_REGULAR))
    {
      mtree_flags |= PV_MTREE_APPLY_FLAGS_GZIP;
    }
  else
    {
      g_clear_pointer (&usr_mtree, g_free);
      usr_mtree = g_build_filename (self->deployment, "usr-mtree.txt", NULL);
    }

  if (!g_file_test (usr_mtree, G_FILE_TEST_IS_REGULAR))
    g_clear_pointer (&usr_mtree, g_free);

  /* Or, if it contains ./files/, assume it's a Flatpak-style runtime where
   * ./files is a merged /usr and ./metadata is an optional GKeyFile. */
  self->source_files = g_build_filename (self->deployment, "files", NULL);

  if (usr_mtree != NULL)
    {
      g_debug ("Assuming %s is a merged-/usr runtime because it has "
               "a /usr mtree",
               self->deployment);
    }
  else if (g_file_test (self->source_files, G_FILE_TEST_IS_DIR))
    {
      g_debug ("Assuming %s is a Flatpak-style runtime", self->deployment);
    }
  else
    {
      g_debug ("Assuming %s is a sysroot or merged /usr", self->deployment);
      g_clear_pointer (&self->source_files, g_free);
      self->source_files = g_strdup (self->deployment);
    }

  g_debug ("Taking runtime files from: %s", 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. */
  if (self->runtime_lock == NULL)
    {
      g_autofree gchar *files_ref = NULL;

      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;

  /* GC old runtimes (if they have become unused) before we create a
   * new one. This means we should only ever have one temporary runtime
   * copy per game that is run concurrently. */
  if (self->variable_dir_fd >= 0
      && (self->flags & PV_RUNTIME_FLAGS_GC_RUNTIMES))
    {
      g_autoptr(GError) local_error = NULL;

      /* Take out an exclusive lock for GC so that we will not conflict
       * with other concurrent processes that are halfway through
       * deploying or unpacking a runtime. */
      if (mutable_lock == NULL)
        mutable_lock = pv_bwrap_lock_new (self->variable_dir_fd, ".ref",
                                          (PV_BWRAP_LOCK_FLAGS_CREATE
                                           | PV_BWRAP_LOCK_FLAGS_WRITE),
                                          &local_error);

      if (mutable_lock == NULL)
        g_debug ("Unable to take an exclusive lock, skipping GC: %s",
                 local_error->message);
      else if (!pv_runtime_garbage_collect (self, mutable_lock, error))
        return FALSE;
    }

  /* Always copy the runtime into var/ before applying a manifest. */
  if (usr_mtree != NULL)
    self->flags |= PV_RUNTIME_FLAGS_COPY_RUNTIME;

  if (self->flags & PV_RUNTIME_FLAGS_COPY_RUNTIME)
    {
      if (self->variable_dir_fd < 0)
        return glnx_throw (error,
                           "Cannot copy runtime without variable directory");

      /* This time take out a non-exclusive lock: any number of processes
       * can safely be creating their own temporary copy at the same
       * time. If another process is doing GC, wait for it to finish,
       * then take our lock. */
      if (mutable_lock == NULL)
        mutable_lock = pv_bwrap_lock_new (self->variable_dir_fd, ".ref",
                                          (PV_BWRAP_LOCK_FLAGS_CREATE
                                           | PV_BWRAP_LOCK_FLAGS_WAIT),
                                          error);

      if (mutable_lock == NULL)
        return FALSE;

      if (!pv_runtime_create_copy (self, mutable_lock, usr_mtree,
                                   mtree_flags, error))
        return FALSE;
    }

  if (self->mutable_sysroot != NULL)
    {
      self->overrides_in_container = "/usr/lib/pressure-vessel/overrides";
      self->overrides = g_build_filename (self->mutable_sysroot,
                                          self->overrides_in_container, NULL);
      self->runtime_files = self->mutable_sysroot;
    }
  else
    {
      /* We currently only need a temporary directory if we don't have
       * a mutable sysroot to work with. */
      g_autofree gchar *tmpdir = g_dir_make_tmp ("pressure-vessel-wrap.XXXXXX",
                                                 error);

      if (tmpdir == NULL)
        return FALSE;

      self->tmpdir = realpath (tmpdir, NULL);

      if (self->tmpdir == NULL)
        return glnx_throw_errno_prefix (error, "realpath(\"%s\")", tmpdir);

      self->overrides = g_build_filename (self->tmpdir, "overrides", NULL);
      self->overrides_in_container = "/overrides";
      self->runtime_files = self->source_files;
    }

  self->runtime_files_on_host = pv_current_namespace_path_to_host_path (self->runtime_files);

  g_mkdir (self->overrides, 0700);

  self->runtime_app = g_build_filename (self->runtime_files, "app", NULL);
  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);
    }

  self->libcapsule_knowledge = g_build_filename (self->runtime_usr,
                                                 "lib", "steamrt",
                                                 "libcapsule-knowledge.keyfile",
                                                 NULL);

  if (!g_file_test (self->libcapsule_knowledge, G_FILE_TEST_EXISTS))
    g_clear_pointer (&self->libcapsule_knowledge, g_free);

  self->runtime_abi_json = g_build_filename (self->runtime_usr, "lib", "steamrt",
                                             "steam-runtime-abi.json", NULL);

  if (!g_file_test (self->runtime_abi_json, G_FILE_TEST_EXISTS))
    g_clear_pointer (&self->runtime_abi_json, g_free);

  os_release = g_build_filename (self->runtime_usr, "lib", "os-release", NULL);

  /* TODO: Teach SrtSystemInfo to be able to load lib/os-release from
   * a merged-/usr, so we don't need to open-code this here */
  if (g_file_get_contents (os_release, &contents, &len, NULL))
    {
      gsize i;
      g_autofree gchar *id = NULL;
      g_autofree gchar *version_id = NULL;
      char *beginning_of_line = contents;

      for (i = 0; i < len; i++)
        {
          if (contents[i] == '\n')
            {
              contents[i] = '\0';

              if (id == NULL &&
                  g_str_has_prefix (beginning_of_line, "ID="))
                id = g_shell_unquote (beginning_of_line + strlen ("ID="), NULL);
              else if (version_id == NULL &&
                       g_str_has_prefix (beginning_of_line, "VERSION_ID="))
                version_id = g_shell_unquote (beginning_of_line + strlen ("VERSION_ID="), NULL);

              beginning_of_line = contents + i + 1;
            }
        }

      if (g_strcmp0 (id, "steamrt") == 0)
        {
          self->is_steamrt = TRUE;

          if (g_strcmp0 (version_id, "1") == 0)
            self->is_scout = TRUE;
        }
    }

  /* If we are in a Flatpak environment we expect to have the host system
   * mounted in `/run/host`. Otherwise we assume that the host system, in the
   * current namespace, is the root. */
  if (g_file_test ("/.flatpak-info", G_FILE_TEST_IS_REGULAR))
    self->host_in_current_namespace = "/run/host";
  else
    self->host_in_current_namespace = "/";

  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, 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_dispose (GObject *object)
{
  PvRuntime *self = PV_RUNTIME (object);

  g_clear_object (&self->provider);
  enumeration_thread_clear (&self->indep_thread);
  enumeration_threads_clear (&self->arch_threads,
                             G_N_ELEMENTS (multiarch_details));

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

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

  pv_runtime_cleanup (self);
  g_free (self->bubblewrap);
  g_strfreev (self->original_environ);
  g_free (self->libcapsule_knowledge);
  g_free (self->runtime_abi_json);
  glnx_close_fd (&self->variable_dir_fd);
  g_free (self->variable_dir);
  glnx_close_fd (&self->mutable_sysroot_fd);
  g_free (self->mutable_sysroot);
  g_free (self->runtime_files_on_host);
  g_free (self->runtime_app);
  g_free (self->runtime_usr);
  g_free (self->source);
  g_free (self->source_files);
  g_free (self->deployment);
  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->dispose = pv_runtime_dispose;
  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_GRAPHICS_PROVIDER] =
    g_param_spec_object ("graphics-provider",
                         "Graphics provider",
                         "Sysroot used for graphics stack, or NULL",
                         PV_TYPE_GRAPHICS_PROVIDER,
                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
                          G_PARAM_STATIC_STRINGS));

  properties[PROP_ORIGINAL_ENVIRON] =
    g_param_spec_boxed ("original-environ", "Original environ",
                        "The original environ to use",
                        G_TYPE_STRV,
                        (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_ID] =
    g_param_spec_string ("id", "ID",
                         "Unique identifier of runtime to be unpacked",
                         NULL,
                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
                          G_PARAM_STATIC_STRINGS));

  properties[PROP_VARIABLE_DIR] =
    g_param_spec_string ("variable-dir", "Variable directory",
                         ("Path to directory for temporary files, or NULL"),
                         NULL,
                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
                          G_PARAM_STATIC_STRINGS));

  properties[PROP_SOURCE] =
    g_param_spec_string ("source", "Source",
                         ("Path to read-only runtime files (merged-/usr "
                          "or sysroot) or archive, in current namespace"),
                         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 in current namespace",
                         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,
                const char *id,
                const char *variable_dir,
                const char *bubblewrap,
                const char *tools_dir,
                PvGraphicsProvider *provider,
                const GStrv original_environ,
                PvRuntimeFlags flags,
                GError **error)
{
  g_return_val_if_fail (source != 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,
                         "graphics-provider", provider,
                         "original-environ", original_environ,
                         "variable-dir", variable_dir,
                         "source", source,
                         "id", id,
                         "tools-directory", tools_dir,
                         "flags", flags,
                         NULL);
}

/* If we are using a runtime, ensure the locales to be generated,
 * 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,
 * and we want -adverb for its locale functionality anyway. */
gboolean
pv_runtime_get_adverb (PvRuntime *self,
                       FlatpakBwrap *bwrap)
{
  g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE);
  /* This will be true if pv_runtime_bind() was successfully called. */
  g_return_val_if_fail (self->adverb_in_container != NULL, FALSE);
  g_return_val_if_fail (bwrap != NULL, FALSE);
  g_return_val_if_fail (flatpak_bwrap_is_empty (bwrap), FALSE);
  g_return_val_if_fail (!pv_bwrap_was_finished (bwrap), FALSE);

  flatpak_bwrap_add_arg (bwrap, self->adverb_in_container);

  if (self->flags & PV_RUNTIME_FLAGS_GENERATE_LOCALES)
    flatpak_bwrap_add_args (bwrap, "--generate-locales", 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 adverb", 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 adverb process
       * to take out its own compatible lock instead. There will be
       * a short window during which we have lost our lock but the
       * adverb 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);
    }

  return TRUE;
}

/*
 * 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_info ("%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
    {
      g_autofree gchar *etc = NULL;
      g_autofree gchar *etc_dest = NULL;

      if (self->bubblewrap == NULL)
        return glnx_throw (error,
                           "Cannot run bubblewrap to set up runtime");

      /* Otherwise, will we need to use bwrap to build a directory hierarchy
       * that is the same shape as the final system. */
      g_info ("%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);
      g_assert (self->tmpdir != 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_on_host,
                              self->runtime_files,
                              self->container_access,
                              error))
        return FALSE;

      /* For simplicity we bind all of /etc here */
      etc = g_build_filename (self->runtime_files_on_host,
                              "etc", NULL);
      etc_dest = g_build_filename (self->container_access,
                                   "etc", NULL);
      flatpak_bwrap_add_args (self->container_access_adverb,
                              "--ro-bind", etc, etc_dest,
                              NULL);
    }

  return TRUE;
}

static FlatpakBwrap *
pv_runtime_get_capsule_capture_libs (PvRuntime *self,
                                     RuntimeArchitecture *arch)
{
  const gchar *ld_library_path;
  g_autofree gchar *remap_app = NULL;
  g_autofree gchar *remap_usr = NULL;
  g_autofree gchar *remap_lib = NULL;
  FlatpakBwrap *ret;

  g_return_val_if_fail (self->provider != NULL, NULL);

  ret = pv_bwrap_copy (self->container_access_adverb);

  /* If we have a custom "LD_LIBRARY_PATH", we want to preserve
   * it when calling capsule-capture-libs */
  ld_library_path = g_environ_getenv (self->original_environ, "LD_LIBRARY_PATH");
  if (ld_library_path != NULL)
    flatpak_bwrap_set_env (ret, "LD_LIBRARY_PATH", ld_library_path, TRUE);

  /* Every symlink that starts with exactly /app/ (for Flatpak) */
  remap_app = g_strjoin (NULL, "/app/", "=",
                         self->provider->path_in_container_ns,
                         "/app/", NULL);

  /* Every symlink that starts with exactly /usr/ */
  remap_usr = g_strjoin (NULL, "/usr/", "=",
                         self->provider->path_in_container_ns,
                         "/usr/", NULL);

  /* Every symlink that starts with /lib, e.g. /lib64 */
  remap_lib = g_strjoin (NULL, "/lib", "=",
                         self->provider->path_in_container_ns,
                         "/lib", NULL);

  flatpak_bwrap_add_args (ret,
                          arch->capsule_capture_libs,
                          "--container", self->container_access,
                          "--remap-link-prefix", remap_app,
                          "--remap-link-prefix", remap_usr,
                          "--remap-link-prefix", remap_lib,
                          "--provider",
                            self->provider->path_in_current_ns,
                          NULL);

  if (self->libcapsule_knowledge)
    flatpak_bwrap_add_args (ret,
                            "--library-knowledge", self->libcapsule_knowledge,
                            NULL);

  return ret;
}

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

  g_return_val_if_fail (self->provider != NULL, FALSE);

  s2tc_in_current_namespace = g_build_filename (self->provider->path_in_current_ns,
                                                s2tc, NULL);

  if (g_file_test (s2tc_in_current_namespace, 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_runtime_get_capsule_capture_libs (self, arch);
      flatpak_bwrap_add_args (temp_bwrap,
                              "--dest", arch->libdir_in_current_namespace,
                              expr,
                              NULL);
      flatpak_bwrap_finish (temp_bwrap);

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

  return TRUE;
}

typedef enum
{
  ICD_KIND_NONEXISTENT,
  ICD_KIND_ABSOLUTE,
  ICD_KIND_SONAME,
  ICD_KIND_META_LAYER,
} IcdKind;

typedef struct
{
  /* (type SrtEglIcd) or (type SrtVulkanIcd) or (type SrtVdpauDriver)
   * or (type SrtVaApiDriver) or (type SrtVulkanLayer) or
   * (type SrtDriDriver) */
  gpointer icd;
  gchar *resolved_library;
  /* Last entry is always NONEXISTENT; 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_DRI_DRIVER (icd) ||
                        SRT_IS_EGL_ICD (icd) ||
                        SRT_IS_VULKAN_ICD (icd) ||
                        SRT_IS_VULKAN_LAYER (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)

/*
 * @destination: (not nullable): where to capture the libraries
 * @patterns: (not nullable): array of patterns for capsule-capture-libs
 */
static gboolean
pv_runtime_capture_libraries (PvRuntime *self,
                              RuntimeArchitecture *arch,
                              const gchar *destination,
                              GPtrArray *patterns,
                              GError **error)
{
  g_autoptr(FlatpakBwrap) temp_bwrap = NULL;
  G_GNUC_UNUSED g_autoptr(SrtProfilingTimer) libc_timer =
    _srt_profiling_start ("Main capsule-capture-libs call");
  gsize i;

  g_return_val_if_fail (self->provider != NULL, FALSE);
  g_return_val_if_fail (runtime_architecture_check_valid (arch), FALSE);
  g_return_val_if_fail (destination != NULL, FALSE);
  g_return_val_if_fail (patterns != NULL, FALSE);

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

  temp_bwrap = pv_runtime_get_capsule_capture_libs (self, arch);
  flatpak_bwrap_add_args (temp_bwrap, "--dest", destination, NULL);

  for (i = 0; i < patterns->len; i++)
    flatpak_bwrap_add_arg (temp_bwrap, (gchar *)g_ptr_array_index (patterns, i));

  flatpak_bwrap_finish (temp_bwrap);

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

  return TRUE;
}

/*
 * @sequence_number: numbered directory to use to disambiguate between
 *  colliding files with the same basename
 * @requested_subdir: (not nullable):
 * @use_numbered_subdirs: (inout) (not optional): if %TRUE, use a
 *  numbered subdirectory per ICD, for the rare case where not all
 *  drivers have a unique basename or where order matters
 * @use_subdir_for_kind_soname: if %TRUE, the @requested_subdir will be always
 *  used. If %FALSE, the @requested_subdir will not be used when the provided
 *  library is of the kind "ICD_KIND_SONAME".
 * @dependency_patterns: (inout) (not nullable): array of patterns for
 *  capsule-capture-libs
 * @search_path: (nullable): Add the parent directory of the resulting
 *  ICD to this search path if necessary
 *
 * Bind the provided @details ICD without its dependencies, and update
 * @dependency_patterns with @details dependency pattern.
 */
static gboolean
bind_icd (PvRuntime *self,
          RuntimeArchitecture *arch,
          gsize sequence_number,
          const char *requested_subdir,
          IcdDetails *details,
          gboolean *use_numbered_subdirs,
          gboolean use_subdir_for_kind_soname,
          GPtrArray *dependency_patterns,
          GString *search_path,
          GError **error)
{
  static const char options[] = "if-exists:if-same-abi";
  g_autofree gchar *in_current_namespace = NULL;
  g_autofree gchar *pattern = NULL;
  g_autofree gchar *dependency_pattern = NULL;
  g_autofree gchar *seq_str = NULL;
  g_autofree gchar *final_path = NULL;
  const char *base;
  const char *mode;
  g_autoptr(FlatpakBwrap) temp_bwrap = NULL;
  gsize multiarch_index;
  g_autoptr(GDir) dir = NULL;
  gsize dir_elements_before = 0;
  gsize dir_elements_after = 0;
  const gchar *subdir = requested_subdir;

  g_return_val_if_fail (self->provider != NULL, FALSE);
  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 (use_numbered_subdirs != NULL, FALSE);
  g_return_val_if_fail (dependency_patterns != NULL, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  g_info ("Capturing loadable module: %s", details->resolved_library);

  if (g_path_is_absolute (details->resolved_library))
    {
      details->kinds[multiarch_index] = ICD_KIND_ABSOLUTE;
      mode = "path";
    }
  else
    {
      details->kinds[multiarch_index] = ICD_KIND_SONAME;
      mode = "soname";
      if (!use_subdir_for_kind_soname)
        subdir = "";
    }

  in_current_namespace = g_build_filename (arch->libdir_in_current_namespace,
                                           subdir, NULL);

  if (g_mkdir_with_parents (in_current_namespace, 0700) != 0)
    return glnx_throw_errno_prefix (error, "Unable to create %s",
                                    in_current_namespace);

  base = glnx_basename (details->resolved_library);

  /* Check whether we can get away with avoiding the sequence number.
   * Depending on the type of ICD, we might want to use the sequence
   * number to force a specific load order. */
  if (!*use_numbered_subdirs)
    {
      g_autofree gchar *path = NULL;

      path = g_build_filename (in_current_namespace, base, NULL);

      /* No, we can't: the ICD would collide with one that we already
       * set up */
      if (g_file_test (path, G_FILE_TEST_IS_SYMLINK))
        *use_numbered_subdirs = TRUE;
    }

  /* If we can't avoid the numbered subdirectory, or want to use one
   * to force a specific load order, create it. */
  if (*use_numbered_subdirs && subdir[0] != '\0')
    {
      seq_str = g_strdup_printf ("%" G_GSIZE_FORMAT, sequence_number);
      g_clear_pointer (&in_current_namespace, g_free);
      in_current_namespace = g_build_filename (arch->libdir_in_current_namespace,
                                               subdir, seq_str, NULL);

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

  final_path = g_build_filename (in_current_namespace, base, NULL);
  if (g_file_test (final_path, G_FILE_TEST_IS_SYMLINK))
    {
      g_info ("\"%s\" is already present, skipping", final_path);
      return TRUE;
    }

  dir = g_dir_open (in_current_namespace, 0, error);
  if (dir == NULL)
    return FALSE;

  /* Number of elements before trying to capture the library */
  while (g_dir_read_name (dir))
    dir_elements_before++;

  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_runtime_get_capsule_capture_libs (self, arch);
  flatpak_bwrap_add_args (temp_bwrap,
                          "--dest", in_current_namespace,
                          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);

  g_dir_rewind (dir);
  while (g_dir_read_name (dir))
    dir_elements_after++;

  if (dir_elements_before == dir_elements_after)
    {
      /* If we have the same number of elements it means that we didn't
       * create a symlink to the ICD itself (it must have been nonexistent
       * or for a different ABI). When this happens we set the kinds to
       * "NONEXISTENT" and return early without trying to capture the
       * dependencies. */
      details->kinds[multiarch_index] = ICD_KIND_NONEXISTENT;
      /* If the directory is empty we can also remove it */
      g_rmdir (in_current_namespace);
      return TRUE;
    }

  /* Only add the numbered subdirectories to the search path. Their
   * parent is expected to be there already. */
  if (search_path != NULL && seq_str != NULL)
    {
      g_autofree gchar *in_container = NULL;

      in_container = g_build_filename (arch->libdir_in_container,
                                       subdir, seq_str, NULL);
      pv_search_path_append (search_path, in_container);
    }

  g_ptr_array_add (dependency_patterns, g_steal_pointer (&dependency_pattern));

  if (details->kinds[multiarch_index] == ICD_KIND_ABSOLUTE)
    {
      g_assert (in_current_namespace != 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_base (PvRuntime *self,
                   FlatpakBwrap *bwrap,
                   PvEnviron *container_env,
                   GError **error)
{
  static const char * const bind_mutable[] =
  {
    "etc",
    "var/cache",
    "var/lib"
  };
  static const char * const dont_bind[] =
  {
    "/etc/asound.conf",
    "/etc/localtime",
    "/etc/machine-id",
    "/var/cache/ldconfig",
    "/var/lib/dbus",
    "/var/lib/dhcp",
    "/var/lib/sudo",
    "/var/lib/urandom",
    NULL
  };
  static const char * const from_host[] =
  {
    /* TODO: Synthesize a passwd with only the user and nobody,
     * like Flatpak does? */
    "/etc/group",
    "/etc/passwd",
    "/etc/host.conf",
    "/etc/hosts",
    "/etc/resolv.conf",
    NULL
  };
  static const char * const from_provider[] =
  {
    "/etc/amd",
    "/etc/drirc",
    "/run/bumblebee.socket",
    NULL
  };
  g_autofree gchar *xrd = g_strdup_printf ("/run/user/%ld", (long) geteuid ());
  gsize i, j;
  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 (container_env != NULL, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

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

  /* In the case where we have a mutable sysroot, we mount the overrides
   * as part of /usr. Make /overrides a symbolic link, to be nice to
   * older steam-runtime-tools versions. */

  if (self->mutable_sysroot != NULL)
    {
      g_assert (self->overrides_in_container[0] == '/');
      g_assert (g_strcmp0 (self->overrides_in_container, "/overrides") != 0);
      flatpak_bwrap_add_args (bwrap,
                              "--symlink",
                              &self->overrides_in_container[1],
                              "/overrides",
                              NULL);

      /* Also make a matching symbolic link on disk, to make it easier
       * to inspect the sysroot. */
      if (TEMP_FAILURE_RETRY (symlinkat (&self->overrides_in_container[1],
                                         self->mutable_sysroot_fd,
                                         "overrides")) != 0)
        return glnx_throw_errno_prefix (error,
                                        "Unable to create symlink \"%s/overrides\" -> \"%s\"",
                                        self->mutable_sysroot,
                                        &self->overrides_in_container[1]);
    }

  flatpak_bwrap_add_args (bwrap,
                          "--dir", "/tmp",
                          "--dir", "/var",
                          "--dir", "/var/tmp",
                          "--symlink", "../run", "/var/run",
                          NULL);

  pv_environ_lock_env (container_env, "XDG_RUNTIME_DIR", xrd);

  if (self->provider != NULL
      && (g_strcmp0 (self->provider->path_in_host_ns, "/") != 0
          || g_strcmp0 (self->provider->path_in_container_ns, "/run/host") != 0))
    {
      g_autofree gchar *provider_etc = NULL;

      if (!pv_bwrap_bind_usr (bwrap,
                              self->provider->path_in_host_ns,
                              self->provider->path_in_current_ns,
                              self->provider->path_in_container_ns,
                              error))
        return FALSE;

      provider_etc = g_build_filename (self->provider->path_in_current_ns,
                                       "etc", NULL);

      if (g_file_test (provider_etc, G_FILE_TEST_IS_DIR))
        {
          g_autofree gchar *in_host = NULL;
          g_autofree gchar *in_container = NULL;

          in_host = g_build_filename (self->provider->path_in_host_ns,
                                      "etc", NULL);
          in_container = g_build_filename (self->provider->path_in_container_ns,
                                           "etc", NULL);

          flatpak_bwrap_add_args (bwrap,
                                  "--ro-bind", in_host, in_container,
                                  NULL);
        }
    }

  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;

          if (g_strv_contains (from_host, dest))
            continue;

          if (self->provider != NULL && g_strv_contains (from_provider, 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
            {
              /* We will run bwrap in the host system, so translate the path
               * if necessary */
              g_autofree gchar *on_host = pv_current_namespace_path_to_host_path (full);
              flatpak_bwrap_add_args (bwrap, "--ro-bind", on_host, dest, NULL);
            }
        }
    }

  /* glibc from some distributions will want to load the ld.so cache from
   * a distribution-specific path, e.g. Clear Linux uses
   * /var/cache/ldconfig/ld.so.cache. For simplicity, we make all these
   * paths symlinks to /etc/ld.so.cache, so that we only have to populate
   * the cache in one place. */
  for (i = 0; i < G_N_ELEMENTS (other_ld_so_cache); i++)
    {
      const char *path = other_ld_so_cache[i];

      flatpak_bwrap_add_args (bwrap,
                              "--symlink", "/etc/ld.so.cache", path,
                              NULL);
    }

  /* glibc from some distributions will want to load the ld.so cache from
   * a distribution- and architecture-specific path, e.g. Exherbo
   * does this. Again, for simplicity we direct all these to the same path:
   * it's OK to mix multiple architectures' libraries into one cache,
   * as done in upstream glibc (and Debian, Arch, etc.). */
  for (i = 0; i < G_N_ELEMENTS (multiarch_details); i++)
    {
      const MultiarchDetails *details = &multiarch_details[i];

      for (j = 0; j < G_N_ELEMENTS (details->other_ld_so_cache); j++)
        {
          const char *base = details->other_ld_so_cache[j];
          g_autofree gchar *path = NULL;

          if (base == NULL)
            break;

          path = g_build_filename ("/etc", base, NULL);
          flatpak_bwrap_add_args (bwrap,
                                  "--symlink", "/etc/ld.so.cache", path,
                                  NULL);
        }
    }

  /* If we are in a Flatpak environment, we need to test if these files are
   * available in the host, and not in the current environment, because we will
   * run bwrap in the host system */
  if (_srt_file_test_in_sysroot (self->host_in_current_namespace, -1,
                                 "/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);
    }
  /* We leave this for completeness but in practice we do not expect to have
   * access to the "/var" host directory because Flatpak usually just binds
   * the host's "etc" and "usr". */
  else if (_srt_file_test_in_sysroot (self->host_in_current_namespace, -1,
                                      "/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);
    }

  for (i = 0; from_host[i] != NULL; i++)
    {
      const char *item = from_host[i];

      if (_srt_file_test_in_sysroot (self->host_in_current_namespace, -1,
                                     item, G_FILE_TEST_EXISTS))
        flatpak_bwrap_add_args (bwrap,
                                "--ro-bind", item, item,
                                NULL);
    }

  if (self->provider != NULL)
    {
      for (i = 0; from_provider[i] != NULL; i++)
        {
          const char *item = from_provider[i];
          g_autoptr(GError) local_error = NULL;
          g_autofree char *path_in_provider = NULL;
          glnx_autofd int fd = -1;

          fd = _srt_resolve_in_sysroot (self->provider->fd, item,
                                        SRT_RESOLVE_FLAGS_NONE,
                                        &path_in_provider,
                                        &local_error);

          if (fd >= 0)
            {
              g_autofree char *host_path = NULL;

              host_path = g_build_filename (self->provider->path_in_host_ns,
                                            path_in_provider, NULL);
              flatpak_bwrap_add_args (bwrap,
                                      "--ro-bind", host_path, item,
                                      NULL);
            }
          else
            {
              g_debug ("Cannot resolve \"%s\" in \"%s\": %s",
                       item, self->provider->path_in_current_ns,
                       local_error->message);
              g_clear_error (&local_error);
            }
        }
    }

  return TRUE;
}

static void
bind_runtime_finish (PvRuntime *self,
                     FlatpakExports *exports,
                     FlatpakBwrap *bwrap)
{
  g_return_if_fail (PV_IS_RUNTIME (self));
  g_return_if_fail (exports != NULL);
  g_return_if_fail (!pv_bwrap_was_finished (bwrap));

  pv_export_symlink_targets (exports, self->overrides);

  if (self->mutable_sysroot == NULL)
    {
      /* self->overrides is in a temporary directory that will be
       * cleaned up before we enter the container, so we need to convert
       * it into a series of --dir and --symlink instructions.
       *
       * We have to do this late, because it adds data fds. */
      pv_bwrap_copy_tree (bwrap, self->overrides, self->overrides_in_container);
    }

  /* /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 (_srt_file_test_in_sysroot (self->host_in_current_namespace, -1,
                                 "/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);
      g_autofree char *localtime_in_current_namespace =
        g_build_filename (self->host_in_current_namespace, "/etc/localtime", NULL);

      target = glnx_readlinkat_malloc (-1, localtime_in_current_namespace, 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);
    }
}

typedef enum
{
  TAKE_FROM_PROVIDER_FLAGS_IF_DIR = (1 << 0),
  TAKE_FROM_PROVIDER_FLAGS_IF_EXISTS = (1 << 1),
  TAKE_FROM_PROVIDER_FLAGS_IF_CONTAINER_COMPATIBLE = (1 << 2),
  TAKE_FROM_PROVIDER_FLAGS_COPY_FALLBACK = (1 << 3),
  TAKE_FROM_PROVIDER_FLAGS_NONE = 0
} TakeFromProviderFlags;

static gboolean
pv_runtime_take_from_provider (PvRuntime *self,
                               FlatpakBwrap *bwrap,
                               const char *source_in_provider,
                               const char *dest_in_container,
                               TakeFromProviderFlags flags,
                               GError **error)
{
  g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE);
  g_return_val_if_fail (self->provider != NULL, FALSE);
  g_return_val_if_fail (bwrap == NULL || !pv_bwrap_was_finished (bwrap), FALSE);
  g_return_val_if_fail (bwrap != NULL || self->mutable_sysroot != NULL, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  if (flags & TAKE_FROM_PROVIDER_FLAGS_IF_DIR)
    {
      if (!_srt_file_test_in_sysroot (self->provider->path_in_current_ns,
                                      self->provider->fd,
                                      source_in_provider, G_FILE_TEST_IS_DIR))
        return TRUE;
    }

  if (flags & TAKE_FROM_PROVIDER_FLAGS_IF_EXISTS)
    {
      if (!_srt_file_test_in_sysroot (self->provider->path_in_current_ns,
                                      self->provider->fd,
                                      source_in_provider, G_FILE_TEST_EXISTS))
        return TRUE;
    }

  if (self->mutable_sysroot != NULL)
    {
      /* Replace ${mutable_sysroot}/usr/lib/locale with a symlink to
       * /run/host/usr/lib/locale, or similar */
      g_autofree gchar *parent_in_container = NULL;
      g_autofree gchar *target = NULL;
      const char *base;
      glnx_autofd int parent_dirfd = -1;

      parent_in_container = g_path_get_dirname (dest_in_container);
      parent_dirfd = _srt_resolve_in_sysroot (self->mutable_sysroot_fd,
                                              parent_in_container,
                                              SRT_RESOLVE_FLAGS_MKDIR_P,
                                              NULL, error);

      if (parent_dirfd < 0)
        return FALSE;

      base = glnx_basename (dest_in_container);

      if (!glnx_shutil_rm_rf_at (parent_dirfd, base, NULL, error))
        return FALSE;

      /* If it isn't in /usr, /lib, etc., then the symlink will be
       * dangling and this probably isn't going to work. */
      if (!path_visible_in_provider_namespace (self->flags, source_in_provider))
        {
          if (flags & TAKE_FROM_PROVIDER_FLAGS_COPY_FALLBACK)
            {
              glnx_autofd int file_fd = -1;
              glnx_autofd int dest_fd = -1;
              glnx_autofd int sysroot_fd = -1;

              if (!glnx_opendirat (-1, self->provider->path_in_current_ns,
                                   FALSE, &sysroot_fd, error))
                return FALSE;

              file_fd = _srt_resolve_in_sysroot (sysroot_fd, source_in_provider,
                                                 SRT_RESOLVE_FLAGS_READABLE,
                                                 NULL, error);
              if (file_fd < 0)
                {
                  g_prefix_error (error,
                                  "Unable to make \"%s\" available in container: ",
                                  source_in_provider);
                  return FALSE;
                }

              /* We already deleted ${parent_dirfd}/${base}, and we don't
               * care about atomicity or durability here, so we can just
               * write in-place. The permissions are uninteresting because
               * we're not expecting other users to read this temporary
               * sysroot anyway, so use 0600 just in case the source file
               * has restrictive permissions. */
              dest_fd = TEMP_FAILURE_RETRY (openat (parent_dirfd, base,
                                                    O_WRONLY|O_CLOEXEC|O_NOCTTY|O_CREAT|O_EXCL,
                                                    0600));

              if (dest_fd < 0)
                return glnx_throw_errno_prefix (error,
                                                "Unable to open \"%s\" for writing",
                                                dest_in_container);

              if (glnx_regfile_copy_bytes (file_fd, dest_fd, (off_t) -1) < 0)
                return glnx_throw_errno_prefix (error,
                                                "Unable to copy contents of \"%s/%s\" to \"%s\"",
                                                self->provider->path_in_current_ns,
                                                source_in_provider,
                                                dest_in_container);

              return TRUE;
            }
          else
            {
              g_warning ("\"%s\" is unlikely to appear in \"%s\"",
                         source_in_provider, self->provider->path_in_container_ns);
              /* ... but try it anyway, it can't hurt */
            }
        }

      target = g_build_filename (self->provider->path_in_container_ns, source_in_provider, NULL);

      if (TEMP_FAILURE_RETRY (symlinkat (target, parent_dirfd, base)) != 0)
        return glnx_throw_errno_prefix (error,
                                        "Unable to create symlink \"%s/%s\" -> \"%s\"",
                                        self->mutable_sysroot,
                                        dest_in_container, target);
    }
  else
    {
      /* We can't edit the runtime in-place, so tell bubblewrap to mount
       * a new version over the top */
      g_assert (bwrap != NULL);

      if (flags & TAKE_FROM_PROVIDER_FLAGS_IF_CONTAINER_COMPATIBLE)
        {
          g_autofree gchar *dest = NULL;

          if (g_str_has_prefix (dest_in_container, "/usr/"))
            dest = g_build_filename (self->runtime_usr,
                                     dest_in_container + strlen ("/usr/"),
                                     NULL);
          else
            dest = g_build_filename (self->runtime_files,
                                     dest_in_container,
                                     NULL);

          if (g_file_test (source_in_provider, G_FILE_TEST_IS_DIR))
            {
              if (!g_file_test (dest, G_FILE_TEST_IS_DIR))
                {
                  g_warning ("Not mounting \"%s\" over non-directory file or "
                             "nonexistent path \"%s\"",
                             source_in_provider, dest);
                  return TRUE;
                }
            }
          else
            {
              if (g_file_test (dest, G_FILE_TEST_IS_DIR) ||
                  !g_file_test (dest, G_FILE_TEST_EXISTS))
                {
                  g_warning ("Not mounting \"%s\" over directory or "
                             "nonexistent path \"%s\"",
                             source_in_provider, dest);
                  return TRUE;
                }
            }
        }

      flatpak_bwrap_add_args (bwrap,
                              "--ro-bind", source_in_provider, dest_in_container,
                              NULL);
    }

  return TRUE;
}

static gboolean
pv_runtime_remove_overridden_libraries (PvRuntime *self,
                                        RuntimeArchitecture *arch,
                                        GError **error)
{
  g_autoptr(GPtrArray) dirs = NULL;
  g_autoptr(SrtProfilingTimer) timer = NULL;
  GHashTable **delete = NULL;
  GLnxDirFdIterator *iters = NULL;
  gboolean ret = FALSE;
  gsize i;

  g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE);
  g_return_val_if_fail (arch != NULL, FALSE);
  g_return_val_if_fail (arch->ld_so != NULL, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  /* Not applicable/possible if we don't have a mutable sysroot */
  g_return_val_if_fail (self->mutable_sysroot != NULL, FALSE);

  timer = _srt_profiling_start ("Removing overridden %s libraries",
                                arch->details->tuple);

  dirs = multiarch_details_get_libdirs (arch->details,
                                        MULTIARCH_LIBDIRS_FLAGS_REMOVE_OVERRIDDEN);
  delete = g_new0 (GHashTable *, dirs->len);
  iters = g_new0 (GLnxDirFdIterator, dirs->len);

  /* We have to figure out what we want to delete before we delete anything,
   * because we can't tell whether a symlink points to a library of a
   * particular SONAME if we already deleted the library. */
  for (i = 0; i < dirs->len; i++)
    {
      const char *libdir = g_ptr_array_index (dirs, i);
      glnx_autofd int libdir_fd = -1;
      struct dirent *dent;
      gsize j;

      /* Mostly ignore error: if the library directory cannot be opened,
       * presumably we don't need to do anything with it... */
        {
          g_autoptr(GError) local_error = NULL;

          libdir_fd = _srt_resolve_in_sysroot (self->mutable_sysroot_fd,
                                               libdir,
                                               SRT_RESOLVE_FLAGS_READABLE,
                                               NULL, &local_error);

          if (libdir_fd < 0)
            {
              g_debug ("Cannot resolve \"%s\" in \"%s\", so no need to delete "
                       "libraries from it: %s",
                       libdir, self->mutable_sysroot, local_error->message);
              g_clear_error (&local_error);
              continue;
            }

          for (j = 0; j < i; j++)
            {
              /* No need to inspect a directory if it's one we already
               * looked at (perhaps via symbolic links) */
              if (iters[j].initialized
                  && _srt_fstatat_is_same_file (libdir_fd, "",
                                                iters[j].fd, ""))
                break;
            }

          if (j < i)
            {
              g_debug ("%s is the same directory as %s, skipping it",
                       libdir, (const char *) g_ptr_array_index (dirs, j));
              continue;
            }
        }

      g_debug ("Removing overridden %s libraries from \"%s\" in \"%s\"...",
               arch->details->tuple, libdir, self->mutable_sysroot);

      if (!glnx_dirfd_iterator_init_take_fd (&libdir_fd, &iters[i], error))
        {
          glnx_prefix_error (error, "Unable to start iterating \"%s/%s\"",
                             self->mutable_sysroot,
                             libdir);
          goto out;
        }

      delete[i] = g_hash_table_new_full (g_str_hash, g_str_equal,
                                         g_free, g_free);

      while (TRUE)
        {
          g_autofree gchar *path = NULL;
          g_autofree gchar *target = NULL;
          const char *target_base;

          if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&iters[i], &dent,
                                                           NULL, error))
            {
              glnx_prefix_error (error, "Unable to iterate over \"%s/%s\"",
                                 self->mutable_sysroot, libdir);
              goto out;
            }

          if (dent == NULL)
            break;

          switch (dent->d_type)
            {
              case DT_REG:
              case DT_LNK:
                break;

              case DT_BLK:
              case DT_CHR:
              case DT_DIR:
              case DT_FIFO:
              case DT_SOCK:
              case DT_UNKNOWN:
              default:
                continue;
            }

          if (!g_str_has_prefix (dent->d_name, "lib"))
            continue;

          if (!g_str_has_suffix (dent->d_name, ".so") &&
              strstr (dent->d_name, ".so.") == NULL)
            continue;

          path = g_build_filename (libdir, dent->d_name, NULL);
          target = glnx_readlinkat_malloc (iters[i].fd, dent->d_name,
                                           NULL, NULL);
          if (target != NULL)
            target_base = glnx_basename (target);
          else
            target_base = NULL;

          /* Suppose we have a shared library libcurl.so.4 -> libcurl.so.4.2.0
           * in the container and libcurl.so.4.7.0 in the provider,
           * with a backwards-compatibility alias libcurl.so.3.
           * dent->d_name might be any of those strings. */

          /* scope for soname_link */
          if (TRUE)   /* to avoid -Wmisleading-indentation */
            {
              g_autofree gchar *soname_link = NULL;

              /* If we're looking at
               * /usr/lib/MULTIARCH/libcurl.so.4 -> libcurl.so.4.2.0, and a
               * symlink .../overrides/lib/MULTIARCH/libcurl.so.4 exists, then
               * we want to delete /usr/lib/MULTIARCH/libcurl.so.4 and
               * /usr/lib/MULTIARCH/libcurl.so.4.2.0. */
              soname_link = g_build_filename (arch->libdir_in_current_namespace,
                                              dent->d_name, NULL);

              if (g_file_test (soname_link, G_FILE_TEST_IS_SYMLINK))
                {
                  if (target_base != NULL)
                    g_hash_table_replace (delete[i],
                                          g_strdup (target_base),
                                          g_strdup (soname_link));

                  g_hash_table_replace (delete[i],
                                        g_strdup (dent->d_name),
                                        g_steal_pointer (&soname_link));
                  continue;
                }
            }

          /* scope for alias_link */
          if (TRUE)   /* to avoid -Wmisleading-indentation */
            {
              g_autofree gchar *alias_link = NULL;
              g_autofree gchar *alias_target = NULL;

              /* If we're looking at
               * /usr/lib/MULTIARCH/libcurl.so.3 -> libcurl.so.4, and a
               * symlink .../aliases/libcurl.so.3 exists and points to
               * e.g. .../overrides/lib/$MULTIARCH/libcurl.so.4, then
               * /usr/lib/MULTIARCH/libcurl.so.3 was overridden and should
               * be deleted; /usr/lib/MULTIARCH/libcurl.so.4 should also
               * be deleted.
               *
               * However, if .../aliases/libcurl.so.3 points to
               * e.g. /usr/lib/MULTIARCH/libcurl.so.4, then the container's
               * library was not overridden and we should not delete
               * anything. */
              alias_link = g_build_filename (arch->aliases_in_current_namespace,
                                             dent->d_name, NULL);
              alias_target = glnx_readlinkat_malloc (AT_FDCWD, alias_link,
                                                     NULL, NULL);

              if (alias_target != NULL
                  && flatpak_has_path_prefix (alias_target,
                                              self->overrides_in_container))
                {
                  if (target_base != NULL)
                    g_hash_table_replace (delete[i],
                                          g_strdup (target_base),
                                          g_strdup (alias_link));

                  g_hash_table_replace (delete[i],
                                        g_strdup (dent->d_name),
                                        g_steal_pointer (&alias_link));
                  continue;
                }
            }

          if (target != NULL)
            {
              g_autofree gchar *soname_link = NULL;

              /* If we're looking at
               * /usr/lib/MULTIARCH/libcurl.so -> libcurl.so.4, and a
               * symlink .../overrides/lib/MULTIARCH/libcurl.so.4 exists,
               * then we want to delete /usr/lib/MULTIARCH/libcurl.so
               * and /usr/lib/MULTIARCH/libcurl.so.4. */
              soname_link = g_build_filename (arch->libdir_in_current_namespace,
                                              glnx_basename (target),
                                              NULL);

              if (g_file_test (soname_link, G_FILE_TEST_IS_SYMLINK))
                {
                  g_hash_table_replace (delete[i],
                                        g_strdup (target_base),
                                        g_strdup (soname_link));
                  g_hash_table_replace (delete[i],
                                        g_strdup (dent->d_name),
                                        g_steal_pointer (&soname_link));
                  continue;
                }
            }

          if (target != NULL)
            {
              g_autofree gchar *alias_link = NULL;
              g_autofree gchar *alias_target = NULL;

              /* If we're looking at
               * /usr/lib/MULTIARCH/libcurl.so.3 -> libcurl.so.4, and a
               * symlink .../aliases/libcurl.so.3 exists and points to
               * e.g. .../overrides/lib/$MULTIARCH/libcurl.so.4, then
               * /usr/lib/MULTIARCH/libcurl.so.3 was overridden and should
               * be deleted; /usr/lib/MULTIARCH/libcurl.so.4 should also
               * be deleted.
               *
               * However, if .../aliases/libcurl.so.3 points to
               * e.g. /usr/lib/MULTIARCH/libcurl.so.4, then the container's
               * library was not overridden and we should not delete it. */
              alias_link = g_build_filename (arch->aliases_in_current_namespace,
                                             glnx_basename (target),
                                             NULL);
              alias_target = glnx_readlinkat_malloc (AT_FDCWD, alias_link,
                                                     NULL, NULL);

              if (alias_target != NULL
                  && flatpak_has_path_prefix (alias_target,
                                              self->overrides_in_container))
                {
                  g_hash_table_replace (delete[i],
                                        g_strdup (target_base),
                                        g_strdup (alias_link));
                  g_hash_table_replace (delete[i],
                                        g_strdup (dent->d_name),
                                        g_steal_pointer (&alias_link));
                  continue;
                }
            }
        }

      /* Iterate over the directory again, to clean up dangling development
       * symlinks */
      glnx_dirfd_iterator_rewind (&iters[i]);

      while (TRUE)
        {
          g_autofree gchar *target = NULL;
          gpointer reason;

          if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&iters[i], &dent,
                                                           NULL, error))
            {
              glnx_prefix_error (error, "Unable to iterate over \"%s/%s\"",
                                 self->mutable_sysroot, libdir);
              goto out;
            }

          if (dent == NULL)
            break;

          if (dent->d_type != DT_LNK)
            continue;

          /* If we were going to delete it anyway, ignore */
          if (g_hash_table_lookup_extended (delete[i], dent->d_name, NULL, NULL))
            continue;

          target = glnx_readlinkat_malloc (iters[i].fd, dent->d_name,
                                           NULL, NULL);

          /* If we're going to delete the target, also delete the symlink
           * rather than leaving it dangling */
          if (g_hash_table_lookup_extended (delete[i], target, NULL, &reason))
            g_hash_table_replace (delete[i], g_strdup (dent->d_name),
                                  g_strdup (reason));
        }
    }

  for (i = 0; i < dirs->len; i++)
    {
      const char *libdir = g_ptr_array_index (dirs, i);

      if (delete[i] == NULL)
        continue;

      g_assert (iters[i].initialized);
      g_assert (iters[i].fd >= 0);

      GLNX_HASH_TABLE_FOREACH_KV (delete[i],
                                  const char *, name,
                                  const char *, reason)
        {
          g_autoptr(GError) local_error = NULL;

          g_debug ("Deleting %s/%s/%s because %s replaces it",
                   self->mutable_sysroot, libdir, name, reason);

          if (!glnx_unlinkat (iters[i].fd, name, 0, &local_error))
            {
              g_warning ("Unable to delete %s/%s/%s: %s",
                         self->mutable_sysroot, libdir,
                         name, local_error->message);
              g_clear_error (&local_error);
            }
        }
    }

  ret = TRUE;

out:
  if (dirs != NULL)
    {
      g_assert (delete != NULL);
      g_assert (iters != NULL);

      for (i = 0; i < dirs->len; i++)
        {
          g_clear_pointer (&delete[i], g_hash_table_unref);
          glnx_dirfd_iterator_clear (&iters[i]);
        }
    }

  g_free (delete);
  g_free (iters);
  return ret;
}

static gboolean
pv_runtime_take_ld_so_from_provider (PvRuntime *self,
                                     RuntimeArchitecture *arch,
                                     const gchar *ld_so_in_runtime,
                                     FlatpakBwrap *bwrap,
                                     GError **error)
{
  glnx_autofd int path_fd = -1;
  g_autofree gchar *ld_so_relative_to_provider = NULL;
  g_autofree gchar *ld_so_in_provider = NULL;

  g_return_val_if_fail (self->provider != NULL, FALSE);
  g_return_val_if_fail (bwrap != NULL || self->mutable_sysroot != NULL, FALSE);

  g_debug ("Making provider's ld.so visible in container");

  path_fd = _srt_resolve_in_sysroot (self->provider->fd,
                                     arch->ld_so, SRT_RESOLVE_FLAGS_READABLE,
                                     &ld_so_relative_to_provider, error);

  if (path_fd < 0)
    {
      g_prefix_error (error, "Unable to determine provider path to %s: ",
                      arch->ld_so);
      return FALSE;
    }

  ld_so_in_provider = g_build_filename (self->provider->path_in_host_ns,
                                        ld_so_relative_to_provider, NULL);

  g_debug ("Provider path: %s -> %s", arch->ld_so, ld_so_relative_to_provider);
  /* Might be either absolute, or relative to the root */
  g_debug ("Container path: %s -> %s", arch->ld_so, ld_so_in_runtime);

  /* If we have a mutable sysroot, we can delete the interoperable path
   * and replace it with a symlink to what we want.
   * For example, overwrite /lib/ld-linux.so.2 with a symlink to
   * /run/host/lib/i386-linux-gnu/ld-2.30.so, or similar. This avoids
   * having to dereference a long chain of symlinks every time we run
   * an executable. */
  if (self->mutable_sysroot != NULL &&
      !pv_runtime_take_from_provider (self, bwrap, ld_so_in_provider,
                                      arch->ld_so,
                                      TAKE_FROM_PROVIDER_FLAGS_NONE, error))
    return FALSE;

  /* If we don't have a mutable sysroot, we cannot replace symlinks,
   * and we also cannot mount onto symlinks (they get dereferenced),
   * so our only choice is to bind-mount
   * /lib/i386-linux-gnu/ld-2.30.so onto
   * /lib/i386-linux-gnu/ld-2.15.so and so on.
   *
   * In the mutable sysroot case, we don't strictly need to
   * overwrite /lib/i386-linux-gnu/ld-2.15.so with a symlink to
   * /run/host/lib/i386-linux-gnu/ld-2.30.so, but we might as well do
   * it anyway, for extra robustness: if we ever run a ld.so that
   * doesn't match the libc we are using (perhaps via an OS-specific,
   * non-standard path), that's pretty much a disaster, because it will
   * just crash. However, all of those (chains of) non-standard symlinks
   * will end up pointing to ld_so_in_runtime. */
  return pv_runtime_take_from_provider (self, bwrap, ld_so_in_provider,
                                        ld_so_in_runtime,
                                        TAKE_FROM_PROVIDER_FLAGS_NONE, error);
}

/*
 * setup_json_manifest:
 * @self: The runtime
 * @bwrap: Append arguments to this bubblewrap invocation to make files
 *  available in the container
 * @sub_dir: `vulkan/icd.d`, `glvnd/egl_vendor.d` or similar
 * @write_to_dir: Path to `/overrides/share/${sub_dir}` in the
 *  current execution environment
 * @details: An #IcdDetails holding a #SrtVulkanLayer or #SrtVulkanIcd,
 *  whichever is appropriate for @sub_dir
 * @seq: Sequence number of @details, used to make unique filenames
 * @search_path: Used to build `$VK_ICD_FILENAMES` or a similar search path
 * @error: Used to raise an error on failure
 *
 * Make a single Vulkan layer or ICD available in the container.
 */
static gboolean
setup_json_manifest (PvRuntime *self,
                     FlatpakBwrap *bwrap,
                     const gchar *sub_dir,
                     const gchar *write_to_dir,
                     IcdDetails *details,
                     gsize seq,
                     GString *search_path,
                     GError **error)
{
  SrtVulkanLayer *layer = NULL;
  SrtVulkanIcd *icd = NULL;
  SrtEglIcd *egl = NULL;
  gboolean need_provider_json = FALSE;
  gsize i;

  g_return_val_if_fail (self->provider != NULL, FALSE);
  g_return_val_if_fail (bwrap != NULL || self->mutable_sysroot != NULL, FALSE);

  if (SRT_IS_VULKAN_LAYER (details->icd))
    layer = SRT_VULKAN_LAYER (details->icd);
  else if (SRT_IS_VULKAN_ICD (details->icd))
    icd = SRT_VULKAN_ICD (details->icd);
  else if (SRT_IS_EGL_ICD (details->icd))
    egl = SRT_EGL_ICD (details->icd);
  else
    g_return_val_if_reached (FALSE);

  /* If the layer failed to load, there's nothing to make available */
  if (layer != NULL)
    {
      if (!srt_vulkan_layer_check_error (layer, NULL))
        return TRUE;
    }
  else if (egl != NULL)
    {
      if (!srt_egl_icd_check_error (egl, NULL))
        return TRUE;
    }
  else
    {
      if (!srt_vulkan_icd_check_error (icd, NULL))
        return TRUE;
    }

  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_autofree gchar *write_to_file = 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",
                                       seq, multiarch_tuples[i]);
          write_to_file = g_build_filename (write_to_dir, json_base, NULL);
          json_in_container = g_build_filename (self->overrides_in_container,
                                                "share", sub_dir,
                                                json_base, NULL);

          if (layer != NULL)
            {
              g_autoptr(SrtVulkanLayer) replacement = NULL;
              replacement = srt_vulkan_layer_new_replace_library_path (layer,
                                                                       details->paths_in_container[i]);

              if (!srt_vulkan_layer_write_to_file (replacement, write_to_file, error))
                return FALSE;
            }
          else if (egl != NULL)
            {
              g_autoptr(SrtEglIcd) replacement = NULL;

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

              if (!srt_egl_icd_write_to_file (replacement, write_to_file,
                                              error))
                return FALSE;
            }
          else
            {
              g_autoptr(SrtVulkanIcd) replacement = NULL;
              replacement = srt_vulkan_icd_new_replace_library_path (icd,
                                                                     details->paths_in_container[i]);

              if (!srt_vulkan_icd_write_to_file (replacement, write_to_file, error))
                return FALSE;
            }

          pv_search_path_append (search_path, json_in_container);
        }
      else if (details->kinds[i] == ICD_KIND_SONAME
               || details->kinds[i] == ICD_KIND_META_LAYER)
        {
          need_provider_json = TRUE;
        }
    }

  if (need_provider_json)
    {
      g_autofree gchar *json_in_container = NULL;
      g_autofree gchar *json_base = NULL;
      const char *json_in_provider = NULL;
      if (layer != NULL)
        json_in_provider = srt_vulkan_layer_get_json_path (layer);
      else if (egl != NULL)
        json_in_provider = srt_egl_icd_get_json_path (egl);
      else
        json_in_provider = srt_vulkan_icd_get_json_path (icd);

      json_base = g_strdup_printf ("%" G_GSIZE_FORMAT ".json", seq);
      json_in_container = g_build_filename (self->overrides_in_container,
                                            "share", sub_dir,
                                            json_base, NULL);

      if (!pv_runtime_take_from_provider (self, bwrap,
                                          json_in_provider,
                                          json_in_container,
                                          TAKE_FROM_PROVIDER_FLAGS_COPY_FALLBACK,
                                          error))
        return FALSE;

      pv_search_path_append (search_path, json_in_container);
    }

  return TRUE;
}

/*
 * setup_each_json_manifest:
 * @self: The runtime
 * @bwrap: Append arguments to this bubblewrap invocation to make files
 *  available in the container
 * @sub_dir: `vulkan/icd.d` or similar
 * @write_to_dir: Path to `/overrides/share/${sub_dir}` in the
 *  current execution environment
 * @details: (element-type IcdDetails): A list of #IcdDetails
 *  holding #SrtVulkanLayer, #SrtVulkanIcd or #SrtEglIcd, as appropriate
 *  for @sub_dir
 * @search_path: Used to build `$VK_ICD_FILENAMES` or a similar search path
 * @error: Used to raise an error on failure
 *
 * Make a list of Vulkan layers or ICDs available in the container.
 */
static gboolean
setup_each_json_manifest (PvRuntime *self,
                          FlatpakBwrap *bwrap,
                          const gchar *sub_dir,
                          GPtrArray *details,
                          GString *search_path,
                          GError **error)
{
  gsize j;
  g_autofree gchar *write_to_dir = NULL;

  g_return_val_if_fail (self->provider != NULL, FALSE);
  g_return_val_if_fail (bwrap != NULL || self->mutable_sysroot != NULL, FALSE);

  write_to_dir = g_build_filename (self->overrides,
                                  "share", sub_dir, NULL);

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

  for (j = 0; j < details->len; j++)
    {
      if (!setup_json_manifest (self, bwrap, sub_dir, write_to_dir,
                                g_ptr_array_index (details, j),
                                j, search_path, error))
        return FALSE;
    }

  return TRUE;
}

static gboolean
collect_vulkan_layers (PvRuntime *self,
                       const GPtrArray *layer_details,
                       GPtrArray *dependency_patterns,
                       RuntimeArchitecture *arch,
                       const gchar *dir_name,
                       GError **error)
{
  gsize j;
  G_GNUC_UNUSED g_autoptr(SrtProfilingTimer) timer =
    _srt_profiling_start ("Collecting Vulkan %s layers", dir_name);

  g_return_val_if_fail (self->provider != NULL, FALSE);
  g_return_val_if_fail (dependency_patterns != NULL, FALSE);
  g_return_val_if_fail (dir_name != NULL, FALSE);

  for (j = 0; j < layer_details->len; j++)
    {
      g_autoptr(SrtLibrary) library = NULL;
      SrtLibraryIssues issues;
      IcdDetails *details = g_ptr_array_index (layer_details, j);
      SrtVulkanLayer *layer = SRT_VULKAN_LAYER (details->icd);
      /* We don't have to use multiple directories unless there are
       * filename collisions, because the order of the JSON manifests
       * might matter, but the order of the actual libraries does not. */
      gboolean use_numbered_subdirs = FALSE;
      /* If we have just a SONAME, we do not want to place the library
       * under a subdir, otherwise ld.so will not be able to find it */
      const gboolean use_subdir_for_kind_soname = FALSE;

      if (!srt_vulkan_layer_check_error (layer, NULL))
        continue;

      /* For meta-layers we don't have a library path */
      if (srt_vulkan_layer_get_library_path(layer) == NULL)
        {
          details->kinds[arch->multiarch_index] = ICD_KIND_META_LAYER;
          continue;
        }

      /* If the library_path is relative to the JSON file, turn it into an
       * absolute path. If it's already absolute, or if it's a basename to be
       * looked up in the system library search path, use it as-is. */
      details->resolved_library = srt_vulkan_layer_resolve_library_path (layer);
      g_assert (details->resolved_library != NULL);

      if (strchr (details->resolved_library, '/') != NULL &&
          (strstr (details->resolved_library, "$ORIGIN/") != NULL ||
           strstr (details->resolved_library, "${ORIGIN}") != NULL ||
           strstr (details->resolved_library, "$LIB/") != NULL ||
           strstr (details->resolved_library, "${LIB}") != NULL ||
           strstr (details->resolved_library, "$PLATFORM/") != NULL ||
           strstr (details->resolved_library, "${PLATFORM}") != NULL))
        {
          /* When loading a library by its absolute or relative path
           * (but not when searching the library path for its basename),
           * glibc expands dynamic string tokens: LIB, PLATFORM, ORIGIN.
           * libcapsule cannot expand these special tokens: the only thing
           * that knows the correct magic values for them is glibc, which has
           * no API to tell us. The only way we can find out the library's
           * real location is to tell libdl to load (dlopen) the library, and
           * see what the resulting path is. */
          if (g_strcmp0 (self->provider->path_in_current_ns, "/") == 0)
            {
              /* It's in our current namespace, so we can dlopen it. */
              issues = srt_check_library_presence (details->resolved_library,
                                                   arch->details->tuple, NULL,
                                                   SRT_LIBRARY_SYMBOLS_FORMAT_PLAIN,
                                                   &library);
              if (issues & (SRT_LIBRARY_ISSUES_CANNOT_LOAD |
                            SRT_LIBRARY_ISSUES_UNKNOWN |
                            SRT_LIBRARY_ISSUES_TIMEOUT))
                {
                  g_info ("Unable to load library %s: %s", details->resolved_library,
                          srt_library_get_messages (library));
                  continue;
                }
              g_free (details->resolved_library);
              details->resolved_library = g_strdup (srt_library_get_absolute_path (library));
            }
          else
            {
              /* Sorry, we can't know how to load this. */
              g_info ("Cannot support ld.so special tokens, e.g. ${LIB}, when provider "
                      "is not the root filesystem");
              continue;
            }
        }

      if (!bind_icd (self, arch, j, dir_name, details, &use_numbered_subdirs,
                     use_subdir_for_kind_soname, dependency_patterns, NULL, error))
        return FALSE;
    }

  return TRUE;
}

/*
 * @self: the runtime
 * @arch: An architecture
 * @ld_so_in_runtime: (out) (nullable) (not optional): Used to return
 *  the path to the architecture's ld.so in the runtime, or to
 *  return %NULL if there is none.
 * @error: Used to raise an error on failure
 *
 * Get the path to the ld.so in the runtime, which is either absolute
 * or relative to the sysroot.
 *
 * Returns: %TRUE on success (possibly yielding %NULL via @ld_so_in_runtime)
 */
static gboolean
pv_runtime_get_ld_so (PvRuntime *self,
                      RuntimeArchitecture *arch,
                      gchar **ld_so_in_runtime,
                      GError **error)
{
  if (self->mutable_sysroot != NULL)
    {
      glnx_autofd int fd = -1;

      fd = _srt_resolve_in_sysroot (self->mutable_sysroot_fd,
                                    arch->ld_so,
                                    SRT_RESOLVE_FLAGS_NONE,
                                    ld_so_in_runtime,
                                    NULL);

      /* Ignore fd, and just let it close: we're resolving
       * the path for its side-effect of populating
       * ld_so_in_runtime. */
    }
  else
    {
      g_autoptr(FlatpakBwrap) temp_bwrap = NULL;
      g_autofree gchar *etc = NULL;

      if (self->bubblewrap == NULL)
        return glnx_throw (error,
                           "Cannot run bubblewrap to set up runtime");

      /* Do it the hard way, by asking a process running in the
       * container (or at least a container resembling the one we
       * are going to use) to resolve it for us */
      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_on_host,
                              self->runtime_files,
                              "/",
                              error))
        return FALSE;

      etc = g_build_filename (self->runtime_files_on_host,
                              "etc", NULL);
      flatpak_bwrap_add_args (temp_bwrap,
                              "--ro-bind",
                              etc,
                              "/etc",
                              NULL);

      if (self->provider != NULL)
        {
          g_autofree gchar *provider_etc = NULL;
          g_autofree gchar *provider_etc_dest = NULL;

          if (!pv_bwrap_bind_usr (temp_bwrap,
                                  self->provider->path_in_host_ns,
                                  self->provider->path_in_current_ns,
                                  self->provider->path_in_container_ns,
                                  error))
            return FALSE;

          provider_etc = g_build_filename (self->provider->path_in_host_ns,
                                           "etc", NULL);
          provider_etc_dest = g_build_filename (self->provider->path_in_container_ns,
                                                "etc", NULL);
          flatpak_bwrap_add_args (temp_bwrap,
                                  "--ro-bind",
                                  provider_etc,
                                  provider_etc_dest,
                                  NULL);
        }

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

      pv_run_sync ((const char * const *) temp_bwrap->argv->pdata,
                   (const char * const *) temp_bwrap->envp, NULL,
                   ld_so_in_runtime, NULL);
    }

  return TRUE;
}

/*
 * @patterns: (inout) (not nullable):
 */
static void
collect_graphics_libraries_patterns (GPtrArray *patterns)
{
  static const char * const sonames[] =
  {
    /* Vulkan */
    "libvulkan.so.1",

    /* VDPAU */
    "libvdpau.so.1",

    /* VA-API */
    "libva.so.1",
    "libva-drm.so.1",
    "libva-glx.so.1",
    "libva-x11.so.1",
    "libva.so.2",
    "libva-drm.so.2",
    "libva-glx.so.2",
    "libva-x11.so.2",
  };
  static const char * const soname_globs[] =
  {
    /* NVIDIA proprietary stack */
    "libEGL.so.*",
    "libEGL_nvidia.so.*",
    "libGL.so.*",
    "libGLESv1_CM.so.*",
    "libGLESv1_CM_nvidia.so.*",
    "libGLESv2.so.*",
    "libGLESv2_nvidia.so.*",
    "libGLX.so.*",
    "libGLX_nvidia.so.*",
    "libGLX_indirect.so.*",
    "libGLdispatch.so.*",
    "libOpenGL.so.*",
    "libcuda.so.*",
    "libglx.so.*",
    "libnvidia-cbl.so.*",
    "libnvidia-cfg.so.*",
    "libnvidia-compiler.so.*",
    "libnvidia-egl-wayland.so.*",
    "libnvidia-eglcore.so.*",
    "libnvidia-encode.so.*",
    "libnvidia-fatbinaryloader.so.*",
    "libnvidia-fbc.so.*",
    "libnvidia-glcore.so.*",
    "libnvidia-glsi.so.*",
    "libnvidia-glvkspirv.so.*",
    "libnvidia-ifr.so.*",
    "libnvidia-ml.so.*",
    "libnvidia-opencl.so.*",
    "libnvidia-opticalflow.so.*",
    "libnvidia-ptxjitcompiler.so.*",
    "libnvidia-rtcore.so.*",
    "libnvidia-tls.so.*",
    "libOpenCL.so.*",
    "libvdpau_nvidia.so.*",
  };
  gsize i;

  g_return_if_fail (patterns != NULL);

  /* Mesa GLX, etc. */
  g_ptr_array_add (patterns, g_strdup ("gl:"));

  for (i = 0; i < G_N_ELEMENTS (sonames); i++)
    g_ptr_array_add (patterns,
                     g_strdup_printf ("if-exists:if-same-abi:soname:%s",
                                      sonames[i]));

  for (i = 0; i < G_N_ELEMENTS (soname_globs); i++)
    g_ptr_array_add (patterns,
                     g_strdup_printf ("if-exists:even-if-older:soname-match:%s",
                                      soname_globs[i]));
}

static gboolean
pv_runtime_collect_libc_family (PvRuntime *self,
                                RuntimeArchitecture *arch,
                                FlatpakBwrap *bwrap,
                                const char *libc,
                                const char *ld_so_in_runtime,
                                const char *provider_in_container_namespace_guarded,
                                GHashTable *gconv_in_provider,
                                GError **error)
{
  g_autoptr(FlatpakBwrap) temp_bwrap = NULL;
  G_GNUC_UNUSED g_autoptr(SrtProfilingTimer) libc_timer =
    _srt_profiling_start ("glibc");
  g_autofree char *libc_target = NULL;

  g_return_val_if_fail (self->provider != NULL, FALSE);
  g_return_val_if_fail (bwrap != NULL || self->mutable_sysroot != NULL, FALSE);

  if (!pv_runtime_take_ld_so_from_provider (self, arch,
                                            ld_so_in_runtime,
                                            bwrap, error))
    return FALSE;

  /* Collect miscellaneous libraries that libc might dlopen. */
  temp_bwrap = pv_runtime_get_capsule_capture_libs (self, arch);
  flatpak_bwrap_add_args (temp_bwrap,
                          "--dest", arch->libdir_in_current_namespace,
                          "if-exists:libidn2.so.0",
                          "if-exists:even-if-older:soname-match:libnss_compat.so.*",
                          "if-exists:even-if-older:soname-match:libnss_db.so.*",
                          "if-exists:even-if-older:soname-match:libnss_dns.so.*",
                          "if-exists:even-if-older:soname-match:libnss_files.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);

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

      dir = g_path_get_dirname (libc_target);

      if (g_str_has_prefix (dir, provider_in_container_namespace_guarded))
        memmove (dir,
                 dir + strlen (self->provider->path_in_container_ns),
                 strlen (dir) - strlen (self->provider->path_in_container_ns) + 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".
       * We check /usr first because otherwise, if the host is merged-/usr and the
       * container is not, we might end up binding /lib instead of /usr/lib
       * and that could cause issues. */
      if (g_str_has_prefix (dir, "/usr/"))
        memmove (dir, dir + strlen ("/usr"), strlen (dir) - strlen ("/usr") + 1);

      /* If it starts with "/app/" we don't prepend "/usr/" because we don't
       * expect "/usr/app/" to be available */
      if (g_str_has_prefix (dir, "/app/"))
        gconv_dir_in_provider = g_build_filename (dir, "gconv", NULL);
      else
        gconv_dir_in_provider = g_build_filename ("/usr", dir, "gconv", NULL);

      if (_srt_file_test_in_sysroot (self->provider->path_in_current_ns,
                                     self->provider->fd,
                                     gconv_dir_in_provider,
                                     G_FILE_TEST_IS_DIR))
        {
          g_hash_table_add (gconv_in_provider, g_steal_pointer (&gconv_dir_in_provider));
          found = TRUE;
        }

      if (!found)
        {
          /* Try again without hwcaps subdirectories.
           * For example, libc6-i386 on SteamOS 2 'brewmaster'
           * contains /lib/i386-linux-gnu/i686/cmov/libc.so.6,
           * for which we want gconv modules from
           * /usr/lib/i386-linux-gnu/gconv, not from
           * /usr/lib/i386-linux-gnu/i686/cmov/gconv. */
          while (g_str_has_suffix (dir, "/cmov") ||
                 g_str_has_suffix (dir, "/i686") ||
                 g_str_has_suffix (dir, "/sse2") ||
                 g_str_has_suffix (dir, "/tls") ||
                 g_str_has_suffix (dir, "/x86_64"))
            {
              char *slash = strrchr (dir, '/');

              g_assert (slash != NULL);
              *slash = '\0';
            }

          g_clear_pointer (&gconv_dir_in_provider, g_free);
          if (g_str_has_prefix (dir, "/app/"))
            gconv_dir_in_provider = g_build_filename (dir, "gconv", NULL);
          else
            gconv_dir_in_provider = g_build_filename ("/usr", dir, "gconv", NULL);

          if (_srt_file_test_in_sysroot (self->provider->path_in_current_ns,
                                         self->provider->fd,
                                         gconv_dir_in_provider,
                                         G_FILE_TEST_IS_DIR))
            {
              g_hash_table_add (gconv_in_provider, g_steal_pointer (&gconv_dir_in_provider));
              found = TRUE;
            }
        }

      if (!found)
        {
          g_info ("We were expecting the gconv modules directory in the provider "
                  "to be located in \"%s/gconv\", but instead it is missing",
                  dir);
        }
    }

  return TRUE;
}

static void
pv_runtime_collect_lib_data (PvRuntime *self,
                             RuntimeArchitecture *arch,
                             const char *dir_basename,
                             const char *lib_path,
                             const char *provider_in_container_namespace_guarded,
                             GHashTable *data_in_provider)
{
  g_autofree char *target = NULL;
  target = glnx_readlinkat_malloc (-1, lib_path, NULL, NULL);

  g_return_if_fail (self->provider != NULL);

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

      /* If we are unable to find the lib data in the provider, we try as
       * a last resort `/usr/share`. This should help for example Exherbo
       * that uses the unusual `/usr/${gnu_tuple}/lib` path for shared
       * libraries. */
      dir_in_provider_fallback = g_build_filename ("/usr", "share", dir_basename, NULL);

      dir = g_path_get_dirname (target);

      lib_multiarch = g_build_filename ("/lib", arch->details->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, provider_in_container_namespace_guarded))
        memmove (dir,
                 dir + strlen (self->provider->path_in_container_ns),
                 strlen (dir) - strlen (self->provider->path_in_container_ns) + 1);

      dir_in_provider = g_build_filename (dir, "share", dir_basename, NULL);

      if (_srt_file_test_in_sysroot (self->provider->path_in_current_ns,
                                     self->provider->fd,
                                     dir_in_provider,
                                     G_FILE_TEST_IS_DIR))
        {
          g_hash_table_add (data_in_provider,
                            g_steal_pointer (&dir_in_provider));
        }
      else if (_srt_file_test_in_sysroot (self->provider->path_in_current_ns,
                                          self->provider->fd,
                                          dir_in_provider_fallback,
                                          G_FILE_TEST_IS_DIR))
        {
          g_hash_table_add (data_in_provider,
                            g_steal_pointer (&dir_in_provider_fallback));
        }
      else
        {
          if (g_strcmp0 (dir_in_provider, dir_in_provider_fallback) == 0)
            g_info ("We were expecting the %s directory in the provider to "
                    "be located in \"%s\", but instead it is missing",
                    dir_basename, dir_in_provider);
          else
            g_info ("We were expecting the %s directory in the provider to "
                    "be located in \"%s\" or \"%s\", but instead it is missing",
                    dir_basename, dir_in_provider, dir_in_provider_fallback);
        }
    }
}

static gboolean
pv_runtime_finish_lib_data (PvRuntime *self,
                            FlatpakBwrap *bwrap,
                            const gchar *dir_basename,
                            const gchar *lib_name,
                            gboolean all_from_provider,
                            GHashTable *data_in_provider,
                            GError **error)
{
  g_autofree gchar *best_data_in_provider = NULL;
  g_autofree gchar *canonical_path = NULL;

  g_return_val_if_fail (self->provider != NULL, FALSE);
  g_return_val_if_fail (bwrap != NULL || self->mutable_sysroot != NULL, FALSE);
  g_return_val_if_fail (dir_basename != NULL, FALSE);

  canonical_path = g_build_filename ("/usr", "share", dir_basename, NULL);

  if (g_hash_table_size (data_in_provider) > 0 && !all_from_provider)
    {
      /* See the explanation in the similar
       * "any_libc_from_provider && !all_libc_from_provider" case, above */
      g_warning ("Using %s from provider system for some but not all "
                 "architectures! Will take /usr/share/%s from provider.",
                 lib_name, dir_basename);
    }

  if (self->is_flatpak_env)
    {
      /* In a Flatpak environment is normal to have more than one data
       * directory from provider, e.g. one for every multiarch */
      GHashTableIter iter;
      const gchar *data_path = NULL;

      g_hash_table_iter_init (&iter, data_in_provider);
      while (g_hash_table_iter_next (&iter, (gpointer *)&data_path, NULL))
        {
          /* If we are in a Flatpak environment we bind the lib data in the
           * same path as the one in the provider because
           * /usr/share/${dir_basename} is not expected to be in the library
           * search path. */
          if (!pv_runtime_take_from_provider (self, bwrap,
                                              data_path, data_path,
                                              TAKE_FROM_PROVIDER_FLAGS_IF_DIR,
                                              error))
            return FALSE;

          if (g_str_has_prefix (data_path, "/app/lib/"))
            {
              /* In a freedesktop.org runtime, for some multiarch, there is
               * a symlink /usr/lib/${arch} that points to /app/lib/${arch}
               * https://gitlab.com/freedesktop-sdk/freedesktop-sdk/-/blob/70cb5835/elements/multiarch/multiarch-platform.bst#L24
               * If we have a path in /app/lib/ here, we also try to
               * replicate the symlink in /usr/lib/ */
              g_autofree gchar *path_in_usr = NULL;
              path_in_usr = g_build_filename ("/usr",
                                              data_path + strlen ("/app"),
                                              NULL);
              if (_srt_fstatat_is_same_file (-1, data_path, -1, path_in_usr))
                {
                  if (!pv_runtime_take_from_provider (self, bwrap,
                                                      data_path, path_in_usr,
                                                      TAKE_FROM_PROVIDER_FLAGS_IF_DIR,
                                                      error))
                    return FALSE;
                }
            }
        }

      return TRUE;
    }

  if (g_hash_table_size (data_in_provider) == 1)
    {
      best_data_in_provider = g_strdup (
        pv_hash_table_get_arbitrary_key (data_in_provider));
    }
  else if (g_hash_table_size (data_in_provider) > 1)
    {
      g_warning ("Found more than one possible %s data directory from provider",
                 dir_basename);
      /* Prioritize "/usr/share/{dir_basename}" if available. Otherwise randomly pick
       * the first directory in the hash table */
      if (g_hash_table_contains (data_in_provider, canonical_path))
        best_data_in_provider = g_strdup (canonical_path);
      else
        best_data_in_provider = g_strdup (
          pv_hash_table_get_arbitrary_key (data_in_provider));
    }

  if (best_data_in_provider != NULL)
    {
      return pv_runtime_take_from_provider (self, bwrap,
                                            best_data_in_provider,
                                            canonical_path,
                                            TAKE_FROM_PROVIDER_FLAGS_IF_CONTAINER_COMPATIBLE,
                                            error);
    }
  else
    {
      return TRUE;
    }
}

static gboolean
pv_runtime_finish_libc_family (PvRuntime *self,
                               FlatpakBwrap *bwrap,
                               GHashTable *gconv_in_provider,
                               GError **error)
{
  g_autofree gchar *localedef = NULL;
  g_autofree gchar *ldconfig = NULL;
  g_autofree gchar *locale = NULL;
  GHashTableIter iter;
  const gchar *gconv_path;
  gsize i;
  /* List of paths where we expect to find "locale", sorted by the most
   * preferred to the least preferred.
   * If the canonical "/usr/lib/locale" is missing, we try the Exherbo's
   * "/usr/${gnu_tuple}/lib/locale" too, before giving up.
   * The locale directory is actually architecture-independent, so we just
   * arbitrarily prefer to use "x86_64-pc-linux-gnu" over the 32-bit couterpart */
  const gchar *lib_locale_path[] = {
    "/usr/lib/locale",
    "/usr/x86_64-pc-linux-gnu/lib/locale",
    "/usr/i686-pc-linux-gnu/lib/locale",
    NULL
  };

  g_return_val_if_fail (self->provider != NULL, FALSE);
  g_return_val_if_fail (bwrap != NULL || self->mutable_sysroot != NULL, FALSE);

  if (self->any_libc_from_provider && !self->all_libc_from_provider)
    {
      /*
       * This shouldn't happen. It would mean that there exist at least
       * two architectures (let's say aaa and bbb) for which we have:
       * provider libc6:aaa < container libc6 < provider 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 provider 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 provider system for some but not all "
                 "architectures! Arbitrarily using provider locales.");
    }

  if (self->any_libc_from_provider)
    {
      g_debug ("Making provider locale data visible in container");

      for (i = 0; i < G_N_ELEMENTS (lib_locale_path) - 1; i++)
        {
          if (_srt_file_test_in_sysroot (self->provider->path_in_current_ns,
                                         self->provider->fd,
                                         lib_locale_path[i], G_FILE_TEST_EXISTS))
            {
              if (!pv_runtime_take_from_provider (self, bwrap,
                                                  lib_locale_path[i],
                                                  "/usr/lib/locale",
                                                  TAKE_FROM_PROVIDER_FLAGS_IF_EXISTS,
                                                  error))
                return FALSE;

              break;
            }
        }

      if (!pv_runtime_take_from_provider (self, bwrap,
                                          "/usr/share/i18n",
                                          "/usr/share/i18n",
                                          TAKE_FROM_PROVIDER_FLAGS_IF_EXISTS,
                                          error))
        return FALSE;

      localedef = pv_graphics_provider_search_in_path_and_bin (self->provider, "localedef");

      if (localedef == NULL)
        {
          g_warning ("Cannot find localedef");
        }
      else if (!pv_runtime_take_from_provider (self, bwrap, localedef,
                                               "/usr/bin/localedef",
                                               TAKE_FROM_PROVIDER_FLAGS_IF_CONTAINER_COMPATIBLE,
                                               error))
        {
          return FALSE;
        }

      locale = pv_graphics_provider_search_in_path_and_bin (self->provider, "locale");

      if (locale == NULL)
        {
          g_warning ("Cannot find locale");
        }
      else if (!pv_runtime_take_from_provider (self, bwrap, locale,
                                               "/usr/bin/locale",
                                               TAKE_FROM_PROVIDER_FLAGS_IF_CONTAINER_COMPATIBLE,
                                               error))
        {
          return FALSE;
        }

      ldconfig = pv_graphics_provider_search_in_path_and_bin (self->provider, "ldconfig");

      if (ldconfig == NULL)
        {
          g_warning ("Cannot find ldconfig");
        }
      else if (!pv_runtime_take_from_provider (self, bwrap,
                                               ldconfig,
                                               "/sbin/ldconfig",
                                               TAKE_FROM_PROVIDER_FLAGS_NONE,
                                               error))
        {
          return FALSE;
        }

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

      g_hash_table_iter_init (&iter, gconv_in_provider);
      while (g_hash_table_iter_next (&iter, (gpointer *)&gconv_path, NULL))
        {
          if (!pv_runtime_take_from_provider (self, bwrap,
                                              gconv_path,
                                              gconv_path,
                                              TAKE_FROM_PROVIDER_FLAGS_IF_DIR,
                                              error))
            return FALSE;
        }
    }
  else
    {
      g_debug ("Using included locale data from container");
      g_debug ("Using included gconv modules from container");
    }

  return TRUE;
}

static gboolean
pv_runtime_create_aliases (PvRuntime *self,
                           RuntimeArchitecture *arch,
                           GError **error)
{
  g_autoptr(JsonParser) parser = NULL;
  G_GNUC_UNUSED g_autoptr(SrtProfilingTimer) timer =
    _srt_profiling_start ("Creating library aliases");
  JsonNode *node = NULL;
  JsonArray *libraries_array = NULL;
  JsonArray *aliases_array = NULL;
  JsonObject *object;

  if (self->runtime_abi_json == NULL)
    {
      g_info ("Runtime ABI JSON not present, not creating library aliases");
      return TRUE;
    }

  parser = json_parser_new ();
  if (!json_parser_load_from_file (parser, self->runtime_abi_json, error))
    return glnx_prefix_error (error, "Error parsing the expected JSON object in \"%s\"",
                              self->runtime_abi_json);

  node = json_parser_get_root (parser);
  object = json_node_get_object (node);

  if (!json_object_has_member (object, "shared_libraries"))
    return glnx_throw (error, "No \"shared_libraries\" in the JSON object \"%s\"",
                       self->runtime_abi_json);

  libraries_array = json_object_get_array_member (object, "shared_libraries");
  if (libraries_array == NULL || json_array_get_length (libraries_array) == 0)
    return TRUE;

  for (guint i = 0; i < json_array_get_length (libraries_array); i++)
    {
      const gchar *soname = NULL;
      g_autofree gchar *soname_in_runtime = NULL;
      g_autofree gchar *soname_in_runtime_usr = NULL;
      g_autofree gchar *soname_in_overrides = NULL;
      g_autofree gchar *target = NULL;
      g_autoptr(GList) members = NULL;

      node = json_array_get_element (libraries_array, i);
      if (!JSON_NODE_HOLDS_OBJECT (node))
        continue;

      object = json_node_get_object (node);

      members = json_object_get_members (object);
      if (members == NULL)
        continue;

      soname = members->data;

      object = json_object_get_object_member (object, soname);
      if (!json_object_has_member (object, "aliases"))
        continue;

      aliases_array = json_object_get_array_member (object, "aliases");
      if (aliases_array == NULL || json_array_get_length (aliases_array) == 0)
        continue;

      soname_in_overrides = g_build_filename (arch->libdir_in_current_namespace,
                                              soname, NULL);
      soname_in_runtime_usr = g_build_filename (self->runtime_usr, "lib",
                                                arch->details->tuple, soname, NULL);
      /* We are not always in a merged-/usr runtime, e.g. if we are using a
       * "sysroot" runtime. */
      soname_in_runtime = g_build_filename (self->runtime_files, "lib",
                                            arch->details->tuple, soname, NULL);

      if (g_file_test (soname_in_overrides,
                       (G_FILE_TEST_IS_REGULAR | G_FILE_TEST_IS_SYMLINK)))
        target = g_build_filename (arch->libdir_in_container, soname, NULL);
      else if (g_file_test (soname_in_runtime_usr,
                            (G_FILE_TEST_IS_REGULAR | G_FILE_TEST_IS_SYMLINK)))
        target = g_build_filename ("/usr/lib", arch->details->tuple, soname, NULL);
      else if (g_file_test (soname_in_runtime,
                            (G_FILE_TEST_IS_REGULAR | G_FILE_TEST_IS_SYMLINK)))
        target = g_build_filename ("/lib", arch->details->tuple, soname, NULL);
      else
        return glnx_throw (error, "The expected library %s is missing from both the runtime "
                           "and the \"overrides\" directory", soname);

      for (guint j = 0; j < json_array_get_length (aliases_array); j++)
        {
          g_autofree gchar *dest = g_build_filename (arch->aliases_in_current_namespace,
                                                     json_array_get_string_element (aliases_array, j),
                                                     NULL);
          if (symlink (target, dest) != 0)
            return glnx_throw_errno_prefix (error,
                                            "Unable to create symlink %s -> %s",
                                            dest, target);
        }
    }
  return TRUE;
}

/*
 * @egl_icd_details: (element-type IcdDetails):
 * @patterns: (element-type filename):
 */
static gboolean
collect_egl_drivers (PvRuntime *self,
                     RuntimeArchitecture *arch,
                     GPtrArray *egl_icd_details,
                     GPtrArray *patterns,
                     GError **error)
{
  G_GNUC_UNUSED g_autoptr(SrtProfilingTimer) timer =
    _srt_profiling_start ("Collecting EGL drivers");
  gsize j;
  /* As with Vulkan layers, the order of the manifests matters
   * but the order of the actual libraries does not. */
  gboolean use_numbered_subdirs = FALSE;
  /* If we have just a SONAME, we do not want to place the library
   * under a subdir, otherwise ld.so will not be able to find it */
  const gboolean use_subdir_for_kind_soname = FALSE;

  g_debug ("Collecting %s EGL drivers from provider...",
           arch->details->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,
                     &use_numbered_subdirs, use_subdir_for_kind_soname,
                     patterns, NULL, error))
        return FALSE;
    }

  return TRUE;
}

/*
 * @vulkan_icd_details: (element-type IcdDetails):
 * @patterns: (element-type filename):
 */
static gboolean
collect_vulkan_icds (PvRuntime *self,
                     RuntimeArchitecture *arch,
                     GPtrArray *vulkan_icd_details,
                     GPtrArray *patterns,
                     GError **error)
{
  G_GNUC_UNUSED g_autoptr(SrtProfilingTimer) timer =
    _srt_profiling_start ("Collecting Vulkan ICDs");
  gsize j;
  /* As with Vulkan layers, the order of the manifests matters
   * but the order of the actual libraries does not. */
  gboolean use_numbered_subdirs = FALSE;
  /* If we have just a SONAME, we do not want to place the library
   * under a subdir, otherwise ld.so will not be able to find it */
  const gboolean use_subdir_for_kind_soname = FALSE;

  g_debug ("Collecting %s Vulkan drivers from provider...",
           arch->details->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,
                     &use_numbered_subdirs, use_subdir_for_kind_soname,
                     patterns, NULL, error))
        return FALSE;
    }

  return TRUE;
}

/*
 * @patterns: (element-type filename):
 */
static gboolean
collect_vdpau_drivers (PvRuntime *self,
                       SrtSystemInfo *system_info,
                       RuntimeArchitecture *arch,
                       GPtrArray *patterns,
                       GError **error)
{
  G_GNUC_UNUSED g_autoptr(SrtProfilingTimer) timer =
    _srt_profiling_start ("Collecting VDPAU drivers");
  g_autoptr(SrtObjectList) vdpau_drivers = NULL;
  /* The VDPAU loader looks up drivers by name, not by readdir(),
   * so order doesn't matter unless there are name collisions. */
  gboolean use_numbered_subdirs = FALSE;
  /* These libraries are always expected to be located under the
   * "vdpau" subdir */
  const gboolean use_subdir_for_kind_soname = TRUE;
  const GList *icd_iter;
  gsize j;

  g_debug ("Enumerating %s VDPAU ICDs on provider...", arch->details->tuple);
    {
      G_GNUC_UNUSED g_autoptr(SrtProfilingTimer) enum_timer =
        _srt_profiling_start ("Enumerating VDPAU drivers");

      vdpau_drivers = srt_system_info_list_vdpau_drivers (system_info,
                                                          arch->details->tuple,
                                                          SRT_DRIVER_FLAGS_NONE);
    }

  for (icd_iter = vdpau_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_vdpau_driver_resolve_library_path (details->icd);
      g_assert (details->resolved_library != NULL);
      g_assert (g_path_is_absolute (details->resolved_library));

      /* In practice we won't actually use the sequence number for VDPAU
       * because they can only be located in a single directory,
       * so by definition we can't have collisions. Anything that
       * ends up in a numbered subdirectory won't get used. */
      if (!bind_icd (self, arch, j, "vdpau", details,
                     &use_numbered_subdirs, use_subdir_for_kind_soname,
                     patterns, NULL, error))
        return FALSE;
    }

  return TRUE;
}

/*
 * @patterns: (element-type filename):
 */
static gboolean
collect_dri_drivers (PvRuntime *self,
                     SrtSystemInfo *system_info,
                     RuntimeArchitecture *arch,
                     GPtrArray *patterns,
                     GString *dri_path,
                     GError **error)
{
  G_GNUC_UNUSED g_autoptr(SrtProfilingTimer) timer =
    _srt_profiling_start ("Collecting DRI drivers");
  g_autoptr(SrtObjectList) dri_drivers = NULL;
  /* The DRI loader looks up drivers by name, not by readdir(),
   * so order doesn't matter unless there are name collisions. */
  gboolean use_numbered_subdirs = FALSE;
  /* These libraries are always expected to be located under the
   * "dri" subdir */
  const gboolean use_subdir_for_kind_soname = TRUE;
  const GList *icd_iter;
  gsize j;

  g_debug ("Enumerating %s DRI drivers on provider...",
           arch->details->tuple);
    {
      G_GNUC_UNUSED g_autoptr(SrtProfilingTimer) enum_timer =
        _srt_profiling_start ("Enumerating DRI drivers");

      dri_drivers = srt_system_info_list_dri_drivers (system_info,
                                                      arch->details->tuple,
                                                      SRT_DRIVER_FLAGS_NONE);
    }

  for (icd_iter = dri_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_dri_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,
                     &use_numbered_subdirs, use_subdir_for_kind_soname,
                     patterns, dri_path, error))
        return FALSE;
    }

  return TRUE;
}

/*
 * @patterns: (element-type filename):
 */
static gboolean
collect_va_api_drivers (PvRuntime *self,
                        SrtSystemInfo *system_info,
                        RuntimeArchitecture *arch,
                        GPtrArray *patterns,
                        GString *va_api_path,
                        GError **error)
{
  G_GNUC_UNUSED g_autoptr(SrtProfilingTimer) timer =
    _srt_profiling_start ("Collecting VA-API drivers");
  g_autoptr(SrtObjectList) va_api_drivers = NULL;
  /* The VA-API loader looks up drivers by name, not by readdir(),
   * so order doesn't matter unless there are name collisions. */
  gboolean use_numbered_subdirs = FALSE;
  /* These libraries are always expected to be located under the
   * "dri" subdir */
  const gboolean use_subdir_for_kind_soname = TRUE;
  const GList *icd_iter;
  gsize j;

  g_debug ("Enumerating %s VA-API drivers on provider...",
           arch->details->tuple);
    {
      G_GNUC_UNUSED g_autoptr(SrtProfilingTimer) enum_timer =
        _srt_profiling_start ("Enumerating VA-API drivers");

      va_api_drivers = srt_system_info_list_va_api_drivers (system_info,
                                                            arch->details->tuple,
                                                            SRT_DRIVER_FLAGS_NONE);
    }

  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,
                     &use_numbered_subdirs, use_subdir_for_kind_soname,
                     patterns, va_api_path, error))
        return FALSE;
    }

  return TRUE;
}

static gboolean
pv_runtime_use_provider_graphics_stack (PvRuntime *self,
                                        FlatpakBwrap *bwrap,
                                        PvEnviron *container_env,
                                        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 ("");
  /* We are currently using the explicit and implicit Vulkan layer paths
   * only to check if we binded at least a single layer */
  g_autoptr(GString) vulkan_exp_layer_path = g_string_new ("");
  g_autoptr(GString) vulkan_imp_layer_path = g_string_new ("");
  g_autoptr(GString) va_api_path = g_string_new ("");
  gboolean any_architecture_works = FALSE;
  g_autoptr(SrtSystemInfo) system_info = NULL;
  g_autoptr(SrtObjectList) egl_icds = NULL;
  g_autoptr(SrtObjectList) vulkan_icds = NULL;
  g_autoptr(SrtObjectList) vulkan_explicit_layers = NULL;
  g_autoptr(SrtObjectList) vulkan_implicit_layers = NULL;
  g_autoptr(GPtrArray) egl_icd_details = NULL;      /* (element-type IcdDetails) */
  g_autoptr(GPtrArray) vulkan_icd_details = NULL;   /* (element-type IcdDetails) */
  g_autoptr(GPtrArray) vulkan_exp_layer_details = NULL;   /* (element-type IcdDetails) */
  g_autoptr(GPtrArray) vulkan_imp_layer_details = NULL;   /* (element-type IcdDetails) */
  g_autoptr(SrtProfilingTimer) timer = NULL;
  g_autoptr(SrtProfilingTimer) part_timer = NULL;
  guint n_egl_icds;
  guint n_vulkan_icds;
  const GList *icd_iter;
  gboolean all_libglx_from_provider = TRUE;
  gboolean all_libdrm_from_provider = TRUE;
  g_autoptr(GHashTable) drirc_data_in_provider = g_hash_table_new_full (g_str_hash,
                                                                        g_str_equal,
                                                                        g_free, NULL);
  g_autoptr(GHashTable) libdrm_data_in_provider = g_hash_table_new_full (g_str_hash,
                                                                         g_str_equal,
                                                                         g_free, NULL);
  g_autoptr(GHashTable) gconv_in_provider = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                                     g_free, NULL);
  g_autofree gchar *provider_in_container_namespace_guarded = NULL;

  g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE);
  g_return_val_if_fail (self->provider != NULL, FALSE);
  g_return_val_if_fail (bwrap != NULL || self->mutable_sysroot != NULL, FALSE);
  g_return_val_if_fail (bwrap == NULL || !pv_bwrap_was_finished (bwrap), FALSE);
  g_return_val_if_fail (container_env != NULL, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  timer = _srt_profiling_start ("Using graphics stack from %s",
                                self->provider->path_in_current_ns);

  if (g_str_has_suffix (self->provider->path_in_container_ns, "/"))
    provider_in_container_namespace_guarded =
      g_strdup (self->provider->path_in_container_ns);
  else
    provider_in_container_namespace_guarded =
      g_strdup_printf ("%s/", self->provider->path_in_container_ns);

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

  if (self->flags & PV_RUNTIME_FLAGS_SINGLE_THREAD)
    system_info = pv_graphics_provider_create_system_info (self->provider);
  else
    system_info = g_object_ref (enumeration_thread_join (&self->indep_thread));

  part_timer = _srt_profiling_start ("Enumerating EGL ICDs");
  g_debug ("Enumerating EGL ICDs on provider 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_info ("Failed to load EGL ICD #%" G_GSIZE_FORMAT  " from %s: %s",
                  j, path, local_error->message);
          g_clear_error (&local_error);
          continue;
        }

      g_info ("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_clear_pointer (&part_timer, _srt_profiling_end);

  part_timer = _srt_profiling_start ("Enumerating Vulkan ICDs");
  g_debug ("Enumerating Vulkan ICDs on provider 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_info ("Failed to load Vulkan ICD #%" G_GSIZE_FORMAT " from %s: %s",
                  j, path, local_error->message);
          g_clear_error (&local_error);
          continue;
        }

      g_info ("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));
    }

  g_clear_pointer (&part_timer, _srt_profiling_end);

  if (self->flags & PV_RUNTIME_FLAGS_IMPORT_VULKAN_LAYERS)
    {
      part_timer = _srt_profiling_start ("Enumerating Vulkan layers");
      g_debug ("Enumerating Vulkan explicit layers on provider system...");
      vulkan_explicit_layers = srt_system_info_list_explicit_vulkan_layers (system_info);

      vulkan_exp_layer_details = g_ptr_array_new_full (g_list_length (vulkan_explicit_layers),
                                                       (GDestroyNotify) G_CALLBACK (icd_details_free));

      for (icd_iter = vulkan_explicit_layers, j = 0;
          icd_iter != NULL;
          icd_iter = icd_iter->next, j++)
        {
          SrtVulkanLayer *layer = icd_iter->data;
          const gchar *path = srt_vulkan_layer_get_json_path (layer);
          GError *local_error = NULL;

          if (!srt_vulkan_layer_check_error (layer, &local_error))
            {
              g_info ("Failed to load Vulkan explicit layer #%" G_GSIZE_FORMAT
                      " from %s: %s", j, path, local_error->message);
              g_clear_error (&local_error);
              continue;
            }

          g_info ("Vulkan explicit layer #%" G_GSIZE_FORMAT " at %s: %s",
                  j, path, srt_vulkan_layer_get_library_path (layer));

          g_ptr_array_add (vulkan_exp_layer_details, icd_details_new (layer));
        }

      g_debug ("Enumerating Vulkan implicit layers on provider system...");
      vulkan_implicit_layers = srt_system_info_list_implicit_vulkan_layers (system_info);

      vulkan_imp_layer_details = g_ptr_array_new_full (g_list_length (vulkan_implicit_layers),
                                                       (GDestroyNotify) G_CALLBACK (icd_details_free));

      for (icd_iter = vulkan_implicit_layers, j = 0;
          icd_iter != NULL;
          icd_iter = icd_iter->next, j++)
        {
          SrtVulkanLayer *layer = icd_iter->data;
          const gchar *path = srt_vulkan_layer_get_json_path (layer);
          const gchar *library_path = NULL;
          GError *local_error = NULL;

          if (!srt_vulkan_layer_check_error (layer, &local_error))
            {
              g_info ("Failed to load Vulkan implicit layer #%" G_GSIZE_FORMAT
                      " from %s: %s", j, path, local_error->message);
              g_clear_error (&local_error);
              continue;
            }

          library_path = srt_vulkan_layer_get_library_path (layer);

          g_info ("Vulkan implicit layer #%" G_GSIZE_FORMAT " at %s: %s",
                  j, path, library_path != NULL ? library_path : "meta-layer");

          g_ptr_array_add (vulkan_imp_layer_details, icd_details_new (layer));
        }

      g_clear_pointer (&part_timer, _srt_profiling_end);
    }

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

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

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

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

      if (runtime_architecture_init (arch, self))
        {
          g_autoptr(GPtrArray) dirs = NULL;
          g_autofree gchar *this_dri_path_in_container = g_build_filename (arch->libdir_in_container,
                                                                           "dri", NULL);
          g_autofree gchar *libc = NULL;
          /* Can either be relative to the sysroot, or absolute */
          g_autofree gchar *ld_so_in_runtime = NULL;
          g_autofree gchar *libdrm = NULL;
          g_autofree gchar *libdrm_amdgpu = NULL;
          g_autofree gchar *libglx_mesa = NULL;
          g_autofree gchar *platform_token = NULL;
          g_autoptr(GPtrArray) patterns = NULL;
          SrtSystemInfo *arch_system_info;

          if (!pv_runtime_get_ld_so (self, arch, &ld_so_in_runtime, error))
            return FALSE;

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

          /* Reserve a size of 128 to avoid frequent reallocation due to the
           * expected high number of patterns that will be added to the array. */
          patterns = g_ptr_array_new_full (128, g_free);

          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);
          pv_search_path_append (va_api_path, this_dri_path_in_container);

          g_mkdir_with_parents (arch->libdir_in_current_namespace, 0755);
          g_mkdir_with_parents (arch->aliases_in_current_namespace, 0755);

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

          collect_graphics_libraries_patterns (patterns);

          if (!collect_egl_drivers (self, arch, egl_icd_details, patterns,
                                    error))
            return FALSE;

          if (!collect_vulkan_icds (self, arch, vulkan_icd_details,
                                    patterns, error))
            return FALSE;

          if (self->flags & PV_RUNTIME_FLAGS_IMPORT_VULKAN_LAYERS)
            {
              g_debug ("Collecting Vulkan explicit layers from provider...");
              if (!collect_vulkan_layers (self, vulkan_exp_layer_details,
                                          patterns, arch, "vulkan_exp_layer", error))
                return FALSE;

              g_debug ("Collecting Vulkan implicit layers from provider...");
              if (!collect_vulkan_layers (self, vulkan_imp_layer_details,
                                          patterns, arch, "vulkan_imp_layer", error))
                return FALSE;
            }

          if (self->flags & PV_RUNTIME_FLAGS_SINGLE_THREAD)
            arch_system_info = system_info;
          else
            arch_system_info = enumeration_thread_join (&self->arch_threads[i]);

          if (!collect_vdpau_drivers (self, arch_system_info, arch, patterns, error))
            return FALSE;

          if (!collect_dri_drivers (self, arch_system_info, arch, patterns,
                                    dri_path, error))
            return FALSE;

          if (!collect_va_api_drivers (self, arch_system_info, arch, patterns,
                                       va_api_path, error))
            return FALSE;

          if (!pv_runtime_capture_libraries (self, arch,
                                             arch->libdir_in_current_namespace,
                                             patterns, error))
            return FALSE;

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

          /* If we are going to use the provider's libc6 (likely)
           * then we have to use its ld.so too. */
          if (g_file_test (libc, G_FILE_TEST_IS_SYMLINK))
            {
              if (!pv_runtime_collect_libc_family (self, arch, bwrap,
                                                   libc, ld_so_in_runtime,
                                                   provider_in_container_namespace_guarded,
                                                   gconv_in_provider,
                                                   error))
                return FALSE;

              self->any_libc_from_provider = TRUE;
            }
          else
            {
              self->all_libc_from_provider = FALSE;
            }

          libdrm = g_build_filename (arch->libdir_in_current_namespace,
                                     "libdrm.so.2", NULL);
          libdrm_amdgpu = g_build_filename (arch->libdir_in_current_namespace,
                                            "libdrm_amdgpu.so.1", NULL);

          /* If we have libdrm_amdgpu.so.1 in overrides we also want to mount
           * ${prefix}/share/libdrm from the provider. ${prefix} is derived from
           * the absolute path of libdrm_amdgpu.so.1 */
          if (g_file_test (libdrm_amdgpu, G_FILE_TEST_IS_SYMLINK))
            {
              pv_runtime_collect_lib_data (self, arch, "libdrm", libdrm_amdgpu,
                                           provider_in_container_namespace_guarded,
                                           libdrm_data_in_provider);
            }
          /* As a fallback we also try libdrm.so.2 because libdrm_amdgpu.so.1
           * might not be available in all providers.
           * It's important to check for libdrm_amdgpu.so.1 first, because
           * the freedesktop.org GL runtime doesn't provide libdrm.so.2, and if
           * we check for it first we would end up looking for the "libdrm"
           * directory in the wrong path */
          else if (g_file_test (libdrm, G_FILE_TEST_IS_SYMLINK))
            {
              pv_runtime_collect_lib_data (self, arch, "libdrm", libdrm,
                                           provider_in_container_namespace_guarded,
                                           libdrm_data_in_provider);
            }
          else
            {
              /* For at least a single architecture, libdrm is newer in the container */
              all_libdrm_from_provider = FALSE;
            }

          libglx_mesa = g_build_filename (arch->libdir_in_current_namespace, "libGLX_mesa.so.0", NULL);

          /* If we have libGLX_mesa.so.0 in overrides we also want to mount
           * ${prefix}/share/drirc.d from the provider. ${prefix} is derived from
           * the absolute path of libGLX_mesa.so.0 */
          if (g_file_test (libglx_mesa, G_FILE_TEST_IS_SYMLINK))
            {
              pv_runtime_collect_lib_data (self, arch, "drirc.d", libglx_mesa,
                                           provider_in_container_namespace_guarded,
                                           drirc_data_in_provider);
            }
          else
            {
              /* For at least a single architecture, libGLX_mesa is newer in the container */
              all_libglx_from_provider = FALSE;
            }

          dirs = multiarch_details_get_libdirs (arch->details,
                                                MULTIARCH_LIBDIRS_FLAGS_NONE);

          for (j = 0; j < dirs->len; j++)
            {
              if (!collect_s2tc (self, arch,
                                 g_ptr_array_index (dirs, j),
                                 error))
                return FALSE;
            }

          /* Unfortunately VDPAU_DRIVER_PATH can hold just a single path, so we can't
           * easily list both x86_64 and i386 paths. As a workaround we set
           * VDPAU_DRIVER_PATH based on ${PLATFORM} - but each of our
           * supported ABIs can have multiple values for ${PLATFORM}, so we
           * need to create symlinks. Try to avoid making use of this,
           * because it's fragile (a new glibc version can introduce
           * new platform strings), but for some things like VDPAU it's our
           * only choice. */
          for (j = 0; j < G_N_ELEMENTS (arch->details->platforms); j++)
            {
              g_autofree gchar *platform_link = NULL;

              if (arch->details->platforms[j] == NULL)
                break;

              platform_link = g_strdup_printf ("%s/lib/platform-%s",
                                               self->overrides,
                                               arch->details->platforms[j]);

              if (symlink (arch->details->tuple, platform_link) != 0)
                return glnx_throw_errno_prefix (error,
                                                "Unable to create symlink %s -> %s",
                                                platform_link, arch->details->tuple);
            }

          platform_token = srt_system_info_dup_libdl_platform (arch_system_info,
                                                               multiarch_tuples[i],
                                                               &local_error);
          if (platform_token == NULL)
            {
              /* This is not a critical error, try to continue */
              g_warning ("The dynamic linker expansion of \"$PLATFORM\" is not what we "
                         "expected, VDPAU drivers might not work: %s", local_error->message);
              g_clear_error (&local_error);
            }

          if (!pv_runtime_create_aliases (self, arch, &local_error))
            {
              /* This is not a critical error, try to continue */
              g_warning ("Unable to create library aliases: %s",
                         local_error->message);
              g_clear_error (&local_error);
              continue;
            }

          /* Make sure we do this last, so that we have really copied
           * everything from the provider that we are going to */
          if (self->mutable_sysroot != NULL &&
              !pv_runtime_remove_overridden_libraries (self, arch, error))
            return FALSE;
        }

      g_clear_pointer (&part_timer, _srt_profiling_end);
    }

  part_timer = _srt_profiling_start ("Finishing graphics stack capture");

  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 graphics provider and the container (tried: %s)",
                   archs->str);
      g_string_free (archs, TRUE);
      return FALSE;
    }

  if (!pv_runtime_finish_libc_family (self, bwrap, gconv_in_provider, error))
    return FALSE;

  if (!pv_runtime_finish_lib_data (self, bwrap, "libdrm", "libdrm",
                                   all_libdrm_from_provider,
                                   libdrm_data_in_provider, error))
    return FALSE;

  if (!pv_runtime_finish_lib_data (self, bwrap, "drirc.d", "libGLX_mesa.so.0",
                                   all_libglx_from_provider,
                                   drirc_data_in_provider, error))
    return FALSE;

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

  if (!setup_each_json_manifest (self, bwrap, "glvnd/egl_vendor.d",
                                 egl_icd_details, egl_path, error))
    return FALSE;

  g_debug ("Setting up Vulkan ICD JSON...");
  if (!setup_each_json_manifest (self, bwrap, "vulkan/icd.d",
                                 vulkan_icd_details, vulkan_path, error))
    return FALSE;

  if (self->flags & PV_RUNTIME_FLAGS_IMPORT_VULKAN_LAYERS)
    {
      g_debug ("Setting up Vulkan explicit layer JSON...");
      if (!setup_each_json_manifest (self, bwrap, "vulkan/explicit_layer.d",
                                     vulkan_exp_layer_details,
                                     vulkan_exp_layer_path, error))
        return FALSE;

      g_debug ("Setting up Vulkan implicit layer JSON...");
      if (!setup_each_json_manifest (self, bwrap, "vulkan/implicit_layer.d",
                                     vulkan_imp_layer_details,
                                     vulkan_imp_layer_path, error))
        return FALSE;
    }

  if (dri_path->len != 0)
    pv_environ_lock_env (container_env, "LIBGL_DRIVERS_PATH", dri_path->str);
  else
    pv_environ_lock_env (container_env, "LIBGL_DRIVERS_PATH", NULL);

  if (egl_path->len != 0)
    pv_environ_lock_env (container_env, "__EGL_VENDOR_LIBRARY_FILENAMES",
                         egl_path->str);
  else
    pv_environ_lock_env (container_env, "__EGL_VENDOR_LIBRARY_FILENAMES",
                         NULL);

  pv_environ_lock_env (container_env, "__EGL_VENDOR_LIBRARY_DIRS", NULL);

  if (vulkan_path->len != 0)
    pv_environ_lock_env (container_env, "VK_ICD_FILENAMES", vulkan_path->str);
  else
    pv_environ_lock_env (container_env, "VK_ICD_FILENAMES", NULL);

  if (self->flags & PV_RUNTIME_FLAGS_IMPORT_VULKAN_LAYERS)
    {
      /* Implicit layers are not affected by "VK_LAYER_PATH". So instead of using
       * this environment variable, we prepend our "/overrides/share" to
       * "XDG_DATA_DIRS" to cover any explicit and implicit layers that we may
       * have. */
      if (vulkan_exp_layer_path->len != 0 || vulkan_imp_layer_path->len != 0)
        {
          const gchar *xdg_data_dirs;
          g_autofree gchar *prepended_data_dirs = NULL;
          g_autofree gchar *override_share = NULL;

          xdg_data_dirs = g_environ_getenv (self->original_environ, "XDG_DATA_DIRS");
          override_share = g_build_filename (self->overrides_in_container, "share", NULL);

          /* Reference:
           * https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html */
          if (xdg_data_dirs == NULL)
            xdg_data_dirs = "/usr/local/share:/usr/share";

          prepended_data_dirs = g_strdup_printf ("%s:%s", override_share, xdg_data_dirs);

          pv_environ_lock_env (container_env, "XDG_DATA_DIRS",
                               prepended_data_dirs);
        }
      pv_environ_lock_env (container_env, "VK_LAYER_PATH", NULL);
    }

  if (va_api_path->len != 0)
    pv_environ_lock_env (container_env, "LIBVA_DRIVERS_PATH",
                         va_api_path->str);
  else
    pv_environ_lock_env (container_env, "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-${PLATFORM}/vdpau" (which is a symlink that we
   * already created). */
  g_autofree gchar *vdpau_val = g_strdup_printf ("%s/lib/platform-${PLATFORM}/vdpau",
                                                 self->overrides_in_container);

  pv_environ_lock_env (container_env, "VDPAU_DRIVER_PATH", vdpau_val);
  return TRUE;
}

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

  g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE);
  g_return_val_if_fail ((exports == NULL) == (bwrap == NULL), FALSE);
  g_return_val_if_fail (bwrap == NULL || !pv_bwrap_was_finished (bwrap), FALSE);
  g_return_val_if_fail (bwrap != NULL || self->mutable_sysroot != NULL, FALSE);
  g_return_val_if_fail (container_env != NULL, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  if (self->flags & PV_RUNTIME_FLAGS_FLATPAK_SUBSANDBOX)
    {
      g_return_val_if_fail (exports == NULL, FALSE);
      g_return_val_if_fail (bwrap == NULL, FALSE);
    }
  else
    {
      g_return_val_if_fail (exports != NULL, FALSE);
      g_return_val_if_fail (bwrap != NULL, FALSE);
    }

  if (bwrap != NULL
      && !bind_runtime_base (self, bwrap, container_env, error))
    return FALSE;

  if (self->provider != NULL)
    {
      if (!pv_runtime_use_provider_graphics_stack (self, bwrap,
                                                   container_env,
                                                   error))
        return FALSE;
    }

  if (bwrap != NULL)
    bind_runtime_finish (self, exports, bwrap);

  pressure_vessel_prefix = g_path_get_dirname (self->tools_dir);

  /* Make sure pressure-vessel itself is visible there. */
  if (self->mutable_sysroot != NULL)
    {
      g_autofree gchar *dest = NULL;
      glnx_autofd int parent_dirfd = -1;

      parent_dirfd = _srt_resolve_in_sysroot (self->mutable_sysroot_fd,
                                              "/usr/lib/pressure-vessel",
                                              SRT_RESOLVE_FLAGS_MKDIR_P,
                                              NULL, error);

      if (parent_dirfd < 0)
        return FALSE;

      if (!glnx_shutil_rm_rf_at (parent_dirfd, "from-host", NULL, error))
        return FALSE;

      dest = glnx_fdrel_abspath (parent_dirfd, "from-host");

      if (!pv_cheap_tree_copy (pressure_vessel_prefix, dest,
                               PV_COPY_FLAGS_NONE, error))
        return FALSE;

      if (bwrap != NULL)
        flatpak_bwrap_add_args (bwrap,
                                "--symlink",
                                "/usr/lib/pressure-vessel/from-host",
                                "/run/pressure-vessel/pv-from-host",
                                NULL);

      self->adverb_in_container = "/usr/lib/pressure-vessel/from-host/bin/pressure-vessel-adverb";
    }
  else
    {
      g_autofree gchar *pressure_vessel_prefix_in_host_namespace =
        pv_current_namespace_path_to_host_path (pressure_vessel_prefix);

      g_assert (bwrap != NULL);

      flatpak_bwrap_add_args (bwrap,
                              "--ro-bind",
                              pressure_vessel_prefix_in_host_namespace,
                              "/run/pressure-vessel/pv-from-host",
                              NULL);
      self->adverb_in_container = "/run/pressure-vessel/pv-from-host/bin/pressure-vessel-adverb";
    }

  if ((self->flags & PV_RUNTIME_FLAGS_IMPORT_VULKAN_LAYERS)
      && exports != NULL)
    {
      /* We have added our imported Vulkan layers to the search path,
       * but we can't just remove ~/.local/share, etc. from the search
       * path without breaking unrelated users of the XDG basedirs spec,
       * such as .desktop files and icons. Mask any remaining Vulkan
       * layers by mounting empty directories over the top. */
      static const char * const layer_suffixes[] =
        {
          _SRT_GRAPHICS_EXPLICIT_VULKAN_LAYER_SUFFIX,
          _SRT_GRAPHICS_IMPLICIT_VULKAN_LAYER_SUFFIX,
        };
      gsize i;

      for (i = 0; i < G_N_ELEMENTS (layer_suffixes); i++)
        {
          g_auto(GStrv) search_path = NULL;
          const char *suffix = layer_suffixes[i];
          gsize j;

          search_path = _srt_graphics_get_vulkan_search_paths ("/",
                                                               self->original_environ,
                                                               multiarch_tuples,
                                                               suffix);

          for (j = 0; search_path != NULL && search_path[j] != NULL; j++)
            {
              const char *dir = search_path[j];

              /* We are mounting our own runtime over /usr anyway, so
               * ignore those */
              if (flatpak_has_path_prefix (dir, "/usr"))
                continue;

              /* Otherwise, if the directory exists, mask it */
              if (g_file_test (dir, G_FILE_TEST_IS_DIR))
                flatpak_exports_add_path_tmpfs (exports, dir);
            }
        }
    }

  if (self->is_scout)
    {
      const gchar *sdl_videodriver;

      /* Some games detect that they have been run outside the Steam Runtime
       * and try to re-run themselves via Steam. Trick them into thinking
       * they are in the LD_LIBRARY_PATH Steam Runtime.
       *
       * We do not do this for games developed against soldier, because
       * backwards compatibility is not a concern for game developers who
       * have specifically opted-in to using the newer runtime. */
      pv_environ_lock_env (container_env, "STEAM_RUNTIME", "/");

      /* Scout is configured without Wayland support. For this reason, if
       * the Wayland driver was forced via SDL_VIDEODRIVER, we expect that
       * every game will fail to launch. When we detect this situation we
       * unset SDL_VIDEODRIVER, so that the default x11 gets chosen instead */
      sdl_videodriver = g_environ_getenv (self->original_environ, "SDL_VIDEODRIVER");
      if (g_strcmp0 (sdl_videodriver, "wayland") == 0)
        pv_environ_lock_env (container_env, "SDL_VIDEODRIVER", NULL);
    }

  pv_runtime_set_search_paths (self, container_env);

  return TRUE;
}

void
pv_runtime_set_search_paths (PvRuntime *self,
                             PvEnviron *container_env)
{
  g_autoptr(GString) ld_library_path = g_string_new ("");
  g_autofree char *terminfo_path = NULL;
  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;
      g_autofree gchar *aliases = NULL;

      ld_path = g_build_filename (self->overrides_in_container, "lib",
                                  multiarch_tuples[i], NULL);

      aliases = g_build_filename (self->overrides_in_container, "lib",
                                  multiarch_tuples[i], "aliases", NULL);

      pv_search_path_append (ld_library_path, ld_path);
      pv_search_path_append (ld_library_path, aliases);
    }

  if (self->flags & PV_RUNTIME_FLAGS_SEARCH_CWD)
    {
      const gchar *install_path = g_environ_getenv (self->original_environ,
                                                    "STEAM_COMPAT_INSTALL_PATH");
      if (install_path != NULL)
        pv_search_path_append (ld_library_path, install_path);
      else
        g_debug ("The environment variable \"STEAM_COMPAT_INSTALL_PATH\" is not set, "
                 "we can't append the game install path to LD_LIBRARY_PATH.");
    }

  /* If the runtime is Debian-based, make sure we search where ncurses-base
   * puts terminfo, even if we're using a non-Debian-based libtinfo.so.6. */
  terminfo_path = g_build_filename (self->source_files, "lib", "terminfo",
                                    NULL);

  if (g_file_test (terminfo_path, G_FILE_TEST_IS_DIR))
    pv_environ_lock_env (container_env, "TERMINFO_DIRS", "/lib/terminfo");

  /* The PATH from outside the container doesn't really make sense inside the
   * container: in principle the layout could be totally different. */
  pv_environ_lock_env (container_env, "PATH", "/usr/bin:/bin");
  pv_environ_lock_env (container_env, "LD_LIBRARY_PATH", ld_library_path->str);
}

gboolean
pv_runtime_use_shared_sockets (PvRuntime *self,
                               FlatpakBwrap *bwrap,
                               PvEnviron *container_env,
                               GError **error)
{
  if (pv_environ_getenv (container_env, "PULSE_SERVER") != NULL
      || self->is_flatpak_env)
    {
      /* Make the PulseAudio driver the default.
       * We do this unconditionally when we are under Flatpak for parity
       * with the freedesktop.org Platform. */
      const gchar *alsa_config = "pcm.!default pulse\n"
                                 "ctl.!default pulse\n";

      if (bwrap != NULL)
        {
          flatpak_bwrap_add_args_data (bwrap, "asound.conf",
                                       alsa_config, -1,
                                       "/etc/asound.conf",
                                       NULL);
        }
      else if (self->mutable_sysroot_fd >= 0)
        {
          /* In a Flatpak sub-sandbox, we can rely on the fact that
           * Flatpak will mount each item in our copy of the runtime's
           * usr/etc/ into /etc, including some that we would normally
           * skip. */
          if (!glnx_file_replace_contents_at (self->mutable_sysroot_fd,
                                              "usr/etc/asound.conf",
                                              (const guint8 *) alsa_config,
                                              strlen (alsa_config),
                                              GLNX_FILE_REPLACE_NODATASYNC,
                                              NULL, error))
            return FALSE;
        }
      else
        {
          g_warning ("Unable to configure libasound.so.2 to use PulseAudio");
        }
    }

  return TRUE;
}

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

const char *
pv_runtime_get_modified_usr (PvRuntime *self)
{
  g_return_val_if_fail (PV_IS_RUNTIME (self), NULL);
  g_return_val_if_fail (self->mutable_sysroot != NULL, NULL);
  return self->runtime_usr;
}

const char *
pv_runtime_get_modified_app (PvRuntime *self)
{
  g_return_val_if_fail (PV_IS_RUNTIME (self), NULL);
  g_return_val_if_fail (self->mutable_sysroot != NULL, NULL);

  if (g_file_test (self->runtime_app, G_FILE_TEST_IS_DIR))
    return self->runtime_app;
  else
    return NULL;
}