From 16c3c69be3c4c5d0e86c051b66153eff3b2ac98a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 6 May 2026 14:41:08 +0200 Subject: [PATCH 1/6] resolvectl: split out parse_protocol --- src/resolve/resolvectl.c | 91 +++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 53 deletions(-) diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 1452131b59..7b7cae68d4 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -3139,16 +3139,38 @@ static int verb_show_server_state(int argc, char *argv[], uintptr_t _data, void return sd_json_variant_dump(d, arg_json_format_flags, NULL, NULL); } -static void help_protocol_types(void) { - if (arg_legend) - puts("Known protocol types:"); - puts("dns\n" - "llmnr\n" - "llmnr-ipv4\n" - "llmnr-ipv6\n" - "mdns\n" - "mdns-ipv4\n" - "mdns-ipv6"); +static int parse_protocol(const char *arg) { + if (streq(arg, "help")) { + if (arg_legend) + puts("Known protocol types:"); + puts("dns\n" + "llmnr\n" + "llmnr-ipv4\n" + "llmnr-ipv6\n" + "mdns\n" + "mdns-ipv4\n" + "mdns-ipv6"); + return 0; + } + + if (streq(arg, "dns")) + arg_flags |= SD_RESOLVED_DNS; + else if (streq(arg, "llmnr")) + arg_flags |= SD_RESOLVED_LLMNR; + else if (streq(arg, "llmnr-ipv4")) + arg_flags |= SD_RESOLVED_LLMNR_IPV4; + else if (streq(arg, "llmnr-ipv6")) + arg_flags |= SD_RESOLVED_LLMNR_IPV6; + else if (streq(arg, "mdns")) + arg_flags |= SD_RESOLVED_MDNS; + else if (streq(arg, "mdns-ipv4")) + arg_flags |= SD_RESOLVED_MDNS_IPV4; + else if (streq(arg, "mdns-ipv6")) + arg_flags |= SD_RESOLVED_MDNS_IPV6; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown protocol specifier: %s", arg); + return 1; } static void help_dns_types(void) { @@ -3434,27 +3456,9 @@ static int compat_parse_argv(int argc, char *argv[]) { break; case 'p': - if (streq(optarg, "help")) { - help_protocol_types(); - return 0; - } else if (streq(optarg, "dns")) - arg_flags |= SD_RESOLVED_DNS; - else if (streq(optarg, "llmnr")) - arg_flags |= SD_RESOLVED_LLMNR; - else if (streq(optarg, "llmnr-ipv4")) - arg_flags |= SD_RESOLVED_LLMNR_IPV4; - else if (streq(optarg, "llmnr-ipv6")) - arg_flags |= SD_RESOLVED_LLMNR_IPV6; - else if (streq(optarg, "mdns")) - arg_flags |= SD_RESOLVED_MDNS; - else if (streq(optarg, "mdns-ipv4")) - arg_flags |= SD_RESOLVED_MDNS_IPV4; - else if (streq(optarg, "mdns-ipv6")) - arg_flags |= SD_RESOLVED_MDNS_IPV6; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown protocol specifier: %s", optarg); - + r = parse_protocol(optarg); + if (r <= 0) + return r; break; case ARG_SERVICE: @@ -3736,28 +3740,9 @@ static int native_parse_argv(int argc, char *argv[]) { break; case 'p': - if (streq(optarg, "help")) { - help_protocol_types(); - return 0; - } else if (streq(optarg, "dns")) - arg_flags |= SD_RESOLVED_DNS; - else if (streq(optarg, "llmnr")) - arg_flags |= SD_RESOLVED_LLMNR; - else if (streq(optarg, "llmnr-ipv4")) - arg_flags |= SD_RESOLVED_LLMNR_IPV4; - else if (streq(optarg, "llmnr-ipv6")) - arg_flags |= SD_RESOLVED_LLMNR_IPV6; - else if (streq(optarg, "mdns")) - arg_flags |= SD_RESOLVED_MDNS; - else if (streq(optarg, "mdns-ipv4")) - arg_flags |= SD_RESOLVED_MDNS_IPV4; - else if (streq(optarg, "mdns-ipv6")) - arg_flags |= SD_RESOLVED_MDNS_IPV6; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown protocol specifier: %s", - optarg); - + r = parse_protocol(optarg); + if (r <= 0) + return r; break; case ARG_RAW: From df24074bb51a72ee5beb8c16c5b6660058892e16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 7 May 2026 09:30:37 +0200 Subject: [PATCH 2/6] resolvectl: move things around in --help Move the "display options" to the end of both --help strings and then reorder the implementation to match. --- src/resolve/resolvectl.c | 207 ++++++++++++++++++++------------------- 1 file changed, 104 insertions(+), 103 deletions(-) diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 7b7cae68d4..fdce913447 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -3206,7 +3206,6 @@ static int compat_help(void) { "%2$sResolve domain names, IPv4 and IPv6 addresses, DNS records, and services.%3$s\n\n" " -h --help Show this help\n" " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" " -4 Resolve IPv4 addresses\n" " -6 Resolve IPv6 addresses\n" " -i --interface=INTERFACE Look on interface\n" @@ -3221,8 +3220,6 @@ static int compat_help(void) { " --cname=BOOL Follow CNAME redirects (default: yes)\n" " --search=BOOL Use search domains for single-label names\n" " (default: yes)\n" - " --raw[=payload|packet] Dump the answer as binary data\n" - " --legend=BOOL Print headers and additional info (default: yes)\n" " --statistics Show resolver statistics\n" " --reset-statistics Reset resolver statistics\n" " --status Show link and server status\n" @@ -3237,6 +3234,9 @@ static int compat_help(void) { " --set-dnssec=MODE Set per-interface DNSSEC mode\n" " --set-nta=DOMAIN Set per-interface DNSSEC NTA\n" " --revert Revert per-interface configuration\n" + " --raw[=payload|packet] Dump the answer as binary data\n" + " --no-pager Do not pipe output into a pager\n" + " --legend=BOOL Print headers and additional info (default: yes)\n" "\nSee the %4$s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -3286,8 +3286,6 @@ static int native_help(void) { "\n%3$sOptions:%4$s\n" " -h --help Show this help\n" " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --no-ask-password Do not prompt for password\n" " -4 Resolve IPv4 addresses\n" " -6 Resolve IPv6 addresses\n" " -i --interface=INTERFACE Look on interface\n" @@ -3310,6 +3308,8 @@ static int native_help(void) { " --search=BOOL Use search domains for single-label names (default:\n" " yes)\n" " --raw[=payload|packet] Dump the answer as binary data\n" + " --no-pager Do not pipe output into a pager\n" + " --no-ask-password Do not prompt for password\n" " --legend=BOOL Print headers and additional info (default: yes)\n" " --json=MODE Output as JSON\n" " -j Same as --json=pretty on tty, --json=short\n" @@ -3418,6 +3418,12 @@ static int compat_parse_argv(int argc, char *argv[]) { return r; break; + case 'p': + r = parse_protocol(optarg); + if (r <= 0) + return r; + break; + case 't': if (streq(optarg, "help")) { help_dns_types(); @@ -3449,59 +3455,10 @@ static int compat_parse_argv(int argc, char *argv[]) { break; - case ARG_LEGEND: - r = parse_boolean_argument("--legend=", optarg, &arg_legend); - if (r < 0) - return r; - break; - - case 'p': - r = parse_protocol(optarg); - if (r <= 0) - return r; - break; - case ARG_SERVICE: arg_mode = MODE_RESOLVE_SERVICE; break; - case ARG_OPENPGP: - arg_mode = MODE_RESOLVE_OPENPGP; - break; - - case ARG_TLSA: - arg_mode = MODE_RESOLVE_TLSA; - if (!optarg || service_family_is_valid(optarg)) - arg_service_family = optarg; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown service family \"%s\".", optarg); - break; - - case ARG_RAW: - if (on_tty()) - return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), - "Refusing to write binary data to tty."); - - if (optarg == NULL || streq(optarg, "payload")) - arg_raw = RAW_PAYLOAD; - else if (streq(optarg, "packet")) - arg_raw = RAW_PACKET; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown --raw specifier \"%s\".", - optarg); - - arg_legend = false; - break; - - case ARG_CNAME: - r = parse_boolean_argument("--cname=", optarg, NULL); - if (r < 0) - return r; - SET_FLAG(arg_flags, SD_RESOLVED_NO_CNAME, r == 0); - break; - case ARG_SERVICE_ADDRESS: r = parse_boolean_argument("--service-address=", optarg, NULL); if (r < 0) @@ -3516,6 +3473,27 @@ static int compat_parse_argv(int argc, char *argv[]) { SET_FLAG(arg_flags, SD_RESOLVED_NO_TXT, r == 0); break; + case ARG_OPENPGP: + arg_mode = MODE_RESOLVE_OPENPGP; + break; + + case ARG_TLSA: + arg_mode = MODE_RESOLVE_TLSA; + if (!optarg || service_family_is_valid(optarg)) + arg_service_family = optarg; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown service family \"%s\".", optarg); + break; + + case ARG_CNAME: + r = parse_boolean_argument("--cname=", optarg, NULL); + if (r < 0) + return r; + SET_FLAG(arg_flags, SD_RESOLVED_NO_CNAME, r == 0); + break; + + case ARG_SEARCH: r = parse_boolean_argument("--search=", optarg, NULL); if (r < 0) @@ -3531,6 +3509,10 @@ static int compat_parse_argv(int argc, char *argv[]) { arg_mode = MODE_RESET_STATISTICS; break; + case ARG_STATUS: + arg_mode = MODE_STATUS; + break; + case ARG_FLUSH_CACHES: arg_mode = MODE_FLUSH_CACHES; break; @@ -3539,14 +3521,6 @@ static int compat_parse_argv(int argc, char *argv[]) { arg_mode = MODE_RESET_SERVER_FEATURES; break; - case ARG_STATUS: - arg_mode = MODE_STATUS; - break; - - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; - break; - case ARG_SET_DNS: r = strv_extend(&arg_set_dns, optarg); if (r < 0) @@ -3595,6 +3569,33 @@ static int compat_parse_argv(int argc, char *argv[]) { arg_mode = MODE_REVERT_LINK; break; + case ARG_RAW: + if (on_tty()) + return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), + "Refusing to write binary data to tty."); + + if (optarg == NULL || streq(optarg, "payload")) + arg_raw = RAW_PAYLOAD; + else if (streq(optarg, "packet")) + arg_raw = RAW_PACKET; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown --raw specifier \"%s\".", + optarg); + + arg_legend = false; + break; + + case ARG_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; + + case ARG_LEGEND: + r = parse_boolean_argument("--legend=", optarg, &arg_legend); + if (r < 0) + return r; + break; + case '?': return -EINVAL; @@ -3703,6 +3704,12 @@ static int native_parse_argv(int argc, char *argv[]) { return r; break; + case 'p': + r = parse_protocol(optarg); + if (r <= 0) + return r; + break; + case 't': if (streq(optarg, "help")) { help_dns_types(); @@ -3733,33 +3740,18 @@ static int native_parse_argv(int argc, char *argv[]) { break; - case ARG_LEGEND: - r = parse_boolean_argument("--legend=", optarg, &arg_legend); + case ARG_SERVICE_ADDRESS: + r = parse_boolean_argument("--service-address=", optarg, NULL); if (r < 0) return r; + SET_FLAG(arg_flags, SD_RESOLVED_NO_ADDRESS, r == 0); break; - case 'p': - r = parse_protocol(optarg); - if (r <= 0) + case ARG_SERVICE_TXT: + r = parse_boolean_argument("--service-txt=", optarg, NULL); + if (r < 0) return r; - break; - - case ARG_RAW: - if (on_tty()) - return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), - "Refusing to write binary data to tty."); - - if (optarg == NULL || streq(optarg, "payload")) - arg_raw = RAW_PAYLOAD; - else if (streq(optarg, "packet")) - arg_raw = RAW_PACKET; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown --raw specifier \"%s\".", - optarg); - - arg_legend = false; + SET_FLAG(arg_flags, SD_RESOLVED_NO_TXT, r == 0); break; case ARG_CNAME: @@ -3797,6 +3789,13 @@ static int native_parse_argv(int argc, char *argv[]) { SET_FLAG(arg_flags, SD_RESOLVED_NO_STALE, r == 0); break; + case ARG_RELAX_SINGLE_LABEL: + r = parse_boolean_argument("--relax-single-label=", optarg, NULL); + if (r < 0) + return r; + SET_FLAG(arg_flags, SD_RESOLVED_RELAX_SINGLE_LABEL, r > 0); + break; + case ARG_ZONE: r = parse_boolean_argument("--zone=", optarg, NULL); if (r < 0) @@ -3818,20 +3817,6 @@ static int native_parse_argv(int argc, char *argv[]) { SET_FLAG(arg_flags, SD_RESOLVED_NO_NETWORK, r == 0); break; - case ARG_SERVICE_ADDRESS: - r = parse_boolean_argument("--service-address=", optarg, NULL); - if (r < 0) - return r; - SET_FLAG(arg_flags, SD_RESOLVED_NO_ADDRESS, r == 0); - break; - - case ARG_SERVICE_TXT: - r = parse_boolean_argument("--service-txt=", optarg, NULL); - if (r < 0) - return r; - SET_FLAG(arg_flags, SD_RESOLVED_NO_TXT, r == 0); - break; - case ARG_SEARCH: r = parse_boolean_argument("--search=", optarg, NULL); if (r < 0) @@ -3839,11 +3824,21 @@ static int native_parse_argv(int argc, char *argv[]) { SET_FLAG(arg_flags, SD_RESOLVED_NO_SEARCH, r == 0); break; - case ARG_RELAX_SINGLE_LABEL: - r = parse_boolean_argument("--relax-single-label=", optarg, NULL); - if (r < 0) - return r; - SET_FLAG(arg_flags, SD_RESOLVED_RELAX_SINGLE_LABEL, r > 0); + case ARG_RAW: + if (on_tty()) + return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), + "Refusing to write binary data to tty."); + + if (optarg == NULL || streq(optarg, "payload")) + arg_raw = RAW_PAYLOAD; + else if (streq(optarg, "packet")) + arg_raw = RAW_PACKET; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown --raw specifier \"%s\".", + optarg); + + arg_legend = false; break; case ARG_NO_PAGER: @@ -3854,6 +3849,12 @@ static int native_parse_argv(int argc, char *argv[]) { arg_ask_password = false; break; + case ARG_LEGEND: + r = parse_boolean_argument("--legend=", optarg, &arg_legend); + if (r < 0) + return r; + break; + case ARG_JSON: r = parse_json_argument(optarg, &arg_json_format_flags); if (r <= 0) From d865863929bd010392330a4358b005c9eb902880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 7 May 2026 09:31:28 +0200 Subject: [PATCH 3/6] resolvectl: move verb implementations to match order in --help --- src/resolve/resolvectl.c | 1851 +++++++++++++++++++------------------- 1 file changed, 925 insertions(+), 926 deletions(-) diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index fdce913447..4088c39b1d 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -122,6 +122,18 @@ static const char* const status_mode_json_field_table[_STATUS_MAX] = { DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(status_mode_json_field, StatusMode); +static int strv_extend_extended_bool(char ***strv, const char *name, const char *value) { + int r; + + if (value) { + r = parse_boolean(value); + if (r >= 0) + return strv_extendf(strv, "%s%s", plus_minus(r), name); + } + + return strv_extendf(strv, "%s=%s", name, value ?: "???"); +} + static int acquire_bus(sd_bus **ret) { _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; int r; @@ -1163,349 +1175,29 @@ static int verb_tlsa(int argc, char *argv[], uintptr_t _data, void *userdata) { return ret; } -static int verb_show_statistics(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(table_unrefp) Table *table = NULL; - sd_json_variant *reply = NULL; +static int varlink_dump_dns_configuration(sd_json_variant **ret) { _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL; + sd_json_variant *v; int r; - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + assert(ret); - r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); + r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve"); if (r < 0) - return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); + return log_error_errno(r, "Failed to connect to service /run/systemd/resolve/io.systemd.Resolve: %m"); - r = varlink_callbo_and_log( - vl, - "io.systemd.Resolve.Monitor.DumpStatistics", - &reply, - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + r = varlink_call_and_log(vl, "io.systemd.Resolve.DumpDNSConfiguration", /* parameters= */ NULL, &reply); if (r < 0) return r; - if (sd_json_format_enabled(arg_json_format_flags)) - return sd_json_variant_dump(reply, arg_json_format_flags, NULL, NULL); + v = sd_json_variant_by_key(reply, "configuration"); - struct statistics { - sd_json_variant *transactions; - sd_json_variant *cache; - sd_json_variant *dnssec; - } statistics; + if (!sd_json_variant_is_array(v)) + return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "DumpDNSConfiguration() response missing 'configuration' key."); - static const sd_json_dispatch_field statistics_dispatch_table[] = { - { "transactions", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, transactions), SD_JSON_MANDATORY }, - { "cache", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, cache), SD_JSON_MANDATORY }, - { "dnssec", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, dnssec), SD_JSON_MANDATORY }, - {}, - }; - - r = sd_json_dispatch(reply, statistics_dispatch_table, SD_JSON_LOG, &statistics); - if (r < 0) - return r; - - struct transactions { - uint64_t n_current_transactions; - uint64_t n_transactions_total; - uint64_t n_timeouts_total; - uint64_t n_timeouts_served_stale_total; - uint64_t n_failure_responses_total; - uint64_t n_failure_responses_served_stale_total; - } transactions; - - static const sd_json_dispatch_field transactions_dispatch_table[] = { - { "currentTransactions", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_current_transactions), SD_JSON_MANDATORY }, - { "totalTransactions", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_transactions_total), SD_JSON_MANDATORY }, - { "totalTimeouts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_timeouts_total), SD_JSON_MANDATORY }, - { "totalTimeoutsServedStale", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_timeouts_served_stale_total), SD_JSON_MANDATORY }, - { "totalFailedResponses", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_failure_responses_total), SD_JSON_MANDATORY }, - { "totalFailedResponsesServedStale", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_failure_responses_served_stale_total), SD_JSON_MANDATORY }, - {}, - }; - - r = sd_json_dispatch(statistics.transactions, transactions_dispatch_table, SD_JSON_LOG, &transactions); - if (r < 0) - return r; - - struct cache { - uint64_t cache_size; - uint64_t n_cache_hit; - uint64_t n_cache_miss; - } cache; - - static const sd_json_dispatch_field cache_dispatch_table[] = { - { "size", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, cache_size), SD_JSON_MANDATORY }, - { "hits", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, n_cache_hit), SD_JSON_MANDATORY }, - { "misses", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, n_cache_miss), SD_JSON_MANDATORY }, - {}, - }; - - r = sd_json_dispatch(statistics.cache, cache_dispatch_table, SD_JSON_LOG, &cache); - if (r < 0) - return r; - - struct dnsssec { - uint64_t n_dnssec_secure; - uint64_t n_dnssec_insecure; - uint64_t n_dnssec_bogus; - uint64_t n_dnssec_indeterminate; - } dnsssec; - - static const sd_json_dispatch_field dnssec_dispatch_table[] = { - { "secure", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_secure), SD_JSON_MANDATORY }, - { "insecure", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_insecure), SD_JSON_MANDATORY }, - { "bogus", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_bogus), SD_JSON_MANDATORY }, - { "indeterminate", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_indeterminate), SD_JSON_MANDATORY }, - {}, - }; - - r = sd_json_dispatch(statistics.dnssec, dnssec_dispatch_table, SD_JSON_LOG, &dnsssec); - if (r < 0) - return r; - - table = table_new_vertical(); - if (!table) - return log_oom(); - - r = table_add_many(table, - TABLE_STRING, "Transactions", - TABLE_SET_COLOR, ansi_highlight(), - TABLE_SET_ALIGN_PERCENT, 0, - TABLE_EMPTY, - TABLE_FIELD, "Current Transactions", - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_UINT64, transactions.n_current_transactions, - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_FIELD, "Total Transactions", - TABLE_UINT64, transactions.n_transactions_total, - TABLE_EMPTY, TABLE_EMPTY, - TABLE_STRING, "Cache", - TABLE_SET_COLOR, ansi_highlight(), - TABLE_SET_ALIGN_PERCENT, 0, - TABLE_EMPTY, - TABLE_FIELD, "Current Cache Size", - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_UINT64, cache.cache_size, - TABLE_FIELD, "Cache Hits", - TABLE_UINT64, cache.n_cache_hit, - TABLE_FIELD, "Cache Misses", - TABLE_UINT64, cache.n_cache_miss, - TABLE_EMPTY, TABLE_EMPTY, - TABLE_STRING, "Failure Transactions", - TABLE_SET_COLOR, ansi_highlight(), - TABLE_SET_ALIGN_PERCENT, 0, - TABLE_EMPTY, - TABLE_FIELD, "Total Timeouts", - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_UINT64, transactions.n_timeouts_total, - TABLE_FIELD, "Total Timeouts (Stale Data Served)", - TABLE_UINT64, transactions.n_timeouts_served_stale_total, - TABLE_FIELD, "Total Failure Responses", - TABLE_UINT64, transactions.n_failure_responses_total, - TABLE_FIELD, "Total Failure Responses (Stale Data Served)", - TABLE_UINT64, transactions.n_failure_responses_served_stale_total, - TABLE_EMPTY, TABLE_EMPTY, - TABLE_STRING, "DNSSEC Verdicts", - TABLE_SET_COLOR, ansi_highlight(), - TABLE_SET_ALIGN_PERCENT, 0, - TABLE_EMPTY, - TABLE_FIELD, "Secure", - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_UINT64, dnsssec.n_dnssec_secure, - TABLE_FIELD, "Insecure", - TABLE_UINT64, dnsssec.n_dnssec_insecure, - TABLE_FIELD, "Bogus", - TABLE_UINT64, dnsssec.n_dnssec_bogus, - TABLE_FIELD, "Indeterminate", - TABLE_UINT64, dnsssec.n_dnssec_indeterminate - ); - if (r < 0) - return table_log_add_error(r); - - return table_print_or_warn(table); -} - -static int verb_reset_statistics(int argc, char *argv[], uintptr_t _data, void *userdata) { - sd_json_variant *reply = NULL; - _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; - int r; - - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - - r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); - if (r < 0) - return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); - - r = varlink_callbo_and_log( - vl, - "io.systemd.Resolve.Monitor.ResetStatistics", - &reply, - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); - if (r < 0) - return r; - - if (sd_json_format_enabled(arg_json_format_flags)) - return sd_json_variant_dump(reply, arg_json_format_flags, NULL, NULL); - - return 0; -} - -static int verb_flush_caches(int argc, char *argv[], uintptr_t _data, 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; - int r; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - r = bus_call_method(bus, bus_resolve_mgr, "FlushCaches", &error, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to flush caches: %s", bus_error_message(&error, r)); - - return 0; -} - -static int verb_reset_server_features(int argc, char *argv[], uintptr_t _data, 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; - int r; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - r = bus_call_method(bus, bus_resolve_mgr, "ResetServerFeatures", &error, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to reset server features: %s", bus_error_message(&error, r)); - - return 0; -} - -static int status_print_strv(DNSConfiguration *c, char **p) { - const unsigned indent = strlen("Global: "); /* Use the same indentation everywhere to make things nice */ - int pos1, pos2; - - assert(c); - - if (c->ifname) - printf("%s%nLink %i (%s)%n%s:", ansi_highlight(), &pos1, c->ifindex, c->ifname, &pos2, ansi_normal()); - else if (c->delegate) - printf("%s%nDelegate %s%n%s:", ansi_highlight(), &pos1, c->delegate, &pos2, ansi_normal()); - else - printf("%s%nGlobal%n%s:", ansi_highlight(), &pos1, &pos2, ansi_normal()); - - size_t cols = columns(), position = pos2 - pos1 + 2; - - STRV_FOREACH(i, p) { - size_t our_len = utf8_console_width(*i); /* This returns -1 on invalid utf-8 (which shouldn't happen). - * If that happens, we'll just print one item per line. */ - - if (position <= indent || size_add(size_add(position, 1), our_len) < cols) { - printf(" %s", *i); - position = size_add(size_add(position, 1), our_len); - } else { - printf("\n%*s%s", (int) indent, "", *i); - position = size_add(our_len, indent); - } - } - - printf("\n"); - - return 0; -} - -static void status_print_string(DNSConfiguration *c, const char *p) { - assert(c); - - if (c->ifname) - printf("%sLink %i (%s)%s: %s\n", - ansi_highlight(), - c->ifindex, - c->ifname, - ansi_normal(), - p); - else if (c->delegate) - printf("%sDelegate %s%s: %s\n", - ansi_highlight(), - c->delegate, - ansi_normal(), - p); - else - printf("%sGlobal%s: %s\n", ansi_highlight(), ansi_normal(), p); -} - -static void status_print_header(DNSConfiguration *c) { - assert(c); - - if (c->ifname) - printf("%sLink %i (%s)%s\n", - ansi_highlight(), - c->ifindex, - c->ifname, - ansi_normal()); - else if (c->delegate) - printf("%sDelegate %s%s\n", - ansi_highlight(), - c->delegate, - ansi_normal()); - else - printf("%sGlobal%s\n", ansi_highlight(), ansi_normal()); -} - -static int dump_list(Table *table, const char *field, char * const *l) { - int r; - - if (strv_isempty(l)) - return 0; - - r = table_add_many(table, - TABLE_FIELD, field, - TABLE_STRV_WRAPPED, l); - if (r < 0) - return table_log_add_error(r); - - return 0; -} - -static int strv_extend_extended_bool(char ***strv, const char *name, const char *value) { - int r; - - if (value) { - r = parse_boolean(value); - if (r >= 0) - return strv_extendf(strv, "%s%s", plus_minus(r), name); - } - - return strv_extendf(strv, "%s=%s", name, value ?: "???"); -} - -static int status_json_filter_fields(sd_json_variant **configuration, StatusMode mode) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - sd_json_variant *w; - const char *field; - int r; - - assert(configuration); - - field = status_mode_json_field_to_string(mode); - if (!field) - /* Nothing to filter for this mode. */ - return 0; - - JSON_VARIANT_ARRAY_FOREACH(w, *configuration) { - /* Always include identifier fields like ifname or delegate, and include the requested - * field even if it is empty in the configuration. */ - r = sd_json_variant_append_arraybo( - &v, - JSON_BUILD_PAIR_VARIANT_NON_NULL("ifname", sd_json_variant_by_key(w, "ifname")), - JSON_BUILD_PAIR_VARIANT_NON_NULL("ifindex", sd_json_variant_by_key(w, "ifindex")), - JSON_BUILD_PAIR_VARIANT_NON_NULL("delegate", sd_json_variant_by_key(w, "delegate")), - SD_JSON_BUILD_PAIR_VARIANT(field, sd_json_variant_by_key(w, field))); - if (r < 0) - return r; - } - - JSON_VARIANT_REPLACE(*configuration, TAKE_PTR(v)); + TAKE_PTR(reply); + *ret = sd_json_variant_ref(v); return 0; } @@ -1553,29 +1245,33 @@ static int status_json_filter_links(sd_json_variant **configuration, char **link return 0; } -static int varlink_dump_dns_configuration(sd_json_variant **ret) { - _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; - _cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL; - sd_json_variant *v; +static int status_json_filter_fields(sd_json_variant **configuration, StatusMode mode) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + sd_json_variant *w; + const char *field; int r; - assert(ret); + assert(configuration); - r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve"); - if (r < 0) - return log_error_errno(r, "Failed to connect to service /run/systemd/resolve/io.systemd.Resolve: %m"); + field = status_mode_json_field_to_string(mode); + if (!field) + /* Nothing to filter for this mode. */ + return 0; - r = varlink_call_and_log(vl, "io.systemd.Resolve.DumpDNSConfiguration", /* parameters= */ NULL, &reply); - if (r < 0) - return r; + JSON_VARIANT_ARRAY_FOREACH(w, *configuration) { + /* Always include identifier fields like ifname or delegate, and include the requested + * field even if it is empty in the configuration. */ + r = sd_json_variant_append_arraybo( + &v, + JSON_BUILD_PAIR_VARIANT_NON_NULL("ifname", sd_json_variant_by_key(w, "ifname")), + JSON_BUILD_PAIR_VARIANT_NON_NULL("ifindex", sd_json_variant_by_key(w, "ifindex")), + JSON_BUILD_PAIR_VARIANT_NON_NULL("delegate", sd_json_variant_by_key(w, "delegate")), + SD_JSON_BUILD_PAIR_VARIANT(field, sd_json_variant_by_key(w, field))); + if (r < 0) + return r; + } - v = sd_json_variant_by_key(reply, "configuration"); - - if (!sd_json_variant_is_array(v)) - return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "DumpDNSConfiguration() response missing 'configuration' key."); - - TAKE_PTR(reply); - *ret = sd_json_variant_ref(v); + JSON_VARIANT_REPLACE(*configuration, TAKE_PTR(v)); return 0; } @@ -1735,6 +1431,92 @@ static int format_scopes_string(DNSConfiguration *configuration, char **ret) { return 0; } +static void status_print_header(DNSConfiguration *c) { + assert(c); + + if (c->ifname) + printf("%sLink %i (%s)%s\n", + ansi_highlight(), + c->ifindex, + c->ifname, + ansi_normal()); + else if (c->delegate) + printf("%sDelegate %s%s\n", + ansi_highlight(), + c->delegate, + ansi_normal()); + else + printf("%sGlobal%s\n", ansi_highlight(), ansi_normal()); +} + +static void status_print_string(DNSConfiguration *c, const char *p) { + assert(c); + + if (c->ifname) + printf("%sLink %i (%s)%s: %s\n", + ansi_highlight(), + c->ifindex, + c->ifname, + ansi_normal(), + p); + else if (c->delegate) + printf("%sDelegate %s%s: %s\n", + ansi_highlight(), + c->delegate, + ansi_normal(), + p); + else + printf("%sGlobal%s: %s\n", ansi_highlight(), ansi_normal(), p); +} + +static int status_print_strv(DNSConfiguration *c, char **p) { + const unsigned indent = strlen("Global: "); /* Use the same indentation everywhere to make things nice */ + int pos1, pos2; + + assert(c); + + if (c->ifname) + printf("%s%nLink %i (%s)%n%s:", ansi_highlight(), &pos1, c->ifindex, c->ifname, &pos2, ansi_normal()); + else if (c->delegate) + printf("%s%nDelegate %s%n%s:", ansi_highlight(), &pos1, c->delegate, &pos2, ansi_normal()); + else + printf("%s%nGlobal%n%s:", ansi_highlight(), &pos1, &pos2, ansi_normal()); + + size_t cols = columns(), position = pos2 - pos1 + 2; + + STRV_FOREACH(i, p) { + size_t our_len = utf8_console_width(*i); /* This returns -1 on invalid utf-8 (which shouldn't happen). + * If that happens, we'll just print one item per line. */ + + if (position <= indent || size_add(size_add(position, 1), our_len) < cols) { + printf(" %s", *i); + position = size_add(size_add(position, 1), our_len); + } else { + printf("\n%*s%s", (int) indent, "", *i); + position = size_add(our_len, indent); + } + } + + printf("\n"); + + return 0; +} + +static int dump_list(Table *table, const char *field, char * const *l) { + int r; + + if (strv_isempty(l)) + return 0; + + r = table_add_many(table, + TABLE_FIELD, field, + TABLE_STRV_WRAPPED, l); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + static int print_configuration(DNSConfiguration *configuration, StatusMode mode, bool *empty_line) { _cleanup_(table_unrefp) Table *table = NULL; int r; @@ -1985,6 +1767,456 @@ static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) return status_full(STATUS_ALL, strv_skip(argv, 1)); } +static int verb_show_statistics(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(table_unrefp) Table *table = NULL; + sd_json_variant *reply = NULL; + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + int r; + + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + + r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); + if (r < 0) + return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); + + r = varlink_callbo_and_log( + vl, + "io.systemd.Resolve.Monitor.DumpStatistics", + &reply, + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + if (r < 0) + return r; + + if (sd_json_format_enabled(arg_json_format_flags)) + return sd_json_variant_dump(reply, arg_json_format_flags, NULL, NULL); + + struct statistics { + sd_json_variant *transactions; + sd_json_variant *cache; + sd_json_variant *dnssec; + } statistics; + + static const sd_json_dispatch_field statistics_dispatch_table[] = { + { "transactions", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, transactions), SD_JSON_MANDATORY }, + { "cache", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, cache), SD_JSON_MANDATORY }, + { "dnssec", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, dnssec), SD_JSON_MANDATORY }, + {}, + }; + + r = sd_json_dispatch(reply, statistics_dispatch_table, SD_JSON_LOG, &statistics); + if (r < 0) + return r; + + struct transactions { + uint64_t n_current_transactions; + uint64_t n_transactions_total; + uint64_t n_timeouts_total; + uint64_t n_timeouts_served_stale_total; + uint64_t n_failure_responses_total; + uint64_t n_failure_responses_served_stale_total; + } transactions; + + static const sd_json_dispatch_field transactions_dispatch_table[] = { + { "currentTransactions", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_current_transactions), SD_JSON_MANDATORY }, + { "totalTransactions", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_transactions_total), SD_JSON_MANDATORY }, + { "totalTimeouts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_timeouts_total), SD_JSON_MANDATORY }, + { "totalTimeoutsServedStale", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_timeouts_served_stale_total), SD_JSON_MANDATORY }, + { "totalFailedResponses", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_failure_responses_total), SD_JSON_MANDATORY }, + { "totalFailedResponsesServedStale", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_failure_responses_served_stale_total), SD_JSON_MANDATORY }, + {}, + }; + + r = sd_json_dispatch(statistics.transactions, transactions_dispatch_table, SD_JSON_LOG, &transactions); + if (r < 0) + return r; + + struct cache { + uint64_t cache_size; + uint64_t n_cache_hit; + uint64_t n_cache_miss; + } cache; + + static const sd_json_dispatch_field cache_dispatch_table[] = { + { "size", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, cache_size), SD_JSON_MANDATORY }, + { "hits", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, n_cache_hit), SD_JSON_MANDATORY }, + { "misses", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, n_cache_miss), SD_JSON_MANDATORY }, + {}, + }; + + r = sd_json_dispatch(statistics.cache, cache_dispatch_table, SD_JSON_LOG, &cache); + if (r < 0) + return r; + + struct dnsssec { + uint64_t n_dnssec_secure; + uint64_t n_dnssec_insecure; + uint64_t n_dnssec_bogus; + uint64_t n_dnssec_indeterminate; + } dnsssec; + + static const sd_json_dispatch_field dnssec_dispatch_table[] = { + { "secure", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_secure), SD_JSON_MANDATORY }, + { "insecure", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_insecure), SD_JSON_MANDATORY }, + { "bogus", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_bogus), SD_JSON_MANDATORY }, + { "indeterminate", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_indeterminate), SD_JSON_MANDATORY }, + {}, + }; + + r = sd_json_dispatch(statistics.dnssec, dnssec_dispatch_table, SD_JSON_LOG, &dnsssec); + if (r < 0) + return r; + + table = table_new_vertical(); + if (!table) + return log_oom(); + + r = table_add_many(table, + TABLE_STRING, "Transactions", + TABLE_SET_COLOR, ansi_highlight(), + TABLE_SET_ALIGN_PERCENT, 0, + TABLE_EMPTY, + TABLE_FIELD, "Current Transactions", + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_UINT64, transactions.n_current_transactions, + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_FIELD, "Total Transactions", + TABLE_UINT64, transactions.n_transactions_total, + TABLE_EMPTY, TABLE_EMPTY, + TABLE_STRING, "Cache", + TABLE_SET_COLOR, ansi_highlight(), + TABLE_SET_ALIGN_PERCENT, 0, + TABLE_EMPTY, + TABLE_FIELD, "Current Cache Size", + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_UINT64, cache.cache_size, + TABLE_FIELD, "Cache Hits", + TABLE_UINT64, cache.n_cache_hit, + TABLE_FIELD, "Cache Misses", + TABLE_UINT64, cache.n_cache_miss, + TABLE_EMPTY, TABLE_EMPTY, + TABLE_STRING, "Failure Transactions", + TABLE_SET_COLOR, ansi_highlight(), + TABLE_SET_ALIGN_PERCENT, 0, + TABLE_EMPTY, + TABLE_FIELD, "Total Timeouts", + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_UINT64, transactions.n_timeouts_total, + TABLE_FIELD, "Total Timeouts (Stale Data Served)", + TABLE_UINT64, transactions.n_timeouts_served_stale_total, + TABLE_FIELD, "Total Failure Responses", + TABLE_UINT64, transactions.n_failure_responses_total, + TABLE_FIELD, "Total Failure Responses (Stale Data Served)", + TABLE_UINT64, transactions.n_failure_responses_served_stale_total, + TABLE_EMPTY, TABLE_EMPTY, + TABLE_STRING, "DNSSEC Verdicts", + TABLE_SET_COLOR, ansi_highlight(), + TABLE_SET_ALIGN_PERCENT, 0, + TABLE_EMPTY, + TABLE_FIELD, "Secure", + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_UINT64, dnsssec.n_dnssec_secure, + TABLE_FIELD, "Insecure", + TABLE_UINT64, dnsssec.n_dnssec_insecure, + TABLE_FIELD, "Bogus", + TABLE_UINT64, dnsssec.n_dnssec_bogus, + TABLE_FIELD, "Indeterminate", + TABLE_UINT64, dnsssec.n_dnssec_indeterminate + ); + if (r < 0) + return table_log_add_error(r); + + return table_print_or_warn(table); +} + +static int verb_reset_statistics(int argc, char *argv[], uintptr_t _data, void *userdata) { + sd_json_variant *reply = NULL; + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + int r; + + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + + r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); + if (r < 0) + return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); + + r = varlink_callbo_and_log( + vl, + "io.systemd.Resolve.Monitor.ResetStatistics", + &reply, + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + if (r < 0) + return r; + + if (sd_json_format_enabled(arg_json_format_flags)) + return sd_json_variant_dump(reply, arg_json_format_flags, NULL, NULL); + + return 0; +} + +static int verb_flush_caches(int argc, char *argv[], uintptr_t _data, 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; + int r; + + r = acquire_bus(&bus); + if (r < 0) + return r; + + r = bus_call_method(bus, bus_resolve_mgr, "FlushCaches", &error, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to flush caches: %s", bus_error_message(&error, r)); + + return 0; +} + +static int verb_reset_server_features(int argc, char *argv[], uintptr_t _data, 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; + int r; + + r = acquire_bus(&bus); + if (r < 0) + return r; + + r = bus_call_method(bus, bus_resolve_mgr, "ResetServerFeatures", &error, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to reset server features: %s", bus_error_message(&error, r)); + + return 0; +} + +static int print_question(char prefix, const char *color, sd_json_variant *question) { + sd_json_variant *q = NULL; + int r; + + assert(color); + + JSON_VARIANT_ARRAY_FOREACH(q, question) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + char buf[DNS_RESOURCE_KEY_STRING_MAX]; + + r = dns_resource_key_from_json(q, &key); + if (r < 0) { + log_warning_errno(r, "Received monitor message with invalid question key, ignoring: %m"); + continue; + } + + printf("%s%s %c%s: %s\n", + color, + glyph(GLYPH_ARROW_RIGHT), + prefix, + ansi_normal(), + dns_resource_key_to_string(key, buf, sizeof(buf))); + } + + return 0; +} + +static int print_answer(sd_json_variant *answer) { + sd_json_variant *a; + int r; + + JSON_VARIANT_ARRAY_FOREACH(a, answer) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_free_ void *d = NULL; + sd_json_variant *jraw; + const char *s; + size_t l; + + jraw = sd_json_variant_by_key(a, "raw"); + if (!jraw) { + log_warning("Received monitor answer lacking valid raw data, ignoring."); + continue; + } + + r = sd_json_variant_unbase64(jraw, &d, &l); + if (r < 0) { + log_warning_errno(r, "Failed to undo base64 encoding of monitor answer raw data, ignoring."); + continue; + } + + r = dns_resource_record_new_from_raw(&rr, d, l); + if (r < 0) { + log_warning_errno(r, "Failed to parse monitor answer RR, ignoring: %m"); + continue; + } + + s = dns_resource_record_to_string(rr); + if (!s) + return log_oom(); + + printf("%s%s A%s: %s\n", + ansi_highlight_yellow(), + glyph(GLYPH_ARROW_LEFT), + ansi_normal(), + s); + } + + return 0; +} + +typedef struct MonitorQueryParams { + sd_json_variant *question; + sd_json_variant *answer; + sd_json_variant *collected_questions; + int rcode; + int error; + int ede_code; + const char *state; + const char *result; + const char *ede_msg; +} MonitorQueryParams; + +static void monitor_query_params_done(MonitorQueryParams *p) { + assert(p); + + sd_json_variant_unref(p->question); + sd_json_variant_unref(p->answer); + sd_json_variant_unref(p->collected_questions); +} + +static void monitor_query_dump(sd_json_variant *v) { + static const sd_json_dispatch_field dispatch_table[] = { + { "question", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, question), SD_JSON_MANDATORY }, + { "answer", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, answer), 0 }, + { "collectedQuestions", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, collected_questions), 0 }, + { "state", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, state), SD_JSON_MANDATORY }, + { "result", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, result), 0 }, + { "rcode", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, rcode), 0 }, + { "errno", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, error), 0 }, + { "extendedDNSErrorCode", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, ede_code), 0 }, + { "extendedDNSErrorMessage", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, ede_msg), 0 }, + {} + }; + + _cleanup_(monitor_query_params_done) MonitorQueryParams p = { + .rcode = -1, + .ede_code = -1, + }; + + assert(v); + + if (sd_json_dispatch(v, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &p) < 0) + return; + + /* First show the current question */ + print_question('Q', ansi_highlight_cyan(), p.question); + + /* And then show the questions that led to this one in case this was a CNAME chain */ + print_question('C', ansi_highlight_grey(), p.collected_questions); + + printf("%s%s S%s: %s", + streq_ptr(p.state, "success") ? ansi_highlight_green() : ansi_highlight_red(), + glyph(GLYPH_ARROW_LEFT), + ansi_normal(), + streq_ptr(p.state, "errno") ? ERRNO_NAME(p.error) : + streq_ptr(p.state, "rcode-failure") ? strna(dns_rcode_to_string(p.rcode)) : + strna(p.state)); + + if (!isempty(p.result)) + printf(": %s", p.result); + + if (p.ede_code >= 0) + printf(" (%s%s%s)", + FORMAT_DNS_EDE_RCODE(p.ede_code), + !isempty(p.ede_msg) ? ": " : "", + strempty(p.ede_msg)); + + puts(""); + + print_answer(p.answer); +} + +static int monitor_reply( + sd_varlink *link, + sd_json_variant *parameters, + const char *error_id, + sd_varlink_reply_flags_t flags, + void *userdata) { + + assert(link); + + if (error_id) { + bool disconnect; + + disconnect = streq(error_id, SD_VARLINK_ERROR_DISCONNECTED); + if (disconnect) + log_info("Disconnected."); + else + log_error("Varlink error: %s", error_id); + + (void) sd_event_exit(ASSERT_PTR(sd_varlink_get_event(link)), disconnect ? EXIT_SUCCESS : EXIT_FAILURE); + return 0; + } + + if (sd_json_variant_by_key(parameters, "ready")) { + /* The first message coming in will just indicate that we are now subscribed. We let our + * caller know if they asked for it. Once the caller sees this they should know that we are + * not going to miss any queries anymore. */ + (void) sd_notify(/* unset_environment=false */ false, "READY=1"); + return 0; + } + + if (!sd_json_format_enabled(arg_json_format_flags)) { + monitor_query_dump(parameters); + printf("\n"); + } else + sd_json_variant_dump(parameters, arg_json_format_flags, NULL, NULL); + + fflush(stdout); + + return 0; +} + +static int verb_monitor(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + int r, c; + + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); + + r = sd_event_set_signal_exit(event, true); + if (r < 0) + return log_error_errno(r, "Failed to enable exit on SIGINT/SIGTERM: %m"); + + r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); + if (r < 0) + return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); + + r = sd_varlink_set_relative_timeout(vl, USEC_INFINITY); /* We want the monitor to run basically forever */ + if (r < 0) + return log_error_errno(r, "Failed to set varlink timeout: %m"); + + r = sd_varlink_attach_event(vl, event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); + + r = sd_varlink_bind_reply(vl, monitor_reply); + if (r < 0) + return log_error_errno(r, "Failed to bind reply callback to varlink connection: %m"); + + r = sd_varlink_observebo( + vl, + "io.systemd.Resolve.Monitor.SubscribeQueryResults", + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + if (r < 0) + return log_error_errno(r, "Failed to issue SubscribeQueryResults() varlink call: %m"); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + r = sd_event_get_exit_code(event, &c); + if (r < 0) + return log_error_errno(r, "Failed to get exit code: %m"); + + return c; +} + static int call_dns(sd_bus *bus, char **dns, const BusLocator *locator, sd_bus_error *error, bool extended) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; int r; @@ -2058,6 +2290,349 @@ static int call_dns(sd_bus *bus, char **dns, const BusLocator *locator, sd_bus_e return r; } +static int dump_cache_item(sd_json_variant *item) { + + struct item_info { + sd_json_variant *key; + sd_json_variant *rrs; + const char *type; + uint64_t until; + } item_info = {}; + + static const sd_json_dispatch_field dispatch_table[] = { + { "key", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct item_info, key), SD_JSON_MANDATORY }, + { "rrs", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant_noref, offsetof(struct item_info, rrs), 0 }, + { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct item_info, type), 0 }, + { "until", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct item_info, until), 0 }, + {}, + }; + + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; + int r, c = 0; + + r = sd_json_dispatch(item, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &item_info); + if (r < 0) + return r; + + r = dns_resource_key_from_json(item_info.key, &k); + if (r < 0) + return log_error_errno(r, "Failed to turn JSON data to resource key: %m"); + + if (item_info.type) + printf("%s %s%s%s\n", DNS_RESOURCE_KEY_TO_STRING(k), ansi_highlight_red(), item_info.type, ansi_normal()); + else { + sd_json_variant *i; + + JSON_VARIANT_ARRAY_FOREACH(i, item_info.rrs) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_free_ void *data = NULL; + sd_json_variant *raw; + size_t size; + + raw = sd_json_variant_by_key(i, "raw"); + if (!raw) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "raw field missing from RR JSON data."); + + r = sd_json_variant_unbase64(raw, &data, &size); + if (r < 0) + return log_error_errno(r, "Unable to decode raw RR JSON data: %m"); + + r = dns_resource_record_new_from_raw(&rr, data, size); + if (r < 0) + return log_error_errno(r, "Failed to parse DNS data: %m"); + + printf("%s\n", dns_resource_record_to_string(rr)); + c++; + } + } + + return c; +} + +static int dump_cache_scope(sd_json_variant *scope) { + struct scope_info { + const char *protocol; + int family; + int ifindex; + const char *ifname; + sd_json_variant *cache; + const char *dnssec_mode; + const char *dns_over_tls_mode; + } scope_info = { + .family = AF_UNSPEC, + }; + sd_json_variant *i; + int r, c = 0; + + static const sd_json_dispatch_field dispatch_table[] = { + { "protocol", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, protocol), SD_JSON_MANDATORY }, + { "family", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(struct scope_info, family), 0 }, + { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(struct scope_info, ifindex), SD_JSON_RELAX }, + { "ifname", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, ifname), 0 }, + { "cache", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant_noref, offsetof(struct scope_info, cache), SD_JSON_MANDATORY }, + { "dnssec", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, dnssec_mode), 0 }, + { "dnsOverTLS", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, dns_over_tls_mode), 0 }, + {}, + }; + + r = sd_json_dispatch(scope, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &scope_info); + if (r < 0) + return r; + + printf("%sScope protocol=%s", ansi_underline(), scope_info.protocol); + + if (scope_info.family != AF_UNSPEC) + printf(" family=%s", af_to_name(scope_info.family)); + + if (scope_info.ifindex > 0) + printf(" ifindex=%i", scope_info.ifindex); + if (scope_info.ifname) + printf(" ifname=%s", scope_info.ifname); + + if (dns_protocol_from_string(scope_info.protocol) == DNS_PROTOCOL_DNS) { + if (scope_info.dnssec_mode) + printf(" DNSSEC=%s", scope_info.dnssec_mode); + if (scope_info.dns_over_tls_mode) + printf(" DNSOverTLS=%s", scope_info.dns_over_tls_mode); + } + + printf("%s\n", ansi_normal()); + + JSON_VARIANT_ARRAY_FOREACH(i, scope_info.cache) { + r = dump_cache_item(i); + if (r < 0) + return r; + + c += r; + } + + if (c == 0) + printf("%sNo entries.%s\n\n", ansi_grey(), ansi_normal()); + else + printf("\n"); + + return 0; +} + +static int verb_show_cache(int argc, char *argv[], uintptr_t _data, void *userdata) { + sd_json_variant *reply = NULL, *d = NULL; + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + int r; + + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + + r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); + if (r < 0) + return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); + + r = varlink_callbo_and_log( + vl, + "io.systemd.Resolve.Monitor.DumpCache", + &reply, + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + if (r < 0) + return r; + + d = sd_json_variant_by_key(reply, "dump"); + if (!d) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "DumpCache() response is missing 'dump' key."); + + if (!sd_json_variant_is_array(d)) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "DumpCache() response 'dump' field not an array"); + + if (!sd_json_format_enabled(arg_json_format_flags)) { + sd_json_variant *i; + + JSON_VARIANT_ARRAY_FOREACH(i, d) { + r = dump_cache_scope(i); + if (r < 0) + return r; + } + + return 0; + } + + return sd_json_variant_dump(d, arg_json_format_flags, NULL, NULL); +} + +static int dump_server_state(sd_json_variant *server) { + _cleanup_(table_unrefp) Table *table = NULL; + TableCell *cell; + + struct server_state { + const char *server_name; + const char *type; + const char *ifname; + int ifindex; + const char *verified_feature_level; + const char *possible_feature_level; + const char *dnssec_mode; + bool dnssec_supported; + size_t received_udp_fragment_max; + uint64_t n_failed_udp; + uint64_t n_failed_tcp; + bool packet_truncated; + bool packet_bad_opt; + bool packet_rrsig_missing; + bool packet_invalid; + bool packet_do_off; + } server_state = { + .ifindex = -1, + }; + + int r; + + static const sd_json_dispatch_field dispatch_table[] = { + { "Server", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, server_name), SD_JSON_MANDATORY }, + { "Type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, type), SD_JSON_MANDATORY }, + { "Interface", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, ifname), 0 }, + { "InterfaceIndex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(struct server_state, ifindex), SD_JSON_RELAX }, + { "VerifiedFeatureLevel", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, verified_feature_level), 0 }, + { "PossibleFeatureLevel", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, possible_feature_level), 0 }, + { "DNSSECMode", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, dnssec_mode), SD_JSON_MANDATORY }, + { "DNSSECSupported", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, dnssec_supported), SD_JSON_MANDATORY }, + { "ReceivedUDPFragmentMax", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, received_udp_fragment_max), SD_JSON_MANDATORY }, + { "FailedUDPAttempts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, n_failed_udp), SD_JSON_MANDATORY }, + { "FailedTCPAttempts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, n_failed_tcp), SD_JSON_MANDATORY }, + { "PacketTruncated", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_truncated), SD_JSON_MANDATORY }, + { "PacketBadOpt", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_bad_opt), SD_JSON_MANDATORY }, + { "PacketRRSIGMissing", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_rrsig_missing), SD_JSON_MANDATORY }, + { "PacketInvalid", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_invalid), SD_JSON_MANDATORY }, + { "PacketDoOff", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_do_off), SD_JSON_MANDATORY }, + {}, + }; + + r = sd_json_dispatch(server, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &server_state); + if (r < 0) + return r; + + table = table_new_vertical(); + if (!table) + return log_oom(); + + assert_se(cell = table_get_cell(table, 0, 0)); + (void) table_set_ellipsize_percent(table, cell, 100); + (void) table_set_align_percent(table, cell, 0); + + r = table_add_cell_stringf(table, NULL, "Server: %s", server_state.server_name); + if (r < 0) + return table_log_add_error(r); + + r = table_add_many(table, + TABLE_EMPTY, + TABLE_FIELD, "Type", + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_STRING, server_state.type); + if (r < 0) + return table_log_add_error(r); + + if (server_state.ifname) { + r = table_add_many(table, + TABLE_FIELD, "Interface", + TABLE_STRING, server_state.ifname); + if (r < 0) + return table_log_add_error(r); + } + + if (server_state.ifindex >= 0) { + r = table_add_many(table, + TABLE_FIELD, "Interface Index", + TABLE_INT, server_state.ifindex); + if (r < 0) + return table_log_add_error(r); + } + + if (server_state.verified_feature_level) { + r = table_add_many(table, + TABLE_FIELD, "Verified feature level", + TABLE_STRING, server_state.verified_feature_level); + if (r < 0) + return table_log_add_error(r); + } + + if (server_state.possible_feature_level) { + r = table_add_many(table, + TABLE_FIELD, "Possible feature level", + TABLE_STRING, server_state.possible_feature_level); + if (r < 0) + return table_log_add_error(r); + } + + r = table_add_many(table, + TABLE_FIELD, "DNSSEC Mode", + TABLE_STRING, server_state.dnssec_mode, + TABLE_FIELD, "DNSSEC Supported", + TABLE_STRING, yes_no(server_state.dnssec_supported), + TABLE_FIELD, "Maximum UDP fragment size received", + TABLE_UINT64, server_state.received_udp_fragment_max, + TABLE_FIELD, "Failed UDP attempts", + TABLE_UINT64, server_state.n_failed_udp, + TABLE_FIELD, "Failed TCP attempts", + TABLE_UINT64, server_state.n_failed_tcp, + TABLE_FIELD, "Seen truncated packet", + TABLE_STRING, yes_no(server_state.packet_truncated), + TABLE_FIELD, "Seen OPT RR getting lost", + TABLE_STRING, yes_no(server_state.packet_bad_opt), + TABLE_FIELD, "Seen RRSIG RR missing", + TABLE_STRING, yes_no(server_state.packet_rrsig_missing), + TABLE_FIELD, "Seen invalid packet", + TABLE_STRING, yes_no(server_state.packet_invalid), + TABLE_FIELD, "Server dropped DO flag", + TABLE_STRING, yes_no(server_state.packet_do_off), + TABLE_SET_ALIGN_PERCENT, 0, + TABLE_EMPTY, TABLE_EMPTY); + + if (r < 0) + return table_log_add_error(r); + + return table_print_or_warn(table); +} + +static int verb_show_server_state(int argc, char *argv[], uintptr_t _data, void *userdata) { + sd_json_variant *reply = NULL, *d = NULL; + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + int r; + + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + + r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); + if (r < 0) + return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); + + r = varlink_callbo_and_log( + vl, + "io.systemd.Resolve.Monitor.DumpServerState", + &reply, + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + if (r < 0) + return r; + + d = sd_json_variant_by_key(reply, "dump"); + if (!d) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "DumpCache() response is missing 'dump' key."); + + if (!sd_json_variant_is_array(d)) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "DumpCache() response 'dump' field not an array"); + + if (!sd_json_format_enabled(arg_json_format_flags)) { + sd_json_variant *i; + + JSON_VARIANT_ARRAY_FOREACH(i, d) { + r = dump_server_state(i); + if (r < 0) + return r; + } + + return 0; + } + + return sd_json_variant_dump(d, arg_json_format_flags, NULL, NULL); +} + static int verb_dns(int argc, char *argv[], uintptr_t _data, 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; @@ -2563,582 +3138,6 @@ static int verb_log_level(int argc, char *argv[], uintptr_t _data, void *userdat return verb_log_control_common(bus, "org.freedesktop.resolve1", argv[0], argc == 2 ? argv[1] : NULL); } -static int print_question(char prefix, const char *color, sd_json_variant *question) { - sd_json_variant *q = NULL; - int r; - - assert(color); - - JSON_VARIANT_ARRAY_FOREACH(q, question) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - char buf[DNS_RESOURCE_KEY_STRING_MAX]; - - r = dns_resource_key_from_json(q, &key); - if (r < 0) { - log_warning_errno(r, "Received monitor message with invalid question key, ignoring: %m"); - continue; - } - - printf("%s%s %c%s: %s\n", - color, - glyph(GLYPH_ARROW_RIGHT), - prefix, - ansi_normal(), - dns_resource_key_to_string(key, buf, sizeof(buf))); - } - - return 0; -} - -static int print_answer(sd_json_variant *answer) { - sd_json_variant *a; - int r; - - JSON_VARIANT_ARRAY_FOREACH(a, answer) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_free_ void *d = NULL; - sd_json_variant *jraw; - const char *s; - size_t l; - - jraw = sd_json_variant_by_key(a, "raw"); - if (!jraw) { - log_warning("Received monitor answer lacking valid raw data, ignoring."); - continue; - } - - r = sd_json_variant_unbase64(jraw, &d, &l); - if (r < 0) { - log_warning_errno(r, "Failed to undo base64 encoding of monitor answer raw data, ignoring."); - continue; - } - - r = dns_resource_record_new_from_raw(&rr, d, l); - if (r < 0) { - log_warning_errno(r, "Failed to parse monitor answer RR, ignoring: %m"); - continue; - } - - s = dns_resource_record_to_string(rr); - if (!s) - return log_oom(); - - printf("%s%s A%s: %s\n", - ansi_highlight_yellow(), - glyph(GLYPH_ARROW_LEFT), - ansi_normal(), - s); - } - - return 0; -} - -typedef struct MonitorQueryParams { - sd_json_variant *question; - sd_json_variant *answer; - sd_json_variant *collected_questions; - int rcode; - int error; - int ede_code; - const char *state; - const char *result; - const char *ede_msg; -} MonitorQueryParams; - -static void monitor_query_params_done(MonitorQueryParams *p) { - assert(p); - - sd_json_variant_unref(p->question); - sd_json_variant_unref(p->answer); - sd_json_variant_unref(p->collected_questions); -} - -static void monitor_query_dump(sd_json_variant *v) { - static const sd_json_dispatch_field dispatch_table[] = { - { "question", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, question), SD_JSON_MANDATORY }, - { "answer", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, answer), 0 }, - { "collectedQuestions", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, collected_questions), 0 }, - { "state", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, state), SD_JSON_MANDATORY }, - { "result", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, result), 0 }, - { "rcode", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, rcode), 0 }, - { "errno", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, error), 0 }, - { "extendedDNSErrorCode", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, ede_code), 0 }, - { "extendedDNSErrorMessage", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, ede_msg), 0 }, - {} - }; - - _cleanup_(monitor_query_params_done) MonitorQueryParams p = { - .rcode = -1, - .ede_code = -1, - }; - - assert(v); - - if (sd_json_dispatch(v, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &p) < 0) - return; - - /* First show the current question */ - print_question('Q', ansi_highlight_cyan(), p.question); - - /* And then show the questions that led to this one in case this was a CNAME chain */ - print_question('C', ansi_highlight_grey(), p.collected_questions); - - printf("%s%s S%s: %s", - streq_ptr(p.state, "success") ? ansi_highlight_green() : ansi_highlight_red(), - glyph(GLYPH_ARROW_LEFT), - ansi_normal(), - streq_ptr(p.state, "errno") ? ERRNO_NAME(p.error) : - streq_ptr(p.state, "rcode-failure") ? strna(dns_rcode_to_string(p.rcode)) : - strna(p.state)); - - if (!isempty(p.result)) - printf(": %s", p.result); - - if (p.ede_code >= 0) - printf(" (%s%s%s)", - FORMAT_DNS_EDE_RCODE(p.ede_code), - !isempty(p.ede_msg) ? ": " : "", - strempty(p.ede_msg)); - - puts(""); - - print_answer(p.answer); -} - -static int monitor_reply( - sd_varlink *link, - sd_json_variant *parameters, - const char *error_id, - sd_varlink_reply_flags_t flags, - void *userdata) { - - assert(link); - - if (error_id) { - bool disconnect; - - disconnect = streq(error_id, SD_VARLINK_ERROR_DISCONNECTED); - if (disconnect) - log_info("Disconnected."); - else - log_error("Varlink error: %s", error_id); - - (void) sd_event_exit(ASSERT_PTR(sd_varlink_get_event(link)), disconnect ? EXIT_SUCCESS : EXIT_FAILURE); - return 0; - } - - if (sd_json_variant_by_key(parameters, "ready")) { - /* The first message coming in will just indicate that we are now subscribed. We let our - * caller know if they asked for it. Once the caller sees this they should know that we are - * not going to miss any queries anymore. */ - (void) sd_notify(/* unset_environment=false */ false, "READY=1"); - return 0; - } - - if (!sd_json_format_enabled(arg_json_format_flags)) { - monitor_query_dump(parameters); - printf("\n"); - } else - sd_json_variant_dump(parameters, arg_json_format_flags, NULL, NULL); - - fflush(stdout); - - return 0; -} - -static int verb_monitor(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; - int r, c; - - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - - r = sd_event_default(&event); - if (r < 0) - return log_error_errno(r, "Failed to get event loop: %m"); - - r = sd_event_set_signal_exit(event, true); - if (r < 0) - return log_error_errno(r, "Failed to enable exit on SIGINT/SIGTERM: %m"); - - r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); - if (r < 0) - return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); - - r = sd_varlink_set_relative_timeout(vl, USEC_INFINITY); /* We want the monitor to run basically forever */ - if (r < 0) - return log_error_errno(r, "Failed to set varlink timeout: %m"); - - r = sd_varlink_attach_event(vl, event, SD_EVENT_PRIORITY_NORMAL); - if (r < 0) - return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); - - r = sd_varlink_bind_reply(vl, monitor_reply); - if (r < 0) - return log_error_errno(r, "Failed to bind reply callback to varlink connection: %m"); - - r = sd_varlink_observebo( - vl, - "io.systemd.Resolve.Monitor.SubscribeQueryResults", - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); - if (r < 0) - return log_error_errno(r, "Failed to issue SubscribeQueryResults() varlink call: %m"); - - r = sd_event_loop(event); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); - - r = sd_event_get_exit_code(event, &c); - if (r < 0) - return log_error_errno(r, "Failed to get exit code: %m"); - - return c; -} - -static int dump_cache_item(sd_json_variant *item) { - - struct item_info { - sd_json_variant *key; - sd_json_variant *rrs; - const char *type; - uint64_t until; - } item_info = {}; - - static const sd_json_dispatch_field dispatch_table[] = { - { "key", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct item_info, key), SD_JSON_MANDATORY }, - { "rrs", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant_noref, offsetof(struct item_info, rrs), 0 }, - { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct item_info, type), 0 }, - { "until", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct item_info, until), 0 }, - {}, - }; - - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; - int r, c = 0; - - r = sd_json_dispatch(item, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &item_info); - if (r < 0) - return r; - - r = dns_resource_key_from_json(item_info.key, &k); - if (r < 0) - return log_error_errno(r, "Failed to turn JSON data to resource key: %m"); - - if (item_info.type) - printf("%s %s%s%s\n", DNS_RESOURCE_KEY_TO_STRING(k), ansi_highlight_red(), item_info.type, ansi_normal()); - else { - sd_json_variant *i; - - JSON_VARIANT_ARRAY_FOREACH(i, item_info.rrs) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_free_ void *data = NULL; - sd_json_variant *raw; - size_t size; - - raw = sd_json_variant_by_key(i, "raw"); - if (!raw) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "raw field missing from RR JSON data."); - - r = sd_json_variant_unbase64(raw, &data, &size); - if (r < 0) - return log_error_errno(r, "Unable to decode raw RR JSON data: %m"); - - r = dns_resource_record_new_from_raw(&rr, data, size); - if (r < 0) - return log_error_errno(r, "Failed to parse DNS data: %m"); - - printf("%s\n", dns_resource_record_to_string(rr)); - c++; - } - } - - return c; -} - -static int dump_cache_scope(sd_json_variant *scope) { - - struct scope_info { - const char *protocol; - int family; - int ifindex; - const char *ifname; - sd_json_variant *cache; - const char *dnssec_mode; - const char *dns_over_tls_mode; - } scope_info = { - .family = AF_UNSPEC, - }; - sd_json_variant *i; - int r, c = 0; - - static const sd_json_dispatch_field dispatch_table[] = { - { "protocol", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, protocol), SD_JSON_MANDATORY }, - { "family", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(struct scope_info, family), 0 }, - { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(struct scope_info, ifindex), SD_JSON_RELAX }, - { "ifname", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, ifname), 0 }, - { "cache", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant_noref, offsetof(struct scope_info, cache), SD_JSON_MANDATORY }, - { "dnssec", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, dnssec_mode), 0 }, - { "dnsOverTLS", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, dns_over_tls_mode), 0 }, - {}, - }; - - r = sd_json_dispatch(scope, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &scope_info); - if (r < 0) - return r; - - printf("%sScope protocol=%s", ansi_underline(), scope_info.protocol); - - if (scope_info.family != AF_UNSPEC) - printf(" family=%s", af_to_name(scope_info.family)); - - if (scope_info.ifindex > 0) - printf(" ifindex=%i", scope_info.ifindex); - if (scope_info.ifname) - printf(" ifname=%s", scope_info.ifname); - - if (dns_protocol_from_string(scope_info.protocol) == DNS_PROTOCOL_DNS) { - if (scope_info.dnssec_mode) - printf(" DNSSEC=%s", scope_info.dnssec_mode); - if (scope_info.dns_over_tls_mode) - printf(" DNSOverTLS=%s", scope_info.dns_over_tls_mode); - } - - printf("%s\n", ansi_normal()); - - JSON_VARIANT_ARRAY_FOREACH(i, scope_info.cache) { - r = dump_cache_item(i); - if (r < 0) - return r; - - c += r; - } - - if (c == 0) - printf("%sNo entries.%s\n\n", ansi_grey(), ansi_normal()); - else - printf("\n"); - - return 0; -} - -static int verb_show_cache(int argc, char *argv[], uintptr_t _data, void *userdata) { - sd_json_variant *reply = NULL, *d = NULL; - _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; - int r; - - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - - r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); - if (r < 0) - return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); - - r = varlink_callbo_and_log( - vl, - "io.systemd.Resolve.Monitor.DumpCache", - &reply, - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); - if (r < 0) - return r; - - d = sd_json_variant_by_key(reply, "dump"); - if (!d) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "DumpCache() response is missing 'dump' key."); - - if (!sd_json_variant_is_array(d)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "DumpCache() response 'dump' field not an array"); - - if (!sd_json_format_enabled(arg_json_format_flags)) { - sd_json_variant *i; - - JSON_VARIANT_ARRAY_FOREACH(i, d) { - r = dump_cache_scope(i); - if (r < 0) - return r; - } - - return 0; - } - - return sd_json_variant_dump(d, arg_json_format_flags, NULL, NULL); -} - -static int dump_server_state(sd_json_variant *server) { - _cleanup_(table_unrefp) Table *table = NULL; - TableCell *cell; - - struct server_state { - const char *server_name; - const char *type; - const char *ifname; - int ifindex; - const char *verified_feature_level; - const char *possible_feature_level; - const char *dnssec_mode; - bool dnssec_supported; - size_t received_udp_fragment_max; - uint64_t n_failed_udp; - uint64_t n_failed_tcp; - bool packet_truncated; - bool packet_bad_opt; - bool packet_rrsig_missing; - bool packet_invalid; - bool packet_do_off; - } server_state = { - .ifindex = -1, - }; - - int r; - - static const sd_json_dispatch_field dispatch_table[] = { - { "Server", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, server_name), SD_JSON_MANDATORY }, - { "Type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, type), SD_JSON_MANDATORY }, - { "Interface", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, ifname), 0 }, - { "InterfaceIndex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(struct server_state, ifindex), SD_JSON_RELAX }, - { "VerifiedFeatureLevel", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, verified_feature_level), 0 }, - { "PossibleFeatureLevel", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, possible_feature_level), 0 }, - { "DNSSECMode", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, dnssec_mode), SD_JSON_MANDATORY }, - { "DNSSECSupported", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, dnssec_supported), SD_JSON_MANDATORY }, - { "ReceivedUDPFragmentMax", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, received_udp_fragment_max), SD_JSON_MANDATORY }, - { "FailedUDPAttempts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, n_failed_udp), SD_JSON_MANDATORY }, - { "FailedTCPAttempts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, n_failed_tcp), SD_JSON_MANDATORY }, - { "PacketTruncated", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_truncated), SD_JSON_MANDATORY }, - { "PacketBadOpt", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_bad_opt), SD_JSON_MANDATORY }, - { "PacketRRSIGMissing", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_rrsig_missing), SD_JSON_MANDATORY }, - { "PacketInvalid", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_invalid), SD_JSON_MANDATORY }, - { "PacketDoOff", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_do_off), SD_JSON_MANDATORY }, - {}, - }; - - r = sd_json_dispatch(server, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &server_state); - if (r < 0) - return r; - - table = table_new_vertical(); - if (!table) - return log_oom(); - - assert_se(cell = table_get_cell(table, 0, 0)); - (void) table_set_ellipsize_percent(table, cell, 100); - (void) table_set_align_percent(table, cell, 0); - - r = table_add_cell_stringf(table, NULL, "Server: %s", server_state.server_name); - if (r < 0) - return table_log_add_error(r); - - r = table_add_many(table, - TABLE_EMPTY, - TABLE_FIELD, "Type", - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_STRING, server_state.type); - if (r < 0) - return table_log_add_error(r); - - if (server_state.ifname) { - r = table_add_many(table, - TABLE_FIELD, "Interface", - TABLE_STRING, server_state.ifname); - if (r < 0) - return table_log_add_error(r); - } - - if (server_state.ifindex >= 0) { - r = table_add_many(table, - TABLE_FIELD, "Interface Index", - TABLE_INT, server_state.ifindex); - if (r < 0) - return table_log_add_error(r); - } - - if (server_state.verified_feature_level) { - r = table_add_many(table, - TABLE_FIELD, "Verified feature level", - TABLE_STRING, server_state.verified_feature_level); - if (r < 0) - return table_log_add_error(r); - } - - if (server_state.possible_feature_level) { - r = table_add_many(table, - TABLE_FIELD, "Possible feature level", - TABLE_STRING, server_state.possible_feature_level); - if (r < 0) - return table_log_add_error(r); - } - - r = table_add_many(table, - TABLE_FIELD, "DNSSEC Mode", - TABLE_STRING, server_state.dnssec_mode, - TABLE_FIELD, "DNSSEC Supported", - TABLE_STRING, yes_no(server_state.dnssec_supported), - TABLE_FIELD, "Maximum UDP fragment size received", - TABLE_UINT64, server_state.received_udp_fragment_max, - TABLE_FIELD, "Failed UDP attempts", - TABLE_UINT64, server_state.n_failed_udp, - TABLE_FIELD, "Failed TCP attempts", - TABLE_UINT64, server_state.n_failed_tcp, - TABLE_FIELD, "Seen truncated packet", - TABLE_STRING, yes_no(server_state.packet_truncated), - TABLE_FIELD, "Seen OPT RR getting lost", - TABLE_STRING, yes_no(server_state.packet_bad_opt), - TABLE_FIELD, "Seen RRSIG RR missing", - TABLE_STRING, yes_no(server_state.packet_rrsig_missing), - TABLE_FIELD, "Seen invalid packet", - TABLE_STRING, yes_no(server_state.packet_invalid), - TABLE_FIELD, "Server dropped DO flag", - TABLE_STRING, yes_no(server_state.packet_do_off), - TABLE_SET_ALIGN_PERCENT, 0, - TABLE_EMPTY, TABLE_EMPTY); - - if (r < 0) - return table_log_add_error(r); - - return table_print_or_warn(table); -} - -static int verb_show_server_state(int argc, char *argv[], uintptr_t _data, void *userdata) { - sd_json_variant *reply = NULL, *d = NULL; - _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; - int r; - - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - - r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); - if (r < 0) - return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); - - r = varlink_callbo_and_log( - vl, - "io.systemd.Resolve.Monitor.DumpServerState", - &reply, - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); - if (r < 0) - return r; - - d = sd_json_variant_by_key(reply, "dump"); - if (!d) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "DumpCache() response is missing 'dump' key."); - - if (!sd_json_variant_is_array(d)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "DumpCache() response 'dump' field not an array"); - - if (!sd_json_format_enabled(arg_json_format_flags)) { - sd_json_variant *i; - - JSON_VARIANT_ARRAY_FOREACH(i, d) { - r = dump_server_state(i); - if (r < 0) - return r; - } - - return 0; - } - - return sd_json_variant_dump(d, arg_json_format_flags, NULL, NULL); -} - static int parse_protocol(const char *arg) { if (streq(arg, "help")) { if (arg_legend) From 4e20d61c65c232a25dd30ce0aad4235dec3f634c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 7 May 2026 13:38:04 +0200 Subject: [PATCH 4/6] shared/verbs: when showing default verb, put [] around the args too The verb cannot be omitted but the args kept, so: resolvectl [status] [link] is wrong, we need: resolvectl [status [link]] Fixes f94da4b4c564f8cff4b5b739456c985e036a4201. --- src/shared/verbs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/verbs.c b/src/shared/verbs.c index 276c6fd5be..274945e5c5 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -200,9 +200,9 @@ int _verbs_get_help_table( r = table_add_cell_stringf(table, NULL, " %s%s%s%s%s", is_default ? "[" : "", verb->verb, - is_default ? "]" : "", verb->argspec ? " " : "", - strempty(verb->argspec)); + strempty(verb->argspec), + is_default ? "]" : ""); if (r < 0) return table_log_add_error(r); From 7cea71e4c112177e74140b39898e9836f334e588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 7 May 2026 09:57:43 +0200 Subject: [PATCH 5/6] resolvectl: convert to OPTION and VERB macros Use OPTION_NAMESPACE() to keep the resolvectl and systemd-resolve option sets separate. The resolvconf-compat path (resolvconf invocation) keeps its own getopt-based parsing. --help output has the expected changes to formatting. Synopis for [status] is now shows that the verb is optional. Co-developed-by: Claude Opus 4.7 --- src/resolve/resolvectl.c | 668 +++++++++++++++------------------------ 1 file changed, 249 insertions(+), 419 deletions(-) diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 4088c39b1d..015345fdba 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -13,6 +12,7 @@ #include "af-list.h" #include "alloc-util.h" +#include "ansi-color.h" #include "argv-util.h" #include "build.h" #include "bus-common-errors.h" @@ -30,17 +30,19 @@ #include "escape.h" #include "format-ifname.h" #include "format-table.h" +#include "glyph-util.h" +#include "help-util.h" #include "hostname-util.h" #include "json-util.h" #include "main-func.h" #include "missing-network.h" #include "netlink-util.h" +#include "options.h" #include "ordered-set.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" #include "polkit-agent.h" -#include "pretty-print.h" #include "resolvconf-compat.h" #include "resolve-util.h" #include "resolvectl.h" @@ -799,6 +801,8 @@ invalid: "Invalid DNS URI: %s", name); } +VERB(verb_query, "query", "HOSTNAME|ADDRESS…", 2, VERB_ANY, 0, + "Resolve domain names, IPv4 and IPv6 addresses"); static int verb_query(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int ret = 0, r; @@ -1009,6 +1013,8 @@ static int resolve_service(sd_bus *bus, const char *name, const char *type, cons return 0; } +VERB(verb_service, "service", "[[NAME] TYPE] DOMAIN", 2, 4, 0, + "Resolve service (SRV)"); static int verb_service(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; @@ -1091,6 +1097,8 @@ static int resolve_openpgp(sd_bus *bus, const char *address) { } #endif +VERB(verb_openpgp, "openpgp", "EMAIL@DOMAIN…", 2, VERB_ANY, 0, + "Query OpenPGP public key"); static int verb_openpgp(int argc, char *argv[], uintptr_t _data, void *userdata) { #if HAVE_OPENSSL _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -1148,6 +1156,8 @@ static bool service_family_is_valid(const char *s) { return STR_IN_SET(s, "tcp", "udp", "sctp"); } +VERB(verb_tlsa, "tlsa", "DOMAIN[:PORT]…", 2, VERB_ANY, 0, + "Query TLS public key"); static int verb_tlsa(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; const char *family = "tcp"; @@ -1763,10 +1773,14 @@ static int status_ifindex(int ifindex, StatusMode mode) { return status_full(mode, STRV_MAKE(ifname)); } +VERB(verb_status, "status", "[LINK…]", VERB_ANY, VERB_ANY, VERB_DEFAULT, + "Show link and server status"); static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { return status_full(STATUS_ALL, strv_skip(argv, 1)); } +VERB(verb_show_statistics, "statistics", NULL, VERB_ANY, 1, 0, + "Show resolver statistics"); static int verb_show_statistics(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; sd_json_variant *reply = NULL; @@ -1928,6 +1942,8 @@ static int verb_show_statistics(int argc, char *argv[], uintptr_t _data, void *u return table_print_or_warn(table); } +VERB(verb_reset_statistics, "reset-statistics", NULL, VERB_ANY, 1, 0, + "Reset resolver statistics"); static int verb_reset_statistics(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_json_variant *reply = NULL; _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; @@ -1953,6 +1969,8 @@ static int verb_reset_statistics(int argc, char *argv[], uintptr_t _data, void * return 0; } +VERB(verb_flush_caches, "flush-caches", NULL, VERB_ANY, 1, 0, + "Flush all local DNS caches"); static int verb_flush_caches(int argc, char *argv[], uintptr_t _data, 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; @@ -1969,6 +1987,8 @@ static int verb_flush_caches(int argc, char *argv[], uintptr_t _data, void *user return 0; } +VERB(verb_reset_server_features, "reset-server-features", NULL, VERB_ANY, 1, 0, + "Forget learnt DNS server feature levels"); static int verb_reset_server_features(int argc, char *argv[], uintptr_t _data, 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; @@ -2168,6 +2188,8 @@ static int monitor_reply( return 0; } +VERB(verb_monitor, "monitor", NULL, VERB_ANY, 1, 0, + "Monitor DNS queries"); static int verb_monitor(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; @@ -2414,6 +2436,8 @@ static int dump_cache_scope(sd_json_variant *scope) { return 0; } +VERB(verb_show_cache, "show-cache", NULL, VERB_ANY, 1, 0, + "Show cache contents"); static int verb_show_cache(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_json_variant *reply = NULL, *d = NULL; _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; @@ -2590,6 +2614,8 @@ static int dump_server_state(sd_json_variant *server) { return table_print_or_warn(table); } +VERB(verb_show_server_state, "show-server-state", NULL, VERB_ANY, 1, 0, + "Show servers state"); static int verb_show_server_state(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_json_variant *reply = NULL, *d = NULL; _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; @@ -2633,6 +2659,8 @@ static int verb_show_server_state(int argc, char *argv[], uintptr_t _data, void return sd_json_variant_dump(d, arg_json_format_flags, NULL, NULL); } +VERB(verb_dns, "dns", "[LINK [SERVER…]]", VERB_ANY, VERB_ANY, 0, + "Get/set per-interface DNS server address"); static int verb_dns(int argc, char *argv[], uintptr_t _data, 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; @@ -2718,6 +2746,8 @@ static int call_domain(sd_bus *bus, char **domain, const BusLocator *locator, sd return sd_bus_call(bus, req, 0, error, NULL); } +VERB(verb_domain, "domain", "[LINK [DOMAIN…]]", VERB_ANY, VERB_ANY, 0, + "Get/set per-interface search domain"); static int verb_domain(int argc, char *argv[], uintptr_t _data, 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; @@ -2757,6 +2787,8 @@ static int verb_domain(int argc, char *argv[], uintptr_t _data, void *userdata) return 0; } +VERB(verb_default_route, "default-route", "[LINK [BOOL]]", VERB_ANY, 3, 0, + "Get/set per-interface default route flag"); static int verb_default_route(int argc, char *argv[], uintptr_t _data, 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; @@ -2801,6 +2833,8 @@ static int verb_default_route(int argc, char *argv[], uintptr_t _data, void *use return 0; } +VERB(verb_llmnr, "llmnr", "[LINK [MODE]]", VERB_ANY, 3, 0, + "Get/set per-interface LLMNR mode"); static int verb_llmnr(int argc, char *argv[], uintptr_t _data, 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; @@ -2859,6 +2893,8 @@ static int verb_llmnr(int argc, char *argv[], uintptr_t _data, void *userdata) { return 0; } +VERB(verb_mdns, "mdns", "[LINK [MODE]]", VERB_ANY, 3, 0, + "Get/set per-interface MulticastDNS mode"); static int verb_mdns(int argc, char *argv[], uintptr_t _data, 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; @@ -2923,6 +2959,8 @@ static int verb_mdns(int argc, char *argv[], uintptr_t _data, void *userdata) { return 0; } +VERB(verb_dns_over_tls, "dnsovertls", "[LINK [MODE]]", VERB_ANY, 3, 0, + "Get/set per-interface DNS-over-TLS mode"); static int verb_dns_over_tls(int argc, char *argv[], uintptr_t _data, 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; @@ -2969,6 +3007,8 @@ static int verb_dns_over_tls(int argc, char *argv[], uintptr_t _data, void *user return 0; } +VERB(verb_dnssec, "dnssec", "[LINK [MODE]]", VERB_ANY, 3, 0, + "Get/set per-interface DNSSEC mode"); static int verb_dnssec(int argc, char *argv[], uintptr_t _data, 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; @@ -3030,6 +3070,8 @@ static int call_nta(sd_bus *bus, char **nta, const BusLocator *locator, sd_bus_ return sd_bus_call(bus, req, 0, error, NULL); } +VERB(verb_nta, "nta", "[LINK [DOMAIN…]]", VERB_ANY, VERB_ANY, 0, + "Get/set per-interface DNSSEC NTA"); static int verb_nta(int argc, char *argv[], uintptr_t _data, 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; @@ -3088,6 +3130,8 @@ static int verb_nta(int argc, char *argv[], uintptr_t _data, void *userdata) { return 0; } +VERB(verb_revert_link, "revert", "LINK", VERB_ANY, 2, 0, + "Revert per-interface configuration"); static int verb_revert_link(int argc, char *argv[], uintptr_t _data, 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; @@ -3125,6 +3169,8 @@ static int verb_revert_link(int argc, char *argv[], uintptr_t _data, void *userd return 0; } +VERB(verb_log_level, "log-level", "[LEVEL]", VERB_ANY, 2, 0, + "Get/set logging threshold for systemd-resolved"); static int verb_log_level(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; @@ -3187,251 +3233,116 @@ static void help_dns_classes(void) { } static int compat_help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("resolvectl", "1", &link); + r = option_parser_get_help_table_ns("systemd-resolve", &options); if (r < 0) - return log_oom(); + return r; pager_open(arg_pager_flags); - printf("%1$s [OPTIONS...] HOSTNAME|ADDRESS...\n" - "%1$s [OPTIONS...] --service [[NAME] TYPE] DOMAIN\n" - "%1$s [OPTIONS...] --openpgp EMAIL@DOMAIN...\n" - "%1$s [OPTIONS...] --statistics\n" - "%1$s [OPTIONS...] --reset-statistics\n" - "\n" - "%2$sResolve domain names, IPv4 and IPv6 addresses, DNS records, and services.%3$s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -4 Resolve IPv4 addresses\n" - " -6 Resolve IPv6 addresses\n" - " -i --interface=INTERFACE Look on interface\n" - " -p --protocol=PROTO|help Look via protocol\n" - " -t --type=TYPE|help Query RR with DNS type\n" - " -c --class=CLASS|help Query RR with DNS class\n" - " --service Resolve service (SRV)\n" - " --service-address=BOOL Resolve address for services (default: yes)\n" - " --service-txt=BOOL Resolve TXT records for services (default: yes)\n" - " --openpgp Query OpenPGP public key\n" - " --tlsa Query TLS public key\n" - " --cname=BOOL Follow CNAME redirects (default: yes)\n" - " --search=BOOL Use search domains for single-label names\n" - " (default: yes)\n" - " --statistics Show resolver statistics\n" - " --reset-statistics Reset resolver statistics\n" - " --status Show link and server status\n" - " --flush-caches Flush all local DNS caches\n" - " --reset-server-features\n" - " Forget learnt DNS server feature levels\n" - " --set-dns=SERVER Set per-interface DNS server address\n" - " --set-domain=DOMAIN Set per-interface search domain\n" - " --set-llmnr=MODE Set per-interface LLMNR mode\n" - " --set-mdns=MODE Set per-interface MulticastDNS mode\n" - " --set-dnsovertls=MODE Set per-interface DNS-over-TLS mode\n" - " --set-dnssec=MODE Set per-interface DNSSEC mode\n" - " --set-nta=DOMAIN Set per-interface DNSSEC NTA\n" - " --revert Revert per-interface configuration\n" - " --raw[=payload|packet] Dump the answer as binary data\n" - " --no-pager Do not pipe output into a pager\n" - " --legend=BOOL Print headers and additional info (default: yes)\n" - "\nSee the %4$s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + help_cmdline("[OPTIONS…] HOSTNAME|ADDRESS…"); + help_cmdline("[OPTIONS…] --service [[NAME] TYPE] DOMAIN"); + help_cmdline("[OPTIONS…] --openpgp EMAIL@DOMAIN…"); + help_cmdline("[OPTIONS…] --statistics"); + help_cmdline("[OPTIONS…] --reset-statistics"); + help_abstract("Resolve domain names, IPv4 and IPv6 addresses, DNS records, and services."); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("resolvectl", "1"); return 0; } static int native_help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL; int r; - r = terminal_urlify_man("resolvectl", "1", &link); + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; + + r = option_parser_get_help_table_ns("resolvectl", &options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); pager_open(arg_pager_flags); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n" - "%5$sSend control commands to the network name resolution manager, or%6$s\n" - "%5$sresolve domain names, IPv4 and IPv6 addresses, DNS records, and services.%6$s\n" - "\n%3$sCommands:%4$s\n" - " query HOSTNAME|ADDRESS... Resolve domain names, IPv4 and IPv6 addresses\n" - " service [[NAME] TYPE] DOMAIN Resolve service (SRV)\n" - " openpgp EMAIL@DOMAIN... Query OpenPGP public key\n" - " tlsa DOMAIN[:PORT]... Query TLS public key\n" - " status [LINK...] Show link and server status\n" - " statistics Show resolver statistics\n" - " reset-statistics Reset resolver statistics\n" - " flush-caches Flush all local DNS caches\n" - " reset-server-features Forget learnt DNS server feature levels\n" - " monitor Monitor DNS queries\n" - " show-cache Show cache contents\n" - " show-server-state Show servers state\n" - " dns [LINK [SERVER...]] Get/set per-interface DNS server address\n" - " domain [LINK [DOMAIN...]] Get/set per-interface search domain\n" - " default-route [LINK [BOOL]] Get/set per-interface default route flag\n" - " llmnr [LINK [MODE]] Get/set per-interface LLMNR mode\n" - " mdns [LINK [MODE]] Get/set per-interface MulticastDNS mode\n" - " dnsovertls [LINK [MODE]] Get/set per-interface DNS-over-TLS mode\n" - " dnssec [LINK [MODE]] Get/set per-interface DNSSEC mode\n" - " nta [LINK [DOMAIN...]] Get/set per-interface DNSSEC NTA\n" - " revert LINK Revert per-interface configuration\n" - " log-level [LEVEL] Get/set logging threshold for systemd-resolved\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -4 Resolve IPv4 addresses\n" - " -6 Resolve IPv6 addresses\n" - " -i --interface=INTERFACE Look on interface\n" - " -p --protocol=PROTO|help Look via protocol\n" - " -t --type=TYPE|help Query RR with DNS type\n" - " -c --class=CLASS|help Query RR with DNS class\n" - " --service-address=BOOL Resolve address for services (default: yes)\n" - " --service-txt=BOOL Resolve TXT records for services (default: yes)\n" - " --cname=BOOL Follow CNAME redirects (default: yes)\n" - " --validate=BOOL Allow DNSSEC validation (default: yes)\n" - " --synthesize=BOOL Allow synthetic response (default: yes)\n" - " --cache=BOOL Allow response from cache (default: yes)\n" - " --stale-data=BOOL Allow response from cache with stale data (default: yes)\n" - " --relax-single-label=BOOL Allow single label lookups to go upstream (default: no)\n" - " --zone=BOOL Allow response from locally registered mDNS/LLMNR\n" - " records (default: yes)\n" - " --trust-anchor=BOOL Allow response from local trust anchor (default:\n" - " yes)\n" - " --network=BOOL Allow response from network (default: yes)\n" - " --search=BOOL Use search domains for single-label names (default:\n" - " yes)\n" - " --raw[=payload|packet] Dump the answer as binary data\n" - " --no-pager Do not pipe output into a pager\n" - " --no-ask-password Do not prompt for password\n" - " --legend=BOOL Print headers and additional info (default: yes)\n" - " --json=MODE Output as JSON\n" - " -j Same as --json=pretty on tty, --json=short\n" - " otherwise\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + help_cmdline("[OPTIONS…] COMMAND …"); + help_abstract("Send control commands to the network name resolution manager, or\n" + "resolve domain names, IPv4 and IPv6 addresses, DNS records, and services."); + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("resolvectl", "1"); return 0; } -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return native_help(); -} +VERB_COMMON_HELP_HIDDEN(native_help); -static int compat_parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_LEGEND, - ARG_SERVICE, - ARG_CNAME, - ARG_SERVICE_ADDRESS, - ARG_SERVICE_TXT, - ARG_OPENPGP, - ARG_TLSA, - ARG_RAW, - ARG_SEARCH, - ARG_STATISTICS, - ARG_RESET_STATISTICS, - ARG_STATUS, - ARG_FLUSH_CACHES, - ARG_RESET_SERVER_FEATURES, - ARG_NO_PAGER, - ARG_SET_DNS, - ARG_SET_DOMAIN, - ARG_SET_LLMNR, - ARG_SET_MDNS, - ARG_SET_DNS_OVER_TLS, - ARG_SET_DNSSEC, - ARG_SET_NTA, - ARG_REVERT_LINK, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "type", required_argument, NULL, 't' }, - { "class", required_argument, NULL, 'c' }, - { "legend", required_argument, NULL, ARG_LEGEND }, - { "interface", required_argument, NULL, 'i' }, - { "protocol", required_argument, NULL, 'p' }, - { "cname", required_argument, NULL, ARG_CNAME }, - { "service", no_argument, NULL, ARG_SERVICE }, - { "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS }, - { "service-txt", required_argument, NULL, ARG_SERVICE_TXT }, - { "openpgp", no_argument, NULL, ARG_OPENPGP }, - { "tlsa", optional_argument, NULL, ARG_TLSA }, - { "raw", optional_argument, NULL, ARG_RAW }, - { "search", required_argument, NULL, ARG_SEARCH }, - { "statistics", no_argument, NULL, ARG_STATISTICS, }, - { "reset-statistics", no_argument, NULL, ARG_RESET_STATISTICS }, - { "status", no_argument, NULL, ARG_STATUS }, - { "flush-caches", no_argument, NULL, ARG_FLUSH_CACHES }, - { "reset-server-features", no_argument, NULL, ARG_RESET_SERVER_FEATURES }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "set-dns", required_argument, NULL, ARG_SET_DNS }, - { "set-domain", required_argument, NULL, ARG_SET_DOMAIN }, - { "set-llmnr", required_argument, NULL, ARG_SET_LLMNR }, - { "set-mdns", required_argument, NULL, ARG_SET_MDNS }, - { "set-dnsovertls", required_argument, NULL, ARG_SET_DNS_OVER_TLS }, - { "set-dnssec", required_argument, NULL, ARG_SET_DNSSEC }, - { "set-nta", required_argument, NULL, ARG_SET_NTA }, - { "revert", no_argument, NULL, ARG_REVERT_LINK }, - {} - }; - - int c, r; +static int compat_parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); - while ((c = getopt_long(argc, argv, "h46i:t:c:p:", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "systemd-resolve" }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + OPTION_NAMESPACE("systemd-resolve"): {} + + OPTION_COMMON_HELP: return compat_help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case '4': + OPTION_SHORT('4', NULL, "Resolve IPv4 addresses"): arg_family = AF_INET; break; - case '6': + OPTION_SHORT('6', NULL, "Resolve IPv6 addresses"): arg_family = AF_INET6; break; - case 'i': - r = ifname_mangle(optarg); + OPTION('i', "interface", "INTERFACE", "Look on interface"): + r = ifname_mangle(opts.arg); if (r < 0) return r; break; - case 'p': - r = parse_protocol(optarg); + OPTION('p', "protocol", "PROTO|help", "Look via protocol"): + r = parse_protocol(opts.arg); if (r <= 0) return r; break; - case 't': - if (streq(optarg, "help")) { + OPTION('t', "type", "TYPE|help", "Query RR with DNS type"): + if (streq(opts.arg, "help")) { help_dns_types(); return 0; } - r = dns_type_from_string(optarg); + r = dns_type_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse RR record type %s: %m", optarg); + return log_error_errno(r, "Failed to parse RR record type %s: %m", opts.arg); arg_type = (uint16_t) r; assert((int) arg_type == r); @@ -3439,167 +3350,162 @@ static int compat_parse_argv(int argc, char *argv[]) { arg_mode = MODE_RESOLVE_RECORD; break; - case 'c': - if (streq(optarg, "help")) { + OPTION('c', "class", "CLASS|help", "Query RR with DNS class"): + if (streq(opts.arg, "help")) { help_dns_classes(); return 0; } - r = dns_class_from_string(optarg); + r = dns_class_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse RR record class %s: %m", optarg); + return log_error_errno(r, "Failed to parse RR record class %s: %m", opts.arg); arg_class = (uint16_t) r; assert((int) arg_class == r); break; - case ARG_SERVICE: + OPTION_LONG("service", NULL, "Resolve service (SRV)"): arg_mode = MODE_RESOLVE_SERVICE; break; - case ARG_SERVICE_ADDRESS: - r = parse_boolean_argument("--service-address=", optarg, NULL); + OPTION_LONG("service-address", "BOOL", "Resolve address for services (default: yes)"): + r = parse_boolean_argument("--service-address=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_ADDRESS, r == 0); break; - case ARG_SERVICE_TXT: - r = parse_boolean_argument("--service-txt=", optarg, NULL); + OPTION_LONG("service-txt", "BOOL", "Resolve TXT records for services (default: yes)"): + r = parse_boolean_argument("--service-txt=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_TXT, r == 0); break; - case ARG_OPENPGP: + OPTION_LONG("openpgp", NULL, "Query OpenPGP public key"): arg_mode = MODE_RESOLVE_OPENPGP; break; - case ARG_TLSA: + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "tlsa", "FAMILY", "Query TLS public key"): arg_mode = MODE_RESOLVE_TLSA; - if (!optarg || service_family_is_valid(optarg)) - arg_service_family = optarg; + if (!opts.arg || service_family_is_valid(opts.arg)) + arg_service_family = opts.arg; else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown service family \"%s\".", optarg); + "Unknown service family \"%s\".", opts.arg); break; - case ARG_CNAME: - r = parse_boolean_argument("--cname=", optarg, NULL); + OPTION_LONG("cname", "BOOL", "Follow CNAME redirects (default: yes)"): + r = parse_boolean_argument("--cname=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_CNAME, r == 0); break; - - case ARG_SEARCH: - r = parse_boolean_argument("--search=", optarg, NULL); + OPTION_LONG("search", "BOOL", "Use search domains for single-label names (default: yes)"): + r = parse_boolean_argument("--search=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_SEARCH, r == 0); break; - case ARG_STATISTICS: + OPTION_LONG("statistics", NULL, "Show resolver statistics"): arg_mode = MODE_STATISTICS; break; - case ARG_RESET_STATISTICS: + OPTION_LONG("reset-statistics", NULL, "Reset resolver statistics"): arg_mode = MODE_RESET_STATISTICS; break; - case ARG_STATUS: + OPTION_LONG("status", NULL, "Show link and server status"): arg_mode = MODE_STATUS; break; - case ARG_FLUSH_CACHES: + OPTION_LONG("flush-caches", NULL, "Flush all local DNS caches"): arg_mode = MODE_FLUSH_CACHES; break; - case ARG_RESET_SERVER_FEATURES: + OPTION_LONG("reset-server-features", NULL, + "Forget learnt DNS server feature levels"): arg_mode = MODE_RESET_SERVER_FEATURES; break; - case ARG_SET_DNS: - r = strv_extend(&arg_set_dns, optarg); + OPTION_LONG("set-dns", "SERVER", "Set per-interface DNS server address"): + r = strv_extend(&arg_set_dns, opts.arg); if (r < 0) return log_oom(); arg_mode = MODE_SET_LINK; break; - case ARG_SET_DOMAIN: - r = strv_extend(&arg_set_domain, optarg); + OPTION_LONG("set-domain", "DOMAIN", "Set per-interface search domain"): + r = strv_extend(&arg_set_domain, opts.arg); if (r < 0) return log_oom(); arg_mode = MODE_SET_LINK; break; - case ARG_SET_LLMNR: - arg_set_llmnr = optarg; + OPTION_LONG("set-llmnr", "MODE", "Set per-interface LLMNR mode"): + arg_set_llmnr = opts.arg; arg_mode = MODE_SET_LINK; break; - case ARG_SET_MDNS: - arg_set_mdns = optarg; + OPTION_LONG("set-mdns", "MODE", "Set per-interface MulticastDNS mode"): + arg_set_mdns = opts.arg; arg_mode = MODE_SET_LINK; break; - case ARG_SET_DNS_OVER_TLS: - arg_set_dns_over_tls = optarg; + OPTION_LONG("set-dnsovertls", "MODE", "Set per-interface DNS-over-TLS mode"): + arg_set_dns_over_tls = opts.arg; arg_mode = MODE_SET_LINK; break; - case ARG_SET_DNSSEC: - arg_set_dnssec = optarg; + OPTION_LONG("set-dnssec", "MODE", "Set per-interface DNSSEC mode"): + arg_set_dnssec = opts.arg; arg_mode = MODE_SET_LINK; break; - case ARG_SET_NTA: - r = strv_extend(&arg_set_nta, optarg); + OPTION_LONG("set-nta", "DOMAIN", "Set per-interface DNSSEC NTA"): + r = strv_extend(&arg_set_nta, opts.arg); if (r < 0) return log_oom(); arg_mode = MODE_SET_LINK; break; - case ARG_REVERT_LINK: + OPTION_LONG("revert", NULL, "Revert per-interface configuration"): arg_mode = MODE_REVERT_LINK; break; - case ARG_RAW: + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "raw", "payload|packet", + "Dump the answer as binary data"): if (on_tty()) return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Refusing to write binary data to tty."); - if (optarg == NULL || streq(optarg, "payload")) + if (opts.arg == NULL || streq(opts.arg, "payload")) arg_raw = RAW_PAYLOAD; - else if (streq(optarg, "packet")) + else if (streq(opts.arg, "packet")) arg_raw = RAW_PACKET; else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown --raw specifier \"%s\".", - optarg); + opts.arg); arg_legend = false; break; - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_LEGEND: - r = parse_boolean_argument("--legend=", optarg, &arg_legend); + OPTION_LONG("legend", "BOOL", "Print headers and additional info (default: yes)"): + r = parse_boolean_argument("--legend=", opts.arg, &arg_legend); if (r < 0) return r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_type == 0 && arg_class != 0) @@ -3623,253 +3529,209 @@ static int compat_parse_argv(int argc, char *argv[]) { "--set-dns=, --set-domain=, --set-llmnr=, --set-mdns=, --set-dnsovertls=, --set-dnssec=, --set-nta= and --revert require --interface=."); } + *remaining_args = option_parser_get_args(&opts); return 1 /* work to do */; } -static int native_parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_LEGEND, - ARG_CNAME, - ARG_VALIDATE, - ARG_SYNTHESIZE, - ARG_CACHE, - ARG_ZONE, - ARG_TRUST_ANCHOR, - ARG_NETWORK, - ARG_SERVICE_ADDRESS, - ARG_SERVICE_TXT, - ARG_RAW, - ARG_SEARCH, - ARG_NO_PAGER, - ARG_NO_ASK_PASSWORD, - ARG_JSON, - ARG_STALE_DATA, - ARG_RELAX_SINGLE_LABEL, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "type", required_argument, NULL, 't' }, - { "class", required_argument, NULL, 'c' }, - { "legend", required_argument, NULL, ARG_LEGEND }, - { "interface", required_argument, NULL, 'i' }, - { "protocol", required_argument, NULL, 'p' }, - { "cname", required_argument, NULL, ARG_CNAME }, - { "validate", required_argument, NULL, ARG_VALIDATE }, - { "synthesize", required_argument, NULL, ARG_SYNTHESIZE }, - { "cache", required_argument, NULL, ARG_CACHE }, - { "zone", required_argument, NULL, ARG_ZONE }, - { "trust-anchor", required_argument, NULL, ARG_TRUST_ANCHOR }, - { "network", required_argument, NULL, ARG_NETWORK }, - { "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS }, - { "service-txt", required_argument, NULL, ARG_SERVICE_TXT }, - { "raw", optional_argument, NULL, ARG_RAW }, - { "search", required_argument, NULL, ARG_SEARCH }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "json", required_argument, NULL, ARG_JSON }, - { "stale-data", required_argument, NULL, ARG_STALE_DATA }, - { "relax-single-label", required_argument, NULL, ARG_RELAX_SINGLE_LABEL }, - {} - }; - - int c, r; +static int native_parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); - while ((c = getopt_long(argc, argv, "h46i:t:c:p:j", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "resolvectl" }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + OPTION_NAMESPACE("resolvectl"): {} + + OPTION_COMMON_HELP: return native_help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case '4': + OPTION_SHORT('4', NULL, "Resolve IPv4 addresses"): arg_family = AF_INET; break; - case '6': + OPTION_SHORT('6', NULL, "Resolve IPv6 addresses"): arg_family = AF_INET6; break; - case 'i': - r = ifname_mangle(optarg); + OPTION('i', "interface", "INTERFACE", "Look on interface"): + r = ifname_mangle(opts.arg); if (r < 0) return r; break; - case 'p': - r = parse_protocol(optarg); + OPTION('p', "protocol", "PROTO|help", "Look via protocol"): + r = parse_protocol(opts.arg); if (r <= 0) return r; break; - case 't': - if (streq(optarg, "help")) { + OPTION('t', "type", "TYPE|help", "Query RR with DNS type"): + if (streq(opts.arg, "help")) { help_dns_types(); return 0; } - r = dns_type_from_string(optarg); + r = dns_type_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse RR record type %s: %m", optarg); + return log_error_errno(r, "Failed to parse RR record type %s: %m", opts.arg); arg_type = (uint16_t) r; assert((int) arg_type == r); break; - case 'c': - if (streq(optarg, "help")) { + OPTION('c', "class", "CLASS|help", "Query RR with DNS class"): + if (streq(opts.arg, "help")) { help_dns_classes(); return 0; } - r = dns_class_from_string(optarg); + r = dns_class_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse RR record class %s: %m", optarg); + return log_error_errno(r, "Failed to parse RR record class %s: %m", opts.arg); arg_class = (uint16_t) r; assert((int) arg_class == r); break; - case ARG_SERVICE_ADDRESS: - r = parse_boolean_argument("--service-address=", optarg, NULL); + OPTION_LONG("service-address", "BOOL", "Resolve address for services (default: yes)"): + r = parse_boolean_argument("--service-address=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_ADDRESS, r == 0); break; - case ARG_SERVICE_TXT: - r = parse_boolean_argument("--service-txt=", optarg, NULL); + OPTION_LONG("service-txt", "BOOL", "Resolve TXT records for services (default: yes)"): + r = parse_boolean_argument("--service-txt=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_TXT, r == 0); break; - case ARG_CNAME: - r = parse_boolean_argument("--cname=", optarg, NULL); + OPTION_LONG("cname", "BOOL", "Follow CNAME redirects (default: yes)"): + r = parse_boolean_argument("--cname=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_CNAME, r == 0); break; - case ARG_VALIDATE: - r = parse_boolean_argument("--validate=", optarg, NULL); + OPTION_LONG("validate", "BOOL", "Allow DNSSEC validation (default: yes)"): + r = parse_boolean_argument("--validate=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_VALIDATE, r == 0); break; - case ARG_SYNTHESIZE: - r = parse_boolean_argument("--synthesize=", optarg, NULL); + OPTION_LONG("synthesize", "BOOL", "Allow synthetic response (default: yes)"): + r = parse_boolean_argument("--synthesize=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_SYNTHESIZE, r == 0); break; - case ARG_CACHE: - r = parse_boolean_argument("--cache=", optarg, NULL); + OPTION_LONG("cache", "BOOL", "Allow response from cache (default: yes)"): + r = parse_boolean_argument("--cache=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_CACHE, r == 0); break; - case ARG_STALE_DATA: - r = parse_boolean_argument("--stale-data=", optarg, NULL); + OPTION_LONG("stale-data", "BOOL", + "Allow response from cache with stale data (default: yes)"): + r = parse_boolean_argument("--stale-data=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_STALE, r == 0); break; - case ARG_RELAX_SINGLE_LABEL: - r = parse_boolean_argument("--relax-single-label=", optarg, NULL); + OPTION_LONG("relax-single-label", "BOOL", + "Allow single label lookups to go upstream (default: no)"): + r = parse_boolean_argument("--relax-single-label=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_RELAX_SINGLE_LABEL, r > 0); break; - case ARG_ZONE: - r = parse_boolean_argument("--zone=", optarg, NULL); + OPTION_LONG("zone", "BOOL", + "Allow response from locally registered mDNS/LLMNR records (default: yes)"): + r = parse_boolean_argument("--zone=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_ZONE, r == 0); break; - case ARG_TRUST_ANCHOR: - r = parse_boolean_argument("--trust-anchor=", optarg, NULL); + OPTION_LONG("trust-anchor", "BOOL", + "Allow response from local trust anchor (default: yes)"): + r = parse_boolean_argument("--trust-anchor=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_TRUST_ANCHOR, r == 0); break; - case ARG_NETWORK: - r = parse_boolean_argument("--network=", optarg, NULL); + OPTION_LONG("network", "BOOL", "Allow response from network (default: yes)"): + r = parse_boolean_argument("--network=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_NETWORK, r == 0); break; - case ARG_SEARCH: - r = parse_boolean_argument("--search=", optarg, NULL); + OPTION_LONG("search", "BOOL", "Use search domains for single-label names (default: yes)"): + r = parse_boolean_argument("--search=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_SEARCH, r == 0); break; - case ARG_RAW: + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "raw", "payload|packet", + "Dump the answer as binary data"): if (on_tty()) return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Refusing to write binary data to tty."); - if (optarg == NULL || streq(optarg, "payload")) + if (opts.arg == NULL || streq(opts.arg, "payload")) arg_raw = RAW_PAYLOAD; - else if (streq(optarg, "packet")) + else if (streq(opts.arg, "packet")) arg_raw = RAW_PACKET; else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown --raw specifier \"%s\".", - optarg); + opts.arg); arg_legend = false; break; - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_NO_ASK_PASSWORD: + OPTION_COMMON_NO_ASK_PASSWORD: arg_ask_password = false; break; - case ARG_LEGEND: - r = parse_boolean_argument("--legend=", optarg, &arg_legend); + OPTION_LONG("legend", "BOOL", "Print headers and additional info (default: yes)"): + r = parse_boolean_argument("--legend=", opts.arg, &arg_legend); if (r < 0) return r; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; - break; - case 'j': + OPTION_COMMON_LOWERCASE_J: arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_type == 0 && arg_class != 0) @@ -3882,140 +3744,107 @@ static int native_parse_argv(int argc, char *argv[]) { if (arg_class != 0 && arg_type == 0) arg_type = DNS_TYPE_A; + *remaining_args = option_parser_get_args(&opts); return 1 /* work to do */; } -static int native_main(int argc, char *argv[]) { - - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "status", VERB_ANY, VERB_ANY, VERB_DEFAULT, verb_status }, - { "query", 2, VERB_ANY, 0, verb_query }, - { "service", 2, 4, 0, verb_service }, - { "openpgp", 2, VERB_ANY, 0, verb_openpgp }, - { "tlsa", 2, VERB_ANY, 0, verb_tlsa }, - { "statistics", VERB_ANY, 1, 0, verb_show_statistics }, - { "reset-statistics", VERB_ANY, 1, 0, verb_reset_statistics }, - { "flush-caches", VERB_ANY, 1, 0, verb_flush_caches }, - { "reset-server-features", VERB_ANY, 1, 0, verb_reset_server_features }, - { "dns", VERB_ANY, VERB_ANY, 0, verb_dns }, - { "domain", VERB_ANY, VERB_ANY, 0, verb_domain }, - { "default-route", VERB_ANY, 3, 0, verb_default_route }, - { "llmnr", VERB_ANY, 3, 0, verb_llmnr }, - { "mdns", VERB_ANY, 3, 0, verb_mdns }, - { "dnsovertls", VERB_ANY, 3, 0, verb_dns_over_tls }, - { "dnssec", VERB_ANY, 3, 0, verb_dnssec }, - { "nta", VERB_ANY, VERB_ANY, 0, verb_nta }, - { "revert", VERB_ANY, 2, 0, verb_revert_link }, - { "log-level", VERB_ANY, 2, 0, verb_log_level }, - { "monitor", VERB_ANY, 1, 0, verb_monitor }, - { "show-cache", VERB_ANY, 1, 0, verb_show_cache }, - { "show-server-state", VERB_ANY, 1, 0, verb_show_server_state }, - {} - }; - - return dispatch_verb(argc, argv, verbs, /* userdata= */ NULL); -} - -static int translate(const char *verb, const char *single_arg, size_t num_args, char **args) { +static int translate(const char *verb, const char *single_arg, char **args) { char **fake, **p; size_t num; assert(verb); - assert(num_args == 0 || args); - num = !!single_arg + num_args + 1; + num = !!single_arg + strv_length(args) + 1; p = fake = newa0(char *, num + 1); *p++ = (char *) verb; if (single_arg) *p++ = (char *) single_arg; - FOREACH_ARRAY(arg, args, num_args) - *p++ = *arg; + STRV_FOREACH(a, args) + *p++ = *a; - optind = 0; - return native_main((int) num, fake); + return dispatch_verb_with_args(fake, /* userdata= */ NULL); } -static int compat_main(int argc, char *argv[]) { +static int compat_main(char **args) { int r = 0; switch (arg_mode) { case MODE_RESOLVE_HOST: case MODE_RESOLVE_RECORD: - return translate("query", NULL, argc - optind, argv + optind); + return translate("query", NULL, args); case MODE_RESOLVE_SERVICE: - return translate("service", NULL, argc - optind, argv + optind); + return translate("service", NULL, args); case MODE_RESOLVE_OPENPGP: - return translate("openpgp", NULL, argc - optind, argv + optind); + return translate("openpgp", NULL, args); case MODE_RESOLVE_TLSA: - return translate("tlsa", arg_service_family, argc - optind, argv + optind); + return translate("tlsa", arg_service_family, args); case MODE_STATISTICS: - return translate("statistics", NULL, 0, NULL); + return translate("statistics", NULL, NULL); case MODE_RESET_STATISTICS: - return translate("reset-statistics", NULL, 0, NULL); + return translate("reset-statistics", NULL, NULL); case MODE_FLUSH_CACHES: - return translate("flush-caches", NULL, 0, NULL); + return translate("flush-caches", NULL, NULL); case MODE_RESET_SERVER_FEATURES: - return translate("reset-server-features", NULL, 0, NULL); + return translate("reset-server-features", NULL, NULL); case MODE_STATUS: - return translate("status", NULL, argc - optind, argv + optind); + return translate("status", NULL, args); case MODE_SET_LINK: assert(arg_ifname); if (arg_disable_default_route) { - r = translate("default-route", arg_ifname, 1, STRV_MAKE("no")); + r = translate("default-route", arg_ifname, STRV_MAKE("no")); if (r < 0) return r; } if (arg_set_dns) { - r = translate("dns", arg_ifname, strv_length(arg_set_dns), arg_set_dns); + r = translate("dns", arg_ifname, arg_set_dns); if (r < 0) return r; } if (arg_set_domain) { - r = translate("domain", arg_ifname, strv_length(arg_set_domain), arg_set_domain); + r = translate("domain", arg_ifname, arg_set_domain); if (r < 0) return r; } if (arg_set_nta) { - r = translate("nta", arg_ifname, strv_length(arg_set_nta), arg_set_nta); + r = translate("nta", arg_ifname, arg_set_nta); if (r < 0) return r; } if (arg_set_llmnr) { - r = translate("llmnr", arg_ifname, 1, (char **) &arg_set_llmnr); + r = translate("llmnr", arg_ifname, STRV_MAKE(arg_set_llmnr)); if (r < 0) return r; } if (arg_set_mdns) { - r = translate("mdns", arg_ifname, 1, (char **) &arg_set_mdns); + r = translate("mdns", arg_ifname, STRV_MAKE(arg_set_mdns)); if (r < 0) return r; } if (arg_set_dns_over_tls) { - r = translate("dnsovertls", arg_ifname, 1, (char **) &arg_set_dns_over_tls); + r = translate("dnsovertls", arg_ifname, STRV_MAKE(arg_set_dns_over_tls)); if (r < 0) return r; } if (arg_set_dnssec) { - r = translate("dnssec", arg_ifname, 1, (char **) &arg_set_dnssec); + r = translate("dnssec", arg_ifname, STRV_MAKE(arg_set_dnssec)); if (r < 0) return r; } @@ -4025,7 +3854,7 @@ static int compat_main(int argc, char *argv[]) { case MODE_REVERT_LINK: assert(arg_ifname); - return translate("revert", arg_ifname, 0, NULL); + return translate("revert", arg_ifname, NULL); case _MODE_INVALID: assert_not_reached(); @@ -4035,6 +3864,7 @@ static int compat_main(int argc, char *argv[]) { } static int run(int argc, char **argv) { + char **args = NULL; bool compat = false; int r; @@ -4046,16 +3876,16 @@ static int run(int argc, char **argv) { r = resolvconf_parse_argv(argc, argv); } else if (invoked_as(argv, "systemd-resolve")) { compat = true; - r = compat_parse_argv(argc, argv); + r = compat_parse_argv(argc, argv, &args); } else - r = native_parse_argv(argc, argv); + r = native_parse_argv(argc, argv, &args); if (r <= 0) return r; if (compat) - return compat_main(argc, argv); + return compat_main(args); - return native_main(argc, argv); + return dispatch_verb_with_args(args, /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION(run); From 366071143d60987f44d6173730ffc3fb875fd804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 7 May 2026 13:54:12 +0200 Subject: [PATCH 6/6] resolvconf-compat: convert to OPTION macros Use the "resolvconf" namespace to keep these options separate from the resolvectl/systemd-resolve sets. Co-developed-by: Claude Opus 4.7 --- src/resolve/resolvconf-compat.c | 129 +++++++++++++------------------- 1 file changed, 52 insertions(+), 77 deletions(-) diff --git a/src/resolve/resolvconf-compat.c b/src/resolve/resolvconf-compat.c index d8e68d524a..d3ddec755a 100644 --- a/src/resolve/resolvconf-compat.c +++ b/src/resolve/resolvconf-compat.c @@ -1,14 +1,15 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" #include "build.h" #include "extract-word.h" #include "fileio.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" -#include "pretty-print.h" +#include "options.h" #include "resolvconf-compat.h" #include "resolvectl.h" #include "string-util.h" @@ -21,36 +22,32 @@ typedef enum LookupType { } LookupType; static int resolvconf_help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("resolvectl", "1", &link); + r = option_parser_get_help_table_ns("resolvconf", &options); if (r < 0) - return log_oom(); + return r; - printf("%1$s -a INTERFACE < FILE\n" - "%1$s -d INTERFACE\n" - "\n" - "Register DNS server and domain configuration with systemd-resolved.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -a Register per-interface DNS server and domain data\n" - " -d Unregister per-interface DNS server and domain data\n" - " -p Do not use this interface as default route\n" - " -f Ignore if specified interface does not exist\n" - " -x Send DNS traffic preferably over this interface\n" - "\n" + help_cmdline("-a INTERFACE = 0); assert(argv); @@ -204,89 +182,86 @@ int resolvconf_parse_argv(int argc, char *argv[]) { arg_mode = _MODE_INVALID; - while ((c = getopt_long(argc, argv, "hadxpfm:uIi:l:Rr:vV", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "resolvconf" }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + OPTION_NAMESPACE("resolvconf"): {} + + OPTION_COMMON_HELP: return resolvconf_help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); /* -a and -d is what everybody can agree on */ - case 'a': + OPTION_SHORT('a', NULL, "Register per-interface DNS server and domain data"): arg_mode = MODE_SET_LINK; break; - case 'd': + OPTION_SHORT('d', NULL, "Unregister per-interface DNS server and domain data"): arg_mode = MODE_REVERT_LINK; break; - /* The exclusive/private/force stuff is an openresolv invention, we support in some skewed way */ - case 'x': - lookup_type = LOOKUP_TYPE_EXCLUSIVE; - break; - - case 'p': + OPTION_SHORT('p', NULL, "Do not use this interface as default route"): lookup_type = LOOKUP_TYPE_PRIVATE; break; - case 'f': + OPTION_SHORT('f', NULL, "Ignore if specified interface does not exist"): arg_ifindex_permissive = true; break; + /* The exclusive/private/force stuff is an openresolv invention, we support in some skewed way */ + OPTION_SHORT('x', NULL, "Send DNS traffic preferably over this interface"): + lookup_type = LOOKUP_TYPE_EXCLUSIVE; + break; + /* The metrics stuff is an openresolv invention we ignore (and don't really need) */ - case 'm': - log_debug("Switch -%c ignored.", c); + OPTION_SHORT('m', "ARG", /* help= */ NULL): + log_debug("Switch -%c ignored.", opts.opt->short_code); break; /* -u supposedly should "update all subscribers". We have no subscribers, hence let's make this a NOP, and exit immediately, cleanly. */ - case 'u': - log_info("Switch -%c ignored.", c); + OPTION_SHORT('u', NULL, /* help= */ NULL): + log_info("Switch -%c ignored.", opts.opt->short_code); return 0; /* The following options are openresolv inventions we don't support. */ - case 'I': - case 'i': - case 'l': - case 'R': - case 'r': - case 'v': - case 'V': + OPTION_SHORT('I', NULL, /* help= */ NULL): {} + OPTION_SHORT('i', "ARG", /* help= */ NULL): {} + OPTION_SHORT('l', "ARG", /* help= */ NULL): {} + OPTION_SHORT('R', NULL, /* help= */ NULL): {} + OPTION_SHORT('r', "ARG", /* help= */ NULL): {} + OPTION_SHORT('v', NULL, /* help= */ NULL): {} + OPTION_SHORT('V', NULL, /* help= */ NULL): return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Switch -%c not supported.", c); + "Switch -%c not supported.", opts.opt->short_code); /* The Debian resolvconf commands we don't support. */ - case ARG_ENABLE_UPDATES: + OPTION_LONG("enable-updates", NULL, /* help= */ NULL): return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Switch --enable-updates not supported."); - case ARG_DISABLE_UPDATES: + OPTION_LONG("disable-updates", NULL, /* help= */ NULL): return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Switch --disable-updates not supported."); - case ARG_UPDATES_ARE_ENABLED: + OPTION_LONG("updates-are-enabled", NULL, /* help= */ NULL): return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Switch --updates-are-enabled not supported."); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_mode == _MODE_INVALID) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected either -a or -d on the command line."); - if (optind+1 != argc) + if (option_parser_get_n_args(&opts) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected interface name as argument."); - r = ifname_resolvconf_mangle(argv[optind]); + r = ifname_resolvconf_mangle(option_parser_get_arg(&opts, 0)); if (r <= 0) return r; - optind++; if (arg_mode == MODE_SET_LINK) { r = parse_stdin(lookup_type);