-
Simon McVittie authored
These arguments take a G_OPTION_ARG_DOUBLE, which is documented to be parsed using either the user's locale or the C locale (in fact they use g_strtod(), which parses it both ways and takes the longer match). The locale matters in locales like de_DE that use a decimal comma instead of a decimal point: we can always parse 2.000000 in the C locale, but we can only parse 2,000000 if we are in a correctly-set-up locale that uses the decimal comma. If we needed to generate new locale files inside the container, then pv-adverb needs to be able to parse its command-line before we have generated those locale files. This means that --terminate-idle-timeout=2,000000 won't work reliably, so we need to use the C locale format, --terminate-idle-timeout=2.000000. Using g_ascii_dtostr() has the side benefit that it uses the shortest possible representation that does not lose precision; in the common case that the timeout is an integer number of seconds, we'll just print it as an integer. Helps: https://github.com/ValveSoftware/steam-runtime/issues/381 Signed-off-by:
Simon McVittie <smcv@collabora.com>
Simon McVittie authoredThese arguments take a G_OPTION_ARG_DOUBLE, which is documented to be parsed using either the user's locale or the C locale (in fact they use g_strtod(), which parses it both ways and takes the longer match). The locale matters in locales like de_DE that use a decimal comma instead of a decimal point: we can always parse 2.000000 in the C locale, but we can only parse 2,000000 if we are in a correctly-set-up locale that uses the decimal comma. If we needed to generate new locale files inside the container, then pv-adverb needs to be able to parse its command-line before we have generated those locale files. This means that --terminate-idle-timeout=2,000000 won't work reliably, so we need to use the C locale format, --terminate-idle-timeout=2.000000. Using g_ascii_dtostr() has the side benefit that it uses the shortest possible representation that does not lose precision; in the common case that the timeout is an integer number of seconds, we'll just print it as an integer. Helps: https://github.com/ValveSoftware/steam-runtime/issues/381 Signed-off-by:
Simon McVittie <smcv@collabora.com>
wrap.c 98.05 KiB
/* pressure-vessel-wrap — run a program in a container that protects $HOME,
* optionally using a Flatpak-style runtime.
*
* Contains code taken from Flatpak.
*
* Copyright © 2014-2019 Red Hat, Inc
* Copyright © 2017-2021 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 <glib.h>
#include <glib/gstdio.h>
#include <gio/gio.h>
#include <locale.h>
#include <stdlib.h>
#include <string.h>
#include "steam-runtime-tools/glib-backports-internal.h"
#include "steam-runtime-tools/utils-internal.h"
#include "libglnx/libglnx.h"
#include "bwrap.h"
#include "bwrap-lock.h"
#include "environ.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"
#include "wrap-setup.h"
/* List of variables that are stripped down from the environment when
* using the secure-execution mode.
* List taken from glibc sysdeps/generic/unsecvars.h */
static const char* unsecure_environment_variables[] = {
"GCONV_PATH",
"GETCONF_DIR",
"GLIBC_TUNABLES",
"HOSTALIASES",
"LD_AUDIT",
"LD_DEBUG",
"LD_DEBUG_OUTPUT",
"LD_DYNAMIC_WEAK",
"LD_HWCAP_MASK",
"LD_LIBRARY_PATH",
"LD_ORIGIN_PATH",
"LD_PRELOAD",
"LD_PROFILE",
"LD_SHOW_AUXV",
"LD_USE_LOAD_BIAS",
"LOCALDOMAIN",
"LOCPATH",
"MALLOC_TRACE",
"NIS_PATH",
"NLSPATH",
"RESOLV_HOST_CONF",
"RES_OPTIONS",
"TMPDIR",
"TZDIR",
NULL,
};
static gchar *
find_executable_dir (GError **error)
{
g_autofree gchar *target = glnx_readlinkat_malloc (-1, "/proc/self/exe",
NULL, error);
if (target == NULL)
return glnx_prefix_error_null (error, "Unable to resolve /proc/self/exe");
return g_path_get_dirname (target);
}
static gchar *
find_bwrap (const char *tools_dir)
{
static const char * const flatpak_libexecdirs[] =
{
"/usr/local/libexec",
"/usr/libexec",
"/usr/lib/flatpak"
};
const char *tmp;
g_autofree gchar *candidate = NULL;
gsize i;
g_return_val_if_fail (tools_dir != NULL, NULL);
tmp = g_getenv ("BWRAP");
if (tmp != NULL)
return g_strdup (tmp);
candidate = g_find_program_in_path ("bwrap");
if (candidate != NULL)
return g_steal_pointer (&candidate);
for (i = 0; i < G_N_ELEMENTS (flatpak_libexecdirs); i++)
{
candidate = g_build_filename (flatpak_libexecdirs[i],
"flatpak-bwrap", NULL);
if (g_file_test (candidate, G_FILE_TEST_IS_EXECUTABLE))
return g_steal_pointer (&candidate);
else
g_clear_pointer (&candidate, g_free);
}
candidate = g_build_filename (tools_dir, "bwrap", NULL);
if (g_file_test (candidate, G_FILE_TEST_IS_EXECUTABLE))
return g_steal_pointer (&candidate);
else
g_clear_pointer (&candidate, g_free);
return NULL;
}
static gchar *
check_bwrap (const char *tools_dir,
gboolean only_prepare)
{
g_autoptr(GError) local_error = NULL;
GError **error = &local_error;
g_autofree gchar *bwrap_executable = NULL;
const char *bwrap_test_argv[] =
{
NULL,
"--bind", "/", "/",
"true",
NULL
};
g_return_val_if_fail (tools_dir != NULL, NULL);
bwrap_executable = find_bwrap (tools_dir);
if (bwrap_executable == NULL)
{
pv_log_failure ("Cannot find bwrap");
}
else if (only_prepare)
{
/* With --only-prepare we don't necessarily expect to be able to run
* it anyway (we are probably in a Docker container that doesn't allow
* creation of nested user namespaces), so just assume that it's the
* right one. */
return g_steal_pointer (&bwrap_executable);
}
else
{
int wait_status;
g_autofree gchar *child_stdout = NULL;
g_autofree gchar *child_stderr = NULL;
bwrap_test_argv[0] = bwrap_executable;
/* We use LEAVE_DESCRIPTORS_OPEN to work around a deadlock in older GLib,
* see flatpak_close_fds_workaround */
if (!g_spawn_sync (NULL, /* cwd */
(gchar **) bwrap_test_argv,
NULL, /* environ */
G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
flatpak_bwrap_child_setup_cb, NULL,
&child_stdout,
&child_stderr,
&wait_status,
error))
{
pv_log_failure ("Cannot run bwrap: %s", local_error->message);
g_clear_error (&local_error);
}
else if (wait_status != 0)
{
pv_log_failure ("Cannot run bwrap: wait status %d", wait_status);
if (child_stdout != NULL && child_stdout[0] != '\0')
pv_log_failure ("Output:\n%s", child_stdout);
if (child_stderr != NULL && child_stderr[0] != '\0')
pv_log_failure ("Diagnostic output:\n%s", child_stderr);
}
else
{
return g_steal_pointer (&bwrap_executable);
}
}
return NULL;
}
static gboolean
check_launch_on_host (const char *launch_executable,
GError **error)
{
g_autofree gchar *child_stdout = NULL;
g_autofree gchar *child_stderr = NULL;
int wait_status;
const char *test_argv[] =
{
NULL,
"--bus-name=org.freedesktop.Flatpak",
"--",
"true",
NULL
};
test_argv[0] = launch_executable;
if (!g_spawn_sync (NULL, /* cwd */
(gchar **) test_argv,
NULL, /* environ */
G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
NULL, NULL, /* child setup */
&child_stdout,
&child_stderr,
&wait_status,
error))
{
return FALSE;
}
if (wait_status != 0)
{
pv_log_failure ("Cannot run commands on host system: wait status %d",
wait_status);
if (child_stdout != NULL && child_stdout[0] != '\0')
pv_log_failure ("Output:\n%s", child_stdout);
if (child_stderr != NULL && child_stderr[0] != '\0')
pv_log_failure ("Diagnostic output:\n%s", child_stderr);
return glnx_throw (error, "Unable to run a command on the host system");
}
return TRUE;
}
/* Nvidia Vulkan ray-tracing requires to load the `nvidia_uvm.ko` kernel
* module, and this is usually done in `libcuda.so.1` by running the setuid
* binary `nvidia-modprobe`. But when we are inside a container we don't bind
* `nvidia-modprobe` and, even if we did, its setuid would not be effective
* because we have `PR_SET_NO_NEW_PRIVS` and we don't have `CAP_SYS_MODULE` in
* our capability bounding set.
* For this reason if the current system is using the proprietary Nvidia
* drivers, and `nvidia_uvm.ko` has not been already loaded, we should execute
* `nvidia-modprobe` before entering in the container environment. */
static gboolean
maybe_load_nvidia_modules (GError **error)
{
const char *nvidia_modprobe_argv[] =
{
"nvidia-modprobe",
"-u",
NULL
};
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
if (g_file_test ("/sys/module/nvidia/version", G_FILE_TEST_IS_REGULAR)
&& !g_file_test ("/sys/module/nvidia_uvm", G_FILE_TEST_IS_DIR))
return pv_run_sync (nvidia_modprobe_argv, NULL, NULL, NULL, error);
return TRUE;
}
typedef enum
{
ENV_MOUNT_FLAGS_COLON_DELIMITED = (1 << 0),
ENV_MOUNT_FLAGS_DEPRECATED = (1 << 1),
ENV_MOUNT_FLAGS_READ_ONLY = (1 << 2),
ENV_MOUNT_FLAGS_NONE = 0
} EnvMountFlags;
typedef struct
{
const char *name;
EnvMountFlags flags;
} EnvMount;
static const EnvMount known_required_env[] =
{
{ "PRESSURE_VESSEL_FILESYSTEMS_RO",
ENV_MOUNT_FLAGS_READ_ONLY | ENV_MOUNT_FLAGS_COLON_DELIMITED },
{ "PRESSURE_VESSEL_FILESYSTEMS_RW", ENV_MOUNT_FLAGS_COLON_DELIMITED },
{ "PROTON_LOG_DIR", ENV_MOUNT_FLAGS_NONE },
{ "STEAM_COMPAT_APP_LIBRARY_PATH", ENV_MOUNT_FLAGS_DEPRECATED },
{ "STEAM_COMPAT_APP_LIBRARY_PATHS",
ENV_MOUNT_FLAGS_COLON_DELIMITED | ENV_MOUNT_FLAGS_DEPRECATED },
{ "STEAM_COMPAT_CLIENT_INSTALL_PATH", ENV_MOUNT_FLAGS_NONE },
{ "STEAM_COMPAT_DATA_PATH", ENV_MOUNT_FLAGS_NONE },
{ "STEAM_COMPAT_INSTALL_PATH", ENV_MOUNT_FLAGS_NONE },
{ "STEAM_COMPAT_LIBRARY_PATHS", ENV_MOUNT_FLAGS_COLON_DELIMITED },
{ "STEAM_COMPAT_MOUNT_PATHS",
ENV_MOUNT_FLAGS_COLON_DELIMITED | ENV_MOUNT_FLAGS_DEPRECATED },
{ "STEAM_COMPAT_MOUNTS", ENV_MOUNT_FLAGS_COLON_DELIMITED },
{ "STEAM_COMPAT_SHADER_PATH", ENV_MOUNT_FLAGS_NONE },
{ "STEAM_COMPAT_TOOL_PATH", ENV_MOUNT_FLAGS_DEPRECATED },
{ "STEAM_COMPAT_TOOL_PATHS", ENV_MOUNT_FLAGS_COLON_DELIMITED },
{ "STEAM_EXTRA_COMPAT_TOOLS_PATHS", ENV_MOUNT_FLAGS_COLON_DELIMITED },
};
static void
bind_and_propagate_from_environ (FlatpakExports *exports,
PvEnviron *container_env,
const char *variable,
EnvMountFlags flags)
{
g_auto(GStrv) values = NULL;
FlatpakFilesystemMode mode = FLATPAK_FILESYSTEM_MODE_READ_WRITE;
const char *value;
const char *before;
const char *after;
gboolean changed = FALSE;
gsize i;
g_return_if_fail (exports != NULL);
g_return_if_fail (variable != NULL);
value = g_getenv (variable);
if (value == NULL)
return;
if (flags & ENV_MOUNT_FLAGS_DEPRECATED)
g_message ("Setting $%s is deprecated", variable);
if (flags & ENV_MOUNT_FLAGS_READ_ONLY)
mode = FLATPAK_FILESYSTEM_MODE_READ_ONLY;
if (flags & ENV_MOUNT_FLAGS_COLON_DELIMITED)
{
values = g_strsplit (value, ":", -1);
before = "...:";
after = ":...";
}
else
{
values = g_new0 (gchar *, 2);
values[0] = g_strdup (value);
values[1] = NULL;
before = "";
after = "";
}
for (i = 0; values[i] != NULL; i++)
{
g_autofree gchar *value_host = NULL;
g_autofree gchar *canon = NULL;
if (values[i][0] == '\0')
continue;
if (!g_file_test (values[i], G_FILE_TEST_EXISTS))
{
g_info ("Not bind-mounting %s=\"%s%s%s\" because it does not exist",
variable, before, values[i], after);
continue;
}
canon = g_canonicalize_filename (values[i], NULL);
value_host = pv_current_namespace_path_to_host_path (canon);
g_info ("Bind-mounting %s=\"%s%s%s\" from the current env as %s=\"%s%s%s\" in the host",
variable, before, values[i], after,
variable, before, value_host, after);
flatpak_exports_add_path_expose (exports, mode, canon);
if (strcmp (values[i], value_host) != 0)
{
g_clear_pointer (&values[i], g_free);
values[i] = g_steal_pointer (&value_host);
changed = TRUE;
}
}
if (changed || g_file_test ("/.flatpak-info", G_FILE_TEST_IS_REGULAR))
{
g_autofree gchar *joined = g_strjoinv (":", values);
pv_environ_lock_env (container_env, variable, joined);
}
}
/* 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. */
static const char * const steam_api_subdirs[] =
{
"root", "steam", "steambeta", "bin", "bin32", "bin64", "sdk32", "sdk64",
};
static gboolean expose_steam (FlatpakExports *exports,
FlatpakFilesystemMode mode,
const char *real_home,
const char *fake_home,
GError **error);
static gboolean
use_fake_home (FlatpakExports *exports,
FlatpakBwrap *bwrap,
PvEnviron *container_env,
const gchar *fake_home,
GError **error)
{
const gchar *real_home = g_get_home_dir ();
g_autofree gchar *cache = g_build_filename (fake_home, ".cache", NULL);
g_autofree gchar *cache2 = g_build_filename (fake_home, "cache", NULL);
g_autofree gchar *tmp = g_build_filename (cache, "tmp", NULL);
g_autofree gchar *config = g_build_filename (fake_home, ".config", NULL);
g_autofree gchar *config2 = g_build_filename (fake_home, "config", NULL);
g_autofree gchar *local = g_build_filename (fake_home, ".local", NULL);
g_autofree gchar *data = g_build_filename (local, "share", NULL);
g_autofree gchar *data2 = g_build_filename (fake_home, "data", NULL);
g_return_val_if_fail (bwrap != NULL, FALSE);
g_return_val_if_fail (exports != NULL, FALSE);
g_return_val_if_fail (fake_home != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
g_mkdir_with_parents (fake_home, 0700);
g_mkdir_with_parents (cache, 0700);
g_mkdir_with_parents (tmp, 0700);
g_mkdir_with_parents (config, 0700);
g_mkdir_with_parents (local, 0700);
g_mkdir_with_parents (data, 0700);
if (!g_file_test (cache2, G_FILE_TEST_EXISTS))
{
g_unlink (cache2);
if (symlink (".cache", cache2) != 0)
return glnx_throw_errno_prefix (error,
"Unable to create symlink %s -> .cache",
cache2);
}
if (!g_file_test (config2, G_FILE_TEST_EXISTS))
{
g_unlink (config2);
if (symlink (".config", config2) != 0)
return glnx_throw_errno_prefix (error,
"Unable to create symlink %s -> .config",
config2);
}
if (!g_file_test (data2, G_FILE_TEST_EXISTS))
{
g_unlink (data2);
if (symlink (".local/share", data2) != 0)
return glnx_throw_errno_prefix (error,
"Unable to create symlink %s -> .local/share",
data2);
}
/* If the logical path to real_home has a symlink among its ancestors
* (e.g. /home/user when /home -> var/home exists), make sure the
* symlink structure gets mirrored in the container */
flatpak_exports_add_path_dir (exports, g_get_home_dir ());
/* Mount the fake home directory onto the physical path to real_home,
* so that it will not conflict with symlinks created by the exports.
* See also https://github.com/flatpak/flatpak/issues/1278 and
* Flatpak commit f1df5cb1 */
flatpak_bwrap_add_bind_arg (bwrap, "--bind", fake_home, real_home);
flatpak_bwrap_add_args (bwrap,
"--bind", tmp, "/var/tmp",
NULL);
pv_environ_lock_env (container_env, "XDG_CACHE_HOME", cache);
pv_environ_lock_env (container_env, "XDG_CONFIG_HOME", config);
pv_environ_lock_env (container_env, "XDG_DATA_HOME", data);
flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_WRITE,
fake_home);
return expose_steam (exports, FLATPAK_FILESYSTEM_MODE_READ_ONLY,
real_home, fake_home, error);
}
static gboolean
expose_steam (FlatpakExports *exports,
FlatpakFilesystemMode mode,
const char *real_home,
const char *fake_home,
GError **error)
{
g_autofree gchar *dot_steam = g_build_filename (real_home, ".steam", NULL);
gsize i;
g_return_val_if_fail (exports != NULL, FALSE);
g_return_val_if_fail (real_home != NULL, FALSE);
g_return_val_if_fail ((unsigned) mode <= FLATPAK_FILESYSTEM_MODE_LAST, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* We need ~/.steam to be visible in the container, even if it's a
* symlink to somewhere outside $HOME. (It's better not to do this; use
* a separate Steam library instead, or use bind-mounts.) */
if (fake_home != NULL)
{
flatpak_exports_add_path_expose (exports, mode, dot_steam);
}
else
{
/* Expose the target, but don't try to create the symlink itself:
* that will fail, because we are already sharing the home directory
* with the container, and there's already a symlink where we want
* to put it. */
g_autofree gchar *target = flatpak_resolve_link (dot_steam, NULL);
if (target != NULL)
flatpak_exports_add_path_expose (exports, mode, target);
}
/*
* These might be API entry points, according to Steam/steam.sh.
* They're usually symlinks into the Steam root, except for in
* older steam Debian packages that had Debian bug #916303.
*
* Even though the symlinks themselves are exposed as part of ~/.steam,
* we need to tell FlatpakExports to also expose the directory to which
* they point, typically (but not necessarily!) ~/.local/share/Steam.
*
* TODO: We probably want to hide part or all of root, steam,
* steambeta?
*/
for (i = 0; i < G_N_ELEMENTS (steam_api_subdirs); i++)
{
g_autofree gchar *dir = g_build_filename (dot_steam,
steam_api_subdirs[i], NULL);
if (fake_home != NULL)
{
g_autofree gchar *mount_point = g_build_filename (fake_home, ".steam",
steam_api_subdirs[i],
NULL);
g_autofree gchar *target = NULL;
target = glnx_readlinkat_malloc (-1, dir, NULL, NULL);
if (target != NULL)
{
/* We used to bind-mount these directories, so transition them
* to symbolic links if we can. */
if (rmdir (mount_point) != 0 && errno != ENOENT && errno != ENOTDIR)
g_debug ("rmdir %s: %s", mount_point, g_strerror (errno));
/* Remove any symlinks that might have already been there. */
if (unlink (mount_point) != 0 && errno != ENOENT)
g_debug ("unlink %s: %s", mount_point, g_strerror (errno));
}
}
flatpak_exports_add_path_expose (exports, mode, dir);
}
return TRUE;
}
/*
* @bwrap: Arguments produced by flatpak_exports_append_bwrap_args(),
* not including an executable name (the 0'th argument must be
* `--bind` or similar)
* @home: The home directory
*
* Adjust arguments in @bwrap to cope with potentially running in a
* container.
*/
static void
adjust_exports (FlatpakBwrap *bwrap,
const char *home)
{
gsize i = 0;
g_debug ("Exported directories:");
while (i < bwrap->argv->len)
{
const char *opt = bwrap->argv->pdata[i];
g_assert (opt != NULL);
if (g_str_equal (opt, "--symlink"))
{
g_assert (i + 3 <= bwrap->argv->len);
/* pdata[i + 1] is the target: unchanged. */
/* pdata[i + 2] is a path in the final container: unchanged. */
g_debug ("%s %s %s",
opt,
(const char *) bwrap->argv->pdata[i + 1],
(const char *) bwrap->argv->pdata[i + 2]);
i += 3;
}
else if (g_str_equal (opt, "--dir") ||
g_str_equal (opt, "--tmpfs"))
{
g_assert (i + 2 <= bwrap->argv->len);
/* pdata[i + 1] is a path in the final container: unchanged. */
g_debug ("%s %s",
opt,
(const char *) bwrap->argv->pdata[i + 1]);
i += 2;
}
else if (g_str_equal (opt, "--ro-bind") ||
g_str_equal (opt, "--bind"))
{
g_autofree gchar *src = NULL;
g_assert (i + 3 <= bwrap->argv->len);
src = g_steal_pointer (&bwrap->argv->pdata[i + 1]);
/* pdata[i + 2] is a path in the final container: unchanged. */
/* Paths in the home directory might need adjusting.
* Paths outside the home directory do not: if they're part of
* /run/host, they've been adjusted already by
* flatpak_exports_take_host_fd(), and if not, they appear in
* the container with the same path as on the host. */
if (flatpak_has_path_prefix (src, home))
bwrap->argv->pdata[i + 1] = pv_current_namespace_path_to_host_path (src);
else
bwrap->argv->pdata[i + 1] = g_steal_pointer (&src);
g_debug ("%s %s %s",
opt,
(const char *) bwrap->argv->pdata[i + 1],
(const char *) bwrap->argv->pdata[i + 2]);
i += 3;
}
else
{
g_return_if_reached ();
}
}
}
typedef enum
{
TRISTATE_NO = 0,
TRISTATE_YES,
TRISTATE_MAYBE
} Tristate;
static gboolean opt_batch = FALSE;
static gboolean opt_copy_runtime = FALSE;
static char **opt_env_if_host = NULL;
static char *opt_fake_home = NULL;
static char **opt_filesystems = NULL;
static char *opt_freedesktop_app_id = NULL;
static char *opt_steam_app_id = NULL;
static gboolean opt_gc_legacy_runtimes = FALSE;
static gboolean opt_gc_runtimes = TRUE;
static gboolean opt_generate_locales = TRUE;
static char *opt_home = NULL;
static char *opt_graphics_provider = NULL;
static char *graphics_provider_mount_point = NULL;
static gboolean opt_launcher = FALSE;
static gboolean opt_only_prepare = FALSE;
static gboolean opt_remove_game_overlay = FALSE;
static gboolean opt_import_vulkan_layers = TRUE;
static PvShell opt_shell = PV_SHELL_NONE;
static GArray *opt_pass_fds = NULL;
static char *opt_runtime = NULL;
static char *opt_runtime_archive = NULL;
static char *opt_runtime_base = NULL;
static char *opt_runtime_id = NULL;
static Tristate opt_share_home = TRISTATE_MAYBE;
static gboolean opt_share_pid = TRUE;
static double opt_terminate_idle_timeout = 0.0;
static double opt_terminate_timeout = -1.0;
static char *opt_variable_dir = NULL;
static gboolean opt_verbose = FALSE;
static gboolean opt_version = FALSE;
static gboolean opt_version_only = FALSE;
static gboolean opt_test = FALSE;
static PvTerminal opt_terminal = PV_TERMINAL_AUTO;
static char *opt_write_final_argv = NULL;
typedef struct
{
const char *variable;
GPtrArray *values;
} PreloadModule;
static PreloadModule opt_preload_modules[] =
{
{ "LD_AUDIT", NULL },
{ "LD_PRELOAD", NULL },
};
static gboolean
append_preload_module (const char *variable,
const char *value,
GError **error)
{
gsize i;
for (i = 0; i < G_N_ELEMENTS (opt_preload_modules); i++)
{
if (strcmp (variable, opt_preload_modules[i].variable) == 0)
break;
}
g_return_val_if_fail (i < G_N_ELEMENTS (opt_preload_modules), FALSE);
if (opt_preload_modules[i].values == NULL)
opt_preload_modules[i].values = g_ptr_array_new_with_free_func (g_free);
g_ptr_array_add (opt_preload_modules[i].values, g_strdup (value));
return TRUE;
}
/*
* check_main_program:
*
* Return %TRUE if the command to execute is the main program.
*/
static gboolean
check_main_program (int argc,
char *argv[])
{
return g_getenv ("SteamAppId") != NULL;
}
static gboolean
opt_host_ld_preload_cb (const gchar *option_name,
const gchar *value,
gpointer data,
GError **error)
{
g_warning ("%s is deprecated, use --ld-preload=%s instead",
option_name, value);
return append_preload_module ("LD_PRELOAD", value, error);
}
static gboolean
opt_ld_audit_cb (const gchar *option_name,
const gchar *value,
gpointer data,
GError **error)
{
return append_preload_module ("LD_AUDIT", value, error);
}
static gboolean
opt_ld_preload_cb (const gchar *option_name,
const gchar *value,
gpointer data,
GError **error)
{
return append_preload_module ("LD_PRELOAD", value, error);
}
static gboolean
opt_copy_runtime_into_cb (const gchar *option_name,
const gchar *value,
gpointer data,
GError **error)
{
if (value == NULL)
{
opt_copy_runtime = FALSE;
}
else if (value[0] == '\0')
{
g_warning ("%s is deprecated, disable with --no-copy-runtime instead",
option_name);
opt_copy_runtime = FALSE;
}
else
{
g_warning ("%s is deprecated, use --copy-runtime and "
"--variable-dir instead",
option_name);
opt_copy_runtime = TRUE;
g_free (opt_variable_dir);
opt_variable_dir = g_strdup (value);
}
return TRUE;
}
static gboolean
opt_pass_fd_cb (const char *name,
const char *value,
gpointer data,
GError **error)
{
char *endptr;
gint64 i64 = g_ascii_strtoll (value, &endptr, 10);
int fd;
int fd_flags;
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
g_return_val_if_fail (value != NULL, FALSE);
if (i64 < 0 || i64 > G_MAXINT || endptr == value || *endptr != '\0')
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Integer out of range or invalid: %s", value);
return FALSE;
}
fd = (int) i64;
fd_flags = fcntl (fd, F_GETFD);
if (fd_flags < 0)
return glnx_throw_errno_prefix (error, "Unable to receive --fd %d", fd);
if (opt_pass_fds == NULL)
opt_pass_fds = g_array_new (FALSE, FALSE, sizeof (int));
g_array_append_val (opt_pass_fds, fd);
return TRUE;
}
static gboolean
opt_shell_cb (const gchar *option_name,
const gchar *value,
gpointer data,
GError **error)
{
if (g_strcmp0 (option_name, "--shell-after") == 0)
value = "after";
else if (g_strcmp0 (option_name, "--shell-fail") == 0)
value = "fail";
else if (g_strcmp0 (option_name, "--shell-instead") == 0)
value = "instead";
if (value == NULL || *value == '\0')
{
opt_shell = PV_SHELL_NONE;
return TRUE;
}
switch (value[0])
{
case 'a':
if (g_strcmp0 (value, "after") == 0)
{
opt_shell = PV_SHELL_AFTER;
return TRUE;
}
break;
case 'f':
if (g_strcmp0 (value, "fail") == 0)
{
opt_shell = PV_SHELL_FAIL;
return TRUE;
}
break;
case 'i':
if (g_strcmp0 (value, "instead") == 0)
{
opt_shell = PV_SHELL_INSTEAD;
return TRUE;
}
break;
case 'n':
if (g_strcmp0 (value, "none") == 0 || g_strcmp0 (value, "no") == 0)
{
opt_shell = PV_SHELL_NONE;
return TRUE;
}
break;
default:
/* fall through to error */
break;
}
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
"Unknown choice \"%s\" for %s", value, option_name);
return FALSE;
}
static gboolean
opt_terminal_cb (const gchar *option_name,
const gchar *value,
gpointer data,
GError **error)
{
if (g_strcmp0 (option_name, "--tty") == 0)
value = "tty";
else if (g_strcmp0 (option_name, "--xterm") == 0)
value = "xterm";
if (value == NULL || *value == '\0')
{
opt_terminal = PV_TERMINAL_AUTO;
return TRUE;
}
switch (value[0])
{
case 'a':
if (g_strcmp0 (value, "auto") == 0)
{
opt_terminal = PV_TERMINAL_AUTO;
return TRUE;
}
break;
case 'n':
if (g_strcmp0 (value, "none") == 0 || g_strcmp0 (value, "no") == 0)
{
opt_terminal = PV_TERMINAL_NONE;
return TRUE;
}
break;
case 't':
if (g_strcmp0 (value, "tty") == 0)
{
opt_terminal = PV_TERMINAL_TTY;
return TRUE;
}
break;
case 'x':
if (g_strcmp0 (value, "xterm") == 0)
{
opt_terminal = PV_TERMINAL_XTERM;
return TRUE;
}
break;
default:
/* fall through to error */
break;
}
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
"Unknown choice \"%s\" for %s", value, option_name);
return FALSE;
}
static gboolean
opt_share_home_cb (const gchar *option_name,
const gchar *value,
gpointer data,
GError **error)
{
if (g_strcmp0 (option_name, "--share-home") == 0)
opt_share_home = TRISTATE_YES;
else if (g_strcmp0 (option_name, "--unshare-home") == 0)
opt_share_home = TRISTATE_NO;
else
g_return_val_if_reached (FALSE);
return TRUE;
}
static gboolean
opt_with_host_graphics_cb (const gchar *option_name,
const gchar *value,
gpointer data,
GError **error)
{
/* This is the old way to get the graphics from the host system */
if (g_strcmp0 (option_name, "--with-host-graphics") == 0)
{
if (g_file_test ("/run/host/usr", G_FILE_TEST_IS_DIR)
&& g_file_test ("/run/host/etc", G_FILE_TEST_IS_DIR))
opt_graphics_provider = g_strdup ("/run/host");
else
opt_graphics_provider = g_strdup ("/");
}
/* This is the old way to avoid using graphics from the host */
else if (g_strcmp0 (option_name, "--without-host-graphics") == 0)
{
opt_graphics_provider = g_strdup ("");
}
else
{
g_return_val_if_reached (FALSE);
}
g_warning ("\"--with-host-graphics\" and \"--without-host-graphics\" have "
"been deprecated and could be removed in future releases. Please use "
"use \"--graphics-provider=/\", \"--graphics-provider=/run/host\" or "
"\"--graphics-provider=\" instead.");
return TRUE;
}
static GOptionEntry options[] =
{
{ "batch", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_batch,
"Disable all interactivity and redirection: ignore --shell*, "
"--terminal, --xterm, --tty. [Default: if $PRESSURE_VESSEL_BATCH]", NULL },
{ "copy-runtime", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_copy_runtime,
"If a --runtime is used, copy it into --variable-dir and edit the "
"copy in-place.",
NULL },
{ "no-copy-runtime", '\0',
G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_copy_runtime,
"Don't behave as described for --copy-runtime. "
"[Default unless $PRESSURE_VESSEL_COPY_RUNTIME is 1 or running in Flatpak]",
NULL },
{ "copy-runtime-into", '\0',
G_OPTION_FLAG_FILENAME|G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK,
&opt_copy_runtime_into_cb,
"Deprecated alias for --copy-runtime and --variable-dir", "DIR" },
{ "env-if-host", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME_ARRAY, &opt_env_if_host,
"Set VAR=VAL if COMMAND is run with /usr from the host system, "
"but not if it is run with /usr from RUNTIME.", "VAR=VAL" },
{ "filesystem", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME_ARRAY, &opt_filesystems,
"Share filesystem directories with the container. "
"They must currently be given as absolute paths.",
"PATH" },
{ "freedesktop-app-id", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &opt_freedesktop_app_id,
"Make --unshare-home use ~/.var/app/ID as home directory, where ID "
"is com.example.MyApp or similar. This interoperates with Flatpak. "
"[Default: $PRESSURE_VESSEL_FDO_APP_ID if set]",
"ID" },
{ "steam-app-id", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &opt_steam_app_id,
"Make --unshare-home use ~/.var/app/com.steampowered.AppN "
"as home directory. [Default: $STEAM_COMPAT_APP_ID or $SteamAppId]",
"N" },
{ "gc-legacy-runtimes", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_gc_legacy_runtimes,
"Garbage-collect old unpacked runtimes in $PRESSURE_VESSEL_RUNTIME_BASE.",
NULL },
{ "no-gc-legacy-runtimes", '\0',
G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_gc_legacy_runtimes,
"Don't garbage-collect old unpacked runtimes in "
"$PRESSURE_VESSEL_RUNTIME_BASE [default].",
NULL },
{ "gc-runtimes", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_gc_runtimes,
"If using --variable-dir, garbage-collect old temporary "
"runtimes. [Default, unless $PRESSURE_VESSEL_GC_RUNTIMES is 0]",
NULL },
{ "no-gc-runtimes", '\0',
G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_gc_runtimes,
"If using --variable-dir, don't garbage-collect old "
"temporary runtimes.", NULL },
{ "generate-locales", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_generate_locales,
"If using --runtime, attempt to generate any missing locales. "
"[Default, unless $PRESSURE_VESSEL_GENERATE_LOCALES is 0]",
NULL },
{ "no-generate-locales", '\0',
G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_generate_locales,
"If using --runtime, don't generate any missing locales.", NULL },
{ "home", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &opt_home,
"Use HOME as home directory. Implies --unshare-home. "
"[Default: $PRESSURE_VESSEL_HOME if set]", "HOME" },
{ "host-ld-preload", '\0',
G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, &opt_host_ld_preload_cb,
"Deprecated alias for --ld-preload=MODULE, which despite its name "
"does not necessarily take the module from the host system",
"MODULE" },
{ "graphics-provider", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &opt_graphics_provider,
"If using --runtime, use PATH as the graphics provider. "
"The path is assumed to be relative to the current namespace, "
"and will be adjusted for use on the host system if pressure-vessel "
"is run in a container. The empty string means use the graphics "
"stack from container."
"[Default: $PRESSURE_VESSEL_GRAPHICS_PROVIDER or '/']", "PATH" },
{ "launcher", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_launcher,
"Instead of specifying a command with its arguments to execute, all the "
"elements after '--' will be used as arguments for "
"'pressure-vessel-launcher'. All the environment variables that are "
"edited by pressure-vessel, or that are known to be wrong in the new "
"container, or that needs to inherit the value from the host system, "
"will be locked. This option implies --batch.", NULL },
{ "ld-audit", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, &opt_ld_audit_cb,
"Add MODULE from current execution environment to LD_AUDIT when "
"executing COMMAND.",
"MODULE" },
{ "ld-preload", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, &opt_ld_preload_cb,
"Add MODULE from current execution environment to LD_PRELOAD when "
"executing COMMAND.",
"MODULE" },
{ "pass-fd", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, opt_pass_fd_cb,
"Let the launched process inherit the given fd.",
NULL },
{ "remove-game-overlay", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_remove_game_overlay,
"Disable the Steam Overlay. "
"[Default if $PRESSURE_VESSEL_REMOVE_GAME_OVERLAY is 1]",
NULL },
{ "keep-game-overlay", '\0',
G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_remove_game_overlay,
"Do not disable the Steam Overlay. "
"[Default unless $PRESSURE_VESSEL_REMOVE_GAME_OVERLAY is 1]",
NULL },
{ "import-vulkan-layers", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_import_vulkan_layers,
"Import Vulkan layers from the host system. "
"[Default unless $PRESSURE_VESSEL_IMPORT_VULKAN_LAYERS is 0]",
NULL },
{ "no-import-vulkan-layers", '\0',
G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_import_vulkan_layers,
"Do not import Vulkan layers from the host system. Please note that "
"certain Vulkan layers might still continue to be reachable from inside "
"the container. This could be the case for all the layers located in "
" `~/.local/share/vulkan` for example, because we usually share the real "
"home directory."
"[Default if $PRESSURE_VESSEL_IMPORT_VULKAN_LAYERS is 0]",
NULL },
{ "runtime", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &opt_runtime,
"Mount the given sysroot or merged /usr in the container, and augment "
"it with the provider's graphics stack. The empty string "
"means don't use a runtime. [Default: $PRESSURE_VESSEL_RUNTIME or '']",
"RUNTIME" },
{ "runtime-archive", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &opt_runtime_archive,
"Unpack the ARCHIVE and use it as the runtime, using --runtime-id to "
"avoid repeatedly unpacking the same archive. "
"[Default: $PRESSURE_VESSEL_RUNTIME_ARCHIVE]",
"ARCHIVE" },
{ "runtime-base", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &opt_runtime_base,
"If a --runtime or --runtime-archive is a relative path, look for "
"it relative to BASE. "
"[Default: $PRESSURE_VESSEL_RUNTIME_BASE or '.']",
"BASE" },
{ "runtime-id", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &opt_runtime_id,
"Reuse a previously-unpacked --runtime-archive if its ID matched this",
"ID" },
{ "share-home", '\0',
G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_share_home_cb,
"Use the real home directory. "
"[Default unless $PRESSURE_VESSEL_HOME is set or "
"$PRESSURE_VESSEL_SHARE_HOME is 0]",
NULL },
{ "unshare-home", '\0',
G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_share_home_cb,
"Use an app-specific home directory chosen according to --home, "
"--freedesktop-app-id, --steam-app-id or $STEAM_COMPAT_APP_ID. "
"[Default if $PRESSURE_VESSEL_HOME is set or "
"$PRESSURE_VESSEL_SHARE_HOME is 0]",
NULL },
{ "share-pid", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_share_pid,
"Do not create a new process ID namespace for the app. "
"[Default, unless $PRESSURE_VESSEL_SHARE_PID is 0]",
NULL },
{ "unshare-pid", '\0',
G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_share_pid,
"Create a new process ID namespace for the app. "
"[Default if $PRESSURE_VESSEL_SHARE_PID is 0]",
NULL },
{ "shell", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, opt_shell_cb,
"--shell=after is equivalent to --shell-after, and so on. "
"[Default: $PRESSURE_VESSEL_SHELL or 'none']",
"{none|after|fail|instead}" },
{ "shell-after", '\0',
G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_shell_cb,
"Run an interactive shell after COMMAND. Executing \"$@\" in that "
"shell will re-run COMMAND [ARGS].",
NULL },
{ "shell-fail", '\0',
G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_shell_cb,
"Run an interactive shell after COMMAND, but only if it fails.",
NULL },
{ "shell-instead", '\0',
G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_shell_cb,
"Run an interactive shell instead of COMMAND. Executing \"$@\" in that "
"shell will run COMMAND [ARGS].",
NULL },
{ "terminal", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, opt_terminal_cb,
"none: disable features that would use a terminal; "
"auto: equivalent to xterm if a --shell option is used, or none; "
"xterm: put game output (and --shell if used) in an xterm; "
"tty: put game output (and --shell if used) on Steam's "
"controlling tty "
"[Default: $PRESSURE_VESSEL_TERMINAL or 'auto']",
"{none|auto|xterm|tty}" },
{ "tty", '\0',
G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_terminal_cb,
"Equivalent to --terminal=tty", NULL },
{ "xterm", '\0',
G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, opt_terminal_cb,
"Equivalent to --terminal=xterm", NULL },
{ "terminate-idle-timeout", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_DOUBLE, &opt_terminate_idle_timeout,
"If --terminate-timeout is used, wait this many seconds before "
"sending SIGTERM. [default: 0.0]",
"SECONDS" },
{ "terminate-timeout", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_DOUBLE, &opt_terminate_timeout,
"Send SIGTERM and SIGCONT to descendant processes that didn't "
"exit within --terminate-idle-timeout. If they don't all exit within "
"this many seconds, send SIGKILL and SIGCONT to survivors. If 0.0, "
"skip SIGTERM and use SIGKILL immediately. Implies --subreaper. "
"[Default: -1.0, meaning don't signal].",
"SECONDS" },
{ "variable-dir", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &opt_variable_dir,
"If a runtime needs to be unpacked or copied, put it in DIR.",
"DIR" },
{ "verbose", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_verbose,
"Be more verbose.", NULL },
{ "version", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_version,
"Print version number and exit.", NULL },
{ "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_NO_ARG | G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK,
opt_with_host_graphics_cb,
"Deprecated alias for \"--graphics-provider=/\" or "
"\"--graphics-provider=/run/host\"", NULL },
{ "without-host-graphics", '\0',
G_OPTION_FLAG_NO_ARG | G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK,
opt_with_host_graphics_cb,
"Deprecated alias for \"--graphics-provider=\"", NULL },
{ "write-final-argv", '\0',
G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_FILENAME, &opt_write_final_argv,
"Write the final argument vector, as null terminated strings, to the "
"given file path.", "PATH" },
{ "test", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_test,
"Smoke test pressure-vessel-wrap and exit.", NULL },
{ "only-prepare", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_only_prepare,
"Prepare runtime, but do not actually run anything.", NULL },
{ NULL }
};
static Tristate
tristate_environment (const gchar *name)
{
const gchar *value = g_getenv (name);
if (g_strcmp0 (value, "1") == 0)
return TRISTATE_YES;
if (g_strcmp0 (value, "0") == 0)
return TRISTATE_NO;
if (value != NULL && value[0] != '\0')
g_warning ("Unrecognised value \"%s\" for $%s", value, name);
return TRISTATE_MAYBE;
}
#define usage_error(...) pv_log_failure (__VA_ARGS__)
int
main (int argc,
char *argv[])
{
g_autoptr(GOptionContext) context = NULL;
g_autoptr(GError) local_error = NULL;
GError **error = &local_error;
int ret = 2;
gsize i;
g_auto(GStrv) original_argv = NULL;
g_auto(GStrv) original_environ = NULL;
int original_argc = argc;
gboolean is_flatpak_env = g_file_test ("/.flatpak-info", G_FILE_TEST_IS_REGULAR);
gboolean is_main_program;
gboolean search_cwd = FALSE;
g_autoptr(FlatpakBwrap) flatpak_subsandbox = NULL;
g_autoptr(FlatpakBwrap) flatpak_run_on_host = NULL;
g_autoptr(PvEnviron) container_env = NULL;
g_autoptr(FlatpakBwrap) bwrap = NULL;
g_autoptr(FlatpakBwrap) bwrap_filesystem_arguments = NULL;
g_autoptr(FlatpakBwrap) bwrap_home_arguments = NULL;
g_autoptr(FlatpakBwrap) argv_in_container = NULL;
g_autoptr(FlatpakBwrap) final_argv = NULL;
g_autoptr(FlatpakExports) exports = NULL;
g_autofree gchar *launch_executable = NULL;
g_autofree gchar *bwrap_executable = NULL;
g_autofree gchar *cwd_p = NULL;
g_autofree gchar *cwd_l = NULL;
g_autofree gchar *cwd_p_host = NULL;
const gchar *home;
const gchar *steam_compat_flags;
g_autofree gchar *tools_dir = NULL;
g_autoptr(PvRuntime) runtime = NULL;
g_autoptr(FILE) original_stdout = NULL;
g_autoptr(GString) lock_env = g_string_new ("");
g_auto(GLnxTmpfile) lock_env_tmpf = { 0, };
g_autofree char *lock_env_fd = NULL;
g_autoptr(GArray) pass_fds_through_adverb = g_array_new (FALSE, FALSE, sizeof (int));
setlocale (LC_ALL, "");
g_set_prgname ("pressure-vessel-wrap");
/* Set up the initial base logging */
pv_set_up_logging (FALSE);
g_info ("pressure-vessel version %s", VERSION);
original_argv = g_new0 (char *, argc + 1);
for (i = 0; i < argc; i++)
original_argv[i] = g_strdup (argv[i]);
if (g_getenv ("STEAM_RUNTIME") != NULL)
{
usage_error ("This program should not be run in the Steam Runtime. "
"Use pressure-vessel-unruntime instead.");
ret = 2;
goto out;
}
original_environ = g_get_environ ();
if (is_flatpak_env)
opt_copy_runtime = TRUE;
is_main_program = check_main_program (argc, argv);
/* Set defaults */
opt_batch = pv_boolean_environment ("PRESSURE_VESSEL_BATCH", FALSE);
/* Process COPY_RUNTIME_INFO first so that COPY_RUNTIME and VARIABLE_DIR
* can override it */
opt_copy_runtime_into_cb ("$PRESSURE_VESSEL_COPY_RUNTIME_INTO",
g_getenv ("PRESSURE_VESSEL_COPY_RUNTIME_INTO"),
NULL, NULL);
opt_copy_runtime = pv_boolean_environment ("PRESSURE_VESSEL_COPY_RUNTIME",
opt_copy_runtime);
opt_runtime_id = g_strdup (g_getenv ("PRESSURE_VESSEL_RUNTIME_ID"));
{
const char *value = g_getenv ("PRESSURE_VESSEL_VARIABLE_DIR");
if (value != NULL)
{
g_free (opt_variable_dir);
opt_variable_dir = g_strdup (value);
}
}
opt_freedesktop_app_id = g_strdup (g_getenv ("PRESSURE_VESSEL_FDO_APP_ID"));
if (opt_freedesktop_app_id != NULL && opt_freedesktop_app_id[0] == '\0')
g_clear_pointer (&opt_freedesktop_app_id, g_free);
opt_home = g_strdup (g_getenv ("PRESSURE_VESSEL_HOME"));
if (opt_home != NULL && opt_home[0] == '\0')
g_clear_pointer (&opt_home, g_free);
opt_remove_game_overlay = pv_boolean_environment ("PRESSURE_VESSEL_REMOVE_GAME_OVERLAY",
FALSE);
opt_import_vulkan_layers = pv_boolean_environment ("PRESSURE_VESSEL_IMPORT_VULKAN_LAYERS",
TRUE);
opt_share_home = tristate_environment ("PRESSURE_VESSEL_SHARE_HOME");
opt_gc_legacy_runtimes = pv_boolean_environment ("PRESSURE_VESSEL_GC_LEGACY_RUNTIMES", FALSE);
opt_gc_runtimes = pv_boolean_environment ("PRESSURE_VESSEL_GC_RUNTIMES", TRUE);
opt_generate_locales = pv_boolean_environment ("PRESSURE_VESSEL_GENERATE_LOCALES", TRUE);
opt_share_pid = pv_boolean_environment ("PRESSURE_VESSEL_SHARE_PID", TRUE);
opt_verbose = pv_boolean_environment ("PRESSURE_VESSEL_VERBOSE", FALSE);
if (!opt_shell_cb ("$PRESSURE_VESSEL_SHELL",
g_getenv ("PRESSURE_VESSEL_SHELL"), NULL, error))
goto out;
if (!opt_terminal_cb ("$PRESSURE_VESSEL_TERMINAL",
g_getenv ("PRESSURE_VESSEL_TERMINAL"), NULL, error))
goto out;
context = g_option_context_new ("[--] COMMAND [ARGS]\n"
"Run COMMAND [ARGS] in a container.\n");
g_option_context_add_main_entries (context, options, NULL);
if (!g_option_context_parse (context, &argc, &argv, error))
goto out;
if (opt_verbose)
pv_set_up_logging (opt_verbose);
/* Specifying either one of these mutually-exclusive options as a
* command-line option disables use of the environment variable for
* the other one */
if (opt_runtime == NULL && opt_runtime_archive == NULL)
{
opt_runtime = g_strdup (g_getenv ("PRESSURE_VESSEL_RUNTIME"));
/* Normalize empty string to NULL to simplify later code */
if (opt_runtime != NULL && opt_runtime[0] == '\0')
g_clear_pointer (&opt_runtime, g_free);
opt_runtime_archive = g_strdup (g_getenv ("PRESSURE_VESSEL_RUNTIME_ARCHIVE"));
if (opt_runtime_archive != NULL && opt_runtime_archive[0] == '\0')
g_clear_pointer (&opt_runtime_archive, g_free);
}
if (opt_runtime_id != NULL)
{
const char *p;
if (opt_runtime_id[0] == '-' || opt_runtime_id[0] == '.')
{
usage_error ("--runtime-id must not start with dash or dot");
goto out;
}
for (p = opt_runtime_id; *p != '\0'; p++)
{
if (!g_ascii_isalnum (*p) && *p != '_' && *p != '-' && *p != '.')
{
usage_error ("--runtime-id may only contain "
"alphanumerics, underscore, dash or dot");
goto out;
}
}
}
if (opt_runtime_base == NULL)
opt_runtime_base = g_strdup (g_getenv ("PRESSURE_VESSEL_RUNTIME_BASE"));
if (opt_runtime != NULL && opt_runtime_archive != NULL)
{
usage_error ("--runtime and --runtime-archive cannot both be used");
goto out;
}
if (opt_graphics_provider == NULL)
opt_graphics_provider = g_strdup (g_getenv ("PRESSURE_VESSEL_GRAPHICS_PROVIDER"));
if (opt_graphics_provider == NULL)
{
/* Also check the deprecated 'PRESSURE_VESSEL_HOST_GRAPHICS' */
Tristate value = tristate_environment ("PRESSURE_VESSEL_HOST_GRAPHICS");
if (value == TRISTATE_MAYBE)
{
opt_graphics_provider = g_strdup ("/");
}
else
{
g_warning ("$PRESSURE_VESSEL_HOST_GRAPHICS is deprecated, "
"please use PRESSURE_VESSEL_GRAPHICS_PROVIDER instead");
if (value == TRISTATE_NO)
opt_graphics_provider = g_strdup ("");
else if (g_file_test ("/run/host/usr", G_FILE_TEST_IS_DIR)
&& g_file_test ("/run/host/etc", G_FILE_TEST_IS_DIR))
opt_graphics_provider = g_strdup ("/run/host");
else
opt_graphics_provider = g_strdup ("/");
}
}
g_assert (opt_graphics_provider != NULL);
if (opt_graphics_provider[0] != '\0' && opt_graphics_provider[0] != '/')
{
usage_error ("--graphics-provider path must be absolute, not \"%s\"",
opt_graphics_provider);
goto out;
}
if (opt_version_only)
{
g_print ("%s\n", VERSION);
ret = 0;
goto out;
}
if (opt_version)
{
g_print ("%s:\n"
" Package: pressure-vessel\n"
" Version: %s\n",
argv[0], VERSION);
ret = 0;
goto out;
}
original_stdout = _srt_divert_stdout_to_stderr (error);
if (original_stdout == NULL)
{
ret = 1;
goto out;
}
_srt_setenv_disable_gio_modules ();
if (argc < 2 && !opt_test && !opt_only_prepare)
{
usage_error ("An executable to run is required");
goto out;
}
if (opt_terminal == PV_TERMINAL_AUTO)
{
if (opt_shell != PV_SHELL_NONE)
opt_terminal = PV_TERMINAL_XTERM;
else
opt_terminal = PV_TERMINAL_NONE;
}
if (opt_terminal == PV_TERMINAL_NONE && opt_shell != PV_SHELL_NONE)
{
usage_error ("--terminal=none is incompatible with --shell");
goto out;
}
/* --launcher implies --batch */
if (opt_launcher)
opt_batch = TRUE;
if (opt_batch)
{
/* --batch or PRESSURE_VESSEL_BATCH=1 overrides these */
opt_shell = PV_SHELL_NONE;
opt_terminal = PV_TERMINAL_NONE;
}
if (argc > 1 && strcmp (argv[1], "--") == 0)
{
argv++;
argc--;
}
home = g_get_home_dir ();
if (opt_share_home == TRISTATE_YES)
{
opt_fake_home = NULL;
}
else if (opt_home)
{
opt_fake_home = g_strdup (opt_home);
}
else if (opt_share_home == TRISTATE_MAYBE)
{
opt_fake_home = NULL;
}
else if (opt_freedesktop_app_id)
{
opt_fake_home = g_build_filename (home, ".var", "app",
opt_freedesktop_app_id, NULL);
}
else if (opt_steam_app_id)
{
opt_freedesktop_app_id = g_strdup_printf ("com.steampowered.App%s",
opt_steam_app_id);
opt_fake_home = g_build_filename (home, ".var", "app",
opt_freedesktop_app_id, NULL);
}
else if (g_getenv ("STEAM_COMPAT_APP_ID") != NULL)
{
opt_freedesktop_app_id = g_strdup_printf ("com.steampowered.App%s",
g_getenv ("STEAM_COMPAT_APP_ID"));
opt_fake_home = g_build_filename (home, ".var", "app",
opt_freedesktop_app_id, NULL);
}
else if (g_getenv ("SteamAppId") != NULL)
{
opt_freedesktop_app_id = g_strdup_printf ("com.steampowered.App%s",
g_getenv ("SteamAppId"));
opt_fake_home = g_build_filename (home, ".var", "app",
opt_freedesktop_app_id, NULL);
}
else
{
usage_error ("Either --home, --freedesktop-app-id, --steam-app-id "
"or $SteamAppId is required");
goto out;
}
if (opt_env_if_host != NULL)
{
for (i = 0; opt_env_if_host[i] != NULL; i++)
{
const char *equals = strchr (opt_env_if_host[i], '=');
if (equals == NULL)
{
usage_error ("--env-if-host argument must be of the form "
"NAME=VALUE, not \"%s\"", opt_env_if_host[i]);
goto out;
}
}
}
if (opt_only_prepare && opt_test)
{
usage_error ("--only-prepare and --test are mutually exclusive");
goto out;
}
if (opt_filesystems != NULL)
{
for (i = 0; opt_filesystems[i] != NULL; i++)
{
if (strchr (opt_filesystems[i], ':') != NULL ||
strchr (opt_filesystems[i], '\\') != NULL)
{
usage_error ("':' and '\\' in --filesystem argument "
"not handled yet");
goto out;
}
else if (!g_path_is_absolute (opt_filesystems[i]))
{
usage_error ("--filesystem argument must be an absolute "
"path, not \"%s\"", opt_filesystems[i]);
goto out;
}
}
}
if (opt_copy_runtime && opt_variable_dir == NULL)
{
usage_error ("--copy-runtime requires --variable-dir");
goto out;
}
/* Finished parsing arguments, so any subsequent failures will make
* us exit 1. */
ret = 1;
if (opt_terminal != PV_TERMINAL_TTY)
{
int fd;
if (!glnx_openat_rdonly (-1, "/dev/null", TRUE, &fd, error))
goto out;
if (dup2 (fd, STDIN_FILENO) < 0)
{
glnx_throw_errno_prefix (error,
"Cannot replace stdin with /dev/null");
goto out;
}
}
pv_get_current_dirs (&cwd_p, &cwd_l);
if (opt_verbose)
{
g_auto(GStrv) env = g_strdupv (original_environ);
g_debug ("Original argv:");
for (i = 0; i < original_argc; i++)
{
g_autofree gchar *quoted = g_shell_quote (original_argv[i]);
g_debug ("\t%" G_GSIZE_FORMAT ": %s", i, quoted);
}
g_debug ("Current working directory:");
g_debug ("\tPhysical: %s", cwd_p);
g_debug ("\tLogical: %s", cwd_l);
g_debug ("Environment variables:");
qsort (env, g_strv_length (env), sizeof (char *), pv_envp_cmp);
for (i = 0; env[i] != NULL; i++)
{
g_autofree gchar *quoted = g_shell_quote (env[i]);
g_debug ("\t%s", quoted);
}
if (opt_launcher)
g_debug ("Arguments for pv-launcher:");
else
g_debug ("Wrapped command:");
for (i = 1; i < argc; i++)
{
g_autofree gchar *quoted = g_shell_quote (argv[i]);
g_debug ("\t%" G_GSIZE_FORMAT ": %s", i, quoted);
}
}
tools_dir = find_executable_dir (error);
if (tools_dir == NULL)
goto out;
g_debug ("Found executable directory: %s", tools_dir);
/* If we are in a Flatpak environment we can't use bwrap directly */
if (is_flatpak_env)
{
launch_executable = g_build_filename (tools_dir,
"pressure-vessel-launch",
NULL);
/* Assume "bwrap" to exist in the host system and to be in its PATH */
bwrap_executable = g_strdup ("bwrap");
/* Deliberately not documented: only people who are in a position
* to run their own modified versions of Flatpak and pressure-vessel
* should be using this, and those people can find this in the
* source code */
if (g_getenv ("PRESSURE_VESSEL_FLATPAK_PR4018") != NULL)
{
g_warning ("Assuming your version of Flatpak contains unmerged "
"changes (#4018, #4125, #4126, #4093)");
/* Use a sub-sandbox */
flatpak_subsandbox = flatpak_bwrap_new (flatpak_bwrap_empty_env);
flatpak_bwrap_add_arg (flatpak_subsandbox,
launch_executable);
/* Tell pressure-vessel-launch to send its whole environment
* to the subsandbox, except for the parts that we edit later.
* This effectively matches bwrap's behaviour. */
flatpak_bwrap_add_arg (flatpak_subsandbox, "--pass-env-matching=*");
flatpak_bwrap_add_arg (flatpak_subsandbox,
"--bus-name=org.freedesktop.portal.Flatpak");
}
/* Also deliberately not documented */
else if (g_getenv ("PRESSURE_VESSEL_FLATPAK_SANDBOX_ESCAPE") != NULL)
{
g_warning ("Assuming permissions have been set to allow Steam "
"to escape from the Flatpak sandbox");
/* If we have permission to escape from the sandbox, we'll do that,
* and launch bwrap that way */
flatpak_run_on_host = flatpak_bwrap_new (flatpak_bwrap_empty_env);
flatpak_bwrap_add_arg (flatpak_run_on_host,
launch_executable);
flatpak_bwrap_add_arg (flatpak_run_on_host,
"--bus-name=org.freedesktop.Flatpak");
/* If we can't launch a command on the host, just fail. */
if (!check_launch_on_host (launch_executable, error))
goto out;
}
else
{
glnx_throw (error,
"pressure-vessel (SteamLinuxRuntime) cannot be run "
"in a Flatpak environment. For Proton 5.13+, "
"unofficial community builds that do not use "
"pressure-vessel are available.");
goto out;
}
}
else
{
g_debug ("Checking for bwrap...");
/* if this fails, it will warn */
bwrap_executable = check_bwrap (tools_dir, opt_only_prepare);
if (bwrap_executable == NULL)
goto out;
g_debug ("OK (%s)", bwrap_executable);
}
if (opt_test)
{
ret = 0;
goto out;
}
/* Invariant: we are in exactly one of these three modes */
g_assert (((flatpak_subsandbox != NULL)
+ (flatpak_run_on_host != NULL)
+ (!is_flatpak_env))
== 1);
if (flatpak_subsandbox == NULL)
{
/* Start with an empty environment and populate it later */
bwrap = flatpak_bwrap_new (flatpak_bwrap_empty_env);
flatpak_bwrap_add_arg (bwrap, bwrap_executable);
bwrap_filesystem_arguments = flatpak_bwrap_new (flatpak_bwrap_empty_env);
exports = flatpak_exports_new ();
}
/* Invariant: we have bwrap or exports iff we also have the other */
g_assert ((bwrap != NULL) == (exports != NULL));
g_assert ((bwrap != NULL) == (bwrap_filesystem_arguments != NULL));
container_env = pv_environ_new ();
if (bwrap != NULL)
{
g_assert (exports != NULL);
g_assert (bwrap_filesystem_arguments != NULL);
if (g_strcmp0 (opt_graphics_provider, "/") == 0)
graphics_provider_mount_point = g_strdup ("/run/host");
else
graphics_provider_mount_point = g_strdup ("/run/gfx");
if (is_flatpak_env)
{
glnx_autofd int fd = TEMP_FAILURE_RETRY (open ("/run/host",
O_CLOEXEC | O_PATH));
if (fd < 0)
{
glnx_throw_errno_prefix (error, "Unable to open /run/host");
goto out;
}
flatpak_exports_take_host_fd (exports, glnx_steal_fd (&fd));
}
/* Protect the controlling terminal from the app/game, unless we are
* running an interactive shell in which case that would break its
* job control. */
if (opt_terminal != PV_TERMINAL_TTY)
flatpak_bwrap_add_arg (bwrap, "--new-session");
/* Start with just the root tmpfs (which appears automatically)
* and the standard API filesystems */
pv_bwrap_add_api_filesystems (bwrap_filesystem_arguments);
/* The FlatpakExports will populate /run/host for us */
flatpak_exports_add_host_etc_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_ONLY);
flatpak_exports_add_host_os_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_ONLY);
/* steam-runtime-system-info uses this to detect pressure-vessel, so we
* need to create it even if it will be empty */
flatpak_bwrap_add_args (bwrap_filesystem_arguments,
"--dir",
"/run/pressure-vessel",
NULL);
}
else
{
g_assert (flatpak_subsandbox != NULL);
if (g_strcmp0 (opt_graphics_provider, "/") == 0)
{
graphics_provider_mount_point = g_strdup ("/run/parent");
}
else if (g_strcmp0 (opt_graphics_provider, "/run/host") == 0)
{
g_warning ("Using host graphics drivers in a Flatpak subsandbox "
"probably won't work");
graphics_provider_mount_point = g_strdup ("/run/host");
}
else
{
glnx_throw (error,
"Flatpak subsandboxing can only use / or /run/host "
"to provide graphics drivers");
goto out;
}
}
if (opt_gc_legacy_runtimes
&& opt_runtime_base != NULL
&& opt_runtime_base[0] != '\0'
&& opt_variable_dir != NULL)
{
if (!pv_runtime_garbage_collect_legacy (opt_variable_dir,
opt_runtime_base,
&local_error))
{
g_warning ("Unable to clean up old runtimes: %s",
local_error->message);
g_clear_error (&local_error);
}
}
steam_compat_flags = g_getenv ("STEAM_COMPAT_FLAGS");
if (steam_compat_flags != NULL)
{
g_auto(GStrv) flags = g_strsplit (steam_compat_flags, ",", -1);
for (i = 0; flags[i] != NULL; i++)
{
if (g_strcmp0 (flags[i], "search-cwd") == 0)
/* This option is used to append the game install path to
* LD_LIBRARY_PATH for legacy purposes, to cope with games that
* relied on the old behaviour of LD_LIBRARY_PATH of ending with
* a colon, which ld.so interprets as the current working directory. */
search_cwd = TRUE;
else
g_info ("STEAM_COMPAT_FLAGS has the unexpected flag \"%s\"", flags[i]);
}
}
if (opt_runtime != NULL || opt_runtime_archive != NULL)
{
PvRuntimeFlags flags = PV_RUNTIME_FLAGS_NONE;
g_autofree gchar *runtime_resolved = NULL;
const char *runtime_path = NULL;
if (opt_gc_runtimes)
flags |= PV_RUNTIME_FLAGS_GC_RUNTIMES;
if (opt_generate_locales)
flags |= PV_RUNTIME_FLAGS_GENERATE_LOCALES;
if (opt_graphics_provider != NULL && opt_graphics_provider[0] != '\0')
flags |= PV_RUNTIME_FLAGS_PROVIDER_GRAPHICS_STACK;
if (opt_verbose)
flags |= PV_RUNTIME_FLAGS_VERBOSE;
if (opt_import_vulkan_layers)
flags |= PV_RUNTIME_FLAGS_IMPORT_VULKAN_LAYERS;
if (opt_copy_runtime)
flags |= PV_RUNTIME_FLAGS_COPY_RUNTIME;
if (flatpak_subsandbox != NULL)
flags |= PV_RUNTIME_FLAGS_FLATPAK_SUBSANDBOX;
/* Only if we are running the main program, append the working directory
* of the game to LD_LIBRARY_PATH. This option is not intended for the
* setup phase. */
if (search_cwd && is_main_program)
flags |= PV_RUNTIME_FLAGS_SEARCH_CWD;
if (opt_runtime != NULL)
{
/* already checked for mutually exclusive options */
g_assert (opt_runtime_archive == NULL);
runtime_path = opt_runtime;
}
else
{
flags |= PV_RUNTIME_FLAGS_UNPACK_ARCHIVE;
runtime_path = opt_runtime_archive;
}
if (!g_path_is_absolute (runtime_path)
&& opt_runtime_base != NULL
&& opt_runtime_base[0] != '\0')
{
runtime_resolved = g_build_filename (opt_runtime_base,
runtime_path, NULL);
runtime_path = runtime_resolved;
}
g_debug ("Configuring runtime %s...", runtime_path);
if (is_flatpak_env && !opt_copy_runtime)
{
glnx_throw (error,
"Cannot set up a runtime inside Flatpak without "
"making a mutable copy");
goto out;
}
runtime = pv_runtime_new (runtime_path,
opt_runtime_id,
opt_variable_dir,
bwrap_executable,
tools_dir,
opt_graphics_provider,
graphics_provider_mount_point,
original_environ,
flags,
error);
if (runtime == NULL)
goto out;
if (!pv_runtime_bind (runtime,
exports,
bwrap_filesystem_arguments,
container_env,
error))
goto out;
if (flatpak_subsandbox != NULL)
{
const char *usr = pv_runtime_get_modified_usr (runtime);
flatpak_bwrap_add_args (flatpak_subsandbox,
"--app-path=",
"--share-pids",
"--usr-path", usr,
NULL);
}
}
else if (flatpak_subsandbox != NULL)
{
/* Nothing special to do here: we'll just create the subsandbox
* without changing the runtime, which means we inherit the
* Flatpak's normal runtime. */
}
else if (flatpak_run_on_host != NULL)
{
glnx_throw (error,
"Cannot operate without a runtime when escaping from "
"a Flatpak app");
goto out;
}
else
{
g_assert (!is_flatpak_env);
g_assert (bwrap != NULL);
g_assert (bwrap_filesystem_arguments != NULL);
g_assert (exports != NULL);
if (!pv_wrap_use_host_os (exports, bwrap_filesystem_arguments, error))
goto out;
}
/* Protect other users' homes (but guard against the unlikely
* situation that they don't exist). We use the FlatpakExports for this
* so that it can be overridden by --filesystem=/home, and so that it
* is sorted correctly with respect to all the other
* home-directory-related exports. */
if (exports != NULL
&& g_file_test ("/home", G_FILE_TEST_EXISTS))
flatpak_exports_add_path_tmpfs (exports, "/home");
g_debug ("Making home directory available...");
if (flatpak_subsandbox != NULL)
{
if (opt_fake_home == NULL)
{
/* Nothing special to do here: we'll use the same home directory
* and exports that the parent Flatpak sandbox used. */
}
else if (flatpak_subsandbox != NULL)
{
/* Not yet supported */
glnx_throw (error,
"Cannot use a game-specific home directory in a "
"Flatpak subsandbox");
goto out;
}
}
else
{
g_assert (flatpak_run_on_host != NULL || !is_flatpak_env);
g_assert (bwrap != NULL);
g_assert (bwrap_filesystem_arguments != NULL);
g_assert (exports != NULL);
if (opt_fake_home == NULL)
{
flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_WRITE,
home);
/* We always export /tmp for now (see below) and it seems odd
* to share /tmp with the host, but not /var/tmp. */
flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_WRITE,
"/var/tmp");
/* TODO: All of ~/.steam has traditionally been read/write when not
* using a per-game home directory, but does it need to be? Maybe we
* should have a future "compat level" in which it's read-only,
* like it already is when using a per-game home directory. */
if (!expose_steam (exports, FLATPAK_FILESYSTEM_MODE_READ_WRITE,
home, NULL, error))
goto out;
}
else
{
bwrap_home_arguments = flatpak_bwrap_new (flatpak_bwrap_empty_env);
if (!use_fake_home (exports, bwrap_home_arguments,
container_env, opt_fake_home,
error))
goto out;
}
}
if (!opt_share_pid)
{
if (bwrap != NULL)
{
g_warning ("Unsharing process ID namespace. This is not expected "
"to work...");
flatpak_bwrap_add_arg (bwrap, "--unshare-pid");
}
else
{
g_assert (flatpak_subsandbox != NULL);
/* pressure-vessel-launch currently hard-codes this */
g_warning ("Process ID namespace is always shared when using a "
"Flatpak subsandbox");
}
}
if (exports != NULL)
/* Always export /tmp for now. SteamVR uses this as a rendezvous
* directory for IPC. */
flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_WRITE,
"/tmp");
/* We need the LD_PRELOADs from Steam visible at the paths that were
* used for them, which might be their physical rather than logical
* locations. Steam doesn't generally use LD_AUDIT, but the Steam app
* on Flathub does, and it needs similar handling. */
for (i = 0; i < G_N_ELEMENTS (opt_preload_modules); i++)
{
const char *variable = opt_preload_modules[i].variable;
const GPtrArray *values = opt_preload_modules[i].values;
g_autoptr(GString) adjusted = NULL;
gsize len;
gsize j;
adjusted = g_string_new ("");
g_debug ("Adjusting %s...", variable);
if (values == NULL)
len = 0;
else
len = values->len;
for (j = 0; j < len; j++)
{
const char *preload = g_ptr_array_index (values, j);
g_assert (preload != NULL);
if (*preload == '\0')
continue;
if (strstr (preload, "gtk3-nocsd") != NULL)
{
g_warning ("Disabling gtk3-nocsd %s: it is known to cause crashes.",
variable);
continue;
}
if (opt_remove_game_overlay
&& g_str_has_suffix (preload, "/gameoverlayrenderer.so"))
{
g_info ("Disabling Steam Overlay: %s", preload);
continue;
}
/* A subsandbox will just have the same LD_PRELOAD as the
* Flatpak itself, except that we have to redirect /usr and /app
* into /run/parent. */
if (flatpak_subsandbox != NULL)
{
if (runtime != NULL
&& (g_str_has_prefix (preload, "/usr/")
|| g_str_has_prefix (preload, "/app/")
|| g_str_has_prefix (preload, "/lib")))
{
g_autofree gchar *adjusted_path = NULL;
adjusted_path = g_build_filename ("/run/parent", preload, NULL);
g_debug ("%s -> %s", preload, adjusted_path);
pv_search_path_append (adjusted, adjusted_path);
}
else
{
g_debug ("%s -> unmodified", preload);
pv_search_path_append (adjusted, preload);
}
/* No FlatpakExports here: any file not in /usr or /app that
* is visible to our "parent" Flatpak app is also visible
* to us. */
continue;
}
if (g_file_test (preload, G_FILE_TEST_EXISTS))
{
if (runtime != NULL
&& (g_str_has_prefix (preload, "/usr/")
|| g_str_has_prefix (preload, "/lib")))
{
g_autofree gchar *adjusted_path = NULL;
adjusted_path = g_build_filename ("/run/host", preload, NULL);
g_debug ("%s -> %s", preload, adjusted_path);
/* When using a runtime we can't write to /usr/ or /libQUAL/,
* so redirect this preloaded module to the corresponding
* location in /run/host. */
pv_search_path_append (adjusted, adjusted_path);
}
else
{
const gchar *steam_path = NULL;
steam_path = g_getenv ("STEAM_COMPAT_CLIENT_INSTALL_PATH");
if (steam_path != NULL && flatpak_has_path_prefix (preload, steam_path))
{
g_debug ("Skipping exposing \"%s\" because it is located "
"under the Steam client install path that we "
"bind by default", preload);
}
else
{
g_debug ("%s -> unmodified, but added to exports", preload);
flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_ONLY,
preload);
}
pv_search_path_append (adjusted, preload);
}
}
else
{
g_info ("%s module '%s' does not exist", variable, preload);
}
}
if (bwrap != NULL)
{
/* If we adjusted the module paths from the one provided by the host
* to something that is valid in the container, we shouldn't add them to
* the bwrap envp. Otherwise when we call "pv_bwrap_execve()" we will
* create an environment that tries to preload libraries that are not
* available, until it actually executes "bwrap".
* This can be avoided by using the bwrap option "--setenv" instead. */
if (adjusted->len != 0)
flatpak_bwrap_add_args (bwrap, "--setenv", variable,
adjusted->str, NULL);
}
else
{
if (adjusted->len != 0)
pv_environ_lock_env (container_env, variable, adjusted->str);
else
pv_environ_lock_env (container_env, variable, NULL);
}
}
if (flatpak_subsandbox == NULL)
{
g_assert (bwrap != NULL);
g_assert (bwrap_filesystem_arguments != NULL);
g_assert (exports != NULL);
g_debug ("Making Steam environment variables available if required...");
for (i = 0; i < G_N_ELEMENTS (known_required_env); i++)
bind_and_propagate_from_environ (exports, container_env,
known_required_env[i].name,
known_required_env[i].flags);
/* On NixOS, all paths hard-coded into libraries are in here */
flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_ONLY,
"/nix");
/* Make arbitrary filesystems available. This is not as complete as
* Flatpak yet. */
if (opt_filesystems != NULL)
{
g_debug ("Processing --filesystem arguments...");
for (i = 0; opt_filesystems[i] != NULL; i++)
{
/* We already checked this */
g_assert (g_path_is_absolute (opt_filesystems[i]));
g_info ("Bind-mounting \"%s\"", opt_filesystems[i]);
flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_WRITE,
opt_filesystems[i]);
}
}
/* Make sure the current working directory (the game we are going to
* run) is available. Some games write here. */
g_debug ("Making current working directory available...");
cwd_p_host = pv_current_namespace_path_to_host_path (cwd_p);
if (_srt_is_same_file (home, cwd_p))
{
g_info ("Not making physical working directory \"%s\" available to "
"container because it is the home directory",
cwd_p);
}
else
{
/* If in Flatpak, we assume that cwd_p_host is visible in the
* current namespace as well as in the host, because it's
* either in our ~/.var/app/$FLATPAK_ID, or a --filesystem that
* was exposed from the host. */
flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_WRITE,
cwd_p_host);
}
flatpak_bwrap_add_args (bwrap,
"--chdir", cwd_p_host,
NULL);
}
else
{
for (i = 0; i < G_N_ELEMENTS (known_required_env); i++)
pv_environ_lock_env (container_env,
known_required_env[i].name,
g_getenv (known_required_env[i].name));
flatpak_bwrap_add_args (flatpak_subsandbox,
"--directory", cwd_p,
NULL);
}
pv_environ_lock_env (container_env, "PWD", NULL);
/* Put Steam Runtime environment variables back, if /usr is mounted
* from the host. */
if (runtime == NULL)
{
g_debug ("Making Steam Runtime available...");
/* We need libraries from the Steam Runtime, so make sure that's
* visible (it should never need to be read/write though) */
if (opt_env_if_host != NULL)
{
for (i = 0; opt_env_if_host[i] != NULL; i++)
{
char *equals = strchr (opt_env_if_host[i], '=');
g_assert (equals != NULL);
if (exports != NULL
&& g_str_has_prefix (opt_env_if_host[i], "STEAM_RUNTIME=/"))
flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_ONLY,
equals + 1);
*equals = '\0';
pv_environ_lock_env (container_env, opt_env_if_host[i],
equals + 1);
*equals = '=';
}
}
}
/* Convert the exported directories into extra bubblewrap arguments */
if (exports != NULL)
{
g_autoptr(FlatpakBwrap) exports_bwrap =
flatpak_bwrap_new (flatpak_bwrap_empty_env);
g_assert (bwrap != NULL);
g_assert (bwrap_filesystem_arguments != NULL);
if (bwrap_home_arguments != NULL)
{
/* The filesystem arguments to set up a fake $HOME (if any) have
* to come before the exports, as they do in Flatpak, so that
* mounting the fake $HOME will not mask the exports used for
* ~/.steam, etc. */
g_warn_if_fail (g_strv_length (bwrap_home_arguments->envp) == 0);
flatpak_bwrap_append_bwrap (bwrap, bwrap_home_arguments);
g_clear_pointer (&bwrap_home_arguments, flatpak_bwrap_free);
}
flatpak_exports_append_bwrap_args (exports, exports_bwrap);
adjust_exports (exports_bwrap, home);
g_warn_if_fail (g_strv_length (exports_bwrap->envp) == 0);
flatpak_bwrap_append_bwrap (bwrap, exports_bwrap);
/* The other filesystem arguments have to come after the exports
* so that if the exports set up symlinks, the other filesystem
* arguments like --dir work with the symlinks' targets. */
g_warn_if_fail (g_strv_length (bwrap_filesystem_arguments->envp) == 0);
flatpak_bwrap_append_bwrap (bwrap, bwrap_filesystem_arguments);
g_clear_pointer (&bwrap_filesystem_arguments, flatpak_bwrap_free);
}
if (bwrap != NULL)
pv_wrap_share_sockets (bwrap, container_env,
(runtime != NULL),
is_flatpak_env);
if (is_flatpak_env)
{
g_autoptr(GList) vars = NULL;
const GList *iter;
if (flatpak_run_on_host != NULL)
{
/* These are the environment variables that will be wrong, or useless,
* in the new container that will be created by escaping from the
* sandbox. Force them to be unset. */
pv_environ_lock_env (container_env, "FLATPAK_ID", NULL);
pv_environ_lock_env (container_env, "FLATPAK_SANDBOX_DIR", NULL);
}
else
{
/* Let these inherit from the sub-sandbox environment */
pv_environ_lock_inherit_env (container_env, "FLATPAK_ID");
pv_environ_lock_inherit_env (container_env, "FLATPAK_SANDBOX_DIR");
}
/* These are the environment variables that might differ in the host
* system. However from inside a container we are not able to know the
* host's value. So we allow them to inherit the value from the host.
* Similarly, if we're starting a sub-sandbox, they should take the
* sub-sandbox's value if different. */
pv_environ_lock_inherit_env (container_env, "DBUS_SESSION_BUS_ADDRESS");
pv_environ_lock_inherit_env (container_env, "DBUS_SYSTEM_BUS_ADDRESS");
pv_environ_lock_inherit_env (container_env, "DISPLAY");
pv_environ_lock_inherit_env (container_env, "XDG_RUNTIME_DIR");
/* The bwrap envp will be completely ignored when calling
* pv-launch, and in fact putting them in its environment
* variables would be wrong, because pv-launch needs to see the
* current execution environment's DBUS_SESSION_BUS_ADDRESS
* (if different). For this reason we convert them to `--setenv`. */
vars = pv_environ_get_vars (container_env);
for (iter = vars; iter != NULL; iter = iter->next)
{
const char *var = iter->data;
const char *val = pv_environ_getenv (container_env, var);
if (flatpak_subsandbox != NULL)
{
if (val != NULL)
flatpak_bwrap_add_arg_printf (flatpak_subsandbox,
"--env=%s=%s",
var, val);
else
flatpak_bwrap_add_args (flatpak_subsandbox,
"--unset-env", var,
NULL);
}
else
{
g_assert (bwrap != NULL);
if (val != NULL)
flatpak_bwrap_add_args (bwrap,
"--setenv", var, val,
NULL);
else
flatpak_bwrap_add_args (bwrap,
"--unsetenv", var,
NULL);
}
}
}
final_argv = flatpak_bwrap_new (original_environ);
/* Lock variables where appropriate */
{
g_autoptr(GList) vars = pv_environ_get_locked (container_env);
const GList *iter;
for (iter = vars; iter != NULL; iter = iter->next)
{
const char *var = iter->data;
g_debug ("Locking environment variable: %s", var);
g_string_append (lock_env, var);
g_string_append_c (lock_env, '\0');
}
}
/* Populate final_argv->envp, overwriting its copy of original_environ.
* We skip this if we are in a Flatpak environment, because in that case
* we already used `--setenv` for all the variables that we care about and
* the final_argv->envp will be ignored anyway, other than as a way to
* invoke pv-launch (for which original_environ is appropriate). */
if (!is_flatpak_env)
{
g_autoptr(GList) vars = NULL;
const GList *iter;
vars = pv_environ_get_vars (container_env);
for (iter = vars; iter != NULL; iter = iter->next)
{
const char *var = iter->data;
const char *val = pv_environ_getenv (container_env, var);
if (val != NULL)
flatpak_bwrap_set_env (final_argv, var, val, TRUE);
else
flatpak_bwrap_unset_env (final_argv, var);
}
/* The setuid bwrap will filter out some of the environment variables,
* so we still have to go via --setenv for these. */
for (i = 0; unsecure_environment_variables[i] != NULL; i++)
{
const char *var = unsecure_environment_variables[i];
const gchar *val = pv_environ_getenv (container_env, var);
if (val != NULL)
flatpak_bwrap_add_args (bwrap, "--setenv", var, val, NULL);
}
}
/* Now that we've populated final_argv->envp, it's too late to change
* any environment variables. Make sure we get an assertion failure
* if we try. */
g_clear_pointer (&container_env, pv_environ_free);
if (opt_launcher)
{
if (!flatpak_buffer_to_sealed_memfd_or_tmpfile (&lock_env_tmpf, "lock-env",
lock_env->str, lock_env->len,
error))
goto out;
lock_env_fd = g_strdup_printf ("%d", lock_env_tmpf.fd);
}
if (flatpak_run_on_host != NULL)
{
int userns_fd, userns2_fd, pidns_fd;
/* Tell the bwrap instance on the host to join the same user and pid
* namespaces as Steam in Flatpak. Otherwise, pid-based IPC between
* the Steam client and the game will fail.
*
* This is not expected to work if bwrap on the host is setuid,
* so it will not work for users of Debian, Arch linux-hardened, etc.,
* but it's better than nothing. */
userns_fd = open ("/run/.userns", O_RDONLY | O_CLOEXEC);
if (userns_fd >= 0)
{
g_array_append_val (pass_fds_through_adverb, userns_fd);
flatpak_bwrap_add_args_data_fd (bwrap, "--userns",
glnx_steal_fd (&userns_fd),
NULL);
userns2_fd = open_namespace_fd_if_needed ("/proc/self/ns/user",
"/run/.userns");
if (userns2_fd >= 0)
{
g_array_append_val (pass_fds_through_adverb, userns2_fd);
flatpak_bwrap_add_args_data_fd (bwrap, "--userns2",
glnx_steal_fd (&userns2_fd),
NULL);
}
}
pidns_fd = open ("/proc/self/ns/pid", O_RDONLY | O_CLOEXEC);
if (pidns_fd >= 0)
{
g_array_append_val (pass_fds_through_adverb, pidns_fd);
flatpak_bwrap_add_args_data_fd (bwrap, "--pidns",
glnx_steal_fd (&pidns_fd),
NULL);
}
}
if (bwrap != NULL)
{
/* Tell the application that it's running under a container manager
* in a generic way (based on https://systemd.io/CONTAINER_INTERFACE/,
* although a lot of that document is intended for "system"
* containers and is less suitable for "app" containers like
* Flatpak and pressure-vessel). */
flatpak_bwrap_add_args (bwrap,
"--setenv", "container", "pressure-vessel",
NULL);
if (!flatpak_bwrap_add_args_data (bwrap,
"container-manager",
"pressure-vessel\n", -1,
"/run/host/container-manager",
error))
return FALSE;
if (opt_verbose)
{
g_debug ("%s options before bundling:", bwrap_executable);
for (i = 0; i < bwrap->argv->len; i++)
{
g_autofree gchar *quoted = NULL;
quoted = g_shell_quote (g_ptr_array_index (bwrap->argv, i));
g_debug ("\t%s", quoted);
}
}
if (!opt_only_prepare)
{
if (!flatpak_bwrap_bundle_args (bwrap, 1, -1, FALSE, error))
goto out;
}
}
argv_in_container = flatpak_bwrap_new (flatpak_bwrap_empty_env);
/* Set up adverb inside container */
{
g_autoptr(FlatpakBwrap) adverb_argv = NULL;
adverb_argv = flatpak_bwrap_new (flatpak_bwrap_empty_env);
if (runtime != NULL)
{
if (!pv_runtime_get_adverb (runtime, adverb_argv))
goto out;
}
else
{
/* If not using a runtime, the adverb in the container has the
* same path as outside */
g_autofree gchar *adverb_in_container =
g_build_filename (tools_dir, "pressure-vessel-adverb", NULL);
flatpak_bwrap_add_arg (adverb_argv, adverb_in_container);
}
if (opt_terminate_timeout >= 0.0)
{
char idle_buf[G_ASCII_DTOSTR_BUF_SIZE] = {};
char timeout_buf[G_ASCII_DTOSTR_BUF_SIZE] = {};
g_ascii_dtostr (idle_buf, sizeof (idle_buf),
opt_terminate_idle_timeout);
g_ascii_dtostr (timeout_buf, sizeof (timeout_buf),
opt_terminate_timeout);
if (opt_terminate_idle_timeout > 0.0)
flatpak_bwrap_add_arg_printf (adverb_argv,
"--terminate-idle-timeout=%s",
idle_buf);
flatpak_bwrap_add_arg_printf (adverb_argv,
"--terminate-timeout=%s",
timeout_buf);
}
flatpak_bwrap_add_args (adverb_argv,
"--exit-with-parent",
"--subreaper",
NULL);
if (opt_pass_fds != NULL)
{
for (i = 0; i < opt_pass_fds->len; i++)
{
int fd = g_array_index (opt_pass_fds, int, i);
flatpak_bwrap_add_fd (adverb_argv, fd);
flatpak_bwrap_add_arg_printf (adverb_argv, "--pass-fd=%d", fd);
}
}
for (i = 0; i < pass_fds_through_adverb->len; i++)
{
int fd = g_array_index (pass_fds_through_adverb, int, i);
flatpak_bwrap_add_arg_printf (adverb_argv, "--pass-fd=%d", fd);
}
if (lock_env_fd != NULL)
flatpak_bwrap_add_arg_printf (adverb_argv, "--pass-fd=%s", lock_env_fd);
switch (opt_shell)
{
case PV_SHELL_AFTER:
flatpak_bwrap_add_arg (adverb_argv, "--shell=after");
break;
case PV_SHELL_FAIL:
flatpak_bwrap_add_arg (adverb_argv, "--shell=fail");
break;
case PV_SHELL_INSTEAD:
flatpak_bwrap_add_arg (adverb_argv, "--shell=instead");
break;
case PV_SHELL_NONE:
flatpak_bwrap_add_arg (adverb_argv, "--shell=none");
break;
default:
g_warn_if_reached ();
}
switch (opt_terminal)
{
case PV_TERMINAL_AUTO:
flatpak_bwrap_add_arg (adverb_argv, "--terminal=auto");
break;
case PV_TERMINAL_NONE:
flatpak_bwrap_add_arg (adverb_argv, "--terminal=none");
break;
case PV_TERMINAL_TTY:
flatpak_bwrap_add_arg (adverb_argv, "--terminal=tty");
break;
case PV_TERMINAL_XTERM:
flatpak_bwrap_add_arg (adverb_argv, "--terminal=xterm");
break;
default:
g_warn_if_reached ();
break;
}
if (opt_verbose)
flatpak_bwrap_add_arg (adverb_argv, "--verbose");
flatpak_bwrap_add_arg (adverb_argv, "--");
g_warn_if_fail (g_strv_length (adverb_argv->envp) == 0);
flatpak_bwrap_append_bwrap (argv_in_container, adverb_argv);
}
if (opt_launcher)
{
g_autoptr(FlatpakBwrap) launcher_argv =
flatpak_bwrap_new (flatpak_bwrap_empty_env);
g_autofree gchar *pressure_vessel_launcher = g_build_filename (tools_dir,
"pressure-vessel-launcher",
NULL);
g_debug ("Adding pressure-vessel-launcher '%s'...", pressure_vessel_launcher);
flatpak_bwrap_add_arg (launcher_argv, pressure_vessel_launcher);
if (opt_verbose)
flatpak_bwrap_add_arg (launcher_argv, "--verbose");
g_debug ("Adding locked environment variables...");
g_assert (lock_env_fd != NULL);
g_assert (lock_env_tmpf.fd >= 0);
flatpak_bwrap_add_fd (launcher_argv, glnx_steal_fd (&lock_env_tmpf.fd));
flatpak_bwrap_add_args (launcher_argv,
"--lock-env-from-fd", lock_env_fd, NULL);
/* In --launcher mode, arguments after the "--" separator are
* passed to the launcher */
flatpak_bwrap_append_argsv (launcher_argv, &argv[1], argc - 1);
g_warn_if_fail (g_strv_length (launcher_argv->envp) == 0);
flatpak_bwrap_append_bwrap (argv_in_container, launcher_argv);
}
else
{
/* In non-"--launcher" mode, arguments after the "--" separator
* are the command to execute, passed to the adverb after "--".
* Because we always use the adverb, we don't need to worry about
* whether argv[1] starts with "-". */
g_debug ("Setting arguments for wrapped command");
flatpak_bwrap_append_argsv (argv_in_container, &argv[1], argc - 1);
}
if (flatpak_subsandbox != NULL)
{
for (i = 0; i < argv_in_container->fds->len; i++)
{
g_autofree char *fd_str = g_strdup_printf ("--forward-fd=%d",
g_array_index (argv_in_container->fds, int, i));
flatpak_bwrap_add_arg (flatpak_subsandbox, fd_str);
}
flatpak_bwrap_add_arg (flatpak_subsandbox, "--");
g_warn_if_fail (g_strv_length (flatpak_subsandbox->envp) == 0);
flatpak_bwrap_append_bwrap (final_argv, flatpak_subsandbox);
}
if (flatpak_run_on_host != NULL)
{
g_assert (bwrap != NULL);
for (i = 0; i < bwrap->fds->len; i++)
{
g_autofree char *fd_str = g_strdup_printf ("--forward-fd=%d",
g_array_index (bwrap->fds, int, i));
flatpak_bwrap_add_arg (flatpak_run_on_host, fd_str);
}
for (i = 0; i < argv_in_container->fds->len; i++)
{
g_autofree char *fd_str = g_strdup_printf ("--forward-fd=%d",
g_array_index (argv_in_container->fds, int, i));
flatpak_bwrap_add_arg (flatpak_run_on_host, fd_str);
}
/* Change the current working directory where pv-launch will run bwrap.
* Bwrap will then set its directory by itself. For this reason here
* we just need a directory that it's known to exist. */
flatpak_bwrap_add_arg (flatpak_run_on_host, "--directory=/");
flatpak_bwrap_add_arg (flatpak_run_on_host, "--");
g_warn_if_fail (g_strv_length (flatpak_run_on_host->envp) == 0);
flatpak_bwrap_append_bwrap (final_argv, flatpak_run_on_host);
}
if (flatpak_subsandbox == NULL)
{
g_warn_if_fail (g_strv_length (bwrap->envp) == 0);
flatpak_bwrap_append_bwrap (final_argv, bwrap);
}
g_warn_if_fail (g_strv_length (argv_in_container->envp) == 0);
flatpak_bwrap_append_bwrap (final_argv, argv_in_container);
/* We'll have permuted the order anyway, so we might as well sort it,
* to make debugging a bit easier. */
if (final_argv->envp != NULL)
qsort (final_argv->envp, g_strv_length (final_argv->envp),
sizeof (char *), pv_envp_cmp);
if (opt_verbose)
{
g_debug ("Final command to execute:");
for (i = 0; i < final_argv->argv->len; i++)
{
g_autofree gchar *quoted = NULL;
quoted = g_shell_quote (g_ptr_array_index (final_argv->argv, i));
g_debug ("\t%s", quoted);
}
g_debug ("Final environment:");
for (i = 0; final_argv->envp != NULL && final_argv->envp[i] != NULL; i++)
{
g_autofree gchar *quoted = NULL;
quoted = g_shell_quote (final_argv->envp[i]);
g_debug ("\t%s", quoted);
}
}
/* Clean up temporary directory before running our long-running process */
if (runtime != NULL)
pv_runtime_cleanup (runtime);
flatpak_bwrap_finish (final_argv);
if (opt_write_final_argv != NULL)
{
FILE *file = fopen (opt_write_final_argv, "w");
if (file == NULL)
{
g_warning ("An error occurred trying to write out the arguments: %s",
g_strerror (errno));
/* This is not a fatal error, try to continue */
}
else
{
for (i = 0; i < final_argv->argv->len; i++)
fprintf (file, "%s%c",
(gchar *) g_ptr_array_index (final_argv->argv, i), '\0');
fclose (file);
}
}
if (!is_flatpak_env)
{
if (!maybe_load_nvidia_modules (error))
{
g_debug ("Cannot load nvidia modules: %s", local_error->message);
g_clear_error (&local_error);
}
}
if (opt_only_prepare)
ret = 0;
else
pv_bwrap_execve (final_argv, fileno (original_stdout), error);
out:
if (local_error != NULL)
pv_log_failure ("%s", local_error->message);
for (i = 0; i < G_N_ELEMENTS (opt_preload_modules); i++)
g_clear_pointer (&opt_preload_modules[i].values, g_ptr_array_unref);
g_clear_pointer (&opt_env_if_host, g_strfreev);
g_clear_pointer (&opt_freedesktop_app_id, g_free);
g_clear_pointer (&opt_steam_app_id, g_free);
g_clear_pointer (&opt_home, g_free);
g_clear_pointer (&opt_fake_home, g_free);
g_clear_pointer (&opt_runtime, g_free);
g_clear_pointer (&opt_runtime_archive, g_free);
g_clear_pointer (&opt_runtime_base, g_free);
g_clear_pointer (&opt_runtime_id, g_free);
g_clear_pointer (&opt_pass_fds, g_array_unref);
g_clear_pointer (&opt_variable_dir, g_free);
g_debug ("Exiting with status %d", ret);
return ret;
}