summaryrefslogtreecommitdiff
path: root/src/busctl/busctl.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/busctl/busctl.c')
-rw-r--r--src/busctl/busctl.c652
1 files changed, 550 insertions, 102 deletions
diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c
index f9b23514c1..96b4177495 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"
@@ -15,11 +16,14 @@
#include "escape.h"
#include "fd-util.h"
#include "fileio.h"
+#include "json.h"
#include "locale-util.h"
#include "log.h"
+#include "main-func.h"
#include "pager.h"
#include "parse-util.h"
#include "path-util.h"
+#include "pretty-print.h"
#include "set.h"
#include "strv.h"
#include "terminal-util.h"
@@ -27,9 +31,14 @@
#include "util.h"
#include "verbs.h"
-static bool arg_no_pager = false;
+static enum {
+ JSON_OFF,
+ JSON_SHORT,
+ JSON_PRETTY,
+} arg_json = JSON_OFF;
+static PagerFlags arg_pager_flags = 0;
static bool arg_legend = true;
-static char *arg_address = NULL;
+static const char *arg_address = NULL;
static bool arg_unique = false;
static bool arg_acquired = false;
static bool arg_activatable = false;
@@ -49,9 +58,14 @@ static bool arg_augment_creds = true;
static bool arg_watch_bind = false;
static usec_t arg_timeout = 0;
+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;
@@ -149,7 +163,7 @@ static int list_bus_names(int argc, char **argv, void *userdata) {
if (r < 0)
return log_error_errno(r, "Failed to list names: %m");
- (void) pager_open(arg_no_pager, false);
+ (void) pager_open(arg_pager_flags);
names = hashmap_new(&string_hash_ops);
if (!names)
@@ -315,8 +329,8 @@ static void print_subtree(const char *prefix, const char *path, char **l) {
l++;
}
- vertical = strjoina(prefix, special_glyph(TREE_VERTICAL));
- space = strjoina(prefix, special_glyph(TREE_SPACE));
+ vertical = strjoina(prefix, special_glyph(SPECIAL_GLYPH_TREE_VERTICAL));
+ space = strjoina(prefix, special_glyph(SPECIAL_GLYPH_TREE_SPACE));
for (;;) {
bool has_more = false;
@@ -337,7 +351,7 @@ static void print_subtree(const char *prefix, const char *path, char **l) {
n++;
}
- printf("%s%s%s\n", prefix, special_glyph(has_more ? TREE_BRANCH : TREE_RIGHT), *l);
+ printf("%s%s%s\n", prefix, special_glyph(has_more ? SPECIAL_GLYPH_TREE_BRANCH : SPECIAL_GLYPH_TREE_RIGHT), *l);
print_subtree(has_more ? vertical : space, *l, l);
l = n;
@@ -464,7 +478,7 @@ static int tree_one(sd_bus *bus, const char *service, const char *prefix, bool m
p = NULL;
}
- (void) pager_open(arg_no_pager, false);
+ (void) pager_open(arg_pager_flags);
l = set_get_strv(done);
if (!l)
@@ -498,7 +512,7 @@ static int tree(int argc, char **argv, void *userdata) {
if (r < 0)
return log_error_errno(r, "Failed to get name list: %m");
- (void) pager_open(arg_no_pager, false);
+ (void) pager_open(arg_pager_flags);
STRV_FOREACH(i, names) {
int q;
@@ -528,7 +542,7 @@ static int tree(int argc, char **argv, void *userdata) {
printf("\n");
if (argv[2]) {
- (void) pager_open(arg_no_pager, false);
+ (void) pager_open(arg_pager_flags);
printf("Service %s%s%s:\n", ansi_highlight(), *i, ansi_normal());
}
@@ -699,8 +713,7 @@ typedef struct Member {
uint64_t flags;
} Member;
-static void member_hash_func(const void *p, struct siphash *state) {
- const Member *m = p;
+static void member_hash_func(const Member *m, struct siphash *state) {
uint64_t arity = 1;
assert(m);
@@ -719,8 +732,7 @@ static void member_hash_func(const void *p, struct siphash *state) {
string_hash_func(m->interface, state);
}
-static int member_compare_func(const void *a, const void *b) {
- const Member *x = a, *y = b;
+static int member_compare_func(const Member *x, const Member *y) {
int d;
assert(x);
@@ -739,10 +751,8 @@ static int member_compare_func(const void *a, const void *b) {
return strcmp_ptr(x->name, y->name);
}
-static int member_compare_funcp(const void *a, const void *b) {
- const Member *const * x = (const Member *const *) a, * const *y = (const Member *const *) b;
-
- return member_compare_func(*x, *y);
+static int member_compare_funcp(Member * const *a, Member * const *b) {
+ return member_compare_func(*a, *b);
}
static void member_free(Member *m) {
@@ -910,12 +920,9 @@ static int on_property(const char *interface, const char *name, const char *sign
return 0;
}
-static int introspect(int argc, char **argv, void *userdata) {
- static const struct hash_ops member_hash_ops = {
- .hash = member_hash_func,
- .compare = member_compare_func,
- };
+DEFINE_PRIVATE_HASH_OPS(member_hash_ops, Member, member_hash_func, member_compare_func);
+static int introspect(int argc, char **argv, void *userdata) {
static const XMLIntrospectOps ops = {
.on_interface = on_interface,
.on_method = on_method,
@@ -1030,7 +1037,7 @@ static int introspect(int argc, char **argv, void *userdata) {
return bus_log_parse_error(r);
}
- (void) pager_open(arg_no_pager, false);
+ (void) pager_open(arg_pager_flags);
name_width = STRLEN("NAME");
type_width = STRLEN("TYPE");
@@ -1063,7 +1070,7 @@ static int introspect(int argc, char **argv, void *userdata) {
if (result_width > 40)
result_width = 40;
- qsort(sorted, k, sizeof(Member*), member_compare_funcp);
+ typesafe_qsort(sorted, k, member_compare_funcp);
if (arg_legend) {
printf("%-*s %-*s %-*s %-*s %s\n",
@@ -1125,6 +1132,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_CONDITION(m->priority != 0, "priority", JSON_BUILD_INTEGER(m->priority)),
+ JSON_BUILD_PAIR("cookie", JSON_BUILD_INTEGER(BUS_MESSAGE_COOKIE(m))),
+ 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");
+
+ 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;
@@ -1244,16 +1288,15 @@ 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) {
int r;
- if (isatty(fileno(stdout)) > 0) {
- log_error("Refusing to write message data to console, please redirect output to a file.");
- return -EINVAL;
- }
+ if (isatty(fileno(stdout)) > 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Refusing to write message data to console, please redirect output to a file.");
bus_pcap_header(arg_snaplen, stdout);
@@ -1339,10 +1382,9 @@ static int message_append_cmdline(sd_bus_message *m, const char *signature, char
if (t == 0)
break;
- if (!v) {
- log_error("Too few parameters for signature.");
- return -EINVAL;
- }
+ if (!v)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Too few parameters for signature.");
signature++;
p++;
@@ -1353,7 +1395,7 @@ static int message_append_cmdline(sd_bus_message *m, const char *signature, char
r = parse_boolean(v);
if (r < 0)
- return log_error_errno(r, "Failed to parse as boolean: %s", v);
+ return log_error_errno(r, "Failed to parse '%s' as boolean: %m", v);
r = sd_bus_message_append_basic(m, t, &r);
break;
@@ -1363,7 +1405,7 @@ static int message_append_cmdline(sd_bus_message *m, const char *signature, char
r = safe_atou8(v, &z);
if (r < 0)
- return log_error_errno(r, "Failed to parse as byte (unsigned 8bit integer): %s", v);
+ return log_error_errno(r, "Failed to parse '%s' as byte (unsigned 8bit integer): %m", v);
r = sd_bus_message_append_basic(m, t, &z);
break;
@@ -1374,7 +1416,7 @@ static int message_append_cmdline(sd_bus_message *m, const char *signature, char
r = safe_atoi16(v, &z);
if (r < 0)
- return log_error_errno(r, "Failed to parse as signed 16bit integer: %s", v);
+ return log_error_errno(r, "Failed to parse '%s' as signed 16bit integer: %m", v);
r = sd_bus_message_append_basic(m, t, &z);
break;
@@ -1385,7 +1427,7 @@ static int message_append_cmdline(sd_bus_message *m, const char *signature, char
r = safe_atou16(v, &z);
if (r < 0)
- return log_error_errno(r, "Failed to parse as unsigned 16bit integer: %s", v);
+ return log_error_errno(r, "Failed to parse '%s' as unsigned 16bit integer: %m", v);
r = sd_bus_message_append_basic(m, t, &z);
break;
@@ -1396,7 +1438,7 @@ static int message_append_cmdline(sd_bus_message *m, const char *signature, char
r = safe_atoi32(v, &z);
if (r < 0)
- return log_error_errno(r, "Failed to parse as signed 32bit integer: %s", v);
+ return log_error_errno(r, "Failed to parse '%s' as signed 32bit integer: %m", v);
r = sd_bus_message_append_basic(m, t, &z);
break;
@@ -1407,7 +1449,7 @@ static int message_append_cmdline(sd_bus_message *m, const char *signature, char
r = safe_atou32(v, &z);
if (r < 0)
- return log_error_errno(r, "Failed to parse as unsigned 32bit integer: %s", v);
+ return log_error_errno(r, "Failed to parse '%s' as unsigned 32bit integer: %m", v);
r = sd_bus_message_append_basic(m, t, &z);
break;
@@ -1418,7 +1460,7 @@ static int message_append_cmdline(sd_bus_message *m, const char *signature, char
r = safe_atoi64(v, &z);
if (r < 0)
- return log_error_errno(r, "Failed to parse as signed 64bit integer: %s", v);
+ return log_error_errno(r, "Failed to parse '%s' as signed 64bit integer: %m", v);
r = sd_bus_message_append_basic(m, t, &z);
break;
@@ -1429,7 +1471,7 @@ static int message_append_cmdline(sd_bus_message *m, const char *signature, char
r = safe_atou64(v, &z);
if (r < 0)
- return log_error_errno(r, "Failed to parse as unsigned 64bit integer: %s", v);
+ return log_error_errno(r, "Failed to parse '%s' as unsigned 64bit integer: %m", v);
r = sd_bus_message_append_basic(m, t, &z);
break;
@@ -1440,7 +1482,7 @@ static int message_append_cmdline(sd_bus_message *m, const char *signature, char
r = safe_atod(v, &z);
if (r < 0)
- return log_error_errno(r, "Failed to parse as double precision floating point: %s", v);
+ return log_error_errno(r, "Failed to parse '%s' as double precision floating point: %m", v);
r = sd_bus_message_append_basic(m, t, &z);
break;
@@ -1459,11 +1501,11 @@ static int message_append_cmdline(sd_bus_message *m, const char *signature, char
r = safe_atou32(v, &n);
if (r < 0)
- return log_error_errno(r, "Failed to parse number of array entries: %s", v);
+ return log_error_errno(r, "Failed to parse '%s' number of array entries: %m", v);
r = signature_element_length(signature, &k);
if (r < 0)
- return log_error_errno(r, "Invalid array signature.");
+ return log_error_errno(r, "Invalid array signature: %m");
{
unsigned i;
@@ -1509,7 +1551,7 @@ static int message_append_cmdline(sd_bus_message *m, const char *signature, char
r = signature_element_length(signature, &k);
if (r < 0)
- return log_error_errno(r, "Invalid struct/dict entry signature.");
+ return log_error_errno(r, "Invalid struct/dict entry signature: %m");
{
char s[k-1];
@@ -1532,12 +1574,12 @@ static int message_append_cmdline(sd_bus_message *m, const char *signature, char
}
case SD_BUS_TYPE_UNIX_FD:
- log_error("UNIX file descriptor not supported as type.");
- return -EINVAL;
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "UNIX file descriptor not supported as type.");
default:
- log_error("Unknown signature type %c.", t);
- return -EINVAL;
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Unknown signature type %c.", t);
}
if (r < 0)
@@ -1548,6 +1590,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 *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) |
+ JSON_FORMAT_COLOR_AUTO,
+ 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;
@@ -1607,8 +2002,20 @@ static int call(int argc, char **argv, void *userdata) {
if (r == 0 && !arg_quiet) {
- if (arg_verbose) {
- (void) pager_open(arg_no_pager, false);
+ if (arg_json != JSON_OFF) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+
+ if (arg_json != JSON_SHORT)
+ (void) pager_open(arg_pager_flags);
+
+ 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_pager_flags);
r = bus_message_dump(reply, stdout, 0);
if (r < 0)
@@ -1656,8 +2063,20 @@ static int get_property(int argc, char **argv, void *userdata) {
if (r < 0)
return bus_log_parse_error(r);
- if (arg_verbose) {
- (void) pager_open(arg_no_pager, false);
+ if (arg_json != JSON_OFF) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+
+ if (arg_json != JSON_SHORT)
+ (void) pager_open(arg_pager_flags);
+
+ 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_pager_flags);
r = bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_SUBTREE_ONLY);
if (r < 0)
@@ -1726,6 +2145,13 @@ static int set_property(int argc, char **argv, void *userdata) {
}
static int help(void) {
+ _cleanup_free_ char *link = NULL;
+ int r;
+
+ r = terminal_urlify_man("busctl", "1", &link);
+ if (r < 0)
+ return log_oom();
+
printf("%s [OPTIONS...] {COMMAND} ...\n\n"
"Introspect the bus.\n\n"
" -h --help Show this help\n"
@@ -1746,6 +2172,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"
@@ -1768,7 +2196,10 @@ static int help(void) {
" set-property SERVICE OBJECT INTERFACE PROPERTY SIGNATURE ARGUMENT...\n"
" Set property value\n"
" help Show this help\n"
- , program_invocation_short_name);
+ "\nSee the %s for details.\n"
+ , program_invocation_short_name
+ , link
+ );
return 0;
}
@@ -1800,33 +2231,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 },
{},
};
@@ -1835,7 +2268,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) {
@@ -1846,7 +2279,7 @@ static int parse_argv(int argc, char *argv[]) {
return version();
case ARG_NO_PAGER:
- arg_no_pager = true;
+ arg_pager_flags |= PAGER_DISABLE;
break;
case ARG_NO_LEGEND:
@@ -1891,12 +2324,11 @@ static int parse_argv(int argc, char *argv[]) {
r = parse_size(optarg, 1024, &sz);
if (r < 0)
- return log_error_errno(r, "Failed to parse size: %s", optarg);
+ return log_error_errno(r, "Failed to parse size '%s': %m", optarg);
- if ((uint64_t) (size_t) sz != sz) {
- log_error("Size out of range.");
- return -E2BIG;
- }
+ if ((uint64_t) (size_t) sz != sz)
+ return log_error_errno(SYNTHETIC_ERRNO(E2BIG),
+ "Size out of range.");
arg_snaplen = (size_t) sz;
break;
@@ -1927,7 +2359,7 @@ static int parse_argv(int argc, char *argv[]) {
case ARG_EXPECT_REPLY:
r = parse_boolean(optarg);
if (r < 0)
- return log_error_errno(r, "Failed to parse --expect-reply= parameter: %s", optarg);
+ return log_error_errno(r, "Failed to parse --expect-reply= parameter '%s': %m", optarg);
arg_expect_reply = r;
break;
@@ -1935,7 +2367,7 @@ static int parse_argv(int argc, char *argv[]) {
case ARG_AUTO_START:
r = parse_boolean(optarg);
if (r < 0)
- return log_error_errno(r, "Failed to parse --auto-start= parameter: %s", optarg);
+ return log_error_errno(r, "Failed to parse --auto-start= parameter '%s': %m", optarg);
arg_auto_start = r;
break;
@@ -1943,7 +2375,7 @@ static int parse_argv(int argc, char *argv[]) {
case ARG_ALLOW_INTERACTIVE_AUTHORIZATION:
r = parse_boolean(optarg);
if (r < 0)
- return log_error_errno(r, "Failed to parse --allow-interactive-authorization= parameter: %s", optarg);
+ return log_error_errno(r, "Failed to parse --allow-interactive-authorization= parameter '%s': %m", optarg);
arg_allow_interactive_authorization = r;
break;
@@ -1951,14 +2383,14 @@ static int parse_argv(int argc, char *argv[]) {
case ARG_TIMEOUT:
r = parse_sec(optarg, &arg_timeout);
if (r < 0)
- return log_error_errno(r, "Failed to parse --timeout= parameter: %s", optarg);
+ return log_error_errno(r, "Failed to parse --timeout= parameter '%s': %m", optarg);
break;
case ARG_AUGMENT_CREDS:
r = parse_boolean(optarg);
if (r < 0)
- return log_error_errno(r, "Failed to parse --augment-creds= parameter: %s", optarg);
+ return log_error_errno(r, "Failed to parse --augment-creds= parameter '%s': %m", optarg);
arg_augment_creds = r;
break;
@@ -1966,11 +2398,34 @@ static int parse_argv(int argc, char *argv[]) {
case ARG_WATCH_BIND:
r = parse_boolean(optarg);
if (r < 0)
- return log_error_errno(r, "Failed to parse --watch-bind= parameter: %s", optarg);
+ return log_error_errno(r, "Failed to parse --watch-bind= parameter '%s': %m", optarg);
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
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Unknown JSON out mode: %s",
+ optarg);
+
+ break;
+
case '?':
return -EINVAL;
@@ -2000,7 +2455,7 @@ static int busctl_main(int argc, char *argv[]) {
return dispatch_verb(argc, argv, verbs, NULL);
}
-int main(int argc, char *argv[]) {
+static int run(int argc, char *argv[]) {
int r;
log_parse_environment();
@@ -2008,16 +2463,9 @@ int main(int argc, char *argv[]) {
r = parse_argv(argc, argv);
if (r <= 0)
- goto finish;
-
- r = busctl_main(argc, argv);
-
-finish:
- /* make sure we terminate the bus connection first, and then close the
- * pager, see issue #3543 for the details. */
- pager_close();
-
- arg_matches = strv_free(arg_matches);
+ return r;
- return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+ return busctl_main(argc, argv);
}
+
+DEFINE_MAIN_FUNCTION(run);