]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
run: add new --pipe option for including "systemd-run" commands in shell pipelines
authorLennart Poettering <lennart@poettering.net>
Fri, 8 Sep 2017 13:38:40 +0000 (15:38 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 12 Sep 2017 14:28:12 +0000 (16:28 +0200)
In this mode, we'll directly connect stdin/stdout/stderr of the invoked
service with whatever systemd-run itself is invoked on. This allows
inclusion of "systemd-run" commands in shell pipelines, as unlike
"--pty" this means EOF of stdin/stdout/stderr are propagated
independently.

If --pty and --pipe are combined systemd-run will automatically pick the
right choice for the context it is invoked in, i.e. --pty when invoked
on a TTY, and --pipe otherwise.

TODO
man/systemd-run.xml
src/run/run.c

diff --git a/TODO b/TODO
index 4e5a28d3f1f8ac92f712a93467a5febb59736e24..d83627974ed509a87aa79904fb74a1fa68c981bc 100644 (file)
--- a/TODO
+++ b/TODO
@@ -27,9 +27,6 @@ Features:
 * dissect: when we discover squashfs, don't claim we had a "writable" partition
   in systemd-dissect
 
-* systemd-run should have a way how to connect a pair of pipes to
-  stdout/stderr/stdin of the invoked service
-
 * set LockPersonality= on all our services
 
 * Add AddUser= setting to unit files, similar to DynamicUser=1 which however
index 5e44b1523ddd0ae4643124e027791e5c96635876..822be3511c59f8209bc7ca6f06918192b1901d16 100644 (file)
         <term><option>--pty</option></term>
         <term><option>-t</option></term>
 
-        <listitem><para>When invoking the command, the transient service connects its standard input and output to the
-        terminal <command>systemd-run</command> is invoked on, via a pseudo TTY device. This allows running binaries
-        that expect interactive user input as services, such as interactive command shells.</para>
+        <listitem><para>When invoking the command, the transient service connects its standard input, output and error
+        to the terminal <command>systemd-run</command> is invoked on, via a pseudo TTY device. This allows running
+        binaries that expect interactive user input/output as services, such as interactive command shells.</para>
 
         <para>Note that
         <citerefentry><refentrytitle>machinectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>'s
         <command>shell</command> command is usually a better alternative for requesting a new, interactive login
-        session on the local host or a local container.</para></listitem>
+        session on the local host or a local container.</para>
+
+        <para>See below for details on how this switch combines with <option>--pipe</option>.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--pipe</option></term>
+        <term><option>-P</option></term>
+
+        <listitem><para>If specified, standard input, output, and error of the transient service are inherited from the
+        <command>systemd-run</command> command itself. Use this option in order to use <command>systemd-run</command>
+        within shell pipelines. Note that this mode is not suitable for interactive command shells and similar, as the
+        service process will not be made TTY controller when invoked on a terminal. Use <option>--pty</option> instead
+        in that case.</para>
+
+        <para>When both <option>--pipe</option> and <option>--pty</option> are used in combination the more appropriate
+        option is automatically determined and used. Specifically, when invoked with standard input, output and error
+        connected to a TTY <option>--pty</option> is used, and otherwise <option>--pipe</option>.</para></listitem>
       </varlistentry>
 
       <varlistentry>
index 86e304091832e2f4ff068e3d7fc6310cbbddc821..2e24f46b175e29af1abafc1e0341bd21dac9b727 100644 (file)
@@ -61,7 +61,12 @@ static int arg_nice = 0;
 static bool arg_nice_set = false;
 static char **arg_environment = NULL;
 static char **arg_property = NULL;
-static bool arg_pty = false;
+static enum {
+        ARG_STDIO_NONE,      /* The default, as it is for normal services, stdin connected to /dev/null, and stdout+stderr to the journal */
+        ARG_STDIO_PTY,       /* Interactive behaviour, requested by --pty: we allocate a pty and connect it to the TTY we are invoked from */
+        ARG_STDIO_DIRECT,    /* Directly pass our stdin/stdout/stderr to the activated service, useful for usage in shell pipelines, requested by --pipe */
+        ARG_STDIO_AUTO,      /* If --pipe and --pty are used together we use --pty when invoked on a TTY, and --pipe otherwise */
+} arg_stdio = ARG_STDIO_NONE;
 static usec_t arg_on_active = 0;
 static usec_t arg_on_boot = 0;
 static usec_t arg_on_startup = 0;
@@ -106,7 +111,9 @@ static void help(void) {
                "     --gid=GROUP                  Run as system group\n"
                "     --nice=NICE                  Nice level\n"
                "  -E --setenv=NAME=VALUE          Set environment\n"
-               "  -t --pty                        Run service on pseudo tty\n"
+               "  -t --pty                        Run service on pseudo TTY as STDIN/STDOUT/\n"
+               "                                  STDERR\n"
+               "  -P --pipe                       Pass STDIN/STDOUT/STDERR directly to service\n"
                "  -q --quiet                      Suppress information messages during runtime\n\n"
                "Timer options:\n"
                "     --on-active=SECONDS          Run after SECONDS delay\n"
@@ -170,8 +177,9 @@ static int parse_argv(int argc, char *argv[]) {
                 { "nice",              required_argument, NULL, ARG_NICE             },
                 { "setenv",            required_argument, NULL, 'E'                  },
                 { "property",          required_argument, NULL, 'p'                  },
-                { "tty",               no_argument,       NULL, 't'                  }, /* deprecated */
+                { "tty",               no_argument,       NULL, 't'                  }, /* deprecated alias */
                 { "pty",               no_argument,       NULL, 't'                  },
+                { "pipe",              no_argument,       NULL, 'P'                  },
                 { "quiet",             no_argument,       NULL, 'q'                  },
                 { "on-active",         required_argument, NULL, ARG_ON_ACTIVE        },
                 { "on-boot",           required_argument, NULL, ARG_ON_BOOT          },
@@ -190,7 +198,7 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
-        while ((c = getopt_long(argc, argv, "+hrH:M:E:p:tq", options, NULL)) >= 0)
+        while ((c = getopt_long(argc, argv, "+hrH:M:E:p:tPq", options, NULL)) >= 0)
 
                 switch (c) {
 
@@ -279,8 +287,18 @@ static int parse_argv(int argc, char *argv[]) {
 
                         break;
 
-                case 't':
-                        arg_pty = true;
+                case 't': /* --pty */
+                        if (IN_SET(arg_stdio, ARG_STDIO_DIRECT, ARG_STDIO_AUTO)) /* if --pipe is already used, upgrade to auto mode */
+                                arg_stdio = ARG_STDIO_AUTO;
+                        else
+                                arg_stdio = ARG_STDIO_PTY;
+                        break;
+
+                case 'P': /* --pipe */
+                        if (IN_SET(arg_stdio, ARG_STDIO_PTY, ARG_STDIO_AUTO)) /* If --pty is already used, upgrade to auto mode */
+                                arg_stdio = ARG_STDIO_AUTO;
+                        else
+                                arg_stdio = ARG_STDIO_DIRECT;
                         break;
 
                 case 'q':
@@ -373,6 +391,16 @@ static int parse_argv(int argc, char *argv[]) {
                         assert_not_reached("Unhandled option");
                 }
 
+
+        if (arg_stdio == ARG_STDIO_AUTO) {
+                /* If we both --pty and --pipe are specified we'll automatically pick --pty if we are connected fully
+                 * to a TTY and pick direct fd passing otherwise. This way, we automatically adapt to usage in a shell
+                 * pipeline, but we are neatly interactive with tty-level isolation otherwise. */
+                arg_stdio = isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) && isatty(STDERR_FILENO) ?
+                        ARG_STDIO_PTY :
+                        ARG_STDIO_DIRECT;
+        }
+
         if ((optind >= argc) && (!arg_unit || !with_timer())) {
                 log_error("Command line to execute required.");
                 return -EINVAL;
@@ -393,18 +421,18 @@ static int parse_argv(int argc, char *argv[]) {
                 return -EINVAL;
         }
 
-        if (arg_pty && (with_timer() || arg_scope)) {
-                log_error("--pty is not compatible in timer or --scope mode.");
+        if (arg_stdio != ARG_STDIO_NONE && (with_timer() || arg_scope)) {
+                log_error("--pty/--pipe is not compatible in timer or --scope mode.");
                 return -EINVAL;
         }
 
-        if (arg_pty && arg_transport == BUS_TRANSPORT_REMOTE) {
-                log_error("--pty is only supported when connecting to the local system or containers.");
+        if (arg_stdio != ARG_STDIO_NONE && arg_transport == BUS_TRANSPORT_REMOTE) {
+                log_error("--pty/--pipe is only supported when connecting to the local system or containers.");
                 return -EINVAL;
         }
 
-        if (arg_pty && arg_no_block) {
-                log_error("--pty is not compatible with --no-block.");
+        if (arg_stdio != ARG_STDIO_NONE && arg_no_block) {
+                log_error("--pty/--pipe is not compatible with --no-block.");
                 return -EINVAL;
         }
 
@@ -481,6 +509,7 @@ static int transient_kill_set_properties(sd_bus_message *m) {
 }
 
 static int transient_service_set_properties(sd_bus_message *m, char **argv, const char *pty_path) {
+        bool send_term = false;
         int r;
 
         assert(m);
@@ -497,7 +526,7 @@ static int transient_service_set_properties(sd_bus_message *m, char **argv, cons
         if (r < 0)
                 return r;
 
-        if (arg_wait || arg_pty) {
+        if (arg_wait || arg_stdio != ARG_STDIO_NONE) {
                 r = sd_bus_message_append(m, "(sv)", "AddRef", "b", 1);
                 if (r < 0)
                         return r;
@@ -534,8 +563,6 @@ static int transient_service_set_properties(sd_bus_message *m, char **argv, cons
         }
 
         if (pty_path) {
-                const char *e;
-
                 r = sd_bus_message_append(m,
                                           "(sv)(sv)(sv)(sv)",
                                           "StandardInput", "s", "tty",
@@ -545,6 +572,23 @@ static int transient_service_set_properties(sd_bus_message *m, char **argv, cons
                 if (r < 0)
                         return r;
 
+                send_term = true;
+
+        } else if (arg_stdio == ARG_STDIO_DIRECT) {
+                r = sd_bus_message_append(m,
+                                          "(sv)(sv)(sv)",
+                                          "StandardInputFileDescriptor", "h", STDIN_FILENO,
+                                          "StandardOutputFileDescriptor", "h", STDOUT_FILENO,
+                                          "StandardErrorFileDescriptor", "h", STDERR_FILENO);
+                if (r < 0)
+                        return r;
+
+                send_term = isatty(STDIN_FILENO) || isatty(STDOUT_FILENO) || isatty(STDERR_FILENO);
+        }
+
+        if (send_term) {
+                const char *e;
+
                 e = getenv("TERM");
                 if (e) {
                         char *n;
@@ -875,7 +919,7 @@ static int start_transient_service(
         assert(argv);
         assert(retval);
 
-        if (arg_pty) {
+        if (arg_stdio == ARG_STDIO_PTY) {
 
                 if (arg_transport == BUS_TRANSPORT_LOCAL) {
                         master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY);
@@ -1000,7 +1044,7 @@ static int start_transient_service(
         if (!arg_quiet)
                 log_info("Running as unit: %s", service);
 
-        if (arg_wait || master >= 0) {
+        if (arg_wait || arg_stdio != ARG_STDIO_NONE) {
                 _cleanup_(run_context_free) RunContext c = {};
                 _cleanup_free_ char *path = NULL;
                 const char *mt;
@@ -1440,7 +1484,7 @@ int main(int argc, char* argv[]) {
 
         /* If --wait is used connect via the bus, unconditionally, as ref/unref is not supported via the limited direct
          * connection */
-        if (arg_wait || arg_pty)
+        if (arg_wait || arg_stdio != ARG_STDIO_NONE)
                 r = bus_connect_transport(arg_transport, arg_host, arg_user, &bus);
         else
                 r = bus_connect_transport_systemd(arg_transport, arg_host, arg_user, &bus);