mirror of
https://github.com/morgan9e/systemd
synced 2026-04-14 00:14:32 +09:00
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:
@@ -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
|
||||
{
|
||||
|
||||
@@ -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 },
|
||||
{}
|
||||
|
||||
Reference in New Issue
Block a user