]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
busctl: add a --json= output mode
authorLennart Poettering <lennart@poettering.net>
Wed, 4 Jul 2018 13:28:09 +0000 (15:28 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Thu, 11 Oct 2018 12:07:38 +0000 (14:07 +0200)
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
src/busctl/busctl.c

index 5154c80efec861692f90f68d873cdfce1a59046b..539ef6c64557a7bb229385bfab3220f8a355a577 100644 (file)
         </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>
 
index 7b651cfcd690fba568f6f0ac864aefa486d0e045..a5673731c0d25ce4a2501ee49e4f5d0de0291eb7 100644 (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"
 #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;