Michael Catanzaro 910cd73
From ba3b85a8fea0151e74de50e841a7f16f9b077a56 Mon Sep 17 00:00:00 2001
56c964d
From: Benjamin Berg <bberg@redhat.com>
56c964d
Date: Mon, 27 Jul 2020 22:22:32 +0200
56c964d
Subject: [PATCH 2/4] gdesktopappinfo: Move launched applications into
56c964d
 transient scope
56c964d
56c964d
Try to move the spawned executable into its own systemd scope. To avoid
56c964d
possible race conditions and ensure proper accounting, we delay the
56c964d
execution of the real command until after the DBus call to systemd has
56c964d
finished.
56c964d
56c964d
From the two approaches we can take here, this is better in the sense
56c964d
that we have a child that the API consumer can watch. API consumers
56c964d
should not be doing this, however, gnome-session needs to watch children
56c964d
during session startup. Until gnome-session is fixed, we will not be
56c964d
able to change this.
56c964d
56c964d
The alternative approach is to delegate launching itself to systemd by
56c964d
creating a transient .service unit instead. This is cleaner and has e.g.
56c964d
the advantage that systemd will take care of log redirection and similar
56c964d
issues.
56c964d
56c964d
Note that this patch is incomplete. The DBus call is done in a "fire and
56c964d
forget" manner, which is fine in most cases, but means that "gio open"
56c964d
will fail to move the child into the new scope as gio quits before the
56c964d
DBus call finishes.
56c964d
---
Michael Catanzaro 910cd73
 gio/gdesktopappinfo.c | 264 ++++++++++++++++++++++++++++++++++++------
Michael Catanzaro 910cd73
 1 file changed, 227 insertions(+), 37 deletions(-)
56c964d
56c964d
diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c
Michael Catanzaro 910cd73
index 1a4b97918..afdcd42ac 100644
56c964d
--- a/gio/gdesktopappinfo.c
56c964d
+++ b/gio/gdesktopappinfo.c
56c964d
@@ -2730,6 +2730,148 @@ notify_desktop_launch (GDBusConnection  *session_bus,
56c964d
 
56c964d
 #define _SPAWN_FLAGS_DEFAULT (G_SPAWN_SEARCH_PATH)
56c964d
 
56c964d
+#if defined(__linux__) && !defined(__BIONIC__)
56c964d
+typedef struct {
56c964d
+  int pipe[2];
56c964d
+  GSpawnChildSetupFunc user_setup;
56c964d
+  gpointer             user_setup_data;
56c964d
+} SpawnWrapperData;
56c964d
+
56c964d
+static void
56c964d
+launch_uris_with_spawn_delay_exec (gpointer user_data)
56c964d
+{
56c964d
+  SpawnWrapperData *data = user_data;
56c964d
+
56c964d
+  /* Clear CLOEXEC again, as that was set due to
56c964d
+   * G_SPAWN_LEAVE_DESCRIPTORS_OPEN not being set. */
56c964d
+  fcntl (data->pipe[0], F_SETFD, 0);
56c964d
+
56c964d
+  /* No need to close read side, we have CLOEXEC set. */
56c964d
+
56c964d
+  if (data->user_setup)
56c964d
+    data->user_setup (data->user_setup_data);
56c964d
+}
56c964d
+
56c964d
+static gchar *
56c964d
+systemd_unit_name_escape (const gchar *in)
56c964d
+{
56c964d
+  /* Adapted from systemd source */
56c964d
+  GString * const str = g_string_sized_new (strlen (in));
56c964d
+
56c964d
+  for (; *in; in++)
56c964d
+    {
56c964d
+      if (g_ascii_isalnum (*in) || *in == ':' || *in == '_' || *in == '.')
56c964d
+        g_string_append_c (str, *in);
56c964d
+      else
56c964d
+        g_string_append_printf (str, "\\x%02x", *in);
56c964d
+    }
56c964d
+  return g_string_free (str, FALSE);
56c964d
+}
56c964d
+
56c964d
+static void
56c964d
+create_systemd_scope (GDBusConnection    *session_bus,
56c964d
+                      GDesktopAppInfo    *info,
56c964d
+                      gint                pid,
56c964d
+                      GAsyncReadyCallback callback,
56c964d
+                      gpointer            user_data)
56c964d
+{
56c964d
+  GVariantBuilder builder;
56c964d
+  const char *app_name = g_get_application_name ();
56c964d
+  char *appid = NULL;
56c964d
+  char *appid_escaped = NULL;
56c964d
+  char *snid_escaped = NULL;
56c964d
+  char *unit_name = NULL;
56c964d
+
56c964d
+  /* In this order:
56c964d
+   *  1. Actual application ID from file
56c964d
+   *  2. Stripping the .desktop from the desktop ID
56c964d
+   *  3. Fall back to using the binary name
56c964d
+   */
56c964d
+  if (info->app_id)
56c964d
+    appid = g_strdup (info->app_id);
56c964d
+  else if (info->desktop_id && g_str_has_suffix (info->desktop_id, ".desktop"))
56c964d
+    appid = g_strndup (info->desktop_id, strlen (info->desktop_id) - 8);
56c964d
+  else
56c964d
+    appid = g_path_get_basename (info->binary);
56c964d
+
56c964d
+  appid_escaped = systemd_unit_name_escape (appid);
56c964d
+
56c964d
+  /* Generate a name conforming to
56c964d
+   *   https://systemd.io/DESKTOP_ENVIRONMENTS/
56c964d
+   * We use the PID to disambiguate, as that should be unique enough.
56c964d
+   */
56c964d
+  unit_name = g_strdup_printf ("app-glib-%s-%d.scope", appid_escaped, pid);
56c964d
+
56c964d
+  g_variant_builder_init (&builder, G_VARIANT_TYPE ("(ssa(sv)a(sa(sv)))"));
56c964d
+  g_variant_builder_add (&builder, "s", unit_name);
56c964d
+  g_variant_builder_add (&builder, "s", "fail");
56c964d
+
56c964d
+  g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(sv)"));
56c964d
+
56c964d
+  /* Add a generic human readable description, can be changed at will. */
56c964d
+  if (app_name)
56c964d
+    g_variant_builder_add (&builder,
56c964d
+                           "(sv)",
56c964d
+                           "Description",
56c964d
+                           g_variant_new_take_string (g_strdup_printf ("Application launched by %s",
56c964d
+                                                                       app_name)));
56c964d
+  g_variant_builder_add (&builder,
56c964d
+                         "(sv)",
56c964d
+                         "PIDs",
56c964d
+                          g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32, &pid, 1, 4));
56c964d
+  /* Default to let systemd garbage collect failed applications we launched. */
56c964d
+  g_variant_builder_add (&builder,
56c964d
+                         "(sv)",
56c964d
+                         "CollectMode",
56c964d
+                          g_variant_new_string ("inactive-or-failed"));
56c964d
+
56c964d
+  g_variant_builder_close (&builder);
56c964d
+
56c964d
+  g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(sa(sv))"));
56c964d
+  g_variant_builder_close (&builder);
56c964d
+
56c964d
+  g_dbus_connection_call (session_bus,
56c964d
+                          "org.freedesktop.systemd1",
56c964d
+                          "/org/freedesktop/systemd1",
56c964d
+                          "org.freedesktop.systemd1.Manager",
56c964d
+                          "StartTransientUnit",
56c964d
+                          g_variant_builder_end (&builder),
56c964d
+                          G_VARIANT_TYPE ("(o)"),
56c964d
+                          G_DBUS_CALL_FLAGS_NO_AUTO_START,
56c964d
+                          1000,
56c964d
+                          NULL,
56c964d
+                          callback,
56c964d
+                          user_data);
56c964d
+
56c964d
+  g_free (appid);
56c964d
+  g_free (appid_escaped);
56c964d
+  g_free (snid_escaped);
56c964d
+  g_free (unit_name);
56c964d
+}
56c964d
+
56c964d
+static void
56c964d
+systemd_scope_created_cb (GObject      *object,
56c964d
+                          GAsyncResult *result,
56c964d
+                          gpointer      user_data)
56c964d
+{
56c964d
+  GVariant *res = NULL;
56c964d
+  GError *error = NULL;
56c964d
+
56c964d
+  res = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
56c964d
+  if (error != NULL)
56c964d
+    {
56c964d
+      g_debug ("Failed to move new child into scope: %s", error->message);
56c964d
+      g_error_free (error);
56c964d
+    }
56c964d
+
56c964d
+  /* Unblock the waiting wrapper binary. */
56c964d
+  close (GPOINTER_TO_INT (user_data));
56c964d
+
56c964d
+  if (res)
56c964d
+    g_variant_unref (res);
56c964d
+}
56c964d
+#endif
56c964d
+
56c964d
 static gboolean
56c964d
 g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo            *info,
56c964d
                                            GDBusConnection            *session_bus,
56c964d
@@ -2750,13 +2892,14 @@ g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo            *info,
56c964d
   GList *old_uris;
56c964d
   GList *dup_uris;
56c964d
 
56c964d
-  char **argv, **envp;
56c964d
+  GStrv argv = NULL, envp = NULL;
56c964d
+  GStrv wrapped_argv = NULL;
56c964d
+  GList *launched_uris = NULL;
56c964d
+  char *sn_id = NULL;
56c964d
   int argc;
56c964d
 
56c964d
   g_return_val_if_fail (info != NULL, FALSE);
56c964d
 
56c964d
-  argv = NULL;
56c964d
-
56c964d
   if (launch_context)
56c964d
     envp = g_app_launch_context_get_environment (launch_context);
56c964d
   else
56c964d
@@ -2770,27 +2913,19 @@ g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo            *info,
56c964d
   do
56c964d
     {
56c964d
       GPid pid;
56c964d
-      GList *launched_uris;
56c964d
       GList *iter;
56c964d
-      char *sn_id = NULL;
56c964d
-      char **wrapped_argv;
56c964d
       int i;
56c964d
-      gsize j;
56c964d
-      const gchar * const wrapper_argv[] =
56c964d
-        {
56c964d
-          "/bin/sh",
56c964d
-          "-e",
56c964d
-          "-u",
56c964d
-          "-c", "export GIO_LAUNCHED_DESKTOP_FILE_PID=$$; exec \"$@\"",
56c964d
-          "sh",  /* argv[0] for sh */
56c964d
-        };
56c964d
+#if defined(__linux__) && !defined(__BIONIC__)
56c964d
+      SpawnWrapperData wrapper_data;
56c964d
+#endif
56c964d
+      GSpawnChildSetupFunc setup = user_setup;
56c964d
+      gpointer             setup_data = user_setup_data;
56c964d
 
56c964d
       old_uris = dup_uris;
56c964d
       if (!expand_application_parameters (info, exec_line, &dup_uris, &argc, &argv, error))
56c964d
-        goto out;
56c964d
+        return FALSE;
56c964d
 
56c964d
       /* Get the subset of URIs we're launching with this process */
56c964d
-      launched_uris = NULL;
56c964d
       for (iter = old_uris; iter != NULL && iter != dup_uris; iter = iter->next)
56c964d
         launched_uris = g_list_prepend (launched_uris, iter->data);
56c964d
       launched_uris = g_list_reverse (launched_uris);
56c964d
@@ -2799,7 +2934,7 @@ g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo            *info,
56c964d
         {
56c964d
           g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
56c964d
                                _("Unable to find terminal required for application"));
56c964d
-          goto out;
56c964d
+          return FALSE;
56c964d
         }
56c964d
 
56c964d
       if (info->filename)
56c964d
@@ -2808,7 +2943,6 @@ g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo            *info,
56c964d
                                  info->filename,
56c964d
                                  TRUE);
56c964d
 
56c964d
-      sn_id = NULL;
56c964d
       if (launch_context)
56c964d
         {
56c964d
           GList *launched_files = create_files_for_uris (launched_uris);
Michael Catanzaro 910cd73
@@ -2837,38 +2971,93 @@ g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo            *info,
56c964d
        * with a wrapper program (grep the GLib git history for
56c964d
        * `gio-launch-desktop` for an example of this which could be
56c964d
        * resurrected). */
56c964d
-      wrapped_argv = g_new (char *, argc + G_N_ELEMENTS (wrapper_argv) + 1);
56c964d
+      wrapped_argv = g_new (char *, argc + 6 + 1);
56c964d
+
56c964d
+      wrapped_argv[0] = g_strdup ("/bin/sh");
56c964d
+      wrapped_argv[1] = g_strdup ("-e");
56c964d
+      wrapped_argv[2] = g_strdup ("-u");
56c964d
+      wrapped_argv[3] = g_strdup ("-c");
56c964d
+      /* argument 4 is filled in below */
56c964d
+      wrapped_argv[5] = g_strdup ("sh");
56c964d
 
56c964d
-      for (j = 0; j < G_N_ELEMENTS (wrapper_argv); j++)
56c964d
-        wrapped_argv[j] = g_strdup (wrapper_argv[j]);
56c964d
       for (i = 0; i < argc; i++)
56c964d
-        wrapped_argv[i + G_N_ELEMENTS (wrapper_argv)] = g_steal_pointer (&argv[i]);
56c964d
+        wrapped_argv[i + 6] = g_steal_pointer (&argv[i]);
Michael Catanzaro 910cd73
+
56c964d
+      wrapped_argv[i + 6] = NULL;
56c964d
+      g_clear_pointer (&argv, g_free);
56c964d
+
56c964d
+#if defined(__linux__) && !defined(__BIONIC__)
56c964d
+      /* Create pipes, if we use a setup func, then set cloexec,
56c964d
+       * otherwise our wrapper script will close both sides. */
56c964d
+      if (!g_unix_open_pipe (wrapper_data.pipe, 0, NULL))
56c964d
+        {
56c964d
+          g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
56c964d
+                               _("Unable to create pipe for systemd synchronization"));
56c964d
+          return FALSE;
56c964d
+        }
56c964d
+
56c964d
+      /* Set CLOEXEC on the write pipe, so we don't need to deal with it in the child. */
56c964d
+      fcntl (wrapper_data.pipe[1], F_SETFD, FD_CLOEXEC);
Michael Catanzaro 910cd73
 
Michael Catanzaro 910cd73
-      wrapped_argv[i + G_N_ELEMENTS (wrapper_argv)] = NULL;
Michael Catanzaro 910cd73
-      g_free (argv);
Michael Catanzaro 910cd73
-      argv = NULL;
56c964d
+      if (!(spawn_flags & G_SPAWN_LEAVE_DESCRIPTORS_OPEN))
56c964d
+        {
56c964d
+          /* In this case, we use a setup function (which could probably also
56c964d
+           * overwrite envp to set GIO_LAUNCHED_DESKTOP_FILE_PID).
56c964d
+           *
56c964d
+           * Note that this does not incur an additional cost because
56c964d
+           * G_SPAWN_LEAVE_DESCRIPTOR_OPEN must be set in order to use
56c964d
+           * posix_spawn. */
56c964d
+          wrapper_data.user_setup = setup;
56c964d
+          wrapper_data.user_setup_data = setup_data;
56c964d
+
56c964d
+          setup = launch_uris_with_spawn_delay_exec;
56c964d
+          setup_data = &wrapper_data;
56c964d
+        }
56c964d
+
56c964d
+      wrapped_argv[4] = g_strdup_printf ("export GIO_LAUNCHED_DESKTOP_FILE_PID=$$; cat <&%1$d; exec \"$@\" %1$d<&-",
56c964d
+                                         wrapper_data.pipe[0]);
56c964d
+#else
56c964d
+      wrapped_argv[4] = g_strdup ("export GIO_LAUNCHED_DESKTOP_FILE_PID=$$; exec \"$@\"");
56c964d
+#endif
56c964d
 
56c964d
       if (!g_spawn_async_with_fds (info->path,
56c964d
                                    wrapped_argv,
56c964d
                                    envp,
56c964d
                                    spawn_flags,
56c964d
-                                   user_setup,
56c964d
-                                   user_setup_data,
56c964d
+                                   setup,
56c964d
+                                   setup_data,
56c964d
                                    &pid,
56c964d
                                    stdin_fd,
56c964d
                                    stdout_fd,
56c964d
                                    stderr_fd,
56c964d
                                    error))
56c964d
         {
56c964d
+#if defined(__linux__) && !defined(__BIONIC__)
56c964d
+          close (wrapper_data.pipe[0]);
56c964d
+          close (wrapper_data.pipe[1]);
56c964d
+#endif
56c964d
+
56c964d
           if (sn_id)
56c964d
             g_app_launch_context_launch_failed (launch_context, sn_id);
56c964d
 
Michael Catanzaro 910cd73
-          g_free (sn_id);
Michael Catanzaro 910cd73
-          g_list_free (launched_uris);
Michael Catanzaro 910cd73
-
Michael Catanzaro 910cd73
           goto out;
56c964d
         }
56c964d
 
56c964d
+#if defined(__linux__) && !defined(__BIONIC__)
56c964d
+      /* We close write side asynchronously later on when the dbus call
56c964d
+       * to systemd finishes. */
56c964d
+      close (wrapper_data.pipe[0]);
56c964d
+
56c964d
+      if (session_bus)
56c964d
+        create_systemd_scope (session_bus,
56c964d
+                              info,
56c964d
+                              pid,
56c964d
+                              systemd_scope_created_cb,
56c964d
+                              GINT_TO_POINTER (wrapper_data.pipe[1]));
56c964d
+      else
56c964d
+        close (wrapper_data.pipe[1]);
56c964d
+#endif
56c964d
+
56c964d
       if (pid_callback != NULL)
56c964d
         pid_callback (info, pid, pid_callback_data);
56c964d
 
Michael Catanzaro 910cd73
@@ -2893,19 +3082,20 @@ g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo            *info,
56c964d
                              sn_id,
56c964d
                              launched_uris);
56c964d
 
56c964d
-      g_free (sn_id);
56c964d
-      g_list_free (launched_uris);
56c964d
-
56c964d
-      g_strfreev (wrapped_argv);
56c964d
-      wrapped_argv = NULL;
56c964d
+      g_clear_pointer (&sn_id, g_free);
56c964d
+      g_clear_pointer (&launched_uris, g_list_free);
56c964d
+      g_clear_pointer (&wrapped_argv, g_strfreev);
56c964d
     }
56c964d
   while (dup_uris != NULL);
56c964d
 
56c964d
   completed = TRUE;
56c964d
 
56c964d
- out:
56c964d
+out:
56c964d
   g_strfreev (argv);
56c964d
   g_strfreev (envp);
56c964d
+  g_clear_pointer (&wrapped_argv, g_strfreev);
56c964d
+  g_list_free (launched_uris);
56c964d
+  g_free (sn_id);
56c964d
 
56c964d
   return completed;
56c964d
 }
56c964d
-- 
Michael Catanzaro 910cd73
2.31.1
56c964d