busctl: add a --json= output mode

A new switch "-j" or "--json=" is added which transforms dbus
marshalling into json. This is extremely useful in combination with
tools such as "jq" to process bus calls further.
This commit is contained in:
Lennart Poettering 2018-07-04 15:28:09 +02:00 committed by Zbigniew Jędrzejewski-Szmek
parent 960d4b29d7
commit 9cebb234b1
2 changed files with 458 additions and 26 deletions

View File

@ -143,6 +143,28 @@
</listitem>
</varlistentry>
<varlistentry>
<term><option>--json=</option><replaceable>MODE</replaceable></term>
<listitem>
<para>When used with the <command>call</command> or <command>get-property</command> command, shows output
formatted as JSON. Expects one of <literal>short</literal> (for the shortest possible output without any
redundant whitespace or line breaks) or <literal>pretty</literal> (for a pretty version of the same, with
indentation and line breaks). Note that transformation from D-Bus marshalling to JSON is done in a loss-less
way, which means type information is embedded into the JSON object tree.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-j</option></term>
<listitem>
<para>Equivalent to <option>--json=pretty</option> when invoked interactively from a terminal. Otherwise
equivalent to <option>--json=short</option>, in particular when the output is piped to some other
program.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--expect-reply=</option><replaceable>BOOL</replaceable></term>

View File

@ -15,6 +15,7 @@
#include "escape.h"
#include "fd-util.h"
#include "fileio.h"
#include "json.h"
#include "locale-util.h"
#include "log.h"
#include "pager.h"
@ -27,6 +28,11 @@
#include "util.h"
#include "verbs.h"
static enum {
JSON_OFF,
JSON_SHORT,
JSON_PRETTY,
} arg_json = JSON_OFF;
static bool arg_no_pager = false;
static bool arg_legend = true;
static const char *arg_address = NULL;
@ -1545,6 +1551,359 @@ static int message_append_cmdline(sd_bus_message *m, const char *signature, char
return 0;
}
static int json_transform_one(sd_bus_message *m, JsonVariant **ret);
static int json_transform_array_or_struct(sd_bus_message *m, JsonVariant **ret) {
size_t n_elements = 0, n_allocated = 0;
JsonVariant **elements = NULL;
int r;
assert(m);
assert(ret);
for (;;) {
r = sd_bus_message_at_end(m, false);
if (r < 0) {
bus_log_parse_error(r);
goto finish;
}
if (r > 0)
break;
if (!GREEDY_REALLOC(elements, n_allocated, n_elements + 1)) {
r = log_oom();
goto finish;
}
r = json_transform_one(m, elements + n_elements);
if (r < 0)
goto finish;
n_elements++;
}
r = json_variant_new_array(ret, elements, n_elements);
finish:
json_variant_unref_many(elements, n_elements);
free(elements);
return r;
}
static int json_transform_variant(sd_bus_message *m, const char *contents, JsonVariant **ret) {
_cleanup_(json_variant_unrefp) JsonVariant *type = NULL, *value = NULL;
int r;
assert(m);
assert(contents);
assert(ret);
r = json_transform_one(m, &value);
if (r < 0)
return r;
r = json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("type", JSON_BUILD_STRING(contents)),
JSON_BUILD_PAIR("data", JSON_BUILD_VARIANT(value))));
if (r < 0)
return log_oom();
return r;
}
static int json_transform_dict_array(sd_bus_message *m, JsonVariant **ret) {
size_t n_elements = 0, n_allocated = 0;
JsonVariant **elements = NULL;
int r;
assert(m);
assert(ret);
for (;;) {
const char *contents;
char type;
r = sd_bus_message_at_end(m, false);
if (r < 0) {
bus_log_parse_error(r);
goto finish;
}
if (r > 0)
break;
r = sd_bus_message_peek_type(m, &type, &contents);
if (r < 0)
return r;
assert(type == 'e');
if (!GREEDY_REALLOC(elements, n_allocated, n_elements + 2)) {
r = log_oom();
goto finish;
}
r = sd_bus_message_enter_container(m, type, contents);
if (r < 0) {
bus_log_parse_error(r);
goto finish;
}
r = json_transform_one(m, elements + n_elements);
if (r < 0)
goto finish;
n_elements++;
r = json_transform_one(m, elements + n_elements);
if (r < 0)
goto finish;
n_elements++;
r = sd_bus_message_exit_container(m);
if (r < 0) {
bus_log_parse_error(r);
goto finish;
}
}
r = json_variant_new_object(ret, elements, n_elements);
finish:
json_variant_unref_many(elements, n_elements);
free(elements);
return r;
}
static int json_transform_one(sd_bus_message *m, JsonVariant **ret) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
const char *contents;
char type;
int r;
assert(m);
assert(ret);
r = sd_bus_message_peek_type(m, &type, &contents);
if (r < 0)
return bus_log_parse_error(r);
switch (type) {
case SD_BUS_TYPE_BYTE: {
uint8_t b;
r = sd_bus_message_read_basic(m, type, &b);
if (r < 0)
return bus_log_parse_error(r);
r = json_variant_new_unsigned(&v, b);
if (r < 0)
return log_error_errno(r, "Failed to transform byte: %m");
break;
}
case SD_BUS_TYPE_BOOLEAN: {
int b;
r = sd_bus_message_read_basic(m, type, &b);
if (r < 0)
return bus_log_parse_error(r);
r = json_variant_new_boolean(&v, b);
if (r < 0)
return log_error_errno(r, "Failed to transform boolean: %m");
break;
}
case SD_BUS_TYPE_INT16: {
int16_t b;
r = sd_bus_message_read_basic(m, type, &b);
if (r < 0)
return bus_log_parse_error(r);
r = json_variant_new_integer(&v, b);
if (r < 0)
return log_error_errno(r, "Failed to transform int16: %m");
break;
}
case SD_BUS_TYPE_UINT16: {
uint16_t b;
r = sd_bus_message_read_basic(m, type, &b);
if (r < 0)
return bus_log_parse_error(r);
r = json_variant_new_unsigned(&v, b);
if (r < 0)
return log_error_errno(r, "Failed to transform uint16: %m");
break;
}
case SD_BUS_TYPE_INT32: {
int32_t b;
r = sd_bus_message_read_basic(m, type, &b);
if (r < 0)
return bus_log_parse_error(r);
r = json_variant_new_integer(&v, b);
if (r < 0)
return log_error_errno(r, "Failed to transform int32: %m");
break;
}
case SD_BUS_TYPE_UINT32: {
uint32_t b;
r = sd_bus_message_read_basic(m, type, &b);
if (r < 0)
return bus_log_parse_error(r);
r = json_variant_new_unsigned(&v, b);
if (r < 0)
return log_error_errno(r, "Failed to transform uint32: %m");
break;
}
case SD_BUS_TYPE_INT64: {
int64_t b;
r = sd_bus_message_read_basic(m, type, &b);
if (r < 0)
return bus_log_parse_error(r);
r = json_variant_new_integer(&v, b);
if (r < 0)
return log_error_errno(r, "Failed to transform int64: %m");
break;
}
case SD_BUS_TYPE_UINT64: {
uint64_t b;
r = sd_bus_message_read_basic(m, type, &b);
if (r < 0)
return bus_log_parse_error(r);
r = json_variant_new_unsigned(&v, b);
if (r < 0)
return log_error_errno(r, "Failed to transform uint64: %m");
break;
}
case SD_BUS_TYPE_DOUBLE: {
double d;
r = sd_bus_message_read_basic(m, type, &d);
if (r < 0)
return bus_log_parse_error(r);
r = json_variant_new_real(&v, d);
if (r < 0)
return log_error_errno(r, "Failed to transform double: %m");
break;
}
case SD_BUS_TYPE_STRING:
case SD_BUS_TYPE_OBJECT_PATH:
case SD_BUS_TYPE_SIGNATURE: {
const char *s;
r = sd_bus_message_read_basic(m, type, &s);
if (r < 0)
return bus_log_parse_error(r);
r = json_variant_new_string(&v, s);
if (r < 0)
return log_error_errno(r, "Failed to transform double: %m");
break;
}
case SD_BUS_TYPE_UNIX_FD:
r = sd_bus_message_read_basic(m, type, NULL);
if (r < 0)
return bus_log_parse_error(r);
r = json_variant_new_null(&v);
if (r < 0)
return log_error_errno(r, "Failed to transform fd: %m");
break;
case SD_BUS_TYPE_ARRAY:
case SD_BUS_TYPE_VARIANT:
case SD_BUS_TYPE_STRUCT:
r = sd_bus_message_enter_container(m, type, contents);
if (r < 0)
return bus_log_parse_error(r);
if (type == SD_BUS_TYPE_VARIANT)
r = json_transform_variant(m, contents, &v);
else if (type == SD_BUS_TYPE_ARRAY && contents[0] == '{')
r = json_transform_dict_array(m, &v);
else
r = json_transform_array_or_struct(m, &v);
if (r < 0)
return r;
r = sd_bus_message_exit_container(m);
if (r < 0)
return bus_log_parse_error(r);
break;
default:
assert_not_reached("Unexpected element type");
}
*ret = TAKE_PTR(v);
return 0;
}
static int json_transform_message(sd_bus_message *m, JsonVariant **ret) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
const char *type;
int r;
assert(m);
assert(ret);
assert_se(type = sd_bus_message_get_signature(m, false));
r = json_transform_array_or_struct(m, &v);
if (r < 0)
return r;
r = json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("type", JSON_BUILD_STRING(type)),
JSON_BUILD_PAIR("data", JSON_BUILD_VARIANT(v))));
if (r < 0)
return log_oom();
return 0;
}
static void json_dump_with_flags(JsonVariant *v, FILE *f) {
json_variant_dump(v,
(arg_json == JSON_PRETTY ? JSON_FORMAT_PRETTY : JSON_FORMAT_NEWLINE) |
colors_enabled() * JSON_FORMAT_COLOR,
f, NULL);
}
static int call(int argc, char **argv, void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
@ -1604,7 +1963,19 @@ static int call(int argc, char **argv, void *userdata) {
if (r == 0 && !arg_quiet) {
if (arg_verbose) {
if (arg_json != JSON_OFF) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
if (arg_json != JSON_SHORT)
(void) pager_open(arg_no_pager, false);
r = json_transform_message(reply, &v);
if (r < 0)
return r;
json_dump_with_flags(v, stdout);
} else if (arg_verbose) {
(void) pager_open(arg_no_pager, false);
r = bus_message_dump(reply, stdout, 0);
@ -1653,7 +2024,19 @@ static int get_property(int argc, char **argv, void *userdata) {
if (r < 0)
return bus_log_parse_error(r);
if (arg_verbose) {
if (arg_json != JSON_OFF) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
if (arg_json != JSON_SHORT)
(void) pager_open(arg_no_pager, false);
r = json_transform_variant(reply, contents, &v);
if (r < 0)
return r;
json_dump_with_flags(v, stdout);
} else if (arg_verbose) {
(void) pager_open(arg_no_pager, false);
r = bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_SUBTREE_ONLY);
@ -1750,6 +2133,8 @@ static int help(void) {
" --list Don't show tree, but simple object path list\n"
" -q --quiet Don't show method call reply\n"
" --verbose Show result values in long format\n"
" --json=MODE Output as JSON\n"
" -j Same as --json=pretty on tty, --json=short otherwise\n"
" --expect-reply=BOOL Expect a method call reply\n"
" --auto-start=BOOL Auto-start destination service\n"
" --allow-interactive-authorization=BOOL\n"
@ -1807,33 +2192,35 @@ static int parse_argv(int argc, char *argv[]) {
ARG_TIMEOUT,
ARG_AUGMENT_CREDS,
ARG_WATCH_BIND,
ARG_JSON,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "system", no_argument, NULL, ARG_SYSTEM },
{ "user", no_argument, NULL, ARG_USER },
{ "address", required_argument, NULL, ARG_ADDRESS },
{ "show-machine", no_argument, NULL, ARG_SHOW_MACHINE },
{ "unique", no_argument, NULL, ARG_UNIQUE },
{ "acquired", no_argument, NULL, ARG_ACQUIRED },
{ "activatable", no_argument, NULL, ARG_ACTIVATABLE },
{ "match", required_argument, NULL, ARG_MATCH },
{ "host", required_argument, NULL, 'H' },
{ "machine", required_argument, NULL, 'M' },
{ "size", required_argument, NULL, ARG_SIZE },
{ "list", no_argument, NULL, ARG_LIST },
{ "quiet", no_argument, NULL, 'q' },
{ "verbose", no_argument, NULL, ARG_VERBOSE },
{ "expect-reply", required_argument, NULL, ARG_EXPECT_REPLY },
{ "auto-start", required_argument, NULL, ARG_AUTO_START },
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "system", no_argument, NULL, ARG_SYSTEM },
{ "user", no_argument, NULL, ARG_USER },
{ "address", required_argument, NULL, ARG_ADDRESS },
{ "show-machine", no_argument, NULL, ARG_SHOW_MACHINE },
{ "unique", no_argument, NULL, ARG_UNIQUE },
{ "acquired", no_argument, NULL, ARG_ACQUIRED },
{ "activatable", no_argument, NULL, ARG_ACTIVATABLE },
{ "match", required_argument, NULL, ARG_MATCH },
{ "host", required_argument, NULL, 'H' },
{ "machine", required_argument, NULL, 'M' },
{ "size", required_argument, NULL, ARG_SIZE },
{ "list", no_argument, NULL, ARG_LIST },
{ "quiet", no_argument, NULL, 'q' },
{ "verbose", no_argument, NULL, ARG_VERBOSE },
{ "expect-reply", required_argument, NULL, ARG_EXPECT_REPLY },
{ "auto-start", required_argument, NULL, ARG_AUTO_START },
{ "allow-interactive-authorization", required_argument, NULL, ARG_ALLOW_INTERACTIVE_AUTHORIZATION },
{ "timeout", required_argument, NULL, ARG_TIMEOUT },
{ "augment-creds",required_argument, NULL, ARG_AUGMENT_CREDS},
{ "watch-bind", required_argument, NULL, ARG_WATCH_BIND },
{ "timeout", required_argument, NULL, ARG_TIMEOUT },
{ "augment-creds", required_argument, NULL, ARG_AUGMENT_CREDS },
{ "watch-bind", required_argument, NULL, ARG_WATCH_BIND },
{ "json", required_argument, NULL, ARG_JSON },
{},
};
@ -1842,7 +2229,7 @@ static int parse_argv(int argc, char *argv[]) {
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "hH:M:q", options, NULL)) >= 0)
while ((c = getopt_long(argc, argv, "hH:M:qj", options, NULL)) >= 0)
switch (c) {
@ -1978,6 +2365,29 @@ static int parse_argv(int argc, char *argv[]) {
arg_watch_bind = r;
break;
case 'j':
if (on_tty())
arg_json = JSON_PRETTY;
else
arg_json = JSON_SHORT;
break;
case ARG_JSON:
if (streq(optarg, "short"))
arg_json = JSON_SHORT;
else if (streq(optarg, "pretty"))
arg_json = JSON_PRETTY;
else if (streq(optarg, "help")) {
fputs("short\n"
"pretty\n", stdout);
return 0;
} else {
log_error("Unknown JSON out mode: %s", optarg);
return -EINVAL;
}
break;
case '?':
return -EINVAL;