]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Introduce systemd-pty-forward 35761/head
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 26 Dec 2024 21:58:33 +0000 (22:58 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 3 Jan 2025 16:07:33 +0000 (17:07 +0100)
This allows running a command with tinted terminal background.

man/rules/meson.build
man/systemd-pty-forward.xml [new file with mode: 0644]
meson.build
src/ptyfwd/meson.build [new file with mode: 0644]
src/ptyfwd/ptyfwd-tool.c [new file with mode: 0644]
test/units/TEST-74-AUX-UTILS.pty-forward.sh [new file with mode: 0755]

index df4af1e543a835c36080ee9cdbab532d87d5d03e..d281842396435423cd2f449161c2e2b5401337f7 100644 (file)
@@ -1052,6 +1052,7 @@ manpages = [
    'systemd-shutdown'],
   ''],
  ['systemd-pstore.service', '8', ['systemd-pstore'], 'ENABLE_PSTORE'],
+ ['systemd-pty-forward', '1', [], ''],
  ['systemd-quotacheck.service',
   '8',
   ['systemd-quotacheck'],
diff --git a/man/systemd-pty-forward.xml b/man/systemd-pty-forward.xml
new file mode 100644 (file)
index 0000000..84e73fc
--- /dev/null
@@ -0,0 +1,81 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+  "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
+
+<refentry id="systemd-pty-forward"
+    xmlns:xi="http://www.w3.org/2001/XInclude">
+  <refentryinfo>
+    <title>systemd-pty-forward</title>
+    <productname>systemd</productname>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>systemd-pty-forward</refentrytitle>
+    <manvolnum>1</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>systemd-pty-forward</refname>
+    <refpurpose>Run a command with a custom terminal background color or title</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>systemd-pty-forward</command>
+      <arg choice="opt" rep="repeat">OPTIONS</arg>
+      <arg choice="req">COMMAND</arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>Description</title>
+
+    <para><command>systemd-pty-forward</command> can be used to run a command with a custom terminal
+    background color or title.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>Options</title>
+    <para>The following options are understood:</para>
+
+    <variablelist>
+      <varlistentry>
+        <term><option>--background=<replaceable>COLOR</replaceable></option></term>
+
+        <listitem><para>Change the terminal background color to the specified ANSI color as long as the
+        command runs. The color specified should be an ANSI X3.64 SGR background color, i.e. strings such as
+        <literal>40</literal>, <literal>41</literal>, …, <literal>47</literal>, <literal>48;2;…</literal>,
+        <literal>48;5;…</literal>. See <ulink
+        url="https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters">ANSI
+        Escape Code (Wikipedia)</ulink> for details.</para>
+
+        <para>Example: <literal>--background=44</literal> for a blue background.</para>
+
+        <xi:include href="version-info.xml" xpointer="v258"/>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--title=<replaceable>TITLE</replaceable></option></term>
+
+        <listitem><para>Change the terminal title to the specified string as long as the command runs.</para>
+
+        <xi:include href="version-info.xml" xpointer="v258"/>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--quiet</option></term>
+        <term><option>-q</option></term>
+
+        <listitem><para>Suppresses additional informational output while running.</para>
+
+        <xi:include href="version-info.xml" xpointer="v258"/></listitem>
+      </varlistentry>
+
+      <xi:include href="standard-options.xml" xpointer="help"/>
+      <xi:include href="standard-options.xml" xpointer="version"/>
+    </variablelist>
+  </refsect1>
+</refentry>
index 538e776ab48cd06fdc8e12cd3cdf64d561307e37..933133e6b11dc9702f62a34a8c5f0b6e9215212f 100644 (file)
@@ -2402,6 +2402,7 @@ subdir('src/pcrextend')
 subdir('src/pcrlock')
 subdir('src/portable')
 subdir('src/pstore')
+subdir('src/ptyfwd')
 subdir('src/quotacheck')
 subdir('src/random-seed')
 subdir('src/rc-local-generator')
diff --git a/src/ptyfwd/meson.build b/src/ptyfwd/meson.build
new file mode 100644 (file)
index 0000000..f615285
--- /dev/null
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+executables += [
+        executable_template + {
+                'name' : 'systemd-pty-forward',
+                'public' : true,
+                'sources' : files('ptyfwd-tool.c'),
+        },
+]
diff --git a/src/ptyfwd/ptyfwd-tool.c b/src/ptyfwd/ptyfwd-tool.c
new file mode 100644 (file)
index 0000000..faa4071
--- /dev/null
@@ -0,0 +1,214 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <getopt.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "build.h"
+#include "event-util.h"
+#include "fd-util.h"
+#include "main-func.h"
+#include "pretty-print.h"
+#include "ptyfwd.h"
+#include "strv.h"
+
+static bool arg_quiet = false;
+static char *arg_background = NULL;
+static char *arg_title = NULL;
+
+STATIC_DESTRUCTOR_REGISTER(arg_background, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_title, freep);
+
+static int help(void) {
+        _cleanup_free_ char *link = NULL;
+        int r;
+
+        r = terminal_urlify_man("systemd-pty-forward", "1", &link);
+        if (r < 0)
+                return log_oom();
+
+        printf("%1$s  [OPTIONS...] COMMAND ...\n"
+               "\n%5$sRun command with a custom terminal background color or title.%6$s\n"
+               "\n%3$sOptions:%4$s\n"
+               "  -h --help              Show this help\n"
+               "     --version           Print version\n"
+               "  -q --quiet             Suppress information messages during runtime\n"
+               "     --background=COLOR  Set ANSI color for background\n"
+               "     --title=TITLE       Set terminal title\n"
+               "\nSee the %2$s for details.\n",
+               program_invocation_short_name,
+               link,
+               ansi_underline(),
+               ansi_normal(),
+               ansi_highlight(),
+               ansi_normal());
+
+        return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+        enum {
+                ARG_VERSION = 0x100,
+                ARG_BACKGROUND,
+                ARG_TITLE,
+        };
+
+        static const struct option options[] = {
+                { "help",               no_argument,       NULL, 'h'                    },
+                { "version",            no_argument,       NULL, ARG_VERSION            },
+                { "quiet",              no_argument,       NULL, 'q'                    },
+                { "background",         required_argument, NULL, ARG_BACKGROUND         },
+                { "title",              required_argument, NULL, ARG_TITLE              },
+                {}
+        };
+
+        int c, r;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        optind = 0;
+        while ((c = getopt_long(argc, argv, "+hq", options, NULL)) >= 0)
+                switch (c) {
+
+                case 'h':
+                        return help();
+
+                case ARG_VERSION:
+                        return version();
+
+                case 'q':
+                        arg_quiet = true;
+                        break;
+
+                case ARG_BACKGROUND:
+                        r = free_and_strdup_warn(&arg_background, optarg);
+                        if (r < 0)
+                                return r;
+                        break;
+
+                case ARG_TITLE:
+                        r = free_and_strdup_warn(&arg_title, optarg);
+                        if (r < 0)
+                                return r;
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        assert_not_reached();
+                }
+
+        return 1;
+}
+
+static int pty_forward_handler(PTYForward *f, int rcode, void *userdata) {
+        sd_event *e = ASSERT_PTR(userdata);
+
+        assert(f);
+
+        if (rcode == -ECANCELED) {
+                log_debug_errno(rcode, "PTY forwarder disconnected.");
+                return sd_event_exit(e, EXIT_SUCCESS);
+        } else if (rcode < 0) {
+                (void) sd_event_exit(e, EXIT_FAILURE);
+                return log_error_errno(rcode, "Error on PTY forwarding logic: %m");
+        }
+
+        return 0;
+}
+
+static int helper_on_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
+        /* Add 128 to signal exit statuses to mimick shells. */
+        return sd_event_exit(sd_event_source_get_event(s), si->si_status + (si->si_code == CLD_EXITED ? 0 : 128));
+}
+
+static int run(int argc, char *argv[]) {
+        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
+        _cleanup_close_ int pty_fd = -EBADF, peer_fd = -EBADF;
+        _cleanup_(pty_forward_freep) PTYForward *forward = NULL;
+        _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
+        _cleanup_(sd_event_source_unrefp) sd_event_source *exit_source = NULL;
+        int r;
+
+        log_setup();
+
+        assert_se(sigprocmask_many(SIG_BLOCK, /*ret_old_mask=*/ NULL, SIGCHLD) >= 0);
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                return r;
+
+        _cleanup_strv_free_ char **l = strv_copy(argv + optind);
+        if (!l)
+                return log_oom();
+
+        r = sd_event_default(&event);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get event loop: %m");
+
+        (void) sd_event_set_signal_exit(event, true);
+
+        pty_fd = openpt_allocate(O_RDWR|O_NOCTTY|O_NONBLOCK|O_CLOEXEC, /*ret_peer=*/ NULL);
+        if (pty_fd < 0)
+                return log_error_errno(pty_fd, "Failed to acquire pseudo tty: %m");
+
+        peer_fd = pty_open_peer(pty_fd, O_RDWR|O_NOCTTY|O_CLOEXEC);
+        if (peer_fd < 0)
+                return log_error_errno(peer_fd, "Failed to open pty peer: %m");
+
+        if (!arg_quiet)
+                log_info("Press ^] three times within 1s to disconnect TTY.");
+
+        r = pty_forward_new(event, pty_fd, /*flags=*/ 0, &forward);
+        if (r < 0)
+                return log_error_errno(r, "Failed to create PTY forwarder: %m");
+
+        if (!isempty(arg_background)) {
+                r = pty_forward_set_background_color(forward, arg_background);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to set background color: %m");
+        }
+
+        if (shall_set_terminal_title() && !isempty(arg_title)) {
+                r = pty_forward_set_title(forward, arg_title);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to set title: %m");
+        }
+
+        pty_forward_set_handler(forward, pty_forward_handler, &event);
+
+        r = pidref_safe_fork_full(
+                        "(sd-ptyfwd)",
+                        (int[]) { peer_fd, peer_fd, peer_fd },
+                        /* except_fds= */ NULL,
+                        /* n_except_fds= */ 0,
+                        /* flags= */ FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_REARRANGE_STDIO,
+                        &pidref);
+        if (r < 0)
+                return log_error_errno(r, "Failed to fork child: %m");
+        if (r == 0) {
+                r = terminal_new_session();
+                if (r < 0)
+                        return log_error_errno(r, "Failed to create new session: %m");
+
+                (void) execvp(l[0], l);
+                log_error_errno(errno, "Failed to execute %s: %m", l[0]);
+                _exit(EXIT_FAILURE);
+        }
+
+        peer_fd = safe_close(peer_fd);
+
+        r = event_add_child_pidref(event, &exit_source, &pidref, WEXITED, helper_on_exit, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add child event source: %m");
+
+        r = sd_event_source_set_child_process_own(exit_source, true);
+        if (r < 0)
+                return log_error_errno(r, "Failed to take ownership of child process: %m");
+
+        return sd_event_loop(event);
+}
+
+DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
diff --git a/test/units/TEST-74-AUX-UTILS.pty-forward.sh b/test/units/TEST-74-AUX-UTILS.pty-forward.sh
new file mode 100755 (executable)
index 0000000..9b9b14a
--- /dev/null
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+set -eux
+set -o pipefail
+
+systemd-pty-forward --background 41 --title test echo foobar