varlinkctl: introduce new --exec switch

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, …
This commit is contained in:
Lennart Poettering
2025-02-14 14:35:18 +01:00
parent 4389e4c2ae
commit fd2d435d97
2 changed files with 168 additions and 5 deletions

View File

@@ -61,6 +61,18 @@
<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>
@@ -307,6 +319,25 @@
</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>
<para>Now that if <option>--exec</option> is specified the the third parameter to
<command>call</command> is not optional (i.e. the method call parameters).</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 +380,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
{

View 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;
@@ -529,18 +540,34 @@ static int verb_call(int argc, char *argv[], void *userdata) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *jp = NULL;
_cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
const char *url, *method, *parameter, *source;
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 */
/* No JSON mode explicitly configured? Then default to the same as -j (except if --exec is used, in
* which case generate shortest possible JSON since we are going to pass it to a program rather than
* a user anyway) */
if (!sd_json_format_enabled(arg_json_format_flags)) {
arg_json_format_flags &= ~SD_JSON_FORMAT_OFF;
arg_json_format_flags |= SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO;
if (arg_exec)
arg_json_format_flags |= SD_JSON_FORMAT_NEWLINE;
else
arg_json_format_flags |= SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO;
}
/* For pipeable text tools it's kinda customary to finish output off in a newline character, and not
@@ -648,6 +675,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 +704,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 memfd 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 +865,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 },
{}