From 9cebb234b187b468a1d7165e4505403386b45226 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 4 Jul 2018 15:28:09 +0200 Subject: [PATCH] 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. --- man/busctl.xml | 22 +++ src/busctl/busctl.c | 462 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 458 insertions(+), 26 deletions(-) diff --git a/man/busctl.xml b/man/busctl.xml index 5154c80efe..539ef6c645 100644 --- a/man/busctl.xml +++ b/man/busctl.xml @@ -143,6 +143,28 @@ + + MODE + + + When used with the call or get-property command, shows output + formatted as JSON. Expects one of short (for the shortest possible output without any + redundant whitespace or line breaks) or pretty (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. + + + + + + + + Equivalent to when invoked interactively from a terminal. Otherwise + equivalent to , in particular when the output is piped to some other + program. + + + BOOL diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c index 7b651cfcd6..a5673731c0 100644 --- a/src/busctl/busctl.c +++ b/src/busctl/busctl.c @@ -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;