diff --git a/compat.h b/compat.h index aefc0d38..cabdf3ad 100644 --- a/compat.h +++ b/compat.h @@ -425,6 +425,7 @@ void *recallocarray(void *, size_t, size_t, size_t); /* systemd.c */ int systemd_activated(void); int systemd_create_socket(int, char **); +int systemd_move_pid_to_new_cgroup(pid_t, char **); #endif #ifdef HAVE_UTF8PROC diff --git a/compat/systemd.c b/compat/systemd.c index cce42ed4..6f790a32 100644 --- a/compat/systemd.c +++ b/compat/systemd.c @@ -19,7 +19,10 @@ #include #include +#include #include +#include +#include #include @@ -64,3 +67,149 @@ fail: xasprintf(cause, "systemd socket error (%s)", strerror(errno)); return (-1); } + +int +systemd_move_pid_to_new_cgroup(pid_t pid, char **cause) +{ + sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_message *m = NULL, *reply = NULL; + sd_bus *bus = NULL; + char *name, *desc, *slice; + sd_id128_t uuid; + int r; + pid_t parent_pid; + + /* Connect to the session bus. */ + r = sd_bus_default_user(&bus); + if (r < 0) { + xasprintf(cause, "failed to connect to session bus: %s", + strerror(-r)); + goto finish; + } + + /* Start building the method call. */ + r = sd_bus_message_new_method_call(bus, &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (r < 0) { + xasprintf(cause, "failed to create bus message: %s", + strerror(-r)); + goto finish; + } + + /* Generate a unique name for the new scope, to avoid collisions. */ + r = sd_id128_randomize(&uuid); + if (r < 0) { + xasprintf(cause, "failed to generate uuid: %s", strerror(-r)); + goto finish; + } + xasprintf(&name, "tmux-spawn-" SD_ID128_UUID_FORMAT_STR ".scope", + SD_ID128_FORMAT_VAL(uuid)); + r = sd_bus_message_append(m, "s", name); + free(name); + if (r < 0) { + xasprintf(cause, "failed to append to bus message: %s", + strerror(-r)); + goto finish; + } + + /* Mode: fail if there's a queued unit with the same name. */ + r = sd_bus_message_append(m, "s", "fail"); + if (r < 0) { + xasprintf(cause, "failed to append to bus message: %s", + strerror(-r)); + goto finish; + } + + /* Start properties array. */ + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) { + xasprintf(cause, "failed to start properties array: %s", + strerror(-r)); + goto finish; + } + + parent_pid = getpid(); + xasprintf(&desc, "tmux child pane %ld launched by process %ld", + (long)pid, (long)parent_pid); + r = sd_bus_message_append(m, "(sv)", "Description", "s", desc); + free(desc); + if (r < 0) { + xasprintf(cause, "failed to append to properties: %s", + strerror(-r)); + goto finish; + } + + /* + * Inherit the slice from the parent process, or default to + * "app-tmux.slice" if that fails. + */ + r = sd_pid_get_user_slice(parent_pid, &slice); + if (r < 0) { + slice = xstrdup("app-tmux.slice"); + } + r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice); + free(slice); + if (r < 0) { + xasprintf(cause, "failed to append to properties: %s", + strerror(-r)); + goto finish; + } + + /* PIDs to add to the scope: length - 1 array of uint32_t. */ + r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, pid); + if (r < 0) { + xasprintf(cause, "failed to append to properties: %s", + strerror(-r)); + goto finish; + } + + /* Clean up the scope even if it fails. */ + r = sd_bus_message_append(m, "(sv)", "CollectMode", "s", + "inactive-or-failed"); + if (r < 0) { + xasprintf(cause, "failed to append to properties: %s", + strerror(-r)); + goto finish; + } + + /* End properties array. */ + r = sd_bus_message_close_container(m); + if (r < 0) { + xasprintf(cause, "failed to end properties array: %s", + strerror(-r)); + goto finish; + } + + /* aux is currently unused and should be passed an empty array. */ + r = sd_bus_message_append(m, "a(sa(sv))", 0); + if (r < 0) { + xasprintf(cause, "failed to append to bus message: %s", + strerror(-r)); + goto finish; + } + + /* Call the method with a timeout of 1 second = 1e6 us. */ + r = sd_bus_call(bus, m, 1000000, &error, &reply); + if (r < 0) { + if (error.message != NULL) { + /* We have a specific error message from sd-bus. */ + xasprintf(cause, "StartTransientUnit call failed: %s", + error.message); + } else { + xasprintf(cause, "StartTransientUnit call failed: %s", + strerror(-r)); + } + goto finish; + } + +finish: + sd_bus_error_free(&error); + sd_bus_message_unref(m); + sd_bus_message_unref(reply); + sd_bus_unref(bus); + + return (r); +} diff --git a/configure.ac b/configure.ac index 8e846042..4b9d75b3 100644 --- a/configure.ac +++ b/configure.ac @@ -420,6 +420,21 @@ if test x"$enable_systemd" = xyes; then fi fi AM_CONDITIONAL(HAVE_SYSTEMD, [test "x$found_systemd" = xyes]) +AC_ARG_ENABLE( + cgroups, + AS_HELP_STRING(--disable-cgroups, disable adding panes to new cgroups with systemd) +) +if test "x$enable_cgroups" = x; then + # Default to the same as $enable_systemd. + enable_cgroups=$enable_systemd +fi +if test "x$enable_cgroups" = xyes; then + if test "x$found_systemd" = xyes; then + AC_DEFINE(ENABLE_CGROUPS) + else + AC_MSG_ERROR("cgroups requires systemd to be enabled") + fi +fi # Check for b64_ntop. If we have b64_ntop, we assume b64_pton as well. AC_MSG_CHECKING(for b64_ntop) diff --git a/spawn.c b/spawn.c index e63133fe..10604028 100644 --- a/spawn.c +++ b/spawn.c @@ -380,8 +380,20 @@ spawn_pane(struct spawn_context *sc, char **cause) } /* In the parent process, everything is done now. */ - if (new_wp->pid != 0) + if (new_wp->pid != 0) { +#if defined(HAVE_SYSTEMD) && defined(ENABLE_CGROUPS) + /* + * Move the child process into a new cgroup for systemd-oomd + * isolation. + */ + if (systemd_move_pid_to_new_cgroup(new_wp->pid, cause) < 0) { + log_debug("%s: moving pane to new cgroup failed: %s", + __func__, *cause); + free (*cause); + } +#endif goto complete; + } /* * Child process. Change to the working directory or home if that