<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" />
<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
{
#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"
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);
" 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"
ARG_COLLECT,
ARG_GRACEFUL,
ARG_TIMEOUT,
+ ARG_EXEC,
};
static const struct option options[] = {
{ "quiet", no_argument, NULL, 'q' },
{ "graceful", required_argument, NULL, ARG_GRACEFUL },
{ "timeout", required_argument, NULL, ARG_TIMEOUT },
+ { "exec", no_argument, NULL, ARG_EXEC },
{},
};
break;
+ case ARG_EXEC:
+ arg_exec = true;
+ break;
+
case '?':
return -EINVAL;
_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)) {
} 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)
} 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;
{ "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 },
{}