From 10d7126365587e449ffe81b9ab7335be4e10cc68 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sat, 9 Jan 2021 16:56:00 +0100 Subject: [PATCH 1/4] json: add new json format flag for disabling JSON output This adds a new flag JSON_FORMAT_OFF that is a marker for "no JSON output please!". Of course, this flag sounds pointless in a JSON implementation, however this is useful in code that can generate JSON output, but also more human friendly output (for example our table formatters). With this in place various tools that so far maintained one boolean field "arg_json" that controlled whether JSON output was requested at all and another field "arg_json_format_flags" for selecing the precise json output flags may merge them into one, simplifying code a bit. --- src/shared/format-table.c | 3 +++ src/shared/json.c | 3 +++ src/shared/json.h | 1 + 3 files changed, 7 insertions(+) diff --git a/src/shared/format-table.c b/src/shared/format-table.c index a13a198b7a..645e5b9afa 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -2536,6 +2536,9 @@ int table_print_json(Table *t, FILE *f, JsonFormatFlags flags) { assert(t); + if (flags & JSON_FORMAT_OFF) /* If JSON output is turned off, use regular output */ + return table_print(t, f); + if (!f) f = stdout; diff --git a/src/shared/json.c b/src/shared/json.c index 6beb56cfa0..9a1a1787df 100644 --- a/src/shared/json.c +++ b/src/shared/json.c @@ -1766,6 +1766,9 @@ int json_variant_format(JsonVariant *v, JsonFormatFlags flags, char **ret) { assert_return(v, -EINVAL); assert_return(ret, -EINVAL); + if (flags & JSON_FORMAT_OFF) + return -ENOEXEC; + { _cleanup_fclose_ FILE *f = NULL; diff --git a/src/shared/json.h b/src/shared/json.h index abc0dccddb..a69e1de5c0 100644 --- a/src/shared/json.h +++ b/src/shared/json.h @@ -175,6 +175,7 @@ typedef enum JsonFormatFlags { JSON_FORMAT_SSE = 1 << 6, /* prefix/suffix with W3C server-sent events */ JSON_FORMAT_SEQ = 1 << 7, /* prefix/suffix with RFC 7464 application/json-seq */ JSON_FORMAT_FLUSH = 1 << 8, /* call fflush() after dumping JSON */ + JSON_FORMAT_OFF = 1 << 9, /* make json_variant_format() fail with -ENOEXEC */ } JsonFormatFlags; int json_variant_format(JsonVariant *v, JsonFormatFlags flags, char **ret); From b95a05d0144fab154d77e35488df65b3e4fa7bb1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sat, 9 Jan 2021 16:58:46 +0100 Subject: [PATCH 2/4] json: add generic cmdline parser for --json= switch --- src/shared/json.c | 21 +++++++++++++++++++++ src/shared/json.h | 2 ++ 2 files changed, 23 insertions(+) diff --git a/src/shared/json.c b/src/shared/json.c index 9a1a1787df..d559111248 100644 --- a/src/shared/json.c +++ b/src/shared/json.c @@ -4459,6 +4459,27 @@ int json_variant_unhex(JsonVariant *v, void **ret, size_t *ret_size) { return unhexmem(json_variant_string(v), (size_t) -1, ret, ret_size); } +int json_parse_cmdline_parameter_and_warn(const char *s, JsonFormatFlags *ret) { + assert(s); + assert(ret); + + if (streq(s, "pretty")) + *ret = JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR_AUTO; + else if (streq(s, "short")) + *ret = JSON_FORMAT_NEWLINE; + else if (streq(s, "off")) + *ret = JSON_FORMAT_OFF; + else if (streq(s, "help")) { + puts("pretty\n" + "short\n" + "off"); + return 0; /* 0 means → we showed a brief help, exit now */ + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown argument to --json= switch: %s", s); + + return 1; /* 1 means → properly parsed */ +} + static const char* const json_variant_type_table[_JSON_VARIANT_TYPE_MAX] = { [JSON_VARIANT_STRING] = "string", [JSON_VARIANT_INTEGER] = "integer", diff --git a/src/shared/json.h b/src/shared/json.h index a69e1de5c0..aad7326513 100644 --- a/src/shared/json.h +++ b/src/shared/json.h @@ -357,5 +357,7 @@ int json_log_internal(JsonVariant *variant, int level, int error, const char *fi int json_variant_unbase64(JsonVariant *v, void **ret, size_t *ret_size); int json_variant_unhex(JsonVariant *v, void **ret, size_t *ret_size); +int json_parse_cmdline_parameter_and_warn(const char *s, JsonFormatFlags *ret); + const char *json_variant_type_to_string(JsonVariantType t); JsonVariantType json_variant_type_from_string(const char *s); From 6a01ea4a2f9c4ed1666387d457b28e572426c97a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sat, 9 Jan 2021 17:08:53 +0100 Subject: [PATCH 3/4] json: port various tools to the new JSON_FORMAT_OFF flag These are the obvious cases. --- src/dissect/dissect.c | 40 ++++++++++++------------------------- src/home/homectl.c | 41 +++++++++++--------------------------- src/partition/repart.c | 45 +++++++++++++----------------------------- 3 files changed, 39 insertions(+), 87 deletions(-) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index dc7e9dc626..c5d161b2b5 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -45,8 +45,7 @@ static const char *arg_source = NULL; static const char *arg_target = NULL; static DissectImageFlags arg_flags = DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_DISCARD_ON_LOOP|DISSECT_IMAGE_RELAX_VAR_CHECK|DISSECT_IMAGE_FSCK; static VeritySettings arg_verity_settings = VERITY_SETTINGS_DEFAULT; -static bool arg_json = false; -static JsonFormatFlags arg_json_format_flags = 0; +static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; STATIC_DESTRUCTOR_REGISTER(arg_verity_settings, verity_settings_done); @@ -242,22 +241,9 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_JSON: - if (streq(optarg, "pretty")) { - arg_json = true; - arg_json_format_flags = JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR_AUTO; - } else if (streq(optarg, "short")) { - arg_json = true; - arg_json_format_flags = JSON_FORMAT_NEWLINE; - } else if (streq(optarg, "off")) { - arg_json = false; - arg_json_format_flags = 0; - } else if (streq(optarg, "help")) { - puts("pretty\n" - "short\n" - "off"); - return 0; - } else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown argument to --json=: %s", optarg); + r = json_parse_cmdline_parameter_and_warn(optarg, &arg_json_format_flags); + if (r <= 0) + return r; break; @@ -353,17 +339,17 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) { assert(m); assert(d); - if (!arg_json) + if (arg_json_format_flags & JSON_FORMAT_OFF) printf(" Name: %s\n", basename(arg_image)); if (ioctl(d->fd, BLKGETSIZE64, &size) < 0) log_debug_errno(errno, "Failed to query size of loopback device: %m"); - else if (!arg_json) { + else if (arg_json_format_flags & JSON_FORMAT_OFF) { char s[FORMAT_BYTES_MAX]; printf(" Size: %s\n", format_bytes(s, sizeof(s), size)); } - if (!arg_json) + if (arg_json_format_flags & JSON_FORMAT_OFF) putc('\n', stdout); r = dissected_image_acquire_metadata(m); @@ -379,7 +365,7 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) { log_warning_errno(r, "OS image is currently in use, proceeding without showing OS image metadata."); else if (r < 0) return log_error_errno(r, "Failed to acquire image metadata: %m"); - else if (!arg_json) { + else if (arg_json_format_flags & JSON_FORMAT_OFF) { if (m->hostname) printf(" Hostname: %s\n", m->hostname); @@ -495,7 +481,11 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) { return table_log_add_error(r); } - if (arg_json) { + if (arg_json_format_flags & JSON_FORMAT_OFF) { + r = table_print(t, stdout); + if (r < 0) + return table_log_print_error(r); + } else { _cleanup_(json_variant_unrefp) JsonVariant *jt = NULL; r = table_to_json(t, &jt); @@ -507,10 +497,6 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) { return log_oom(); json_variant_dump(v, arg_json_format_flags, stdout, NULL); - } else { - r = table_print(t, stdout); - if (r < 0) - return log_error_errno(r, "Failed to dump table: %m"); } return 0; diff --git a/src/home/homectl.c b/src/home/homectl.c index c9e54b1e67..f9c07dfec7 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -56,8 +56,7 @@ static uint64_t arg_disk_size_relative = UINT64_MAX; static char **arg_pkcs11_token_uri = NULL; static char **arg_fido2_device = NULL; static bool arg_recovery_key = false; -static bool arg_json = false; -static JsonFormatFlags arg_json_format_flags = 0; +static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; static bool arg_and_resize = false; static bool arg_and_change_password = false; static enum { @@ -171,22 +170,19 @@ static int list_homes(int argc, char *argv[], void *userdata) { if (r < 0) return bus_log_parse_error(r); - if (table_get_rows(table) > 1 || arg_json) { + if (table_get_rows(table) > 1 || !FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) { r = table_set_sort(table, (size_t) 0, (size_t) -1); if (r < 0) return table_log_sort_error(r); table_set_header(table, arg_legend); - if (arg_json) - r = table_print_json(table, stdout, arg_json_format_flags); - else - r = table_print(table, NULL); + r = table_print_json(table, stdout, arg_json_format_flags); if (r < 0) return table_log_print_error(r); } - if (arg_legend && !arg_json) { + if (arg_legend && (arg_json_format_flags & JSON_FORMAT_OFF)) { if (table_get_rows(table) > 1) printf("\n%zu home areas listed.\n", table_get_rows(table) - 1); else @@ -462,7 +458,9 @@ static void dump_home_record(UserRecord *hr) { log_warning("Warning: lacking rights to acquire privileged fields of user record of '%s', output incomplete.", hr->user_name); } - if (arg_json) { + if (arg_json_format_flags & JSON_FORMAT_OFF) + user_record_show(hr, true); + else { _cleanup_(user_record_unrefp) UserRecord *stripped = NULL; if (arg_export_format == EXPORT_FORMAT_STRIPPED) @@ -477,8 +475,7 @@ static void dump_home_record(UserRecord *hr) { hr = stripped; json_variant_dump(hr->json, arg_json_format_flags, stdout, NULL); - } else - user_record_show(hr, true); + } } static char **mangle_user_list(char **list, char ***ret_allocated) { @@ -3235,27 +3232,13 @@ static int parse_argv(int argc, char *argv[]) { } case 'j': - arg_json = true; arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO; break; case ARG_JSON: - if (streq(optarg, "pretty")) { - arg_json = true; - arg_json_format_flags = JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR_AUTO; - } else if (streq(optarg, "short")) { - arg_json = true; - arg_json_format_flags = JSON_FORMAT_NEWLINE; - } else if (streq(optarg, "off")) { - arg_json = false; - arg_json_format_flags = 0; - } else if (streq(optarg, "help")) { - puts("pretty\n" - "short\n" - "off"); - return 0; - } else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown argument to --json=: %s", optarg); + r = json_parse_cmdline_parameter_and_warn(optarg, &arg_json_format_flags); + if (r <= 0) + return r; break; @@ -3267,7 +3250,7 @@ static int parse_argv(int argc, char *argv[]) { else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specifying -E more than twice is not supported."); - arg_json = true; + arg_json_format_flags &= ~JSON_FORMAT_OFF; if (arg_json_format_flags == 0) arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO; break; diff --git a/src/partition/repart.c b/src/partition/repart.c index 02d7f227f8..d98051d134 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -106,8 +106,7 @@ static bool arg_randomize = false; static int arg_pretty = -1; static uint64_t arg_size = UINT64_MAX; static bool arg_size_auto = false; -static bool arg_json = false; -static JsonFormatFlags arg_json_format_flags = 0; +static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; static void *arg_key = NULL; static size_t arg_key_size = 0; static char *arg_tpm2_device = NULL; @@ -1824,7 +1823,7 @@ static int context_dump_partitions(Context *context, const char *node) { Partition *p; int r; - if (!arg_json && context->n_partitions == 0) { + if ((arg_json_format_flags & JSON_FORMAT_OFF) && context->n_partitions == 0) { log_info("Empty partition table."); return 0; } @@ -1834,12 +1833,12 @@ static int context_dump_partitions(Context *context, const char *node) { return log_oom(); if (!DEBUG_LOGGING) { - if (arg_json) - (void) table_set_display(t, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) 4, - (size_t) 5, (size_t) 6, (size_t) 7, (size_t) 9, (size_t) 10, (size_t) 12, (size_t) -1); - else + if (arg_json_format_flags & JSON_FORMAT_OFF) (void) table_set_display(t, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) 4, (size_t) 8, (size_t) 11, (size_t) -1); + else + (void) table_set_display(t, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) 4, + (size_t) 5, (size_t) 6, (size_t) 7, (size_t) 9, (size_t) 10, (size_t) 12, (size_t) -1); } (void) table_set_align_percent(t, table_get_cell(t, 0, 4), 100); @@ -1893,7 +1892,7 @@ static int context_dump_partitions(Context *context, const char *node) { return table_log_add_error(r); } - if (!arg_json && (sum_padding > 0 || sum_size > 0)) { + if ((arg_json_format_flags & JSON_FORMAT_OFF) && (sum_padding > 0 || sum_size > 0)) { char s[FORMAT_BYTES_MAX]; const char *a, *b; @@ -1919,12 +1918,9 @@ static int context_dump_partitions(Context *context, const char *node) { return table_log_add_error(r); } - if (arg_json) - r = table_print_json(t, stdout, arg_json_format_flags); - else - r = table_print(t, stdout); + r = table_print_json(t, stdout, arg_json_format_flags); if (r < 0) - return log_error_errno(r, "Failed to dump table: %m"); + return table_log_print_error(r); return 0; } @@ -3203,13 +3199,13 @@ static int context_write_partition_table( if (arg_pretty > 0 || (arg_pretty < 0 && isatty(STDOUT_FILENO) > 0) || - arg_json) { + !FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) { (void) context_dump_partitions(context, node); putc('\n', stdout); - if (!arg_json) + if (arg_json_format_flags & JSON_FORMAT_OFF) (void) context_dump_partition_bar(context, node); putc('\n', stdout); fflush(stdout); @@ -3679,22 +3675,9 @@ static int parse_argv(int argc, char *argv[]) { } case ARG_JSON: - if (streq(optarg, "pretty")) { - arg_json = true; - arg_json_format_flags = JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR_AUTO; - } else if (streq(optarg, "short")) { - arg_json = true; - arg_json_format_flags = JSON_FORMAT_NEWLINE; - } else if (streq(optarg, "off")) { - arg_json = false; - arg_json_format_flags = 0; - } else if (streq(optarg, "help")) { - puts("pretty\n" - "short\n" - "off"); - return 0; - } else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown argument to --json=: %s", optarg); + r = json_parse_cmdline_parameter_and_warn(optarg, &arg_json_format_flags); + if (r <= 0) + return r; break; From 923d37c64a695489f2b284794c664899f69e293c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sat, 9 Jan 2021 17:31:02 +0100 Subject: [PATCH 4/4] busctl: port busctl to JSON_FORMAT_OFF too This is a bit more complex, since busctl's JSON code predates json.c and was only minimally updated sofar. --- src/busctl/busctl.c | 60 ++++++++++++--------------------------------- 1 file changed, 16 insertions(+), 44 deletions(-) diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c index 7e293e47e5..276cae1b8b 100644 --- a/src/busctl/busctl.c +++ b/src/busctl/busctl.c @@ -31,11 +31,7 @@ #include "user-util.h" #include "verbs.h" -static enum { - JSON_OFF, - JSON_SHORT, - JSON_PRETTY, -} arg_json = JSON_OFF; +static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; static PagerFlags arg_pager_flags = 0; static bool arg_legend = true; static bool arg_full = false; @@ -67,7 +63,6 @@ STATIC_DESTRUCTOR_REGISTER(arg_matches, strv_freep); #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_close_unrefp) sd_bus *bus = NULL; @@ -362,13 +357,10 @@ static int list_bus_names(int argc, char **argv, void *userdata) { return log_error_errno(r, "Failed to fill line: %m"); } - (void) pager_open(arg_pager_flags); + if (arg_json_format_flags & (JSON_FORMAT_OFF|JSON_FORMAT_PRETTY|JSON_FORMAT_PRETTY_AUTO)) + (void) pager_open(arg_pager_flags); - if (arg_json) - r = table_print_json(table, stdout, - (arg_json == JSON_PRETTY ? JSON_FORMAT_PRETTY : JSON_FORMAT_NEWLINE) | JSON_FORMAT_COLOR_AUTO); - else - r = table_print(table, stdout); + r = table_print_json(table, NULL, arg_json_format_flags); if (r < 0) return table_log_print_error(r); @@ -1228,7 +1220,7 @@ static int message_json(sd_bus_message *m, FILE *f) { if (r < 0) return log_error_errno(r, "Failed to build JSON object: %m"); - json_dump_with_flags(w, f); + json_variant_dump(w, arg_json_format_flags, f, NULL); return 0; } @@ -1355,7 +1347,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, arg_json != JSON_OFF ? message_json : message_dump); + return monitor(argc, argv, (arg_json_format_flags & JSON_FORMAT_OFF) ? message_dump : message_json); } static int verb_capture(int argc, char **argv, void *userdata) { @@ -2006,14 +1998,6 @@ static int json_transform_message(sd_bus_message *m, JsonVariant **ret) { 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; @@ -2072,17 +2056,17 @@ static int call(int argc, char **argv, void *userdata) { if (r == 0 && !arg_quiet) { - if (arg_json != JSON_OFF) { + if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) { _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; - if (arg_json != JSON_SHORT) + if (arg_json_format_flags & (JSON_FORMAT_PRETTY|JSON_FORMAT_PRETTY_AUTO)) (void) pager_open(arg_pager_flags); r = json_transform_message(reply, &v); if (r < 0) return r; - json_dump_with_flags(v, stdout); + json_variant_dump(v, arg_json_format_flags, NULL, NULL); } else if (arg_verbose) { (void) pager_open(arg_pager_flags); @@ -2181,17 +2165,17 @@ static int get_property(int argc, char **argv, void *userdata) { if (r < 0) return bus_log_parse_error(r); - if (arg_json != JSON_OFF) { + if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) { _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; - if (arg_json != JSON_SHORT) + if (arg_json_format_flags & (JSON_FORMAT_PRETTY|JSON_FORMAT_PRETTY_AUTO)) (void) pager_open(arg_pager_flags); r = json_transform_variant(reply, contents, &v); if (r < 0) return r; - json_dump_with_flags(v, stdout); + json_variant_dump(v, arg_json_format_flags, NULL, NULL); } else if (arg_verbose) { (void) pager_open(arg_pager_flags); @@ -2543,25 +2527,13 @@ static int parse_argv(int argc, char *argv[]) { break; case 'j': - if (on_tty()) - arg_json = JSON_PRETTY; - else - arg_json = JSON_SHORT; + arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO; 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); + r = json_parse_cmdline_parameter_and_warn(optarg, &arg_json_format_flags); + if (r <= 0) + return r; break;