From cdf6f34a2fd1448c5d1b75f4717c57b057dd51b2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 13 Jun 2024 09:29:10 +0200 Subject: [PATCH 1/6] io-util: add new helper fputs_with_newline() --- src/basic/io-util.c | 16 ++++++++++++++++ src/basic/io-util.h | 3 +++ src/core/unit.c | 6 ++---- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/basic/io-util.c b/src/basic/io-util.c index 6bcbef34136..e07e1a1e191 100644 --- a/src/basic/io-util.c +++ b/src/basic/io-util.c @@ -306,3 +306,19 @@ ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length) { return q - (const uint8_t*) p; } + +int fputs_with_newline(const char *s, FILE *f) { + assert(s); + assert(f); + + /* This is like fputs() but outputs a trailing newline char, but only if the string doesn't end in a + * newline anyway. Just like fputs() returns EOF on error. Otherwise returns 0 in case we didn't + * append a newline, > 0 otherwise. */ + + if (fputs(s, f) == EOF) + return EOF; + if (endswith(s, "\n")) + return 0; + + return fputc('\n', f) == EOF ? EOF : 1; +} diff --git a/src/basic/io-util.h b/src/basic/io-util.h index e027c1a878c..0d9007a304d 100644 --- a/src/basic/io-util.h +++ b/src/basic/io-util.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "macro.h" @@ -44,3 +45,5 @@ static inline bool FILE_SIZE_VALID_OR_INFINITY(uint64_t l) { return FILE_SIZE_VALID(l); } + +int fputs_with_newline(const char *s, FILE *f); diff --git a/src/core/unit.c b/src/core/unit.c index 35e790ebba5..99c1db3a1a6 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -33,6 +33,7 @@ #include "format-util.h" #include "id128-util.h" #include "install.h" +#include "io-util.h" #include "iovec-util.h" #include "label-util.h" #include "load-dropin.h" @@ -4569,10 +4570,7 @@ int unit_write_setting(Unit *u, UnitWriteFlags flags, const char *name, const ch if (u->transient_file) { /* When this is a transient unit file in creation, then let's not create a new drop-in but instead * write to the transient unit file. */ - fputs(data, u->transient_file); - - if (!endswith(data, "\n")) - fputc('\n', u->transient_file); + fputs_with_newline(data, u->transient_file); /* Remember which section we wrote this entry to */ u->last_section_private = !!(flags & UNIT_PRIVATE); From ef4bfa552422fb6cfec331e22367c072c9027ae2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 24 May 2024 15:23:24 +0200 Subject: [PATCH 2/6] pretty-print: make separator line grey Let's deemphasize the line in the output a bit. --- src/shared/pretty-print.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/pretty-print.c b/src/shared/pretty-print.c index c75f74a6c6e..2c46f3e2dcf 100644 --- a/src/shared/pretty-print.c +++ b/src/shared/pretty-print.c @@ -289,7 +289,7 @@ void print_separator(void) { size_t c = columns(); flockfile(stdout); - fputs_unlocked(ANSI_UNDERLINE, stdout); + fputs_unlocked(ANSI_GREY_UNDERLINE, stdout); for (size_t i = 0; i < c; i++) fputc_unlocked(' ', stdout); From 18863534f866b5ea38eab61ca2683544e85abeda Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 24 May 2024 15:24:23 +0200 Subject: [PATCH 3/6] varlinkctl: be friendly to later extensions of GetInfo Varlink call --- src/varlinkctl/varlinkctl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index 474ec9def82..9db3a9250c8 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -235,7 +235,7 @@ static int verb_info(int argc, char *argv[], void *userdata) { }; _cleanup_(get_info_data_done) GetInfoData data = {}; - r = sd_json_dispatch(reply, dispatch_table, SD_JSON_LOG, &data); + r = sd_json_dispatch(reply, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &data); if (r < 0) return r; From 2475b0e81a38689b647168680a9521488388fd4c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 24 May 2024 15:25:23 +0200 Subject: [PATCH 4/6] varlinkctl: make interface argument to "introspect" optional, and allow more than one let's make it easier to use the introspection functionality of "varlinkctl": if no interface name is shown, display the introspection data of all available interfaces. Moreover, allow that multiple interfaces can be listed, in which case we enumerate them all. This relieves the user from having to list interfaces first in order to find the ones which to introspect. --- man/varlinkctl.xml | 11 ++-- src/varlinkctl/varlinkctl.c | 108 ++++++++++++++++++++++++------------ 2 files changed, 77 insertions(+), 42 deletions(-) diff --git a/man/varlinkctl.xml b/man/varlinkctl.xml index eefc3d1de0e..5a3e1d00504 100644 --- a/man/varlinkctl.xml +++ b/man/varlinkctl.xml @@ -41,7 +41,7 @@ OPTIONS introspect ADDRESS - INTERFACE + INTERFACE @@ -120,11 +120,12 @@ - introspect ADDRESS INTERFACE + introspect ADDRESS [INTERFACE…] - Show interface definition of the specified interface provided by the specified - service. Expects a service address in one of the formats described above and a Varlink interface - name. + Show the interface definitions of the specified interfaces provided by the specified + service. Expects a service address in one of the formats described above and optionally one or more + Varlink interface names. If no interface names are specified, shows all provided interfaces by the + service. diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index 9db3a9250c8..6d9a6a97210 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -6,6 +6,7 @@ #include "fd-util.h" #include "fileio.h" #include "format-table.h" +#include "io-util.h" #include "main-func.h" #include "pager.h" #include "parse-argument.h" @@ -37,7 +38,7 @@ static int help(void) { " info ADDRESS Show service information\n" " list-interfaces ADDRESS\n" " List interfaces implemented by service\n" - " introspect ADDRESS INTERFACE\n" + " introspect ADDRESS [INTERFACE…]\n" " Show interface definition\n" " call ADDRESS METHOD [PARAMS]\n" " Invoke method\n" @@ -289,56 +290,89 @@ typedef struct GetInterfaceDescriptionData { static int verb_introspect(int argc, char *argv[], void *userdata) { _cleanup_(varlink_unrefp) Varlink *vl = NULL; - const char *url, *interface; + _cleanup_strv_free_ char **auto_interfaces = NULL; + char **interfaces; + const char *url; int r; - assert(argc == 3); + assert(argc >= 2); url = argv[1]; - interface = argv[2]; + interfaces = strv_skip(argv, 2); r = varlink_connect_auto(&vl, url); if (r < 0) return r; - sd_json_variant *reply = NULL; - r = varlink_callb_and_log( - vl, - "org.varlink.service.GetInterfaceDescription", - &reply, - SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_STRING("interface", interface))); - if (r < 0) - return r; + if (strv_isempty(interfaces)) { + sd_json_variant *reply = NULL; - pager_open(arg_pager_flags); + /* If no interface is specified, introspect all of them */ - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { - static const struct sd_json_dispatch_field dispatch_table[] = { - { "description", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, SD_JSON_MANDATORY }, - {} - }; - _cleanup_(varlink_interface_freep) VarlinkInterface *vi = NULL; - const char *description = NULL; - unsigned line = 0, column = 0; - - r = sd_json_dispatch(reply, dispatch_table, SD_JSON_LOG, &description); + r = varlink_call_and_log(vl, "org.varlink.service.GetInfo", /* parameters= */ NULL, &reply); if (r < 0) return r; - /* Try to parse the returned description, so that we can add syntax highlighting */ - r = varlink_idl_parse(ASSERT_PTR(description), &line, &column, &vi); - if (r < 0) { - log_warning_errno(r, "Failed to parse returned interface description at %u:%u, showing raw interface description: %m", line, column); + const struct sd_json_dispatch_field dispatch_table[] = { + { "interfaces", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, PTR_TO_SIZE(&auto_interfaces), SD_JSON_MANDATORY }, + {} + }; - fputs(description, stdout); - if (!endswith(description, "\n")) - fputs("\n", stdout); - } else { - r = varlink_idl_dump(stdout, /* use_colors= */ -1, vi); + r = sd_json_dispatch(reply, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, NULL); + 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(strv_uniq(auto_interfaces)); + } + + /* Automatically switch on JSON_SEQ if we output multiple JSON objects */ + if (strv_length(interfaces) > 1) + arg_json_format_flags |= SD_JSON_FORMAT_SEQ; + + STRV_FOREACH(i, interfaces) { + sd_json_variant *reply = NULL; + r = varlink_callb_and_log( + vl, + "org.varlink.service.GetInterfaceDescription", + &reply, + SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_STRING("interface", *i))); + if (r < 0) + return r; + + pager_open(arg_pager_flags); + + if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + static const struct sd_json_dispatch_field dispatch_table[] = { + { "description", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, SD_JSON_MANDATORY }, + {} + }; + _cleanup_(varlink_interface_freep) VarlinkInterface *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 log_error_errno(r, "Failed to format parsed interface description: %m"); - } - } else - sd_json_variant_dump(reply, arg_json_format_flags, stdout, NULL); + return r; + + if (i > interfaces) + print_separator(); + + /* Try to parse the returned description, so that we can add syntax highlighting */ + r = varlink_idl_parse(ASSERT_PTR(description), &line, &column, &vi); + if (r < 0) { + log_warning_errno(r, "Failed to parse returned interface description at %u:%u, showing raw interface description: %m", line, column); + + fputs_with_newline(description, stdout); + } else { + r = varlink_idl_dump(stdout, /* use_colors= */ -1, vi); + if (r < 0) + return log_error_errno(r, "Failed to format parsed interface description: %m"); + } + } else + sd_json_variant_dump(reply, arg_json_format_flags, stdout, NULL); + } return 0; } @@ -538,7 +572,7 @@ static int varlinkctl_main(int argc, char *argv[]) { static const Verb verbs[] = { { "info", 2, 2, 0, verb_info }, { "list-interfaces", 2, 2, 0, verb_info }, - { "introspect", 3, 3, 0, verb_introspect }, + { "introspect", 2, VERB_ANY, 0, verb_introspect }, { "call", 3, 4, 0, verb_call }, { "validate-idl", 1, 2, 0, verb_validate_idl }, { "help", VERB_ANY, VERB_ANY, 0, verb_help }, From 16cfe84c2495c1d9e753e2b8e21b69bea21ebaf4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 24 May 2024 16:34:12 +0200 Subject: [PATCH 5/6] varlinkctl: add new list-methods verb For putting together "varlinkctl call" command lines it's useful to quickly enumerate all methods implemented by a service. Hence, let's add a new "list-methods" which uses the introspection data of a service to quickly list methods. This is implemented as a special flavour of the "introspect" logic, and just suppresses all output except for the method names. --- man/varlinkctl.xml | 11 ++++++++ src/varlinkctl/varlinkctl.c | 52 ++++++++++++++++++++++++++++++++----- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/man/varlinkctl.xml b/man/varlinkctl.xml index 5a3e1d00504..0dedc1e4db5 100644 --- a/man/varlinkctl.xml +++ b/man/varlinkctl.xml @@ -119,6 +119,17 @@ + + list-methods ADDRESS [INTERFACE…] + + Show list of methods implemented by the specified service. Expects a service address + in one of the formats described above as well as one or more interface names. If no interface name is + specified, lists all methods of all interfaces implemented by the service, otherwise just the methods + in the specified services. + + + + introspect ADDRESS [INTERFACE…] diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index 6d9a6a97210..739770d2961 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -38,6 +38,9 @@ static int help(void) { " info ADDRESS Show service information\n" " list-interfaces ADDRESS\n" " List interfaces implemented by service\n" + " list-methods ADDRESS [INTERFACE…]\n" + " List methods implemented by services or specific\n" + " interfaces\n" " introspect ADDRESS [INTERFACE…]\n" " Show interface definition\n" " call ADDRESS METHOD [PARAMS]\n" @@ -293,9 +296,11 @@ static int verb_introspect(int argc, char *argv[], void *userdata) { _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); @@ -328,9 +333,11 @@ static int verb_introspect(int argc, char *argv[], void *userdata) { } /* Automatically switch on JSON_SEQ if we output multiple JSON objects */ - if (strv_length(interfaces) > 1) + 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_callb_and_log( @@ -341,9 +348,7 @@ static int verb_introspect(int argc, char *argv[], void *userdata) { if (r < 0) return r; - pager_open(arg_pager_flags); - - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF) || list_methods) { static const struct sd_json_dispatch_field dispatch_table[] = { { "description", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, SD_JSON_MANDATORY }, {} @@ -356,22 +361,56 @@ static int verb_introspect(int argc, char *argv[], void *userdata) { if (r < 0) return r; - if (i > interfaces) + if (!list_methods && i > interfaces) print_separator(); /* Try to parse the returned description, so that we can add syntax highlighting */ r = 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(description, stdout); + } else if (list_methods) { + for (const VarlinkSymbol *const *y = vi->symbols, *symbol; (symbol = *y); y++) { + if (symbol->symbol_type != 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 = varlink_idl_dump(stdout, /* use_colors= */ -1, vi); if (r < 0) return log_error_errno(r, "Failed to format parsed interface description: %m"); } - } else + } 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(strv_uniq(methods)); + + if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) + 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; @@ -573,6 +612,7 @@ static int varlinkctl_main(int argc, char *argv[]) { { "info", 2, 2, 0, verb_info }, { "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 }, { "validate-idl", 1, 2, 0, verb_validate_idl }, { "help", VERB_ANY, VERB_ANY, 0, verb_help }, From e1ef88e56e3bdb95c52c80d05df47ed603ba4abe Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 24 May 2024 17:02:52 +0200 Subject: [PATCH 6/6] test: add test for new varlinkctl features --- test/units/TEST-74-AUX-UTILS.varlinkctl.sh | 23 +++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh index 1d5a98b214b..9de014bdad4 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -25,6 +25,15 @@ varlinkctl info -j /run/systemd/journal/io.systemd.journal | jq . varlinkctl list-interfaces /run/systemd/journal/io.systemd.journal varlinkctl list-interfaces -j /run/systemd/journal/io.systemd.journal | jq . +varlinkctl list-methods /run/systemd/journal/io.systemd.journal +varlinkctl list-methods -j /run/systemd/journal/io.systemd.journal | jq . + +varlinkctl list-methods /run/systemd/journal/io.systemd.journal io.systemd.Journal +varlinkctl list-methods -j /run/systemd/journal/io.systemd.journal io.systemd.Journal | jq . + +varlinkctl introspect /run/systemd/journal/io.systemd.journal +varlinkctl introspect -j /run/systemd/journal/io.systemd.journal | jq --seq . + varlinkctl introspect /run/systemd/journal/io.systemd.journal io.systemd.Journal varlinkctl introspect -j /run/systemd/journal/io.systemd.journal io.systemd.Journal | jq . @@ -52,6 +61,7 @@ if [[ -x /usr/lib/systemd/systemd-pcrextend ]]; then varlinkctl info exec:/usr/lib/systemd/systemd-pcrextend varlinkctl list-interfaces /usr/lib/systemd/systemd-pcrextend varlinkctl introspect /usr/lib/systemd/systemd-pcrextend io.systemd.PCRExtend + varlinkctl introspect /usr/lib/systemd/systemd-pcrextend fi # SSH transport @@ -83,10 +93,18 @@ SYSTEMD_SSH="$SSHBINDIR/ssh" varlinkctl info ssh:foobar:/run/systemd/journal/io. # Go through all varlink sockets we can find under /run/systemd/ for some extra coverage find /run/systemd/ -name "io.systemd*" -type s | while read -r socket; do varlinkctl info "$socket" + varlinkctl info -j "$socket" + varlinkctl list-interfaces "$socket" + varlinkctl list-interfaces -j "$socket" + varlinkctl list-methods "$socket" + varlinkctl list-methods -j "$socket" + varlinkctl introspect "$socket" + varlinkctl introspect -j "$socket" varlinkctl list-interfaces "$socket" | while read -r interface; do varlinkctl introspect "$socket" "$interface" done + done (! varlinkctl) @@ -104,9 +122,12 @@ done (! varlinkctl list-interfaces) (! varlinkctl list-interfaces "") (! varlinkctl introspect) -(! varlinkctl introspect /run/systemd/journal/io.systemd.journal) (! varlinkctl introspect /run/systemd/journal/io.systemd.journal "") (! varlinkctl introspect "" "") +(! varlinkctl list-methods /run/systemd/journal/io.systemd.journal "") +(! varlinkctl list-methods -j /run/systemd/journal/io.systemd.journal "") +(! varlinkctl list-methods "") +(! varlinkctl list-methods -j "") (! varlinkctl call) (! varlinkctl call "") (! varlinkctl call "" "")