]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolvectl: convert to OPTION and VERB macros
authorZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Thu, 7 May 2026 07:57:43 +0000 (09:57 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Thu, 7 May 2026 16:39:32 +0000 (18:39 +0200)
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 <noreply@anthropic.com>
src/resolve/resolvectl.c

index 4088c39b1da600808be7a2efe421c77f12a61c77..015345fdba7d9e288059fd88b58f4465f88ad973 100644 (file)
@@ -1,6 +1,5 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
-#include <getopt.h>
 #include <locale.h>
 #include <net/if.h>
 
@@ -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"
 #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();
-}
-
-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           },
-                {}
-        };
+VERB_COMMON_HELP_HIDDEN(native_help);
 
-        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);
+
+        OptionParser opts = { argc, argv, .namespace = "systemd-resolve" };
 
-        while ((c = getopt_long(argc, argv, "h46i:t:c:p:", options, NULL)) >= 0)
+        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);
+
+        OptionParser opts = { argc, argv, .namespace = "resolvectl" };
 
-        while ((c = getopt_long(argc, argv, "h46i:t:c:p:j", options, NULL)) >= 0)
+        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);