mirror of
https://github.com/systemd/systemd
synced 2024-10-15 12:34:37 +00:00
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:
parent
960d4b29d7
commit
9cebb234b1
|
@ -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>
|
||||
|
||||
|
|
|
@ -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,6 +2192,7 @@ static int parse_argv(int argc, char *argv[]) {
|
|||
ARG_TIMEOUT,
|
||||
ARG_AUGMENT_CREDS,
|
||||
ARG_WATCH_BIND,
|
||||
ARG_JSON,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
|
@ -1832,8 +2218,9 @@ static int parse_argv(int argc, char *argv[]) {
|
|||
{ "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},
|
||||
{ "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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue