diff --git a/debian/control b/debian/control index 4db9d42c50126e90e3d3119ac242cdf7a2f9787a..eb432da46c0315b5e1a8b2d96c272611dd957158 100644 --- a/debian/control +++ b/debian/control @@ -5,6 +5,7 @@ Maintainer: Simon McVittie <smcv@collabora.com> Standards-Version: 4.4.0 Build-Depends: debhelper (>= 9), + g++ (>= 4:4.8) | g++-4.8, libglib2.0-dev, libsteam-runtime-tools-0-dev (>= 0.20190926.0~), libxau-dev, diff --git a/debian/gitlab-ci.yml b/debian/gitlab-ci.yml index ac6ce36c75925cebb4e5835fd1fb90a0d6c0817b..889471f255d95d54a777a1f319cb57d15290bb72 100644 --- a/debian/gitlab-ci.yml +++ b/debian/gitlab-ci.yml @@ -131,6 +131,9 @@ build:scout: ${NULL+} fi + # g++ 4.6 is too old for the submodule (see also debian/rules) + export CXX=g++-4.8 + meson \ --prefix="$(pwd)/_build/prefix" \ -Dsrcdir=src \ diff --git a/debian/rules b/debian/rules index e5c89d9ff9ff4a524f0991354415edd9363435b4..20f722eaf75dc4ec912b2737c62a7f5b77ae69dc 100755 --- a/debian/rules +++ b/debian/rules @@ -7,6 +7,10 @@ export LC_ALL=C.UTF-8 include /usr/share/dpkg/default.mk +ifeq ($(shell dpkg --compare-versions `c++ -dumpversion || echo 0` ge 4.8 || echo old),old) +export CXX = g++-4.8 +endif + meson_options = ifeq ($(DEB_DISTRIBUTION),UNRELEASED) diff --git a/meson.build b/meson.build index c01943700924a4e0633e58a6dbde38500df81654..89efe5df86e2375f527a38b447d09c2b05c7bafa 100644 --- a/meson.build +++ b/meson.build @@ -24,10 +24,13 @@ project( 'pressure-vessel', 'c', default_options: [ + 'c_std=c99', + 'cpp_std=c++11', 'warning_level=2', ], ) +gnome = import('gnome') prove = find_program('prove', required : false) sh = find_program('sh', required : true) @@ -105,6 +108,7 @@ project_warning_cflags = [ project_include_dirs = include_directories( '.', + 'src', 'subprojects', ) @@ -176,6 +180,16 @@ add_project_arguments( language : 'c', ) +# Headers to scan for enum/flags types. +headers = [ + 'src/runtime.h', +] + +enums = gnome.mkenums_simple( + 'enumtypes', + sources : headers, +) + executable( 'pressure-vessel-with-lock', sources : [ @@ -222,12 +236,14 @@ executable( 'src/flatpak-utils-base-private.h', 'src/flatpak-utils.c', 'src/flatpak-utils-private.h', + 'src/runtime.c', + 'src/runtime.h', 'src/utils.c', 'src/utils.h', 'src/wrap.c', 'src/wrap-interactive.c', 'src/wrap-interactive.h', - ], + ] + enums, c_args : [ '-Wno-unused-local-typedefs', ], diff --git a/pressure-vessel-test-ui b/pressure-vessel-test-ui index a0b5270e96d5df9f1cef256a91ac29b42d631cc9..dbb93d93975ddb1c826c474a3a7d2e741a6b5bc4 100755 --- a/pressure-vessel-test-ui +++ b/pressure-vessel-test-ui @@ -61,6 +61,23 @@ def tristate_environment(name): return None +def boolean_environment(name, default): + # type: (str, bool) -> bool + value = os.getenv(name) + + if value is None: + return default + + if value == '1': + return True + + if value in ('', '0'): + return False + + logger.warning('Unrecognised value %r for $%s', value, name) + return default + + class Gui: def __init__(self): # type: (...) -> None @@ -145,6 +162,30 @@ class Gui: row += 1 + self.host_graphics_check = Gtk.CheckButton.new_with_label( + 'Use host-system graphics stack' + ) + value = boolean_environment('PRESSURE_VESSEL_HOST_GRAPHICS', True) + self.host_graphics_check.set_active(value) + self.grid.attach(self.host_graphics_check, 1, row, 1, 1) + row += 1 + + label = Gtk.Label.new('') + label.set_markup( + '<small><i>' + "Most games and GPUs won't work when this is disabled." + '</i></small>' + ) + label.set_halign(Gtk.Align.START) + label.set_line_wrap(True) + self.grid.attach(label, 1, row, 1, 1) + row += 1 + + self._container_runtime_changed(self.container_runtime_combo) + self.container_runtime_combo.connect( + 'changed', + self._container_runtime_changed) + self.unshare_home_check = Gtk.CheckButton.new_with_label( 'Use a separate home directory per game' ) @@ -258,6 +299,12 @@ class Gui: else: self.xterm_check.set_sensitive(True) + def _container_runtime_changed(self, combo): + if combo.get_active_id() == '/': + self.host_graphics_check.set_sensitive(False) + else: + self.host_graphics_check.set_sensitive(True) + def _describe_runtime( self, path # type: str @@ -362,6 +409,11 @@ class Gui: argv.append('--runtime') argv.append(os.path.join(id, 'files')) + if self.host_graphics_check.get_active(): + argv.append('--with-host-graphics') + else: + argv.append('--without-host-graphics') + if self.unshare_home_check.get_active(): argv.append('--unshare-home') else: diff --git a/src/bwrap.c b/src/bwrap.c index cadd9c83d53fd5056c707f9ff43dc67d66f60306..5f5e7701df7dfd9bec3d4466547caa3803ae3835 100644 --- a/src/bwrap.c +++ b/src/bwrap.c @@ -404,3 +404,19 @@ pv_bwrap_add_api_filesystems (FlatpakBwrap *bwrap) "--dev-bind", "/dev/shm", "/dev/shm", NULL); } + +FlatpakBwrap * +pv_bwrap_copy (FlatpakBwrap *bwrap) +{ + FlatpakBwrap *ret; + + g_return_val_if_fail (bwrap != NULL, NULL); + g_return_val_if_fail (!pv_bwrap_was_finished (bwrap), NULL); + /* bwrap can't own any fds, because if it did, + * flatpak_bwrap_append_bwrap() would steal them. */ + g_return_val_if_fail (bwrap->fds == NULL || bwrap->fds->len == 0, NULL); + + ret = flatpak_bwrap_new (NULL); + flatpak_bwrap_append_bwrap (ret, bwrap); + return ret; +} diff --git a/src/bwrap.h b/src/bwrap.h index 792db2a7e2c6b7653e949de7b021d85021f3d1c2..2a02a7923dfc6aad4036c97375135b7034090e62 100644 --- a/src/bwrap.h +++ b/src/bwrap.h @@ -49,3 +49,5 @@ pv_bwrap_was_finished (FlatpakBwrap *bwrap) return (bwrap->argv->len >= 1 && bwrap->argv->pdata[bwrap->argv->len - 1] == NULL); } + +FlatpakBwrap *pv_bwrap_copy (FlatpakBwrap *bwrap); diff --git a/src/runtime.c b/src/runtime.c new file mode 100644 index 0000000000000000000000000000000000000000..576c48eea33f0a0f992c015f65b7d631daf15c9d --- /dev/null +++ b/src/runtime.c @@ -0,0 +1,1863 @@ +/* + * Copyright © 2020 Collabora Ltd. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "src/runtime.h" + +#include <sysexits.h> + +#include <gio/gio.h> + +/* Include these before steam-runtime-tools.h so that their backport of + * G_DEFINE_AUTOPTR_CLEANUP_FUNC will be visible to it */ +#include "glib-backports.h" +#include "libglnx/libglnx.h" + +#include <steam-runtime-tools/steam-runtime-tools.h> + +#include "bwrap.h" +#include "bwrap-lock.h" +#include "enumtypes.h" +#include "flatpak-run-private.h" +#include "utils.h" + +/* + * PvRuntime: + * + * Object representing a runtime to be used as the /usr for a game. + */ + +struct _PvRuntime +{ + GObject parent; + + gchar *bubblewrap; + gchar *source_files; + gchar *tools_dir; + PvBwrapLock *runtime_lock; + + gchar *tmpdir; + gchar *overrides; + gchar *overrides_bin; + gchar *container_access; + FlatpakBwrap *container_access_adverb; + gchar *runtime_usr; /* either source_files or that + "/usr" */ + + PvRuntimeFlags flags; + gboolean any_libc_from_host; + gboolean all_libc_from_host; +}; + +struct _PvRuntimeClass +{ + GObjectClass parent; +}; + +enum { + PROP_0, + PROP_BUBBLEWRAP, + PROP_FLAGS, + PROP_SOURCE_FILES, + PROP_TOOLS_DIRECTORY, + N_PROPERTIES +}; + +static GParamSpec *properties[N_PROPERTIES] = { NULL }; + +static void pv_runtime_initable_iface_init (GInitableIface *iface, + gpointer unused); + +G_DEFINE_TYPE_WITH_CODE (PvRuntime, pv_runtime, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + pv_runtime_initable_iface_init)) + +/* + * Supported Debian-style multiarch tuples + */ +static const char * const multiarch_tuples[] = +{ + "x86_64-linux-gnu", + "i386-linux-gnu", + NULL +}; + +/* + * Directories other than /usr/lib that we must search for loadable + * modules, in the same order as multiarch_tuples + */ +static const char * const libquals[] = +{ + "lib64", + "lib32" +}; + +static gboolean pv_runtime_use_host_graphics_stack (PvRuntime *self, + FlatpakBwrap *bwrap, + GError **error); +static void pv_runtime_set_search_paths (PvRuntime *self, + FlatpakBwrap *bwrap); + +static void +pv_runtime_init (PvRuntime *self) +{ + self->any_libc_from_host = FALSE; + self->all_libc_from_host = FALSE; +} + +static void +pv_runtime_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + PvRuntime *self = PV_RUNTIME (object); + + switch (prop_id) + { + case PROP_BUBBLEWRAP: + g_value_set_string (value, self->bubblewrap); + break; + + case PROP_FLAGS: + g_value_set_flags (value, self->flags); + break; + + case PROP_SOURCE_FILES: + g_value_set_string (value, self->source_files); + break; + + case PROP_TOOLS_DIRECTORY: + g_value_set_string (value, self->tools_dir); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +pv_runtime_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + PvRuntime *self = PV_RUNTIME (object); + + switch (prop_id) + { + case PROP_BUBBLEWRAP: + /* Construct-only */ + g_return_if_fail (self->bubblewrap == NULL); + self->bubblewrap = g_value_dup_string (value); + break; + + case PROP_FLAGS: + self->flags = g_value_get_flags (value); + break; + + case PROP_SOURCE_FILES: + /* Construct-only */ + g_return_if_fail (self->source_files == NULL); + self->source_files = g_value_dup_string (value); + break; + + case PROP_TOOLS_DIRECTORY: + /* Construct-only */ + g_return_if_fail (self->tools_dir == NULL); + self->tools_dir = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +pv_runtime_constructed (GObject *object) +{ + PvRuntime *self = PV_RUNTIME (object); + + G_OBJECT_CLASS (pv_runtime_parent_class)->constructed (object); + + g_return_if_fail (self->bubblewrap != NULL); + g_return_if_fail (self->source_files != NULL); + g_return_if_fail (self->tools_dir != NULL); +} + +static gboolean +pv_runtime_initable_init (GInitable *initable, + GCancellable *cancellable G_GNUC_UNUSED, + GError **error) +{ + PvRuntime *self = PV_RUNTIME (initable); + g_autofree gchar *files_ref = NULL; + + g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!g_file_test (self->bubblewrap, G_FILE_TEST_IS_EXECUTABLE)) + { + return glnx_throw (error, "\"%s\" is not executable", + self->bubblewrap); + } + + if (!g_file_test (self->source_files, G_FILE_TEST_IS_DIR)) + { + return glnx_throw (error, "\"%s\" is not a directory", + self->source_files); + } + + if (!g_file_test (self->tools_dir, G_FILE_TEST_IS_DIR)) + { + return glnx_throw (error, "\"%s\" is not a directory", + self->tools_dir); + } + + /* Take a lock on the runtime until we're finished with setup, + * to make sure it doesn't get deleted. */ + files_ref = g_build_filename (self->source_files, ".ref", NULL); + self->runtime_lock = pv_bwrap_lock_new (files_ref, + PV_BWRAP_LOCK_FLAGS_CREATE, + error); + + /* If the runtime is being deleted, ... don't use it, I suppose? */ + if (self->runtime_lock == NULL) + return FALSE; + + g_debug ("Creating temporary directories..."); + + /* Using a runtime requires a temporary directory */ + self->tmpdir = g_dir_make_tmp ("pressure-vessel-wrap.XXXXXX", error); + + if (self->tmpdir == NULL) + return FALSE; + + self->overrides = g_build_filename (self->tmpdir, "overrides", NULL); + g_mkdir (self->overrides, 0700); + self->overrides_bin = g_build_filename (self->overrides, "bin", NULL); + g_mkdir (self->overrides_bin, 0700); + + self->runtime_usr = g_build_filename (self->source_files, "usr", NULL); + + if (!g_file_test (self->runtime_usr, G_FILE_TEST_IS_DIR)) + { + /* source_files is just a merged /usr. */ + g_free (self->runtime_usr); + self->runtime_usr = g_strdup (self->source_files); + } + + return TRUE; +} + +void +pv_runtime_cleanup (PvRuntime *self) +{ + g_autoptr(GError) local_error = NULL; + + g_return_if_fail (PV_IS_RUNTIME (self)); + + if (self->tmpdir != NULL && + !glnx_shutil_rm_rf_at (-1, self->tmpdir, NULL, &local_error)) + { + g_warning ("Unable to delete temporary directory: %s", + local_error->message); + } + + g_clear_pointer (&self->overrides_bin, g_free); + g_clear_pointer (&self->overrides, g_free); + g_clear_pointer (&self->container_access, g_free); + g_clear_pointer (&self->container_access_adverb, flatpak_bwrap_free); + g_clear_pointer (&self->tmpdir, g_free); +} + +static void +pv_runtime_finalize (GObject *object) +{ + PvRuntime *self = PV_RUNTIME (object); + + pv_runtime_cleanup (self); + g_free (self->bubblewrap); + g_free (self->runtime_usr); + g_free (self->source_files); + g_free (self->tools_dir); + pv_bwrap_lock_free (self->runtime_lock); + + G_OBJECT_CLASS (pv_runtime_parent_class)->finalize (object); +} + +static void +pv_runtime_class_init (PvRuntimeClass *cls) +{ + GObjectClass *object_class = G_OBJECT_CLASS (cls); + + object_class->get_property = pv_runtime_get_property; + object_class->set_property = pv_runtime_set_property; + object_class->constructed = pv_runtime_constructed; + object_class->finalize = pv_runtime_finalize; + + properties[PROP_BUBBLEWRAP] = + g_param_spec_string ("bubblewrap", "Bubblewrap", + "Bubblewrap executable", + NULL, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + properties[PROP_FLAGS] = + g_param_spec_flags ("flags", "Flags", + "Flags affecting how we set up the runtime", + PV_TYPE_RUNTIME_FLAGS, PV_RUNTIME_FLAGS_NONE, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + properties[PROP_SOURCE_FILES] = + g_param_spec_string ("source-files", "Source files", + ("Path to read-only runtime files (merged-/usr " + "or sysroot) on host system"), + NULL, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + properties[PROP_TOOLS_DIRECTORY] = + g_param_spec_string ("tools-directory", "Tools directory", + "Path to pressure-vessel/bin on host system", + NULL, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPERTIES, properties); +} + +PvRuntime * +pv_runtime_new (const char *source_files, + const char *bubblewrap, + const char *tools_dir, + PvRuntimeFlags flags, + GError **error) +{ + g_return_val_if_fail (source_files != NULL, NULL); + g_return_val_if_fail (bubblewrap != NULL, NULL); + g_return_val_if_fail (tools_dir != NULL, NULL); + g_return_val_if_fail ((flags & ~(PV_RUNTIME_FLAGS_HOST_GRAPHICS_STACK)) == 0, + NULL); + + return g_initable_new (PV_TYPE_RUNTIME, + NULL, + error, + "bubblewrap", bubblewrap, + "source-files", source_files, + "tools-directory", tools_dir, + "flags", flags, + NULL); +} + +/* If we are using a runtime, pass the lock fd to the executed process, + * and make it act as a subreaper for the game itself. + * + * If we were using --unshare-pid then we could use bwrap --sync-fd + * and rely on bubblewrap's init process for this, but we currently + * can't do that without breaking gameoverlayrender.so's assumptions. */ +void +pv_runtime_append_lock_adverb (PvRuntime *self, + FlatpakBwrap *bwrap) +{ + g_return_if_fail (PV_IS_RUNTIME (self)); + g_return_if_fail (!pv_bwrap_was_finished (bwrap)); + + flatpak_bwrap_add_args (bwrap, + "/run/pressure-vessel/bin/pressure-vessel-with-lock", + "--subreaper", + NULL); + + if (pv_bwrap_lock_is_ofd (self->runtime_lock)) + { + int fd = pv_bwrap_lock_steal_fd (self->runtime_lock); + g_autofree gchar *fd_str = NULL; + + g_debug ("Passing lock fd %d down to with-lock", fd); + flatpak_bwrap_add_fd (bwrap, fd); + fd_str = g_strdup_printf ("%d", fd); + flatpak_bwrap_add_args (bwrap, + "--fd", fd_str, + NULL); + } + else + { + /* + * We were unable to take out an open file descriptor lock, + * so it will be released on fork(). Tell the with-lock process + * to take out its own compatible lock instead. There will be + * a short window during which we have lost our lock but the + * with-lock process has not taken its lock - that's unavoidable + * if we want to use exec() to replace ourselves with the + * container. + * + * pv_bwrap_bind_usr() arranges for /.ref to either be a + * symbolic link to /usr/.ref which is the runtime_lock + * (if opt_runtime is a merged /usr), or the runtime_lock + * itself (otherwise). + */ + g_debug ("Telling process in container to lock /.ref"); + flatpak_bwrap_add_args (bwrap, + "--lock-file", "/.ref", + NULL); + } + + flatpak_bwrap_add_args (bwrap, + "--", + NULL); +} + +/* + * Set self->container_access_adverb to a (possibly empty) command prefix + * that will result in the container being available at + * self->container_access, with write access to self->overrides, and + * read-only access to everything else. + */ +static gboolean +pv_runtime_provide_container_access (PvRuntime *self, + GError **error) +{ + /* TODO: Avoid using bwrap if we don't need to: when run from inside + * a Flatpak, it won't work. + * + * If we are working with a non-merged-/usr runtime, we can just + * set self->container_access to its path. + * + * Similarly, if we are working with a writeable copy of a runtime + * that we are editing in-place, we can set self->container_access to + * that. */ + + if (self->container_access_adverb == NULL) + { + self->container_access = g_build_filename (self->tmpdir, "mnt", NULL); + + g_mkdir (self->container_access, 0700); + self->container_access = self->container_access; + + self->container_access_adverb = flatpak_bwrap_new (NULL); + flatpak_bwrap_add_args (self->container_access_adverb, + self->bubblewrap, + "--ro-bind", "/", "/", + "--bind", self->overrides, self->overrides, + "--tmpfs", self->container_access, + NULL); + if (!pv_bwrap_bind_usr (self->container_access_adverb, + self->source_files, + self->container_access, + error)) + return FALSE; + } + + return TRUE; +} + +static gboolean +try_bind_dri (PvRuntime *self, + FlatpakBwrap *bwrap, + const char *tool_path, + const char *libdir, + const char *libdir_on_host, + GError **error) +{ + g_autofree gchar *dri = g_build_filename (libdir, "dri", NULL); + g_autofree gchar *s2tc = g_build_filename (libdir, "libtxc_dxtn.so", NULL); + + if (g_file_test (dri, G_FILE_TEST_IS_DIR)) + { + g_autoptr(FlatpakBwrap) temp_bwrap = NULL; + g_autofree gchar *expr = NULL; + g_autofree gchar *host_dri = NULL; + g_autofree gchar *dest_dri = NULL; + + expr = g_strdup_printf ("only-dependencies:if-exists:path-match:%s/dri/*.so", + libdir); + + if (!pv_runtime_provide_container_access (self, error)) + return FALSE; + + temp_bwrap = pv_bwrap_copy (self->container_access_adverb); + flatpak_bwrap_add_args (temp_bwrap, + tool_path, + "--container", self->container_access, + "--link-target", "/run/host", + "--dest", libdir_on_host, + "--provider", "/", + expr, + NULL); + flatpak_bwrap_finish (temp_bwrap); + + if (!pv_bwrap_run_sync (temp_bwrap, NULL, error)) + return FALSE; + + g_clear_pointer (&temp_bwrap, flatpak_bwrap_free); + + /* TODO: If we're already in a container, rely on /run/host + * already being mounted, so we don't need to re-enter a container + * here. */ + host_dri = g_build_filename ("/run/host", libdir, "dri", NULL); + dest_dri = g_build_filename (libdir_on_host, "dri", NULL); + temp_bwrap = flatpak_bwrap_new (NULL); + flatpak_bwrap_add_args (temp_bwrap, + self->bubblewrap, + "--ro-bind", "/", "/", + "--tmpfs", "/run", + "--ro-bind", "/", "/run/host", + "--bind", self->overrides, self->overrides, + "sh", "-c", + "ln -fns \"$1\"/* \"$2\"", + "sh", /* $0 */ + host_dri, + dest_dri, + NULL); + flatpak_bwrap_finish (temp_bwrap); + + if (!pv_bwrap_run_sync (temp_bwrap, NULL, error)) + return FALSE; + } + + if (g_file_test (s2tc, G_FILE_TEST_EXISTS)) + { + g_autoptr(FlatpakBwrap) temp_bwrap = NULL; + g_autofree gchar *expr = NULL; + + expr = g_strdup_printf ("path-match:%s", s2tc); + + if (!pv_runtime_provide_container_access (self, error)) + return FALSE; + + temp_bwrap = pv_bwrap_copy (self->container_access_adverb); + flatpak_bwrap_add_args (temp_bwrap, + tool_path, + "--container", self->container_access, + "--link-target", "/run/host", + "--dest", libdir_on_host, + "--provider", "/", + expr, + NULL); + flatpak_bwrap_finish (temp_bwrap); + + if (!pv_bwrap_run_sync (temp_bwrap, NULL, error)) + return FALSE; + } + + return TRUE; +} + +/* + * Try to make sure we have all the locales we need, by running + * the helper from steam-runtime-tools in the container. If this + * fails, it isn't fatal - carry on anyway. + * + * @bwrap must be set up to have the same libc that we will be using + * for the container. + */ +static void +ensure_locales (PvRuntime *self, + gboolean on_host, + FlatpakBwrap *bwrap) +{ + g_autoptr(GError) local_error = NULL; + g_autoptr(FlatpakBwrap) run_locale_gen = NULL; + g_autofree gchar *locale_gen = NULL; + g_autofree gchar *locales = g_build_filename (self->overrides, "locales", NULL); + g_autoptr(GDir) dir = NULL; + int exit_status; + + /* bwrap can't own any fds yet, because if it did, + * flatpak_bwrap_append_bwrap() would steal them. */ + g_return_if_fail (bwrap->fds == NULL || bwrap->fds->len == 0); + + g_mkdir (locales, 0700); + + run_locale_gen = flatpak_bwrap_new (NULL); + + if (on_host) + { + locale_gen = g_build_filename (self->tools_dir, + "pressure-vessel-locale-gen", + NULL); + + flatpak_bwrap_add_args (run_locale_gen, + self->bubblewrap, + "--ro-bind", "/", "/", + NULL); + pv_bwrap_add_api_filesystems (run_locale_gen); + flatpak_bwrap_add_args (run_locale_gen, + "--bind", locales, locales, + "--chdir", locales, + locale_gen, + "--verbose", + NULL); + } + else + { + locale_gen = g_build_filename ("/run/host/tools", + "pressure-vessel-locale-gen", + NULL); + + flatpak_bwrap_append_bwrap (run_locale_gen, bwrap); + pv_bwrap_copy_tree (run_locale_gen, self->overrides, "/overrides"); + + if (!flatpak_bwrap_bundle_args (run_locale_gen, 1, -1, FALSE, + &local_error)) + { + g_warning ("Unable to set up locale-gen command: %s", + local_error->message); + g_clear_error (&local_error); + } + + flatpak_bwrap_add_args (run_locale_gen, + "--ro-bind", self->tools_dir, "/run/host/tools", + "--bind", locales, "/overrides/locales", + "--chdir", "/overrides/locales", + locale_gen, + "--verbose", + NULL); + } + + flatpak_bwrap_finish (run_locale_gen); + + /* locale-gen exits 72 (EX_OSFILE) if it had to correct for + * missing locales at OS level. This is not an error. */ + if (!pv_bwrap_run_sync (run_locale_gen, &exit_status, &local_error)) + { + if (exit_status == EX_OSFILE) + g_debug ("pressure-vessel-locale-gen created missing locales"); + else + g_warning ("Unable to generate locales: %s", local_error->message); + + g_clear_error (&local_error); + } + else + { + g_debug ("No locales generated"); + } + + dir = g_dir_open (locales, 0, NULL); + + /* If the directory is not empty, make it the container's LOCPATH */ + if (dir != NULL && g_dir_read_name (dir) != NULL) + { + g_autoptr(GString) locpath = NULL; + + g_debug ("%s is non-empty", locales); + + locpath = g_string_new ("/overrides/locales"); + pv_search_path_append (locpath, g_getenv ("LOCPATH")); + flatpak_bwrap_add_args (bwrap, + "--setenv", "LOCPATH", locpath->str, + NULL); + } + else + { + g_debug ("%s is empty", locales); + } +} + +typedef enum +{ + ICD_KIND_NONEXISTENT, + ICD_KIND_ABSOLUTE, + ICD_KIND_SONAME +} IcdKind; + +typedef struct +{ + /* (type SrtEglIcd) or (type SrtVulkanIcd) */ + gpointer icd; + gchar *resolved_library; + /* Last entry is always NONEXISTENT */ + IcdKind kinds[G_N_ELEMENTS (multiarch_tuples)]; + /* Last entry is always NULL */ + gchar *paths_in_container[G_N_ELEMENTS (multiarch_tuples)]; +} IcdDetails; + +static IcdDetails * +icd_details_new (gpointer icd) +{ + IcdDetails *self; + gsize i; + + g_return_val_if_fail (G_IS_OBJECT (icd), NULL); + g_return_val_if_fail (SRT_IS_EGL_ICD (icd) || SRT_IS_VULKAN_ICD (icd), + NULL); + + self = g_slice_new0 (IcdDetails); + self->icd = g_object_ref (icd); + self->resolved_library = NULL; + + for (i = 0; i < G_N_ELEMENTS (multiarch_tuples); i++) + { + self->kinds[i] = ICD_KIND_NONEXISTENT; + self->paths_in_container[i] = NULL; + } + + return self; +} + +static void +icd_details_free (IcdDetails *self) +{ + gsize i; + + g_object_unref (self->icd); + g_free (self->resolved_library); + + for (i = 0; i < G_N_ELEMENTS (multiarch_tuples); i++) + g_free (self->paths_in_container[i]); + + g_slice_free (IcdDetails, self); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (IcdDetails, icd_details_free) + +static gboolean +bind_icd (PvRuntime *self, + gsize multiarch_index, + gsize sequence_number, + const char *tool_path, + const char *libdir_on_host, + const char *libdir_in_container, + const char *subdir, + IcdDetails *details, + GError **error) +{ + static const char options[] = "if-exists:if-same-abi"; + g_autofree gchar *on_host = NULL; + g_autofree gchar *pattern = NULL; + g_autofree gchar *dependency_pattern = NULL; + g_autofree gchar *seq_str = NULL; + const char *mode; + g_autoptr(FlatpakBwrap) temp_bwrap = NULL; + + g_return_val_if_fail (tool_path != NULL, FALSE); + g_return_val_if_fail (libdir_on_host != NULL, FALSE); + g_return_val_if_fail (subdir != NULL, FALSE); + g_return_val_if_fail (multiarch_index < G_N_ELEMENTS (multiarch_tuples) - 1, + FALSE); + g_return_val_if_fail (details != NULL, FALSE); + g_return_val_if_fail (details->resolved_library != NULL, FALSE); + g_return_val_if_fail (details->kinds[multiarch_index] == ICD_KIND_NONEXISTENT, + FALSE); + g_return_val_if_fail (details->paths_in_container[multiarch_index] == NULL, + FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (g_path_is_absolute (details->resolved_library)) + { + details->kinds[multiarch_index] = ICD_KIND_ABSOLUTE; + mode = "path"; + + /* Because the ICDs might have collisions among their + * basenames (might differ only by directory), we put each + * in its own numbered directory. */ + seq_str = g_strdup_printf ("%" G_GSIZE_FORMAT, sequence_number); + on_host = g_build_filename (libdir_on_host, subdir, seq_str, NULL); + + g_debug ("Ensuring %s exists", on_host); + + if (g_mkdir_with_parents (on_host, 0700) != 0) + return glnx_throw_errno_prefix (error, "Unable to create %s", on_host); + } + else + { + /* ICDs in the default search path by definition can't collide: + * one of them is the first one we find, and we use that one. */ + details->kinds[multiarch_index] = ICD_KIND_SONAME; + mode = "soname"; + } + + pattern = g_strdup_printf ("no-dependencies:even-if-older:%s:%s:%s", + options, mode, details->resolved_library); + dependency_pattern = g_strdup_printf ("only-dependencies:%s:%s:%s", + options, mode, details->resolved_library); + + if (!pv_runtime_provide_container_access (self, error)) + return FALSE; + + temp_bwrap = pv_bwrap_copy (self->container_access_adverb); + flatpak_bwrap_add_args (temp_bwrap, + tool_path, + "--container", self->container_access, + "--link-target", "/run/host", + "--dest", on_host == NULL ? libdir_on_host : on_host, + "--provider", "/", + pattern, + NULL); + flatpak_bwrap_finish (temp_bwrap); + + if (!pv_bwrap_run_sync (temp_bwrap, NULL, error)) + return FALSE; + + g_clear_pointer (&temp_bwrap, flatpak_bwrap_free); + + if (on_host != NULL) + { + /* Try to remove the directory we created. If it succeeds, then we + * can optimize slightly by not capturing the dependencies: there's + * no point, because we know we didn't create a symlink to the ICD + * itself. (It must have been nonexistent or for a different ABI.) */ + if (g_rmdir (on_host) == 0) + { + details->kinds[multiarch_index] = ICD_KIND_NONEXISTENT; + return TRUE; + } + } + + temp_bwrap = pv_bwrap_copy (self->container_access_adverb); + flatpak_bwrap_add_args (temp_bwrap, + tool_path, + "--container", self->container_access, + "--link-target", "/run/host", + "--dest", libdir_on_host, + "--provider", "/", + dependency_pattern, + NULL); + flatpak_bwrap_finish (temp_bwrap); + + if (!pv_bwrap_run_sync (temp_bwrap, NULL, error)) + return FALSE; + + g_clear_pointer (&temp_bwrap, flatpak_bwrap_free); + + if (details->kinds[multiarch_index] == ICD_KIND_ABSOLUTE) + { + g_assert (seq_str != NULL); + g_assert (on_host != NULL); + details->paths_in_container[multiarch_index] = g_build_filename (libdir_in_container, + subdir, + seq_str, + glnx_basename (details->resolved_library), + NULL); + } + + return TRUE; +} + +static gboolean +bind_runtime (PvRuntime *self, + FlatpakBwrap *bwrap, + GError **error) +{ + static const char * const bind_mutable[] = + { + "etc", + "var/cache", + "var/lib" + }; + static const char * const dont_bind[] = + { + "/etc/group", + "/etc/passwd", + "/etc/host.conf", + "/etc/hosts", + "/etc/localtime", + "/etc/machine-id", + "/etc/resolv.conf", + "/var/lib/dbus", + "/var/lib/dhcp", + "/var/lib/sudo", + "/var/lib/urandom", + NULL + }; + g_autofree gchar *xrd = g_strdup_printf ("/run/user/%ld", (long) geteuid ()); + gsize i; + const gchar *member; + + g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE); + g_return_val_if_fail (!pv_bwrap_was_finished (bwrap), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!pv_bwrap_bind_usr (bwrap, self->source_files, "/", error)) + return FALSE; + + flatpak_bwrap_add_args (bwrap, + "--setenv", "XDG_RUNTIME_DIR", xrd, + "--tmpfs", "/run", + "--tmpfs", "/tmp", + "--tmpfs", "/var", + "--symlink", "../run", "/var/run", + NULL); + + if (!pv_bwrap_bind_usr (bwrap, "/", "/run/host", error)) + return FALSE; + + for (i = 0; i < G_N_ELEMENTS (bind_mutable); i++) + { + g_autofree gchar *path = g_build_filename (self->source_files, + bind_mutable[i], + NULL); + g_autoptr(GDir) dir = NULL; + + dir = g_dir_open (path, 0, NULL); + + if (dir == NULL) + continue; + + for (member = g_dir_read_name (dir); + member != NULL; + member = g_dir_read_name (dir)) + { + g_autofree gchar *dest = g_build_filename ("/", bind_mutable[i], + member, NULL); + g_autofree gchar *full = NULL; + g_autofree gchar *target = NULL; + + if (g_strv_contains (dont_bind, dest)) + continue; + + full = g_build_filename (self->source_files, + bind_mutable[i], + member, + NULL); + target = glnx_readlinkat_malloc (-1, full, NULL, NULL); + + if (target != NULL) + flatpak_bwrap_add_args (bwrap, "--symlink", target, dest, NULL); + else + flatpak_bwrap_add_args (bwrap, "--ro-bind", full, dest, NULL); + } + } + + if (g_file_test ("/etc/machine-id", G_FILE_TEST_EXISTS)) + { + flatpak_bwrap_add_args (bwrap, + "--ro-bind", "/etc/machine-id", "/etc/machine-id", + "--symlink", "/etc/machine-id", + "/var/lib/dbus/machine-id", + NULL); + } + else if (g_file_test ("/var/lib/dbus/machine-id", G_FILE_TEST_EXISTS)) + { + flatpak_bwrap_add_args (bwrap, + "--ro-bind", "/var/lib/dbus/machine-id", + "/etc/machine-id", + "--symlink", "/etc/machine-id", + "/var/lib/dbus/machine-id", + NULL); + } + + if (g_file_test ("/etc/resolv.conf", G_FILE_TEST_EXISTS)) + flatpak_bwrap_add_args (bwrap, + "--ro-bind", "/etc/resolv.conf", "/etc/resolv.conf", + NULL); + if (g_file_test ("/etc/host.conf", G_FILE_TEST_EXISTS)) + flatpak_bwrap_add_args (bwrap, + "--ro-bind", "/etc/host.conf", "/etc/host.conf", + NULL); + if (g_file_test ("/etc/hosts", G_FILE_TEST_EXISTS)) + flatpak_bwrap_add_args (bwrap, + "--ro-bind", "/etc/hosts", "/etc/hosts", + NULL); + + /* TODO: Synthesize a passwd with only the user and nobody, + * like Flatpak does? */ + if (g_file_test ("/etc/passwd", G_FILE_TEST_EXISTS)) + flatpak_bwrap_add_args (bwrap, + "--ro-bind", "/etc/passwd", "/etc/passwd", + NULL); + if (g_file_test ("/etc/group", G_FILE_TEST_EXISTS)) + flatpak_bwrap_add_args (bwrap, + "--ro-bind", "/etc/group", "/etc/group", + NULL); + + if (self->flags & PV_RUNTIME_FLAGS_HOST_GRAPHICS_STACK) + { + if (!pv_runtime_use_host_graphics_stack (self, bwrap, error)) + return FALSE; + } + + /* This needs to be done after pv_runtime_use_host_graphics_stack() + * has decided whether to bring in the host system's libc. */ + ensure_locales (self, self->any_libc_from_host, bwrap); + + /* These can add data fds to @bwrap, so they must come last - after + * other functions stop using @bwrap as a basis for their own bwrap + * invocations with flatpak_bwrap_append_bwrap(). + * Otherwise, when flatpak_bwrap_append_bwrap() calls + * flatpak_bwrap_steal_fds(), it will make the original FlatpakBwrap + * unusable. */ + + flatpak_run_add_wayland_args (bwrap); + flatpak_run_add_x11_args (bwrap, TRUE); + flatpak_run_add_pulseaudio_args (bwrap); + flatpak_run_add_session_dbus_args (bwrap); + flatpak_run_add_system_dbus_args (bwrap); + pv_bwrap_copy_tree (bwrap, self->overrides, "/overrides"); + + /* /etc/localtime and /etc/resolv.conf can not exist (or be symlinks to + * non-existing targets), in which case we don't want to attempt to create + * bogus symlinks or bind mounts, as that will cause flatpak run to fail. + */ + if (g_file_test ("/etc/localtime", G_FILE_TEST_EXISTS)) + { + g_autofree char *target = NULL; + gboolean is_reachable = FALSE; + g_autofree char *tz = flatpak_get_timezone (); + g_autofree char *timezone_content = g_strdup_printf ("%s\n", tz); + + target = glnx_readlinkat_malloc (-1, "/etc/localtime", NULL, NULL); + + if (target != NULL) + { + g_autoptr(GFile) base_file = NULL; + g_autoptr(GFile) target_file = NULL; + g_autofree char *target_canonical = NULL; + + base_file = g_file_new_for_path ("/etc"); + target_file = g_file_resolve_relative_path (base_file, target); + target_canonical = g_file_get_path (target_file); + + is_reachable = g_str_has_prefix (target_canonical, "/usr/"); + } + + if (is_reachable) + { + flatpak_bwrap_add_args (bwrap, + "--symlink", target, "/etc/localtime", + NULL); + } + else + { + flatpak_bwrap_add_args (bwrap, + "--ro-bind", "/etc/localtime", "/etc/localtime", + NULL); + } + + flatpak_bwrap_add_args_data (bwrap, "timezone", + timezone_content, -1, "/etc/timezone", + NULL); + } + + return TRUE; +} + +static gboolean +pv_runtime_use_host_graphics_stack (PvRuntime *self, + FlatpakBwrap *bwrap, + GError **error) +{ + gsize i, j; + g_autoptr(GString) dri_path = g_string_new (""); + g_autoptr(GString) egl_path = g_string_new (""); + g_autoptr(GString) vulkan_path = g_string_new (""); + gboolean any_architecture_works = FALSE; + g_autofree gchar *localedef = NULL; + g_autofree gchar *dir_on_host = NULL; + g_autoptr(SrtSystemInfo) system_info = srt_system_info_new (NULL); + g_autoptr(SrtObjectList) egl_icds = NULL; + g_autoptr(SrtObjectList) vulkan_icds = NULL; + g_autoptr(GPtrArray) egl_icd_details = NULL; /* (element-type IcdDetails) */ + g_autoptr(GPtrArray) vulkan_icd_details = NULL; /* (element-type IcdDetails) */ + guint n_egl_icds; + guint n_vulkan_icds; + const GList *icd_iter; + gboolean all_libdrm_from_host = TRUE; + g_autoptr(GHashTable) libdrm_data_from_host = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + g_autofree gchar *libdrm_data_in_runtime = NULL; + g_autofree gchar *best_libdrm_data_from_host = NULL; + + g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE); + g_return_val_if_fail (!pv_bwrap_was_finished (bwrap), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (!pv_runtime_provide_container_access (self, error)) + return FALSE; + + g_debug ("Enumerating EGL ICDs on host system..."); + egl_icds = srt_system_info_list_egl_icds (system_info, multiarch_tuples); + n_egl_icds = g_list_length (egl_icds); + + egl_icd_details = g_ptr_array_new_full (n_egl_icds, + (GDestroyNotify) G_CALLBACK (icd_details_free)); + + for (icd_iter = egl_icds, j = 0; + icd_iter != NULL; + icd_iter = icd_iter->next, j++) + { + SrtEglIcd *icd = icd_iter->data; + const gchar *path = srt_egl_icd_get_json_path (icd); + GError *local_error = NULL; + + if (!srt_egl_icd_check_error (icd, &local_error)) + { + g_debug ("Failed to load EGL ICD #%" G_GSIZE_FORMAT " from %s: %s", + j, path, local_error->message); + g_clear_error (&local_error); + continue; + } + + g_debug ("EGL ICD #%" G_GSIZE_FORMAT " at %s: %s", + j, path, srt_egl_icd_get_library_path (icd)); + + g_ptr_array_add (egl_icd_details, icd_details_new (icd)); + } + + g_debug ("Enumerating Vulkan ICDs on host system..."); + vulkan_icds = srt_system_info_list_vulkan_icds (system_info, + multiarch_tuples); + n_vulkan_icds = g_list_length (vulkan_icds); + + vulkan_icd_details = g_ptr_array_new_full (n_vulkan_icds, + (GDestroyNotify) G_CALLBACK (icd_details_free)); + + for (icd_iter = vulkan_icds, j = 0; + icd_iter != NULL; + icd_iter = icd_iter->next, j++) + { + SrtVulkanIcd *icd = icd_iter->data; + const gchar *path = srt_vulkan_icd_get_json_path (icd); + GError *local_error = NULL; + + if (!srt_vulkan_icd_check_error (icd, &local_error)) + { + g_debug ("Failed to load Vulkan ICD #%" G_GSIZE_FORMAT " from %s: %s", + j, path, local_error->message); + g_clear_error (&local_error); + continue; + } + + g_debug ("Vulkan ICD #%" G_GSIZE_FORMAT " at %s: %s", + j, path, srt_vulkan_icd_get_library_path (icd)); + + g_ptr_array_add (vulkan_icd_details, icd_details_new (icd)); + } + + g_assert (multiarch_tuples[G_N_ELEMENTS (multiarch_tuples) - 1] == NULL); + + /* We set this FALSE later if we decide not to use the host libc for + * some architecture. */ + self->all_libc_from_host = TRUE; + + for (i = 0; i < G_N_ELEMENTS (multiarch_tuples) - 1; i++) + { + g_autofree gchar *tool = g_strdup_printf ("%s-capsule-capture-libs", + multiarch_tuples[i]); + g_autofree gchar *tool_path = NULL; + g_autofree gchar *ld_so = NULL; + const gchar *argv[] = { NULL, "--print-ld.so", NULL }; + + g_debug ("Checking for %s libraries...", multiarch_tuples[i]); + + tool_path = g_build_filename (self->tools_dir, tool, NULL); + argv[0] = tool_path; + + /* This has the side-effect of testing whether we can run binaries + * for this architecture on the host system. */ + ld_so = pv_capture_output (argv, NULL); + + if (ld_so != NULL) + { + g_auto(GStrv) dirs = NULL; + g_autoptr(FlatpakBwrap) temp_bwrap = NULL; + g_autofree gchar *libdir_in_container = g_build_filename ("/overrides", + "lib", + multiarch_tuples[i], + NULL); + g_autofree gchar *libdir_on_host = g_build_filename (self->overrides, "lib", + multiarch_tuples[i], + NULL); + g_autofree gchar *this_dri_path_on_host = g_build_filename (libdir_on_host, + "dri", NULL); + g_autofree gchar *this_dri_path_in_container = g_build_filename (libdir_in_container, + "dri", NULL); + g_autofree gchar *libc = NULL; + g_autofree gchar *ld_so_in_runtime = NULL; + g_autofree gchar *libdrm = NULL; + const gchar *libqual = NULL; + + temp_bwrap = flatpak_bwrap_new (NULL); + flatpak_bwrap_add_args (temp_bwrap, + self->bubblewrap, + NULL); + + if (!pv_bwrap_bind_usr (temp_bwrap, + self->source_files, + "/", + error)) + return FALSE; + + flatpak_bwrap_add_args (temp_bwrap, + "readlink", "-e", ld_so, + NULL); + flatpak_bwrap_finish (temp_bwrap); + + ld_so_in_runtime = pv_capture_output ((const char * const *) temp_bwrap->argv->pdata, + NULL); + + g_clear_pointer (&temp_bwrap, flatpak_bwrap_free); + + if (ld_so_in_runtime == NULL) + { + g_debug ("Container does not have %s so it cannot run " + "%s binaries", ld_so, multiarch_tuples[i]); + continue; + } + + any_architecture_works = TRUE; + g_debug ("Container path: %s -> %s", ld_so, ld_so_in_runtime); + + pv_search_path_append (dri_path, this_dri_path_in_container); + + g_mkdir_with_parents (libdir_on_host, 0755); + g_mkdir_with_parents (this_dri_path_on_host, 0755); + + g_debug ("Collecting GLX drivers from host system..."); + + temp_bwrap = pv_bwrap_copy (self->container_access_adverb); + flatpak_bwrap_add_args (temp_bwrap, + tool_path, + "--container", self->container_access, + "--link-target", "/run/host", + "--dest", libdir_on_host, + "--provider", "/", + "gl:", + NULL); + flatpak_bwrap_finish (temp_bwrap); + + if (!pv_bwrap_run_sync (temp_bwrap, NULL, error)) + return FALSE; + + g_clear_pointer (&temp_bwrap, flatpak_bwrap_free); + + temp_bwrap = pv_bwrap_copy (self->container_access_adverb); + flatpak_bwrap_add_args (temp_bwrap, + tool_path, + "--container", self->container_access, + "--link-target", "/run/host", + "--dest", libdir_on_host, + "--provider", "/", + "if-exists:even-if-older:soname-match:libEGL.so.*", + "if-exists:even-if-older:soname-match:libEGL_nvidia.so.*", + "if-exists:even-if-older:soname-match:libGL.so.*", + "if-exists:even-if-older:soname-match:libGLESv1_CM.so.*", + "if-exists:even-if-older:soname-match:libGLESv1_CM_nvidia.so.*", + "if-exists:even-if-older:soname-match:libGLESv2.so.*", + "if-exists:even-if-older:soname-match:libGLESv2_nvidia.so.*", + "if-exists:even-if-older:soname-match:libGLX.so.*", + "if-exists:even-if-older:soname-match:libGLX_nvidia.so.*", + "if-exists:even-if-older:soname-match:libGLX_indirect.so.*", + "if-exists:even-if-older:soname-match:libGLdispatch.so.*", + "if-exists:even-if-older:soname-match:libOpenGL.so.*", + "if-exists:even-if-older:soname-match:libcuda.so.*", + "if-exists:even-if-older:soname-match:libglx.so.*", + "if-exists:even-if-older:soname-match:libnvidia-cbl.so.*", + "if-exists:even-if-older:soname-match:libnvidia-cfg.so.*", + "if-exists:even-if-older:soname-match:libnvidia-compiler.so.*", + "if-exists:even-if-older:soname-match:libnvidia-egl-wayland.so.*", + "if-exists:even-if-older:soname-match:libnvidia-eglcore.so.*", + "if-exists:even-if-older:soname-match:libnvidia-encode.so.*", + "if-exists:even-if-older:soname-match:libnvidia-fatbinaryloader.so.*", + "if-exists:even-if-older:soname-match:libnvidia-fbc.so.*", + "if-exists:even-if-older:soname-match:libnvidia-glcore.so.*", + "if-exists:even-if-older:soname-match:libnvidia-glsi.so.*", + "if-exists:even-if-older:soname-match:libnvidia-glvkspirv.so.*", + "if-exists:even-if-older:soname-match:libnvidia-ifr.so.*", + "if-exists:even-if-older:soname-match:libnvidia-ml.so.*", + "if-exists:even-if-older:soname-match:libnvidia-opencl.so.*", + "if-exists:even-if-older:soname-match:libnvidia-opticalflow.so.*", + "if-exists:even-if-older:soname-match:libnvidia-ptxjitcompiler.so.*", + "if-exists:even-if-older:soname-match:libnvidia-rtcore.so.*", + "if-exists:even-if-older:soname-match:libnvidia-tls.so.*", + "if-exists:even-if-older:soname-match:libOpenCL.so.*", + "if-exists:even-if-older:soname-match:libvdpau_nvidia.so.*", + NULL); + flatpak_bwrap_finish (temp_bwrap); + + if (!pv_bwrap_run_sync (temp_bwrap, NULL, error)) + return FALSE; + + g_clear_pointer (&temp_bwrap, flatpak_bwrap_free); + + g_debug ("Collecting %s EGL drivers from host system...", + multiarch_tuples[i]); + + for (j = 0; j < egl_icd_details->len; j++) + { + IcdDetails *details = g_ptr_array_index (egl_icd_details, j); + SrtEglIcd *icd = SRT_EGL_ICD (details->icd); + + if (!srt_egl_icd_check_error (icd, NULL)) + continue; + + details->resolved_library = srt_egl_icd_resolve_library_path (icd); + g_assert (details->resolved_library != NULL); + + if (!bind_icd (self, + i, + j, + tool_path, + libdir_on_host, + libdir_in_container, + "glvnd", + details, + error)) + return FALSE; + } + + g_debug ("Collecting %s Vulkan drivers from host system...", + multiarch_tuples[i]); + + temp_bwrap = pv_bwrap_copy (self->container_access_adverb); + flatpak_bwrap_add_args (temp_bwrap, + tool_path, + "--container", self->container_access, + "--link-target", "/run/host", + "--dest", libdir_on_host, + "--provider", "/", + "if-exists:if-same-abi:soname:libvulkan.so.1", + NULL); + flatpak_bwrap_finish (temp_bwrap); + + if (!pv_bwrap_run_sync (temp_bwrap, NULL, error)) + return FALSE; + + g_clear_pointer (&temp_bwrap, flatpak_bwrap_free); + + for (j = 0; j < vulkan_icd_details->len; j++) + { + IcdDetails *details = g_ptr_array_index (vulkan_icd_details, j); + SrtVulkanIcd *icd = SRT_VULKAN_ICD (details->icd); + + if (!srt_vulkan_icd_check_error (icd, NULL)) + continue; + + details->resolved_library = srt_vulkan_icd_resolve_library_path (icd); + g_assert (details->resolved_library != NULL); + + if (!bind_icd (self, + i, + j, + tool_path, + libdir_on_host, + libdir_in_container, + "vulkan", + details, + error)) + return FALSE; + } + + libc = g_build_filename (libdir_on_host, "libc.so.6", NULL); + + /* If we are going to use the host system's libc6 (likely) + * then we have to use its ld.so too. */ + if (g_file_test (libc, G_FILE_TEST_IS_SYMLINK)) + { + g_autofree gchar *ld_so_in_host = NULL; + + g_debug ("Making host ld.so visible in container"); + + ld_so_in_host = flatpak_canonicalize_filename (ld_so); + g_debug ("Host path: %s -> %s", ld_so, ld_so_in_host); + + g_clear_pointer (&temp_bwrap, flatpak_bwrap_free); + + temp_bwrap = flatpak_bwrap_new (NULL); + flatpak_bwrap_add_args (temp_bwrap, + self->bubblewrap, + NULL); + + if (!pv_bwrap_bind_usr (temp_bwrap, + self->source_files, + "/", + error)) + return FALSE; + + flatpak_bwrap_add_args (temp_bwrap, + "readlink", "-f", ld_so, + NULL); + flatpak_bwrap_finish (temp_bwrap); + + g_debug ("Container path: %s -> %s", ld_so, ld_so_in_runtime); + flatpak_bwrap_add_args (bwrap, + "--ro-bind", ld_so_in_host, + ld_so_in_runtime, + NULL); + + /* Collect miscellaneous libraries that libc might dlopen. + * At the moment this is just libidn2. */ + temp_bwrap = pv_bwrap_copy (self->container_access_adverb); + flatpak_bwrap_add_args (temp_bwrap, + tool_path, + "--container", self->container_access, + "--link-target", "/run/host", + "--dest", libdir_on_host, + "--provider", "/", + "if-exists:libidn2.so.0", + NULL); + flatpak_bwrap_finish (temp_bwrap); + + if (!pv_bwrap_run_sync (temp_bwrap, NULL, error)) + return FALSE; + + g_clear_pointer (&temp_bwrap, flatpak_bwrap_free); + + self->any_libc_from_host = TRUE; + } + else + { + self->all_libc_from_host = FALSE; + } + + libdrm = g_build_filename (libdir_on_host, "libdrm.so.2", NULL); + + /* If we have libdrm.so.2 in overrides we also want to mount + * ${prefix}/share/libdrm from the host. ${prefix} is derived from + * the absolute path of libdrm.so.2 */ + if (g_file_test (libdrm, G_FILE_TEST_IS_SYMLINK)) + { + g_autofree char *target = NULL; + target = glnx_readlinkat_malloc (-1, libdrm, NULL, NULL); + + if (target != NULL) + { + g_autofree gchar *dir = NULL; + g_autofree gchar *lib_multiarch = NULL; + g_autofree gchar *libdrm_dir_in_host = NULL; + + dir = g_path_get_dirname (target); + + lib_multiarch = g_build_filename ("/lib", multiarch_tuples[i], NULL); + if (g_str_has_suffix (dir, lib_multiarch)) + dir[strlen (dir) - strlen (lib_multiarch)] = '\0'; + else if (g_str_has_suffix (dir, "/lib64")) + dir[strlen (dir) - strlen ("/lib64")] = '\0'; + else if (g_str_has_suffix (dir, "/lib32")) + dir[strlen (dir) - strlen ("/lib32")] = '\0'; + else if (g_str_has_suffix (dir, "/lib")) + dir[strlen (dir) - strlen ("/lib")] = '\0'; + + if (g_str_has_prefix (dir, "/run/host")) + memmove (dir, dir + strlen ("/run/host"), strlen (dir) - strlen ("/run/host") + 1); + + libdrm_dir_in_host = g_build_filename (dir, "share", "libdrm", NULL); + + if (g_file_test (libdrm_dir_in_host, G_FILE_TEST_IS_DIR)) + { + g_hash_table_add (libdrm_data_from_host, g_steal_pointer (&libdrm_dir_in_host)); + } + else + { + g_debug ("We were expecting to have the libdrm directory in the host " + "to be located in \"%s\", but instead it is missing", + libdrm_dir_in_host); + } + } + } + else + { + /* For at least a single architecture, libdrm is newer in the container */ + all_libdrm_from_host = FALSE; + } + + /* /lib32 or /lib64 */ + g_assert (i < G_N_ELEMENTS (libquals)); + libqual = libquals[i]; + + dirs = g_new0 (gchar *, 7); + dirs[0] = g_build_filename ("/lib", multiarch_tuples[i], NULL); + dirs[1] = g_build_filename ("/usr", "lib", multiarch_tuples[i], NULL); + dirs[2] = g_strdup ("/lib"); + dirs[3] = g_strdup ("/usr/lib"); + dirs[4] = g_build_filename ("/", libqual, NULL); + dirs[5] = g_build_filename ("/usr", libqual, NULL); + + for (j = 0; j < 6; j++) + { + if (!try_bind_dri (self, bwrap, tool_path, dirs[j], + libdir_on_host, error)) + return FALSE; + } + } + else + { + g_debug ("Cannot determine ld.so for %s", multiarch_tuples[i]); + } + } + + if (!any_architecture_works) + { + GString *archs = g_string_new (""); + + g_assert (multiarch_tuples[G_N_ELEMENTS (multiarch_tuples) - 1] == NULL); + + for (i = 0; i < G_N_ELEMENTS (multiarch_tuples) - 1; i++) + { + if (archs->len > 0) + g_string_append (archs, ", "); + + g_string_append (archs, multiarch_tuples[i]); + } + + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "None of the supported CPU architectures are common to " + "the host system and the container (tried: %s)", + archs->str); + g_string_free (archs, TRUE); + return FALSE; + } + + if (self->any_libc_from_host && !self->all_libc_from_host) + { + /* + * This shouldn't happen. It would mean that there exist at least + * two architectures (let's say aaa and bbb) for which we have: + * host libc6:aaa < container libc6 < host libc6:bbb + * (we know that the container's libc6:aaa and libc6:bbb are + * constrained to be the same version because that's how multiarch + * works). + * + * If the host system locales work OK with both the aaa and bbb + * versions, let's assume they will also work with the intermediate + * version from the container... + */ + g_warning ("Using glibc from host system for some but not all " + "architectures! Arbitrarily using host locales."); + } + + if (self->any_libc_from_host) + { + g_debug ("Making host locale data visible in container"); + + if (g_file_test ("/usr/lib/locale", G_FILE_TEST_EXISTS)) + flatpak_bwrap_add_args (bwrap, + "--ro-bind", "/usr/lib/locale", + "/usr/lib/locale", + NULL); + + if (g_file_test ("/usr/share/i18n", G_FILE_TEST_EXISTS)) + flatpak_bwrap_add_args (bwrap, + "--ro-bind", "/usr/share/i18n", + "/usr/share/i18n", + NULL); + + localedef = g_find_program_in_path ("localedef"); + + if (localedef == NULL) + { + g_warning ("Cannot find localedef in PATH"); + } + else + { + g_autofree gchar *target = g_build_filename ("/run/host", + localedef, NULL); + + flatpak_bwrap_add_args (bwrap, + "--symlink", target, + "/overrides/bin/localedef", + NULL); + } + } + else + { + g_debug ("Using included locale data from container"); + } + + if (g_hash_table_size (libdrm_data_from_host) > 0 && !all_libdrm_from_host) + { + /* See the explanation in the similar "any_libc_from_host && !all_libc_from_host" + * case, above */ + g_warning ("Using libdrm.so.2 from host system for some but not all " + "architectures! Will take /usr/share/libdrm from host."); + } + + if (g_hash_table_size (libdrm_data_from_host) == 1) + { + best_libdrm_data_from_host = g_strdup (pv_hash_table_get_arbitrary_key (libdrm_data_from_host)); + } + else if (g_hash_table_size (libdrm_data_from_host) > 1) + { + g_warning ("Found more than one possible libdrm data directory from host"); + /* Prioritize "/usr/share/libdrm" if available. Otherwise randomly pick + * the first directory in the hash table */ + if (g_hash_table_contains (libdrm_data_from_host, "/usr/share/libdrm")) + best_libdrm_data_from_host = g_strdup ("/usr/share/libdrm"); + else + best_libdrm_data_from_host = g_strdup (pv_hash_table_get_arbitrary_key (libdrm_data_from_host)); + } + + libdrm_data_in_runtime = g_build_filename (self->runtime_usr, "share", "libdrm", NULL); + + if (best_libdrm_data_from_host != NULL && + g_file_test (libdrm_data_in_runtime, G_FILE_TEST_IS_DIR)) + { + flatpak_bwrap_add_args (bwrap, + "--ro-bind", best_libdrm_data_from_host, "/usr/share/libdrm", + NULL); + } + + g_debug ("Setting up EGL ICD JSON..."); + + dir_on_host = g_build_filename (self->overrides, + "share", "glvnd", "egl_vendor.d", NULL); + + if (g_mkdir_with_parents (dir_on_host, 0700) != 0) + { + glnx_throw_errno_prefix (error, "Unable to create %s", dir_on_host); + return FALSE; + } + + for (j = 0; j < egl_icd_details->len; j++) + { + IcdDetails *details = g_ptr_array_index (egl_icd_details, j); + SrtEglIcd *icd = SRT_EGL_ICD (details->icd); + gboolean need_host_json = FALSE; + + if (!srt_egl_icd_check_error (icd, NULL)) + continue; + + for (i = 0; i < G_N_ELEMENTS (multiarch_tuples) - 1; i++) + { + g_assert (i < G_N_ELEMENTS (details->kinds)); + g_assert (i < G_N_ELEMENTS (details->paths_in_container)); + + if (details->kinds[i] == ICD_KIND_ABSOLUTE) + { + g_autoptr(SrtEglIcd) replacement = NULL; + g_autofree gchar *json_on_host = NULL; + g_autofree gchar *json_in_container = NULL; + g_autofree gchar *json_base = NULL; + + g_assert (details->paths_in_container[i] != NULL); + + json_base = g_strdup_printf ("%" G_GSIZE_FORMAT "-%s.json", + j, multiarch_tuples[i]); + json_on_host = g_build_filename (dir_on_host, json_base, NULL); + json_in_container = g_build_filename ("/overrides", "share", + "glvnd", "egl_vendor.d", + json_base, NULL); + + replacement = srt_egl_icd_new_replace_library_path (icd, + details->paths_in_container[i]); + + if (!srt_egl_icd_write_to_file (replacement, json_on_host, + error)) + return FALSE; + + pv_search_path_append (egl_path, json_in_container); + } + else if (details->kinds[i] == ICD_KIND_SONAME) + { + need_host_json = TRUE; + } + } + + if (need_host_json) + { + g_autofree gchar *json_in_container = NULL; + g_autofree gchar *json_base = NULL; + const char *json_on_host = srt_egl_icd_get_json_path (icd); + + json_base = g_strdup_printf ("%" G_GSIZE_FORMAT ".json", j); + json_in_container = g_build_filename ("/overrides", "share", + "glvnd", "egl_vendor.d", + json_base, NULL); + + flatpak_bwrap_add_args (bwrap, + "--ro-bind", json_on_host, json_in_container, + NULL); + pv_search_path_append (egl_path, json_in_container); + } + } + + g_debug ("Setting up Vulkan ICD JSON..."); + + dir_on_host = g_build_filename (self->overrides, + "share", "vulkan", "icd.d", NULL); + + if (g_mkdir_with_parents (dir_on_host, 0700) != 0) + { + glnx_throw_errno_prefix (error, "Unable to create %s", dir_on_host); + return FALSE; + } + + for (j = 0; j < vulkan_icd_details->len; j++) + { + IcdDetails *details = g_ptr_array_index (vulkan_icd_details, j); + SrtVulkanIcd *icd = SRT_VULKAN_ICD (details->icd); + gboolean need_host_json = FALSE; + + if (!srt_vulkan_icd_check_error (icd, NULL)) + continue; + + for (i = 0; i < G_N_ELEMENTS (multiarch_tuples) - 1; i++) + { + g_assert (i < G_N_ELEMENTS (details->kinds)); + g_assert (i < G_N_ELEMENTS (details->paths_in_container)); + + if (details->kinds[i] == ICD_KIND_ABSOLUTE) + { + g_autoptr(SrtVulkanIcd) replacement = NULL; + g_autofree gchar *json_on_host = NULL; + g_autofree gchar *json_in_container = NULL; + g_autofree gchar *json_base = NULL; + + g_assert (details->paths_in_container[i] != NULL); + + json_base = g_strdup_printf ("%" G_GSIZE_FORMAT "-%s.json", + j, multiarch_tuples[i]); + json_on_host = g_build_filename (dir_on_host, json_base, NULL); + json_in_container = g_build_filename ("/overrides", "share", + "vulkan", "icd.d", + json_base, NULL); + + replacement = srt_vulkan_icd_new_replace_library_path (icd, + details->paths_in_container[i]); + + if (!srt_vulkan_icd_write_to_file (replacement, json_on_host, + error)) + return FALSE; + + pv_search_path_append (vulkan_path, json_in_container); + } + else if (details->kinds[i] == ICD_KIND_SONAME) + { + need_host_json = TRUE; + } + } + + if (need_host_json) + { + g_autofree gchar *json_in_container = NULL; + g_autofree gchar *json_base = NULL; + const char *json_on_host = srt_vulkan_icd_get_json_path (icd); + + json_base = g_strdup_printf ("%" G_GSIZE_FORMAT ".json", j); + json_in_container = g_build_filename ("/overrides", "share", + "vulkan", "icd.d", + json_base, NULL); + + flatpak_bwrap_add_args (bwrap, + "--ro-bind", json_on_host, json_in_container, + NULL); + pv_search_path_append (vulkan_path, json_in_container); + } + } + + if (dri_path->len != 0) + flatpak_bwrap_add_args (bwrap, + "--setenv", "LIBGL_DRIVERS_PATH", dri_path->str, + NULL); + else + flatpak_bwrap_add_args (bwrap, + "--unsetenv", "LIBGL_DRIVERS_PATH", + NULL); + + if (egl_path->len != 0) + flatpak_bwrap_add_args (bwrap, + "--setenv", "__EGL_VENDOR_LIBRARY_FILENAMES", + egl_path->str, NULL); + else + flatpak_bwrap_add_args (bwrap, + "--unsetenv", "__EGL_VENDOR_LIBRARY_FILENAMES", + NULL); + + flatpak_bwrap_add_args (bwrap, + "--unsetenv", "__EGL_VENDOR_LIBRARY_DIRS", + NULL); + + if (vulkan_path->len != 0) + flatpak_bwrap_add_args (bwrap, + "--setenv", "VK_ICD_FILENAMES", + vulkan_path->str, NULL); + else + flatpak_bwrap_add_args (bwrap, + "--unsetenv", "VK_ICD_FILENAMES", + NULL); + + return TRUE; +} + +gboolean +pv_runtime_bind (PvRuntime *self, + FlatpakBwrap *bwrap, + GError **error) +{ + g_autofree gchar *pressure_vessel_prefix = NULL; + + g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE); + g_return_val_if_fail (!pv_bwrap_was_finished (bwrap), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + /* Start with just the root tmpfs (which appears automatically) + * and the standard API filesystems */ + pv_bwrap_add_api_filesystems (bwrap); + + if (!bind_runtime (self, bwrap, error)) + return FALSE; + + pressure_vessel_prefix = g_path_get_dirname (self->tools_dir); + + /* Make sure pressure-vessel itself is visible there. */ + flatpak_bwrap_add_args (bwrap, + "--ro-bind", + pressure_vessel_prefix, + "/run/pressure-vessel", + NULL); + + pv_runtime_set_search_paths (self, bwrap); + + return TRUE; +} + +void +pv_runtime_set_search_paths (PvRuntime *self, + FlatpakBwrap *bwrap) +{ + g_autoptr(GString) ld_library_path = g_string_new (""); + g_autoptr(GString) bin_path = g_string_new (""); + gsize i; + + pv_search_path_append (bin_path, "/overrides/bin"); + pv_search_path_append (bin_path, g_getenv ("PATH")); + flatpak_bwrap_add_args (bwrap, + "--setenv", "PATH", + bin_path->str, + NULL); + + /* TODO: Adapt the use_ld_so_cache code from Flatpak instead + * of setting LD_LIBRARY_PATH, for better robustness against + * games that set their own LD_LIBRARY_PATH ignoring what they + * got from the environment */ + g_assert (multiarch_tuples[G_N_ELEMENTS (multiarch_tuples) - 1] == NULL); + + for (i = 0; i < G_N_ELEMENTS (multiarch_tuples) - 1; i++) + { + g_autofree gchar *ld_path = NULL; + + ld_path = g_build_filename ("/overrides", "lib", + multiarch_tuples[i], NULL); + + pv_search_path_append (ld_library_path, ld_path); + } + + /* This would be filtered out by a setuid bwrap, so we have to go + * via --setenv. */ + flatpak_bwrap_add_args (bwrap, + "--setenv", "LD_LIBRARY_PATH", + ld_library_path->str, + NULL); +} + +static void +pv_runtime_initable_iface_init (GInitableIface *iface, + gpointer unused G_GNUC_UNUSED) +{ + iface->init = pv_runtime_initable_init; +} diff --git a/src/runtime.h b/src/runtime.h new file mode 100644 index 0000000000000000000000000000000000000000..0fc2ec30ef2e625aba24a281122556f1c1a5ca61 --- /dev/null +++ b/src/runtime.h @@ -0,0 +1,69 @@ +/* + * Copyright © 2020 Collabora Ltd. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <glib.h> +#include <glib/gstdio.h> +#include <glib-object.h> + +#include "glib-backports.h" +#include "libglnx/libglnx.h" + +#include "flatpak-bwrap-private.h" +#include "flatpak-utils-base-private.h" + +/** + * PvRuntimeFlags: + * @PV_RUNTIME_FLAGS_HOST_GRAPHICS_STACK: Use host system graphics stack + * @PV_RUNTIME_FLAGS_NONE: None of the above + * + * Flags affecting how we set up the runtime. + */ +typedef enum +{ + PV_RUNTIME_FLAGS_HOST_GRAPHICS_STACK = (1 << 0), + PV_RUNTIME_FLAGS_NONE = 0 +} PvRuntimeFlags; + +typedef struct _PvRuntime PvRuntime; +typedef struct _PvRuntimeClass PvRuntimeClass; + +#define PV_TYPE_RUNTIME (pv_runtime_get_type ()) +#define PV_RUNTIME(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PV_TYPE_RUNTIME, PvRuntime)) +#define PV_RUNTIME_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST ((cls), PV_TYPE_RUNTIME, PvRuntimeClass)) +#define PV_IS_RUNTIME(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PV_TYPE_RUNTIME)) +#define PV_IS_RUNTIME_CLASS(cls) (G_TYPE_CHECK_CLASS_TYPE ((cls), PV_TYPE_RUNTIME)) +#define PV_RUNTIME_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), PV_TYPE_RUNTIME, PvRuntimeClass) +GType pv_runtime_get_type (void); + +PvRuntime *pv_runtime_new (const char *source_files, + const char *bubblewrap, + const char *tools_dir, + PvRuntimeFlags flags, + GError **error); + +void pv_runtime_append_lock_adverb (PvRuntime *self, + FlatpakBwrap *bwrap); +gboolean pv_runtime_bind (PvRuntime *self, + FlatpakBwrap *bwrap, + GError **error); +void pv_runtime_cleanup (PvRuntime *self); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (PvRuntime, g_object_unref) diff --git a/src/wrap.c b/src/wrap.c index 1eb31fea39781ef39eb23041ff5c016d77b85b83..f70843a123f3d26d5a812bbb33256336063305de 100644 --- a/src/wrap.c +++ b/src/wrap.c @@ -28,44 +28,19 @@ #include <locale.h> #include <stdlib.h> -#include <sysexits.h> -/* Include these before steam-runtime-tools.h so that their backport of - * G_DEFINE_AUTOPTR_CLEANUP_FUNC will be visible to it */ #include "glib-backports.h" #include "libglnx/libglnx.h" -#include <steam-runtime-tools/steam-runtime-tools.h> - #include "bwrap.h" #include "bwrap-lock.h" #include "flatpak-bwrap-private.h" -#include "flatpak-run-private.h" #include "flatpak-utils-base-private.h" #include "flatpak-utils-private.h" +#include "runtime.h" #include "utils.h" #include "wrap-interactive.h" -/* - * Supported Debian-style multiarch tuples - */ -static const char * const multiarch_tuples[] = -{ - "x86_64-linux-gnu", - "i386-linux-gnu", - NULL -}; - -/* - * Directories other than /usr/lib that we must search for loadable - * modules, in the same order as multiarch_tuples - */ -static const char * const libquals[] = -{ - "lib64", - "lib32" -}; - static gchar * find_executable_dir (GError **error) { @@ -184,1337 +159,6 @@ check_bwrap (const char *tools_dir) return NULL; } -static gboolean -try_bind_dri (FlatpakBwrap *bwrap, - FlatpakBwrap *mount_runtime_on_scratch, - const char *overrides, - const char *scratch, - const char *tool_path, - const char *libdir, - const char *libdir_on_host, - GError **error) -{ - g_autofree gchar *dri = g_build_filename (libdir, "dri", NULL); - g_autofree gchar *s2tc = g_build_filename (libdir, "libtxc_dxtn.so", NULL); - - /* mount_runtime_on_scratch can't own any fds, because if it did, - * flatpak_bwrap_append_bwrap() would steal them. */ - g_return_val_if_fail (mount_runtime_on_scratch->fds == NULL - || mount_runtime_on_scratch->fds->len == 0, - FALSE); - - if (g_file_test (dri, G_FILE_TEST_IS_DIR)) - { - g_autoptr(FlatpakBwrap) temp_bwrap = NULL; - g_autofree gchar *expr = NULL; - g_autofree gchar *host_dri = NULL; - g_autofree gchar *dest_dri = NULL; - - expr = g_strdup_printf ("only-dependencies:if-exists:path-match:%s/dri/*.so", - libdir); - - temp_bwrap = flatpak_bwrap_new (NULL); - g_warn_if_fail (mount_runtime_on_scratch->fds == NULL - || mount_runtime_on_scratch->fds->len == 0); - flatpak_bwrap_append_bwrap (temp_bwrap, mount_runtime_on_scratch); - flatpak_bwrap_add_args (temp_bwrap, - tool_path, - "--container", scratch, - "--link-target", "/run/host", - "--dest", libdir_on_host, - "--provider", "/", - expr, - NULL); - flatpak_bwrap_finish (temp_bwrap); - - if (!pv_bwrap_run_sync (temp_bwrap, NULL, error)) - return FALSE; - - g_clear_pointer (&temp_bwrap, flatpak_bwrap_free); - - host_dri = g_build_filename ("/run/host", libdir, "dri", NULL); - dest_dri = g_build_filename (libdir_on_host, "dri", NULL); - temp_bwrap = flatpak_bwrap_new (NULL); - flatpak_bwrap_add_args (temp_bwrap, - bwrap->argv->pdata[0], - "--ro-bind", "/", "/", - "--tmpfs", "/run", - "--ro-bind", "/", "/run/host", - "--bind", overrides, overrides, - "sh", "-c", - "ln -fns \"$1\"/* \"$2\"", - "sh", /* $0 */ - host_dri, - dest_dri, - NULL); - flatpak_bwrap_finish (temp_bwrap); - - if (!pv_bwrap_run_sync (temp_bwrap, NULL, error)) - return FALSE; - } - - if (g_file_test (s2tc, G_FILE_TEST_EXISTS)) - { - g_autoptr(FlatpakBwrap) temp_bwrap = NULL; - g_autofree gchar *expr = NULL; - - expr = g_strdup_printf ("path-match:%s", s2tc); - temp_bwrap = flatpak_bwrap_new (NULL); - g_warn_if_fail (mount_runtime_on_scratch->fds == NULL - || mount_runtime_on_scratch->fds->len == 0); - flatpak_bwrap_append_bwrap (temp_bwrap, mount_runtime_on_scratch); - flatpak_bwrap_add_args (temp_bwrap, - tool_path, - "--container", scratch, - "--link-target", "/run/host", - "--dest", libdir_on_host, - "--provider", "/", - expr, - NULL); - flatpak_bwrap_finish (temp_bwrap); - - if (!pv_bwrap_run_sync (temp_bwrap, NULL, error)) - return FALSE; - } - - return TRUE; -} - -/* - * Try to make sure we have all the locales we need, by running - * the helper from steam-runtime-tools in the container. If this - * fails, it isn't fatal - carry on anyway. - * - * @bwrap must be set up to have the same libc that we will be using - * for the container. - */ -static void -ensure_locales (gboolean on_host, - const char *tools_dir, - FlatpakBwrap *bwrap, - const char *overrides) -{ - g_autoptr(GError) local_error = NULL; - g_autoptr(FlatpakBwrap) run_locale_gen = NULL; - g_autofree gchar *locale_gen = NULL; - g_autofree gchar *locales = g_build_filename (overrides, "locales", NULL); - g_autoptr(GDir) dir = NULL; - int exit_status; - - /* bwrap can't own any fds yet, because if it did, - * flatpak_bwrap_append_bwrap() would steal them. */ - g_return_if_fail (bwrap->fds == NULL || bwrap->fds->len == 0); - - g_mkdir (locales, 0700); - - run_locale_gen = flatpak_bwrap_new (NULL); - - if (on_host) - { - locale_gen = g_build_filename (tools_dir, - "pressure-vessel-locale-gen", - NULL); - - flatpak_bwrap_add_args (run_locale_gen, - bwrap->argv->pdata[0], - "--ro-bind", "/", "/", - NULL); - pv_bwrap_add_api_filesystems (run_locale_gen); - flatpak_bwrap_add_args (run_locale_gen, - "--bind", locales, locales, - "--chdir", locales, - locale_gen, - "--verbose", - NULL); - } - else - { - locale_gen = g_build_filename ("/run/host/tools", - "pressure-vessel-locale-gen", - NULL); - - flatpak_bwrap_append_bwrap (run_locale_gen, bwrap); - pv_bwrap_copy_tree (run_locale_gen, overrides, "/overrides"); - - if (!flatpak_bwrap_bundle_args (run_locale_gen, 1, -1, FALSE, - &local_error)) - { - g_warning ("Unable to set up locale-gen command: %s", - local_error->message); - g_clear_error (&local_error); - } - - flatpak_bwrap_add_args (run_locale_gen, - "--ro-bind", tools_dir, "/run/host/tools", - "--bind", locales, "/overrides/locales", - "--chdir", "/overrides/locales", - locale_gen, - "--verbose", - NULL); - } - - flatpak_bwrap_finish (run_locale_gen); - - /* locale-gen exits 72 (EX_OSFILE) if it had to correct for - * missing locales at OS level. This is not an error. */ - if (!pv_bwrap_run_sync (run_locale_gen, &exit_status, &local_error)) - { - if (exit_status == EX_OSFILE) - g_debug ("pressure-vessel-locale-gen created missing locales"); - else - g_warning ("Unable to generate locales: %s", local_error->message); - - g_clear_error (&local_error); - } - else - { - g_debug ("No locales generated"); - } - - dir = g_dir_open (locales, 0, NULL); - - /* If the directory is not empty, make it the container's LOCPATH */ - if (dir != NULL && g_dir_read_name (dir) != NULL) - { - g_autoptr(GString) locpath = NULL; - - g_debug ("%s is non-empty", locales); - - locpath = g_string_new ("/overrides/locales"); - pv_search_path_append (locpath, g_getenv ("LOCPATH")); - flatpak_bwrap_add_args (bwrap, - "--setenv", "LOCPATH", locpath->str, - NULL); - } - else - { - g_debug ("%s is empty", locales); - } -} - -typedef enum -{ - ICD_KIND_NONEXISTENT, - ICD_KIND_ABSOLUTE, - ICD_KIND_SONAME -} IcdKind; - -typedef struct -{ - /* (type SrtEglIcd) or (type SrtVulkanIcd) */ - gpointer icd; - gchar *resolved_library; - /* Last entry is always NONEXISTENT */ - IcdKind kinds[G_N_ELEMENTS (multiarch_tuples)]; - /* Last entry is always NULL */ - gchar *paths_in_container[G_N_ELEMENTS (multiarch_tuples)]; -} IcdDetails; - -static IcdDetails * -icd_details_new (gpointer icd) -{ - IcdDetails *self; - gsize i; - - g_return_val_if_fail (G_IS_OBJECT (icd), NULL); - g_return_val_if_fail (SRT_IS_EGL_ICD (icd) || SRT_IS_VULKAN_ICD (icd), - NULL); - - self = g_slice_new0 (IcdDetails); - self->icd = g_object_ref (icd); - self->resolved_library = NULL; - - for (i = 0; i < G_N_ELEMENTS (multiarch_tuples); i++) - { - self->kinds[i] = ICD_KIND_NONEXISTENT; - self->paths_in_container[i] = NULL; - } - - return self; -} - -static void -icd_details_free (IcdDetails *self) -{ - gsize i; - - g_object_unref (self->icd); - g_free (self->resolved_library); - - for (i = 0; i < G_N_ELEMENTS (multiarch_tuples); i++) - g_free (self->paths_in_container[i]); - - g_slice_free (IcdDetails, self); -} - -G_DEFINE_AUTOPTR_CLEANUP_FUNC (IcdDetails, icd_details_free) - -static gboolean -bind_icd (gsize multiarch_index, - gsize sequence_number, - const char *tool_path, - FlatpakBwrap *mount_runtime_on_scratch, - const char *scratch, - const char *libdir_on_host, - const char *libdir_in_container, - const char *subdir, - IcdDetails *details, - GError **error) -{ - static const char options[] = "if-exists:if-same-abi"; - g_autofree gchar *on_host = NULL; - g_autofree gchar *pattern = NULL; - g_autofree gchar *dependency_pattern = NULL; - g_autofree gchar *seq_str = NULL; - const char *mode; - g_autoptr(FlatpakBwrap) temp_bwrap = NULL; - - g_return_val_if_fail (tool_path != NULL, FALSE); - g_return_val_if_fail (mount_runtime_on_scratch != NULL, FALSE); - g_return_val_if_fail (mount_runtime_on_scratch->fds == NULL - || mount_runtime_on_scratch->fds->len == 0, - FALSE); - g_return_val_if_fail (scratch != NULL, FALSE); - g_return_val_if_fail (libdir_on_host != NULL, FALSE); - g_return_val_if_fail (subdir != NULL, FALSE); - g_return_val_if_fail (multiarch_index < G_N_ELEMENTS (multiarch_tuples) - 1, - FALSE); - g_return_val_if_fail (details != NULL, FALSE); - g_return_val_if_fail (details->resolved_library != NULL, FALSE); - g_return_val_if_fail (details->kinds[multiarch_index] == ICD_KIND_NONEXISTENT, - FALSE); - g_return_val_if_fail (details->paths_in_container[multiarch_index] == NULL, - FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - - if (g_path_is_absolute (details->resolved_library)) - { - details->kinds[multiarch_index] = ICD_KIND_ABSOLUTE; - mode = "path"; - - /* Because the ICDs might have collisions among their - * basenames (might differ only by directory), we put each - * in its own numbered directory. */ - seq_str = g_strdup_printf ("%" G_GSIZE_FORMAT, sequence_number); - on_host = g_build_filename (libdir_on_host, subdir, seq_str, NULL); - - g_debug ("Ensuring %s exists", on_host); - - if (g_mkdir_with_parents (on_host, 0700) != 0) - return glnx_throw_errno_prefix (error, "Unable to create %s", on_host); - } - else - { - /* ICDs in the default search path by definition can't collide: - * one of them is the first one we find, and we use that one. */ - details->kinds[multiarch_index] = ICD_KIND_SONAME; - mode = "soname"; - } - - pattern = g_strdup_printf ("no-dependencies:even-if-older:%s:%s:%s", - options, mode, details->resolved_library); - dependency_pattern = g_strdup_printf ("only-dependencies:%s:%s:%s", - options, mode, details->resolved_library); - - temp_bwrap = flatpak_bwrap_new (NULL); - flatpak_bwrap_append_bwrap (temp_bwrap, mount_runtime_on_scratch); - flatpak_bwrap_add_args (temp_bwrap, - tool_path, - "--container", scratch, - "--link-target", "/run/host", - "--dest", on_host == NULL ? libdir_on_host : on_host, - "--provider", "/", - pattern, - NULL); - flatpak_bwrap_finish (temp_bwrap); - - if (!pv_bwrap_run_sync (temp_bwrap, NULL, error)) - return FALSE; - - g_clear_pointer (&temp_bwrap, flatpak_bwrap_free); - - if (on_host != NULL) - { - /* Try to remove the directory we created. If it succeeds, then we - * can optimize slightly by not capturing the dependencies: there's - * no point, because we know we didn't create a symlink to the ICD - * itself. (It must have been nonexistent or for a different ABI.) */ - if (g_rmdir (on_host) == 0) - { - details->kinds[multiarch_index] = ICD_KIND_NONEXISTENT; - return TRUE; - } - } - - temp_bwrap = flatpak_bwrap_new (NULL); - g_warn_if_fail (mount_runtime_on_scratch->fds == NULL - || mount_runtime_on_scratch->fds->len == 0); - flatpak_bwrap_append_bwrap (temp_bwrap, mount_runtime_on_scratch); - flatpak_bwrap_add_args (temp_bwrap, - tool_path, - "--container", scratch, - "--link-target", "/run/host", - "--dest", libdir_on_host, - "--provider", "/", - dependency_pattern, - NULL); - flatpak_bwrap_finish (temp_bwrap); - - if (!pv_bwrap_run_sync (temp_bwrap, NULL, error)) - return FALSE; - - g_clear_pointer (&temp_bwrap, flatpak_bwrap_free); - - if (details->kinds[multiarch_index] == ICD_KIND_ABSOLUTE) - { - g_assert (seq_str != NULL); - g_assert (on_host != NULL); - details->paths_in_container[multiarch_index] = g_build_filename (libdir_in_container, - subdir, - seq_str, - glnx_basename (details->resolved_library), - NULL); - } - - return TRUE; -} - -static gboolean -bind_runtime (FlatpakBwrap *bwrap, - const char *tools_dir, - const char *runtime, - const char *overrides, - const char *scratch, - GError **error) -{ - static const char * const bind_mutable[] = - { - "etc", - "var/cache", - "var/lib" - }; - static const char * const dont_bind[] = - { - "/etc/group", - "/etc/passwd", - "/etc/host.conf", - "/etc/hosts", - "/etc/localtime", - "/etc/machine-id", - "/etc/resolv.conf", - "/var/lib/dbus", - "/var/lib/dhcp", - "/var/lib/sudo", - "/var/lib/urandom", - NULL - }; - g_autofree gchar *xrd = g_strdup_printf ("/run/user/%ld", (long) geteuid ()); - gsize i, j; - const gchar *member; - g_autoptr(GString) dri_path = g_string_new (""); - g_autoptr(GString) egl_path = g_string_new (""); - g_autoptr(GString) vulkan_path = g_string_new (""); - gboolean any_architecture_works = FALSE; - gboolean any_libc_from_host = FALSE; - gboolean all_libc_from_host = TRUE; - gboolean all_libdrm_from_host = TRUE; - g_autoptr(GHashTable) libdrm_data_from_host = g_hash_table_new_full (g_str_hash, g_str_equal, - g_free, NULL); - g_autofree gchar *libdrm_data_in_runtime = NULL; - g_autofree gchar *best_libdrm_data_from_host = NULL; - g_autofree gchar *localedef = NULL; - g_autofree gchar *dir_on_host = NULL; - g_autoptr(SrtSystemInfo) system_info = srt_system_info_new (NULL); - g_autoptr(SrtObjectList) egl_icds = NULL; - g_autoptr(SrtObjectList) vulkan_icds = NULL; - g_autoptr(GPtrArray) egl_icd_details = NULL; /* (element-type IcdDetails) */ - g_autoptr(GPtrArray) vulkan_icd_details = NULL; /* (element-type IcdDetails) */ - guint n_egl_icds; - guint n_vulkan_icds; - const GList *icd_iter; - - g_return_val_if_fail (tools_dir != NULL, FALSE); - g_return_val_if_fail (runtime != NULL, FALSE); - g_return_val_if_fail (overrides != NULL, FALSE); - g_return_val_if_fail (scratch != NULL, FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - - if (!pv_bwrap_bind_usr (bwrap, runtime, "/", error)) - return FALSE; - - flatpak_bwrap_add_args (bwrap, - "--setenv", "XDG_RUNTIME_DIR", xrd, - "--tmpfs", "/run", - "--tmpfs", "/tmp", - "--tmpfs", "/var", - "--symlink", "../run", "/var/run", - NULL); - - for (i = 0; i < G_N_ELEMENTS (bind_mutable); i++) - { - g_autofree gchar *path = g_build_filename (runtime, bind_mutable[i], - NULL); - g_autoptr(GDir) dir = NULL; - - dir = g_dir_open (path, 0, NULL); - - if (dir == NULL) - continue; - - for (member = g_dir_read_name (dir); - member != NULL; - member = g_dir_read_name (dir)) - { - g_autofree gchar *dest = g_build_filename ("/", bind_mutable[i], - member, NULL); - g_autofree gchar *full = NULL; - g_autofree gchar *target = NULL; - - if (g_strv_contains (dont_bind, dest)) - continue; - - full = g_build_filename (runtime, bind_mutable[i], member, NULL); - target = glnx_readlinkat_malloc (-1, full, NULL, NULL); - - if (target != NULL) - flatpak_bwrap_add_args (bwrap, "--symlink", target, dest, NULL); - else - flatpak_bwrap_add_args (bwrap, "--ro-bind", full, dest, NULL); - } - } - - if (g_file_test ("/etc/machine-id", G_FILE_TEST_EXISTS)) - { - flatpak_bwrap_add_args (bwrap, - "--ro-bind", "/etc/machine-id", "/etc/machine-id", - "--symlink", "/etc/machine-id", - "/var/lib/dbus/machine-id", - NULL); - } - else if (g_file_test ("/var/lib/dbus/machine-id", G_FILE_TEST_EXISTS)) - { - flatpak_bwrap_add_args (bwrap, - "--ro-bind", "/var/lib/dbus/machine-id", - "/etc/machine-id", - "--symlink", "/etc/machine-id", - "/var/lib/dbus/machine-id", - NULL); - } - - if (g_file_test ("/etc/resolv.conf", G_FILE_TEST_EXISTS)) - flatpak_bwrap_add_args (bwrap, - "--ro-bind", "/etc/resolv.conf", "/etc/resolv.conf", - NULL); - if (g_file_test ("/etc/host.conf", G_FILE_TEST_EXISTS)) - flatpak_bwrap_add_args (bwrap, - "--ro-bind", "/etc/host.conf", "/etc/host.conf", - NULL); - if (g_file_test ("/etc/hosts", G_FILE_TEST_EXISTS)) - flatpak_bwrap_add_args (bwrap, - "--ro-bind", "/etc/hosts", "/etc/hosts", - NULL); - - /* TODO: Synthesize a passwd with only the user and nobody, - * like Flatpak does? */ - if (g_file_test ("/etc/passwd", G_FILE_TEST_EXISTS)) - flatpak_bwrap_add_args (bwrap, - "--ro-bind", "/etc/passwd", "/etc/passwd", - NULL); - if (g_file_test ("/etc/group", G_FILE_TEST_EXISTS)) - flatpak_bwrap_add_args (bwrap, - "--ro-bind", "/etc/group", "/etc/group", - NULL); - - g_debug ("Enumerating EGL ICDs on host system..."); - egl_icds = srt_system_info_list_egl_icds (system_info, multiarch_tuples); - n_egl_icds = g_list_length (egl_icds); - - egl_icd_details = g_ptr_array_new_full (n_egl_icds, - (GDestroyNotify) G_CALLBACK (icd_details_free)); - - for (icd_iter = egl_icds, j = 0; - icd_iter != NULL; - icd_iter = icd_iter->next, j++) - { - SrtEglIcd *icd = icd_iter->data; - const gchar *path = srt_egl_icd_get_json_path (icd); - GError *local_error = NULL; - - if (!srt_egl_icd_check_error (icd, &local_error)) - { - g_debug ("Failed to load EGL ICD #%" G_GSIZE_FORMAT " from %s: %s", - j, path, local_error->message); - g_clear_error (&local_error); - continue; - } - - g_debug ("EGL ICD #%" G_GSIZE_FORMAT " at %s: %s", - j, path, srt_egl_icd_get_library_path (icd)); - - g_ptr_array_add (egl_icd_details, icd_details_new (icd)); - } - - g_debug ("Enumerating Vulkan ICDs on host system..."); - vulkan_icds = srt_system_info_list_vulkan_icds (system_info, - multiarch_tuples); - n_vulkan_icds = g_list_length (vulkan_icds); - - vulkan_icd_details = g_ptr_array_new_full (n_vulkan_icds, - (GDestroyNotify) G_CALLBACK (icd_details_free)); - - for (icd_iter = vulkan_icds, j = 0; - icd_iter != NULL; - icd_iter = icd_iter->next, j++) - { - SrtVulkanIcd *icd = icd_iter->data; - const gchar *path = srt_vulkan_icd_get_json_path (icd); - GError *local_error = NULL; - - if (!srt_vulkan_icd_check_error (icd, &local_error)) - { - g_debug ("Failed to load Vulkan ICD #%" G_GSIZE_FORMAT " from %s: %s", - j, path, local_error->message); - g_clear_error (&local_error); - continue; - } - - g_debug ("Vulkan ICD #%" G_GSIZE_FORMAT " at %s: %s", - j, path, srt_vulkan_icd_get_library_path (icd)); - - g_ptr_array_add (vulkan_icd_details, icd_details_new (icd)); - } - - g_assert (multiarch_tuples[G_N_ELEMENTS (multiarch_tuples) - 1] == NULL); - - for (i = 0; i < G_N_ELEMENTS (multiarch_tuples) - 1; i++) - { - g_autofree gchar *tool = g_strdup_printf ("%s-capsule-capture-libs", - multiarch_tuples[i]); - g_autofree gchar *tool_path = NULL; - g_autofree gchar *ld_so = NULL; - const gchar *argv[] = { NULL, "--print-ld.so", NULL }; - - g_debug ("Checking for %s libraries...", multiarch_tuples[i]); - - tool_path = g_build_filename (tools_dir, tool, NULL); - argv[0] = tool_path; - - /* This has the side-effect of testing whether we can run binaries - * for this architecture on the host system. */ - ld_so = pv_capture_output (argv, NULL); - - if (ld_so != NULL) - { - g_auto(GStrv) dirs = NULL; - g_autoptr(FlatpakBwrap) mount_runtime_on_scratch = NULL; - g_autoptr(FlatpakBwrap) temp_bwrap = NULL; - g_autofree gchar *libdir_in_container = g_build_filename ("/overrides", - "lib", - multiarch_tuples[i], - NULL); - g_autofree gchar *libdir_on_host = g_build_filename (overrides, "lib", - multiarch_tuples[i], - NULL); - g_autofree gchar *this_dri_path_on_host = g_build_filename (libdir_on_host, - "dri", NULL); - g_autofree gchar *this_dri_path_in_container = g_build_filename (libdir_in_container, - "dri", NULL); - g_autofree gchar *libc = NULL; - g_autofree gchar *ld_so_in_runtime = NULL; - g_autofree gchar *libdrm = NULL; - const gchar *libqual = NULL; - - temp_bwrap = flatpak_bwrap_new (NULL); - flatpak_bwrap_add_args (temp_bwrap, - bwrap->argv->pdata[0], - NULL); - - if (!pv_bwrap_bind_usr (temp_bwrap, runtime, "/", error)) - return FALSE; - - flatpak_bwrap_add_args (temp_bwrap, - "readlink", "-e", ld_so, - NULL); - flatpak_bwrap_finish (temp_bwrap); - - ld_so_in_runtime = pv_capture_output ((const char * const *) temp_bwrap->argv->pdata, - NULL); - - g_clear_pointer (&temp_bwrap, flatpak_bwrap_free); - - if (ld_so_in_runtime == NULL) - { - g_debug ("Container does not have %s so it cannot run " - "%s binaries", ld_so, multiarch_tuples[i]); - continue; - } - - any_architecture_works = TRUE; - g_debug ("Container path: %s -> %s", ld_so, ld_so_in_runtime); - - pv_search_path_append (dri_path, this_dri_path_in_container); - - g_mkdir_with_parents (libdir_on_host, 0755); - g_mkdir_with_parents (this_dri_path_on_host, 0755); - - mount_runtime_on_scratch = flatpak_bwrap_new (NULL); - flatpak_bwrap_add_args (mount_runtime_on_scratch, - bwrap->argv->pdata[0], - "--ro-bind", "/", "/", - "--bind", overrides, overrides, - "--tmpfs", scratch, - NULL); - if (!pv_bwrap_bind_usr (mount_runtime_on_scratch, runtime, scratch, - error)) - return FALSE; - - g_debug ("Collecting GLX drivers from host system..."); - - temp_bwrap = flatpak_bwrap_new (NULL); - /* mount_runtime_on_scratch can't own any fds, because if it did, - * flatpak_bwrap_append_bwrap() would steal them. */ - g_warn_if_fail (mount_runtime_on_scratch->fds == NULL - || mount_runtime_on_scratch->fds->len == 0); - flatpak_bwrap_append_bwrap (temp_bwrap, mount_runtime_on_scratch); - flatpak_bwrap_add_args (temp_bwrap, - tool_path, - "--container", scratch, - "--link-target", "/run/host", - "--dest", libdir_on_host, - "--provider", "/", - "gl:", - NULL); - flatpak_bwrap_finish (temp_bwrap); - - if (!pv_bwrap_run_sync (temp_bwrap, NULL, error)) - return FALSE; - - g_clear_pointer (&temp_bwrap, flatpak_bwrap_free); - - temp_bwrap = flatpak_bwrap_new (NULL); - g_warn_if_fail (mount_runtime_on_scratch->fds == NULL - || mount_runtime_on_scratch->fds->len == 0); - flatpak_bwrap_append_bwrap (temp_bwrap, mount_runtime_on_scratch); - flatpak_bwrap_add_args (temp_bwrap, - tool_path, - "--container", scratch, - "--link-target", "/run/host", - "--dest", libdir_on_host, - "--provider", "/", - "if-exists:even-if-older:soname-match:libEGL.so.*", - "if-exists:even-if-older:soname-match:libEGL_nvidia.so.*", - "if-exists:even-if-older:soname-match:libGL.so.*", - "if-exists:even-if-older:soname-match:libGLESv1_CM.so.*", - "if-exists:even-if-older:soname-match:libGLESv1_CM_nvidia.so.*", - "if-exists:even-if-older:soname-match:libGLESv2.so.*", - "if-exists:even-if-older:soname-match:libGLESv2_nvidia.so.*", - "if-exists:even-if-older:soname-match:libGLX.so.*", - "if-exists:even-if-older:soname-match:libGLX_nvidia.so.*", - "if-exists:even-if-older:soname-match:libGLX_indirect.so.*", - "if-exists:even-if-older:soname-match:libGLdispatch.so.*", - "if-exists:even-if-older:soname-match:libOpenGL.so.*", - "if-exists:even-if-older:soname-match:libcuda.so.*", - "if-exists:even-if-older:soname-match:libglx.so.*", - "if-exists:even-if-older:soname-match:libnvidia-cbl.so.*", - "if-exists:even-if-older:soname-match:libnvidia-cfg.so.*", - "if-exists:even-if-older:soname-match:libnvidia-compiler.so.*", - "if-exists:even-if-older:soname-match:libnvidia-egl-wayland.so.*", - "if-exists:even-if-older:soname-match:libnvidia-eglcore.so.*", - "if-exists:even-if-older:soname-match:libnvidia-encode.so.*", - "if-exists:even-if-older:soname-match:libnvidia-fatbinaryloader.so.*", - "if-exists:even-if-older:soname-match:libnvidia-fbc.so.*", - "if-exists:even-if-older:soname-match:libnvidia-glcore.so.*", - "if-exists:even-if-older:soname-match:libnvidia-glsi.so.*", - "if-exists:even-if-older:soname-match:libnvidia-glvkspirv.so.*", - "if-exists:even-if-older:soname-match:libnvidia-ifr.so.*", - "if-exists:even-if-older:soname-match:libnvidia-ml.so.*", - "if-exists:even-if-older:soname-match:libnvidia-opencl.so.*", - "if-exists:even-if-older:soname-match:libnvidia-opticalflow.so.*", - "if-exists:even-if-older:soname-match:libnvidia-ptxjitcompiler.so.*", - "if-exists:even-if-older:soname-match:libnvidia-rtcore.so.*", - "if-exists:even-if-older:soname-match:libnvidia-tls.so.*", - "if-exists:even-if-older:soname-match:libOpenCL.so.*", - "if-exists:even-if-older:soname-match:libvdpau_nvidia.so.*", - NULL); - flatpak_bwrap_finish (temp_bwrap); - - if (!pv_bwrap_run_sync (temp_bwrap, NULL, error)) - return FALSE; - - g_clear_pointer (&temp_bwrap, flatpak_bwrap_free); - - g_debug ("Collecting %s EGL drivers from host system...", - multiarch_tuples[i]); - - for (j = 0; j < egl_icd_details->len; j++) - { - IcdDetails *details = g_ptr_array_index (egl_icd_details, j); - SrtEglIcd *icd = SRT_EGL_ICD (details->icd); - - if (!srt_egl_icd_check_error (icd, NULL)) - continue; - - details->resolved_library = srt_egl_icd_resolve_library_path (icd); - g_assert (details->resolved_library != NULL); - - if (!bind_icd (i, - j, - tool_path, - mount_runtime_on_scratch, - scratch, - libdir_on_host, - libdir_in_container, - "glvnd", - details, - error)) - return FALSE; - } - - g_debug ("Collecting %s Vulkan drivers from host system...", - multiarch_tuples[i]); - - temp_bwrap = flatpak_bwrap_new (NULL); - g_warn_if_fail (mount_runtime_on_scratch->fds == NULL - || mount_runtime_on_scratch->fds->len == 0); - flatpak_bwrap_append_bwrap (temp_bwrap, mount_runtime_on_scratch); - flatpak_bwrap_add_args (temp_bwrap, - tool_path, - "--container", scratch, - "--link-target", "/run/host", - "--dest", libdir_on_host, - "--provider", "/", - "if-exists:if-same-abi:soname:libvulkan.so.1", - NULL); - flatpak_bwrap_finish (temp_bwrap); - - if (!pv_bwrap_run_sync (temp_bwrap, NULL, error)) - return FALSE; - - g_clear_pointer (&temp_bwrap, flatpak_bwrap_free); - - for (j = 0; j < vulkan_icd_details->len; j++) - { - IcdDetails *details = g_ptr_array_index (vulkan_icd_details, j); - SrtVulkanIcd *icd = SRT_VULKAN_ICD (details->icd); - - if (!srt_vulkan_icd_check_error (icd, NULL)) - continue; - - details->resolved_library = srt_vulkan_icd_resolve_library_path (icd); - g_assert (details->resolved_library != NULL); - - if (!bind_icd (i, - j, - tool_path, - mount_runtime_on_scratch, - scratch, - libdir_on_host, - libdir_in_container, - "vulkan", - details, - error)) - return FALSE; - } - - libc = g_build_filename (libdir_on_host, "libc.so.6", NULL); - - /* If we are going to use the host system's libc6 (likely) - * then we have to use its ld.so too. */ - if (g_file_test (libc, G_FILE_TEST_IS_SYMLINK)) - { - g_autofree gchar *ld_so_in_host = NULL; - - g_debug ("Making host ld.so visible in container"); - - ld_so_in_host = flatpak_canonicalize_filename (ld_so); - g_debug ("Host path: %s -> %s", ld_so, ld_so_in_host); - - g_clear_pointer (&temp_bwrap, flatpak_bwrap_free); - - temp_bwrap = flatpak_bwrap_new (NULL); - flatpak_bwrap_add_args (temp_bwrap, - bwrap->argv->pdata[0], - NULL); - - if (!pv_bwrap_bind_usr (temp_bwrap, runtime, "/", error)) - return FALSE; - - flatpak_bwrap_add_args (temp_bwrap, - "readlink", "-f", ld_so, - NULL); - flatpak_bwrap_finish (temp_bwrap); - - g_debug ("Container path: %s -> %s", ld_so, ld_so_in_runtime); - flatpak_bwrap_add_args (bwrap, - "--ro-bind", ld_so_in_host, - ld_so_in_runtime, - NULL); - - /* Collect miscellaneous libraries that libc might dlopen. - * At the moment this is just libidn2. */ - temp_bwrap = flatpak_bwrap_new (NULL); - g_warn_if_fail (mount_runtime_on_scratch->fds == NULL - || mount_runtime_on_scratch->fds->len == 0); - flatpak_bwrap_append_bwrap (temp_bwrap, mount_runtime_on_scratch); - flatpak_bwrap_add_args (temp_bwrap, - tool_path, - "--container", scratch, - "--link-target", "/run/host", - "--dest", libdir_on_host, - "--provider", "/", - "if-exists:libidn2.so.0", - NULL); - flatpak_bwrap_finish (temp_bwrap); - - if (!pv_bwrap_run_sync (temp_bwrap, NULL, error)) - return FALSE; - - g_clear_pointer (&temp_bwrap, flatpak_bwrap_free); - - any_libc_from_host = TRUE; - } - else - { - all_libc_from_host = FALSE; - } - - libdrm = g_build_filename (libdir_on_host, "libdrm.so.2", NULL); - - /* If we have libdrm.so.2 in overrides we also want to mount - * ${prefix}/share/libdrm from the host. ${prefix} is derived from - * the absolute path of libdrm.so.2 */ - if (g_file_test (libdrm, G_FILE_TEST_IS_SYMLINK)) - { - g_autofree char *target = NULL; - target = glnx_readlinkat_malloc (-1, libdrm, NULL, NULL); - - if (target != NULL) - { - g_autofree gchar *dir = NULL; - g_autofree gchar *lib_multiarch = NULL; - g_autofree gchar *libdrm_dir_in_host = NULL; - - dir = g_path_get_dirname (target); - - lib_multiarch = g_build_filename ("/lib", multiarch_tuples[i], NULL); - if (g_str_has_suffix (dir, lib_multiarch)) - dir[strlen (dir) - strlen (lib_multiarch)] = '\0'; - else if (g_str_has_suffix (dir, "/lib64")) - dir[strlen (dir) - strlen ("/lib64")] = '\0'; - else if (g_str_has_suffix (dir, "/lib32")) - dir[strlen (dir) - strlen ("/lib32")] = '\0'; - else if (g_str_has_suffix (dir, "/lib")) - dir[strlen (dir) - strlen ("/lib")] = '\0'; - - if (g_str_has_prefix (dir, "/run/host")) - memmove (dir, dir + strlen ("/run/host"), strlen (dir) - strlen ("/run/host") + 1); - - libdrm_dir_in_host = g_build_filename (dir, "share", "libdrm", NULL); - - if (g_file_test (libdrm_dir_in_host, G_FILE_TEST_IS_DIR)) - { - g_hash_table_add (libdrm_data_from_host, g_steal_pointer (&libdrm_dir_in_host)); - } - else - { - g_debug ("We were expecting to have the libdrm directory in the host " - "to be located in \"%s\", but instead it is missing", - libdrm_dir_in_host); - } - } - } - else - { - /* For at least a single architecture, libdrm is newer in the container */ - all_libdrm_from_host = FALSE; - } - - /* /lib32 or /lib64 */ - g_assert (i < G_N_ELEMENTS (libquals)); - libqual = libquals[i]; - - dirs = g_new0 (gchar *, 7); - dirs[0] = g_build_filename ("/lib", multiarch_tuples[i], NULL); - dirs[1] = g_build_filename ("/usr", "lib", multiarch_tuples[i], NULL); - dirs[2] = g_strdup ("/lib"); - dirs[3] = g_strdup ("/usr/lib"); - dirs[4] = g_build_filename ("/", libqual, NULL); - dirs[5] = g_build_filename ("/usr", libqual, NULL); - - for (j = 0; j < 6; j++) - { - if (!try_bind_dri (bwrap, mount_runtime_on_scratch, - overrides, scratch, tool_path, dirs[j], - libdir_on_host, error)) - return FALSE; - } - } - else - { - g_debug ("Cannot determine ld.so for %s", multiarch_tuples[i]); - } - } - - if (!any_architecture_works) - { - GString *archs = g_string_new (""); - - g_assert (multiarch_tuples[G_N_ELEMENTS (multiarch_tuples) - 1] == NULL); - - for (i = 0; i < G_N_ELEMENTS (multiarch_tuples) - 1; i++) - { - if (archs->len > 0) - g_string_append (archs, ", "); - - g_string_append (archs, multiarch_tuples[i]); - } - - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "None of the supported CPU architectures are common to " - "the host system and the container (tried: %s)", - archs->str); - g_string_free (archs, TRUE); - return FALSE; - } - - if (any_libc_from_host && !all_libc_from_host) - { - /* - * This shouldn't happen. It would mean that there exist at least - * two architectures (let's say aaa and bbb) for which we have: - * host libc6:aaa < container libc6 < host libc6:bbb - * (we know that the container's libc6:aaa and libc6:bbb are - * constrained to be the same version because that's how multiarch - * works). - * - * If the host system locales work OK with both the aaa and bbb - * versions, let's assume they will also work with the intermediate - * version from the container... - */ - g_warning ("Using glibc from host system for some but not all " - "architectures! Arbitrarily using host locales."); - } - - if (any_libc_from_host) - { - g_debug ("Making host locale data visible in container"); - - if (g_file_test ("/usr/lib/locale", G_FILE_TEST_EXISTS)) - flatpak_bwrap_add_args (bwrap, - "--ro-bind", "/usr/lib/locale", - "/usr/lib/locale", - NULL); - - if (g_file_test ("/usr/share/i18n", G_FILE_TEST_EXISTS)) - flatpak_bwrap_add_args (bwrap, - "--ro-bind", "/usr/share/i18n", - "/usr/share/i18n", - NULL); - - localedef = g_find_program_in_path ("localedef"); - - if (localedef == NULL) - { - g_warning ("Cannot find localedef in PATH"); - } - else - { - g_autofree gchar *target = g_build_filename ("/run/host", - localedef, NULL); - - flatpak_bwrap_add_args (bwrap, - "--symlink", target, - "/overrides/bin/localedef", - NULL); - } - } - else - { - g_debug ("Using included locale data from container"); - } - - if (g_hash_table_size (libdrm_data_from_host) > 0 && !all_libdrm_from_host) - { - /* See the explanation in the similar "any_libc_from_host && !all_libc_from_host" - * case, above */ - g_warning ("Using libdrm.so.2 from host system for some but not all " - "architectures! Will take /usr/share/libdrm from host."); - } - - if (g_hash_table_size (libdrm_data_from_host) == 1) - { - best_libdrm_data_from_host = g_strdup (pv_hash_table_get_arbitrary_key (libdrm_data_from_host)); - } - else if (g_hash_table_size (libdrm_data_from_host) > 1) - { - g_warning ("Found more than one possible libdrm data directory from host"); - /* Prioritize "/usr/share/libdrm" if available. Otherwise randomly pick - * the first directory in the hash table */ - if (g_hash_table_contains (libdrm_data_from_host, "/usr/share/libdrm")) - best_libdrm_data_from_host = g_strdup ("/usr/share/libdrm"); - else - best_libdrm_data_from_host = g_strdup (pv_hash_table_get_arbitrary_key (libdrm_data_from_host)); - } - - g_autofree gchar *runtime_usr = g_build_filename (runtime, "usr", NULL); - if (g_file_test (runtime_usr, G_FILE_TEST_IS_DIR)) - libdrm_data_in_runtime = g_build_filename (runtime_usr, "share", "libdrm", NULL); - else - libdrm_data_in_runtime = g_build_filename (runtime, "share", "libdrm", NULL); - - if (best_libdrm_data_from_host != NULL && - g_file_test (libdrm_data_in_runtime, G_FILE_TEST_IS_DIR)) - { - flatpak_bwrap_add_args (bwrap, - "--ro-bind", best_libdrm_data_from_host, "/usr/share/libdrm", - NULL); - } - - g_debug ("Setting up EGL ICD JSON..."); - - dir_on_host = g_build_filename (overrides, - "share", "glvnd", "egl_vendor.d", NULL); - - if (g_mkdir_with_parents (dir_on_host, 0700) != 0) - { - glnx_throw_errno_prefix (error, "Unable to create %s", dir_on_host); - return FALSE; - } - - for (j = 0; j < egl_icd_details->len; j++) - { - IcdDetails *details = g_ptr_array_index (egl_icd_details, j); - SrtEglIcd *icd = SRT_EGL_ICD (details->icd); - gboolean need_host_json = FALSE; - - if (!srt_egl_icd_check_error (icd, NULL)) - continue; - - for (i = 0; i < G_N_ELEMENTS (multiarch_tuples) - 1; i++) - { - g_assert (i < G_N_ELEMENTS (details->kinds)); - g_assert (i < G_N_ELEMENTS (details->paths_in_container)); - - if (details->kinds[i] == ICD_KIND_ABSOLUTE) - { - g_autoptr(SrtEglIcd) replacement = NULL; - g_autofree gchar *json_on_host = NULL; - g_autofree gchar *json_in_container = NULL; - g_autofree gchar *json_base = NULL; - - g_assert (details->paths_in_container[i] != NULL); - - json_base = g_strdup_printf ("%" G_GSIZE_FORMAT "-%s.json", - j, multiarch_tuples[i]); - json_on_host = g_build_filename (dir_on_host, json_base, NULL); - json_in_container = g_build_filename ("/overrides", "share", - "glvnd", "egl_vendor.d", - json_base, NULL); - - replacement = srt_egl_icd_new_replace_library_path (icd, - details->paths_in_container[i]); - - if (!srt_egl_icd_write_to_file (replacement, json_on_host, - error)) - return FALSE; - - pv_search_path_append (egl_path, json_in_container); - } - else if (details->kinds[i] == ICD_KIND_SONAME) - { - need_host_json = TRUE; - } - } - - if (need_host_json) - { - g_autofree gchar *json_in_container = NULL; - g_autofree gchar *json_base = NULL; - const char *json_on_host = srt_egl_icd_get_json_path (icd); - - json_base = g_strdup_printf ("%" G_GSIZE_FORMAT ".json", j); - json_in_container = g_build_filename ("/overrides", "share", - "glvnd", "egl_vendor.d", - json_base, NULL); - - flatpak_bwrap_add_args (bwrap, - "--ro-bind", json_on_host, json_in_container, - NULL); - pv_search_path_append (egl_path, json_in_container); - } - } - - g_debug ("Setting up Vulkan ICD JSON..."); - - dir_on_host = g_build_filename (overrides, - "share", "vulkan", "icd.d", NULL); - - if (g_mkdir_with_parents (dir_on_host, 0700) != 0) - { - glnx_throw_errno_prefix (error, "Unable to create %s", dir_on_host); - return FALSE; - } - - for (j = 0; j < vulkan_icd_details->len; j++) - { - IcdDetails *details = g_ptr_array_index (vulkan_icd_details, j); - SrtVulkanIcd *icd = SRT_VULKAN_ICD (details->icd); - gboolean need_host_json = FALSE; - - if (!srt_vulkan_icd_check_error (icd, NULL)) - continue; - - for (i = 0; i < G_N_ELEMENTS (multiarch_tuples) - 1; i++) - { - g_assert (i < G_N_ELEMENTS (details->kinds)); - g_assert (i < G_N_ELEMENTS (details->paths_in_container)); - - if (details->kinds[i] == ICD_KIND_ABSOLUTE) - { - g_autoptr(SrtVulkanIcd) replacement = NULL; - g_autofree gchar *json_on_host = NULL; - g_autofree gchar *json_in_container = NULL; - g_autofree gchar *json_base = NULL; - - g_assert (details->paths_in_container[i] != NULL); - - json_base = g_strdup_printf ("%" G_GSIZE_FORMAT "-%s.json", - j, multiarch_tuples[i]); - json_on_host = g_build_filename (dir_on_host, json_base, NULL); - json_in_container = g_build_filename ("/overrides", "share", - "vulkan", "icd.d", - json_base, NULL); - - replacement = srt_vulkan_icd_new_replace_library_path (icd, - details->paths_in_container[i]); - - if (!srt_vulkan_icd_write_to_file (replacement, json_on_host, - error)) - return FALSE; - - pv_search_path_append (vulkan_path, json_in_container); - } - else if (details->kinds[i] == ICD_KIND_SONAME) - { - need_host_json = TRUE; - } - } - - if (need_host_json) - { - g_autofree gchar *json_in_container = NULL; - g_autofree gchar *json_base = NULL; - const char *json_on_host = srt_vulkan_icd_get_json_path (icd); - - json_base = g_strdup_printf ("%" G_GSIZE_FORMAT ".json", j); - json_in_container = g_build_filename ("/overrides", "share", - "vulkan", "icd.d", - json_base, NULL); - - flatpak_bwrap_add_args (bwrap, - "--ro-bind", json_on_host, json_in_container, - NULL); - pv_search_path_append (vulkan_path, json_in_container); - } - } - - if (!pv_bwrap_bind_usr (bwrap, "/", "/run/host", error)) - return FALSE; - - ensure_locales (any_libc_from_host, tools_dir, bwrap, overrides); - - if (dri_path->len != 0) - flatpak_bwrap_add_args (bwrap, - "--setenv", "LIBGL_DRIVERS_PATH", dri_path->str, - NULL); - else - flatpak_bwrap_add_args (bwrap, - "--unsetenv", "LIBGL_DRIVERS_PATH", - NULL); - - if (egl_path->len != 0) - flatpak_bwrap_add_args (bwrap, - "--setenv", "__EGL_VENDOR_LIBRARY_FILENAMES", - egl_path->str, NULL); - else - flatpak_bwrap_add_args (bwrap, - "--unsetenv", "__EGL_VENDOR_LIBRARY_FILENAMES", - NULL); - - flatpak_bwrap_add_args (bwrap, - "--unsetenv", "__EGL_VENDOR_LIBRARY_DIRS", - NULL); - - if (vulkan_path->len != 0) - flatpak_bwrap_add_args (bwrap, - "--setenv", "VK_ICD_FILENAMES", - vulkan_path->str, NULL); - else - flatpak_bwrap_add_args (bwrap, - "--unsetenv", "VK_ICD_FILENAMES", - NULL); - - /* These can add data fds to @bwrap, so they must come last - after - * other functions stop using @bwrap as a basis for their own bwrap - * invocations with flatpak_bwrap_append_bwrap(). - * Otherwise, when flatpak_bwrap_append_bwrap() calls - * flatpak_bwrap_steal_fds(), it will make the original FlatpakBwrap - * unusable. */ - - flatpak_run_add_wayland_args (bwrap); - flatpak_run_add_x11_args (bwrap, TRUE); - flatpak_run_add_pulseaudio_args (bwrap); - flatpak_run_add_session_dbus_args (bwrap); - flatpak_run_add_system_dbus_args (bwrap); - pv_bwrap_copy_tree (bwrap, overrides, "/overrides"); - - /* /etc/localtime and /etc/resolv.conf can not exist (or be symlinks to - * non-existing targets), in which case we don't want to attempt to create - * bogus symlinks or bind mounts, as that will cause flatpak run to fail. - */ - if (g_file_test ("/etc/localtime", G_FILE_TEST_EXISTS)) - { - g_autofree char *target = NULL; - gboolean is_reachable = FALSE; - g_autofree char *tz = flatpak_get_timezone (); - g_autofree char *timezone_content = g_strdup_printf ("%s\n", tz); - - target = glnx_readlinkat_malloc (-1, "/etc/localtime", NULL, NULL); - - if (target != NULL) - { - g_autoptr(GFile) base_file = NULL; - g_autoptr(GFile) target_file = NULL; - g_autofree char *target_canonical = NULL; - - base_file = g_file_new_for_path ("/etc"); - target_file = g_file_resolve_relative_path (base_file, target); - target_canonical = g_file_get_path (target_file); - - is_reachable = g_str_has_prefix (target_canonical, "/usr/"); - } - - if (is_reachable) - { - flatpak_bwrap_add_args (bwrap, - "--symlink", target, "/etc/localtime", - NULL); - } - else - { - flatpak_bwrap_add_args (bwrap, - "--ro-bind", "/etc/localtime", "/etc/localtime", - NULL); - } - - flatpak_bwrap_add_args_data (bwrap, "timezone", - timezone_content, -1, "/etc/timezone", - NULL); - } - - return TRUE; -} - /* Order matters here: root, steam and steambeta are or might be symlinks * to the root of the Steam installation, so we want to bind-mount their * targets before we deal with the rest. */ @@ -1678,6 +322,7 @@ static char *opt_freedesktop_app_id = NULL; static char *opt_steam_app_id = NULL; static char *opt_home = NULL; static gboolean opt_host_fallback = FALSE; +static gboolean opt_host_graphics = TRUE; static PvShell opt_shell = PV_SHELL_NONE; static GPtrArray *opt_ld_preload = NULL; static char *opt_runtime_base = NULL; @@ -1939,6 +584,14 @@ static GOptionEntry options[] = { "version-only", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &opt_version_only, "Print version number (no other information) and exit.", NULL }, + { "with-host-graphics", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_host_graphics, + "If using --runtime, use the host graphics stack (default)", NULL }, + { "without-host-graphics", '\0', + G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_host_graphics, + "If using --runtime, don't use the host graphics stack. " + "This is likely to result in software rendering or a crash.", + NULL }, { "test", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_test, "Smoke test pressure-vessel-wrap and exit.", NULL }, @@ -2002,22 +655,15 @@ main (int argc, int original_argc = argc; g_autoptr(FlatpakBwrap) bwrap = NULL; g_autoptr(FlatpakBwrap) wrapped_command = NULL; - g_autofree gchar *tmpdir = NULL; - g_autofree gchar *pressure_vessel_prefix = NULL; - g_autofree gchar *scratch = NULL; - g_autofree gchar *overrides = NULL; - g_autofree gchar *overrides_bin = NULL; g_autofree gchar *bwrap_executable = NULL; - g_autoptr(GString) ld_library_path = g_string_new (""); - g_autoptr(GString) bin_path = g_string_new (""); g_autoptr(GString) adjusted_ld_preload = g_string_new (""); g_autofree gchar *cwd_p = NULL; g_autofree gchar *cwd_l = NULL; - g_autoptr(PvBwrapLock) runtime_lock = NULL; const gchar *home; g_autofree gchar *bwrap_help = NULL; g_autofree gchar *tools_dir = NULL; const gchar *bwrap_help_argv[] = { "<bwrap>", "--help", NULL }; + g_autoptr(PvRuntime) runtime = NULL; setlocale (LC_ALL, ""); pv_avoid_gvfs (); @@ -2055,6 +701,8 @@ main (int argc, g_clear_pointer (&opt_home, g_free); opt_share_home = tristate_environment ("PRESSURE_VESSEL_SHARE_HOME"); + opt_host_graphics = boolean_environment ("PRESSURE_VESSEL_HOST_GRAPHICS", + TRUE); opt_verbose = boolean_environment ("PRESSURE_VESSEL_VERBOSE", FALSE); if (!opt_shell_cb ("$PRESSURE_VESSEL_SHELL", @@ -2259,8 +907,6 @@ main (int argc, g_debug ("Found executable directory: %s", tools_dir); - pressure_vessel_prefix = g_path_get_dirname (tools_dir); - wrapped_command = flatpak_bwrap_new (NULL); switch (opt_terminal) @@ -2373,19 +1019,6 @@ main (int argc, if (bwrap_help == NULL) goto out; - g_debug ("Creating temporary directories..."); - tmpdir = g_dir_make_tmp ("pressure-vessel-wrap.XXXXXX", error); - - if (tmpdir == NULL) - goto out; - - scratch = g_build_filename (tmpdir, "scratch", NULL); - g_mkdir (scratch, 0700); - overrides = g_build_filename (tmpdir, "overrides", NULL); - g_mkdir (overrides, 0700); - overrides_bin = g_build_filename (overrides, "bin", NULL); - g_mkdir (overrides_bin, 0700); - bwrap = flatpak_bwrap_new (NULL); flatpak_bwrap_add_arg (bwrap, bwrap_executable); @@ -2397,65 +1030,24 @@ main (int argc, if (opt_runtime != NULL && opt_runtime[0] != '\0') { - g_autofree gchar *files_ref = NULL; + PvRuntimeFlags flags = PV_RUNTIME_FLAGS_NONE; - g_debug ("Configuring runtime..."); + if (opt_host_graphics) + flags |= PV_RUNTIME_FLAGS_HOST_GRAPHICS_STACK; - /* Start with just the root tmpfs (which appears automatically) - * and the standard API filesystems */ - pv_bwrap_add_api_filesystems (bwrap); + g_debug ("Configuring runtime %s...", opt_runtime); - /* Take a lock on the runtime until we're finished with setup, - * to make sure it doesn't get deleted. */ - files_ref = g_build_filename (opt_runtime, ".ref", NULL); - runtime_lock = pv_bwrap_lock_new (files_ref, - PV_BWRAP_LOCK_FLAGS_CREATE, - error); + runtime = pv_runtime_new (opt_runtime, + bwrap_executable, + tools_dir, + flags, + error); - /* If the runtime is being deleted, ... don't use it, I suppose? */ - if (runtime_lock == NULL) + if (runtime == NULL) goto out; - pv_search_path_append (bin_path, "/overrides/bin"); - pv_search_path_append (bin_path, g_getenv ("PATH")); - flatpak_bwrap_add_args (bwrap, - "--setenv", "PATH", - bin_path->str, - NULL); - - /* TODO: Adapt the use_ld_so_cache code from Flatpak instead - * of setting LD_LIBRARY_PATH, for better robustness against - * games that set their own LD_LIBRARY_PATH ignoring what they - * got from the environment */ - g_assert (multiarch_tuples[G_N_ELEMENTS (multiarch_tuples) - 1] == NULL); - - for (i = 0; i < G_N_ELEMENTS (multiarch_tuples) - 1; i++) - { - g_autofree gchar *ld_path = NULL; - - ld_path = g_build_filename ("/overrides", "lib", - multiarch_tuples[i], NULL); - - pv_search_path_append (ld_library_path, ld_path); - } - - /* This would be filtered out by a setuid bwrap, so we have to go - * via --setenv. */ - flatpak_bwrap_add_args (bwrap, - "--setenv", "LD_LIBRARY_PATH", - ld_library_path->str, - NULL); - - if (!bind_runtime (bwrap, tools_dir, opt_runtime, overrides, - scratch, error)) + if (!pv_runtime_bind (runtime, bwrap, error)) goto out; - - /* Make sure pressure-vessel itself is visible there. */ - flatpak_bwrap_add_args (bwrap, - "--ro-bind", - pressure_vessel_prefix, - "/run/pressure-vessel", - NULL); } else { @@ -2510,8 +1102,7 @@ main (int argc, if (g_file_test (preload, G_FILE_TEST_EXISTS)) { - if (opt_runtime != NULL - && opt_runtime[0] != '\0' + if (runtime != NULL && (g_str_has_prefix (preload, "/usr/") || g_str_has_prefix (preload, "/lib"))) { @@ -2580,7 +1171,7 @@ main (int argc, /* Put Steam Runtime environment variables back, if /usr is mounted * from the host. */ - if (opt_runtime == NULL || opt_runtime[0] == '\0') + if (runtime == NULL) { g_debug ("Making Steam Runtime available..."); @@ -2630,57 +1221,8 @@ main (int argc, if (!flatpak_bwrap_bundle_args (bwrap, 1, -1, FALSE, error)) goto out; - /* If we are using a runtime, pass the lock fd to the executed process, - * and make it act as a subreaper for the game itself. - * - * If we were using --unshare-pid then we could use bwrap --sync-fd - * and rely on bubblewrap's init process for this, but we currently - * can't do that without breaking gameoverlayrender.so's assumptions. */ - if (runtime_lock != NULL) - { - flatpak_bwrap_add_args (bwrap, - "/run/pressure-vessel/bin/pressure-vessel-with-lock", - "--subreaper", - NULL); - - if (pv_bwrap_lock_is_ofd (runtime_lock)) - { - int fd = pv_bwrap_lock_steal_fd (runtime_lock); - g_autofree gchar *fd_str = NULL; - - g_debug ("Passing lock fd %d down to with-lock", fd); - flatpak_bwrap_add_fd (bwrap, fd); - fd_str = g_strdup_printf ("%d", fd); - flatpak_bwrap_add_args (bwrap, - "--fd", fd_str, - NULL); - } - else - { - /* - * We were unable to take out an open file descriptor lock, - * so it will be released on fork(). Tell the with-lock process - * to take out its own compatible lock instead. There will be - * a short window during which we have lost our lock but the - * with-lock process has not taken its lock - that's unavoidable - * if we want to use exec() to replace ourselves with the - * container. - * - * pv_bwrap_bind_usr() arranges for /.ref to either be a - * symbolic link to /usr/.ref which is the runtime_lock - * (if opt_runtime is a merged /usr), or the runtime_lock - * itself (otherwise). - */ - g_debug ("Telling process in container to lock /.ref"); - flatpak_bwrap_add_args (bwrap, - "--lock-file", "/.ref", - NULL); - } - - flatpak_bwrap_add_args (bwrap, - "--", - NULL); - } + if (runtime != NULL) + pv_runtime_append_lock_adverb (runtime, bwrap); g_debug ("Adding wrapped command..."); flatpak_bwrap_append_args (bwrap, wrapped_command->argv); @@ -2709,15 +1251,8 @@ main (int argc, } /* Clean up temporary directory before running our long-running process */ - if (tmpdir != NULL && - !glnx_shutil_rm_rf_at (-1, tmpdir, NULL, error)) - { - g_warning ("Unable to delete temporary directory: %s", - local_error->message); - g_clear_error (&local_error); - } - - g_clear_pointer (&tmpdir, g_free); + if (runtime != NULL) + pv_runtime_cleanup (runtime); flatpak_bwrap_finish (bwrap); pv_bwrap_execve (bwrap, error); @@ -2726,14 +1261,6 @@ out: if (local_error != NULL) g_warning ("%s", local_error->message); - if (tmpdir != NULL && - !glnx_shutil_rm_rf_at (-1, tmpdir, NULL, error)) - { - g_warning ("Unable to delete temporary directory: %s", - local_error->message); - g_clear_error (&local_error); - } - g_clear_pointer (&opt_ld_preload, g_ptr_array_unref); g_clear_pointer (&opt_env_if_host, g_strfreev); g_clear_pointer (&opt_freedesktop_app_id, g_free);