Newer
Older
{ "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_NONE, G_OPTION_ARG_CALLBACK, &opt_host_ld_preload_cb,
"Add MODULE from the host system to LD_PRELOAD when executing COMMAND.",
"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 '/run/host' 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 },
{ "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-base", '\0',
G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &opt_runtime_base,
"If a --runtime is a relative path, look for it relative to BASE. "
"[Default: $PRESSURE_VESSEL_RUNTIME_BASE or '.']",
"BASE" },
{ "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 "
"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" },
{ "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-bwrap-arguments", '\0',
G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_FILENAME, &opt_write_bwrap,
"Write the final bwrap arguments, 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;
}
static void
cli_log_func (const gchar *log_domain,
GLogLevelFlags log_level,
const gchar *message,
gpointer user_data)
{
g_printerr ("%s[%d]: %s\n", (const char *) user_data, my_pid, message);
}
int
main (int argc,
char *argv[])
{
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;

Ludovico de Nittis
committed
gboolean is_flatpak_env = g_file_test ("/.flatpak-info", G_FILE_TEST_IS_REGULAR);
g_autoptr(FlatpakBwrap) bwrap = NULL;
g_autoptr(FlatpakBwrap) exports_bwrap = NULL;
g_autoptr(FlatpakExports) exports = NULL;
g_autoptr(FlatpakBwrap) adverb_args = NULL;
g_autofree gchar *adverb_in_container = NULL;
g_autoptr(FlatpakBwrap) wrapped_command = NULL;
g_autofree gchar *launch_executable = NULL;
g_autofree gchar *bwrap_executable = NULL;
g_autoptr(GString) adjusted_ld_preload = g_string_new ("");
g_autofree gchar *cwd_p = NULL;
g_autofree gchar *cwd_l = NULL;
g_autofree gchar *cwd_p_host = NULL;
const gchar *home;
g_autofree gchar *tools_dir = NULL;
g_autoptr(PvRuntime) runtime = NULL;
g_autoptr(FILE) original_stdout = NULL;
GHashTableIter iter;
const gchar *env_var;
g_autoptr(GString) lock_env = g_string_new ("");
g_auto(GLnxTmpfile) lock_env_tmpf = { 0, };
gpointer k;
g_autoptr(GHashTable) extra_locked_vars_to_unset = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free, NULL);
g_autoptr(GHashTable) extra_locked_vars_to_inherit = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free, NULL);
g_autofree char *lock_env_fd = NULL;
g_autoptr(GArray) pass_fds_through_adverb = g_array_new (FALSE, FALSE, sizeof (int));
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
g_set_prgname ("pressure-vessel-wrap");
g_log_set_handler (G_LOG_DOMAIN,
G_LOG_LEVEL_WARNING | G_LOG_LEVEL_MESSAGE,
cli_log_func, (void *) g_get_prgname ());
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)
{
g_printerr ("%s: This program should not be run in the Steam Runtime.",
g_get_prgname ());
g_printerr ("%s: Use pressure-vessel-unruntime instead.",
g_get_prgname ());
ret = 2;
goto out;
}
original_environ = g_get_environ ();
/* Set defaults */
opt_batch = pv_boolean_environment ("PRESSURE_VESSEL_BATCH", FALSE);
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_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_runtime == NULL)
opt_runtime = g_strdup (g_getenv ("PRESSURE_VESSEL_RUNTIME"));
if (opt_runtime_base == NULL)
opt_runtime_base = g_strdup (g_getenv ("PRESSURE_VESSEL_RUNTIME_BASE"));
if (opt_runtime != NULL
&& opt_runtime[0] != '\0'
&& !g_path_is_absolute (opt_runtime)
&& opt_runtime_base != NULL
&& opt_runtime_base[0] != '\0')
{
g_autofree gchar *tmp = g_steal_pointer (&opt_runtime);
opt_runtime = g_build_filename (opt_runtime_base, tmp, NULL);
}
if (opt_copy_runtime_into == NULL)
opt_copy_runtime_into = g_strdup (g_getenv ("PRESSURE_VESSEL_COPY_RUNTIME_INTO"));
if (opt_copy_runtime_into != NULL
&& opt_copy_runtime_into[0] == '\0')
opt_copy_runtime_into = NULL;
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
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' */
if (!pv_boolean_environment ("PRESSURE_VESSEL_HOST_GRAPHICS", TRUE))
opt_graphics_provider = g_strdup ("");
else if (g_file_test ("/run/host", 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] != '/')
{
g_printerr ("%s: --graphics-provider path must be absolute, not \"%s\"\n",
g_get_prgname (), opt_graphics_provider);
goto out;
}
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 (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)
{
g_printerr ("%s: An executable to run is required\n",
g_get_prgname ());
goto out;
}
if (opt_terminal == PV_TERMINAL_AUTO)
if (opt_shell != PV_SHELL_NONE)
opt_terminal = PV_TERMINAL_XTERM;
if (opt_terminal == PV_TERMINAL_NONE && opt_shell != PV_SHELL_NONE)
g_printerr ("%s: --terminal=none is incompatible with --shell\n",
g_get_prgname ());
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);
}
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
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
{
g_printerr ("%s: Either --home, --freedesktop-app-id, --steam-app-id "
"or $SteamAppId is required\n",
g_get_prgname ());
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)
{
g_printerr ("%s: --env-if-host argument must be of the form "
"NAME=VALUE, not \"%s\"\n",
g_get_prgname (), opt_env_if_host[i]);
goto out;
}
if (opt_only_prepare && opt_test)
{
g_printerr ("%s: --only-prepare and --test are mutually exclusive",
g_get_prgname ());
goto out;
}
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
if (opt_filesystems != NULL)
{
for (i = 0; opt_filesystems[i] != NULL; i++)
{
if (strchr (opt_filesystems[i], ':') != NULL ||
strchr (opt_filesystems[i], '\\') != NULL)
{
g_printerr ("%s: ':' and '\\' in --filesystem argument "
"not handled yet\n",
g_get_prgname ());
goto out;
}
else if (!g_path_is_absolute (opt_filesystems[i]))
{
g_printerr ("%s: --filesystem argument must be an absolute "
"path, not \"%s\"\n",
g_get_prgname (), opt_filesystems[i]);
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_log_set_handler (G_LOG_DOMAIN,
G_LOG_LEVEL_DEBUG | G_LOG_LEVEL_INFO,
cli_log_func, (void *) g_get_prgname ());
g_message ("Original argv:");
for (i = 0; i < original_argc; i++)
{
g_autofree gchar *quoted = g_shell_quote (original_argv[i]);
g_message ("\t%" G_GSIZE_FORMAT ": %s", i, quoted);
}
g_message ("Current working directory:");
g_message ("\tPhysical: %s", cwd_p);
g_message ("\tLogical: %s", cwd_l);
g_message ("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_message ("\t%s", quoted);
}
g_message ("Wrapped command:");
for (i = 1; i < argc; i++)
{
g_autofree gchar *quoted = g_shell_quote (argv[i]);
g_message ("\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);
wrapped_command = flatpak_bwrap_new (flatpak_bwrap_empty_env);
if (argc > 1 && argv[1][0] == '-' && !opt_launcher)
{
/* Make sure wrapped_command is something we can validly pass to env(1) */
if (strchr (argv[1], '=') != NULL)
flatpak_bwrap_add_args (wrapped_command,
"sh", "-euc", "exec \"$@\"", "sh",
NULL);
/* Make sure bwrap will interpret wrapped_command as the end of its
* options */
flatpak_bwrap_add_arg (wrapped_command, "env");
}
g_debug ("Setting arguments for wrapped command");
flatpak_bwrap_append_argsv (wrapped_command, &argv[1], argc - 1);

Ludovico de Nittis
committed
/* 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);

Ludovico de Nittis
committed
/* Assume "bwrap" to exist in the host system and to be in its PATH */
bwrap_executable = g_strdup ("bwrap");
/* If we can't launch a command on the host, just fail. */
if (!check_launch_on_host (launch_executable, error))
goto out;

Ludovico de Nittis
committed
}
else
{
g_debug ("Checking for bwrap...");
/* if this fails, it will warn */

Ludovico de Nittis
committed
bwrap_executable = check_bwrap (tools_dir, opt_only_prepare);
if (bwrap_executable == NULL)
goto out;
g_debug ("OK (%s)", bwrap_executable);
ret = 0;
goto out;
/* Start with an empty environment and populate it later */
bwrap = flatpak_bwrap_new (flatpak_bwrap_empty_env);
flatpak_bwrap_add_arg (bwrap, bwrap_executable);
exports = flatpak_exports_new ();
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);
/* The FlatpakExports will populate /run/host for us */
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,
"--dir",
"/run/pressure-vessel",
NULL);
if (opt_runtime != NULL && opt_runtime[0] != '\0')
PvRuntimeFlags flags = PV_RUNTIME_FLAGS_NONE;
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;
g_debug ("Configuring runtime %s...", opt_runtime);
runtime = pv_runtime_new (opt_runtime,
opt_copy_runtime_into,
bwrap_executable,
tools_dir,
opt_graphics_provider,
graphics_provider_mount_point,
goto out;
if (!pv_runtime_bind (runtime,
exports,
bwrap,
extra_locked_vars_to_unset,
extra_locked_vars_to_inherit,
error))
else if (is_flatpak_env)
{
glnx_throw (error,
"Cannot operate without a runtime from inside a "
"Flatpak app");
goto out;
}
static const char * const export_os_mutable[] = { "/etc", "/tmp", "/var" };
g_assert (!is_flatpak_env);
if (!pv_bwrap_bind_usr (bwrap, "/", "/", "/", error))
goto out;
for (i = 0; i < G_N_ELEMENTS (export_os_mutable); i++)
{
const char *dir = export_os_mutable[i];
if (g_file_test (dir, G_FILE_TEST_EXISTS))
flatpak_bwrap_add_args (bwrap, "--bind", dir, dir, NULL);
}
/* We do each subdirectory of /run separately, so that we can
* always create /run/host and /run/pressure-vessel. */
if (!export_contents_of_run (bwrap, error))
goto out;
/* This handles everything except:
*
* /app (should be unnecessary)
* /boot (should be unnecessary)
* /dev (handled by pv_bwrap_add_api_filesystems() above)
* /etc (handled by export_os_mutable)
* /proc (handled by pv_bwrap_add_api_filesystems() above)
* /root (should be unnecessary)
* /run (handled by export_contents_of_run())
* /sys (handled by pv_bwrap_add_api_filesystems() above)
* /tmp (handled by export_os_mutable)
* /usr, /lib, /lib32, /lib64, /bin, /sbin
* (all handled by pv_bwrap_bind_usr() above)
* /var (handled by export_os_mutable)
*/
if (!export_root_dirs_like_filesystem_host (exports,
FLATPAK_FILESYSTEM_MODE_READ_WRITE,
error))
}
/* Protect other users' homes (but guard against the unlikely
* situation that they don't exist) */
if (g_file_test ("/home", G_FILE_TEST_EXISTS))
flatpak_bwrap_add_args (bwrap,
"--tmpfs", "/home",
NULL);
g_debug ("Making home directory available...");
if (opt_fake_home == NULL)
{
flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_WRITE,
home);
/* 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;
if (!use_fake_home (exports, bwrap, opt_fake_home, error))
goto out;
}
if (!opt_share_pid)
{
g_warning ("Unsharing process ID namespace. This is not expected "
"to work...");
flatpak_bwrap_add_arg (bwrap, "--unshare-pid");
}
/* 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");
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
g_debug ("Adjusting LD_PRELOAD...");
/* 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. */
if (opt_ld_preload != NULL)
{
for (i = 0; i < opt_ld_preload->len; i++)
{
const char *preload = g_ptr_array_index (opt_ld_preload, i);
g_assert (preload != NULL);
if (*preload == '\0')
continue;
/* We have the beginnings of infrastructure to set a LD_PRELOAD
* from inside the container, but currently the only thing we
* support is it coming from the host. */
g_assert (g_str_has_prefix (preload, "host:"));
preload = preload + 5;
if (strstr (preload, "gtk3-nocsd") != NULL)
{
g_warning ("Disabling gtk3-nocsd LD_PRELOAD: it is known to cause crashes.");
continue;
}
if (g_file_test (preload, G_FILE_TEST_EXISTS))
{
if (opt_remove_game_overlay
&& g_str_has_suffix (preload, "/gameoverlayrenderer.so"))
{
g_debug ("Disabling Steam Overlay: %s", preload);
continue;
}
&& (g_str_has_prefix (preload, "/usr/")
|| g_str_has_prefix (preload, "/lib")))
{
g_autofree gchar *in_run_host = g_build_filename ("/run/host",
preload,
NULL);
/* 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_ld_preload, in_run_host);
flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_ONLY,
preload);
pv_search_path_append (adjusted_ld_preload, preload);
}
}
else
{
g_debug ("LD_PRELOAD module '%s' does not exist", preload);
}
}
}
if (adjusted_ld_preload->len != 0)
flatpak_bwrap_set_env (bwrap, "LD_PRELOAD", adjusted_ld_preload->str, TRUE);

Ludovico de Nittis
committed
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, bwrap,
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_debug ("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_debug ("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);
g_hash_table_add (extra_locked_vars_to_unset, g_strdup ("PWD"));
/* Put Steam Runtime environment variables back, if /usr is mounted
* from the host. */
{
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 (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';
flatpak_bwrap_set_env (bwrap, opt_env_if_host[i], equals + 1, TRUE);
*equals = '=';
}
}
}
/* Convert the exported directories into extra bubblewrap arguments */
exports_bwrap = flatpak_bwrap_new (flatpak_bwrap_empty_env);
flatpak_exports_append_bwrap_args (exports, exports_bwrap);
adjust_exports (exports_bwrap, home);
flatpak_bwrap_append_bwrap (bwrap, exports_bwrap);
flatpak_run_add_font_path_args (bwrap);
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
/* We need to set up IPC rendezvous points relatively late, so that
* even if we are sharing /tmp via --filesystem=/tmp, we'll still
* mount our own /tmp/.X11-unix over the top of the OS's. */
if (runtime != NULL)
{
flatpak_run_add_wayland_args (bwrap);
/* When in a Flatpak container the "DISPLAY" env is equal to ":99.0",
* but it might be different on the host system. As a workaround we simply
* bind the whole "/tmp/.X11-unix" directory and later unset the container
* "DISPLAY" env.
*/
if (is_flatpak_env)
{
flatpak_bwrap_add_args (bwrap,
"--ro-bind", "/tmp/.X11-unix", "/tmp/.X11-unix",
NULL);
}
else
{
flatpak_run_add_x11_args (bwrap, extra_locked_vars_to_unset, TRUE);