Merge pull request #33012 from poettering/varlinkctl-list-methods

varlinkctl: make interface parameter for "varlinkctl introspect" optional, and add "list-methods" verb
This commit is contained in:
Lennart Poettering 2024-06-13 11:05:07 +02:00 committed by GitHub
commit 639256f380
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 172 additions and 48 deletions

View file

@ -41,7 +41,7 @@
<arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="plain">introspect</arg> <arg choice="plain">introspect</arg>
<arg choice="plain"><replaceable>ADDRESS</replaceable></arg> <arg choice="plain"><replaceable>ADDRESS</replaceable></arg>
<arg choice="plain"><replaceable>INTERFACE</replaceable></arg> <arg choice="opt"><replaceable>INTERFACE</replaceable></arg>
</cmdsynopsis> </cmdsynopsis>
<cmdsynopsis> <cmdsynopsis>
@ -120,11 +120,23 @@
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term><command>introspect</command> <replaceable>ADDRESS</replaceable> <replaceable>INTERFACE</replaceable></term> <term><command>list-methods</command> <replaceable>ADDRESS</replaceable> [<replaceable>INTERFACE</replaceable>]</term>
<listitem><para>Show interface definition of the specified interface provided by the specified <listitem><para>Show list of methods implemented by the specified service. Expects a service address
service. Expects a service address in one of the formats described above and a Varlink interface in one of the formats described above as well as one or more interface names. If no interface name is
name.</para> specified, lists all methods of all interfaces implemented by the service, otherwise just the methods
in the specified services.</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
<varlistentry>
<term><command>introspect</command> <replaceable>ADDRESS</replaceable> [<replaceable>INTERFACE…</replaceable>]</term>
<listitem><para>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.</para>
<xi:include href="version-info.xml" xpointer="v255"/></listitem> <xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry> </varlistentry>

View file

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

View file

@ -5,6 +5,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h>
#include <sys/types.h> #include <sys/types.h>
#include "macro.h" #include "macro.h"
@ -44,3 +45,5 @@ static inline bool FILE_SIZE_VALID_OR_INFINITY(uint64_t l) {
return FILE_SIZE_VALID(l); return FILE_SIZE_VALID(l);
} }
int fputs_with_newline(const char *s, FILE *f);

View file

@ -33,6 +33,7 @@
#include "format-util.h" #include "format-util.h"
#include "id128-util.h" #include "id128-util.h"
#include "install.h" #include "install.h"
#include "io-util.h"
#include "iovec-util.h" #include "iovec-util.h"
#include "label-util.h" #include "label-util.h"
#include "load-dropin.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) { if (u->transient_file) {
/* When this is a transient unit file in creation, then let's not create a new drop-in but instead /* 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. */ * write to the transient unit file. */
fputs(data, u->transient_file); fputs_with_newline(data, u->transient_file);
if (!endswith(data, "\n"))
fputc('\n', u->transient_file);
/* Remember which section we wrote this entry to */ /* Remember which section we wrote this entry to */
u->last_section_private = !!(flags & UNIT_PRIVATE); u->last_section_private = !!(flags & UNIT_PRIVATE);

View file

@ -289,7 +289,7 @@ void print_separator(void) {
size_t c = columns(); size_t c = columns();
flockfile(stdout); flockfile(stdout);
fputs_unlocked(ANSI_UNDERLINE, stdout); fputs_unlocked(ANSI_GREY_UNDERLINE, stdout);
for (size_t i = 0; i < c; i++) for (size_t i = 0; i < c; i++)
fputc_unlocked(' ', stdout); fputc_unlocked(' ', stdout);

View file

@ -6,6 +6,7 @@
#include "fd-util.h" #include "fd-util.h"
#include "fileio.h" #include "fileio.h"
#include "format-table.h" #include "format-table.h"
#include "io-util.h"
#include "main-func.h" #include "main-func.h"
#include "pager.h" #include "pager.h"
#include "parse-argument.h" #include "parse-argument.h"
@ -37,7 +38,10 @@ static int help(void) {
" info ADDRESS Show service information\n" " info ADDRESS Show service information\n"
" list-interfaces ADDRESS\n" " list-interfaces ADDRESS\n"
" List interfaces implemented by service\n" " List interfaces implemented by service\n"
" introspect ADDRESS INTERFACE\n" " list-methods ADDRESS [INTERFACE…]\n"
" List methods implemented by services or specific\n"
" interfaces\n"
" introspect ADDRESS [INTERFACE…]\n"
" Show interface definition\n" " Show interface definition\n"
" call ADDRESS METHOD [PARAMS]\n" " call ADDRESS METHOD [PARAMS]\n"
" Invoke method\n" " Invoke method\n"
@ -235,7 +239,7 @@ static int verb_info(int argc, char *argv[], void *userdata) {
}; };
_cleanup_(get_info_data_done) GetInfoData data = {}; _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) if (r < 0)
return r; return r;
@ -289,56 +293,125 @@ typedef struct GetInterfaceDescriptionData {
static int verb_introspect(int argc, char *argv[], void *userdata) { static int verb_introspect(int argc, char *argv[], void *userdata) {
_cleanup_(varlink_unrefp) Varlink *vl = NULL; _cleanup_(varlink_unrefp) Varlink *vl = NULL;
const char *url, *interface; _cleanup_strv_free_ char **auto_interfaces = NULL;
char **interfaces;
const char *url;
bool list_methods;
int r; int r;
assert(argc == 3); assert(argc >= 2);
list_methods = streq(argv[0], "list-methods");
url = argv[1]; url = argv[1];
interface = argv[2]; interfaces = strv_skip(argv, 2);
r = varlink_connect_auto(&vl, url); r = varlink_connect_auto(&vl, url);
if (r < 0) if (r < 0)
return r; return r;
sd_json_variant *reply = NULL; if (strv_isempty(interfaces)) {
r = varlink_callb_and_log( sd_json_variant *reply = NULL;
vl,
"org.varlink.service.GetInterfaceDescription",
&reply,
SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_STRING("interface", interface)));
if (r < 0)
return r;
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)) { r = varlink_call_and_log(vl, "org.varlink.service.GetInfo", /* parameters= */ NULL, &reply);
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);
if (r < 0) if (r < 0)
return r; return r;
/* Try to parse the returned description, so that we can add syntax highlighting */ const struct sd_json_dispatch_field dispatch_table[] = {
r = varlink_idl_parse(ASSERT_PTR(description), &line, &column, &vi); { "interfaces", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, PTR_TO_SIZE(&auto_interfaces), SD_JSON_MANDATORY },
if (r < 0) { {}
log_warning_errno(r, "Failed to parse returned interface description at %u:%u, showing raw interface description: %m", line, column); };
fputs(description, stdout); r = sd_json_dispatch(reply, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, NULL);
if (!endswith(description, "\n")) if (r < 0)
fputs("\n", stdout); return r;
} else {
r = varlink_idl_dump(stdout, /* use_colors= */ -1, vi); 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 (!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(
vl,
"org.varlink.service.GetInterfaceDescription",
&reply,
SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_STRING("interface", *i)));
if (r < 0)
return r;
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 },
{}
};
_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) if (r < 0)
return log_error_errno(r, "Failed to format parsed interface description: %m"); return r;
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 {
pager_open(arg_pager_flags);
sd_json_variant_dump(reply, arg_json_format_flags, stdout, NULL);
} }
} else }
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; return 0;
} }
@ -538,7 +611,8 @@ static int varlinkctl_main(int argc, char *argv[]) {
static const Verb verbs[] = { static const Verb verbs[] = {
{ "info", 2, 2, 0, verb_info }, { "info", 2, 2, 0, verb_info },
{ "list-interfaces", 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 },
{ "list-methods", 2, VERB_ANY, 0, verb_introspect },
{ "call", 3, 4, 0, verb_call }, { "call", 3, 4, 0, verb_call },
{ "validate-idl", 1, 2, 0, verb_validate_idl }, { "validate-idl", 1, 2, 0, verb_validate_idl },
{ "help", VERB_ANY, VERB_ANY, 0, verb_help }, { "help", VERB_ANY, VERB_ANY, 0, verb_help },

View file

@ -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 /run/systemd/journal/io.systemd.journal
varlinkctl list-interfaces -j /run/systemd/journal/io.systemd.journal | jq . 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 /run/systemd/journal/io.systemd.journal io.systemd.Journal
varlinkctl introspect -j /run/systemd/journal/io.systemd.journal io.systemd.Journal | jq . 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 info exec:/usr/lib/systemd/systemd-pcrextend
varlinkctl list-interfaces /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 io.systemd.PCRExtend
varlinkctl introspect /usr/lib/systemd/systemd-pcrextend
fi fi
# SSH transport # 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 # 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 find /run/systemd/ -name "io.systemd*" -type s | while read -r socket; do
varlinkctl info "$socket" 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 list-interfaces "$socket" | while read -r interface; do
varlinkctl introspect "$socket" "$interface" varlinkctl introspect "$socket" "$interface"
done done
done done
(! varlinkctl) (! varlinkctl)
@ -104,9 +122,12 @@ done
(! varlinkctl list-interfaces) (! varlinkctl list-interfaces)
(! varlinkctl list-interfaces "") (! varlinkctl list-interfaces "")
(! varlinkctl introspect) (! varlinkctl introspect)
(! varlinkctl introspect /run/systemd/journal/io.systemd.journal)
(! varlinkctl introspect /run/systemd/journal/io.systemd.journal "") (! varlinkctl introspect /run/systemd/journal/io.systemd.journal "")
(! varlinkctl introspect "" "") (! 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 "") (! varlinkctl call "")
(! varlinkctl call "" "") (! varlinkctl call "" "")