diff options
Diffstat (limited to 'src/busctl/busctl.c')
-rw-r--r-- | src/busctl/busctl.c | 652 |
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); |