]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
verbs: add a section to list verbs similarly to options
authorZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Fri, 27 Feb 2026 10:38:29 +0000 (11:38 +0100)
committerZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Fri, 20 Mar 2026 16:26:52 +0000 (17:26 +0100)
src/shared/verbs.c
src/shared/verbs.h
src/systemctl/systemctl-main.c

index 8c174a6c88de4079770ad62bf57daee55ecd3361..bf2d440bb0ec8c6a7cbe519fa041df8cbde173c2 100644 (file)
@@ -3,6 +3,7 @@
 #include <getopt.h>
 
 #include "env-util.h"
+#include "format-table.h"
 #include "log.h"
 #include "string-util.h"
 #include "strv.h"
@@ -57,33 +58,34 @@ bool should_bypass(const char *env_prefix) {
         return true;
 }
 
-const Verb* verbs_find_verb(const char *name, const Verb verbs[]) {
+const Verb* verbs_find_verb(const char *name, const Verb verbs[], const Verb verbs_end[]) {
         assert(verbs);
 
-        for (size_t i = 0; verbs[i].dispatch; i++)
-                if (name ? streq(name, verbs[i].verb) : FLAGS_SET(verbs[i].flags, VERB_DEFAULT))
-                        return verbs + i;
+        for (const Verb *verb = verbs; verb < verbs_end; verb++)
+                if (name ? streq(name, verb->verb) : FLAGS_SET(verb->flags, VERB_DEFAULT))
+                        return verb;
 
         /* At the end of the list? */
         return NULL;
 }
 
-int dispatch_verb_with_args(char **args, const Verb verbs[], void *userdata) {
+int _dispatch_verb_with_args(char **args, const Verb verbs[], const Verb verbs_end[], void *userdata) {
         int r;
 
         assert(verbs);
+        assert(verbs_end > verbs);
         assert(verbs[0].dispatch);
         assert(verbs[0].verb);
 
         const char *name = args ? args[0] : NULL;
         size_t left = strv_length(args);
 
-        const Verb *verb = verbs_find_verb(name, verbs);
+        const Verb *verb = verbs_find_verb(name, verbs, verbs_end);
         if (!verb) {
                 _cleanup_strv_free_ char **verb_strv = NULL;
 
-                for (size_t i = 0; verbs[i].dispatch; i++) {
-                        r = strv_extend(&verb_strv, verbs[i].verb);
+                for (verb = verbs; verb < verbs_end; verb++) {
+                        r = strv_extend(&verb_strv, verb->verb);
                         if (r < 0)
                                 return log_oom();
                 }
@@ -139,5 +141,46 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) {
         assert(argv);
         assert(argc >= optind);
 
-        return dispatch_verb_with_args(strv_skip(argv, optind), verbs, userdata);
+        size_t n = 0;
+        while (verbs[n].dispatch)
+                n++;
+
+        return _dispatch_verb_with_args(strv_skip(argv, optind), verbs, verbs + n, userdata);
+}
+
+int _verbs_get_help_table(const Verb verbs[], const Verb verbs_end[], Table **ret) {
+        int r;
+
+        assert(ret);
+
+        _cleanup_(table_unrefp) Table *table = table_new("verb", "help");
+        if (!table)
+                return log_oom();
+
+        for (const Verb *verb = verbs; verb < verbs_end; verb++) {
+                assert(verb->dispatch);
+
+                /* We indent the option string by two spaces. We could set the minimum cell width and
+                 * right-align for a similar result, but that'd be more work. This is only used for
+                 * display. */
+                r = table_add_cell_stringf(table, NULL, "  %s%s%s",
+                                           verb->verb,
+                                           verb->argspec ? " " : "",
+                                           strempty(verb->argspec));
+                if (r < 0)
+                        return table_log_add_error(r);
+
+                const char *help = verb->help ?: "FIXME";
+                _cleanup_strv_free_ char **s = strv_split(help, /* separators= */ NULL);
+                if (!s)
+                        return log_oom();
+
+                r = table_add_many(table, TABLE_STRV_WRAPPED, s);
+                if (r < 0)
+                        return table_log_add_error(r);
+        }
+
+        table_set_header(table, false);
+        *ret = TAKE_PTR(table);
+        return 0;
 }
index febb8ccfc24fd030441c7ac5a397a7a3bea35266..062e49466f9ea966d88bed0486ba2fd1ec913d2f 100644 (file)
@@ -16,12 +16,58 @@ typedef struct {
         VerbFlags flags;
         int (* const dispatch)(int argc, char *argv[], uintptr_t data, void *userdata);
         uintptr_t data;
+        const char *argspec;
+        const char *help;
 } Verb;
 
+#define VERB_FULL(d, v, a, amin, amax, f, dat, h)                       \
+        static int d(int, char**, uintptr_t, void*);                    \
+        _section_("SYSTEMD_VERBS")                                      \
+        _alignptr_                                                      \
+        _used_                                                          \
+        _retain_                                                        \
+        _variable_no_sanitize_address_                                  \
+        static const Verb CONCATENATE(d, _data) = {                     \
+                .verb = v,                                              \
+                .min_args = amin,                                       \
+                .max_args = amax,                                       \
+                .flags = f,                                             \
+                .dispatch = d,                                          \
+                .data = dat,                                            \
+                .argspec = a,                                           \
+                .help = h,                                              \
+        }
+
+/* The same as VERB_FULL, but without the data argument */
+#define VERB(d, v, a, amin, amax, f, h)                                 \
+        VERB_FULL(d, v, a, amin, amax, f, /* dat= */ 0, h)
+
+/* Simplified VERB for parameters that take no argument */
+#define VERB_NOARG(d, v, h)                                             \
+        VERB(d, v, /* a= */ NULL, /* amin= */ VERB_ANY, /* amax= */ 1, /* f= */ 0, h)
+
+/* This is magically mapped to the beginning and end of the section */
+extern const Verb __start_SYSTEMD_VERBS[];
+extern const Verb __stop_SYSTEMD_VERBS[];
+
 bool running_in_chroot_or_offline(void);
 
 bool should_bypass(const char *env_prefix);
 
-const Verb* verbs_find_verb(const char *name, const Verb verbs[]);
-int dispatch_verb_with_args(char **args, const Verb verbs[], void *userdata);
+const Verb* verbs_find_verb(const char *name, const Verb verbs[], const Verb verbs_end[]);
+
+int _dispatch_verb_with_args(char **args, const Verb verbs[], const Verb verbs_end[], void *userdata);
+#define dispatch_verb_with_args(args, userdata) \
+        _dispatch_verb_with_args(args, ALIGN_PTR(__start_SYSTEMD_VERBS), __stop_SYSTEMD_VERBS, userdata)
+
 int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata);
+
+int _verbs_get_help_table(const Verb verbs[], const Verb verbs_end[], Table **ret);
+#define verbs_get_help_table(ret) \
+        _verbs_get_help_table(ALIGN_PTR(__start_SYSTEMD_VERBS), __stop_SYSTEMD_VERBS, ret)
+
+#define VERB_COMMON_HELP(impl)                                          \
+        VERB(verb_help, "help", NULL, VERB_ANY, VERB_ANY, 0, "Show this help"); \
+        static int verb_help(int argc, char **argv, uintptr_t data, void *userdata) { \
+                return impl();                                          \
+        }
index 565b3a8878adb5505912e7759aff1a5fad56b037..4d1830071f47e99b3d0897a71b9af832acddeb73 100644 (file)
@@ -135,7 +135,7 @@ static int systemctl_main(int argc, char *argv[]) {
                 {}
         };
 
-        const Verb *verb = verbs_find_verb(argv[optind], verbs);
+        const Verb *verb = verbs_find_verb(argv[optind], verbs, verbs + ELEMENTSOF(verbs) - 1);
         if (verb && (verb->flags & VERB_ONLINE_ONLY) && arg_root)
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                        "Verb '%s' cannot be used with --root= or --image=.",