Skip to content
Snippets Groups Projects
wrap.c 61.8 KiB
Newer Older
  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;
    }

  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_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;

  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;
    }

      g_print ("%s:\n"
               " Package: pressure-vessel\n"
               " Version: %s\n",
               argv[0], VERSION);
  original_stdout = pv_divert_stdout_to_stderr (error);

  if (original_stdout == NULL)
    {
      ret = 1;
      goto out;
    }

  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;
        opt_terminal = PV_TERMINAL_NONE;
  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;
  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)
  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 ("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;
    }

  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 (is_flatpak_env)
    host_in_current_ns = "/run/host";
  else
    host_in_current_ns = "/";

  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_get_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);
      case PV_TERMINAL_TTY:
        g_debug ("Wrapping command to use tty");
        if (!pv_bwrap_wrap_tty (wrapped_command, error))
      case PV_TERMINAL_XTERM:
        g_debug ("Wrapping command with xterm");
        pv_bwrap_wrap_in_xterm (wrapped_command);
      case PV_TERMINAL_AUTO:
      case PV_TERMINAL_NONE:
      default:
        /* do nothing */
        break;
  if (opt_shell != PV_SHELL_NONE || opt_terminal == PV_TERMINAL_XTERM)
      /* In the (PV_SHELL_NONE, PV_TERMINAL_XTERM) case, just don't let the
       * xterm close before the user has had a chance to see the output */
      pv_bwrap_wrap_interactive (wrapped_command, opt_shell);
  if (argc > 1 && argv[1][0] == '-')
    {
      /* 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);

  /* If we are in a Flatpak environment we can't use bwrap directly */
  if (is_flatpak_env)
    {
      g_debug ("Checking for flatpak-spawn...");
      spawn_executable = check_flatpak_spawn ();
      /* Assume "bwrap" to exist in the host system and to be in its PATH */
      bwrap_executable = g_strdup ("bwrap");
    }
  else
    {
      g_debug ("Checking for bwrap...");
      bwrap_executable = check_bwrap (tools_dir, opt_only_prepare);
    }
      if ((is_flatpak_env && spawn_executable == NULL)
          || bwrap_executable == NULL)
          if (spawn_executable != NULL)
            g_debug ("OK (%s) (%s)", spawn_executable, bwrap_executable);
          else
            g_debug ("OK (%s)", bwrap_executable);
  if ((is_flatpak_env && spawn_executable == NULL)
      || bwrap_executable == NULL)
      /* TODO in a Flatpak environment, host is not what we expect it to be */
      if (opt_host_fallback)
        {
          g_message ("Falling back to executing wrapped command directly");

          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);

                  *equals = '\0';
                  flatpak_bwrap_set_env (wrapped_command, opt_env_if_host[i],
                                         equals + 1, TRUE);
                }
            }

          flatpak_bwrap_finish (wrapped_command);

          /* flatpak_bwrap_finish did this */
          g_assert (g_ptr_array_index (wrapped_command->argv,
                                       wrapped_command->argv->len - 1) == NULL);

          execvpe (g_ptr_array_index (wrapped_command->argv, 0),
                   (char * const *) wrapped_command->argv->pdata,
                   wrapped_command->envp);

          glnx_throw_errno_prefix (error, "execvpe %s",
                                   (gchar *) g_ptr_array_index (wrapped_command->argv, 0));
          goto out;
        }
      else
        {
          goto out;
        }
    }

  if (!is_flatpak_env)
    {
      g_debug ("Checking bwrap features...");
      bwrap_help_argv[0] = bwrap_executable;
      bwrap_help = pv_capture_output (bwrap_help_argv, error);

  bwrap = flatpak_bwrap_new (NULL);
  flatpak_bwrap_add_arg (bwrap, bwrap_executable);

  /* 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);

  if (!pv_bwrap_bind_usr (bwrap, "/", host_in_current_ns, "/run/host", error))
    goto out;

  /* https://github.com/flatpak/flatpak/pull/3733 */
  if (pv_file_test_in_sysroot (host_in_current_ns, "/etc/os-release",
                               G_FILE_TEST_EXISTS))
    flatpak_bwrap_add_args (bwrap,
                            "--ro-bind", "/etc/os-release",
                            "/run/host/os-release",
                            NULL);
  else if (pv_file_test_in_sysroot (host_in_current_ns, "/usr/lib/os-release",
                               G_FILE_TEST_EXISTS))
    flatpak_bwrap_add_args (bwrap,
                            "--ro-bind", "/usr/lib/os-release",
                            "/run/host/os-release",
                            NULL);

  /* 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;

      g_debug ("Configuring runtime %s...", opt_runtime);
      runtime = pv_runtime_new (opt_runtime,
                                bwrap_executable,
                                tools_dir,
                                opt_graphics_provider,
                                graphics_provider_mount_point,
      if (runtime == NULL)
      if (!pv_runtime_bind (runtime, bwrap, 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;

      /* This mounts over the top of the subset of /etc mounted by
       * pv_bwrap_bind_usr(), but that's harmless. */
      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 (bwrap, error))
        goto out;
    }

  /* 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_bwrap_add_args (bwrap,
                              "--bind", home, home,
                              NULL);
    }
  else
    {
      if (!use_fake_home (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");
    }

  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 (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);
                }
              else
                {
                  flatpak_bwrap_add_args (bwrap,
                                          "--ro-bind", preload, preload,
                                          NULL);
                  pv_search_path_append (adjusted_ld_preload, preload);
                }
            }
          else
            {
              g_debug ("LD_PRELOAD module '%s' does not exist", preload);
            }
        }
    }

  /* Put the caller's LD_PRELOAD back.
   * This would be filtered out by a setuid bwrap, so we have to go
   * via --setenv. */

  if (adjusted_ld_preload->len != 0)
      flatpak_bwrap_add_args (bwrap,
                              "--setenv", "LD_PRELOAD",
                              adjusted_ld_preload->str,
                              NULL);
  else
      flatpak_bwrap_add_args (bwrap,
                              "--unsetenv", "LD_PRELOAD",
                              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 (known_required_env[i], bwrap);
  /* 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_bwrap_add_args (bwrap,
                                  "--bind", opt_filesystems[i],
                                  opt_filesystems[i],
                                  NULL);
        }
    }

  /* 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 (pv_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
    {
      flatpak_bwrap_add_args (bwrap,
                              "--bind", cwd_p_host, cwd_p_host,
  flatpak_bwrap_add_args (bwrap,
  /* 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 (g_str_has_prefix (opt_env_if_host[i], "STEAM_RUNTIME=/"))
                {
                  flatpak_bwrap_add_args (bwrap,
                                          "--ro-bind", equals + 1,
                                          equals + 1,
                                          NULL);
                }

              *equals = '\0';
              /* We do this via --setenv instead of flatpak_bwrap_set_env()
               * to make sure they aren't filtered out by a setuid bwrap. */
              flatpak_bwrap_add_args (bwrap,
                                      "--setenv", opt_env_if_host[i],
                                      equals + 1,
                                      NULL);
              *equals = '=';
            }
        }
    }

  /* 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 unset the container
       * "DISPLAY" env.
       */
      if (g_file_test ("/.flatpak-info", G_FILE_TEST_IS_REGULAR))
        {
          flatpak_bwrap_add_args (bwrap,
                                  "--ro-bind", "/tmp/.X11-unix", "/tmp/.X11-unix",
                                  NULL);
          flatpak_bwrap_unset_env (bwrap, "DISPLAY");
        }
      else
        {
          flatpak_run_add_x11_args (bwrap, TRUE);
        }

      flatpak_run_add_pulseaudio_args (bwrap);
      flatpak_run_add_session_dbus_args (bwrap);
      flatpak_run_add_system_dbus_args (bwrap);
    }

  if (opt_verbose)
    {
      g_message ("%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_message ("\t%s", quoted);
        }
    }

  if (!flatpak_bwrap_bundle_args (bwrap, 1, -1, FALSE, error))
    goto out;

  adverb_args = flatpak_bwrap_new (flatpak_bwrap_empty_env);
  if (runtime != NULL)
    adverb_in_container = pv_runtime_get_adverb (runtime, adverb_args);

  if (opt_terminate_timeout >= 0.0)
    {
      if (opt_terminate_idle_timeout > 0.0)
        flatpak_bwrap_add_arg_printf (adverb_args,
                                      "--terminate-idle-timeout=%f",
                                      opt_terminate_idle_timeout);

      flatpak_bwrap_add_arg_printf (adverb_args,
                                    "--terminate-timeout=%f",
                                    opt_terminate_timeout);
    }

  /* If not using a runtime, the adverb in the container has the
   * same path as outside */
  if (adverb_in_container == NULL)
    adverb_in_container = g_build_filename (tools_dir,
                                            "pressure-vessel-adverb",
                                            NULL);
  flatpak_bwrap_add_args (bwrap,
                          adverb_in_container,
                          "--exit-with-parent",
                          "--subreaper",
                          NULL);
  if (opt_verbose)
    flatpak_bwrap_add_arg (bwrap, "--verbose");
  flatpak_bwrap_append_bwrap (bwrap, adverb_args);
  flatpak_bwrap_add_arg (bwrap, "--");
  g_debug ("Adding wrapped command...");
  flatpak_bwrap_append_args (bwrap, wrapped_command->argv);
  if (is_flatpak_env)
    {
      /* Just use the envp from @bwrap */
      g_autoptr(FlatpakBwrap) flatpak_spawn = flatpak_bwrap_new (flatpak_bwrap_empty_env);
      flatpak_bwrap_add_arg (flatpak_spawn, spawn_executable);
      flatpak_bwrap_add_arg (flatpak_spawn, "--host");

      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_spawn, fd_str);
        }
      /* Change the current working directory where flatpak-spawn will run.
       * 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_spawn, "--directory=/");

      flatpak_bwrap_append_bwrap (flatpak_spawn, bwrap);
      g_clear_pointer (&bwrap, flatpak_bwrap_free);
      bwrap = g_steal_pointer (&flatpak_spawn);
    }

      g_message ("Final %s options:", 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_message ("\t%s", quoted);
        }

      g_message ("%s environment:", bwrap_executable);

      for (i = 0; bwrap->envp != NULL && bwrap->envp[i] != NULL; i++)
        {
          g_autofree gchar *quoted = NULL;

          quoted = g_shell_quote (bwrap->envp[i]);
          g_message ("\t%s", quoted);
        }
  /* Clean up temporary directory before running our long-running process */
  if (runtime != NULL)
    pv_runtime_cleanup (runtime);
  if (opt_write_bwrap != NULL)
    {
      FILE *file = fopen (opt_write_bwrap, "w");
      if (file == NULL)
        {
          g_warning ("An error occurred trying to write the bwrap arguments: %s",
                    g_strerror (errno));
          /* This is not a fatal error, try to continue */
        }
      else
        {
          for (i = 0; i < bwrap->argv->len; i++)
            fprintf (file, "%s%c", (gchar *)g_ptr_array_index (bwrap->argv, i), '\0');

          fclose (file);
        }
    }

  if (opt_only_prepare)
    ret = 0;
  else
    pv_bwrap_execve (bwrap, fileno (original_stdout), error);

out:
  if (local_error != NULL)
    g_warning ("%s", local_error->message);

  g_clear_pointer (&opt_ld_preload, g_ptr_array_unref);
  g_clear_pointer (&opt_env_if_host, g_strfreev);
  g_clear_pointer (&opt_freedesktop_app_id, g_free);
  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_base, g_free);
  g_clear_pointer (&opt_runtime, g_free);

  g_debug ("Exiting with status %d", ret);