diff --git a/man/launcher.1.md b/man/launcher.1.md index d2afb561a2d7107a4bc04d12a39868f2857de1df..4f0c512767d57398724ebbaf125c7b4d920a8235 100644 --- a/man/launcher.1.md +++ b/man/launcher.1.md @@ -12,6 +12,7 @@ pressure-vessel-launcher - server to launch processes in a container **pressure-vessel-launcher** [**--replace**] [**--verbose**] +[**--info-fd**] *N* {**--bus-name** *NAME*|**--socket** *SOCKET*|**--socket-directory** *PATH*} # DESCRIPTION @@ -52,6 +53,11 @@ D-Bus session bus, and executes arbitrary commands as subprocesses. readable, meaning either data is available or end-of-file has been reached. +**--info-fd** *FD* +: Print details of the server on *FD* (see **OUTPUT** below). + This fd will be closed (reach end-of-file) when the server is ready. + The default is standard output, equivalent to **--info-fd=1**. + **--replace** : When used with **--bus-name**, allow other **pressure-vessel-launcher** processes to take over the bus name, @@ -64,7 +70,9 @@ D-Bus session bus, and executes arbitrary commands as subprocesses. # OUTPUT **pressure-vessel-launcher** prints zero or more lines of -structured text on standard output, and then closes standard output: +structured text on the file descriptor specified by **--info-fd**, +and then closes both the **--info-fd** and standard output. The default +**--info-fd** is standard output. **socket=**PATH : The launcher is listening on *PATH*, and can be contacted by @@ -86,11 +94,12 @@ structured text on standard output, and then closes standard output: (such as **dbus-send --session**) to the session bus and sending method calls to _NAME_. -Clients must wait until after standard output has been closed, or wait -for the bus name to appear, before connecting by bus name. +Clients must wait until after either the **--info-fd** or standard output +has been closed, or wait for the bus name to appear, before connecting +by bus name. -Clients must wait until after standard output has been closed before -connecting by socket name. +Clients must wait until after either the **--info-fd** or standard output +has been closed before connecting by socket name. Unstructured diagnostic messages are printed on standard error, which remains open throughout. @@ -211,13 +220,15 @@ instead of fifos. mkfifo "${tmpdir}/ready-fifo" pressure-vessel-wrap \ --filesystem="${tmpdir}" \ + --pass-fd=3 ... \ -- \ pressure-vessel-launcher \ --exit-on-readable=0 \ + --info-fd=3 \ --socket="${tmpdir}/launcher" \ < "${tmpdir}/exit-fifo" \ - > "${tmpdir}/ready-fifo" \ + 3> "${tmpdir}/ready-fifo" \ & launcher_pid="$!" sleep infinity > "${tmpdir}/exit-fifo" & diff --git a/man/wrap.1.md b/man/wrap.1.md index 29ffe762402f8f1f3ab0575a3627fe30f4fbde1d..eee4e4e8228340aa1a6197bbf33d9c795d130e5c 100644 --- a/man/wrap.1.md +++ b/man/wrap.1.md @@ -78,6 +78,12 @@ pressure-vessel-wrap - run programs in a bubblewrap container With `--copy-runtime-into`, the prepared runtime will appear in a subdirectory of *DIR*. +`--pass-fd` *FD* +: Pass the file descriptor *FD* (specified as a small positive integer) + from the parent process to the *COMMAND*. The default is to only pass + through file descriptors 0, 1 and 2 + (**stdin**, **stdout** and **stderr**). + `--runtime=` : Use the current execution environment's /usr to provide /usr in the container. @@ -253,13 +259,13 @@ The following environment variables (among others) are read by `STEAM_COMPAT_APP_ID` (integer) : Equivalent to `--steam-app-id="$STEAM_COMPAT_APP_ID"`. - (Not used yet, but should be.) `STEAM_COMPAT_APP_LIBRARY_PATH` (path) -: (Not used yet, but should be.) +: Deprecated equivalent of `STEAM_COMPAT_MOUNTS`, except that it is + a single path instead of being colon-delimited. -`STEAM_COMPAT_APP_LIBRARY_PATHS` (path) -: (Not used yet, but should be?) +`STEAM_COMPAT_APP_LIBRARY_PATHS` (`:`-separated list of paths) +: Deprecated equivalent of `STEAM_COMPAT_MOUNTS`. `STEAM_COMPAT_CLIENT_INSTALL_PATH` (path) : When used as a Steam compatibility tool, the absolute path to the @@ -271,11 +277,40 @@ The following environment variables (among others) are read by variable data directory used by Proton, if any. This is made available read/write in the container. +`STEAM_COMPAT_INSTALL_PATH` (path) +: Top-level directory containing the game itself, even if the current + working directory is actually a subdirectory of this. + This is made available read/write in the container. + +`STEAM_COMPAT_LIBRARY_PATHS` (`:`-separated list of paths) +: Colon-delimited list of paths to Steam Library directories containing + the game, the compatibility tools if any, and any other resources + that the game will need, such as DirectX installers. + Each is currently made available read/write in the container. + +`STEAM_COMPAT_MOUNT_PATHS` (`:`-separated list of paths) +: Deprecated equivalent of `STEAM_COMPAT_MOUNTS`. + +`STEAM_COMPAT_MOUNTS` (`:`-separated list of paths) +: Colon-delimited list of paths to additional directories that are to + be made available read/write in the container. + `STEAM_COMPAT_SESSION_ID` (integer) : (Not used yet, but should be.) +`STEAM_COMPAT_SHADER_PATH` (path) +: When used as a Steam compatibility tool, the absolute path to the + variable data directory used for cached shaders, if any. + This is made available read/write in the container. + +`STEAM_COMPAT_TOOL_PATH` (path) +: Deprecated equivalent of `STEAM_COMPAT_TOOL_PATHS`, except that it is + a single path instead of being colon-delimited. + `STEAM_COMPAT_TOOL_PATHS` (`:`-separated list of paths) -: (Not used yet, but should be.) +: Colon-delimited list of paths to Steam compatibility tools in use, + such as Proton and the Steam Linux Runtime. + They are currently made available read/write in the container. `STEAM_RUNTIME` (path) : **pressure-vessel-wrap** refuses to run if this environment variable diff --git a/src/adverb.c b/src/adverb.c index 46239b366e60a2e4391c8888a0f1aed59678bb30..cec616196a9a325c49c6209cef05ef78f2872219 100644 --- a/src/adverb.c +++ b/src/adverb.c @@ -868,6 +868,22 @@ main (int argc, goto out; } + /* If the parent or child writes to a passed fd and closes it, + * don't stand in the way of that. Skip fds 0 (stdin), + * 1 (stdout) and 2 (stderr); we have moved our original stdout + * to another fd which will be dealt with below, and we want to keep + * our stdin and stderr open. */ + if (child_setup_data.pass_fds != NULL) + { + gsize i; + + for (i = 0; child_setup_data.pass_fds[i] > -1; i++) + { + if (child_setup_data.pass_fds[i] > 2) + close (child_setup_data.pass_fds[i]); + } + } + g_free (child_setup_data.pass_fds); /* If the child writes to stdout and closes it, don't interfere */ diff --git a/src/launcher.c b/src/launcher.c index e26df69e5db7426c06e5768b1da511881e644cac..6e76b72d892fb95b95f7eb8b6296a762f987da6b 100644 --- a/src/launcher.c +++ b/src/launcher.c @@ -57,12 +57,27 @@ typedef GDBusServer AutoDBusServer; G_DEFINE_AUTOPTR_CLEANUP_FUNC(AutoDBusServer, g_object_unref) static FILE *original_stdout = NULL; +static FILE *info_fh = NULL; static GDBusConnection *session_bus = NULL; static GHashTable *client_pid_data_hash = NULL; static guint name_owner_id = 0; static GMainLoop *main_loop; static PvLauncher1 *launcher; +/* + * Close the --info-fd, and also close standard output (if different). + */ +static void +close_info_fh (void) +{ + if (info_fh == original_stdout) + original_stdout = NULL; + else + g_clear_pointer (&original_stdout, fclose); + + g_clear_pointer (&info_fh, fclose); +} + static void skeleton_died_cb (gpointer data) { @@ -558,10 +573,10 @@ on_name_acquired (GDBusConnection *connection, if (*ret == EX_UNAVAILABLE) { *ret = 0; - g_assert (original_stdout != NULL); - fprintf (original_stdout, "bus_name=%s\n", name); - fflush (original_stdout); - g_clear_pointer (&original_stdout, fclose); + g_assert (info_fh != NULL); + fprintf (info_fh, "bus_name=%s\n", name); + fflush (info_fh); + close_info_fh (); } } @@ -963,6 +978,7 @@ set_up_exit_on_readable (int fd, static gchar *opt_bus_name = NULL; static gint opt_exit_on_readable_fd = -1; +static gint opt_info_fd = -1; static gboolean opt_replace = FALSE; static gchar *opt_socket = NULL; static gchar *opt_socket_directory = NULL; @@ -980,6 +996,11 @@ static GOptionEntry options[] = "Exit when data is available for reading or when end-of-file is " "reached on this fd, usually 0 for stdin.", "FD" }, + { "info-fd", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_INT, &opt_info_fd, + "Indicate readiness and print details of how to connect on this " + "file descriptor instead of stdout.", + "FD" }, { "replace", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_replace, "Replace a previous instance with the same bus name. " @@ -1076,6 +1097,24 @@ main (int argc, goto out; } + if (opt_info_fd > 0) /* < 0 means unset, and 0 is stdout itself */ + { + info_fh = fdopen (opt_info_fd, "w"); + + if (info_fh == NULL) + { + glnx_null_throw_errno_prefix (error, + "Unable to create a stdio wrapper for fd %d", + opt_info_fd); + ret = EX_OSERR; + goto out; + } + } + else + { + info_fh = original_stdout; + } + if (opt_exit_on_readable_fd >= 0) { if (!set_up_exit_on_readable (opt_exit_on_readable_fd, @@ -1261,16 +1300,16 @@ main (int argc, } if (opt_socket != NULL) - fprintf (original_stdout, "socket=%s\n", opt_socket); + fprintf (info_fh, "socket=%s\n", opt_socket); if (server != NULL) - fprintf (original_stdout, "dbus_address=%s\n", + fprintf (info_fh, "dbus_address=%s\n", g_dbus_server_get_client_address (server)); if (opt_bus_name == NULL) { - fflush (original_stdout); - g_clear_pointer (&original_stdout, fclose); + fflush (info_fh); + close_info_fh (); } g_debug ("Entering main loop"); @@ -1307,7 +1346,7 @@ out: ret = EX_UNAVAILABLE; g_clear_error (&local_error); - g_clear_pointer (&original_stdout, fclose); + close_info_fh (); g_debug ("Exiting with status %d", ret); return ret; diff --git a/src/wrap.c b/src/wrap.c index 1133bb968be53bb77e310320181cdb5abaab8783..e20d930dd3322a30f0f257466582a3b58590f19e 100644 --- a/src/wrap.c +++ b/src/wrap.c @@ -325,15 +325,49 @@ export_contents_of_run (FlatpakBwrap *bwrap, return TRUE; } +typedef enum +{ + ENV_MOUNT_FLAGS_COLON_DELIMITED = (1 << 0), + ENV_MOUNT_FLAGS_DEPRECATED = (1 << 1), + ENV_MOUNT_FLAGS_NONE = 0 +} EnvMountFlags; + +typedef struct +{ + const char *name; + EnvMountFlags flags; +} EnvMount; + +static const EnvMount known_required_env[] = +{ + { "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 }, +}; + static void bind_and_propagate_from_environ (FlatpakExports *exports, FlatpakBwrap *bwrap, FlatpakFilesystemMode mode, - const char *variable) + const char *variable, + EnvMountFlags flags) { - g_autofree gchar *value_host = NULL; - g_autofree gchar *canon = NULL; + g_auto(GStrv) values = NULL; const char *value; + const char *before; + const char *after; + gboolean changed = FALSE; + gsize i; g_return_if_fail (exports != NULL); g_return_if_fail ((unsigned) mode <= FLATPAK_FILESYSTEM_MODE_LAST); @@ -344,24 +378,63 @@ bind_and_propagate_from_environ (FlatpakExports *exports, if (value == NULL) return; - if (!g_file_test (value, G_FILE_TEST_EXISTS)) + if (flags & ENV_MOUNT_FLAGS_DEPRECATED) + g_message ("Setting $%s is deprecated", variable); + + if (flags & ENV_MOUNT_FLAGS_COLON_DELIMITED) + { + values = g_strsplit (value, ":", -1); + before = "...:"; + after = ":..."; + } + else { - g_debug ("Not bind-mounting %s=\"%s\" because it does not exist", - variable, value); - return; + values = g_new0 (gchar *, 2); + values[0] = g_strdup (value); + values[1] = NULL; + before = ""; + after = ""; } - canon = g_canonicalize_filename (value, NULL); - value_host = pv_current_namespace_path_to_host_path (canon); + for (i = 0; values[i] != NULL; i++) + { + g_autofree gchar *value_host = NULL; + g_autofree gchar *canon = NULL; - g_debug ("Bind-mounting %s=\"%s\" from the current env as %s=\"%s\" in the host", - variable, value, variable, value_host); - flatpak_exports_add_path_expose (exports, mode, canon); + if (values[i][0] == '\0') + continue; - if (strcmp (value, value_host) != 0) - flatpak_bwrap_add_args (bwrap, - "--setenv", variable, value_host, - NULL); + if (!g_file_test (values[i], G_FILE_TEST_EXISTS)) + { + g_debug ("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_debug ("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_autofree gchar *joined = g_strjoinv (":", values); + + flatpak_bwrap_add_args (bwrap, + "--setenv", variable, joined, + NULL); + } } /* Order matters here: root, steam and steambeta are or might be symlinks @@ -584,6 +657,7 @@ static gboolean opt_only_prepare = FALSE; static gboolean opt_remove_game_overlay = FALSE; static PvShell opt_shell = PV_SHELL_NONE; static GPtrArray *opt_ld_preload = NULL; +static GArray *opt_pass_fds = NULL; static char *opt_runtime_base = NULL; static char *opt_runtime = NULL; static Tristate opt_share_home = TRISTATE_MAYBE; @@ -613,6 +687,41 @@ opt_host_ld_preload_cb (const gchar *option_name, 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, @@ -814,7 +923,8 @@ static GOptionEntry options[] = { "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: $SteamAppId]", "N" }, + "as home directory. [Default: $STEAM_COMPAT_APP_ID or $SteamAppId]", + "N" }, { "gc-runtimes", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_gc_runtimes, "If using --copy-runtime-into, garbage-collect old temporary " @@ -851,6 +961,10 @@ static GOptionEntry options[] = "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" }, + { "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. " @@ -881,7 +995,7 @@ static GOptionEntry options[] = { "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 $SteamAppId. " + "--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 }, @@ -1031,12 +1145,6 @@ main (int argc, const gchar *bwrap_help_argv[] = { "<bwrap>", "--help", NULL }; g_autoptr(PvRuntime) runtime = NULL; g_autoptr(FILE) original_stdout = NULL; - const char * const known_required_env[] = - { - "STEAM_COMPAT_CLIENT_INSTALL_PATH", - "STEAM_COMPAT_DATA_PATH", - "STEAM_COMPAT_TOOL_PATH", - }; my_pid = getpid (); @@ -1240,6 +1348,13 @@ main (int argc, 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", @@ -1625,6 +1740,12 @@ main (int argc, 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"); + g_debug ("Adjusting LD_PRELOAD..."); /* We need the LD_PRELOADs from Steam visible at the paths that were @@ -1702,7 +1823,8 @@ main (int argc, for (i = 0; i < G_N_ELEMENTS (known_required_env); i++) bind_and_propagate_from_environ (exports, bwrap, FLATPAK_FILESYSTEM_MODE_READ_WRITE, - known_required_env[i]); + known_required_env[i].name, + known_required_env[i].flags); /* Make arbitrary filesystems available. This is not as complete as * Flatpak yet. */ @@ -1831,8 +1953,11 @@ main (int argc, } } - if (!flatpak_bwrap_bundle_args (bwrap, 1, -1, FALSE, error)) - goto out; + if (!opt_only_prepare) + { + if (!flatpak_bwrap_bundle_args (bwrap, 1, -1, FALSE, error)) + goto out; + } adverb_args = flatpak_bwrap_new (flatpak_bwrap_empty_env); @@ -1864,6 +1989,17 @@ main (int argc, "--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 (bwrap, fd); + flatpak_bwrap_add_arg_printf (bwrap, "--pass-fd=%d", fd); + } + } + switch (opt_shell) { case PV_SHELL_AFTER: @@ -2005,6 +2141,7 @@ out: g_clear_pointer (&opt_fake_home, g_free); g_clear_pointer (&opt_runtime_base, g_free); g_clear_pointer (&opt_runtime, g_free); + g_clear_pointer (&opt_pass_fds, g_array_unref); g_debug ("Exiting with status %d", ret); return ret; diff --git a/tests/containers.py b/tests/containers.py index 53324152500333f3bfb091b277af4a24efee256c..e011a1d5896431784be3895c1d0b4390ceb0680a 100755 --- a/tests/containers.py +++ b/tests/containers.py @@ -144,6 +144,35 @@ class TestContainers(BaseTest): cls.pv_dir = os.path.join(cls.tmpdir.name, 'pressure-vessel') os.makedirs(cls.pv_dir, exist_ok=True) + for var, plural in ( + ('STEAM_COMPAT_APP_LIBRARY_PATH', False), + ('STEAM_COMPAT_APP_LIBRARY_PATHS', True), + ('STEAM_COMPAT_CLIENT_INSTALL_PATH', False), + ('STEAM_COMPAT_DATA_PATH', False), + ('STEAM_COMPAT_INSTALL_PATH', False), + ('STEAM_COMPAT_LIBRARY_PATHS', True), + ('STEAM_COMPAT_MOUNT_PATHS', True), + ('STEAM_COMPAT_MOUNTS', True), + ('STEAM_COMPAT_SHADER_PATH', False), + ('STEAM_COMPAT_TOOL_PATH', False), + ('STEAM_COMPAT_TOOL_PATHS', True), + ): + paths = [] + + if plural: + names = [var + '_n1', var + '_n2'] + else: + # Use an inconvenient name with colon and space + # to check that we handle those correctly + names = [var + ': .d'] + + for name in names: + path = os.path.join(cls.tmpdir.name, name) + os.makedirs(path, exist_ok=True) + paths.append(path) + + os.environ[var] = ':'.join(paths) + if 'PRESSURE_VESSEL_UNINSTALLED' in os.environ: os.makedirs(os.path.join(cls.pv_dir, 'bin')) @@ -521,11 +550,35 @@ class TestContainers(BaseTest): ) self.assertEqual(completed.returncode, 0) - bwrap_arguments = bwrap_temp_file.read().decode().split("\0") + bwrap_arguments = bwrap_temp_file.read().decode() if locales: - self.assertIn("--generate-locales", bwrap_arguments) + self.assertIn("\0--generate-locales\0", bwrap_arguments) else: - self.assertNotIn("--generate-locales", bwrap_arguments) + self.assertNotIn("\0--generate-locales\0", bwrap_arguments) + + if only_prepare: + for var, plural in ( + ('STEAM_COMPAT_APP_LIBRARY_PATH', False), + ('STEAM_COMPAT_APP_LIBRARY_PATHS', True), + ('STEAM_COMPAT_CLIENT_INSTALL_PATH', False), + ('STEAM_COMPAT_INSTALL_PATH', False), + ('STEAM_COMPAT_LIBRARY_PATHS', True), + ('STEAM_COMPAT_MOUNT_PATHS', True), + ('STEAM_COMPAT_MOUNTS', True), + ('STEAM_COMPAT_SHADER_PATH', False), + ('STEAM_COMPAT_TOOL_PATH', False), + ('STEAM_COMPAT_TOOL_PATHS', True), + ): + if plural: + paths = os.environ[var].split(':') + else: + paths = [os.environ[var]] + + for path in paths: + self.assertIn( + "\0--bind\0{}\0{}".format(path, path), + bwrap_arguments, + ) bwrap_temp_file.close() diff --git a/tests/inside-runtime.py b/tests/inside-runtime.py index a283ade914e756c26a733aa208edb9c8dd857457..cb83c29d2f6078341fef762a529f584acb0ec09f 100755 --- a/tests/inside-runtime.py +++ b/tests/inside-runtime.py @@ -9,6 +9,7 @@ import errno import json import logging import os +import re import shlex import subprocess import sys @@ -627,6 +628,41 @@ class TestInsideRuntime(BaseTest): self.assertEqual(raised.exception.errno, errno.EROFS) + def test_imported(self) -> None: + mount_points = set() # type: typing.Set[str] + + with open('/proc/self/mountinfo') as mountinfo: + for line in mountinfo: + _, _, _, _, mount_point, *_ = re.split(r'[ \t]', line) + + mount_point = re.sub( + r'\\([0-7][0-7][0-7])', + lambda m: chr(int(m.group(1), 8)), + mount_point, + ) + mount_points.add(mount_point) + + for var, plural in ( + ('STEAM_COMPAT_APP_LIBRARY_PATH', False), + ('STEAM_COMPAT_APP_LIBRARY_PATHS', True), + ('STEAM_COMPAT_CLIENT_INSTALL_PATH', False), + ('STEAM_COMPAT_DATA_PATH', False), + ('STEAM_COMPAT_INSTALL_PATH', False), + ('STEAM_COMPAT_LIBRARY_PATHS', True), + ('STEAM_COMPAT_MOUNT_PATHS', True), + ('STEAM_COMPAT_MOUNTS', True), + ('STEAM_COMPAT_SHADER_PATH', False), + ('STEAM_COMPAT_TOOL_PATH', False), + ('STEAM_COMPAT_TOOL_PATHS', True), + ): + if plural: + paths = os.environ[var].split(':') + else: + paths = [os.environ[var]] + + for path in paths: + self.assertIn(path, mount_points) + if __name__ == '__main__': assert sys.version_info >= (3, 5), 'Python 3.5+ is required'