/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>

#include "sd-daemon.h"
#include "sd-varlink.h"

#include "build.h"
#include "bus-util.h"
#include "chase.h"
#include "env-util.h"
#include "escape.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-table.h"
#include "format-util.h"
#include "log.h"
#include "main-func.h"
#include "memfd-util.h"
#include "options.h"
#include "pager.h"
#include "parse-argument.h"
#include "parse-util.h"
#include "path-lookup.h"
#include "path-util.h"
#include "pidfd-util.h"
#include "polkit-agent.h"
#include "pretty-print.h"
#include "process-util.h"
#include "recurse-dir.h"
#include "runtime-scope.h"
#include "socket-forward.h"
#include "string-util.h"
#include "strv.h"
#include "terminal-util.h"
#include "time-util.h"
#include "varlink-idl-util.h"
#include "varlink-util.h"
#include "verbs.h"
#include "version.h"

typedef struct PushFds {
        int *fds;
        size_t n_fds;
} PushFds;

static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF;
static PagerFlags arg_pager_flags = 0;
static sd_varlink_method_flags_t arg_method_flags = 0;
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 bool arg_upgrade = false;
static PushFds arg_push_fds = {};
static bool arg_ask_password = true;
static bool arg_legend = true;
static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;

static void push_fds_done(PushFds *p) {
        assert(p);

        close_many_and_free(p->fds, p->n_fds);
        *p = (PushFds) {};
}

STATIC_DESTRUCTOR_REGISTER(arg_graceful, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_push_fds, push_fds_done);

static int help(void) {
        _cleanup_free_ char *link = NULL;
        _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL;
        int r;

        r = terminal_urlify_man("varlinkctl", "1", &link);
        if (r < 0)
                return log_oom();

        r = option_parser_get_help_table(&options);
        if (r < 0)
                return r;

        r = verbs_get_help_table(&verbs);
        if (r < 0)
                return r;

        (void) table_sync_column_widths(0, options, verbs);

        pager_open(arg_pager_flags);

        printf("%s [OPTIONS...] COMMAND ...\n\n"
               "%sIntrospect Varlink Services.%s\n"
               "\nCommands:\n",
               program_invocation_short_name,
               ansi_highlight(),
               ansi_normal());

        r = table_print_or_warn(verbs);
        if (r < 0)
                return r;

        printf("\nOptions:\n");

        r = table_print_or_warn(options);
        if (r < 0)
                return r;

        printf("\nWith --exec, specify the command to invoke:\n"
               "  %s --exec call ADDRESS METHOD PARAMS -- CMDLINE…\n",
               program_invocation_short_name);

        printf("\nSee the %s for details.\n", link);
        return 0;
}

VERB_COMMON_HELP(help);

static int parse_argv(int argc, char *argv[], char ***ret_args) {
        int r;

        assert(argc >= 0);
        assert(argv);

        OptionParser opts = { argc, argv };

        FOREACH_OPTION_OR_RETURN(c, &opts)
                switch (c) {

                OPTION_COMMON_HELP:
                        return help();

                OPTION_COMMON_VERSION:
                        return version();

                OPTION_COMMON_NO_ASK_PASSWORD:
                        arg_ask_password = false;
                        break;

                OPTION_COMMON_NO_PAGER:
                        arg_pager_flags |= PAGER_DISABLE;
                        break;

                OPTION_LONG("system", NULL, "Enumerate system registry"):
                        arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
                        break;

                OPTION_LONG("user", NULL, "Enumerate user registry"):
                        arg_runtime_scope = RUNTIME_SCOPE_USER;
                        break;

                OPTION_LONG("more", NULL, "Request multiple responses"):
                        arg_method_flags = (arg_method_flags & ~SD_VARLINK_METHOD_ONEWAY) | SD_VARLINK_METHOD_MORE;
                        break;

                OPTION_LONG("collect", NULL, "Collect multiple responses in a JSON array"):
                        arg_collect = true;
                        break;

                OPTION_LONG("oneway", NULL, "Do not request response"):
                        arg_method_flags = (arg_method_flags & ~SD_VARLINK_METHOD_MORE) | SD_VARLINK_METHOD_ONEWAY;
                        break;

                OPTION_COMMON_JSON:
                        r = parse_json_argument(opts.arg, &arg_json_format_flags);
                        if (r <= 0)
                                return r;
                        break;

                OPTION_COMMON_LOWERCASE_J:
                        arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO;
                        break;

                OPTION('q', "quiet", NULL, "Do not output method reply"):
                        arg_quiet = true;
                        break;

                OPTION_LONG("graceful", "ERROR", "Treat specified Varlink error as success"):
                        r = varlink_idl_qualified_symbol_name_is_valid(opts.arg);
                        if (r < 0)
                                return log_error_errno(r, "Failed to validate Varlink error name '%s': %m", opts.arg);
                        if (r == 0)
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid Varlink error name: %s", opts.arg);

                        if (strv_extend(&arg_graceful, opts.arg) < 0)
                                return log_oom();
                        break;

                OPTION_LONG("timeout", "SECS", "Maximum time to wait for method call completion"):
                        if (isempty(opts.arg)) {
                                arg_timeout = USEC_INFINITY;
                                break;
                        }

                        r = parse_sec(opts.arg, &arg_timeout);
                        if (r < 0)
                                return log_error_errno(r, "Failed to parse --timeout= parameter '%s': %m", opts.arg);

                        if (arg_timeout == 0)
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Timeout cannot be zero.");
                        break;

                OPTION_SHORT('E', NULL, "Short for --more --timeout=infinity"):
                        arg_timeout = USEC_INFINITY;
                        arg_method_flags = (arg_method_flags & ~SD_VARLINK_METHOD_ONEWAY) | SD_VARLINK_METHOD_MORE;
                        break;

                OPTION_LONG("upgrade", NULL,
                            "Request protocol upgrade (connection becomes raw"
                            " bidirectional pipe on stdin/stdout after reply)"):
                        arg_upgrade = true;
                        break;

                OPTION_LONG("exec", NULL, "Invoke method and pass response and fds to command"):
                        arg_exec = true;
                        break;

                OPTION_LONG("push-fd", "FD", "Pass the specified fd along with method call"): {
                        if (!GREEDY_REALLOC(arg_push_fds.fds, arg_push_fds.n_fds + 1))
                                return log_oom();

                        _cleanup_close_ int add_fd = -EBADF;
                        if (STARTSWITH_SET(opts.arg, "/", "./")) {
                                /* We usually expect a numeric fd spec, but as an extension let's treat this
                                 * as a path to open in read-only mode in case this is clearly an absolute or
                                 * relative path */
                                add_fd = open(opts.arg, O_CLOEXEC|O_RDONLY|O_NOCTTY);
                                if (add_fd < 0)
                                        return log_error_errno(errno, "Failed to open '%s': %m", opts.arg);
                        } else {
                                int parsed_fd = parse_fd(opts.arg);
                                if (parsed_fd < 0)
                                        return log_error_errno(parsed_fd, "Failed to parse --push-fd= parameter: %s", opts.arg);

                                /* Make a copy, so that the same fd could be used multiple times in a reasonable
                                 * way. This also validates the fd early */
                                add_fd = fcntl(parsed_fd, F_DUPFD_CLOEXEC, 3);
                                if (add_fd < 0)
                                        return log_error_errno(errno, "Failed to duplicate file descriptor %i: %m", parsed_fd);
                        }

                        arg_push_fds.fds[arg_push_fds.n_fds++] = TAKE_FD(add_fd);
                        break;
                }
                }

        /* If more than one reply is expected, imply JSON-SEQ output, and set SD_JSON_FORMAT_FLUSH */
        if (FLAGS_SET(arg_method_flags, SD_VARLINK_METHOD_MORE))
                arg_json_format_flags |= SD_JSON_FORMAT_SEQ|SD_JSON_FORMAT_FLUSH;

        strv_sort_uniq(arg_graceful);

        *ret_args = option_parser_get_args(&opts);
        return 1;
}

static int varlink_connect_auto(sd_varlink **ret, const char *where) {
        int r;

        assert(ret);
        assert(where);

        _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;

        if (STARTSWITH_SET(where, "/", "./")) { /* If the string starts with a slash or dot slash we use it as a file system path */
                _cleanup_close_ int fd = -EBADF;
                struct stat st;

                fd = open(where, O_PATH|O_CLOEXEC);
                if (fd < 0)
                        return log_error_errno(errno, "Failed to open '%s': %m", where);

                if (fstat(fd, &st) < 0)
                        return log_error_errno(errno, "Failed to stat '%s': %m", where);

                if (S_ISSOCK(st.st_mode)) {
                        /* Is this a socket in the fs? Then connect() to it. */

                        r = sd_varlink_connect_address(&vl, FORMAT_PROC_FD_PATH(fd));
                        if (r < 0)
                                return log_error_errno(r, "Failed to connect to '%s': %m", where);

                } else if (S_ISREG(st.st_mode) && (st.st_mode & 0111)) {
                        /* Is this an executable binary? Then fork it off. */

                        r = sd_varlink_connect_exec(&vl, where, STRV_MAKE(where)); /* Ideally we'd use FORMAT_PROC_FD_PATH(fd) here too, but that breaks the #! logic */
                        if (r < 0)
                                return log_error_errno(r, "Failed to spawn '%s' process: %m", where);
                } else
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unrecognized path '%s' is neither an AF_UNIX socket, nor an executable binary.", where);
        } else {
                /* Otherwise assume this is an URL */
                r = sd_varlink_connect_url(&vl, where);
                if (r < 0)
                        return log_error_errno(r, "Failed to connect to URL '%s': %m", where);
        }

        if (arg_timeout != 0) {
                r = sd_varlink_set_relative_timeout(vl, arg_timeout);
                if (r < 0)
                        log_warning_errno(r, "Failed to set Varlink timeout, ignoring: %m");
        }

        *ret = TAKE_PTR(vl);
        return 0;
}

typedef struct GetInfoData {
        const char *vendor;
        const char *product;
        const char *version;
        const char *url;
        char **interfaces;
} GetInfoData;

static void get_info_data_done(GetInfoData *d) {
        assert(d);

        d->interfaces = strv_free(d->interfaces);
}

VERB(verb_info, "info", "ADDRESS", 2, 2, 0, "Show service information");
VERB(verb_info, "list-interfaces", "ADDRESS", 2, 2, 0, "List interfaces implemented by service");
static int verb_info(int argc, char *argv[], uintptr_t _data, void *userdata) {
        _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
        const char *url;
        int r;

        assert(argc == 2);
        url = argv[1];

        r = varlink_connect_auto(&vl, url);
        if (r < 0)
                return r;

        sd_json_variant *reply = NULL;
        r = varlink_call_and_log(vl, "org.varlink.service.GetInfo", /* parameters= */ NULL, &reply);
        if (r < 0)
                return r;

        pager_open(arg_pager_flags);

        if (!sd_json_format_enabled(arg_json_format_flags)) {
                static const sd_json_dispatch_field dispatch_table[] = {
                        { "vendor",     SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(GetInfoData, vendor),     SD_JSON_MANDATORY },
                        { "product",    SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(GetInfoData, product),    SD_JSON_MANDATORY },
                        { "version",    SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(GetInfoData, version),    SD_JSON_MANDATORY },
                        { "url",        SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(GetInfoData, url),        SD_JSON_MANDATORY },
                        { "interfaces", SD_JSON_VARIANT_ARRAY,  sd_json_dispatch_strv,         offsetof(GetInfoData, interfaces), SD_JSON_MANDATORY },
                        {}
                };
                _cleanup_(get_info_data_done) GetInfoData data = {};

                r = sd_json_dispatch(reply, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &data);
                if (r < 0)
                        return r;

                strv_sort(data.interfaces);

                if (streq_ptr(argv[0], "list-interfaces")) {
                        STRV_FOREACH(i, data.interfaces)
                                puts(*i);

                        return 0;
                } else {
                        _cleanup_(table_unrefp) Table *t = NULL;

                        t = table_new_vertical();
                        if (!t)
                                return log_oom();

                        r = table_add_many(
                                        t,
                                        TABLE_FIELD, "Vendor",
                                        TABLE_STRING, data.vendor,
                                        TABLE_FIELD, "Product",
                                        TABLE_STRING, data.product,
                                        TABLE_FIELD, "Version",
                                        TABLE_STRING, data.version,
                                        TABLE_FIELD, "URL",
                                        TABLE_STRING, data.url,
                                        TABLE_SET_URL, data.url,
                                        TABLE_FIELD, "Interfaces",
                                        TABLE_STRV, data.interfaces);
                        if (r < 0)
                                return table_log_add_error(r);

                        return table_print_or_warn(t);
                }
        } else {
                sd_json_variant *v = streq_ptr(argv[0], "list-interfaces") ?
                        sd_json_variant_by_key(reply, "interfaces") : reply;

                return sd_json_variant_dump(v, arg_json_format_flags, stdout, NULL);
        }
}

static size_t break_columns(void) {
        int r;

        /* Rebreak the interface data to the TTY width */
        if (on_tty())
                return columns();

        /* if not connected to a tty, still allow the caller to control the columns via the usual env var */
        r = getenv_columns();
        if (r < 0)
                return SIZE_MAX;

        return r;
}

typedef struct GetInterfaceDescriptionData {
        const char *description;
} GetInterfaceDescriptionData;

VERB(verb_introspect, "introspect", "ADDRESS [INTERFACE…]", 2, VERB_ANY, 0, "Show interface definition");
VERB(verb_introspect, "list-methods", "ADDRESS [INTERFACE…]", 2, VERB_ANY, 0,
     "List methods implemented by services or specific interfaces");
static int verb_introspect(int argc, char *argv[], uintptr_t _data, void *userdata) {
        _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
        _cleanup_strv_free_ char **auto_interfaces = NULL;
        char **interfaces;
        const char *url;
        bool list_methods;
        int r;

        assert(argc >= 2);
        list_methods = streq(argv[0], "list-methods");
        url = argv[1];
        interfaces = strv_skip(argv, 2);

        STRV_FOREACH(i, interfaces)
                if (!varlink_idl_interface_name_is_valid(*i))
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid Varlink interface name: '%s'", *i);

        r = varlink_connect_auto(&vl, url);
        if (r < 0)
                return r;

        if (strv_isempty(interfaces)) {
                sd_json_variant *reply = NULL;

                /* If no interface is specified, introspect all of them */

                r = varlink_call_and_log(vl, "org.varlink.service.GetInfo", /* parameters= */ NULL, &reply);
                if (r < 0)
                        return r;

                static const sd_json_dispatch_field dispatch_table[] = {
                        { "interfaces", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, 0, SD_JSON_MANDATORY },
                        {}
                };

                r = sd_json_dispatch(reply, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &auto_interfaces);
                if (r < 0)
                        return r;

                if (strv_isempty(auto_interfaces))
                        return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "Service doesn't report any implemented interfaces.");

                interfaces = strv_sort_uniq(auto_interfaces);
        }

        /* Automatically switch on JSON_SEQ if we output multiple JSON objects */
        if (!list_methods && strv_length(interfaces) > 1)
                arg_json_format_flags |= SD_JSON_FORMAT_SEQ;

        _cleanup_strv_free_ char **methods = NULL;

        STRV_FOREACH(i, interfaces) {
                sd_json_variant *reply = NULL;
                r = varlink_callbo_and_log(
                                vl,
                                "org.varlink.service.GetInterfaceDescription",
                                &reply,
                                SD_JSON_BUILD_PAIR_STRING("interface", *i));
                if (r < 0)
                        return r;

                if (!sd_json_format_enabled(arg_json_format_flags) || list_methods) {
                        static const sd_json_dispatch_field dispatch_table[] = {
                                { "description", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, SD_JSON_MANDATORY },
                                {}
                        };
                        _cleanup_(sd_varlink_interface_freep) sd_varlink_interface *vi = NULL;
                        const char *description = NULL;
                        unsigned line = 0, column = 0;

                        r = sd_json_dispatch(reply, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &description);
                        if (r < 0)
                                return r;

                        if (!list_methods && i > interfaces)
                                print_separator();

                        /* Try to parse the returned description, so that we can add syntax highlighting */
                        r = sd_varlink_idl_parse(ASSERT_PTR(description), &line, &column, &vi);
                        if (r < 0) {
                                if (list_methods)
                                        return log_error_errno(r, "Failed to parse returned interface description at %u:%u: %m", line, column);

                                log_warning_errno(r, "Failed to parse returned interface description at %u:%u, showing raw interface description: %m", line, column);

                                pager_open(arg_pager_flags);
                                fputs_with_newline(stdout, description);
                        } else if (list_methods) {
                                for (const sd_varlink_symbol *const *y = vi->symbols, *symbol; (symbol = *y); y++) {
                                        if (symbol->symbol_type != SD_VARLINK_METHOD)
                                                continue;

                                        r = strv_extendf(&methods, "%s.%s", vi->name, symbol->name);
                                        if (r < 0)
                                                return log_oom();
                                }
                        } else {
                                pager_open(arg_pager_flags);
                                r = sd_varlink_idl_dump(stdout, vi, SD_VARLINK_IDL_FORMAT_COLOR_AUTO, break_columns());
                                if (r < 0)
                                        return log_error_errno(r, "Failed to format parsed interface description: %m");
                        }
                } else {
                        pager_open(arg_pager_flags);
                        sd_json_variant_dump(reply, arg_json_format_flags, stdout, NULL);
                }
        }

        if (list_methods) {
                pager_open(arg_pager_flags);

                strv_sort_uniq(methods);

                if (!sd_json_format_enabled(arg_json_format_flags))
                        strv_print(methods);
                else {
                        _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;

                        r = sd_json_build(&j, SD_JSON_BUILD_STRV(methods));
                        if (r < 0)
                                return log_error_errno(r, "Failed to build JSON array: %m");

                        sd_json_variant_dump(j, arg_json_format_flags, stdout, NULL);
                }
        }

        return 0;
}

static int reply_callback(
                sd_varlink *link,
                sd_json_variant *parameters,
                const char *error,
                sd_varlink_reply_flags_t flags,
                void *userdata)  {

        int *ret = ASSERT_PTR(userdata), r;

        assert(link);

        if (error) {
                /* Propagate the error we received via sd_notify() */
                (void) sd_notifyf(/* unset_environment= */ false, "VARLINKERROR=%s", error);

                if (strv_contains(arg_graceful, error)) {
                        log_full(arg_quiet ? LOG_DEBUG : LOG_INFO,
                                 "Method call returned expected error: %s", error);

                        r = 0;
                } else {
                        /* If we can translate this to an errno, let's print that as errno and return it, otherwise, return a generic error code */
                        r = sd_varlink_error_to_errno(error, parameters);
                        if (r != -EBADR)
                                *ret = log_error_errno(r, "Method call failed: %m");
                        else
                                r = *ret = log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call failed: %s", error);
                }
        } else {
                /* Let the caller know we have received at least one reply now. This is useful for
                 * subscription style interfaces where the first reply indicates the subscription being
                 * successfully enabled. */
                (void) sd_notify(/* unset_environment= */ false, "READY=1");
                r = 0;
        }

        if (!arg_quiet)
                sd_json_variant_dump(parameters, arg_json_format_flags, stdout, NULL);

        return r;
}

static int upgrade_forward_done(SocketForward *sf, int error, void *userdata) {
        sd_event *event = ASSERT_PTR(userdata);

        return sd_event_exit(event, error < 0 ? error : 0);
}

/* This will only return if something goes wrong, otherwise the exec_cmdline
 * is run and replaces our code. */
static int exec_with_listen_fds(char **exec_cmdline, int *fds, size_t n_fds) {
        _cleanup_free_ char *j = quote_command_line(exec_cmdline, SHELL_ESCAPE_EMPTY);
        if (!j)
                return log_oom();

        log_close();
        log_set_open_when_needed(true);

        int r = close_all_fds(fds, n_fds);
        if (r < 0)
                return log_error_errno(r, "Failed to close all remaining file descriptors: %m");

        r = pack_fds(fds, n_fds);
        if (r < 0)
                return log_error_errno(r, "Failed to rearrange file descriptors: %m");

        r = fd_cloexec_many(fds, n_fds, false);
        if (r < 0)
                return log_error_errno(r, "Failed to disable O_CLOEXEC for file descriptors: %m");

        if (n_fds > 0) {
                r = setenvf("LISTEN_FDS", /* overwrite= */ true, "%zu", n_fds);
                if (r < 0)
                        return log_error_errno(r, "Failed to set $LISTEN_FDS environment variable: %m");

                r = setenvf("LISTEN_PID", /* overwrite= */ true, PID_FMT, getpid_cached());
                if (r < 0)
                        return log_error_errno(r, "Failed to set $LISTEN_PID environment variable: %m");

                uint64_t pidfdid;
                if (pidfd_get_inode_id_self_cached(&pidfdid) >= 0) {
                        r = setenvf("LISTEN_PIDFDID", /* overwrite= */ true, "%" PRIu64, pidfdid);
                        if (r < 0)
                                return log_error_errno(r, "Failed to set $LISTEN_PIDFDID environment variable: %m");
                }
        } else {
                (void) unsetenv("LISTEN_FDS");
                (void) unsetenv("LISTEN_PID");
                (void) unsetenv("LISTEN_PIDFDID");
        }
        (void) unsetenv("LISTEN_FDNAMES");

        log_debug("Executing: %s", j);

        execvp(exec_cmdline[0], exec_cmdline);
        return log_error_errno(errno, "Failed to execute '%s': %m", j);
}

static int varlink_call_and_upgrade(const char *url, const char *method, sd_json_variant *parameters, char **exec_cmdline) {
        _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
        _cleanup_(socket_forward_freep) SocketForward *sf = NULL;
        _cleanup_close_ int input_fd = -EBADF, output_fd = -EBADF;
        const char *error_id = NULL;
        int r;

        r = varlink_connect_auto(&vl, url);
        if (r < 0)
                return r;

        r = sd_varlink_call_and_upgrade(
                        vl,
                        method,
                        parameters,
                        /* ret_parameters= */ NULL,
                        &error_id,
                        &input_fd,
                        &output_fd);
        if (r < 0)
                return log_error_errno(r, "Failed to upgrade connection via %s(): %m", method);
        if (!isempty(error_id))
                return log_error_errno(SYNTHETIC_ERRNO(EBADE), "Upgrade via %s() failed with error: %s", method, error_id);

        if (!strv_isempty(exec_cmdline)) {
                /* --exec mode: place the upgraded connection on stdin/stdout so that the child
                 * process can just read/write naturally. */
                (void) sd_notify(/* unset_environment= */ false, "READY=1");

                log_close();
                log_set_open_when_needed(true);

                int keep_fds[] = { TAKE_FD(input_fd), TAKE_FD(output_fd) };

                r = close_all_fds(keep_fds, 2);
                if (r < 0) {
                        log_error_errno(r, "Failed to close remaining file descriptors: %m");
                        _exit(EXIT_FAILURE);
                }

                r = move_fd(keep_fds[0], STDIN_FILENO, /* cloexec= */ false);
                if (r < 0) {
                        log_error_errno(r, "Failed to move upgraded connection to stdin: %m");
                        _exit(EXIT_FAILURE);
                }

                r = move_fd(keep_fds[1], STDOUT_FILENO, /* cloexec= */ false);
                if (r < 0) {
                        log_error_errno(r, "Failed to move upgraded connection to stdout: %m");
                        _exit(EXIT_FAILURE);
                }

                (void) exec_with_listen_fds(exec_cmdline, /* fds= */ NULL, /* n_fds= */ 0);
                /* This is only reached on failure, otherwise we continue with exec_cmdline). */
                _exit(EXIT_FAILURE);
        }

        /* No --exec: bidirectional proxy between stdin/stdout and the upgraded socket */

        /* Use a clean event loop just for the forwarding to avoid any interference. */
        r = sd_event_new(&event);
        if (r < 0)
                return log_error_errno(r, "Failed to allocate event loop: %m");

        _cleanup_close_ int stdin_fd = fcntl(STDIN_FILENO, F_DUPFD_CLOEXEC, 3);
        if (stdin_fd < 0)
                return log_error_errno(errno, "Failed to dup stdin: %m");

        _cleanup_close_ int stdout_fd = fcntl(STDOUT_FILENO, F_DUPFD_CLOEXEC, 3);
        if (stdout_fd < 0)
                return log_error_errno(errno, "Failed to dup stdout: %m");

        r = socket_forward_new_pair(
                        event,
                        TAKE_FD(stdin_fd), TAKE_FD(stdout_fd),
                        TAKE_FD(input_fd), TAKE_FD(output_fd),
                        upgrade_forward_done, event,
                        &sf);
        if (r < 0)
                return log_error_errno(r, "Failed to set up socket forwarding for varlink: %m");

        r = sd_event_loop(event);
        if (r < 0)
                return log_error_errno(r, "Failed to run socket forward for varlink: %m");

        return 0;
}

VERB(verb_call, "call", "ADDRESS METHOD [PARAMS]", 3, VERB_ANY, 0, "Invoke method");
static int verb_call(int argc, char *argv[], uintptr_t _data, 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 **exec_cmdline;
        int r;

        assert(argc >= 3);

        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.");

        if (arg_upgrade) {
                if (arg_collect || arg_method_flags != 0 || arg_push_fds.n_fds > 0 || !strv_isempty(arg_graceful))
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--upgrade may not be combined with --collect/--more/--oneway/--push-fd=/--graceful.");
        }

        (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password);

        url = argv[1];
        method = argv[2];
        parameter = argc > 3 && !streq(argv[3], "-") ? argv[3] : NULL;
        exec_cmdline = strv_skip(argv, 4);

        if (!varlink_idl_qualified_symbol_name_is_valid(method))
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid qualified method name: '%s' (Expected valid Varlink interface name, followed by a dot, followed by a valid Varlink symbol name.)", method);

        if (arg_upgrade) {
                /* For --upgrade, parse parameters from argv only (stdin is used for the upgraded connection) */
                if (parameter) {
                        r = sd_json_parse(parameter, SD_JSON_PARSE_MUST_BE_OBJECT, &jp, /* reterr_line= */ NULL, /* reterr_column= */ NULL);
                        if (r < 0)
                                return log_error_errno(r, "Failed to parse parameters: %m");
                }

                return varlink_call_and_upgrade(url, method, jp, exec_cmdline);
        }

        /* 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;

                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
         * leave incomplete lines hanging around. */
        arg_json_format_flags |= SD_JSON_FORMAT_NEWLINE;

        unsigned line = 0, column = 0;
        if (parameter) {
                source = "<argv[4]>";

                /* <argv[4]> is correct, as dispatch_verb() shifts arguments by one for the verb. */
                r = sd_json_parse_with_source(parameter, source, SD_JSON_PARSE_MUST_BE_OBJECT, &jp, &line, &column);
        } else {
                if (isatty_safe(STDIN_FILENO) && !arg_quiet)
                        log_notice("Expecting method call parameter JSON object on standard input. (Provide empty string or {} for no parameters.)");

                source = "<stdin>";

                r = sd_json_parse_file_at(stdin, AT_FDCWD, source, SD_JSON_PARSE_MUST_BE_OBJECT, &jp, &line, &column);
        }
        if (r < 0 && r != -ENODATA)
                return log_error_errno(r, "Failed to parse parameters at %s:%u:%u: %m", source, line, column);

        /* If parsing resulted in ENODATA the provided string was empty. As convenience to users we'll accept
         * that and treat it as equivalent to an empty object: as a call with empty set of parameters. This
         * mirrors how we do this in our C APIs too, where we are happy to accept NULL instead of a proper
         * JsonVariant object for method calls. */

        r = varlink_connect_auto(&vl, url);
        if (r < 0)
                return r;

        if (arg_push_fds.n_fds > 0) {
                r = sd_varlink_set_allow_fd_passing_output(vl, true);
                if (r < 0)
                        return log_error_errno(r, "Failed to enable fd passing: %m");

                FOREACH_ARRAY(f, arg_push_fds.fds, arg_push_fds.n_fds) {
                        r = sd_varlink_push_fd(vl, *f);
                        if (r < 0)
                                return log_error_errno(r, "Failed to push file descriptor: %m");

                        TAKE_FD(*f); /* we passed ownership away */
                }
        }

        if (arg_collect) {
                sd_json_variant *reply = NULL;
                const char *error = NULL;

                r = sd_varlink_collect(vl, method, jp, &reply, &error);
                if (r < 0)
                        return log_error_errno(r, "Failed to issue %s() call: %m", method);
                if (error) {
                        /* Propagate the error we received via sd_notify() */
                        (void) sd_notifyf(/* unset_environment= */ false, "VARLINKERROR=%s", error);

                        if (strv_contains(arg_graceful, error)) {
                                log_full(arg_quiet ? LOG_DEBUG : LOG_INFO,
                                         "Method call %s() returned expected error: %s", method, error);

                                r = 0;
                        } else {
                                r = sd_varlink_error_to_errno(error, reply);
                                if (r != -EBADR)
                                        log_error_errno(r, "Method call %s() failed: %m", method);
                                else
                                        r = log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call %s() failed: %s", method, error);
                        }
                } else
                        r = 0;

                if (arg_quiet)
                        return r;

                pager_open(arg_pager_flags);
                sd_json_variant_dump(reply, arg_json_format_flags, stdout, NULL);
                return r;

        } else if (arg_method_flags & SD_VARLINK_METHOD_ONEWAY) {
                r = sd_varlink_send(vl, method, jp);
                if (r < 0)
                        return log_error_errno(r, "Failed to issue %s() call: %m", method);

                r = sd_varlink_flush(vl);
                if (r < 0)
                        return log_error_errno(r, "Failed to flush Varlink connection: %m");

        } else if (arg_method_flags & SD_VARLINK_METHOD_MORE) {

                int ret = 0;
                sd_varlink_set_userdata(vl, &ret);

                r = sd_varlink_bind_reply(vl, reply_callback);
                if (r < 0)
                        return log_error_errno(r, "Failed to bind reply callback: %m");

                r = sd_varlink_observe(vl, method, jp);
                if (r < 0)
                        return log_error_errno(r, "Failed to issue %s() call: %m", method);

                for (;;) {
                        r = sd_varlink_is_idle(vl);
                        if (r < 0)
                                return log_error_errno(r, "Failed to check if varlink connection is idle: %m");
                        if (r > 0)
                                break;

                        r = sd_varlink_process(vl);
                        if (r < 0)
                                return log_error_errno(r, "Failed to process varlink connection: %m");
                        if (r != 0)
                                continue;

                        r = sd_varlink_wait(vl, USEC_INFINITY);
                        if (r < 0)
                                return log_error_errno(r, "Failed to wait for varlink connection events: %m");
                }

                return ret;
        } 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)
                        return log_error_errno(r, "Failed to issue %s() call: %m", method);

                /* If the server returned an error to us, then fail, but first output the associated parameters */
                if (error) {
                        /* Propagate the error we received via sd_notify() */
                        (void) sd_notifyf(/* unset_environment= */ false, "VARLINKERROR=%s", error);

                        if (strv_contains(arg_graceful, error)) {
                                log_full(arg_quiet ? LOG_DEBUG : LOG_INFO,
                                         "Method call %s() returned expected error: %s", method, error);

                                r = 0;
                        } else if (streq(error, SD_VARLINK_ERROR_EXPECTED_MORE))
                                r = log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call %s() failed: called without 'more' flag, but flag needs to be set.", method);
                        else {
                                r = sd_varlink_error_to_errno(error, reply);
                                if (r != -EBADR)
                                        log_error_errno(r, "Method call %s() failed: %m", method);
                                else
                                        r = log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call %s() failed: %s", method, error);
                        }
                } 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");

                        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. */

                        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);
                        }

                        (void) exec_with_listen_fds(exec_cmdline, fd_array, m);
                        /* This is only reached on failure, otherwise we continue with exec_cmdline. */
                        _exit(EXIT_FAILURE);
                }

                if (arg_quiet)
                        return r;

                pager_open(arg_pager_flags);

                sd_json_variant_dump(reply, arg_json_format_flags, stdout, NULL);
                return r;
        }

        return 0;
}

VERB(verb_validate_idl, "validate-idl", "[FILE]", 1, 2, 0, "Validate interface description");
static int verb_validate_idl(int argc, char *argv[], uintptr_t _data, void *userdata) {
        _cleanup_(sd_varlink_interface_freep) sd_varlink_interface *vi = NULL;
        _cleanup_free_ char *text = NULL;
        const char *fname;
        unsigned line = 1, column = 1;
        int r;

        fname = argc > 1 ? argv[1] : NULL;

        if (fname) {
                r = read_full_file(fname, &text, NULL);
                if (r < 0)
                        return log_error_errno(r, "Failed to read interface description file '%s': %m", fname);
        } else {
                r = read_full_stream(stdin, &text, NULL);
                if (r < 0)
                        return log_error_errno(r, "Failed to read interface description from stdin: %m");

                fname = "<stdin>";
        }

        r = sd_varlink_idl_parse(text, &line, &column, &vi);
        if (r == -EBADMSG)
                return log_error_errno(r, "%s:%u:%u: Bad syntax.", fname, line, column);
        if (r == -ENETUNREACH)
                return log_error_errno(r, "%s:%u:%u: Failed to parse interface description due an unresolved type.", fname, line, column);
        if (r < 0)
                return log_error_errno(r, "%s:%u:%u: Failed to parse interface description: %m", fname, line, column);

        r = varlink_idl_consistent(vi, LOG_ERR);
        if (r == -EUCLEAN)
                return log_error_errno(r, "Interface is inconsistent.");
        if (r == -ENOTUNIQ)
                return log_error_errno(r, "Field or symbol not unique in interface.");
        if (r < 0)
                return log_error_errno(r, "Failed to check interface for consistency: %m");

        if (arg_quiet)
                return 0;

        pager_open(arg_pager_flags);

        r = sd_varlink_idl_dump(stdout, vi, SD_VARLINK_IDL_FORMAT_COLOR_AUTO, break_columns());
        if (r < 0)
                return log_error_errno(r, "Failed to format parsed interface description: %m");

        return 0;
}

VERB_NOARG(verb_list_registry, "list-registry", "Show list of services in the service registry");
static int verb_list_registry(int argc, char *argv[], uintptr_t _data, void *userdata) {
        int r;

        assert(argc <= 1);

        _cleanup_free_ char *reg_path = NULL;
        r = runtime_directory_generic(arg_runtime_scope, "varlink/registry", &reg_path);
        if (r < 0)
                return log_error_errno(r, "Failed to determine registry path: %m");

        _cleanup_(table_unrefp) Table *table = table_new("interface", "entrypoint");
        if (!table)
                return log_oom();

        (void) table_set_sort(table, (size_t) 0);

        _cleanup_close_ int regfd = open(reg_path, O_DIRECTORY|O_CLOEXEC);
        if (regfd < 0)  {
                if (errno != ENOENT)
                        return log_error_errno(errno, "Failed to open '%s': %m", reg_path);
        } else {
                _cleanup_free_ DirectoryEntries *des = NULL;
                r = readdir_all(regfd, RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des);
                if (r < 0)
                        return log_error_errno(r, "Failed to enumerate '%s': %m", reg_path);

                FOREACH_ARRAY(i, des->entries, des->n_entries) {
                        struct dirent *de = *i;

                        if (!varlink_idl_interface_name_is_valid(de->d_name)) {
                                log_debug("Found file '%s' whose names does not qualify as valid Varlink interface name, skipping.", de->d_name);
                                continue;
                        }

                        _cleanup_free_ char *j = path_join(reg_path, de->d_name);
                        if (!j)
                                return log_oom();

                        switch (de->d_type) {
                        case DT_LNK: {
                                _cleanup_free_ char *resolved = NULL;

                                r = chase(j, /* root= */ NULL, CHASE_MUST_BE_SOCKET, &resolved, /* ret_fd= */ NULL);
                                if (r < 0) {
                                        log_warning_errno(r, "Failed to resolve '%s', skipping: %m", j);
                                        continue;
                                }

                                _cleanup_free_ char *address = strjoin("unix:", resolved);
                                if (!address)
                                        return log_oom();

                                r = table_add_many(
                                                table,
                                                TABLE_STRING, de->d_name,
                                                TABLE_STRING, address);
                                if (r < 0)
                                        return r;

                                break;
                        }

                        case DT_SOCK: {
                                _cleanup_free_ char *address = strjoin("unix:", j);
                                if (!address)
                                        return log_oom();

                                r = table_add_many(
                                                table,
                                                TABLE_STRING, de->d_name,
                                                TABLE_STRING, address);
                                if (r < 0)
                                        return r;

                                break;
                        }

                        default:
                                log_debug("Ignoring inode '%s' of unexpected type: %m", de->d_name);
                        }
                }
        }

        if (!table_isempty(table) || sd_json_format_enabled(arg_json_format_flags)) {
                r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, /* show_header= */ true);
                if (r < 0)
                        return log_error_errno(r, "Failed to output table: %m");
        }

        if (arg_legend && !sd_json_format_enabled(arg_json_format_flags)) {
                if (table_isempty(table))
                        printf("No services registered.\n");
                else
                        printf("\n%zu registered services listed.\n", table_get_rows(table) - 1);
        }

        return 0;
}

/* Build a minimal IDL from a qualified method name so that introspection works. The parsed interface is
 * returned to the caller who must keep it alive for the lifetime of the server
 * (sd_varlink_server_add_interface() borrows the pointer). */
static int varlink_server_add_interface_from_method(sd_varlink_server *s, const char *method, sd_varlink_interface **ret_interface) {
        assert(s);
        assert(method);
        assert(ret_interface);

        const char *dot = strrchr(method, '.');
        assert(dot);

        _cleanup_free_ char *interface_name = strndup(method, dot - method);
        if (!interface_name)
                return log_oom();

        /* Note that we do not need to put the upgrade flag comment here, it is added automatically
         * by varlink_idl_format_symbol() because of the SD_VARLINK_REQUIRES_UPGRADE flag. */
        _cleanup_free_ char *idl_text = strjoin(
                        "interface ", interface_name, "\n"
                        "\n"
                        "method ", dot + 1, " () -> ()\n");
        if (!idl_text)
                return log_oom();

        _cleanup_(sd_varlink_interface_freep) sd_varlink_interface *iface = NULL;
        int r = sd_varlink_idl_parse(idl_text, /* reterr_line= */ NULL, /* reterr_column= */ NULL, &iface);
        if (r < 0)
                return log_error_errno(r, "Failed to parse IDL for method '%s': %m", method);

        /* Mark the method as requiring the upgrade flag so introspection shows the annotation */
        assert(iface->symbols[0] && iface->symbols[0]->symbol_type == SD_VARLINK_METHOD);
        ((sd_varlink_symbol*) iface->symbols[0])->symbol_flags |= SD_VARLINK_REQUIRES_UPGRADE;

        r = sd_varlink_server_add_interface(s, iface);
        if (r < 0)
                return r;

        *ret_interface = TAKE_PTR(iface);

        return 0;
}

static int method_serve_upgrade(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
        char **exec_cmdline = ASSERT_PTR(userdata);
        _cleanup_close_ int input_fd = -EBADF, output_fd = -EBADF;
        int r;

        if (!FLAGS_SET(flags, SD_VARLINK_METHOD_UPGRADE))
                return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_UPGRADE, NULL);

        r = sd_varlink_reply_and_upgrade(link, /* parameters= */ NULL, &input_fd, &output_fd);
        if (r < 0)
                return log_error_errno(r, "Failed to upgrade connection: %m");

        /* Copy exec_cmdline before forking: pidref_safe_fork() calls rename_process() which
         * overwrites the argv area that exec_cmdline points into. */
        _cleanup_strv_free_ char **cmdline_copy = strv_copy(exec_cmdline);
        if (!cmdline_copy)
                return log_oom();

        r = pidref_safe_fork_full(
                        "(serve)",
                        (int[]) { input_fd, output_fd, STDERR_FILENO },
                        /* except_fds= */ NULL, /* n_except_fds= */ 0,
                        FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_REARRANGE_STDIO|FORK_DETACH|FORK_LOG,
                        /* ret= */ NULL);
        if (r < 0)
                return r;
        if (r == 0) {
                execvp(cmdline_copy[0], cmdline_copy);
                log_error_errno(errno, "Failed to execute '%s': %m", cmdline_copy[0]);
                _exit(EXIT_FAILURE);
        }

        return 0;
}

VERB(verb_serve, "serve", "METHOD CMDLINE…", 3, VERB_ANY, 0, "Serve a command via varlink protocol upgrade");
static int verb_serve(int argc, char *argv[], uintptr_t _data, void *userdata) {
        _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL;
        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
        const char *method;
        char **exec_cmdline;
        int r, n;

        assert(argc >= 3); /* Guaranteed by verb dispatch table */

        method = argv[1];
        exec_cmdline = argv + 2;

        r = varlink_idl_qualified_symbol_name_is_valid(method);
        if (r < 0)
                return log_error_errno(r, "Failed to validate method name '%s': %m", method);
        if (r == 0)
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid qualified method name: '%s'", method);

        /* Require socket activation */
        n = sd_listen_fds(/* unset_environment= */ true);
        if (n < 0)
                return log_error_errno(n, "Failed to determine passed file descriptors: %m");
        if (n == 0)
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No file descriptors passed via socket activation.");
        if (n > 1)
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected exactly one socket activation fd, got %d.", n);

        r = sd_event_default(&event);
        if (r < 0)
                return log_error_errno(r, "Failed to get event loop: %m");

        r = sd_varlink_server_new(&s, SD_VARLINK_SERVER_INHERIT_USERDATA|SD_VARLINK_SERVER_UPGRADABLE);
        if (r < 0)
                return log_error_errno(r, "Failed to allocate varlink server: %m");

        _cleanup_free_ char *description = strjoin("serve:", method);
        if (!description)
                return log_oom();

        r = sd_varlink_server_set_description(s, description);
        if (r < 0)
                return log_error_errno(r, "Failed to set server description: %m");

        r = sd_varlink_server_bind_method(s, method, method_serve_upgrade);
        if (r < 0)
                return log_error_errno(r, "Failed to bind method '%s': %m", method);

        _cleanup_(sd_varlink_interface_freep) sd_varlink_interface *iface = NULL;
        r = varlink_server_add_interface_from_method(s, method, &iface);
        if (r < 0)
                return log_error_errno(r, "Failed to add interface for method '%s': %m", method);

        sd_varlink_server_set_userdata(s, exec_cmdline);

        r = sd_varlink_server_listen_fd(s, SD_LISTEN_FDS_START);
        if (r < 0)
                return log_error_errno(r, "Failed to listen on socket activation fd: %m");

        r = sd_varlink_server_attach_event(s, event, SD_EVENT_PRIORITY_NORMAL);
        if (r < 0)
                return log_error_errno(r, "Failed to attach varlink server to event loop: %m");

        (void) sd_notify(/* unset_environment= */ false, "READY=1");

        r = sd_event_loop(event);
        if (r < 0)
                return log_error_errno(r, "Failed to run event loop: %m");

        return 0;
}

static int run(int argc, char *argv[]) {
        int r;

        log_setup();

        char **args = NULL;  /* unnecessary initialization to appease gcc <= 13 */
        r = parse_argv(argc, argv, &args);
        if (r <= 0)
                return r;

        return dispatch_verb(args, NULL);
}

DEFINE_MAIN_FUNCTION(run);
