From: Lennart Poettering Date: Fri, 14 Feb 2025 13:35:18 +0000 (+0100) Subject: varlinkctl: introduce new --exec switch X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=e27749501b5cb5aa9eeb7a210b26b9e2fe6f29af;p=thirdparty%2Fsystemd.git 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, … --- diff --git a/man/varlinkctl.xml b/man/varlinkctl.xml index 41d911c5115..59c758b5e73 100644 --- a/man/varlinkctl.xml +++ b/man/varlinkctl.xml @@ -61,6 +61,18 @@ ARGUMENTS + + varlinkctl + OPTIONS + --exec call + call + ADDRESS + METHOD + ARGUMENTS + -- + CMDLINE + + varlinkctl OPTIONS @@ -307,6 +319,22 @@ + + + + + Once the method call issued via call 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 $LISTEN_FDS protocol. This + functionality may be used to consume replies that come with associated file descriptors in a + reasonable way. + + + + + @@ -349,7 +377,9 @@ type ResolvedAddress( Invoking a Method - The following command resolves a hostname via systemd-resolved.service8's ResolveHostname method call. + The following command resolves a hostname via + systemd-resolved.service8's + ResolveHostname method call. $ varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name":"systemd.io","family":2}' -j { diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index 76c9e4850a7..a720368df46 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -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 }, {}