diff --git a/pressure-vessel/runtime.c b/pressure-vessel/runtime.c index 62fb34ef2fdd41f2b2c39249cd48802d8770d22f..443bd05f5280bb4d2f186257f13603d488f06027 100644 --- a/pressure-vessel/runtime.c +++ b/pressure-vessel/runtime.c @@ -5751,3 +5751,103 @@ pv_runtime_get_modified_app (PvRuntime *self) else return NULL; } + +/* + * Return %TRUE if the runtime provides @library, either directly or + * via the graphics-stack provider. + */ +gboolean +pv_runtime_has_library (PvRuntime *self, + const char *library) +{ + glnx_autofd int source_files_fd = -1; + gsize i; + + g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE); + g_return_val_if_fail (library != NULL, FALSE); + + g_debug ("Checking whether runtime has library: %s", library); + + for (i = 0; i < PV_N_SUPPORTED_ARCHITECTURES; i++) + { + const PvMultiarchDetails *details = &pv_multiarch_details[i]; + g_autoptr(GPtrArray) dirs = NULL; + gsize j; + + dirs = pv_multiarch_details_get_libdirs (details, + PV_MULTIARCH_LIBDIRS_FLAGS_NONE); + + for (j = 0; j < dirs->len; j++) + { + const char *libdir = g_ptr_array_index (dirs, j); + g_autofree gchar *path = g_build_filename (libdir, library, NULL); + + if (self->mutable_sysroot_fd >= 0) + { + glnx_autofd int fd = -1; + + fd = _srt_resolve_in_sysroot (self->mutable_sysroot_fd, path, + SRT_RESOLVE_FLAGS_NONE, + NULL, NULL); + + if (fd >= 0) + { + g_debug ("-> yes, ${mutable_sysroot}/%s", path); + return TRUE; + } + } + else + { + glnx_autofd int fd = -1; + + /* The runtime isn't necessarily a sysroot (it might just be a + * merged /usr) but in practice it'll be close enough: we look + * up each library in /usr/foo and /foo anyway. */ + if (source_files_fd < 0) + { + if (!glnx_opendirat (AT_FDCWD, self->source_files, TRUE, + &source_files_fd, NULL)) + continue; + } + + fd = _srt_resolve_in_sysroot (source_files_fd, path, + SRT_RESOLVE_FLAGS_NONE, + NULL, NULL); + + if (fd >= 0) + { + g_debug ("-> yes, ${source_files}/%s", path); + return TRUE; + } + } + + /* If the graphics stack provider is not the same as the current + * namespace (in practice this rarely/never happens), we also + * want to steer clear of libraries that only exist in the + * graphics stack provider. + * + * If the graphics stack provider *is* the current namespace, + * and the library doesn't exist in the container runtime, then + * it's OK to use libraries from it in LD_PRELOAD, because there + * is no other version that might have been meant. */ + if (self->provider != NULL + && g_strcmp0 (self->provider->path_in_current_ns, "/") != 0) + { + glnx_autofd int fd = -1; + + fd = _srt_resolve_in_sysroot (self->provider->fd, path, + SRT_RESOLVE_FLAGS_NONE, + NULL, NULL); + + if (fd >= 0) + { + g_debug ("-> yes, ${provider}/%s", path); + return TRUE; + } + } + } + } + + g_debug ("-> no"); + return FALSE; +} diff --git a/pressure-vessel/runtime.h b/pressure-vessel/runtime.h index dfb9f19f47ba299fe111763bdf8cdc04dd570985..6370deea3b411809feeed5bc61f6b7ca6004d93b 100644 --- a/pressure-vessel/runtime.h +++ b/pressure-vessel/runtime.h @@ -113,4 +113,7 @@ gboolean pv_runtime_use_shared_sockets (PvRuntime *self, PvEnviron *container_env, GError **error); +gboolean pv_runtime_has_library (PvRuntime *self, + const char *library); + G_DEFINE_AUTOPTR_CLEANUP_FUNC (PvRuntime, g_object_unref) diff --git a/pressure-vessel/utils.c b/pressure-vessel/utils.c index f1a5d4a0ba7051a0784207219222abc2cf645abe..322ca0ff62632909dece050f3323f2a4225455f9 100644 --- a/pressure-vessel/utils.c +++ b/pressure-vessel/utils.c @@ -994,57 +994,3 @@ pv_delete_dangling_symlink (int dirfd, debug_path, name, g_strerror (saved_errno)); } } - -/** - * pv_append_preload_module: - * @preload_modules: #PreloadModule objects array - * @n_preload_modules: Size of @preload_modules - * @variable: Variable where @value will be appended to - * @value: Value that needs to be appended - * - * Append @value to the #PreloadModule whose "variable" is the same as the - * provided @variable. - */ -void -pv_append_preload_module (PreloadModule preload_modules[], - gsize n_preload_modules, - const char *variable, - const char *value) -{ - gsize i; - - g_return_if_fail (preload_modules != NULL); - g_return_if_fail (n_preload_modules != 0); - g_return_if_fail (variable != NULL); - - for (i = 0; i < n_preload_modules; i++) - { - if (strcmp (variable, preload_modules[i].variable) == 0) - break; - } - - if (i == n_preload_modules) - g_return_if_reached (); - - if (preload_modules[i].values == NULL) - preload_modules[i].values = g_ptr_array_new_with_free_func (g_free); - - g_ptr_array_add (preload_modules[i].values, g_strdup (value)); -} - -/** - * pv_preload_modules_free: - * @preload_modules: #PreloadModule objects array - * @n_preload_modules: Size of @preload_modules - * - * Free the values stored in @preload_modules. - */ -void -pv_preload_modules_free (PreloadModule preload_modules[], - gsize n_preload_modules) -{ - gsize i; - - for (i = 0; i < n_preload_modules; i++) - g_clear_pointer (&preload_modules[i].values, g_ptr_array_unref); -} diff --git a/pressure-vessel/utils.h b/pressure-vessel/utils.h index fb92733a0f7e1682862840c07588120f46db1368..97e1a0a8d3faafa034df6ee0d625af869f5b8875 100644 --- a/pressure-vessel/utils.h +++ b/pressure-vessel/utils.h @@ -44,12 +44,6 @@ #define pv_log_failure(...) \ g_log (G_LOG_DOMAIN, PV_LOG_LEVEL_FAILURE, __VA_ARGS__) -typedef struct -{ - const char *variable; - GPtrArray *values; -} PreloadModule; - void pv_get_current_dirs (gchar **cwd_p, gchar **cwd_l); @@ -87,12 +81,3 @@ void pv_set_up_logging (gboolean opt_verbose); void pv_delete_dangling_symlink (int dirfd, const char *debug_path, const char *name); - -void pv_append_preload_module (PreloadModule preload_modules[], - gsize n_preload_modules, - const char *variable, - const char *value); - -void -pv_preload_modules_free (PreloadModule preload_modules[], - gsize n_preload_modules); diff --git a/pressure-vessel/wrap-setup.c b/pressure-vessel/wrap-setup.c index b381bdbe8a1438f589bc4cd20485c2b8631fb3be..c9982426e1d374c7938dcf5784e0851aea7ee05a 100644 --- a/pressure-vessel/wrap-setup.c +++ b/pressure-vessel/wrap-setup.c @@ -21,6 +21,7 @@ #include "wrap-setup.h" #include "steam-runtime-tools/glib-backports-internal.h" +#include "steam-runtime-tools/libdl-internal.h" #include "steam-runtime-tools/utils-internal.h" #include "libglnx/libglnx.h" @@ -28,6 +29,8 @@ #include "bwrap.h" #include "flatpak-run-private.h" +#include "flatpak-utils-private.h" +#include "supported-architectures.h" /* * Use code borrowed from Flatpak to share various bits of the @@ -330,3 +333,440 @@ pv_wrap_move_into_scope (const char *steam_app_id) if (local_error != NULL) g_debug ("Cannot move into a systemd scope: %s", local_error->message); } + +static void +append_preload_internal (GPtrArray *argv, + const char *option, + const char *multiarch_tuple, + const char *export_path, + const char *original_path, + GStrv env, + PvAppendPreloadFlags flags, + PvRuntime *runtime, + FlatpakExports *exports) +{ + gboolean flatpak_subsandbox = ((flags & PV_APPEND_PRELOAD_FLAGS_FLATPAK_SUBSANDBOX) != 0); + + if (runtime != NULL + && (g_str_has_prefix (original_path, "/usr/") + || g_str_has_prefix (original_path, "/lib") + || (flatpak_subsandbox && g_str_has_prefix (original_path, "/app/")))) + { + g_autofree gchar *adjusted_path = NULL; + const char *target = flatpak_subsandbox ? "/run/parent" : "/run/host"; + + adjusted_path = g_build_filename (target, original_path, NULL); + g_debug ("%s -> %s", original_path, adjusted_path); + + if (multiarch_tuple != NULL) + g_ptr_array_add (argv, g_strdup_printf ("%s=%s:abi=%s", + option, adjusted_path, + multiarch_tuple)); + else + g_ptr_array_add (argv, g_strdup_printf ("%s=%s", + option, adjusted_path)); + } + else + { + g_debug ("%s -> unmodified", original_path); + + if (multiarch_tuple != NULL) + g_ptr_array_add (argv, g_strdup_printf ("%s=%s:abi=%s", + option, original_path, + multiarch_tuple)); + else + g_ptr_array_add (argv, g_strdup_printf ("%s=%s", + option, original_path)); + + if (exports != NULL && export_path != NULL && export_path[0] == '/') + { + const gchar *steam_path = g_environ_getenv (env, "STEAM_COMPAT_CLIENT_INSTALL_PATH"); + + if (steam_path != NULL + && flatpak_has_path_prefix (export_path, steam_path)) + { + g_debug ("Skipping exposing \"%s\" because it is located " + "under the Steam client install path that we " + "bind by default", export_path); + } + else + { + g_debug ("%s needs adding to exports", export_path); + flatpak_exports_add_path_expose (exports, + FLATPAK_FILESYSTEM_MODE_READ_ONLY, + export_path); + } + } + } +} + +/* + * Deal with a LD_PRELOAD or LD_AUDIT module that contains tokens whose + * expansion we can't control or predict, such as ${ORIGIN} or future + * additions. We can't do much with these, because we can't assume that + * the dynamic string tokens will expand in the same way for us as they + * will for other programs. + * + * We mostly have to pass them into the container and hope for the best. + * We can rewrite a /usr/, /lib or /app/ prefix, and we can export the + * directory containing the first path component that has a dynamic + * string token: for example, /opt/plat-${PLATFORM}/preload.so or + * /opt/$PLATFORM/preload.so both have to be exported as /opt. + * + * Arguments are the same as for pv_wrap_append_preload(). + */ +static void +append_preload_unsupported_token (GPtrArray *argv, + const char *option, + const char *preload, + GStrv env, + PvAppendPreloadFlags flags, + PvRuntime *runtime, + FlatpakExports *exports) +{ + g_autofree gchar *export_path = NULL; + char *dollar; + char *slash; + + g_debug ("Found $ORIGIN or unsupported token in \"%s\"", + preload); + + if (preload[0] == '/') + { + export_path = g_strdup (preload); + dollar = strchr (export_path, '$'); + g_assert (dollar != NULL); + /* Truncate before '$' */ + dollar[0] = '\0'; + slash = strrchr (export_path, '/'); + /* It's an absolute path, so there is definitely a '/' before '$' */ + g_assert (slash != NULL); + /* Truncate before last '/' before '$' */ + slash[0] = '\0'; + + /* If that truncation leaves it empty, don't try to expose + * the whole root filesystem */ + if (export_path[0] != '/') + { + g_debug ("Not exporting root filesystem for \"%s\"", + preload); + g_clear_pointer (&export_path, g_free); + } + else + { + g_debug ("Exporting \"%s\" for \"%s\"", + export_path, preload); + } + } + else + { + /* Original path was relative and contained an unsupported + * token like $ORIGIN. Pass it through as-is, without any extra + * exports (because we don't know what the token means!), and + * hope for the best. export_path stays NULL. */ + g_debug ("Not exporting \"%s\": not an absolute path, or starts " + "with $ORIGIN", + preload); + } + + append_preload_internal (argv, + option, + NULL, + export_path, + preload, + env, + flags, + runtime, + exports); +} + +/* + * Deal with a LD_PRELOAD or LD_AUDIT module that contains tokens whose + * expansion is ABI-dependent but otherwise fixed. We do these by + * breaking it up into several ABI-dependent LD_PRELOAD modules, which + * are recombined by pv-adverb. We have to do this because the expansion + * of the ABI-dependent tokens could be different in the container, due + * to using a different glibc. + * + * Arguments are the same as for pv_wrap_append_preload(). + */ +static void +append_preload_per_architecture (GPtrArray *argv, + const char *option, + const char *preload, + GStrv env, + PvAppendPreloadFlags flags, + PvRuntime *runtime, + FlatpakExports *exports) +{ + g_autoptr(SrtSystemInfo) system_info = srt_system_info_new (NULL); + gsize i; + + if (system_info == NULL) + system_info = srt_system_info_new (NULL); + + for (i = 0; i < PV_N_SUPPORTED_ARCHITECTURES; i++) + { + g_autoptr(GString) mock_path = NULL; + g_autoptr(SrtLibrary) details = NULL; + const char *path; + + srt_system_info_check_library (system_info, + pv_multiarch_details[i].tuple, + preload, + &details); + path = srt_library_get_absolute_path (details); + + if (flags & PV_APPEND_PRELOAD_FLAGS_IN_UNIT_TESTS) + { + /* Use mock results to get predictable behaviour in the unit + * tests, replacing the real result (above). This avoids needing + * to have real libraries in place when we do unit testing. + * + * tests/pressure-vessel/wrap-setup.c is the other side of this. */ + g_autofree gchar *lib = NULL; + const char *platform = NULL; + + /* As a mock ${LIB}, behave like Debian or the fdo SDK. */ + lib = g_strdup_printf ("lib/%s", pv_multiarch_details[i].tuple); + + /* As a mock ${PLATFORM}, use the first one listed. */ + platform = pv_multiarch_details[i].platforms[0]; + + mock_path = g_string_new (preload); + + if (strchr (preload, '/') == NULL) + { + g_string_printf (mock_path, "/path/to/%s/%s", lib, preload); + } + else + { + g_string_replace (mock_path, "$LIB", lib, 0); + g_string_replace (mock_path, "${LIB}", lib, 0); + g_string_replace (mock_path, "$PLATFORM", platform, 0); + g_string_replace (mock_path, "${PLATFORM}", platform, 0); + } + + path = mock_path->str; + + /* As a special case, pretend one 64-bit library failed to load, + * so we can exercise what happens when there's only a 32-bit + * library available. */ + if (strstr (path, "only-32-bit") != NULL + && strcmp (pv_multiarch_details[i].tuple, + SRT_ABI_I386) != 0) + path = NULL; + } + + if (path != NULL) + { + g_debug ("Found %s version of %s at %s", + pv_multiarch_details[i].tuple, preload, path); + append_preload_internal (argv, + option, + pv_multiarch_details[i].tuple, + path, + path, + env, + flags, + runtime, + exports); + } + else + { + g_info ("Unable to load %s version of %s", + pv_multiarch_details[i].tuple, + preload); + } + } +} + +static void +append_preload_basename (GPtrArray *argv, + const char *option, + const char *preload, + GStrv env, + PvAppendPreloadFlags flags, + PvRuntime *runtime, + FlatpakExports *exports) +{ + gboolean runtime_has_library = FALSE; + + if (runtime != NULL) + runtime_has_library = pv_runtime_has_library (runtime, preload); + + if (flags & PV_APPEND_PRELOAD_FLAGS_IN_UNIT_TESTS) + { + /* Mock implementation for unit tests: behave as though the + * container has everything except libfakeroot/libfakechroot. */ + if (g_str_has_prefix (preload, "libfake")) + runtime_has_library = FALSE; + else + runtime_has_library = TRUE; + } + + if (runtime_has_library) + { + /* If the library exists in the container runtime or in the + * stack we imported from the graphics provider, e.g. + * LD_PRELOAD=libpthread.so.0, then we certainly don't want + * to be loading it from the current namespace: that would + * bypass our logic for comparing library versions and picking + * the newest. Just pass through the LD_PRELOAD item into the + * container, and let the dynamic linker in the container choose + * what it means (container runtime or graphics provider as + * appropriate). */ + g_debug ("Found \"%s\" in runtime or graphics stack provider, " + "passing %s through as-is", + preload, option); + append_preload_internal (argv, + option, + NULL, + NULL, + preload, + env, + flags, + runtime, + NULL); + } + else + { + /* There's no such library in the container runtime or in the + * graphics provider, so it's OK to inject the version from the + * current namespace. Use the same trick as for ${PLATFORM} to + * turn it into (up to) one absolute path per ABI. */ + g_debug ("Did not find \"%s\" in runtime or graphics stack provider, " + "splitting architectures", + preload); + append_preload_per_architecture (argv, + option, + preload, + env, + flags, + runtime, + exports); + } +} + +/** + * pv_wrap_append_preload: + * @argv: (element-type filename): Array of command-line options to populate + * @variable: (type filename): Environment variable from which this + * preload module was taken, either `LD_AUDIT` or `LD_PRELOAD` + * @option: (type filename): Command-line option to add to @argv, + * either `--ld-audit` or `--ld-preload` + * @preload: (type filename): Path of preloadable module in current + * namespace, possibly including special ld.so tokens such as `$LIB`, + * or basename of a preloadable module to be found in the standard + * library search path + * @env: (array zero-terminated=1) (element-type filename): Environment + * variables to be used instead of `environ` + * @flags: Flags to adjust behaviour + * @runtime: (nullable): Runtime to be used in container + * @exports: (nullable): Used to configure extra paths that need to be + * exported into the container + * + * Adjust @preload to be valid for the container and append it + * to @argv. + */ +void +pv_wrap_append_preload (GPtrArray *argv, + const char *variable, + const char *option, + const char *preload, + GStrv env, + PvAppendPreloadFlags flags, + PvRuntime *runtime, + FlatpakExports *exports) +{ + SrtLoadableKind kind; + SrtLoadableFlags loadable_flags; + + g_return_if_fail (argv != NULL); + g_return_if_fail (option != NULL); + g_return_if_fail (preload != NULL); + g_return_if_fail (runtime == NULL || PV_IS_RUNTIME (runtime)); + + if (strstr (preload, "gtk3-nocsd") != NULL) + { + g_warning ("Disabling gtk3-nocsd %s: it is known to cause crashes.", + variable); + return; + } + + if ((flags & PV_APPEND_PRELOAD_FLAGS_REMOVE_GAME_OVERLAY) + && g_str_has_suffix (preload, "/gameoverlayrenderer.so")) + { + g_info ("Disabling Steam Overlay: %s", preload); + return; + } + + kind = _srt_loadable_classify (preload, &loadable_flags); + + switch (kind) + { + case SRT_LOADABLE_KIND_BASENAME: + /* Basenames can't have dynamic string tokens. */ + g_warn_if_fail ((loadable_flags & SRT_LOADABLE_FLAGS_DYNAMIC_TOKENS) == 0); + append_preload_basename (argv, + option, + preload, + env, + flags, + runtime, + exports); + break; + + case SRT_LOADABLE_KIND_PATH: + /* Paths can have dynamic string tokens. */ + if (loadable_flags & (SRT_LOADABLE_FLAGS_ORIGIN + | SRT_LOADABLE_FLAGS_UNKNOWN_TOKENS)) + { + append_preload_unsupported_token (argv, + option, + preload, + env, + flags, + runtime, + exports); + } + else if (loadable_flags & SRT_LOADABLE_FLAGS_ABI_DEPENDENT) + { + g_debug ("Found $LIB or $PLATFORM in \"%s\", splitting architectures", + preload); + append_preload_per_architecture (argv, + option, + preload, + env, + flags, + runtime, + exports); + } + else + { + /* All dynamic tokens should be handled above, so we can + * assume that preload is a concrete filename */ + g_warn_if_fail ((loadable_flags & SRT_LOADABLE_FLAGS_DYNAMIC_TOKENS) == 0); + append_preload_internal (argv, + option, + NULL, + preload, + preload, + env, + flags, + runtime, + exports); + } + break; + + case SRT_LOADABLE_KIND_ERROR: + default: + /* Empty string or similar syntactically invalid token: + * ignore with a warning. Since steam-runtime-tools!352 and + * steamlinuxruntime!64, the wrapper scripts don't give us + * an empty argument any more. */ + g_warning ("Ignoring invalid loadable module \"%s\"", preload); + + break; + } +} diff --git a/pressure-vessel/wrap-setup.h b/pressure-vessel/wrap-setup.h index cfeef954a5360116cee4cac94eac54ea1287aa2c..12dac2cd63dc206be3201d75003debdef768227e 100644 --- a/pressure-vessel/wrap-setup.h +++ b/pressure-vessel/wrap-setup.h @@ -26,6 +26,7 @@ #include "flatpak-bwrap-private.h" #include "flatpak-exports-private.h" +#include "runtime.h" #include "wrap-pipewire.h" void pv_wrap_share_sockets (FlatpakBwrap *bwrap, @@ -40,3 +41,29 @@ gboolean pv_wrap_use_host_os (FlatpakExports *exports, void pv_wrap_move_into_scope (const char *steam_app_id); const char *pv_wrap_get_steam_app_id (const char *from_command_line); + +/** + * PvAppendPreloadFlags: + * @PV_APPEND_PRELOAD_FLAGS_FLATPAK_SUBSANDBOX: The game will be run in + * a Flatpak subsandbox + * @PV_APPEND_PRELOAD_FLAGS_REMOVE_GAME_OVERLAY: Disable the Steam Overlay + * @PV_APPEND_PRELOAD_FLAGS_NONE: None of the above + * + * Flags affecting the behaviour of pv_wrap_append_preload(). + */ +typedef enum +{ + PV_APPEND_PRELOAD_FLAGS_FLATPAK_SUBSANDBOX = (1 << 0), + PV_APPEND_PRELOAD_FLAGS_REMOVE_GAME_OVERLAY = (1 << 1), + PV_APPEND_PRELOAD_FLAGS_IN_UNIT_TESTS = (1 << 2), + PV_APPEND_PRELOAD_FLAGS_NONE = 0 +} PvAppendPreloadFlags; + +void pv_wrap_append_preload (GPtrArray *argv, + const char *variable, + const char *option, + const char *preload, + GStrv env, + PvAppendPreloadFlags flags, + PvRuntime *runtime, + FlatpakExports *exports); diff --git a/pressure-vessel/wrap.c b/pressure-vessel/wrap.c index 13acbb2a330673069dda6019f4a58cd5b16a39f0..38fe6b7db2736771928cff87538148a76b30bc6c 100644 --- a/pressure-vessel/wrap.c +++ b/pressure-vessel/wrap.c @@ -696,6 +696,7 @@ static gboolean opt_remove_game_overlay = FALSE; static gboolean opt_import_vulkan_layers = TRUE; static PvShell opt_shell = PV_SHELL_NONE; static GArray *opt_pass_fds = NULL; +static GArray *opt_preload_modules = NULL; static char *opt_runtime = NULL; static char *opt_runtime_archive = NULL; static char *opt_runtime_base = NULL; @@ -714,28 +715,50 @@ static gboolean opt_test = FALSE; static PvTerminal opt_terminal = PV_TERMINAL_AUTO; static char *opt_write_final_argv = NULL; -static PreloadModule opt_preload_modules[] = +typedef enum { - { "LD_AUDIT", NULL }, - { "LD_PRELOAD", NULL }, -}; -static const char * const adverb_preload_options[G_N_ELEMENTS (opt_preload_modules)] = + PRELOAD_VARIABLE_INDEX_LD_AUDIT, + PRELOAD_VARIABLE_INDEX_LD_PRELOAD, +} PreloadVariableIndex; + +static const struct { - /* Must be in the same order as opt_preload_modules */ - "--ld-audit", - "--ld-preload", + const char *variable; + const char *adverb_option; +} preload_options[] = +{ + [PRELOAD_VARIABLE_INDEX_LD_AUDIT] = { "LD_AUDIT", "--ld-audit" }, + [PRELOAD_VARIABLE_INDEX_LD_PRELOAD] = { "LD_PRELOAD", "--ld-preload" }, }; +typedef struct +{ + PreloadVariableIndex which; + gchar *preload; +} WrapPreloadModule; + +static void +wrap_preload_module_clear (gpointer p) +{ + WrapPreloadModule *self = p; + + g_free (self->preload); +} + static gboolean -opt_host_ld_preload_cb (const gchar *option_name, - const gchar *value, - gpointer data, - GError **error) +opt_ld_something (PreloadVariableIndex which, + const char *value, + GError **error) { - g_warning ("%s is deprecated, use --ld-preload=%s instead", - option_name, value); - pv_append_preload_module (opt_preload_modules, G_N_ELEMENTS (opt_preload_modules), - "LD_PRELOAD", value); + WrapPreloadModule module = { which, g_strdup (value) }; + + if (opt_preload_modules == NULL) + { + opt_preload_modules = g_array_new (FALSE, FALSE, sizeof (WrapPreloadModule)); + g_array_set_clear_func (opt_preload_modules, wrap_preload_module_clear); + } + + g_array_append_val (opt_preload_modules, module); return TRUE; } @@ -745,9 +768,7 @@ opt_ld_audit_cb (const gchar *option_name, gpointer data, GError **error) { - pv_append_preload_module (opt_preload_modules, G_N_ELEMENTS (opt_preload_modules), - "LD_AUDIT", value); - return TRUE; + return opt_ld_something (PRELOAD_VARIABLE_INDEX_LD_AUDIT, value, error); } static gboolean @@ -756,9 +777,18 @@ opt_ld_preload_cb (const gchar *option_name, gpointer data, GError **error) { - pv_append_preload_module (opt_preload_modules, G_N_ELEMENTS (opt_preload_modules), - "LD_PRELOAD", value); - return TRUE; + return opt_ld_something (PRELOAD_VARIABLE_INDEX_LD_PRELOAD, value, error); +} + +static gboolean +opt_host_ld_preload_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + g_warning ("%s is deprecated, use --ld-preload=%s instead", + option_name, value); + return opt_ld_preload_cb (option_name, value, data, error); } static gboolean @@ -1320,6 +1350,7 @@ main (int argc, const char *steam_app_id; g_autoptr(GPtrArray) adverb_preload_argv = NULL; int result; + PvAppendPreloadFlags append_preload_flags = PV_APPEND_PRELOAD_FLAGS_NONE; setlocale (LC_ALL, ""); @@ -1763,6 +1794,10 @@ main (int argc, bwrap_filesystem_arguments = flatpak_bwrap_new (flatpak_bwrap_empty_env); exports = flatpak_exports_new (); } + else + { + append_preload_flags |= PV_APPEND_PRELOAD_FLAGS_FLATPAK_SUBSANDBOX; + } /* Invariant: we have bwrap or exports iff we also have the other */ g_assert ((bwrap != NULL) == (exports != NULL)); @@ -2085,129 +2120,35 @@ main (int argc, adverb_preload_argv = g_ptr_array_new_with_free_func (g_free); + if (opt_remove_game_overlay) + append_preload_flags |= PV_APPEND_PRELOAD_FLAGS_REMOVE_GAME_OVERLAY; + /* We need the LD_PRELOADs from Steam visible at the paths that were * used for them, which might be their physical rather than logical * locations. Steam doesn't generally use LD_AUDIT, but the Steam app * on Flathub does, and it needs similar handling. */ - for (i = 0; i < G_N_ELEMENTS (opt_preload_modules); i++) + if (opt_preload_modules != NULL) { - const char *variable = opt_preload_modules[i].variable; - const GPtrArray *values = opt_preload_modules[i].values; - const char *option = adverb_preload_options[i]; - gsize len; gsize j; - g_debug ("Adjusting %s...", variable); + g_debug ("Adjusting LD_AUDIT/LD_PRELOAD modules..."); - if (values == NULL) - len = 0; - else - len = values->len; - - for (j = 0; j < len; j++) + for (j = 0; j < opt_preload_modules->len; j++) { - const char *preload = g_ptr_array_index (values, j); - - g_assert (preload != NULL); - - if (*preload == '\0') - continue; - - if (strstr (preload, "gtk3-nocsd") != NULL) - { - g_warning ("Disabling gtk3-nocsd %s: it is known to cause crashes.", - variable); - continue; - } - - if (opt_remove_game_overlay - && g_str_has_suffix (preload, "/gameoverlayrenderer.so")) - { - g_info ("Disabling Steam Overlay: %s", preload); - continue; - } - - /* A subsandbox will just have the same LD_PRELOAD as the - * Flatpak itself, except that we have to redirect /usr and /app - * into /run/parent. */ - if (flatpak_subsandbox != NULL) - { - if (runtime != NULL - && (g_str_has_prefix (preload, "/usr/") - || g_str_has_prefix (preload, "/app/") - || g_str_has_prefix (preload, "/lib"))) - { - g_autofree gchar *adjusted_path = NULL; - - adjusted_path = g_build_filename ("/run/parent", preload, NULL); - g_debug ("%s -> %s", preload, adjusted_path); - g_ptr_array_add (adverb_preload_argv, - g_strdup_printf ("%s=%s", - option, - adjusted_path)); - } - else - { - g_debug ("%s -> unmodified", preload); - g_ptr_array_add (adverb_preload_argv, - g_strdup_printf ("%s=%s", - option, - preload)); - } - - /* No FlatpakExports here: any file not in /usr or /app that - * is visible to our "parent" Flatpak app is also visible - * to us. */ - continue; - } - - if (g_file_test (preload, G_FILE_TEST_EXISTS)) - { - if (runtime != NULL - && (g_str_has_prefix (preload, "/usr/") - || g_str_has_prefix (preload, "/lib"))) - { - g_autofree gchar *adjusted_path = NULL; - - adjusted_path = g_build_filename ("/run/host", preload, NULL); - g_debug ("%s -> %s", preload, adjusted_path); - /* When using a runtime we can't write to /usr/ or /libQUAL/, - * so redirect this preloaded module to the corresponding - * location in /run/host. */ - g_ptr_array_add (adverb_preload_argv, - g_strdup_printf ("%s=%s", - option, - adjusted_path)); - } - else - { - const gchar *steam_path = NULL; - steam_path = g_getenv ("STEAM_COMPAT_CLIENT_INSTALL_PATH"); - - if (steam_path != NULL && flatpak_has_path_prefix (preload, steam_path)) - { - g_debug ("Skipping exposing \"%s\" because it is located " - "under the Steam client install path that we " - "bind by default", preload); - } - else - { - g_debug ("%s -> unmodified, but added to exports", preload); - flatpak_exports_add_path_expose (exports, - FLATPAK_FILESYSTEM_MODE_READ_ONLY, - preload); - } - - g_ptr_array_add (adverb_preload_argv, - g_strdup_printf ("%s=%s", - option, - preload)); - } - } - else - { - g_info ("%s module '%s' does not exist", variable, preload); - } + const WrapPreloadModule *module = &g_array_index (opt_preload_modules, + WrapPreloadModule, + j); + + g_assert (module->which >= 0); + g_assert (module->which < G_N_ELEMENTS (preload_options)); + pv_wrap_append_preload (adverb_preload_argv, + preload_options[module->which].variable, + preload_options[module->which].adverb_option, + module->preload, + environ, + append_preload_flags, + runtime, + exports); } } @@ -2787,8 +2728,7 @@ out: if (local_error != NULL) pv_log_failure ("%s", local_error->message); - pv_preload_modules_free (opt_preload_modules, G_N_ELEMENTS (opt_preload_modules)); - + g_clear_pointer (&opt_preload_modules, g_array_unref); g_clear_pointer (&adverb_preload_argv, g_ptr_array_unref); g_clear_pointer (&opt_env_if_host, g_strfreev); g_clear_pointer (&opt_freedesktop_app_id, g_free); diff --git a/steam-runtime-tools/libdl-internal.h b/steam-runtime-tools/libdl-internal.h index dc3b70179a36918ee4521ebb22d480d507501119..09799f90d1e0b8ac13f811d3a2eab6123a3f7d09 100644 --- a/steam-runtime-tools/libdl-internal.h +++ b/steam-runtime-tools/libdl-internal.h @@ -35,3 +35,23 @@ G_GNUC_INTERNAL gchar *_srt_libdl_detect_lib (gchar **envp, const char *helpers_path, const char *multiarch_tuple, GError **error); + +typedef enum +{ + SRT_LOADABLE_KIND_ERROR, + SRT_LOADABLE_KIND_BASENAME, + SRT_LOADABLE_KIND_PATH, +} SrtLoadableKind; + +typedef enum +{ + SRT_LOADABLE_FLAGS_DYNAMIC_TOKENS = (1 << 0), + SRT_LOADABLE_FLAGS_ABI_DEPENDENT = (1 << 1), + SRT_LOADABLE_FLAGS_ORIGIN = (1 << 2), + SRT_LOADABLE_FLAGS_UNKNOWN_TOKENS = (1 << 3), + SRT_LOADABLE_FLAGS_NONE = 0 +} SrtLoadableFlags; + +G_GNUC_INTERNAL +SrtLoadableKind _srt_loadable_classify (const char *loadable, + SrtLoadableFlags *flags_out); diff --git a/steam-runtime-tools/libdl.c b/steam-runtime-tools/libdl.c index 7223f4366ca559578025d635459abbb8f1b756ab..5debbef237c4101970355cbfffe40702b0962fee 100644 --- a/steam-runtime-tools/libdl.c +++ b/steam-runtime-tools/libdl.c @@ -126,3 +126,97 @@ _srt_libdl_detect_lib (gchar **envp, "detect-lib", error); } + +SrtLoadableKind +_srt_loadable_classify (const char *loadable, + SrtLoadableFlags *flags_out) +{ + SrtLoadableFlags flags = SRT_LOADABLE_FLAGS_NONE; + SrtLoadableKind kind; + gsize i; + + g_return_val_if_fail (loadable != NULL, SRT_LOADABLE_KIND_ERROR); + + if (loadable[0] == '\0') + { + kind = SRT_LOADABLE_KIND_ERROR; + goto out; + } + + if (strchr (loadable, '/') != NULL) + { + kind = SRT_LOADABLE_KIND_PATH; + } + else + { + /* Dynamic string tokens are not interpreted in a bare SONAME + * so we don't need to do that part. */ + kind = SRT_LOADABLE_KIND_BASENAME; + goto out; + } + + for (i = 0; loadable[i] != '\0'; i++) + { + if (loadable[i] == '$') + { + const char *token = loadable + i + 1; + gsize len; + + flags |= SRT_LOADABLE_FLAGS_DYNAMIC_TOKENS; + + if (token[0] == '{') + { + token++; + len = strcspn (token, "}"); + } + else + { + len = 0; + + while (g_ascii_isalnum (token[len]) || token[len] == '_') + len++; + } + + switch (len) + { + case 3: + if (strncmp ("LIB", token, len) == 0) + { + flags |= SRT_LOADABLE_FLAGS_ABI_DEPENDENT; + continue; + } + + break; + + case 6: + if (strncmp ("ORIGIN", token, len) == 0) + { + flags |= SRT_LOADABLE_FLAGS_ORIGIN; + continue; + } + + break; + + case 8: + if (strncmp ("PLATFORM", token, len) == 0) + { + flags |= SRT_LOADABLE_FLAGS_ABI_DEPENDENT; + continue; + } + + break; + + default: + break; + } + + flags |= SRT_LOADABLE_FLAGS_UNKNOWN_TOKENS; + } + } + +out: + if (flags_out != NULL) + *flags_out = flags; + + return kind; +} diff --git a/tests/libdl.c b/tests/libdl.c new file mode 100644 index 0000000000000000000000000000000000000000..964d0c54295ab1cc42202ed73aa45cd100b17063 --- /dev/null +++ b/tests/libdl.c @@ -0,0 +1,131 @@ +/* + * Copyright © 2019 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 <steam-runtime-tools/steam-runtime-tools.h> + +#include <string.h> +#include <unistd.h> + +#include <glib.h> +#include <glib/gstdio.h> + +#include "steam-runtime-tools/libdl-internal.h" +#include "test-utils.h" + +typedef struct +{ + int unused; +} Fixture; + +typedef struct +{ + int unused; +} Config; + +static void +setup (Fixture *f, + gconstpointer context) +{ + G_GNUC_UNUSED const Config *config = context; +} + +static void +teardown (Fixture *f, + gconstpointer context) +{ + G_GNUC_UNUSED const Config *config = context; +} + +#define ERROR SRT_LOADABLE_KIND_ERROR +#define PATH SRT_LOADABLE_KIND_PATH +#define BASE SRT_LOADABLE_KIND_BASENAME +#define DYN SRT_LOADABLE_FLAGS_DYNAMIC_TOKENS +#define ABI SRT_LOADABLE_FLAGS_ABI_DEPENDENT +#define ORIGIN SRT_LOADABLE_FLAGS_ORIGIN +#define UNKNOWN SRT_LOADABLE_FLAGS_UNKNOWN_TOKENS +#define NONE SRT_LOADABLE_FLAGS_NONE +static const struct +{ + const char *loadable; + SrtLoadableKind kind; + SrtLoadableFlags flags; +} +libdl_classify_tests[] = +{ + { "", ERROR, NONE }, + { "/usr/lib/libc.so.6", PATH, NONE }, + { "/usr/$LIB/libMangoHud.so", PATH, DYN|ABI }, + { "${LIB}/libfoo.so", PATH, DYN|ABI }, + { "/usr/plat-$PLATFORM/libc.so.6", PATH, DYN|ABI }, + { "${PLATFORM}/libc.so.6", PATH, DYN|ABI }, + { "${ORIGIN}/../${LIB}/libc.so.6", PATH, DYN|ABI|ORIGIN }, + { "/$ORIGIN/libfoo", PATH, DYN|ORIGIN }, + { "$ORIGIN/$FUTURE/${PLATFORM}-libfoo.so", PATH, DYN|ORIGIN|ABI|UNKNOWN }, + { "${FUTURE}/libfoo.so", PATH, DYN|UNKNOWN }, + { "libc.so.6", BASE, NONE }, + { "libMangoHud.so", BASE, NONE }, + { "looks-like-${TOKENS}-interpreted-but-not-really", BASE, NONE }, +}; +#undef ERROR +#undef PATH +#undef BASE +#undef DYN +#undef ABI +#undef ORIGIN +#undef UNKNOWN +#undef NONE + +static void +test_libdl_classify (Fixture *f, + gconstpointer context) +{ + gsize i; + + for (i = 0; i < G_N_ELEMENTS (libdl_classify_tests); i++) + { + SrtLoadableFlags flags = -1; + SrtLoadableKind kind; + + kind = _srt_loadable_classify (libdl_classify_tests[i].loadable, &flags); + + g_assert_cmpuint (kind, ==, libdl_classify_tests[i].kind); + g_assert_cmphex (flags, ==, libdl_classify_tests[i].flags); + + kind = _srt_loadable_classify (libdl_classify_tests[i].loadable, NULL); + g_assert_cmpuint (kind, ==, libdl_classify_tests[i].kind); + } +} + +int +main (int argc, + char **argv) +{ + g_test_init (&argc, &argv, NULL); + + g_test_add ("/libdl/classify", Fixture, NULL, setup, + test_libdl_classify, teardown); + + return g_test_run (); +} diff --git a/tests/meson.build b/tests/meson.build index 101e75e31900a75015316012b12cd4b6e6627e54..ff8b1be0d1dfa32104d1d2deb0f52c033fe07d57 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -42,6 +42,7 @@ tests = [ ], }, {'name': 'json-utils', 'static': true}, + {'name': 'libdl', 'static': true}, {'name': 'library'}, {'name': 'locale'}, {'name': 'system-info'}, diff --git a/tests/pressure-vessel/meson.build b/tests/pressure-vessel/meson.build index 19b153611bb6b5386eedd089318f4c05ee4347db..6b713d72336c6ef5938164fbe8a8ff7bff21efc5 100644 --- a/tests/pressure-vessel/meson.build +++ b/tests/pressure-vessel/meson.build @@ -45,9 +45,16 @@ compiled_tests = [ 'bwrap-lock', 'resolve-in-sysroot', 'wait-for-child-processes', + 'wrap-setup', 'utils', ] +# The wrap-setup test fails to link unless -lelf is forced in, with undefined +# references to elf_version() etc. It isn't clear why... but this seems to work. +force_libelf = declare_dependency( + link_args : ['-Wl,--no-as-needed,-lelf,--as-needed'], +) + foreach test_name : compiled_tests exe = executable( 'test-' + test_name, @@ -57,6 +64,7 @@ foreach test_name : compiled_tests libglnx_dep, test_utils_static_libsteamrt_dep, pressure_vessel_wrap_lib_dep, + force_libelf, ], include_directories : pv_include_dirs, install : false, diff --git a/tests/pressure-vessel/wrap-setup.c b/tests/pressure-vessel/wrap-setup.c new file mode 100644 index 0000000000000000000000000000000000000000..fad380efb8d9e2c1c536c8e424492203f3562cf6 --- /dev/null +++ b/tests/pressure-vessel/wrap-setup.c @@ -0,0 +1,882 @@ +/* + * 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 (); +}