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;
_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();
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);
if (!table)
return log_oom();
+ bool in_group = group == NULL; /* Are we currently in the section on the array that forms
+ * group <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
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 {
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, \
.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)
#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 <gr>. */
+#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[];
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) { \
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 },
{}
};
/* 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) {
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 },
{}
};
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);