From e56008636b3edc41b9487cd9a6df39c2dc5784bd Mon Sep 17 00:00:00 2001 From: Simon McVittie <smcv@collabora.com> Date: Fri, 16 Aug 2019 13:36:57 +0100 Subject: [PATCH] wrap: Redo how running an interactive shell works Signed-off-by: Simon McVittie <smcv@collabora.com> --- README.md | 32 +++++--- pressure-vessel-test-ui | 21 ++++- src/wrap.c | 175 ++++++++++++++++++++++++++++++++-------- 3 files changed, 182 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 59fa4850f..59d180223 100644 --- a/README.md +++ b/README.md @@ -287,8 +287,8 @@ Instructions for testing itself, or to make it reload an edited `localconfig.vdf`? * Run the game. If successful, you will get a GUI launcher that lists possible - runtimes. Choose one, uncheck "Run in an xterm" for now, and click Run. - You should get a game. `pstree -pa` will look something like this: + runtimes. Choose one and click Run. You should get a game. + `pstree -pa` will look something like this: |-SteamChildMonit,13109 | `-sh,13110 -c... @@ -376,10 +376,10 @@ configured. /opt/pressure-vessel/bin/pressure-vessel-wrap -- ./whatever-game Optionally add more options before the `--`, such as `--runtime`, - `--xterm` and `--interactive`. + `--xterm` and `--shell-instead`. In particular, if the `xterm` can't be launched and you are debugging - why, use the `--interactive` option to get a shell in the container. + why, use the `--tty` option to get a shell in the container. If `bwrap` is failing to start up and so you can't get a shell in the container at all, construct a simpler-but-simpler `bwrap` command-line @@ -389,7 +389,7 @@ configured. system (if no runtime) contains an `xterm` binary, you can use something like: - /opt/pressure-vessel/bin/pressure-vessel-unruntime --xterm -- %command% + /opt/pressure-vessel/bin/pressure-vessel-unruntime --shell-instead -- %command% to run an xterm containing an interactive shell. The interactive shell's current working directory matches the game's, and you can @@ -398,19 +398,27 @@ configured. When ready, run `"$@"` in the interactive shell (with the double quotes included!) to run the game. - Checking "Run in an xterm" in the GUI launcher is the - same as `--xterm` on the command-line. + Or, use `--shell-after` or `--shell-fail` to try running the game first, + and then run a shell afterwards, either unconditionally or only if + the command fails. + + Similarly, you can use `--xterm` to make the game's output appear in + an xterm without having an interactive shell. + + When you use any of these options, Steam will treat the xterm as part + of the game, so you cannot launch the game again from the Steam UI + until you have exited from the xterm. * For interactive testing, if you ran Steam from a shell (not normally - valid on SteamOS!), you can use: + valid on SteamOS!), you can use something like: - /opt/pressure-vessel/bin/pressure-vessel-unruntime --interactive -- %command% + /opt/pressure-vessel/bin/pressure-vessel-unruntime --tty --shell-instead -- %command% This is a simpler version of `--xterm`, which uses the terminal from which you ran Steam instead of an xterm inside the container. The interactive shell's current working directory matches the game's. - As with `--xterm`, run `"$@"` in the interactive shell to run the game. + As with the xterm, run `"$@"` in the interactive shell to run the game. ### More options @@ -535,8 +543,8 @@ the same as for `flatpak run --filesystem=host` in Flatpak. ### Interactive debugging -`--interactive` and `--xterm` work by wrapping an increasingly long -"adverb" command around the command to be run. +`--tty`, `--xterm` and the `--shell` options work by wrapping an +increasingly long "adverb" command around the command to be run. ### Unsharing the home directory diff --git a/pressure-vessel-test-ui b/pressure-vessel-test-ui index fdf001f04..8c9a31b61 100755 --- a/pressure-vessel-test-ui +++ b/pressure-vessel-test-ui @@ -121,11 +121,25 @@ class Gui: row += 1 self.xterm_check = Gtk.CheckButton.new_with_label('Run in an xterm') - self.xterm_check.set_active(True) + self.xterm_check.set_active(False) self.grid.attach(self.xterm_check, 1, row, 1, 1) row += 1 + shell_label = Gtk.Label.new('Run an interactive shell') + self.grid.attach(shell_label, 0, row, 1, 1) + + self.shell_combo = Gtk.ComboBoxText.new() + self.shell_combo.append('', 'No') + self.shell_combo.append('--shell-after', 'After running the command') + self.shell_combo.append('--shell-fail', 'If the command fails') + self.shell_combo.append( + '--shell-instead', 'Instead of running the command') + self.shell_combo.set_active(0) + self.grid.attach(self.shell_combo, 1, row, 1, 1) + + row += 1 + buttons_grid = Gtk.Grid( column_spacing=6, column_homogeneous=True, @@ -261,6 +275,11 @@ class Gui: if self.xterm_check.get_active(): argv.append('--xterm') + id = self.shell_combo.get_active_id() + + if id is not None and id != '': + argv.append(id) + argv.append('--verbose') argv.extend(sys.argv[1:]) diff --git a/src/wrap.c b/src/wrap.c index a4e4bda5a..7fe1554e4 100644 --- a/src/wrap.c +++ b/src/wrap.c @@ -1463,25 +1463,95 @@ wrap_in_xterm (FlatpakBwrap *wrapped_command) flatpak_bwrap_add_args (wrapped_command, "xterm", "-e", + /* Original command will go here and become + * the argv of xterm -e */ + NULL); +} + +typedef enum +{ + SHELL_NONE = 0, + SHELL_AFTER, + SHELL_FAIL, + SHELL_INSTEAD +} Shell; + +static void +wrap_interactive (FlatpakBwrap *wrapped_command, + Shell shell) +{ + static const char preamble[] = + { + "prgname=\"$1\"\n" + "shift\n" + }; + const char *script = ""; + static const char start_shell[] = + { + "echo\n" + "echo\n" + "echo\n" + "echo \"$1: Starting interactive shell (original command is in " + "\\\"\\$@\\\")\"\n" + "echo\n" + "echo\n" + "echo\n" + "exec bash -i -s \"$@\"\n" + }; + gchar *command; + + g_return_if_fail (wrapped_command != NULL); + + switch (shell) + { + case SHELL_NONE: + script = + "e=0\n" + "\"$@\" || e=$?\n" + "echo\n" + "echo \"Press Enter or ^D to continue...\"\n" + "read reply || true\n" + "exit \"$e\"\n"; + break; + + case SHELL_AFTER: + script = + "e=0\n" + "\"$@\" || e=$?\n"; + break; + + case SHELL_FAIL: + script = + "if \"$@\"; then exit 0; else e=\"$?\"; fi\n" + "echo \"$1: command exit status $e\"\n"; + break; + + case SHELL_INSTEAD: + break; + + default: + g_return_if_reached (); + } + + command = g_strdup_printf ("%s%s%s", + preamble, + script, + shell == SHELL_NONE ? "" : start_shell); + + flatpak_bwrap_add_args (wrapped_command, "sh", "-euc", - "echo\n" - "echo \"$1: Starting interactive shell " - "(original command is in " - "\\\"\\$@\\\")\"\n" - "echo\n" - "shift\n" - "exec \"$@\"\n", + command, "sh", /* $0 for sh */ g_get_prgname (), /* $1 for sh */ - "bash", "-i", "-s", /* Original command will go here and become + * the argv of command, and eventually * the argv of bash -i -s */ NULL); } static gboolean -wrap_interactive (FlatpakBwrap *wrapped_command, - GError **error) +wrap_tty (FlatpakBwrap *wrapped_command, + GError **error) { int fd; @@ -1514,15 +1584,6 @@ wrap_interactive (FlatpakBwrap *wrapped_command, return glnx_throw_errno_prefix (error, "Cannot use /dev/tty as stderr"); g_close (fd, NULL); - - flatpak_bwrap_add_args (wrapped_command, - "bash", "-i", "-s", - /* Original command will go here and become - * the argv of bash -i -s */ - NULL); - - g_message ("Starting interactive shell " - "(original command is in \"$@\""); return TRUE; } @@ -1532,12 +1593,15 @@ static char *opt_freedesktop_app_id = NULL; static char *opt_steam_app_id = NULL; static char *opt_home = NULL; static gboolean opt_host_fallback = FALSE; -static gboolean opt_interactive = FALSE; +static gboolean opt_shell_after = FALSE; +static gboolean opt_shell_fail = FALSE; +static gboolean opt_shell_instead = FALSE; static GPtrArray *opt_ld_preload = NULL; static char *opt_runtime = NULL; static gboolean opt_share_home = TRUE; static gboolean opt_verbose = FALSE; static gboolean opt_version = FALSE; +static gboolean opt_tty = FALSE; static gboolean opt_xterm = FALSE; static gboolean @@ -1575,11 +1639,6 @@ static GOptionEntry options[] = { "host-ld-preload", 0, 0, G_OPTION_ARG_CALLBACK, &opt_host_ld_preload_cb, "Add MODULE from the host system to LD_PRELOAD when executing COMMAND.", "MODULE" }, - { "interactive", 0, 0, G_OPTION_ARG_NONE, &opt_interactive, - "Run an interactive shell instead of COMMAND. Executing \"$@\" in that " - "shell will run COMMAND [ARGS]. This process must have a controlling " - "terminal.", - NULL }, { "runtime", 0, 0, G_OPTION_ARG_FILENAME, &opt_runtime, "Mount the given sysroot or merged /usr in the container, and augment " "it with the host system's graphics stack.", @@ -1589,9 +1648,26 @@ static GOptionEntry options[] = { "unshare-home", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_share_home, "Use an app-specific home directory chosen according to --home, " "--freedesktop-app-id, --steam-app-id or $SteamAppId.", NULL }, + { "shell-after", 0, 0, G_OPTION_ARG_NONE, &opt_shell_after, + "Run an interactive shell after COMMAND. Executing \"$@\" in that " + "shell will re-run COMMAND [ARGS].", + NULL }, + { "shell-fail", 0, 0, G_OPTION_ARG_NONE, &opt_shell_fail, + "Run an interactive shell after COMMAND, but only if it fails.", + NULL }, + { "shell-instead", 0, 0, G_OPTION_ARG_NONE, &opt_shell_instead, + "Run an interactive shell instead of COMMAND. Executing \"$@\" in that " + "shell will run COMMAND [ARGS].", + NULL }, + { "tty", 0, 0, G_OPTION_ARG_NONE, &opt_tty, + "For --shell-after, --shell-fail and --shell-instead, use Steam's " + "controlling tty instead of running an xterm.", + NULL }, { "xterm", 0, 0, G_OPTION_ARG_NONE, &opt_xterm, - "Same as --interactive, but run the shell in an xterm. xterm(1) " - "must be installed in the RUNTIME, if used.", NULL }, + "Run an xterm which will display the log output from the game. " + "Implied by --shell-after, --shell-fail and --shell-instead, " + "unless --tty is used.", + NULL }, { "verbose", 0, 0, G_OPTION_ARG_NONE, &opt_verbose, "Be more verbose.", NULL }, { "version", 0, 0, G_OPTION_ARG_NONE, &opt_version, @@ -1680,6 +1756,18 @@ main (int argc, goto out; } + if (opt_tty && opt_xterm) + { + g_printerr ("%s: --tty and --xterm are contradictory\n", + g_get_prgname ()); + goto out; + } + + if ((opt_shell_after || opt_shell_fail || opt_shell_instead) && !opt_tty) + { + opt_xterm = TRUE; + } + if (strcmp (argv[1], "--") == 0) { argv++; @@ -1740,7 +1828,7 @@ main (int argc, * us exit 1. */ ret = 1; - if (!opt_interactive) + if (!opt_tty) { int fd; @@ -1808,16 +1896,37 @@ main (int argc, wrapped_command = flatpak_bwrap_new (NULL); + if (opt_tty) + { + g_debug ("Wrapping command to use tty"); + + if (!wrap_tty (wrapped_command, error)) + goto out; + } + if (opt_xterm) { g_debug ("Wrapping command with xterm"); wrap_in_xterm (wrapped_command); } - else if (opt_interactive) + + if (opt_shell_instead) { - g_debug ("Wrapping command with interactive shell"); - if (!wrap_interactive (wrapped_command, error)) - goto out; + wrap_interactive (wrapped_command, SHELL_INSTEAD); + } + else if (opt_shell_after) + { + wrap_interactive (wrapped_command, SHELL_AFTER); + } + else if (opt_shell_fail) + { + wrap_interactive (wrapped_command, SHELL_FAIL); + } + else if (opt_xterm) + { + /* Just don't let the xterm close before the user has had a chance + * to see the output */ + wrap_interactive (wrapped_command, SHELL_NONE); } if (argv[1][0] == '-') @@ -1903,7 +2012,7 @@ main (int argc, /* 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_interactive) + if (!opt_tty) flatpak_bwrap_add_arg (bwrap, "--new-session"); if (opt_runtime != NULL) -- GitLab