]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
varlinkctl: introduce new --exec switch
authorLennart Poettering <lennart@poettering.net>
Fri, 14 Feb 2025 13:35:18 +0000 (14:35 +0100)
committerLennart Poettering <lennart@poettering.net>
Tue, 18 Feb 2025 12:17:54 +0000 (13:17 +0100)
This new switch makes it possible to process fds attached to a varlink
reply: if specified it will execute a command line specified by the
caller, will pass the response json as stdin, and any fds acquired as
fd3, fd4, …

man/varlinkctl.xml
src/varlinkctl/varlinkctl.c

index 41d911c5115335bd890be04a10fb1b7ed342ed16..59c758b5e73a172b167fa1487bacc7e1e2d4fae7 100644 (file)
       <arg choice="opt"><replaceable>ARGUMENTS</replaceable></arg>
     </cmdsynopsis>
 
+    <cmdsynopsis>
+      <command>varlinkctl</command>
+      <arg choice="opt" rep="repeat">OPTIONS</arg>
+      <arg choice="plain">--exec call</arg>
+      <arg choice="plain">call</arg>
+      <arg choice="plain"><replaceable>ADDRESS</replaceable></arg>
+      <arg choice="plain"><replaceable>METHOD</replaceable></arg>
+      <arg choice="plain"><replaceable>ARGUMENTS</replaceable></arg>
+      <arg choice="plain">--</arg>
+      <arg choice="plain"><replaceable>CMDLINE</replaceable></arg>
+    </cmdsynopsis>
+
     <cmdsynopsis>
       <command>varlinkctl</command>
       <arg choice="opt" rep="repeat">OPTIONS</arg>
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--exec</option></term>
+
+        <listitem>
+          <para>Once the method call issued via <command>call</command> completed successfully, chainload the
+          specified command line, with the method call output parameters serialized to JSON passed into
+          standard input (and standard output and standard error inherited from the invoking
+          process). Moreover any file descriptors passed back on the underlying communication socket are
+          passed to the invoked process via the usual <varname>$LISTEN_FDS</varname> protocol. This
+          functionality may be used to consume replies that come with associated file descriptors in a
+          reasonable way.</para>
+
+          <xi:include href="version-info.xml" xpointer="v258"/>
+        </listitem>
+      </varlistentry>
+
       <xi:include href="standard-options.xml" xpointer="no-pager" />
       <xi:include href="standard-options.xml" xpointer="help" />
       <xi:include href="standard-options.xml" xpointer="version" />
@@ -349,7 +377,9 @@ type ResolvedAddress(
     <example>
       <title>Invoking a Method</title>
 
-      <para>The following command resolves a hostname via <citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>'s <function>ResolveHostname</function>  method call.</para>
+      <para>The following command resolves a hostname via
+      <citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>'s
+      <function>ResolveHostname</function> method call.</para>
 
       <programlisting>$ varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name":"systemd.io","family":2}' -j
 {
index 76c9e4850a710e2dca96a0681f332fcf4e72714c..a720368df463a42ef5f59c790489ff7e19381288 100644 (file)
@@ -5,11 +5,13 @@
 #include "sd-varlink.h"
 
 #include "build.h"
+#include "env-util.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "format-table.h"
 #include "io-util.h"
 #include "main-func.h"
+#include "memfd-util.h"
 #include "pager.h"
 #include "parse-argument.h"
 #include "path-util.h"
@@ -27,6 +29,7 @@ static bool arg_collect = false;
 static bool arg_quiet = false;
 static char **arg_graceful = NULL;
 static usec_t arg_timeout = 0;
+static bool arg_exec = false;
 
 STATIC_DESTRUCTOR_REGISTER(arg_graceful, strv_freep);
 
@@ -53,6 +56,8 @@ static int help(void) {
                "                         Show interface definition\n"
                "  call ADDRESS METHOD [PARAMS]\n"
                "                         Invoke method\n"
+               "  --exec call ADDRESS METHOD PARAMS -- CMDLINE…\n"
+               "                         Invoke method and pass response and fds to command\n"
                "  validate-idl [FILE]    Validate interface description\n"
                "  help                   Show this help\n"
                "\n%3$sOptions:%4$s\n"
@@ -94,6 +99,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_COLLECT,
                 ARG_GRACEFUL,
                 ARG_TIMEOUT,
+                ARG_EXEC,
         };
 
         static const struct option options[] = {
@@ -107,6 +113,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "quiet",    no_argument,       NULL, 'q'          },
                 { "graceful", required_argument, NULL, ARG_GRACEFUL },
                 { "timeout",  required_argument, NULL, ARG_TIMEOUT  },
+                { "exec",     no_argument,       NULL, ARG_EXEC     },
                 {},
         };
 
@@ -187,6 +194,10 @@ static int parse_argv(int argc, char *argv[]) {
 
                         break;
 
+                case ARG_EXEC:
+                        arg_exec = true;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -530,13 +541,23 @@ static int verb_call(int argc, char *argv[], void *userdata) {
         _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
         const char *url, *method, *parameter, *source;
         unsigned line = 0, column = 0;
+        char **cmdline;
         int r;
 
         assert(argc >= 3);
-        assert(argc <= 4);
+
+        if (argc > 4 && !arg_exec)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments.");
+        if (arg_exec && argc < 5)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected command line to execute.");
+
+        if (arg_exec && (arg_collect || (arg_method_flags & (SD_VARLINK_METHOD_ONEWAY|SD_VARLINK_METHOD_MORE))) != 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--exec and --collect/--more/--oneway may not be combined.");
+
         url = argv[1];
         method = argv[2];
         parameter = argc > 3 && !streq(argv[3], "-") ? argv[3] : NULL;
+        cmdline = strv_skip(argv, 4);
 
         /* No JSON mode explicitly configured? Then default to the same as -j */
         if (!sd_json_format_enabled(arg_json_format_flags)) {
@@ -648,6 +669,15 @@ static int verb_call(int argc, char *argv[], void *userdata) {
         } else {
                 sd_json_variant *reply = NULL;
                 const char *error = NULL;
+                bool process_fds = false;
+
+                if (arg_exec) {
+                        r = sd_varlink_set_allow_fd_passing_input(vl, true);
+                        if (r < 0)
+                                log_debug_errno(r, "Unable to enable file descriptor receiving, ignoring: %m");
+                        else
+                                process_fds = true;
+                }
 
                 r = sd_varlink_call(vl, method, jp, &reply, &error);
                 if (r < 0)
@@ -668,6 +698,100 @@ static int verb_call(int argc, char *argv[], void *userdata) {
                 } else
                         r = 0;
 
+                if (arg_exec && r == 0) {
+                        (void) sd_notify(/* unset_environment= */ false, "READY=1");
+
+                        _cleanup_free_ char *formatted = NULL;
+                        r = sd_json_variant_format(reply, arg_json_format_flags, &formatted);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to format reply: %m");
+
+                        _cleanup_close_ int mfd = memfd_new_and_seal_string("varlink-reply", formatted);
+                        if (mfd < 0)
+                                return log_error_errno(mfd, "Failed to allocate memory fd for reply: %m");
+
+                        _cleanup_free_ char *j = strv_join(cmdline, " ");
+                        if (!j)
+                                return log_oom();
+
+                        int *fd_array = NULL, n = 0;
+                        size_t m = 0;
+                        CLEANUP_ARRAY(fd_array, m, close_many_and_free);
+
+                        if (process_fds) {
+                                n = sd_varlink_get_n_fds(vl);
+                                if (n < 0)
+                                        return log_error_errno(n, "Failed to determine how many file descriptors we received: %m");
+
+                                fd_array = new(int, n);
+                                if (!fd_array)
+                                        return log_oom();
+
+                                for (int i = 0; i < n; i++) {
+                                        fd_array[m] = sd_varlink_take_fd(vl, i);
+                                        if (fd_array[m] < 0)
+                                                return log_error_errno(fd_array[m], "Failed to acquire fd we received: %m");
+
+                                        m++;
+                                }
+                        }
+
+                        /* We'll now close all remaining fds. This means we are stealing other code that
+                         * lives in our process their fds. Hence we will now no longer bubble up any
+                         * errors. */
+
+                        log_close();
+                        log_set_open_when_needed(true);
+
+                        r = move_fd(mfd, STDIN_FILENO, /* cloexec= */ false);
+                        if (r < 0) {
+                                log_error_errno(r, "Failed to move reply to STDIN_FILENO: %m");
+                                _exit(EXIT_FAILURE);
+                        }
+
+                        r = close_all_fds(fd_array, m);
+                        if (r < 0) {
+                                log_error_errno(r, "Failed to close all remaining file descriptors: %m");
+                                _exit(EXIT_FAILURE);
+                        }
+
+                        r = pack_fds(fd_array, m);
+                        if (r < 0) {
+                                log_error_errno(r, "Failed to rearrange file descriptors: %m");
+                                _exit(EXIT_FAILURE);
+                        }
+
+                        r = fd_cloexec_many(fd_array, m, false);
+                        if (r < 0) {
+                                log_error_errno(r, "Failed to disable O_CLOEXEC for file descriptors: %m");
+                                _exit(EXIT_FAILURE);
+                        }
+
+                        if (m > 0) {
+                                r = setenvf("LISTEN_FDS", /* overwrite= */ true, "%zu", m);
+                                if (r < 0) {
+                                        log_error_errno(r, "Failed to set $LISTEN_FDS environment variable: %m");
+                                        _exit(EXIT_FAILURE);
+                                }
+
+                                r = setenvf("LISTEN_PID", /* overwrite= */ true, PID_FMT, getpid_cached());
+                                if (r < 0) {
+                                        log_error_errno(r, "Failed to set $LISTEN_PID environment variable: %m");
+                                        _exit(EXIT_FAILURE);
+                                }
+                        } else {
+                                (void) unsetenv("LISTEN_FDS");
+                                (void) unsetenv("LISTEN_PID");
+                        }
+                        (void) unsetenv("LISTEN_FDNAMES");
+
+                        log_debug("Executing: %s", j);
+
+                        execvp(cmdline[0], cmdline);
+                        log_error_errno(errno, "Failed to execute '%s': %m", j);
+                        _exit(EXIT_FAILURE);
+                }
+
                 if (arg_quiet)
                         return r;
 
@@ -735,7 +859,7 @@ static int varlinkctl_main(int argc, char *argv[]) {
                 { "list-interfaces", 2,        2,        0, verb_info         },
                 { "introspect",      2,        VERB_ANY, 0, verb_introspect   },
                 { "list-methods",    2,        VERB_ANY, 0, verb_introspect   },
-                { "call",            3,        4,        0, verb_call         },
+                { "call",            3,        VERB_ANY, 0, verb_call         },
                 { "validate-idl",    1,        2,        0, verb_validate_idl },
                 { "help",            VERB_ANY, VERB_ANY, 0, verb_help         },
                 {}