commit fa169233c072450f7c1fd3c47fd9408e98dc8ed8 Author: upstream Date: Sat Oct 17 03:22:03 2026 +0200 Upstream tmux/tmux state of the touched files before 2b9e5a850c diff --git a/compat/systemd.c b/compat/systemd.c new file mode 100644 index 0000000..4d62f57 --- /dev/null +++ b/compat/systemd.c @@ -0,0 +1,389 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2022 Nicholas Marriott + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "tmux.h" + +#ifndef SD_ID128_UUID_FORMAT_STR +#define SD_ID128_UUID_FORMAT_STR \ + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x" +#endif + +int +systemd_activated(void) +{ + return (sd_listen_fds(0) >= 1); +} + +int +systemd_create_socket(int flags, char **cause) +{ + int fds; + int fd; + struct sockaddr_un sa; + socklen_t addrlen = sizeof sa; + + fds = sd_listen_fds(0); + if (fds > 1) { /* too many file descriptors */ + errno = E2BIG; + goto fail; + } + + if (fds == 1) { /* socket-activated */ + fd = SD_LISTEN_FDS_START; + if (!sd_is_socket_unix(fd, SOCK_STREAM, 1, NULL, 0)) { + errno = EPFNOSUPPORT; + goto fail; + } + if (getsockname(fd, (struct sockaddr *)&sa, &addrlen) == -1) + goto fail; + socket_path = xstrdup(sa.sun_path); + return (fd); + } + + return (server_create_socket(flags, cause)); + +fail: + if (cause != NULL) + xasprintf(cause, "systemd socket error (%s)", strerror(errno)); + return (-1); +} + +struct systemd_job_watch { + const char *path; + int done; +}; + +static int +job_removed_handler(sd_bus_message *m, void *userdata, + __unused sd_bus_error *ret_error) +{ + struct systemd_job_watch *watch = userdata; + const char *path = NULL; + uint32_t id; + int r; + + /* This handler could be called during the sd_bus_call. */ + if (watch->path == NULL) + return 0; + + r = sd_bus_message_read(m, "uo", &id, &path); + if (r < 0) + return (r); + + if (strcmp(path, watch->path) == 0) + watch->done = 1; + + return (0); +} + +static int +systemd_new_scope_message(sd_bus *bus, int oom_policy, sd_bus_message **mp, + char **cause) +{ + char *name, *desc, *slice, *unit; + sd_id128_t uuid; + int r; + pid_t pid, parent_pid; + + /* Start building the method call. */ + r = sd_bus_message_new_method_call(bus, mp, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (r < 0) { + xasprintf(cause, "failed to create bus message: %s", + strerror(-r)); + return (r); + } + + /* 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)); + return (r); + } + xasprintf(&name, "tmux-spawn-" SD_ID128_UUID_FORMAT_STR ".scope", + SD_ID128_FORMAT_VAL(uuid)); + r = sd_bus_message_append(*mp, "s", name); + free(name); + if (r < 0) { + xasprintf(cause, "failed to append to bus message: %s", + strerror(-r)); + return (r); + } + + /* Mode: fail if there's a queued unit with the same name. */ + r = sd_bus_message_append(*mp, "s", "fail"); + if (r < 0) { + xasprintf(cause, "failed to append to bus message: %s", + strerror(-r)); + return (r); + } + + /* Start properties array. */ + r = sd_bus_message_open_container(*mp, 'a', "(sv)"); + if (r < 0) { + xasprintf(cause, "failed to start properties array: %s", + strerror(-r)); + return (r); + } + + pid = getpid(); + parent_pid = getppid(); + xasprintf(&desc, "tmux child pane %ld launched by process %ld", + (long)pid, (long)parent_pid); + r = sd_bus_message_append(*mp, "(sv)", "Description", "s", desc); + free(desc); + if (r < 0) { + xasprintf(cause, "failed to append to properties: %s", + strerror(-r)); + return (r); + } + + /* + * Make sure that the session shells are terminated with SIGHUP since + * bash and friends tend to ignore SIGTERM. + */ + r = sd_bus_message_append(*mp, "(sv)", "SendSIGHUP", "b", 1); + if (r < 0) { + xasprintf(cause, "failed to append to properties: %s", + strerror(-r)); + return (r); + } + + /* + * 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(*mp, "(sv)", "Slice", "s", slice); + free(slice); + if (r < 0) { + xasprintf(cause, "failed to append to properties: %s", + strerror(-r)); + return (r); + } + + /* PIDs to add to the scope: length - 1 array of uint32_t. */ + r = sd_bus_message_append(*mp, "(sv)", "PIDs", "au", 1, pid); + if (r < 0) { + xasprintf(cause, "failed to append to properties: %s", + strerror(-r)); + return (r); + } + + /* + * A pane is an interactive session like a login, so one process in + * it being killed by the OOM killer must not take the shell and every + * other process in the pane with it. systemd defaults scopes to + * OOMPolicy=stop, which does exactly that; logind uses continue for + * session scopes. + */ + if (oom_policy) { + r = sd_bus_message_append(*mp, "(sv)", "OOMPolicy", "s", + "continue"); + if (r < 0) { + xasprintf(cause, "failed to append to properties: %s", + strerror(-r)); + return (r); + } + } + + /* Clean up the scope even if it fails. */ + r = sd_bus_message_append(*mp, "(sv)", "CollectMode", "s", + "inactive-or-failed"); + if (r < 0) { + xasprintf(cause, "failed to append to properties: %s", + strerror(-r)); + return (r); + } + + /* + * Try locating systemd unit that started the server, and mark pane units + * as dependent on it. Use "Before" to make sure systemd will not try to + * kill them first. + */ + if (sd_pid_get_user_unit(parent_pid, &unit) == 0 || + sd_pid_get_unit(parent_pid, &unit) == 0) { + r = sd_bus_message_append(*mp, "(sv)", "Before", "as", 1, + unit); + if (r >= 0) { + r = sd_bus_message_append(*mp, "(sv)", "PartOf", "as", + 1, unit); + } + free(unit); + if (r < 0) { + xasprintf(cause, "failed to append to properties: %s", + strerror(-r)); + return (r); + } + } + + /* End properties array. */ + r = sd_bus_message_close_container(*mp); + if (r < 0) { + xasprintf(cause, "failed to end properties array: %s", + strerror(-r)); + return (r); + } + + /* aux is currently unused and should be passed an empty array. */ + r = sd_bus_message_append(*mp, "a(sa(sv))", 0); + if (r < 0) { + xasprintf(cause, "failed to append to bus message: %s", + strerror(-r)); + return (r); + } + + return (0); +} + +int +systemd_move_to_new_cgroup(char **cause) +{ + sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_message *m = NULL, *reply = NULL; + sd_bus *bus = NULL; + sd_bus_slot *slot = NULL; + int r; + uint64_t elapsed_usec; + struct timeval start, now; + struct systemd_job_watch watch = {}; + + gettimeofday(&start, NULL); + + /* 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 watching for JobRemoved events. */ + r = sd_bus_match_signal(bus, &slot, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "JobRemoved", + job_removed_handler, + &watch); + if (r < 0) { + xasprintf(cause, "failed to create match signal: %s", + strerror(-r)); + goto finish; + } + + /* + * Build and send the request. systemd before 253 rejects OOMPolicy on a + * scope as an unknown property, so try once more without it. + */ + r = systemd_new_scope_message(bus, 1, &m, cause); + if (r < 0) + goto finish; + r = sd_bus_call(bus, m, 1000000, &error, &reply); + if (r < 0 && sd_bus_error_has_name(&error, + SD_BUS_ERROR_PROPERTY_READ_ONLY)) { + sd_bus_error_free(&error); + m = sd_bus_message_unref(m); + r = systemd_new_scope_message(bus, 0, &m, cause); + if (r < 0) + goto finish; + 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; + } + + /* Get the job (object path) from the reply */ + r = sd_bus_message_read(reply, "o", &watch.path); + if (r < 0) { + xasprintf(cause, "failed to parse method reply: %s", + strerror(-r)); + goto finish; + } + + while (!watch.done) { + /* Process events including callbacks. */ + r = sd_bus_process(bus, NULL); + if (r < 0) { + xasprintf(cause, + "failed waiting for cgroup allocation: %s", + strerror(-r)); + goto finish; + } + + /* + * A positive return means we handled an event and should keep + * processing; zero indicates no events available, so wait. + */ + if (r > 0) + continue; + + gettimeofday(&now, NULL); + elapsed_usec = (now.tv_sec - start.tv_sec) * 1000000 + + now.tv_usec - start.tv_usec; + + if (elapsed_usec >= 1000000) { + xasprintf(cause, + "timeout waiting for cgroup allocation"); + goto finish; + } + + r = sd_bus_wait(bus, 1000000 - elapsed_usec); + if (r < 0) { + xasprintf(cause, + "failed waiting for cgroup allocation: %s", + strerror(-r)); + goto finish; + } + } + +finish: + sd_bus_error_free(&error); + sd_bus_message_unref(m); + sd_bus_message_unref(reply); + sd_bus_slot_unref(slot); + sd_bus_unref(bus); + + return (r); +}