/*
 * Copyright © 2019-2021 Collabora Ltd.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <stdlib.h>
#include <unistd.h>

#include <glib.h>
#include <glib/gstdio.h>

#include "steam-runtime-tools/glib-backports-internal.h"
#include "steam-runtime-tools/utils-internal.h"
#include "libglnx/libglnx.h"

#include "tests/test-utils.h"

#include "supported-architectures.h"
#include "wrap-setup.h"
#include "utils.h"

/* These match the first entry in PvMultiArchdetails.platforms,
 * which is the easiest realistic thing for a mock implementation of
 * srt_system_info_check_library() to use. */
#define MOCK_PLATFORM_32 "i686"
#define MOCK_PLATFORM_64 "xeon_phi"

/* These match Debian multiarch, which is as good a thing as any for
 * a mock implementation of srt_system_info_check_library() to use. */
#define MOCK_LIB_32 "lib/" SRT_ABI_I386
#define MOCK_LIB_64 "lib/" SRT_ABI_X86_64

typedef struct
{
  TestsOpenFdSet old_fds;
  gchar *tmpdir;
  gchar *mock_host;
  gchar *mock_runtime;
  gchar *var;
  GStrv env;
} Fixture;

typedef struct
{
  int unused;
} Config;

static int
open_or_die (const char *path,
             int flags,
             int mode)
{
  glnx_autofd int fd = open (path, flags | O_CLOEXEC, mode);

  if (fd >= 0)
    return glnx_steal_fd (&fd);
  else
    g_error ("open(%s, 0x%x): %s", path, flags, g_strerror (errno));
}

static FlatpakExports *
fixture_create_exports (Fixture *f)
{
  g_autoptr(FlatpakExports) exports = flatpak_exports_new ();
  glnx_autofd int fd = open_or_die (f->mock_host, O_RDONLY | O_DIRECTORY, 0755);

  flatpak_exports_take_host_fd (exports, glnx_steal_fd (&fd));
  return g_steal_pointer (&exports);
}

static PvRuntime *
fixture_create_runtime (Fixture *f,
                        PvRuntimeFlags flags)
{
  g_autoptr(GError) local_error = NULL;
  g_autoptr(PvGraphicsProvider) graphics_provider = NULL;
  g_autoptr(PvRuntime) runtime = NULL;
  const char *gfx_in_container;

  if (flags & PV_RUNTIME_FLAGS_FLATPAK_SUBSANDBOX)
    gfx_in_container = "/run/parent";
  else
    gfx_in_container = "/run/host";

  graphics_provider = pv_graphics_provider_new ("/", gfx_in_container,
                                                &local_error);
  g_assert_no_error (local_error);
  g_assert_nonnull (graphics_provider);

  runtime = pv_runtime_new (f->mock_runtime,
                            "mock_platform_1.0",
                            f->var,
                            NULL,
                            graphics_provider,
                            environ,
                            (flags
                             | PV_RUNTIME_FLAGS_VERBOSE
                             | PV_RUNTIME_FLAGS_SINGLE_THREAD),
                            &local_error);
  g_assert_no_error (local_error);
  g_assert_nonnull (runtime);
  return g_steal_pointer (&runtime);
}

static void
setup (Fixture *f,
       gconstpointer context)
{
  static const char * const touch[] =
  {
    "app/lib/libpreloadA.so",
    "future/libs-post2038/.exists",
    "home/me/libpreloadH.so",
    "lib/libpreload-rootfs.so",
    "opt/" MOCK_LIB_32 "/libpreloadL.so",
    "opt/" MOCK_LIB_64 "/libpreloadL.so",
    "overlay/libs/usr/lib/libpreloadO.so",
    "platform/plat-" MOCK_PLATFORM_32 "/libpreloadP.so",
    "platform/plat-" MOCK_PLATFORM_64 "/libpreloadP.so",
    "steam/lib/gameoverlayrenderer.so",
    "usr/lib/libpreloadU.so",
    "usr/local/lib/libgtk3-nocsd.so.0",
    "in-root-plat-" MOCK_PLATFORM_32 "-only-32-bit.so",
  };
  g_autoptr(GError) local_error = NULL;
  gsize i;

  f->old_fds = tests_check_fd_leaks_enter ();

  f->tmpdir = g_dir_make_tmp ("pressure-vessel-tests.XXXXXX", &local_error);
  g_assert_no_error (local_error);
  f->mock_host = g_build_filename (f->tmpdir, "host", NULL);
  f->mock_runtime = g_build_filename (f->tmpdir, "runtime", NULL);
  f->var = g_build_filename (f->tmpdir, "var", NULL);
  g_assert_no_errno (g_mkdir (f->mock_host, 0755));
  g_assert_no_errno (g_mkdir (f->mock_runtime, 0755));
  g_assert_no_errno (g_mkdir (f->var, 0755));

  for (i = 0; i < G_N_ELEMENTS (touch); i++)
    {
      g_autofree char *dir = g_path_get_dirname (touch[i]);
      g_autofree char *full_dir = g_build_filename (f->mock_host, dir, NULL);
      g_autofree char *full_path = g_build_filename (f->mock_host, touch[i], NULL);

      g_test_message ("Creating %s", full_dir);
      g_assert_no_errno (g_mkdir_with_parents (full_dir, 0755));
      g_test_message ("Creating %s", full_path);
      g_file_set_contents (full_path, "", 0, &local_error);
      g_assert_no_error (local_error);
    }

  f->env = g_get_environ ();
  f->env = g_environ_setenv (f->env, "STEAM_COMPAT_CLIENT_INSTALL_PATH",
                             "/steam", TRUE);
}

static void
teardown (Fixture *f,
          gconstpointer context)
{
  g_autoptr(GError) local_error = NULL;

  if (f->tmpdir != NULL)
    {
      glnx_shutil_rm_rf_at (-1, f->tmpdir, NULL, &local_error);
      g_assert_no_error (local_error);
    }

  tests_check_fd_leaks_leave (f->old_fds);
  g_clear_pointer (&f->mock_host, g_free);
  g_clear_pointer (&f->mock_runtime, g_free);
  g_clear_pointer (&f->tmpdir, g_free);
  g_clear_pointer (&f->var, g_free);
  g_clear_pointer (&f->env, g_strfreev);
}

static void
populate_ld_preload (Fixture *f,
                     GPtrArray *argv,
                     PvAppendPreloadFlags flags,
                     PvRuntime *runtime,
                     FlatpakExports *exports)
{
  static const struct
  {
    const char *string;
    const char *warning;
  } preloads[] =
  {
    { "", .warning = "Ignoring invalid loadable module \"\"" },
    { "", .warning = "Ignoring invalid loadable module \"\"" },
    { "/app/lib/libpreloadA.so" },
    { "/platform/plat-$PLATFORM/libpreloadP.so" },
    { "/opt/${LIB}/libpreloadL.so" },
    { "/lib/libpreload-rootfs.so" },
    { "/usr/lib/libpreloadU.so" },
    { "/home/me/libpreloadH.so" },
    { "/steam/lib/gameoverlayrenderer.so" },
    { "/overlay/libs/${ORIGIN}/../lib/libpreloadO.so" },
    { "/future/libs-$FUTURE/libpreloadF.so" },
    { "/in-root-plat-${PLATFORM}-only-32-bit.so" },
    { "/in-root-${FUTURE}.so" },
    { "./${RELATIVE}.so" },
    { "./relative.so" },
    { "libfakeroot.so" },
    { "libpthread.so.0" },
    {
      "/usr/local/lib/libgtk3-nocsd.so.0",
      .warning = "Disabling gtk3-nocsd LD_PRELOAD: it is known to cause crashes.",
    },
    { "", .warning = "Ignoring invalid loadable module \"\"" },
  };
  gsize i;

  for (i = 0; i < G_N_ELEMENTS (preloads); i++)
    {
      GLogLevelFlags old_fatal_mask = G_LOG_FATAL_MASK;

      /* We expect a warning for libgtk3-nocsd.so.0, but the test framework
       * makes warnings and critical warnings fatal, in addition to the
       * usual fatal errors. Temporarily relax that to just critical
       * warnings and fatal errors. */
      if (preloads[i].warning != NULL)
        {
          old_fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK | G_LOG_LEVEL_CRITICAL);
#if GLIB_CHECK_VERSION(2, 34, 0)
          /* We can't check for the message during unit testing when
           * compiling with GLib 2.32 from scout, but we can check for it
           * in developer builds against a newer GLib. Note that this
           * assumes pressure-vessel doesn't define G_LOG_USE_STRUCTURED,
           * but that's a GLib 2.50 feature which we are unlikely to use
           * while we are still building against scout. */
          g_test_expect_message ("pressure-vessel",
                                 G_LOG_LEVEL_WARNING,
                                 preloads[i].warning);
#endif
        }

      pv_wrap_append_preload (argv,
                              "LD_PRELOAD",
                              "--ld-preload",
                              preloads[i].string,
                              f->env,
                              flags | PV_APPEND_PRELOAD_FLAGS_IN_UNIT_TESTS,
                              runtime,
                              exports);

      /* If we modified the fatal mask, put back the old value. */
      if (preloads[i].warning != NULL)
        {
#if GLIB_CHECK_VERSION(2, 34, 0)
          g_test_assert_expected_messages ();
#endif
          g_log_set_always_fatal (old_fatal_mask);
        }
    }

  for (i = 0; i < argv->len; i++)
    g_test_message ("argv[%" G_GSIZE_FORMAT "]: %s",
                    i, (const char *) g_ptr_array_index (argv, i));

  g_test_message ("argv->len: %" G_GSIZE_FORMAT, i);
}

static void
test_remap_ld_preload (Fixture *f,
                       gconstpointer context)
{
  g_autoptr(FlatpakExports) exports = fixture_create_exports (f);
  g_autoptr(GPtrArray) argv = g_ptr_array_new_with_free_func (g_free);
  g_autoptr(PvRuntime) runtime = fixture_create_runtime (f, PV_RUNTIME_FLAGS_NONE);
  gsize i;

  populate_ld_preload (f, argv, PV_APPEND_PRELOAD_FLAGS_NONE, runtime, exports);

  i = 0;

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/app/lib/libpreloadA.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/platform/plat-"
                    MOCK_PLATFORM_64
                    "/libpreloadP.so:abi="
                    SRT_ABI_X86_64));

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/platform/plat-"
                    MOCK_PLATFORM_32
                    "/libpreloadP.so:abi="
                    SRT_ABI_I386));

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/opt/"
                    MOCK_LIB_64
                    "/libpreloadL.so:abi="
                    SRT_ABI_X86_64));

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/opt/"
                    MOCK_LIB_32
                    "/libpreloadL.so:abi="
                    SRT_ABI_I386));

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/run/host/lib/libpreload-rootfs.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/run/host/usr/lib/libpreloadU.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/home/me/libpreloadH.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/steam/lib/gameoverlayrenderer.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/overlay/libs/${ORIGIN}/../lib/libpreloadO.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/future/libs-$FUTURE/libpreloadF.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/in-root-plat-"
                    MOCK_PLATFORM_32
                    "-only-32-bit.so:abi="
                    SRT_ABI_I386));

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/in-root-${FUTURE}.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=./${RELATIVE}.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=./relative.so");

  /* Our mock implementation of pv_runtime_has_library() behaves as though
   * libfakeroot is not in the runtime or graphics stack provider, only
   * the current namespace */
  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/path/to/"
                    MOCK_LIB_64
                    "/libfakeroot.so:abi="
                    SRT_ABI_X86_64));
  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/path/to/"
                    MOCK_LIB_32
                    "/libfakeroot.so:abi="
                    SRT_ABI_I386));

  /* Our mock implementation of pv_runtime_has_library() behaves as though
   * libpthread.so.0 *is* in the runtime, as we would expect */
  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=libpthread.so.0");

  g_assert_cmpuint (argv->len, ==, i);

  /* FlatpakExports never exports /app */
  g_assert_false (flatpak_exports_path_is_visible (exports, "/app"));
  g_assert_false (flatpak_exports_path_is_visible (exports, "/app/lib"));
  g_assert_false (flatpak_exports_path_is_visible (exports, "/app/lib/libpreloadA.so"));

  /* We don't always export /home etc. so we have to explicitly export
   * this one */
  g_assert_false (flatpak_exports_path_is_visible (exports, "/home"));
  g_assert_false (flatpak_exports_path_is_visible (exports, "/home/me"));
  g_assert_true (flatpak_exports_path_is_visible (exports, "/home/me/libpreloadH.so"));

  /* We don't always export /opt, so we have to explicitly export
   * these. */
  g_assert_false (flatpak_exports_path_is_visible (exports, "/opt"));
  g_assert_false (flatpak_exports_path_is_visible (exports, "/opt/lib"));
  g_assert_true (flatpak_exports_path_is_visible (exports, "/opt/" MOCK_LIB_32 "/libpreloadL.so"));
  g_assert_true (flatpak_exports_path_is_visible (exports, "/opt/" MOCK_LIB_64 "/libpreloadL.so"));

  /* We don't always export /platform, so we have to explicitly export
   * these. */
  g_assert_false (flatpak_exports_path_is_visible (exports, "/platform"));
  g_assert_true (flatpak_exports_path_is_visible (exports, "/platform/plat-" MOCK_PLATFORM_32 "/libpreloadP.so"));
  g_assert_true (flatpak_exports_path_is_visible (exports, "/platform/plat-" MOCK_PLATFORM_64 "/libpreloadP.so"));

  /* FlatpakExports never exports /lib as /lib */
  g_assert_false (flatpak_exports_path_is_visible (exports, "/lib"));
  g_assert_false (flatpak_exports_path_is_visible (exports, "/lib/libpreload-rootfs.so"));

  /* FlatpakExports never exports /usr as /usr */
  g_assert_false (flatpak_exports_path_is_visible (exports, "/usr"));
  g_assert_false (flatpak_exports_path_is_visible (exports, "/usr/lib"));
  g_assert_false (flatpak_exports_path_is_visible (exports, "/usr/lib/libpreloadU.so"));

  /* We assume STEAM_COMPAT_CLIENT_INSTALL_PATH is dealt with separately */
  g_assert_false (flatpak_exports_path_is_visible (exports, "/steam"));
  g_assert_false (flatpak_exports_path_is_visible (exports, "/steam/lib"));
  g_assert_false (flatpak_exports_path_is_visible (exports, "/steam/lib/gameoverlayrenderer.so"));

  /* We don't know what ${ORIGIN} will expand to, so we have to cut off at
   * /overlay/libs */
  g_assert_false (flatpak_exports_path_is_visible (exports, "/overlay"));
  g_assert_true (flatpak_exports_path_is_visible (exports, "/overlay/libs"));

  /* We don't know what ${FUTURE} will expand to, so we have to cut off at
   * /future */
  g_assert_true (flatpak_exports_path_is_visible (exports, "/future"));

  /* We don't export the entire root directory just because it has a
   * module in it */
  g_assert_true (flatpak_exports_path_is_visible (exports, "/"));
}

static void
test_remap_ld_preload_flatpak (Fixture *f,
                               gconstpointer context)
{
  g_autoptr(GPtrArray) argv = g_ptr_array_new_with_free_func (g_free);
  g_autoptr(PvRuntime) runtime = fixture_create_runtime (f, PV_RUNTIME_FLAGS_FLATPAK_SUBSANDBOX);
  gsize i;

  populate_ld_preload (f, argv, PV_APPEND_PRELOAD_FLAGS_FLATPAK_SUBSANDBOX,
                       runtime, NULL);

  i = 0;

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/run/parent/app/lib/libpreloadA.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/platform/plat-"
                    MOCK_PLATFORM_64
                    "/libpreloadP.so:abi="
                    SRT_ABI_X86_64));

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/platform/plat-"
                    MOCK_PLATFORM_32
                    "/libpreloadP.so:abi="
                    SRT_ABI_I386));

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/opt/"
                    MOCK_LIB_64
                    "/libpreloadL.so:abi="
                    SRT_ABI_X86_64));

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/opt/"
                    MOCK_LIB_32
                    "/libpreloadL.so:abi="
                    SRT_ABI_I386));

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/run/parent/lib/libpreload-rootfs.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/run/parent/usr/lib/libpreloadU.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/home/me/libpreloadH.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/steam/lib/gameoverlayrenderer.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/overlay/libs/${ORIGIN}/../lib/libpreloadO.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/future/libs-$FUTURE/libpreloadF.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/in-root-plat-"
                    MOCK_PLATFORM_32
                    "-only-32-bit.so:abi="
                    SRT_ABI_I386));

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/in-root-${FUTURE}.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=./${RELATIVE}.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=./relative.so");

  /* Our mock implementation of pv_runtime_has_library() behaves as though
   * libfakeroot is not in the runtime or graphics stack provider, only
   * the current namespace */
  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/path/to/"
                    MOCK_LIB_64
                    "/libfakeroot.so:abi="
                    SRT_ABI_X86_64));
  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/path/to/"
                    MOCK_LIB_32
                    "/libfakeroot.so:abi="
                    SRT_ABI_I386));

  /* Our mock implementation of pv_runtime_has_library() behaves as though
   * libpthread.so.0 *is* in the runtime, as we would expect */
  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=libpthread.so.0");

  g_assert_cmpuint (argv->len, ==, i);
}

/*
 * In addition to testing the rare case where there's no runtime,
 * this one also exercises PV_APPEND_PRELOAD_FLAGS_REMOVE_GAME_OVERLAY,
 * which is the implementation of --remove-game-overlay.
 */
static void
test_remap_ld_preload_no_runtime (Fixture *f,
                                  gconstpointer context)
{
  g_autoptr(GPtrArray) argv = g_ptr_array_new_with_free_func (g_free);
  g_autoptr(FlatpakExports) exports = fixture_create_exports (f);
  gsize i;

  populate_ld_preload (f, argv, PV_APPEND_PRELOAD_FLAGS_REMOVE_GAME_OVERLAY,
                       NULL, exports);

  i = 0;

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/app/lib/libpreloadA.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/platform/plat-"
                    MOCK_PLATFORM_64
                    "/libpreloadP.so:abi="
                    SRT_ABI_X86_64));

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/platform/plat-"
                    MOCK_PLATFORM_32
                    "/libpreloadP.so:abi="
                    SRT_ABI_I386));

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/opt/"
                    MOCK_LIB_64
                    "/libpreloadL.so:abi="
                    SRT_ABI_X86_64));

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/opt/"
                    MOCK_LIB_32
                    "/libpreloadL.so:abi="
                    SRT_ABI_I386));

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/lib/libpreload-rootfs.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/usr/lib/libpreloadU.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/home/me/libpreloadH.so");

  /* /steam/lib/gameoverlayrenderer.so is missing because we used the
   * REMOVE_GAME_OVERLAY flag */

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/overlay/libs/${ORIGIN}/../lib/libpreloadO.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/future/libs-$FUTURE/libpreloadF.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/in-root-plat-"
                    MOCK_PLATFORM_32
                    "-only-32-bit.so:abi="
                    SRT_ABI_I386));

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/in-root-${FUTURE}.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=./${RELATIVE}.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=./relative.so");

  /* Our mock implementation of pv_runtime_has_library() behaves as though
   * libfakeroot is not in the runtime or graphics stack provider, only
   * the current namespace */
  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/path/to/"
                    MOCK_LIB_64
                    "/libfakeroot.so:abi="
                    SRT_ABI_X86_64));
  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/path/to/"
                    MOCK_LIB_32
                    "/libfakeroot.so:abi="
                    SRT_ABI_I386));

  /* Our mock implementation of pv_runtime_has_library() behaves as though
   * libpthread.so.0 *is* in the runtime, as we would expect */
  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=libpthread.so.0");

  g_assert_cmpuint (argv->len, ==, i);

  /* FlatpakExports never exports /app */
  g_assert_false (flatpak_exports_path_is_visible (exports, "/app"));
  g_assert_false (flatpak_exports_path_is_visible (exports, "/app/lib"));
  g_assert_false (flatpak_exports_path_is_visible (exports, "/app/lib/libpreloadA.so"));

  /* We don't always export /home etc. so we have to explicitly export
   * this one */
  g_assert_false (flatpak_exports_path_is_visible (exports, "/home"));
  g_assert_false (flatpak_exports_path_is_visible (exports, "/home/me"));
  g_assert_true (flatpak_exports_path_is_visible (exports, "/home/me/libpreloadH.so"));

  /* We don't always export /opt, so we have to explicitly export
   * these. */
  g_assert_false (flatpak_exports_path_is_visible (exports, "/opt"));
  g_assert_false (flatpak_exports_path_is_visible (exports, "/opt/lib"));
  g_assert_true (flatpak_exports_path_is_visible (exports, "/opt/" MOCK_LIB_32 "/libpreloadL.so"));
  g_assert_true (flatpak_exports_path_is_visible (exports, "/opt/" MOCK_LIB_64 "/libpreloadL.so"));

  /* We don't always export /platform, so we have to explicitly export
   * these. */
  g_assert_false (flatpak_exports_path_is_visible (exports, "/platform"));
  g_assert_true (flatpak_exports_path_is_visible (exports, "/platform/plat-" MOCK_PLATFORM_32 "/libpreloadP.so"));
  g_assert_true (flatpak_exports_path_is_visible (exports, "/platform/plat-" MOCK_PLATFORM_64 "/libpreloadP.so"));

  /* FlatpakExports never exports /lib as /lib */
  g_assert_false (flatpak_exports_path_is_visible (exports, "/lib"));
  g_assert_false (flatpak_exports_path_is_visible (exports, "/lib/libpreload-rootfs.so"));

  /* FlatpakExports never exports /usr as /usr */
  g_assert_false (flatpak_exports_path_is_visible (exports, "/usr"));
  g_assert_false (flatpak_exports_path_is_visible (exports, "/usr/lib"));
  g_assert_false (flatpak_exports_path_is_visible (exports, "/usr/lib/libpreloadU.so"));

  /* We don't know what ${ORIGIN} will expand to, so we have to cut off at
   * /overlay/libs */
  g_assert_false (flatpak_exports_path_is_visible (exports, "/overlay"));
  g_assert_true (flatpak_exports_path_is_visible (exports, "/overlay/libs"));

  /* We don't know what ${FUTURE} will expand to, so we have to cut off at
   * /future */
  g_assert_true (flatpak_exports_path_is_visible (exports, "/future"));

  /* We don't export the entire root directory just because it has a
   * module in it */
  g_assert_true (flatpak_exports_path_is_visible (exports, "/"));
}

static void
test_remap_ld_preload_flatpak_no_runtime (Fixture *f,
                                          gconstpointer context)
{
  g_autoptr(GPtrArray) argv = g_ptr_array_new_with_free_func (g_free);
  gsize i;

  populate_ld_preload (f, argv, PV_APPEND_PRELOAD_FLAGS_FLATPAK_SUBSANDBOX,
                       NULL, NULL);

  i = 0;

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/app/lib/libpreloadA.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/platform/plat-"
                    MOCK_PLATFORM_64
                    "/libpreloadP.so:abi="
                    SRT_ABI_X86_64));

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/platform/plat-"
                    MOCK_PLATFORM_32
                    "/libpreloadP.so:abi="
                    SRT_ABI_I386));

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/opt/"
                    MOCK_LIB_64
                    "/libpreloadL.so:abi="
                    SRT_ABI_X86_64));

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/opt/"
                    MOCK_LIB_32
                    "/libpreloadL.so:abi="
                    SRT_ABI_I386));

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/lib/libpreload-rootfs.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/usr/lib/libpreloadU.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/home/me/libpreloadH.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/steam/lib/gameoverlayrenderer.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/overlay/libs/${ORIGIN}/../lib/libpreloadO.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/future/libs-$FUTURE/libpreloadF.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/in-root-plat-"
                    MOCK_PLATFORM_32
                    "-only-32-bit.so:abi="
                    SRT_ABI_I386));

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=/in-root-${FUTURE}.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=./${RELATIVE}.so");

  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=./relative.so");

  /* Our mock implementation of pv_runtime_has_library() behaves as though
   * libfakeroot is not in the runtime or graphics stack provider, only
   * the current namespace */
  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/path/to/"
                    MOCK_LIB_64
                    "/libfakeroot.so:abi="
                    SRT_ABI_X86_64));
  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==,
                   ("--ld-preload=/path/to/"
                    MOCK_LIB_32
                    "/libfakeroot.so:abi="
                    SRT_ABI_I386));

  /* Our mock implementation of pv_runtime_has_library() behaves as though
   * libpthread.so.0 *is* in the runtime, as we would expect */
  g_assert_cmpuint (argv->len, >, i);
  g_assert_cmpstr (g_ptr_array_index (argv, i++),
                   ==, "--ld-preload=libpthread.so.0");

  g_assert_cmpuint (argv->len, ==, i);
}

int
main (int argc,
      char **argv)
{
  _srt_setenv_disable_gio_modules ();

  g_test_init (&argc, &argv, NULL);
  g_test_add ("/remap-ld-preload", Fixture, NULL,
              setup, test_remap_ld_preload, teardown);
  g_test_add ("/remap-ld-preload-flatpak", Fixture, NULL,
              setup, test_remap_ld_preload_flatpak, teardown);
  g_test_add ("/remap-ld-preload-no-runtime", Fixture, NULL,
              setup, test_remap_ld_preload_no_runtime, teardown);
  g_test_add ("/remap-ld-preload-flatpak-no-runtime", Fixture, NULL,
              setup, test_remap_ld_preload_flatpak_no_runtime, teardown);

  return g_test_run ();
}