]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
busctl: convert to the new option and verb parsers
authorZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Tue, 14 Apr 2026 10:56:01 +0000 (12:56 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Thu, 14 May 2026 12:15:54 +0000 (14:15 +0200)
The conversion doesn't work great, because some of the verbs take many
arguments and the first column is extermely wide. So similarly to
kernel-install, I dropped the sync of column widths. This allows the
help for options to use most of the available space.

-C/--capsule is now documented, fixup for
 00431b2b66cb59540deda4ea018170a289673585.

Verb functions are renamed to match verb names.

The missing first param is added to the synopsis of "wait".
It now matches the man page.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
src/busctl/busctl.c

index 1e6fb64e964645be523910018cd93e0db41e2d8c..79ee1c55e8ecd28b99179731c4c48e022ff573f9 100644 (file)
@@ -1,6 +1,5 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
-#include <getopt.h>
 #include <unistd.h>
 
 #include "sd-bus.h"
@@ -9,6 +8,7 @@
 #include "sd-json.h"
 
 #include "alloc-util.h"
+#include "ansi-color.h"
 #include "bitfield.h"
 #include "build.h"
 #include "bus-dump.h"
 #include "fileio.h"
 #include "format-table.h"
 #include "glyph-util.h"
+#include "help-util.h"
 #include "log.h"
 #include "logarithm.h"
 #include "main-func.h"
 #include "memstream-util.h"
+#include "options.h"
 #include "os-util.h"
 #include "pager.h"
 #include "parse-argument.h"
 #include "parse-util.h"
 #include "path-util.h"
-#include "pretty-print.h"
 #include "runtime-scope.h"
 #include "set.h"
 #include "string-util.h"
@@ -175,7 +176,9 @@ static void notify_bus_error(const sd_bus_error *error) {
         (void) sd_notifyf(/* unset_environment= */ false, "BUSERROR=%s", error->name);
 }
 
-static int verb_list_bus_names(int argc, char *argv[], uintptr_t _data, void *userdata) {
+VERB(verb_list, "list", NULL, VERB_ANY, 1, VERB_DEFAULT,
+     "List bus names");
+static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) {
         _cleanup_strv_free_ char **acquired = NULL, **activatable = NULL;
         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         _cleanup_hashmap_free_ Hashmap *names = NULL;
@@ -537,6 +540,8 @@ static int tree_one(sd_bus *bus, const char *service) {
         return r;
 }
 
+VERB(verb_tree, "tree", "[SERVICE…]", VERB_ANY, VERB_ANY, 0,
+     "Show object tree of service");
 static int verb_tree(int argc, char *argv[], uintptr_t _data, void *userdata) {
         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         int r;
@@ -996,6 +1001,8 @@ static int members_flags_to_string(const Member *m, char **ret) {
         return 0;
 }
 
+VERB(verb_introspect, "introspect", "SERVICE OBJECT [INTERFACE]", 3, 4, 0,
+     "Introspect an object");
 static int verb_introspect(int argc, char *argv[], uintptr_t _data, void *userdata) {
         static const XMLIntrospectOps ops = {
                 .on_interface = on_interface,
@@ -1377,10 +1384,14 @@ static int monitor(int argc, char **argv, int (*dump)(sd_bus_message *m, FILE *f
         }
 }
 
+VERB(verb_monitor, "monitor", "[SERVICE…]", VERB_ANY, VERB_ANY, 0,
+     "Show bus traffic");
 static int verb_monitor(int argc, char *argv[], uintptr_t _data, void *userdata) {
         return monitor(argc, argv, sd_json_format_enabled(arg_json_format_flags) ? message_json : message_dump);
 }
 
+VERB(verb_capture, "capture", "[SERVICE…]", VERB_ANY, VERB_ANY, 0,
+     "Capture bus traffic as pcap");
 static int verb_capture(int argc, char *argv[], uintptr_t _data, void *userdata) {
         _cleanup_free_ char *osname = NULL;
         static const char info[] =
@@ -1408,6 +1419,8 @@ static int verb_capture(int argc, char *argv[], uintptr_t _data, void *userdata)
         return r;
 }
 
+VERB(verb_status, "status", "[SERVICE]", VERB_ANY, 2, 0,
+     "Show bus service, process, or bus owner credentials");
 static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) {
         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
@@ -1778,6 +1791,8 @@ static int bus_message_dump(sd_bus_message *m, uint64_t flags) {
         return 0;
 }
 
+VERB(verb_call, "call", "SERVICE OBJECT INTERFACE METHOD [SIGNATURE [ARGUMENT…]]", 5, VERB_ANY, 0,
+     "Call a method");
 static int verb_call(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;
@@ -1845,7 +1860,9 @@ static int verb_call(int argc, char *argv[], uintptr_t _data, void *userdata) {
         return bus_message_dump(reply, /* flags= */ 0);
 }
 
-static int verb_emit_signal(int argc, char *argv[], uintptr_t _data, void *userdata) {
+VERB(verb_emit, "emit", "OBJECT INTERFACE SIGNAL [SIGNATURE [ARGUMENT…]]", 4, VERB_ANY, 0,
+     "Emit a signal");
+static int verb_emit(int argc, char *argv[], uintptr_t _data, void *userdata) {
         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
         _cleanup_fdset_free_ FDSet *passed_fdset = NULL;
@@ -1890,6 +1907,8 @@ static int verb_emit_signal(int argc, char *argv[], uintptr_t _data, void *userd
         return 0;
 }
 
+VERB(verb_get_property, "get-property", "SERVICE OBJECT INTERFACE PROPERTY…", 5, VERB_ANY, 0,
+     "Get property value");
 static int verb_get_property(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;
@@ -1948,7 +1967,9 @@ static int on_bus_signal(sd_bus_message *msg, void *userdata, sd_bus_error *ret_
         return 0;
 }
 
-static int verb_wait_signal(int argc, char *argv[], uintptr_t _data, void *userdata) {
+VERB(verb_wait, "wait", "[SERVICE] OBJECT INTERFACE SIGNAL", 4, 5, 0,
+     "Wait for a signal");
+static int verb_wait(int argc, char *argv[], uintptr_t _data, void *userdata) {
         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         _cleanup_(sd_event_unrefp) sd_event *e = NULL;
         _cleanup_(sd_event_source_unrefp) sd_event_source *timer = NULL;
@@ -1996,6 +2017,8 @@ static int verb_wait_signal(int argc, char *argv[], uintptr_t _data, void *userd
         return sd_event_loop(e);
 }
 
+VERB(verb_set_property, "set-property", "SERVICE OBJECT INTERFACE PROPERTY SIGNATURE ARGUMENT…", 6, VERB_ANY, 0,
+     "Set property value");
 static int verb_set_property(int argc, char *argv[], uintptr_t _data, void *userdata) {
         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
@@ -2052,235 +2075,131 @@ static int verb_set_property(int argc, char *argv[], uintptr_t _data, void *user
 }
 
 static int help(void) {
-        _cleanup_free_ char *link = NULL;
+        _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL;
         int r;
 
-        r = terminal_urlify_man("busctl", "1", &link);
+        pager_open(arg_pager_flags);
+
+        r = verbs_get_help_table(&verbs);
         if (r < 0)
-                return log_oom();
+                return r;
 
-        pager_open(arg_pager_flags);
+        r = option_parser_get_help_table(&options);
+        if (r < 0)
+                return r;
 
-        printf("%1$s [OPTIONS...] COMMAND ...\n\n"
-               "%5$sIntrospect the D-Bus IPC bus.%6$s\n"
-               "\n%3$sCommands%4$s:\n"
-               "  list                     List bus names\n"
-               "  status [SERVICE]         Show bus service, process or bus owner credentials\n"
-               "  monitor [SERVICE...]     Show bus traffic\n"
-               "  capture [SERVICE...]     Capture bus traffic as pcap\n"
-               "  tree [SERVICE...]        Show object tree of service\n"
-               "  introspect SERVICE OBJECT [INTERFACE]\n"
-               "  call SERVICE OBJECT INTERFACE METHOD [SIGNATURE [ARGUMENT...]]\n"
-               "                           Call a method\n"
-               "  emit OBJECT INTERFACE SIGNAL [SIGNATURE [ARGUMENT...]]\n"
-               "                           Emit a signal\n"
-               "  wait OBJECT INTERFACE SIGNAL\n"
-               "                           Wait for a signal\n"
-               "  get-property SERVICE OBJECT INTERFACE PROPERTY...\n"
-               "                           Get property value\n"
-               "  set-property SERVICE OBJECT INTERFACE PROPERTY SIGNATURE ARGUMENT...\n"
-               "                           Set property value\n"
-               "  help                     Show this help\n"
-               "\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-legend           Do not show the headers and footers\n"
-               "  -l --full                Do not ellipsize output\n"
-               "     --system              Connect to system bus\n"
-               "     --user                Connect to user bus\n"
-               "  -H --host=[USER@]HOST    Operate on remote host\n"
-               "  -M --machine=CONTAINER   Operate on local container\n"
-               "     --address=ADDRESS     Connect to bus specified by address\n"
-               "     --show-machine        Show machine ID column in list\n"
-               "     --unique              Only show unique names\n"
-               "     --acquired            Only show acquired names\n"
-               "     --activatable         Only show activatable names\n"
-               "     --match=MATCH         Only show matching messages\n"
-               "     --size=SIZE           Maximum length of captured packet\n"
-               "     --list                Don't show tree, but simple object path list\n"
-               "  -q --quiet               Don't show method call reply\n"
-               "     --verbose             Show result values in long format\n"
-               "     --json=MODE           Output as JSON\n"
-               "  -j                       Same as --json=pretty on tty, --json=short otherwise\n"
-               "     --xml-interface       Dump the XML description in introspect command\n"
-               "     --expect-reply=BOOL   Expect a method call reply\n"
-               "     --auto-start=BOOL     Auto-start destination service\n"
-               "     --allow-interactive-authorization=BOOL\n"
-               "                           Allow interactive authorization for operation\n"
-               "     --timeout=SECS        Maximum time to wait for method call completion\n"
-               "     --augment-creds=BOOL  Extend credential data with data read from /proc/$PID\n"
-               "     --watch-bind=BOOL     Wait for bus AF_UNIX socket to be bound in the file\n"
-               "                           system\n"
-               "     --destination=SERVICE Destination service of a signal\n"
-               "  -N --limit-messages=NUMBER\n"
-               "                           Stop monitoring after receiving the specified number\n"
-               "                           of messages\n"
-               "\nSee the %2$s for details.\n",
-               program_invocation_short_name,
-               link,
-               ansi_underline(),
-               ansi_normal(),
-               ansi_highlight(),
-               ansi_normal());
+        /* Note: column widths are not synced, because the verbs table is very wide. */
 
-        return 0;
-}
+        help_cmdline("[OPTIONS…] COMMAND …");
+        help_abstract("Introspect the D-Bus IPC bus.");
 
-static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        return help();
-}
+        help_section("Commands");
+        r = table_print_or_warn(verbs);
+        if (r < 0)
+                return r;
 
-static int parse_argv(int argc, char *argv[]) {
+        help_section("Options");
+        r = table_print_or_warn(options);
+        if (r < 0)
+                return r;
 
-        enum {
-                ARG_VERSION = 0x100,
-                ARG_NO_PAGER,
-                ARG_NO_LEGEND,
-                ARG_SYSTEM,
-                ARG_USER,
-                ARG_ADDRESS,
-                ARG_MATCH,
-                ARG_SHOW_MACHINE,
-                ARG_UNIQUE,
-                ARG_ACQUIRED,
-                ARG_ACTIVATABLE,
-                ARG_SIZE,
-                ARG_LIST,
-                ARG_VERBOSE,
-                ARG_XML_INTERFACE,
-                ARG_EXPECT_REPLY,
-                ARG_AUTO_START,
-                ARG_ALLOW_INTERACTIVE_AUTHORIZATION,
-                ARG_TIMEOUT,
-                ARG_AUGMENT_CREDS,
-                ARG_WATCH_BIND,
-                ARG_JSON,
-                ARG_DESTINATION,
-        };
+        help_man_page_reference("busctl", "1");
 
-        static const struct option options[] = {
-                { "help",                            no_argument,       NULL, 'h'                                 },
-                { "version",                         no_argument,       NULL, ARG_VERSION                         },
-                { "no-pager",                        no_argument,       NULL, ARG_NO_PAGER                        },
-                { "no-legend",                       no_argument,       NULL, ARG_NO_LEGEND                       },
-                { "full",                            no_argument,       NULL, 'l'                                 },
-                { "system",                          no_argument,       NULL, ARG_SYSTEM                          },
-                { "user",                            no_argument,       NULL, ARG_USER                            },
-                { "address",                         required_argument, NULL, ARG_ADDRESS                         },
-                { "show-machine",                    no_argument,       NULL, ARG_SHOW_MACHINE                    },
-                { "unique",                          no_argument,       NULL, ARG_UNIQUE                          },
-                { "acquired",                        no_argument,       NULL, ARG_ACQUIRED                        },
-                { "activatable",                     no_argument,       NULL, ARG_ACTIVATABLE                     },
-                { "match",                           required_argument, NULL, ARG_MATCH                           },
-                { "host",                            required_argument, NULL, 'H'                                 },
-                { "machine",                         required_argument, NULL, 'M'                                 },
-                { "capsule",                         required_argument, NULL, 'C'                                 },
-                { "size",                            required_argument, NULL, ARG_SIZE                            },
-                { "list",                            no_argument,       NULL, ARG_LIST                            },
-                { "quiet",                           no_argument,       NULL, 'q'                                 },
-                { "verbose",                         no_argument,       NULL, ARG_VERBOSE                         },
-                { "xml-interface",                   no_argument,       NULL, ARG_XML_INTERFACE                   },
-                { "expect-reply",                    required_argument, NULL, ARG_EXPECT_REPLY                    },
-                { "auto-start",                      required_argument, NULL, ARG_AUTO_START                      },
-                { "allow-interactive-authorization", required_argument, NULL, ARG_ALLOW_INTERACTIVE_AUTHORIZATION },
-                { "timeout",                         required_argument, NULL, ARG_TIMEOUT                         },
-                { "augment-creds",                   required_argument, NULL, ARG_AUGMENT_CREDS                   },
-                { "watch-bind",                      required_argument, NULL, ARG_WATCH_BIND                      },
-                { "json",                            required_argument, NULL, ARG_JSON                            },
-                { "destination",                     required_argument, NULL, ARG_DESTINATION                     },
-                { "limit-messages",                  required_argument, NULL, 'N'                                 },
-                {},
-        };
+        return 0;
+}
 
-        int c, r;
+VERB_COMMON_HELP(help);
 
+static int parse_argv(int argc, char *argv[], char ***remaining_args) {
         assert(argc >= 0);
         assert(argv);
+        assert(remaining_args);
 
-        while ((c = getopt_long(argc, argv, "hH:M:C:J:qjlN:", options, NULL)) >= 0)
+        OptionParser opts = { argc, argv };
+        int r;
 
+        FOREACH_OPTION_OR_RETURN(c, &opts)
                 switch (c) {
 
-                case 'h':
+                OPTION_COMMON_HELP:
                         return help();
 
-                case ARG_VERSION:
+                OPTION_COMMON_VERSION:
                         return version();
 
-                case ARG_NO_PAGER:
+                OPTION_COMMON_NO_PAGER:
                         arg_pager_flags |= PAGER_DISABLE;
                         break;
 
-                case ARG_NO_LEGEND:
+                OPTION_COMMON_NO_LEGEND:
                         arg_legend = false;
                         break;
 
-                case 'l':
+                OPTION('l', "full", NULL, "Do not ellipsize output"):
                         arg_full = true;
                         break;
 
-                case ARG_SYSTEM:
+                OPTION_LONG("system", NULL, "Connect to system bus"):
                         arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
                         break;
 
-                case ARG_USER:
+                OPTION_LONG("user", NULL, "Connect to user bus"):
                         arg_runtime_scope = RUNTIME_SCOPE_USER;
                         break;
 
-                case 'H':
+                OPTION_COMMON_HOST:
                         arg_transport = BUS_TRANSPORT_REMOTE;
-                        arg_host = optarg;
+                        arg_host = opts.arg;
                         break;
 
-                case 'M':
-                        r = parse_machine_argument(optarg, &arg_host, &arg_transport);
+                OPTION_COMMON_MACHINE:
+                        r = parse_machine_argument(opts.arg, &arg_host, &arg_transport);
                         if (r < 0)
                                 return r;
                         break;
 
-                case 'C':
-                        r = capsule_name_is_valid(optarg);
+                OPTION('C', "capsule", "NAME", "Operate on capsule"):
+                        r = capsule_name_is_valid(opts.arg);
                         if (r < 0)
-                                return log_error_errno(r, "Unable to validate capsule name '%s': %m", optarg);
+                                return log_error_errno(r, "Unable to validate capsule name '%s': %m", opts.arg);
                         if (r == 0)
-                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", optarg);
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", opts.arg);
 
-                        arg_host = optarg;
+                        arg_host = opts.arg;
                         arg_transport = BUS_TRANSPORT_CAPSULE;
                         break;
 
-                case ARG_ADDRESS:
-                        arg_address = optarg;
+                OPTION_LONG("address", "ADDRESS", "Connect to bus specified by address"):
+                        arg_address = opts.arg;
                         break;
 
-                case ARG_SHOW_MACHINE:
+                OPTION_LONG("show-machine", NULL, "Show machine ID column in list"):
                         arg_show_machine = true;
                         break;
 
-                case ARG_UNIQUE:
+                OPTION_LONG("unique", NULL, "Only show unique names"):
                         arg_unique = true;
                         break;
 
-                case ARG_ACQUIRED:
+                OPTION_LONG("acquired", NULL, "Only show acquired names"):
                         arg_acquired = true;
                         break;
 
-                case ARG_ACTIVATABLE:
+                OPTION_LONG("activatable", NULL, "Only show activatable names"):
                         arg_activatable = true;
                         break;
 
-                case ARG_MATCH:
-                        if (strv_extend(&arg_matches, optarg) < 0)
+                OPTION_LONG("match", "MATCH", "Only show matching messages"):
+                        if (strv_extend(&arg_matches, opts.arg) < 0)
                                 return log_oom();
                         break;
 
-                case ARG_SIZE: {
+                OPTION_LONG("size", "SIZE", "Maximum length of captured packet"): {
                         uint64_t sz;
 
-                        r = parse_size(optarg, 1024, &sz);
+                        r = parse_size(opts.arg, 1024, &sz);
                         if (r < 0)
-                                return log_error_errno(r, "Failed to parse size '%s': %m", optarg);
+                                return log_error_errno(r, "Failed to parse size '%s': %m", opts.arg);
 
                         if ((uint64_t) (size_t) sz !=  sz)
                                 return log_error_errno(SYNTHETIC_ERRNO(E2BIG),
@@ -2290,145 +2209,123 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
                 }
 
-                case ARG_LIST:
+                OPTION_LONG("list", NULL, "Don't show tree, but simple object path list"):
                         arg_list = true;
                         break;
 
-                case 'q':
+                OPTION('q', "quiet", NULL, "Don't show method call reply"):
                         arg_quiet = true;
                         break;
 
-                case ARG_VERBOSE:
+                OPTION_LONG("verbose", NULL, "Show result values in long format"):
                         arg_verbose = true;
                         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 ARG_XML_INTERFACE:
+                OPTION_LONG("xml-interface", NULL, "Dump the XML description in introspect command"):
                         arg_xml_interface = true;
                         break;
 
-                case ARG_EXPECT_REPLY:
-                        r = parse_boolean_argument("--expect-reply=", optarg, &arg_expect_reply);
+                OPTION_LONG("expect-reply", "BOOL", "Expect a method call reply"):
+                        r = parse_boolean_argument("--expect-reply=", opts.arg, &arg_expect_reply);
                         if (r < 0)
                                 return r;
                         break;
 
-                case ARG_AUTO_START:
-                        r = parse_boolean_argument("--auto-start=", optarg, &arg_auto_start);
+                OPTION_LONG("auto-start", "BOOL", "Auto-start destination service"):
+                        r = parse_boolean_argument("--auto-start=", opts.arg, &arg_auto_start);
                         if (r < 0)
                                 return r;
                         break;
 
-                case ARG_ALLOW_INTERACTIVE_AUTHORIZATION:
-                        r = parse_boolean_argument("--allow-interactive-authorization=", optarg,
+                OPTION_LONG("allow-interactive-authorization", "",
+                            "Allow interactive authorization for operation"):
+                        r = parse_boolean_argument("--allow-interactive-authorization=", opts.arg,
                                                    &arg_allow_interactive_authorization);
                         if (r < 0)
                                 return r;
                         break;
 
-                case ARG_TIMEOUT:
-                        if (isempty(optarg)) {
+                OPTION_LONG("timeout", "SECS", "Maximum time to wait for method call completion"):
+                        if (isempty(opts.arg)) {
                                 arg_timeout = 0; /* Reset to default */
                                 break;
                         }
 
-                        r = parse_sec(optarg, &arg_timeout);
+                        r = parse_sec(opts.arg, &arg_timeout);
                         if (r < 0)
-                                return log_error_errno(r, "Failed to parse --timeout= parameter '%s': %m", optarg);
-
+                                return log_error_errno(r, "Failed to parse --timeout= parameter '%s': %m", opts.arg);
                         break;
 
-                case ARG_AUGMENT_CREDS:
-                        r = parse_boolean_argument("--augment-creds=", optarg, &arg_augment_creds);
+                OPTION_LONG("augment-creds", "BOOL",
+                            "Extend credential data with data read from /proc/$PID"):
+                        r = parse_boolean_argument("--augment-creds=", opts.arg, &arg_augment_creds);
                         if (r < 0)
                                 return r;
                         break;
 
-                case ARG_WATCH_BIND:
-                        r = parse_boolean_argument("--watch-bind=", optarg, &arg_watch_bind);
+                OPTION_LONG("watch-bind", "BOOL",
+                            "Wait for bus AF_UNIX socket to be bound in the file system"):
+                        r = parse_boolean_argument("--watch-bind=", opts.arg, &arg_watch_bind);
                         if (r < 0)
                                 return r;
                         break;
 
-                case ARG_DESTINATION:
-                        arg_destination = optarg;
+                OPTION_LONG("destination", "SERVICE", "Destination service of a signal"):
+                        arg_destination = opts.arg;
                         break;
 
-                case 'N':
-                        if (isempty(optarg)) {
+                OPTION('N', "limit-messages", "NUMBER",
+                       "Stop monitoring after receiving the specified number of messages"):
+                        if (isempty(opts.arg)) {
                                 /* Reset to default */
                                 arg_limit_messages = UINT64_MAX;
                                 arg_limit_signals = 1;
                                 break;
                         }
 
-                        if (streq(optarg, "infinity")) {
+                        if (streq(opts.arg, "infinity")) {
                                 arg_limit_signals = arg_limit_messages = UINT64_MAX;
                                 break;
                         }
 
-                        r = safe_atou64(optarg, &arg_limit_messages);
+                        r = safe_atou64(opts.arg, &arg_limit_messages);
                         if (r < 0)
-                                return log_error_errno(r, "Failed to parse --limit-messages= parameter: %s", optarg);
+                                return log_error_errno(r, "Failed to parse --limit-messages= parameter: %s", opts.arg);
                         if (arg_limit_messages == 0)
                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--limit-messages= parameter cannot be 0");
 
                         arg_limit_signals = arg_limit_messages;
                         break;
-
-                case '?':
-                        return -EINVAL;
-
-                default:
-                        assert_not_reached();
                 }
 
         if (arg_full < 0)
                 arg_full = terminal_is_dumb();
 
+        *remaining_args = option_parser_get_args(&opts);
         return 1;
 }
 
-static int busctl_main(int argc, char *argv[]) {
-        static const Verb verbs[] = {
-                { "list",         VERB_ANY, 1,        VERB_DEFAULT, verb_list_bus_names },
-                { "status",       VERB_ANY, 2,        0,            verb_status         },
-                { "monitor",      VERB_ANY, VERB_ANY, 0,            verb_monitor        },
-                { "capture",      VERB_ANY, VERB_ANY, 0,            verb_capture        },
-                { "tree",         VERB_ANY, VERB_ANY, 0,            verb_tree           },
-                { "introspect",   3,        4,        0,            verb_introspect     },
-                { "call",         5,        VERB_ANY, 0,            verb_call           },
-                { "emit",         4,        VERB_ANY, 0,            verb_emit_signal    },
-                { "wait",         4,        5,        0,            verb_wait_signal    },
-                { "get-property", 5,        VERB_ANY, 0,            verb_get_property   },
-                { "set-property", 6,        VERB_ANY, 0,            verb_set_property   },
-                { "help",         VERB_ANY, VERB_ANY, 0,            verb_help           },
-                {}
-        };
-
-        return dispatch_verb(argc, argv, verbs, NULL);
-}
-
 static int run(int argc, char *argv[]) {
         int r;
 
         log_setup();
 
-        r = parse_argv(argc, argv);
+        char **args = NULL;
+        r = parse_argv(argc, argv, &args);
         if (r <= 0)
                 return r;
 
-        return busctl_main(argc, argv);
+        return dispatch_verb_with_args(args, NULL);
 }
 
 DEFINE_MAIN_FUNCTION(run);