From 2de622531400f45f60d8182d41fceb1d7335e5e8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 27 Nov 2018 20:07:58 +0100 Subject: [PATCH 1/3] busctl: support json mode also for 'busctl monitor' --- src/busctl/busctl.c | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c index 04c0b73fd2..aa96fd12a9 100644 --- a/src/busctl/busctl.c +++ b/src/busctl/busctl.c @@ -8,6 +8,7 @@ #include "alloc-util.h" #include "bus-dump.h" #include "bus-internal.h" +#include "bus-message.h" #include "bus-signature.h" #include "bus-type.h" #include "bus-util.h" @@ -62,6 +63,9 @@ STATIC_DESTRUCTOR_REGISTER(arg_matches, strv_freep); #define NAME_IS_ACQUIRED INT_TO_PTR(1) #define NAME_IS_ACTIVATABLE INT_TO_PTR(2) +static int json_transform_message(sd_bus_message *m, JsonVariant **ret); +static void json_dump_with_flags(JsonVariant *v, FILE *f); + static int acquire_bus(bool set_monitor, sd_bus **ret) { _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; int r; @@ -1132,6 +1136,43 @@ static int message_pcap(sd_bus_message *m, FILE *f) { return bus_message_pcap_frame(m, arg_snaplen, f); } +static int message_json(sd_bus_message *m, FILE *f) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL; + char e[2]; + int r; + + r = json_transform_message(m, &v); + if (r < 0) + return r; + + e[0] = m->header->endian; + e[1] = 0; + + r = json_build(&w, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("type", JSON_BUILD_STRING(bus_message_type_to_string(m->header->type))), + JSON_BUILD_PAIR("endian", JSON_BUILD_STRING(e)), + JSON_BUILD_PAIR("flags", JSON_BUILD_INTEGER(m->header->flags)), + JSON_BUILD_PAIR("version", JSON_BUILD_INTEGER(m->header->version)), + JSON_BUILD_PAIR("priority", JSON_BUILD_INTEGER(m->priority)), + JSON_BUILD_PAIR("cookie", JSON_BUILD_INTEGER(BUS_MESSAGE_COOKIE(m))), + JSON_BUILD_PAIR("reply_cookie", JSON_BUILD_INTEGER(m->reply_cookie)), + JSON_BUILD_PAIR("sender", JSON_BUILD_STRING(m->sender)), + JSON_BUILD_PAIR("destination", JSON_BUILD_STRING(m->destination)), + JSON_BUILD_PAIR("path", JSON_BUILD_STRING(m->path)), + JSON_BUILD_PAIR("interface", JSON_BUILD_STRING(m->interface)), + JSON_BUILD_PAIR("member", JSON_BUILD_STRING(m->member)), + JSON_BUILD_PAIR("monotonic", JSON_BUILD_INTEGER(m->monotonic)), + JSON_BUILD_PAIR("realtime", JSON_BUILD_INTEGER(m->realtime)), + JSON_BUILD_PAIR("seqnum", JSON_BUILD_INTEGER(m->seqnum)), + JSON_BUILD_PAIR("error_name", JSON_BUILD_STRING(m->error.name)), + JSON_BUILD_PAIR("payload", JSON_BUILD_VARIANT(v)))); + if (r < 0) + return log_error_errno(r, "Failed to build JSON object: %m"); + + json_dump_with_flags(w, f); + return 0; +} + static int monitor(int argc, char **argv, int (*dump)(sd_bus_message *m, FILE *f)) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *message = NULL; @@ -1251,7 +1292,7 @@ static int monitor(int argc, char **argv, int (*dump)(sd_bus_message *m, FILE *f } static int verb_monitor(int argc, char **argv, void *userdata) { - return monitor(argc, argv, message_dump); + return monitor(argc, argv, arg_json != JSON_OFF ? message_json : message_dump); } static int verb_capture(int argc, char **argv, void *userdata) { @@ -2038,7 +2079,7 @@ static int get_property(int argc, char **argv, void *userdata) { json_dump_with_flags(v, stdout); - } else if (arg_verbose) { + } else if (arg_verbose) { (void) pager_open(arg_pager_flags); r = bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_SUBTREE_ONLY); From 319a4f27c477cbc63decdc510b7ca698a7be44f4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 27 Nov 2018 22:26:21 +0100 Subject: [PATCH 2/3] json: teach json builder "conditional" object fields Quite often when we generate objects some fields should only be generated in some conditions. Let's add high-level support for that. Matching the existing JSON_BUILD_PAIR() this adds JSON_BUILD_PAIR_CONDITIONAL() which is very similar, but takes an additional parameter: a boolean condition. If "true" this acts like JSON_BUILD_PAIR(), but if false then the whole pair is suppressed. This sounds simply, but requires a tiny bit of complexity: when complex sub-variants are used in fields, then we also need to suppress them. --- src/shared/json.c | 165 ++++++++++++++++++++++++++++++++++--------- src/shared/json.h | 2 + src/test/test-json.c | 16 +++++ 3 files changed, 149 insertions(+), 34 deletions(-) diff --git a/src/shared/json.c b/src/shared/json.c index eec6ea7baf..420555ae54 100644 --- a/src/shared/json.c +++ b/src/shared/json.c @@ -2249,6 +2249,7 @@ typedef struct JsonStack { size_t n_elements, n_elements_allocated; unsigned line_before; unsigned column_before; + size_t n_suppress; /* When building: if > 0, suppress this many subsequent elements. If == (size_t) -1, suppress all subsequent elements */ } JsonStack; static void json_stack_release(JsonStack *s) { @@ -2656,6 +2657,8 @@ int json_buildv(JsonVariant **ret, va_list ap) { for (;;) { _cleanup_(json_variant_unrefp) JsonVariant *add = NULL; + size_t n_subtract = 0; /* how much to subtract from current->n_suppress, i.e. how many elements would + * have been added to the current variant */ JsonStack *current; int command; @@ -2679,9 +2682,13 @@ int json_buildv(JsonVariant **ret, va_list ap) { p = va_arg(ap, const char *); - r = json_variant_new_string(&add, p); - if (r < 0) - goto finish; + if (current->n_suppress == 0) { + r = json_variant_new_string(&add, p); + if (r < 0) + goto finish; + } + + n_subtract = 1; if (current->expect == EXPECT_TOPLEVEL) current->expect = EXPECT_END; @@ -2703,9 +2710,13 @@ int json_buildv(JsonVariant **ret, va_list ap) { j = va_arg(ap, intmax_t); - r = json_variant_new_integer(&add, j); - if (r < 0) - goto finish; + if (current->n_suppress == 0) { + r = json_variant_new_integer(&add, j); + if (r < 0) + goto finish; + } + + n_subtract = 1; if (current->expect == EXPECT_TOPLEVEL) current->expect = EXPECT_END; @@ -2727,9 +2738,13 @@ int json_buildv(JsonVariant **ret, va_list ap) { j = va_arg(ap, uintmax_t); - r = json_variant_new_unsigned(&add, j); - if (r < 0) - goto finish; + if (current->n_suppress == 0) { + r = json_variant_new_unsigned(&add, j); + if (r < 0) + goto finish; + } + + n_subtract = 1; if (current->expect == EXPECT_TOPLEVEL) current->expect = EXPECT_END; @@ -2751,9 +2766,13 @@ int json_buildv(JsonVariant **ret, va_list ap) { d = va_arg(ap, long double); - r = json_variant_new_real(&add, d); - if (r < 0) - goto finish; + if (current->n_suppress == 0) { + r = json_variant_new_real(&add, d); + if (r < 0) + goto finish; + } + + n_subtract = 1; if (current->expect == EXPECT_TOPLEVEL) current->expect = EXPECT_END; @@ -2775,9 +2794,13 @@ int json_buildv(JsonVariant **ret, va_list ap) { b = va_arg(ap, int); - r = json_variant_new_boolean(&add, b); - if (r < 0) - goto finish; + if (current->n_suppress == 0) { + r = json_variant_new_boolean(&add, b); + if (r < 0) + goto finish; + } + + n_subtract = 1; if (current->expect == EXPECT_TOPLEVEL) current->expect = EXPECT_END; @@ -2796,9 +2819,13 @@ int json_buildv(JsonVariant **ret, va_list ap) { goto finish; } - r = json_variant_new_null(&add); - if (r < 0) - goto finish; + if (current->n_suppress == 0) { + r = json_variant_new_null(&add); + if (r < 0) + goto finish; + } + + n_subtract = 1; if (current->expect == EXPECT_TOPLEVEL) current->expect = EXPECT_END; @@ -2816,12 +2843,16 @@ int json_buildv(JsonVariant **ret, va_list ap) { goto finish; } + /* Note that we don't care for current->n_suppress here, after all the variant is already + * allocated anyway... */ add = va_arg(ap, JsonVariant*); if (!add) add = JSON_VARIANT_MAGIC_NULL; else json_variant_ref(add); + n_subtract = 1; + if (current->expect == EXPECT_TOPLEVEL) current->expect = EXPECT_END; else if (current->expect == EXPECT_OBJECT_VALUE) @@ -2841,13 +2872,17 @@ int json_buildv(JsonVariant **ret, va_list ap) { l = va_arg(ap, const char *); - if (!l) - add = JSON_VARIANT_MAGIC_NULL; - else { + if (l) { + /* Note that we don't care for current->n_suppress here, we should generate parsing + * errors even in suppressed object properties */ + r = json_parse(l, &add, NULL, NULL); if (r < 0) goto finish; - } + } else + add = JSON_VARIANT_MAGIC_NULL; + + n_subtract = 1; if (current->expect == EXPECT_TOPLEVEL) current->expect = EXPECT_END; @@ -2881,6 +2916,10 @@ int json_buildv(JsonVariant **ret, va_list ap) { stack[n_stack++] = (JsonStack) { .expect = EXPECT_ARRAY_ELEMENT, + .n_suppress = current->n_suppress != 0 ? (size_t) -1 : 0, /* if we shall suppress the + * new array, then we should + * also suppress all array + * members */ }; break; @@ -2893,9 +2932,13 @@ int json_buildv(JsonVariant **ret, va_list ap) { assert(n_stack > 1); - r = json_variant_new_array(&add, current->elements, current->n_elements); - if (r < 0) - goto finish; + if (current->n_suppress == 0) { + r = json_variant_new_array(&add, current->elements, current->n_elements); + if (r < 0) + goto finish; + } + + n_subtract = 1; json_stack_release(current); n_stack--, current--; @@ -2912,9 +2955,13 @@ int json_buildv(JsonVariant **ret, va_list ap) { l = va_arg(ap, char **); - r = json_variant_new_array_strv(&add, l); - if (r < 0) - goto finish; + if (current->n_suppress == 0) { + r = json_variant_new_array_strv(&add, l); + if (r < 0) + goto finish; + } + + n_subtract = 1; if (current->expect == EXPECT_TOPLEVEL) current->expect = EXPECT_END; @@ -2948,6 +2995,10 @@ int json_buildv(JsonVariant **ret, va_list ap) { stack[n_stack++] = (JsonStack) { .expect = EXPECT_OBJECT_KEY, + .n_suppress = current->n_suppress != 0 ? (size_t) -1 : 0, /* if we shall suppress the + * new object, then we should + * also suppress all object + * members */ }; break; @@ -2961,9 +3012,13 @@ int json_buildv(JsonVariant **ret, va_list ap) { assert(n_stack > 1); - r = json_variant_new_object(&add, current->elements, current->n_elements); - if (r < 0) - goto finish; + if (current->n_suppress == 0) { + r = json_variant_new_object(&add, current->elements, current->n_elements); + if (r < 0) + goto finish; + } + + n_subtract = 1; json_stack_release(current); n_stack--, current--; @@ -2980,15 +3035,47 @@ int json_buildv(JsonVariant **ret, va_list ap) { n = va_arg(ap, const char *); - r = json_variant_new_string(&add, n); - if (r < 0) + if (current->n_suppress == 0) { + r = json_variant_new_string(&add, n); + if (r < 0) + goto finish; + } + + n_subtract = 1; + + current->expect = EXPECT_OBJECT_VALUE; + break; + } + + case _JSON_BUILD_PAIR_CONDITION: { + const char *n; + bool b; + + if (current->expect != EXPECT_OBJECT_KEY) { + r = -EINVAL; goto finish; + } + + b = va_arg(ap, int); + n = va_arg(ap, const char *); + + if (b && current->n_suppress == 0) { + r = json_variant_new_string(&add, n); + if (r < 0) + goto finish; + } + + n_subtract = 1; /* we generated one item */ + + if (!b && current->n_suppress != (size_t) -1) + current->n_suppress += 2; /* Suppress this one and the next item */ current->expect = EXPECT_OBJECT_VALUE; break; }} - if (add) { + /* If a variant was generated, add it to our current variant, but only if we are not supposed to suppress additions */ + if (add && current->n_suppress == 0) { if (!GREEDY_REALLOC(current->elements, current->n_elements_allocated, current->n_elements + 1)) { r = -ENOMEM; goto finish; @@ -2996,6 +3083,16 @@ int json_buildv(JsonVariant **ret, va_list ap) { current->elements[current->n_elements++] = TAKE_PTR(add); } + + /* If we are supposed to suppress items, let's subtract how many items where generated from that + * counter. Except if the counter is (size_t) -1, i.e. we shall suppress an infinite number of elements + * on this stack level */ + if (current->n_suppress != (size_t) -1) { + if (current->n_suppress <= n_subtract) /* Saturated */ + current->n_suppress = 0; + else + current->n_suppress -= n_subtract; + } } done: diff --git a/src/shared/json.h b/src/shared/json.h index c9482d23eb..278ff77d30 100644 --- a/src/shared/json.h +++ b/src/shared/json.h @@ -177,6 +177,7 @@ enum { _JSON_BUILD_OBJECT_BEGIN, _JSON_BUILD_OBJECT_END, _JSON_BUILD_PAIR, + _JSON_BUILD_PAIR_CONDITION, _JSON_BUILD_NULL, _JSON_BUILD_VARIANT, _JSON_BUILD_LITERAL, @@ -192,6 +193,7 @@ enum { #define JSON_BUILD_ARRAY(...) _JSON_BUILD_ARRAY_BEGIN, __VA_ARGS__, _JSON_BUILD_ARRAY_END #define JSON_BUILD_OBJECT(...) _JSON_BUILD_OBJECT_BEGIN, __VA_ARGS__, _JSON_BUILD_OBJECT_END #define JSON_BUILD_PAIR(n, ...) _JSON_BUILD_PAIR, ({ const char *_x = n; _x; }), __VA_ARGS__ +#define JSON_BUILD_PAIR_CONDITION(c, n, ...) _JSON_BUILD_PAIR_CONDITION, ({ bool _x = c; _x; }), ({ const char *_x = n; _x; }), __VA_ARGS__ #define JSON_BUILD_NULL _JSON_BUILD_NULL #define JSON_BUILD_VARIANT(v) _JSON_BUILD_VARIANT, ({ JsonVariant *_x = v; _x; }) #define JSON_BUILD_LITERAL(l) _JSON_BUILD_LITERAL, ({ const char *_x = l; _x; }) diff --git a/src/test/test-json.c b/src/test/test-json.c index 6a57f88882..d3ece0bc0b 100644 --- a/src/test/test-json.c +++ b/src/test/test-json.c @@ -316,6 +316,22 @@ static void test_build(void) { a = json_variant_unref(a); b = json_variant_unref(b); + + assert_se(json_build(&a, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("x", JSON_BUILD_STRING("y")), + JSON_BUILD_PAIR("z", JSON_BUILD_STRING("a")), + JSON_BUILD_PAIR("b", JSON_BUILD_STRING("c")) + )) >= 0); + + assert_se(json_build(&b, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("x", JSON_BUILD_STRING("y")), + JSON_BUILD_PAIR_CONDITION(false, "p", JSON_BUILD_STRING("q")), + JSON_BUILD_PAIR_CONDITION(true, "z", JSON_BUILD_STRING("a")), + JSON_BUILD_PAIR_CONDITION(false, "j", JSON_BUILD_ARRAY(JSON_BUILD_STRING("k"), JSON_BUILD_STRING("u"), JSON_BUILD_STRING("i"))), + JSON_BUILD_PAIR("b", JSON_BUILD_STRING("c")) + )) >= 0); + + assert_se(json_variant_equal(a, b)); } static void test_source(void) { From 3919bc24aae13fb8e8f8574fed6bc9b02c008c04 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 27 Nov 2018 22:30:30 +0100 Subject: [PATCH 3/3] busctl: use new JSON_BUILD_PAIR_CONDITIONAL() for minimizing bus message JSON transformations Let's not generate object fields that aren't defined for a message. --- src/busctl/busctl.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c index aa96fd12a9..1b0939caba 100644 --- a/src/busctl/busctl.c +++ b/src/busctl/busctl.c @@ -1153,18 +1153,18 @@ static int message_json(sd_bus_message *m, FILE *f) { JSON_BUILD_PAIR("endian", JSON_BUILD_STRING(e)), JSON_BUILD_PAIR("flags", JSON_BUILD_INTEGER(m->header->flags)), JSON_BUILD_PAIR("version", JSON_BUILD_INTEGER(m->header->version)), - JSON_BUILD_PAIR("priority", JSON_BUILD_INTEGER(m->priority)), + JSON_BUILD_PAIR_CONDITION(m->priority != 0, "priority", JSON_BUILD_INTEGER(m->priority)), JSON_BUILD_PAIR("cookie", JSON_BUILD_INTEGER(BUS_MESSAGE_COOKIE(m))), - JSON_BUILD_PAIR("reply_cookie", JSON_BUILD_INTEGER(m->reply_cookie)), - JSON_BUILD_PAIR("sender", JSON_BUILD_STRING(m->sender)), - JSON_BUILD_PAIR("destination", JSON_BUILD_STRING(m->destination)), - JSON_BUILD_PAIR("path", JSON_BUILD_STRING(m->path)), - JSON_BUILD_PAIR("interface", JSON_BUILD_STRING(m->interface)), - JSON_BUILD_PAIR("member", JSON_BUILD_STRING(m->member)), - JSON_BUILD_PAIR("monotonic", JSON_BUILD_INTEGER(m->monotonic)), - JSON_BUILD_PAIR("realtime", JSON_BUILD_INTEGER(m->realtime)), - JSON_BUILD_PAIR("seqnum", JSON_BUILD_INTEGER(m->seqnum)), - JSON_BUILD_PAIR("error_name", JSON_BUILD_STRING(m->error.name)), + JSON_BUILD_PAIR_CONDITION(m->reply_cookie != 0, "reply_cookie", JSON_BUILD_INTEGER(m->reply_cookie)), + JSON_BUILD_PAIR_CONDITION(m->sender, "sender", JSON_BUILD_STRING(m->sender)), + JSON_BUILD_PAIR_CONDITION(m->destination, "destination", JSON_BUILD_STRING(m->destination)), + JSON_BUILD_PAIR_CONDITION(m->path, "path", JSON_BUILD_STRING(m->path)), + JSON_BUILD_PAIR_CONDITION(m->interface, "interface", JSON_BUILD_STRING(m->interface)), + JSON_BUILD_PAIR_CONDITION(m->member, "member", JSON_BUILD_STRING(m->member)), + JSON_BUILD_PAIR_CONDITION(m->monotonic != 0, "monotonic", JSON_BUILD_INTEGER(m->monotonic)), + JSON_BUILD_PAIR_CONDITION(m->realtime != 0, "realtime", JSON_BUILD_INTEGER(m->realtime)), + JSON_BUILD_PAIR_CONDITION(m->seqnum != 0, "seqnum", JSON_BUILD_INTEGER(m->seqnum)), + JSON_BUILD_PAIR_CONDITION(m->error.name, "error_name", JSON_BUILD_STRING(m->error.name)), JSON_BUILD_PAIR("payload", JSON_BUILD_VARIANT(v)))); if (r < 0) return log_error_errno(r, "Failed to build JSON object: %m");