[vte/wip/systemd: 3/4] lib: Add systemd support



commit c68f43cc7f90c13bf6f3b21af320e259aba13673
Author: Christian Persch <chpe src gnome org>
Date:   Sun Feb 2 23:16:16 2020 +0100

    lib: Add systemd support
    
    Move newly created child processes into their own systemd user scope.
    Apparently this is required so that when the OOM killer catches one
    of gnome-terminal-server's child processes, it doesn't also kill
    gnome-terminal-server itself and thus all and every terminals in it.
    
    https://gitlab.gnome.org/GNOME/gnome-terminal/issues/206
    https://bugzilla.gnome.org/show_bug.cgi?id=744736
    https://bugzilla.redhat.com/show_bug.cgi?id=1796828

 doc/reference/vte-sections.txt |   2 +
 meson.build                    |  15 ++++--
 src/app/app.cc                 |  11 ++++-
 src/meson.build                |  10 ++++
 src/pty.cc                     |  61 ++++++++++++++++++------
 src/systemd.cc                 | 104 +++++++++++++++++++++++++++++++++++++++++
 src/systemd.hh                 |  33 +++++++++++++
 src/vte/vtepty.h               |   4 +-
 src/vtegtk.cc                  |  13 +++++-
 src/vtepty.cc                  |  35 ++++++++++++++
 10 files changed, 267 insertions(+), 21 deletions(-)
---
diff --git a/doc/reference/vte-sections.txt b/doc/reference/vte-sections.txt
index fa8844b3..20067c88 100644
--- a/doc/reference/vte-sections.txt
+++ b/doc/reference/vte-sections.txt
@@ -198,6 +198,8 @@ vte_pty_set_utf8
 
 <SUBSECTION>
 VTE_SPAWN_NO_PARENT_ENVV
+VTE_SPAWN_NO_SYSTEMD_SCOPE
+VTE_SPAWN_REQUIRE_SYSTEMD_SCOPE
 vte_pty_spawn_async
 vte_pty_spawn_finish
 
diff --git a/meson.build b/meson.build
index c3b08772..845acb01 100644
--- a/meson.build
+++ b/meson.build
@@ -37,14 +37,15 @@ gtk3_max_allowed_version  = '3.20'
 gtk4_req_version          = '4.0.0'
 
 fribidi_req_version       = '1.0.0'
-gio_req_version           = '2.44.0'
-glib_req_version          = '2.44.0'
-glib_min_req_version      = '2.44'
-glib_max_allowed_version  = '2.44'
+gio_req_version           = '2.52.0'
+glib_req_version          = '2.52.0'
+glib_min_req_version      = '2.52'
+glib_max_allowed_version  = '2.52'
 gnutls_req_version        = '3.2.7'
 icu_uc_req_version        = '4.8'
 pango_req_version         = '1.22.0'
 pcre2_req_version         = '10.21'
+systemd_req_version       = '202'
 
 # API
 
@@ -435,6 +436,12 @@ else
   icu_dep = dependency('', required: false)
 endif
 
+if host_machine.system() == 'linux'
+  systemd_dep = dependency('libsystemd', version: '>=' + systemd_req_version)
+else
+  systemd_dep = dependency('', required: false)
+endif
+
 # Write config.h
 
 configure_file(
diff --git a/src/app/app.cc b/src/app/app.cc
index 0a66ad00..6257c2f1 100644
--- a/src/app/app.cc
+++ b/src/app/app.cc
@@ -59,7 +59,9 @@ public:
         gboolean no_rewrap{false};
         gboolean no_shaping{false};
         gboolean no_shell{false};
+        gboolean no_systemd_scope{false};
         gboolean object_notifications{false};
+        gboolean require_systemd_scope{false};
         gboolean reverse{false};
         gboolean test_mode{false};
         gboolean version{false};
@@ -403,12 +405,16 @@ public:
                           "Disable Arabic shaping", nullptr },
                         { "no-shell", 'S', 0, G_OPTION_ARG_NONE, &no_shell,
                           "Disable spawning a shell inside the terminal", nullptr },
+                        { "no-systemd-scope", 0, 0, G_OPTION_ARG_NONE, &no_systemd_scope,
+                          "Don't use systemd user scope", nullptr },
                         { "object-notifications", 'N', 0, G_OPTION_ARG_NONE, &object_notifications,
                           "Print VteTerminal object notifications", nullptr },
                         { "output-file", 0, 0, G_OPTION_ARG_FILENAME, &output_filename,
                           "Save terminal contents to file at exit", nullptr },
                         { "reverse", 0, 0, G_OPTION_ARG_NONE, &reverse,
                           "Reverse foreground/background colors", nullptr },
+                        { "require-systemd-scope", 0, 0, G_OPTION_ARG_NONE, &require_systemd_scope,
+                          "Require use of a systemd user scope", nullptr },
                         { "scrollback-lines", 'n', 0, G_OPTION_ARG_INT, &scrollback_lines,
                           "Specify the number of scrollback-lines (-1 for infinite)", nullptr },
                         { "transparent", 'T', 0, G_OPTION_ARG_INT, &transparency_percent,
@@ -1201,12 +1207,15 @@ vteapp_window_launch_argv(VteappWindow* window,
                           char** argv,
                           GError** error)
 {
+        auto const spawn_flags = GSpawnFlags(G_SPAWN_SEARCH_PATH_FROM_ENVP |
+                                             (options.no_systemd_scope ? VTE_SPAWN_NO_SYSTEMD_SCOPE : 0) |
+                                             (options.require_systemd_scope ? 
VTE_SPAWN_REQUIRE_SYSTEMD_SCOPE : 0));
         vte_terminal_spawn_async(window->terminal,
                                  VTE_PTY_DEFAULT,
                                  options.working_directory,
                                  argv,
                                  options.environment,
-                                 G_SPAWN_SEARCH_PATH_FROM_ENVP,
+                                 spawn_flags,
                                  nullptr, nullptr, nullptr, /* child setup, data and destroy */
                                  30 * 1000 /* 30s timeout */,
                                  nullptr /* cancellable */,
diff --git a/src/meson.build b/src/meson.build
index a385716f..4242dd61 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -79,6 +79,11 @@ regex_sources = files(
   'regex.hh'
 )
 
+systemd_sources = files(
+  'systemd.cc',
+  'systemd.hh',
+)
+
 utf8_sources = files(
   'utf8.cc',
   'utf8.hh',
@@ -142,6 +147,10 @@ if get_option('icu')
   libvte_common_sources += icu_sources
 endif
 
+if host_machine.system() == 'linux'
+  libvte_common_sources += systemd_sources
+endif
+
 libvte_common_doc_sources = files(
   # These file contain gtk-doc comments to be extracted for docs and gir
   'pty.cc',
@@ -188,6 +197,7 @@ libvte_common_deps = libvte_common_public_deps + [
   pcre2_dep,
   libm_dep,
   pthreads_dep,
+  systemd_dep,
   zlib_dep,
 ]
 
diff --git a/src/pty.cc b/src/pty.cc
index c17600c0..1333c5d2 100644
--- a/src/pty.cc
+++ b/src/pty.cc
@@ -74,6 +74,10 @@
 
 #include "glib-glue.hh"
 
+#ifdef __linux__
+#include "systemd.hh"
+#endif
+
 /* NSIG isn't in POSIX, so if it doesn't exist use this here. See bug #759196 */
 #ifndef NSIG
 #define NSIG (8 * sizeof(sigset_t))
@@ -344,20 +348,19 @@ pty_child_setup_cb(void* data)
 }
 
 /*
- * __vte_pty_spawn:
- * @pty: a #VtePty
- * @directory: the name of a directory the command should start in, or %NULL
+ * Pty::spawn:
+ * @directory: the name of a directory the command should start in, or %nullptr
  *   to use the cwd
  * @argv: child's argument vector
  * @envv: a list of environment variables to be added to the environment before
- *   starting the process, or %NULL
+ *   starting the process, or %nullptr
  * @spawn_flags: flags from #GSpawnFlags
  * @child_setup: function to run in the child just before exec()
  * @child_setup_data: user data for @child_setup
- * @child_pid: a location to store the child PID, or %NULL
- * @timeout: a timeout value in ms, or %NULL
- * @cancellable: a #GCancellable, or %NULL
- * @error: return location for a #GError, or %NULL
+ * @child_pid: a location to store the child PID, or %nullptr
+ * @timeout: a timeout value in ms, or %nullptr
+ * @cancellable: a #GCancellable, or %nullptr
+ * @error: return location for a #GError, or %nullptr
  *
  * Uses g_spawn_async() to spawn the command in @argv. The child's environment will
  * be the parent environment with the variables in @envv set afterwards.
@@ -393,6 +396,14 @@ Pty::spawn(char const* directory,
         int i;
         GPollFD pollfd;
 
+#ifndef __linux__
+        if (spawn_flags & VTE_SPAWN_REQUIRE_SYSTEMD_SCOPE) {
+                g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                            "systemd not available");
+                return false;
+        }
+#endif
+
         if (cancellable && !g_cancellable_make_pollfd(cancellable, &pollfd)) {
                 vte::util::restore_errno errsv;
                 g_set_error(error,
@@ -432,13 +443,14 @@ Pty::spawn(char const* directory,
        m_extra_child_setup.func = child_setup_func;
        m_extra_child_setup.data = child_setup_data;
 
+        auto pid = pid_t{-1};
         auto err = vte::glib::Error{};
         ret = vte_spawn_async_with_pipes_cancellable(directory,
                                                      argv, envp2,
                                                      (GSpawnFlags)spawn_flags,
                                                      (GSpawnChildSetupFunc)pty_child_setup_cb,
                                                      this,
-                                                     child_pid,
+                                                     &pid,
                                                      nullptr, nullptr, nullptr,
                                                      timeout,
                                                      cancellable ? &pollfd : nullptr,
@@ -453,7 +465,7 @@ Pty::spawn(char const* directory,
                                                              (GSpawnFlags)spawn_flags,
                                                              (GSpawnChildSetupFunc)pty_child_setup_cb,
                                                              this,
-                                                             child_pid,
+                                                             &pid,
                                                              nullptr, nullptr, nullptr,
                                                              timeout,
                                                              cancellable ? &pollfd : nullptr,
@@ -468,10 +480,33 @@ Pty::spawn(char const* directory,
         if (cancellable)
                 g_cancellable_release_fd(cancellable);
 
-        if (ret)
-                return true;
+#ifdef __linux__
+        if (ret &&
+            !(spawn_flags & VTE_SPAWN_NO_SYSTEMD_SCOPE) &&
+            !vte::systemd::create_scope_for_pid_sync(pid,
+                                                     timeout, // FIXME: recalc timeout
+                                                     cancellable,
+                                                     err)) {
+                if (spawn_flags & VTE_SPAWN_REQUIRE_SYSTEMD_SCOPE) {
+                        auto pgrp = getpgid(pid);
+                        if (pgrp != -1) {
+                                kill(-pgrp, SIGHUP);
+                        }
+
+                        kill(pid, SIGHUP);
+
+                        ret = false;
+                } else {
+                        err.reset();
+                }
+        }
+#endif // __linux__
+
+        if (!ret)
+                return err.propagate(error);
 
-        return err.propagate(error);
+        *child_pid = pid;
+        return true;
 }
 
 /*
diff --git a/src/systemd.cc b/src/systemd.cc
new file mode 100644
index 00000000..4ba40c6a
--- /dev/null
+++ b/src/systemd.cc
@@ -0,0 +1,104 @@
+/*
+ * Copyright © 2020 Christian Persch
+ *
+ * This library 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 library 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 General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "systemd.hh"
+
+#include <memory>
+
+#include <systemd/sd-login.h>
+
+#include "glib-glue.hh"
+#include "refptr.hh"
+
+namespace vte::systemd {
+
+bool
+create_scope_for_pid_sync(pid_t pid,
+                          int timeout,
+                          GCancellable* cancellable,
+                          GError** error)
+{
+        {
+                char* unit = nullptr;
+                if (auto r = sd_pid_get_user_unit(pid, &unit) < 0) {
+                        g_set_error(error, G_IO_ERROR, g_io_error_from_errno(-r),
+                                    "Failed sd_pid_get_user_unit(%d): %s",
+                                    pid,
+                                    g_strerror(-r));
+                        return false;
+                }
+                free(unit);
+        }
+
+        auto bus = vte::glib::take_ref(g_bus_get_sync(G_BUS_TYPE_SESSION, cancellable, error));
+        if (!bus)
+                return false;
+
+        auto uuid = vte::glib::take_string(g_uuid_string_random());
+        auto scope = vte::glib::take_string(g_strdup_printf("vte-spawn-%s.scope", uuid.get()));
+        auto prgname = vte::glib::take_string(g_utf8_make_valid(g_get_prgname(), -1));
+        auto description = vte::glib::take_string(g_strdup_printf("VTE child process %d launched by %s 
process %d", pid, prgname.get(), getpid()));
+
+        auto builder_stack = GVariantBuilder{};
+        auto builder = &builder_stack;
+        g_variant_builder_init(builder, G_VARIANT_TYPE("(ssa(sv)a(sa(sv)))"));
+
+        g_variant_builder_add(builder, "s", scope.get()); // unit name
+        g_variant_builder_add(builder, "s", "fail");      // failure mode
+
+        // Unit properties
+        g_variant_builder_open(builder, G_VARIANT_TYPE("a(sv)"));
+
+        g_variant_builder_add(builder, "(sv)", "CollectMode", g_variant_new_string("inactive-or-failed"));
+        g_variant_builder_add(builder, "(sv)", "Description", g_variant_new_string(description.get()));
+
+        g_variant_builder_open(builder, G_VARIANT_TYPE("(sv)"));
+        g_variant_builder_add(builder, "s", "PIDs");
+        g_variant_builder_open(builder, G_VARIANT_TYPE("v"));
+        g_variant_builder_open(builder, G_VARIANT_TYPE("au"));
+        g_variant_builder_add(builder, "u", unsigned(pid));
+        g_variant_builder_close(builder); // au
+        g_variant_builder_close(builder); // v
+        g_variant_builder_close(builder); // (sv)
+
+        g_variant_builder_close(builder); // a(sv)
+
+        // No auxiliary units
+        g_variant_builder_open(builder, G_VARIANT_TYPE("a(sa(sv))"));
+        g_variant_builder_close(builder);
+
+        // Create transient scope
+        auto reply = std::unique_ptr<GVariant, decltype(&g_variant_unref)>
+                {g_dbus_connection_call_sync(bus.get(),
+                                             "org.freedesktop.systemd1",
+                                             "/org/freedesktop/systemd1",
+                                             "org.freedesktop.systemd1.Manager",
+                                             "StartTransientUnit",
+                                             g_variant_builder_end(builder), // parameters
+                                             G_VARIANT_TYPE("(o)"), // reply type,
+                                             GDBusCallFlags{G_DBUS_CALL_FLAGS_NO_AUTO_START},
+                                             timeout, // in ms
+                                             cancellable,
+                                             error),
+                 &g_variant_unref};
+
+        return bool(reply);
+}
+
+} // namespace vte::systemd
diff --git a/src/systemd.hh b/src/systemd.hh
new file mode 100644
index 00000000..0857fd0e
--- /dev/null
+++ b/src/systemd.hh
@@ -0,0 +1,33 @@
+/*
+ * Copyright © 2020 Christian Persch
+ *
+ * This library 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 library 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 General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <glib.h>
+#include <gio/gio.h>
+
+namespace vte::systemd {
+
+bool create_scope_for_pid_sync(pid_t pid,
+                               int timeout,
+                               GCancellable* cancellable,
+                               GError** error);
+
+} // namespace vte::systemd
diff --git a/src/vte/vtepty.h b/src/vte/vtepty.h
index 3b7aac9e..06822740 100644
--- a/src/vte/vtepty.h
+++ b/src/vte/vtepty.h
@@ -30,7 +30,9 @@
 
 G_BEGIN_DECLS
 
-#define VTE_SPAWN_NO_PARENT_ENVV (1 << 25)
+#define VTE_SPAWN_NO_PARENT_ENVV        (1 << 25)
+#define VTE_SPAWN_NO_SYSTEMD_SCOPE      (1 << 26)
+#define VTE_SPAWN_REQUIRE_SYSTEMD_SCOPE (1 << 27)
 
 _VTE_PUBLIC
 GQuark vte_pty_error_quark (void);
diff --git a/src/vtegtk.cc b/src/vtegtk.cc
index 35eee92f..080095ef 100644
--- a/src/vtegtk.cc
+++ b/src/vtegtk.cc
@@ -2705,6 +2705,15 @@ vte_terminal_watch_child (VteTerminal *terminal,
  * The caller should also make sure that symlinks were preserved while constructing the value of 
@working_directory,
  * e.g. by using vte_terminal_get_current_directory_uri(), g_get_current_dir() or get_current_dir_name().
  *
+ * On linux only, and unless %VTE_SPAWN_NO_SYSTEMD_SCOPE is passed in @spawn_flags,
+ * the newly created child process will be moved to its own systemd user scope; and
+ * if %VTE_SPAWN_REQUIRE_SYSTEMD_SCOPE is passed, and creation of the systemd user
+ * scope fails, the whole spawn will fail.
+ *
+ * Note that you can override the options used for the systemd user scope by
+ * providing a systemd override file for 'vte-spawn-.scope' unit. See man:systemd.unit(5)
+ * for further information.
+ *
  * Returns: %TRUE on success, or %FALSE on error with @error filled in
  *
  * Deprecated: 0.48: Use vte_terminal_spawn_async() instead.
@@ -2715,7 +2724,7 @@ vte_terminal_spawn_sync(VteTerminal *terminal,
                         const char *working_directory,
                         char **argv,
                         char **envv,
-                        GSpawnFlags spawn_flags_,
+                        GSpawnFlags spawn_flags,
                         GSpawnChildSetupFunc child_setup,
                         gpointer child_setup_data,
                         GPid *child_pid /* out */,
@@ -2736,7 +2745,7 @@ vte_terminal_spawn_sync(VteTerminal *terminal,
                             working_directory,
                             argv,
                             envv,
-                            spawn_flags_,
+                            spawn_flags,
                             child_setup, child_setup_data,
                             &pid,
                             -1 /* no timeout */,
diff --git a/src/vtepty.cc b/src/vtepty.cc
index 01d7d3eb..88415046 100644
--- a/src/vtepty.cc
+++ b/src/vtepty.cc
@@ -94,6 +94,32 @@ _vte_pty_get_impl(VtePty* pty)
  * to vte_pty_spawn_async() etc. are passed to the child process.
  */
 
+/**
+ * VTE_SPAWN_NO_SYSTEMD_SCOPE:
+ *
+ * Use this as a spawn flag (together with flags from #GSpawnFlags) in
+ * vte_pty_spawn_async().
+ *
+ * Prevents vte_pty_spawn_async() etc. from moving the newly created child
+ * process to a systemd user scope.
+ *
+ * Since: 0.60
+ */
+
+/**
+ * VTE_SPAWN_REQUIRE_SYSTEMD_SCOPE
+ *
+ * Use this as a spawn flag (together with flags from #GSpawnFlags) in
+ * vte_pty_spawn_async().
+ *
+ * Requires vte_pty_spawn_async() etc. to move the newly created child
+ * process to a systemd user scope; if that fails, the whole spawn fails.
+ *
+ * This is supported on Linux only.
+ *
+ * Since: 0.60
+ */
+
 /**
  * vte_pty_child_setup:
  * @pty: a #VtePty
@@ -664,6 +690,15 @@ async_spawn_run_in_thread(GTask *task,
  * use a child setup function that unsets the FD_CLOEXEC flag on that file
  * descriptor.
  *
+ * On linux only, and unless %VTE_SPAWN_NO_SYSTEMD_SCOPE is passed in @spawn_flags,
+ * the newly created child process will be moved to its own systemd user scope; and
+ * if %VTE_SPAWN_REQUIRE_SYSTEMD_SCOPE is passed, and creation of the systemd user
+ * scope fails, the whole spawn will fail.
+ *
+ * Note that you can override the options used for the systemd user scope by
+ * providing a systemd override file for 'vte-spawn-.scope' unit. See man:systemd.unit(5)
+ * for further information.
+ *
  * See vte_pty_new(), g_spawn_async() and vte_terminal_watch_child() for more information.
  *
  * Since: 0.48


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