From: Zbigniew Jędrzejewski-Szmek Date: Wed, 25 Mar 2026 12:57:54 +0000 (+0100) Subject: shared/verbs: introduce verb groups X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b59daaa4cf05f9354c20eb8e6ad6ed13c0cede3a;p=thirdparty%2Fsystemd.git shared/verbs: introduce verb groups This mirrors the idea and implementation for options. Previously, the Verb struct was named as verb_func_data_nn, but with the group marker entry, we don't have 'verb_func', so let's just call the item verb_data_nn. Using the verb here is a complication that is not needed. --- diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h index 1941e88d376..39004183d90 100644 --- a/src/fundamental/macro-fundamental.h +++ b/src/fundamental/macro-fundamental.h @@ -143,7 +143,6 @@ #define XCONCATENATE(x, y) x ## y #define CONCATENATE(x, y) XCONCATENATE(x, y) -#define CONCATENATE3(x, y, z) CONCATENATE(x, CONCATENATE(y, z)) #define assert_cc(expr) _Static_assert(expr, #expr) diff --git a/src/shared/options.c b/src/shared/options.c index 86f274821ed..c5118f33426 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -266,7 +266,7 @@ int _option_parser_get_help_table( bool in_group = group == NULL; /* Are we currently in the section on the array that forms * group ? The first part is the default group, so - * the group was not specified, we are in. */ + * if the group was not specified, we are in. */ for (const Option *opt = options; opt < options_end; opt++) { bool group_marker = FLAGS_SET(opt->flags, OPTION_GROUP_MARKER); diff --git a/src/shared/verbs.c b/src/shared/verbs.c index 47f219fab6f..dfecf048612 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -58,12 +58,21 @@ bool should_bypass(const char *env_prefix) { return true; } +static bool verb_is_metadata(const Verb *verb) { + /* A metadata entry that is not a real verb, like the group marker */ + return FLAGS_SET(ASSERT_PTR(verb)->flags, VERB_GROUP_MARKER); +} + const Verb* verbs_find_verb(const char *name, const Verb verbs[], const Verb verbs_end[]) { assert(verbs); - for (const Verb *verb = verbs; verb < verbs_end; verb++) + for (const Verb *verb = verbs; verb < verbs_end; verb++) { + if (verb_is_metadata(verb)) + continue; + if (name ? streq(name, verb->verb) : FLAGS_SET(verb->flags, VERB_DEFAULT)) return verb; + } /* At the end of the list? */ return NULL; @@ -85,6 +94,9 @@ int _dispatch_verb_with_args(char **args, const Verb verbs[], const Verb verbs_e _cleanup_strv_free_ char **verb_strv = NULL; for (verb = verbs; verb < verbs_end; verb++) { + if (verb_is_metadata(verb)) + continue; + r = strv_extend(&verb_strv, verb->verb); if (r < 0) return log_oom(); @@ -142,13 +154,17 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { assert(argc >= optind); size_t n = 0; - while (verbs[n].dispatch) + while (verbs[n].verb) 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 _verbs_get_help_table( + const Verb verbs[], + const Verb verbs_end[], + const char *group, + Table **ret) { int r; assert(ret); @@ -157,10 +173,23 @@ int _verbs_get_help_table(const Verb verbs[], const Verb verbs_end[], Table **re if (!table) return log_oom(); + bool in_group = group == NULL; /* Are we currently in the section on the array that forms + * group ? The first part is the default group, so + * if the group was not specified, we are in. */ + for (const Verb *verb = verbs; verb < verbs_end; verb++) { - assert(verb->dispatch); + assert(verb->verb); + + bool group_marker = FLAGS_SET(verb->flags, VERB_GROUP_MARKER); + if (!in_group) { + in_group = group_marker && streq(group, verb->verb); + continue; + } + if (group_marker) + break; /* End of group */ if (!verb->help) + /* No help string — we do not show the verb */ continue; /* We indent the option string by two spaces. We could set the minimum cell width and diff --git a/src/shared/verbs.h b/src/shared/verbs.h index 7980db62fe9..6b380041b81 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -8,6 +8,7 @@ typedef enum VerbFlags { VERB_DEFAULT = 1 << 0, /* The verb to run if no verb is specified */ VERB_ONLINE_ONLY = 1 << 1, /* Just do nothing when running in chroot or offline */ + VERB_GROUP_MARKER = 1 << 2, /* Fake verb entry to separate groups */ } VerbFlags; typedef struct { @@ -20,16 +21,13 @@ typedef struct { const char *help; } Verb; -#define VERB_FULL(d, v, a, amin, amax, f, dat, h) \ - DISABLE_WARNING_REDUNDANT_DECLS \ - static int d(int, char**, uintptr_t, void*); \ - REENABLE_WARNING \ +#define _VERB_DATA(d, v, a, amin, amax, f, dat, h) \ _section_("SYSTEMD_VERBS") \ _alignptr_ \ _used_ \ _retain_ \ _variable_no_sanitize_address_ \ - static const Verb CONCATENATE3(d, _data_, __COUNTER__) = { \ + static const Verb CONCATENATE(verb_data_, __COUNTER__) = { \ .verb = v, \ .min_args = amin, \ .max_args = amax, \ @@ -40,6 +38,12 @@ typedef struct { .help = h, \ } +#define VERB_FULL(d, v, a, amin, amax, f, dat, h) \ + DISABLE_WARNING_REDUNDANT_DECLS \ + static int d(int, char**, uintptr_t, void*); \ + REENABLE_WARNING \ + _VERB_DATA(d, v, a, amin, amax, f, dat, 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) @@ -48,6 +52,11 @@ typedef struct { #define VERB_NOARG(d, v, h) \ VERB(d, v, /* a= */ NULL, /* amin= */ VERB_ANY, /* amax= */ 1, /* f= */ 0, h) +/* Magic entry in the table (which will not be returned) that designates the start of the group . */ +#define VERB_GROUP(gr) \ + _VERB_DATA(/* d= */ NULL, /* v= */ gr, /* a= */ NULL, /* amin= */ 0, /* amax= */ 0, \ + /* f= */ VERB_GROUP_MARKER, /* dat= */ 0, /* h= */ NULL) + /* This is magically mapped to the beginning and end of the section */ extern const Verb __start_SYSTEMD_VERBS[]; extern const Verb __stop_SYSTEMD_VERBS[]; @@ -64,9 +73,15 @@ int _dispatch_verb_with_args(char **args, const Verb verbs[], const Verb verbs_e 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) +int _verbs_get_help_table( + const Verb verbs[], + const Verb verbs_end[], + const char *group, + Table **ret); +#define verbs_get_help_table_group(group, ret) \ + _verbs_get_help_table(ALIGN_PTR(__start_SYSTEMD_VERBS), __stop_SYSTEMD_VERBS, group, ret) +#define verbs_get_help_table(ret) \ + verbs_get_help_table_group(/* group= */ NULL, ret) #define _VERB_COMMON_HELP_IMPL(impl) \ static int verb_help(int argc, char **argv, uintptr_t data, void *userdata) { \ diff --git a/src/test/test-verbs.c b/src/test/test-verbs.c index 79c5c27dd94..41ae5a87f37 100644 --- a/src/test/test-verbs.c +++ b/src/test/test-verbs.c @@ -16,14 +16,16 @@ static int noop_dispatcher(int argc, char *argv[], uintptr_t _data, void *userda TEST(verbs) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, - { "list-images", VERB_ANY, 1, 0, noop_dispatcher }, - { "list", VERB_ANY, 2, VERB_DEFAULT, noop_dispatcher }, - { "status", 2, VERB_ANY, 0, noop_dispatcher }, - { "show", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, - { "terminate", 2, VERB_ANY, 0, noop_dispatcher }, - { "login", 2, 2, 0, noop_dispatcher }, - { "copy-to", 3, 4, 0, noop_dispatcher }, + { "help", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, + { "list-images", VERB_ANY, 1, 0, noop_dispatcher }, + { "list", VERB_ANY, 2, VERB_DEFAULT, noop_dispatcher }, + { "status", 2, VERB_ANY, 0, noop_dispatcher }, + { "Group2", VERB_ANY, VERB_ANY, VERB_GROUP_MARKER, NULL }, + { "show", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, + { "terminate", 2, VERB_ANY, 0, noop_dispatcher }, + { "Group3", 0, 0, VERB_GROUP_MARKER, NULL }, + { "login", 2, 2, 0, noop_dispatcher }, + { "copy-to", 3, 4, 0, noop_dispatcher }, {} }; @@ -44,6 +46,12 @@ TEST(verbs) { /* no verb, but a default is set */ test_dispatch_one(STRV_EMPTY, verbs, 0); + + /* the group entry shall not be found */ + test_dispatch_one(STRV_MAKE("Group2"), verbs, -EINVAL); + + /* the group entry shall not be found */ + test_dispatch_one(STRV_MAKE("Group3"), verbs, -EINVAL); } TEST(verbs_no_default) { @@ -60,14 +68,15 @@ TEST(verbs_no_default) { TEST(verbs_no_default_many) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, - { "list-images", VERB_ANY, 1, 0, noop_dispatcher }, - { "list", VERB_ANY, 2, 0, noop_dispatcher }, - { "status", 2, VERB_ANY, 0, noop_dispatcher }, - { "show", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, - { "terminate", 2, VERB_ANY, 0, noop_dispatcher }, - { "login", 2, 2, 0, noop_dispatcher }, - { "copy-to", 3, 4, 0, noop_dispatcher }, + { "help", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, + { "list-images", VERB_ANY, 1, 0, noop_dispatcher }, + { "list", VERB_ANY, 2, 0, noop_dispatcher }, + { "status", 2, VERB_ANY, 0, noop_dispatcher }, + { "Specials", VERB_ANY, VERB_ANY, VERB_GROUP_MARKER, NULL }, + { "show", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, + { "terminate", 2, VERB_ANY, 0, noop_dispatcher }, + { "login", 2, 2, 0, noop_dispatcher }, + { "copy-to", 3, 4, 0, noop_dispatcher }, {} }; @@ -75,6 +84,7 @@ TEST(verbs_no_default_many) { test_dispatch_one(STRV_MAKE("hel"), verbs, -EINVAL); test_dispatch_one(STRV_MAKE("helpp"), verbs, -EINVAL); test_dispatch_one(STRV_MAKE("hgrejgoraoiosafso"), verbs, -EINVAL); + test_dispatch_one(STRV_MAKE("Specials"), verbs, -EINVAL); } DEFINE_TEST_MAIN(LOG_INFO);