diff options
author | Christian Persch <chpe@src.gnome.org> | 2020-02-04 18:13:47 +0100 |
---|---|---|
committer | Christian Persch <chpe@src.gnome.org> | 2020-02-04 18:13:47 +0100 |
commit | 1d488966c70f97a86962e0f4a470451bf1cd223d (patch) | |
tree | d000fe8293f9c59c1c470eeaa8268a9991283afe | |
parent | 427eebbf210c0d090abe8f220e4dac7fc1021bb5 (diff) | |
download | vte-1d488966c70f97a86962e0f4a470451bf1cd223d.tar.gz |
systemd: 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.
Fixes: 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
-rw-r--r-- | doc/reference/vte-sections.txt | 2 | ||||
-rw-r--r-- | meson.build | 15 | ||||
-rw-r--r-- | src/app/app.cc | 11 | ||||
-rw-r--r-- | src/meson.build | 10 | ||||
-rw-r--r-- | src/pty.cc | 61 | ||||
-rw-r--r-- | src/systemd.cc | 104 | ||||
-rw-r--r-- | src/systemd.hh | 33 | ||||
-rw-r--r-- | src/vte/vtepty.h | 4 | ||||
-rw-r--r-- | src/vtegtk.cc | 13 | ||||
-rw-r--r-- | src/vtepty.cc | 34 |
10 files changed, 266 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, ] @@ -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..33534b4c 100644 --- a/src/vtegtk.cc +++ b/src/vtegtk.cc @@ -2715,7 +2715,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 */, @@ -2724,6 +2724,7 @@ vte_terminal_spawn_sync(VteTerminal *terminal, { g_return_val_if_fail(VTE_IS_TERMINAL(terminal), FALSE); g_return_val_if_fail(argv != NULL, FALSE); + g_return_val_if_fail((spawn_flags & (VTE_SPAWN_NO_SYSTEMD_SCOPE | VTE_SPAWN_REQUIRE_SYSTEMD_SCOPE)) == 0, FALSE); g_return_val_if_fail(child_setup_data == NULL || child_setup, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); @@ -2736,7 +2737,7 @@ vte_terminal_spawn_sync(VteTerminal *terminal, working_directory, argv, envv, - spawn_flags_, + spawn_flags, child_setup, child_setup_data, &pid, -1 /* no timeout */, @@ -2881,6 +2882,14 @@ spawn_async_cb (GObject *source, * When the operation fails, @callback will be called with a -1 #GPid, * and a non-%NULL #GError containing the error information. * + * Beginning with 0.60, and 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. + * 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. + * * Note that if @terminal has been destroyed before the operation is called, * @callback will be called with a %NULL @terminal; you must not do anything * in the callback besides freeing any resources associated with @user_data, diff --git a/src/vtepty.cc b/src/vtepty.cc index 01d7d3eb..5dfba033 100644 --- a/src/vtepty.cc +++ b/src/vtepty.cc @@ -95,6 +95,32 @@ _vte_pty_get_impl(VtePty* pty) */ /** + * 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,14 @@ async_spawn_run_in_thread(GTask *task, * use a child setup function that unsets the FD_CLOEXEC flag on that file * descriptor. * + * Beginning with 0.60, and 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. + * 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 |