[gnome-terminal/benzea/systemd-integration: 2/7] systemd: Add systemd helper to move processes into scopes



commit 9538c6bb2329fd57d44e750b22d6ecc96f922209
Author: Benjamin Berg <bberg redhat com>
Date:   Tue Feb 4 13:04:14 2020 +0100

    systemd: Add systemd helper to move processes into scopes
    
    Doing this creates a better separation between the server and the
    different terminals.
    
    This helper is basically copied from gnome-desktop. Copying the code is
    not ideal, but not a lot of GNOME applications have a use for this and
    it is not clear how a more generic API should looks like.
    
    Luckily this change is rather self contained and a port to another API
    will be trivial when it becomes available.

 configure.ac           |  21 ++++
 src/Makefile.am        |   6 +-
 src/terminal-systemd.c | 273 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/terminal-systemd.h |  37 +++++++
 4 files changed, 336 insertions(+), 1 deletion(-)
---
diff --git a/configure.ac b/configure.ac
index 93ac619e..8d11cdb7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -216,6 +216,27 @@ fi
 
 AM_CONDITIONAL([WITH_NAUTILUS_EXTENSION],[test "$with_nautilus_extension" = "yes"])
 
+# ***************************
+# libsystemd availability
+# ***************************
+
+AC_MSG_CHECKING([Whether to integrate with systemd])
+AC_ARG_ENABLE([systemd],
+  [AS_HELP_STRING([--disable-systemd],[Disable gnome-terminal systemd integration])],
+  [],[enable_systemd=yes])
+AC_MSG_RESULT([$enable_systemd])
+
+if test "$enable_systemd" = "yes"; then
+  PKG_CHECK_MODULES([SYSTEMD],[
+    libsystemd])
+
+  AC_SUBST(SYSTEMD_CFLAGS)
+  AC_SUBST(SYSTEMD_LIBS)
+  AC_DEFINE([HAVE_SYSTEMD],[1],[Define to 1 if libsystemd is available])
+fi
+
+AM_CONDITIONAL([ENABLE_SYSTEMD],[test "$enable_systemd" = "yes"])
+
 # ***********
 # Compilation
 # ***********
diff --git a/src/Makefile.am b/src/Makefile.am
index a145b387..9d993451 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -85,6 +85,8 @@ gnome_terminal_server_SOURCES = \
        terminal-screen-container.h \
        terminal-search-popover.c \
        terminal-search-popover.h \
+       terminal-systemd.c \
+       terminal-systemd.h \
        terminal-tab-label.c \
        terminal-tab-label.h \
        terminal-util.c \
@@ -106,7 +108,8 @@ gnome_terminal_server_CFLAGS = \
        $(TERM_CFLAGS) \
        $(PTHREAD_CFLAGS) \
        $(WARN_CFLAGS) \
-       $(AM_CFLAGS)
+       $(AM_CFLAGS) \
+       $(SYSTEMD_CFLAGS)
 
 gnome_terminal_server_LDFLAGS = \
        $(AM_LDFLAGS)
@@ -114,6 +117,7 @@ gnome_terminal_server_LDFLAGS = \
 gnome_terminal_server_LDADD = \
        $(TERM_LIBS) \
        $(PTHREAD_LIBS) \
+       $(SYSTEMD_LIBS) \
        $(NULL)
 
 TYPES_H_FILES = \
diff --git a/src/terminal-systemd.c b/src/terminal-systemd.c
new file mode 100644
index 00000000..d22f52c7
--- /dev/null
+++ b/src/terminal-systemd.c
@@ -0,0 +1,273 @@
+/* terminal-systemd.c
+ *
+ * Copyright 2019 Benjamin Berg <bberg redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+/* This file has been copied from gnome-desktop and only modified to use a
+ * different unit name.
+ */
+
+#include "config.h"
+
+#include "terminal-systemd.h"
+
+#ifdef HAVE_SYSTEMD
+#include <errno.h>
+#include <systemd/sd-login.h>
+
+typedef struct {
+  char       *name;
+  char       *description;
+  gint32      pid;
+} StartSystemdScopeData;
+
+static void
+start_systemd_scope_data_free (StartSystemdScopeData *data)
+{
+  g_clear_pointer (&data->name, g_free);
+  g_clear_pointer (&data->description, g_free);
+  g_free (data);
+}
+
+static void
+on_start_transient_unit_cb (GObject      *source,
+                            GAsyncResult *res,
+                            gpointer      user_data)
+{
+  g_autoptr (GTask) task = G_TASK (user_data);
+  g_autoptr (GVariant) reply = NULL;
+  GError *error = NULL;
+  StartSystemdScopeData *task_data = g_task_get_task_data (task);
+
+  reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source),
+                                         res, &error);
+  if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    {
+      g_warning ("Could not create transient scope for PID %d: %s",
+                 task_data->pid, error->message);
+      g_task_return_error (task, error);
+      return;
+    }
+
+  g_debug ("Created transient scope for PID %d", task_data->pid);
+
+  g_task_return_boolean (task, TRUE);
+}
+
+static void
+start_systemd_scope (GDBusConnection *connection, GTask *task)
+{
+  GVariantBuilder builder;
+  g_autofree char *unit_name = NULL;
+  StartSystemdScopeData *task_data = g_task_get_task_data (task);
+  GStrv binds_to = { "gnome-terminal-server.service", NULL };
+
+  g_assert (task_data != NULL);
+
+  /* This needs to be unique, hopefully the pid will be enough. */
+  unit_name = g_strdup_printf ("gnome-terminal-%s-%d.scope", task_data->name, task_data->pid);
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE ("(ssa(sv)a(sa(sv)))"));
+  g_variant_builder_add (&builder, "s", unit_name);
+  g_variant_builder_add (&builder, "s", "fail");
+
+  /* Note that gnome-session ships a drop-in to control further defaults. */
+  g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(sv)"));
+  if (task_data->description)
+    g_variant_builder_add (&builder,
+                           "(sv)",
+                           "Description",
+                           g_variant_new_string (task_data->description));
+  g_variant_builder_add (&builder,
+                         "(sv)",
+                         "PIDs",
+                          g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32, &task_data->pid, 1, 4));
+
+  g_variant_builder_close (&builder);
+
+  g_variant_builder_open (&builder, G_VARIANT_TYPE ("a(sa(sv))"));
+  g_variant_builder_close (&builder);
+
+  g_dbus_connection_call (connection,
+                          "org.freedesktop.systemd1",
+                          "/org/freedesktop/systemd1",
+                          "org.freedesktop.systemd1.Manager",
+                          "StartTransientUnit",
+                          g_variant_builder_end (&builder),
+                          G_VARIANT_TYPE ("(o)"),
+                          G_DBUS_CALL_FLAGS_NO_AUTO_START,
+                          1000,
+                          g_task_get_cancellable (task),
+                          on_start_transient_unit_cb,
+                          task);
+}
+
+static void
+on_bus_gotten_cb (GObject      *source,
+                  GAsyncResult *res,
+                  gpointer      user_data)
+{
+  g_autoptr(GTask) task = G_TASK (user_data);
+  g_autoptr(GDBusConnection) connection = NULL;
+  GError *error = NULL;
+
+  connection = g_bus_get_finish (res, &error);
+  if (error)
+    {
+      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        g_warning ("Could not get session bus: %s", error->message);
+
+      g_task_return_error (task, error);
+      return;
+    }
+
+  start_systemd_scope (connection, g_steal_pointer (&task));
+}
+#endif
+
+/**
+ * terminal_start_systemd_scope:
+ * @name: Name for the application
+ * @pid: The PID of the application
+ * @description: (nullable): A description to use for the unit, or %NULL
+ * @connection: (nullable): An #GDBusConnection to the session bus, or %NULL
+ * @cancellable: (nullable): #GCancellable to use
+ * @callback: (nullable): Callback to call when the operation is done
+ * @user_data: Data to be passed to @callback
+ *
+ * If the current process is running inside a user systemd instance, then move
+ * the launched PID into a transient scope. The given @name will be used to
+ * create a unit name. It should be the application ID for desktop files or
+ * the executable in all other cases.
+ *
+ * It is advisable to use this function every time where the started application
+ * can be considered reasonably independent of the launching application. Placing
+ * it in a scope creates proper separation between the programs rather than being
+ * considered a single entity by systemd.
+ *
+ * It is always safe to call this function. Note that a successful return code
+ * does not imply that a unit has been created. It solely means that no error
+ * condition was hit sending the request.
+ *
+ * If @connection is %NULL then g_dbus_get() will be called internally.
+ *
+ * Note that most callers will not need to handle errors. As such, it is normal
+ * to pass a %NULL @callback.
+ *
+ * Stability: unstable
+ */
+void
+terminal_start_systemd_scope (const char           *name,
+                              gint32                pid,
+                              const char           *description,
+                              GDBusConnection      *connection,
+                              GCancellable         *cancellable,
+                              GAsyncReadyCallback   callback,
+                              gpointer              user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+#ifdef HAVE_SYSTEMD
+  g_autofree char *own_unit = NULL;
+  const char *valid_chars =
+    "-._1234567890"
+    "abcdefghijklmnopqrstuvwxyz"
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+  StartSystemdScopeData *task_data;
+  gint res;
+
+  task = g_task_new (NULL, cancellable, callback, user_data);
+  task_data = g_new0 (StartSystemdScopeData, 1);
+
+  task_data->pid = pid;
+
+  /* Create a nice and (mangled) name to embed into the unit */
+  if (name == NULL)
+    name = "anonymous";
+
+  if (name[0] == '/')
+    name++;
+
+  task_data->name = g_str_to_ascii (name, "C");
+  g_strdelimit (task_data->name, "/", '-');
+  g_strcanon (task_data->name, valid_chars, '_');
+
+  task_data->description = g_strdup (description);
+  if (task_data->description == NULL)
+    {
+      const char *app_name = g_get_application_name();
+
+      if (app_name)
+        task_data->description = g_strdup_printf ("Application launched by %s",
+                                                  app_name);
+    }
+
+  g_task_set_task_data (task, task_data, (GDestroyNotify) start_systemd_scope_data_free);
+
+  /* We cannot do anything if this process is not managed by the
+   * systemd user instance. */
+  res = sd_pid_get_user_unit (getpid (), &own_unit);
+  if (res == -ENODATA)
+    {
+      g_debug ("Not systemd managed, will not move PID %d into transient scope\n", pid);
+      g_task_return_boolean (task, TRUE);
+
+      return;
+    }
+  if (res < 0)
+    {
+      g_warning ("Error fetching user unit for own pid: %d\n", -res);
+      g_task_return_new_error (task,
+                               G_IO_ERROR,
+                               g_io_error_from_errno (-res),
+                               "Error fetching user unit for own pid: %d", -res);
+
+      return;
+    }
+
+  if (connection == NULL)
+    g_bus_get (G_BUS_TYPE_SESSION, cancellable, on_bus_gotten_cb, g_steal_pointer (&task));
+  else
+    start_systemd_scope (connection, g_steal_pointer (&task));
+#else
+  g_debug ("Not creating transient scope for PID %d. Systemd support not compiled in.", pid);
+
+  task = g_task_new (NULL, cancellable, callback, user_data);
+  g_task_return_boolean (task, TRUE);
+#endif
+}
+
+/**
+ * terminal_start_systemd_scope_finish:
+ * @res: A #GAsyncResult
+ * @error: Return location for errors, or %NULL to ignore
+ *
+ * Finish an asynchronous operation to create a transient scope that was
+ * started with terminal_start_systemd_scope().
+ *
+ * Note that a successful return code does not imply that a unit has been
+ * created. It solely means that no error condition was hit sending the request.
+ *
+ * Returns: %FALSE on error, %TRUE otherwise
+ */
+gboolean
+terminal_start_systemd_scope_finish (GAsyncResult  *res,
+                                  GError       **error)
+{
+  return g_task_propagate_boolean (G_TASK (res), error);
+}
diff --git a/src/terminal-systemd.h b/src/terminal-systemd.h
new file mode 100644
index 00000000..2df6449c
--- /dev/null
+++ b/src/terminal-systemd.h
@@ -0,0 +1,37 @@
+/* terminal-systemd.h
+ *
+ * Copyright 2019 Benjamin Berg <bberg redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#ifndef _TERMINAL_SYSTEMD_H
+#define _TERMINAL_SYSTEMD_H
+
+#include <gio/gio.h>
+
+void terminal_start_systemd_scope (const char           *name,
+                                   gint32                pid,
+                                   const char           *description,
+                                   GDBusConnection      *connection,
+                                   GCancellable         *cancellable,
+                                   GAsyncReadyCallback   callback,
+                                   gpointer              user_data);
+
+gboolean terminal_start_systemd_scope_finish (GAsyncResult  *res,
+                                              GError       **error);
+
+#endif /* _GNOME_TERMINAL_H */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]