diff --git a/pressure-vessel/adverb.1.md b/pressure-vessel/adverb.1.md index 8ebdbbdb9833d60b739a5d0bf259d0559bfe0b72..3ff6d1edcefedae55a45d759c2bd479bce355be6 100644 --- a/pressure-vessel/adverb.1.md +++ b/pressure-vessel/adverb.1.md @@ -21,6 +21,9 @@ pressure-vessel-adverb - wrap processes in various ways [**--ld-audit** *MODULE*[**:arch=***TUPLE*]...] [**--ld-preload** *MODULE*[**:**...]...] [**--pass-fd** *FD*...] +[[**--add-ld.so-path** *PATH*...] +**--regenerate-ld.so-cache** *PATH*] +[**--set-ld-library-path** *VALUE*] [**--shell** **none**|**after**|**fail**|**instead**] [**--subreaper**] [**--terminal** **none**|**auto**|**tty**|**xterm**] @@ -43,6 +46,12 @@ exit status. # OPTIONS +**--add-ld.so-path** *PATH* +: Add *PATH* to the search path for **--regenerate-ld.so-cache**. + The final search path will consist of all **--add-ld.so-path** + arguments in the order they are given, followed by the lines + from `runtime-ld.so.conf` in order. + **--create** : Create each **--lock-file** that appears on the command-line after this option if it does not exist, until a **--no-create** option @@ -106,6 +115,34 @@ exit status. through file descriptors 0, 1 and 2 (**stdin**, **stdout** and **stderr**). +**--regenerate-ld.so-cache** *PATH* +: Regenerate "ld.so.cache" in the directory *PATH*. + + On entry to **pressure-vessel-adverb**, *PATH* should + contain `runtime-ld.so.conf`, a symbolic link or copy + of the runtime's original `/etc/ld.so.conf`. It will + usually also contain `ld.so.conf` and `ld.so.cache` + as symbolic links or copies of the runtime's original + `/etc/ld.so.conf` and `/etc/ld.so.cache`. + + Before executing the *COMMAND*, **pressure-vessel-adverb** + will construct a new `ld.so.conf` in *PATH*, consisting of + all **--add-ld.so-path** arguments, followed by the contents + of `runtime-ld.so.conf`; then it will generate a new + `ld.so.cache` from that configuration. Both of these + will atomically replace the original files in *PATH*. + + Other filenames in *PATH* will be used temporarily. + + To make use of this feature, a container's `/etc/ld.so.conf` + and `/etc/ld.so.cache` should usually be symbolic links into + the *PATH* used here, which will typically be below `/run`. + +**--set-ld-library-path** *VALUE* +: Set the environment variable LD_LIBRARY_PATH to *VALUE* after + processing **--regenerate-ld.so-cache** (if used), but before + executing *COMMAND*. + **--shell=after** : Run an interactive shell after *COMMAND* exits. In that shell, running **"$@"** will re-run *COMMAND*. diff --git a/pressure-vessel/adverb.c b/pressure-vessel/adverb.c index 4bf57a30cfd41895d529793ef20b5d2143ee0df9..d255b587a116331c117344c9b0666427f4bab389 100644 --- a/pressure-vessel/adverb.c +++ b/pressure-vessel/adverb.c @@ -50,11 +50,14 @@ static const char * const *global_original_environ = NULL; static GPtrArray *global_locks = NULL; +static GPtrArray *global_ld_so_conf_entries = NULL; static GArray *global_pass_fds = NULL; static gboolean opt_batch = FALSE; static gboolean opt_create = FALSE; static gboolean opt_exit_with_parent = FALSE; static gboolean opt_generate_locales = FALSE; +static gchar *opt_regenerate_ld_so_cache = NULL; +static gchar *opt_set_ld_library_path = NULL; static PvShell opt_shell = PV_SHELL_NONE; static gboolean opt_subreaper = FALSE; static PvTerminal opt_terminal = PV_TERMINAL_AUTO; @@ -251,6 +254,20 @@ opt_fd_cb (const char *name, return TRUE; } +static gboolean +opt_add_ld_so_cb (const char *name, + const char *value, + gpointer data, + GError **error) +{ + g_return_val_if_fail (global_ld_so_conf_entries != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + g_ptr_array_add (global_ld_so_conf_entries, g_strdup (value)); + return TRUE; +} + static gboolean opt_ld_something (const char *option, gsize index_in_preload_variables, @@ -616,6 +633,113 @@ generate_lib_temp_dirs (LibTempDirs *lib_temp_dirs, return TRUE; } +static gboolean +regenerate_ld_so_cache (const GPtrArray *ld_so_cache_paths, + const char *dir, + GError **error) +{ + g_autoptr(GPtrArray) argv = g_ptr_array_new (); + g_autoptr(GString) conf = g_string_new (""); + g_autofree gchar *child_stdout = NULL; + g_autofree gchar *child_stderr = NULL; + g_autofree gchar *conf_path = g_build_filename (dir, "ld.so.conf", NULL); + g_autofree gchar *rt_conf_path = g_build_filename (dir, "runtime-ld.so.conf", NULL); + g_autofree gchar *replace_path = g_build_filename (dir, "ld.so.cache", NULL); + g_autofree gchar *new_path = g_build_filename (dir, "new-ld.so.cache", NULL); + g_autofree gchar *contents = NULL; + int wait_status; + gsize i; + + for (i = 0; ld_so_cache_paths != NULL && i < ld_so_cache_paths->len; i++) + { + const gchar *value = g_ptr_array_index (ld_so_cache_paths, i); + if (strchr (value, '\n') != NULL + || strchr (value, '\t') != NULL + || value[0] != '/') + return glnx_throw (error, + "Cannot include path entry \"%s\" in ld.so.conf", + value); + + g_debug ("%s: Adding \"%s\" to beginning of ld.so.conf", + G_STRFUNC, value); + g_string_append (conf, value); + g_string_append_c (conf, '\n'); + } + + /* Ignore read error, if any */ + if (g_file_get_contents (rt_conf_path, &contents, NULL, NULL)) + { + g_debug ("%s: Appending runtime's ld.so.conf:\n%s", G_STRFUNC, contents); + g_string_append (conf, contents); + } + + /* This atomically replaces conf_path, so we don't need to do the + * atomic bit ourselves */ + if (!g_file_set_contents (conf_path, conf->str, -1, error)) + return FALSE; + + while (TRUE) + { + char *newline = strchr (conf->str, '\n'); + + if (newline != NULL) + *newline = '\0'; + + g_debug ("%s: final ld.so.conf: %s", G_STRFUNC, conf->str); + + if (newline != NULL) + g_string_erase (conf, 0, newline + 1 - conf->str); + else + break; + } + + /* Items in this GPtrArray are borrowed, not copied. + * + * /sbin/ldconfig might be a symlink into /run/host, or it might + * be from the runtime, depending which version of glibc we're + * using. + * + * ldconfig overwrites the file in-place rather than atomically, + * so we write to a new filename, and do the atomic-overwrite + * ourselves if ldconfig succeeds. */ + g_ptr_array_add (argv, (char *) "/sbin/ldconfig"); + g_ptr_array_add (argv, (char *) "-f"); /* Path to ld.so.conf */ + g_ptr_array_add (argv, conf_path); + g_ptr_array_add (argv, (char *) "-C"); /* Path to new cache */ + g_ptr_array_add (argv, new_path); + g_ptr_array_add (argv, (char *) "-X"); /* Don't update symlinks */ + + if (opt_verbose) + g_ptr_array_add (argv, (char *) "-v"); + + g_ptr_array_add (argv, NULL); + + if (!run_helper_sync (dir, + (const char * const *) argv->pdata, + global_original_environ, + &child_stdout, + &child_stderr, + &wait_status, + error)) + return glnx_prefix_error (error, "Cannot run /sbin/ldconfig"); + + if (child_stdout != NULL && child_stdout[0] != '\0') + g_debug ("Output:\n%s", child_stdout); + + if (child_stderr != NULL && child_stderr[0] != '\0') + g_debug ("Diagnostic output:\n%s", child_stderr); + + if (!g_spawn_check_exit_status (wait_status, error)) + return glnx_prefix_error (error, "Unable to generate %s", new_path); + + /* Atomically replace ld.so.cache with new-ld.so.cache. */ + if (!glnx_renameat (AT_FDCWD, new_path, AT_FDCWD, replace_path, error)) + return glnx_prefix_error (error, "Cannot move %s to %s", + new_path, replace_path); + + return TRUE; +} + static gboolean generate_locales (gchar **locpath_out, GError **error) @@ -769,6 +893,23 @@ static GOptionEntry options[] = G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_generate_locales, "Don't generate any missing locales [default].", NULL }, + { "regenerate-ld.so-cache", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &opt_regenerate_ld_so_cache, + "Regenerate ld.so.cache in the given directory, incorporating " + "the paths from \"add-ld.so-path\", if any. An empty argument results in " + "not doing this [default].", + "PATH" }, + { "add-ld.so-path", '\0', + G_OPTION_FLAG_FILENAME, G_OPTION_ARG_CALLBACK, opt_add_ld_so_cb, + "While regenerating the ld.so.cache, include PATH as an additional " + "ld.so.conf.d entry. May be repeated.", + "PATH" }, + { "set-ld-library-path", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &opt_set_ld_library_path, + "Set the environment variable LD_LIBRARY_PATH to VALUE before " + "executing COMMAND.", + "VALUE" }, + { "write", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_write, "Lock each subsequent lock file for write access.", @@ -863,6 +1004,7 @@ main (int argc, char *argv[]) { g_auto(GStrv) original_environ = NULL; + g_autoptr(GPtrArray) ld_so_conf_entries = NULL; g_autoptr(GPtrArray) locks = NULL; g_autoptr(GOptionContext) context = NULL; g_autoptr(GError) local_error = NULL; @@ -895,6 +1037,9 @@ main (int argc, original_environ = g_get_environ (); global_original_environ = (const char * const *) original_environ; + ld_so_conf_entries = g_ptr_array_new_with_free_func (g_free); + global_ld_so_conf_entries = ld_so_conf_entries; + locks = g_ptr_array_new_with_free_func ((GDestroyNotify) pv_bwrap_lock_free); global_locks = locks; @@ -1158,6 +1303,24 @@ main (int argc, } } + if (opt_regenerate_ld_so_cache != NULL + && opt_regenerate_ld_so_cache[0] != '\0') + { + if (!regenerate_ld_so_cache (global_ld_so_conf_entries, opt_regenerate_ld_so_cache, + error)) + { + goto out; + } + g_debug ("Generated ld.so.cache in %s", opt_regenerate_ld_so_cache); + } + + if (opt_set_ld_library_path != NULL) + { + g_debug ("Setting LD_LIBARY_PATH to \"%s\"", opt_set_ld_library_path); + flatpak_bwrap_set_env (wrapped_command, "LD_LIBRARY_PATH", + opt_set_ld_library_path, TRUE); + } + if (opt_generate_locales) { G_GNUC_UNUSED g_autoptr(SrtProfilingTimer) profiling = @@ -1309,8 +1472,10 @@ main (int argc, } out: + global_ld_so_conf_entries = NULL; global_locks = NULL; g_clear_pointer (&global_pass_fds, g_array_unref); + g_clear_pointer (&opt_regenerate_ld_so_cache, g_free); if (locales_temp_dir != NULL) _srt_rm_rf (locales_temp_dir); diff --git a/pressure-vessel/runtime.c b/pressure-vessel/runtime.c index f471ae1f1e6aa91dac04f37f9595aee9f4a10125..62fb34ef2fdd41f2b2c39249cd48802d8770d22f 100644 --- a/pressure-vessel/runtime.c +++ b/pressure-vessel/runtime.c @@ -2437,7 +2437,7 @@ bind_runtime_base (PvRuntime *self, NULL }; g_autofree gchar *xrd = g_strdup_printf ("/run/user/%ld", (long) geteuid ()); - gsize i, j; + gsize i; const gchar *member; g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE); @@ -2544,6 +2544,16 @@ bind_runtime_base (PvRuntime *self, if (self->provider != NULL && g_strv_contains (from_provider, dest)) continue; + if (self->mutable_sysroot != NULL) + { + /* If we have a mutable sysroot, we handle ld.so.cache + * separately later, because we want to set it up to be + * possible for the -adverb to overwrite it. */ + if (strcmp (dest, "/etc/ld.so.cache") == 0 + || strcmp (dest, "/etc/ld.so.conf") == 0) + continue; + } + full = g_build_filename (self->runtime_files, bind_mutable[i], member, @@ -2564,44 +2574,6 @@ bind_runtime_base (PvRuntime *self, } } - /* glibc from some distributions will want to load the ld.so cache from - * a distribution-specific path, e.g. Clear Linux uses - * /var/cache/ldconfig/ld.so.cache. For simplicity, we make all these - * paths symlinks to /etc/ld.so.cache, so that we only have to populate - * the cache in one place. */ - for (i = 0; pv_other_ld_so_cache[i] != NULL; i++) - { - const char *path = pv_other_ld_so_cache[i]; - - flatpak_bwrap_add_args (bwrap, - "--symlink", "/etc/ld.so.cache", path, - NULL); - } - - /* glibc from some distributions will want to load the ld.so cache from - * a distribution- and architecture-specific path, e.g. Exherbo - * does this. Again, for simplicity we direct all these to the same path: - * it's OK to mix multiple architectures' libraries into one cache, - * as done in upstream glibc (and Debian, Arch, etc.). */ - for (i = 0; i < PV_N_SUPPORTED_ARCHITECTURES; i++) - { - const PvMultiarchDetails *details = &pv_multiarch_details[i]; - - for (j = 0; j < G_N_ELEMENTS (details->other_ld_so_cache); j++) - { - const char *base = details->other_ld_so_cache[j]; - g_autofree gchar *path = NULL; - - if (base == NULL) - break; - - path = g_build_filename ("/etc", base, NULL); - flatpak_bwrap_add_args (bwrap, - "--symlink", "/etc/ld.so.cache", path, - NULL); - } - } - /* If we are in a Flatpak environment, we need to test if these files are * available in the host, and not in the current environment, because we will * run bwrap in the host system */ @@ -2677,6 +2649,211 @@ bind_runtime_base (PvRuntime *self, return TRUE; } +/* + * Exactly as symlinkat(2), except that if the destination already exists, + * it will be removed. + */ +static gboolean +pv_runtime_symlinkat (const gchar *target, + int destination_dirfd, + const gchar *destination, + GError **error) +{ + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!glnx_shutil_rm_rf_at (destination_dirfd, destination, NULL, error)) + return FALSE; + + if (TEMP_FAILURE_RETRY (symlinkat (target, destination_dirfd, destination)) != 0) + return glnx_throw_errno_prefix (error, + "Unable to create symlink \".../%s\" -> \"%s\"", + destination, target); + + return TRUE; +} + +static gboolean +bind_runtime_ld_so (PvRuntime *self, + FlatpakBwrap *bwrap, + PvEnviron *container_env, + GError **error) +{ + gsize i, j; + + g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE); + g_return_val_if_fail (bwrap == NULL || !pv_bwrap_was_finished (bwrap), FALSE); + g_return_val_if_fail (self->is_flatpak_env || bwrap != NULL, FALSE); + g_return_val_if_fail (self->mutable_sysroot != NULL || !self->is_flatpak_env, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (self->is_flatpak_env) + { + const gchar *xrd = NULL; + g_autofree gchar *ldso_runtime_dir = NULL; + g_autofree gchar *xrd_ld_so_conf = NULL; + g_autofree gchar *xrd_ld_so_cache = NULL; + glnx_autofd int sysroot_etc_dirfd = -1; + glnx_autofd int ldso_runtime_dirfd = -1; + + sysroot_etc_dirfd = _srt_resolve_in_sysroot (self->mutable_sysroot_fd, + "/etc", + SRT_RESOLVE_FLAGS_MKDIR_P, + NULL, error); + if (sysroot_etc_dirfd < 0) + return FALSE; + + /* Because we're running under Flatpak in this code path, + * we expect that there is a XDG_RUNTIME_DIR even if the host system + * doesn't provide one; and because we require Flatpak 1.11.1, + * we can assume it's shared between our current sandbox and the + * game's subsandbox, with the same path in both. */ + xrd = g_environ_getenv (self->original_environ, "XDG_RUNTIME_DIR"); + if (xrd == NULL) + { + g_warning ("The environment variable XDG_RUNTIME_DIR is not set, skipping regeneration of ld.so"); + return TRUE; + } + + ldso_runtime_dir = g_build_filename (xrd, "pressure-vessel", "ldso", NULL); + if (g_mkdir_with_parents (ldso_runtime_dir, 0700) != 0) + return glnx_throw_errno_prefix (error, "Unable to create %s", + ldso_runtime_dir); + + xrd_ld_so_conf = g_build_filename (ldso_runtime_dir, "ld.so.conf", NULL); + xrd_ld_so_cache = g_build_filename (ldso_runtime_dir, "ld.so.cache", NULL); + + if (!glnx_opendirat (-1, ldso_runtime_dir, TRUE, &ldso_runtime_dirfd, error)) + return FALSE; + + /* Rename the original ld.so.cache and conf because we will create + * symlinks in their places */ + if (!glnx_renameat (self->mutable_sysroot_fd, "etc/ld.so.cache", + self->mutable_sysroot_fd, "etc/runtime-ld.so.cache", error)) + return FALSE; + if (!glnx_renameat (self->mutable_sysroot_fd, "etc/ld.so.conf", + self->mutable_sysroot_fd, "etc/runtime-ld.so.conf", error)) + return FALSE; + + if (!pv_runtime_symlinkat (xrd_ld_so_cache, self->mutable_sysroot_fd, + "etc/ld.so.cache", error)) + return FALSE; + if (!pv_runtime_symlinkat (xrd_ld_so_conf, self->mutable_sysroot_fd, + "etc/ld.so.conf", error)) + return FALSE; + + /* Create a symlink to the runtime's version */ + if (!pv_runtime_symlinkat ("/etc/runtime-ld.so.cache", ldso_runtime_dirfd, + "runtime-ld.so.cache", error)) + return FALSE; + if (!pv_runtime_symlinkat ("/etc/runtime-ld.so.conf", ldso_runtime_dirfd, + "runtime-ld.so.conf", error)) + return FALSE; + + /* Initially it's a symlink to the runtime's version and we rely on + * LD_LIBRARY_PATH for our overrides, but -adverb will overwrite this + * symlink */ + if (!pv_runtime_symlinkat ("runtime-ld.so.cache", ldso_runtime_dirfd, + "ld.so.cache", error)) + return FALSE; + if (!pv_runtime_symlinkat ("runtime-ld.so.conf", ldso_runtime_dirfd, + "ld.so.conf", error)) + return FALSE; + + /* Initially we have the following situation: + * ($XRD is an abbreviation for $XDG_RUNTIME_DIR) + * ${mutable_sysroot}/etc/ld.so.cache -> $XRD/pressure-vessel/ldso/ld.so.cache + * $XRD/pressure-vessel/ldso/ld.so.cache -> runtime-ld.so.cache + * $XRD/pressure-vessel/ldso/runtime-ld.so.cache -> ${mutable_sysroot}/etc/runtime-ld.so.cache + * ${mutable_sysroot}/etc/runtime-ld.so.cache is the original runtime's ld.so.cache + * + * After exectuting -adverb we expect the symlink $XRD/pressure-vessel/ldso/ld.so.cache + * to be replaced with a newly generated ld.so.cache that incorporates the + * necessary paths from LD_LIBRARY_PATH */ + } + else + { + g_assert (bwrap != NULL); + + const gchar *ld_so_cache_path = "/run/pressure-vessel/ldso/ld.so.cache"; + g_autofree gchar *ld_so_cache_on_host = NULL; + g_autofree gchar *ld_so_conf_on_host = NULL; + + /* We only support runtimes that include /etc/ld.so.cache and + * /etc/ld.so.conf at their interoperable path. */ + ld_so_cache_on_host = g_build_filename (self->runtime_files_on_host, + "etc", "ld.so.cache", NULL); + ld_so_conf_on_host = g_build_filename (self->runtime_files_on_host, + "etc", "ld.so.conf", NULL); + + flatpak_bwrap_add_args (bwrap, + "--tmpfs", "/run/pressure-vessel/ldso", + /* We put the ld.so.cache somewhere that we can + * overwrite from inside the container by + * replacing the symlink. */ + "--symlink", + ld_so_cache_path, + "/etc/ld.so.cache", + "--symlink", + "/run/pressure-vessel/ldso/ld.so.conf", + "/etc/ld.so.conf", + /* Initially it's a symlink to the runtime's + * version and we rely on LD_LIBRARY_PATH + * for our overrides, but -adverb will + * overwrite this symlink. */ + "--symlink", + "runtime-ld.so.cache", + ld_so_cache_path, + "--symlink", + "runtime-ld.so.conf", + "/run/pressure-vessel/ldso/ld.so.conf", + /* Put the runtime's version in place too. */ + "--ro-bind", ld_so_cache_on_host, + "/run/pressure-vessel/ldso/runtime-ld.so.cache", + "--ro-bind", ld_so_conf_on_host, + "/run/pressure-vessel/ldso/runtime-ld.so.conf", + NULL); + + /* glibc from some distributions will want to load the ld.so cache from + * a distribution-specific path, e.g. Clear Linux uses + * /var/cache/ldconfig/ld.so.cache. For simplicity, we make all these paths + * symlinks, so that we only have to populate the cache in one place. */ + for (i = 0; pv_other_ld_so_cache[i] != NULL; i++) + { + const char *path = pv_other_ld_so_cache[i]; + + flatpak_bwrap_add_args (bwrap, + "--symlink", ld_so_cache_path, path, + NULL); + } + + /* glibc from some distributions will want to load the ld.so cache from + * a distribution- and architecture-specific path, e.g. Exherbo + * does this. Again, for simplicity we direct all these to the same path: + * it's OK to mix multiple architectures' libraries into one cache, + * as done in upstream glibc (and Debian, Arch, etc.). */ + for (i = 0; i < PV_N_SUPPORTED_ARCHITECTURES; i++) + { + const PvMultiarchDetails *details = &pv_multiarch_details[i]; + + for (j = 0; j < G_N_ELEMENTS (details->other_ld_so_cache); j++) + { + const char *base = details->other_ld_so_cache[j]; + g_autofree gchar *path = NULL; + + if (base == NULL) + break; + + path = g_build_filename ("/etc", base, NULL); + flatpak_bwrap_add_args (bwrap, + "--symlink", ld_so_cache_path, path, + NULL); + } + } + } + + return TRUE; +} + static void bind_runtime_finish (PvRuntime *self, FlatpakExports *exports, @@ -5282,6 +5459,7 @@ pv_runtime_bind (PvRuntime *self, FlatpakExports *exports, FlatpakBwrap *bwrap, PvEnviron *container_env, + gchar **regenerate_ld_so_cache, GError **error) { g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE); @@ -5306,6 +5484,12 @@ pv_runtime_bind (PvRuntime *self, && !bind_runtime_base (self, bwrap, container_env, error)) return FALSE; + if (bwrap != NULL || self->is_flatpak_env) + { + if (!bind_runtime_ld_so (self, bwrap, container_env, error)) + return FALSE; + } + if (self->provider != NULL) { if (!pv_runtime_use_provider_graphics_stack (self, bwrap, @@ -5430,6 +5614,26 @@ pv_runtime_bind (PvRuntime *self, pv_runtime_set_search_paths (self, container_env); + if (regenerate_ld_so_cache != NULL) + { + if (self->is_flatpak_env) + { + const gchar *xrd; + /* As in bind_runtime_ld_so(), we expect Flatpak to provide this + * in practice, even if the host system does not. */ + xrd = g_environ_getenv (self->original_environ, "XDG_RUNTIME_DIR"); + if (xrd == NULL) + *regenerate_ld_so_cache = NULL; + else + *regenerate_ld_so_cache = g_build_filename (xrd, "pressure-vessel", + "ldso", NULL); + } + else + { + *regenerate_ld_so_cache = g_strdup ("/run/pressure-vessel/ldso"); + } + } + return TRUE; } @@ -5441,10 +5645,10 @@ pv_runtime_set_search_paths (PvRuntime *self, g_autofree char *terminfo_path = NULL; gsize i; - /* TODO: Adapt the use_ld_so_cache code from Flatpak instead - * of setting LD_LIBRARY_PATH, for better robustness against - * games that set their own LD_LIBRARY_PATH ignoring what they - * got from the environment */ + /* We need to set LD_LIBRARY_PATH here so that we can run + * pressure-vessel-adverb, even if it is going to regenerate + * the ld.so.cache for better robustness before launching the + * actual game */ g_assert (pv_multiarch_tuples[PV_N_SUPPORTED_ARCHITECTURES] == NULL); for (i = 0; i < PV_N_SUPPORTED_ARCHITECTURES; i++) diff --git a/pressure-vessel/runtime.h b/pressure-vessel/runtime.h index c201a920638bead3e46f98dcec026afe508c658b..dfb9f19f47ba299fe111763bdf8cdc04dd570985 100644 --- a/pressure-vessel/runtime.h +++ b/pressure-vessel/runtime.h @@ -98,6 +98,7 @@ gboolean pv_runtime_bind (PvRuntime *self, FlatpakExports *exports, FlatpakBwrap *bwrap, PvEnviron *container_env, + gchar **regenerate_ld_so_cache, GError **error); const char *pv_runtime_get_modified_usr (PvRuntime *self); const char *pv_runtime_get_modified_app (PvRuntime *self); diff --git a/pressure-vessel/wrap.c b/pressure-vessel/wrap.c index dab5cdefbc5169fee973e8ee355f22422c28d36c..13acbb2a330673069dda6019f4a58cd5b16a39f0 100644 --- a/pressure-vessel/wrap.c +++ b/pressure-vessel/wrap.c @@ -1310,11 +1310,13 @@ main (int argc, g_autofree gchar *cwd_p = NULL; g_autofree gchar *cwd_l = NULL; g_autofree gchar *cwd_p_host = NULL; + g_autofree gchar *container_ld_library_path = NULL; const gchar *home; g_autofree gchar *tools_dir = NULL; g_autoptr(PvRuntime) runtime = NULL; g_autoptr(FILE) original_stdout = NULL; g_autoptr(GArray) pass_fds_through_adverb = g_array_new (FALSE, FALSE, sizeof (int)); + g_autofree gchar *regenerate_ld_so_cache = NULL; const char *steam_app_id; g_autoptr(GPtrArray) adverb_preload_argv = NULL; int result; @@ -1928,6 +1930,7 @@ main (int argc, exports, bwrap_filesystem_arguments, container_env, + ®enerate_ld_so_cache, error)) goto out; @@ -2365,6 +2368,10 @@ main (int argc, goto out; } + /* Save this value before freeing container_env */ + container_ld_library_path = g_strdup (pv_environ_getenv (container_env, + "LD_LIBRARY_PATH")); + if (is_flatpak_env) { g_autoptr(GList) vars = NULL; @@ -2509,6 +2516,37 @@ main (int argc, { if (!pv_runtime_get_adverb (runtime, adverb_argv)) goto out; + + if (regenerate_ld_so_cache != NULL) + { + g_autoptr(GString) adverb_ld_library_path = g_string_new (""); + g_auto(GStrv) parts = NULL; + + flatpak_bwrap_add_args (adverb_argv, + "--regenerate-ld.so-cache", + regenerate_ld_so_cache, + NULL); + + if (container_ld_library_path != NULL) + parts = g_strsplit (container_ld_library_path, ":", 0); + + for (i = 0; parts != NULL && parts[i] != NULL; i++) + { + if (g_str_has_suffix (parts[i], "/aliases")) + pv_search_path_append (adverb_ld_library_path, parts[i]); + else + flatpak_bwrap_add_args (adverb_argv, + "--add-ld.so-path", + parts[i], + NULL); + } + + if (adverb_ld_library_path->len > 0) + flatpak_bwrap_add_args (adverb_argv, + "--set-ld-library-path", + adverb_ld_library_path->str, + NULL); + } } else { diff --git a/tests/pressure-vessel/inside-runtime.py b/tests/pressure-vessel/inside-runtime.py index f425a56f64291a3d2b9a2202301cf93995b7dcc7..a282f6a24660c5318fac91e30ed51b520cdcee33 100755 --- a/tests/pressure-vessel/inside-runtime.py +++ b/tests/pressure-vessel/inside-runtime.py @@ -164,6 +164,26 @@ class TestInsideRuntime(BaseTest): with open('/run/host/container-manager', 'r') as reader: self.assertEqual(reader.read(), 'pressure-vessel\n') + self.assertIsNotNone(os.environ.get('LD_LIBRARY_PATH')) + + parts = os.environ.get('LD_LIBRARY_PATH', '').split(':') + + if os.getenv('TEST_INSIDE_RUNTIME_IS_COPY'): + for path in parts: + self.assertTrue(path.endswith('/aliases')) + else: + if ( + 'HOST_LD_LINUX_SO_REALPATH' in os.environ + and Path('/usr/lib/i386-linux-gnu').is_dir() + ): + self.assertIn('/overrides/lib/i386-linux-gnu', parts) + + if ( + 'HOST_LD_LINUX_X86_64_SO_REALPATH' in os.environ + and Path('/usr/lib/x86_64-linux-gnu').is_dir() + ): + self.assertIn('/overrides/lib/x86_64-linux-gnu', parts) + def test_overrides(self) -> None: if os.getenv('TEST_INSIDE_RUNTIME_IS_COPY'): target = os.readlink('/overrides') @@ -172,6 +192,62 @@ class TestInsideRuntime(BaseTest): self.assertTrue(Path('/overrides').is_dir()) self.assertTrue(Path('/overrides/lib').is_dir()) + if ( + 'HOST_LD_LINUX_SO_REALPATH' in os.environ + and Path('/usr/lib/i386-linux-gnu').is_dir() + ): + self.assertTrue(Path('/overrides/lib/i386-linux-gnu').is_dir()) + + if ( + 'HOST_LD_LINUX_X86_64_SO_REALPATH' in os.environ + and Path('/usr/lib/x86_64-linux-gnu').is_dir() + ): + self.assertTrue(Path('/overrides/lib/x86_64-linux-gnu').is_dir()) + + if os.getenv('TEST_INSIDE_RUNTIME_IS_COPY'): + # /etc/ld.so.* are symlinks to the mutable version + target = os.readlink('/etc/ld.so.cache') + self.assertEqual(target, '/run/pressure-vessel/ldso/ld.so.cache') + target = os.readlink('/etc/ld.so.conf') + self.assertEqual(target, '/run/pressure-vessel/ldso/ld.so.conf') + + # Exherbo compatibility symlinks also exist + target = os.readlink('/etc/ld-i686-pc-linux-gnu.cache') + self.assertEqual(target, '/run/pressure-vessel/ldso/ld.so.cache') + target = os.readlink('/etc/ld-x86_64-pc-linux-gnu.cache') + self.assertEqual(target, '/run/pressure-vessel/ldso/ld.so.cache') + # Clear Linux compatibility symlinks, too + target = os.readlink('/var/cache/ldconfig/ld.so.cache') + self.assertEqual(target, '/run/pressure-vessel/ldso/ld.so.cache') + + with open('/run/pressure-vessel/ldso/runtime-ld.so.conf') as reader: + lines_originally = reader.readlines() + + with open('/etc/ld.so.conf') as reader: + lines_used = reader.readlines() + + for line in lines_originally: + self.assertIn(line, lines_used) + + if ( + 'HOST_LD_LINUX_SO_REALPATH' in os.environ + and Path('/usr/lib/i386-linux-gnu').is_dir() + ): + self.assertIn( + '/usr/lib/pressure-vessel/overrides/lib/i386-linux-gnu\n', + lines_used, + ) + + if ( + 'HOST_LD_LINUX_X86_64_SO_REALPATH' in os.environ + and Path('/usr/lib/x86_64-linux-gnu').is_dir() + ): + self.assertIn( + ('/usr/lib/pressure-vessel/overrides/' + 'lib/x86_64-linux-gnu\n'), + lines_used, + ) + def test_glibc(self) -> None: glibc = ctypes.cdll.LoadLibrary('libc.so.6') gnu_get_libc_version = glibc.gnu_get_libc_version