shared/verbs: add concept of "shared verbs"

This allows commands like systemd-sysext + systemd-confext to be handled
without duplicating entries in the verbs table. The basic idea is to have
the following layout in the SYSTEMD_VERBS section:

  VERB_COMMAND_MARKER → "systemd-sysext", .flags = COMMAND_VERBS_SHARED
  VERB_COMMAND_MARKER → "systemd-confext", .flags = COMMAND_VERBS_SHARED
  VERB("status"),
  VERB("merge"),
  ...

And then in places where we want to work on the verbs, we skip over the
verb entries with VERB_COMMAND_MARKER and COMMAND_VERBS_SHARED flag set.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2026-08-25 09:33:53 +02:00
parent 2b4c6dda9d
commit b62d32c7b5
2 changed files with 32 additions and 3 deletions
+27 -3
View File
@@ -467,6 +467,27 @@ static int print_wrapped(const char *text, const char *ansi_seq) {
return 0;
}
static const Verb* verbs_find_cmd_verbs(const Verb *cmdverb, const Verb verbs_end[]) {
assert(cmdverb);
assert(FLAGS_SET(cmdverb->flags, VERB_COMMAND_MARKER));
assert(cmdverb < verbs_end);
const CommandDescription *cmd = (const CommandDescription*) ASSERT_PTR(cmdverb->data);
/* With COMMAND_VERBS_SHARED, we have a group of VERB_COMMAND_MARKER entries, and
* then the shared verbs below. Thus, we need to skip over them to locate the
* first actual verb. Without COMMAND_VERBS_SHARED, the verbs start immediately
* after the VERB_COMMAND_MARKER entry. */
const Verb *actual = cmdverb + 1;
if (FLAGS_SET(cmd->flags, COMMAND_VERBS_SHARED))
while (actual < verbs_end &&
FLAGS_SET(actual->flags, VERB_COMMAND_MARKER) &&
FLAGS_SET(((const CommandDescription*) ASSERT_PTR(actual->data))->flags,
COMMAND_VERBS_SHARED))
actual++;
return actual;
}
int _command_print_help_full(
const Verb verbs[],
const Verb verbs_end[],
@@ -482,6 +503,7 @@ int _command_print_help_full(
"Command %s not known", name);
const CommandDescription *cmd = (const CommandDescription*) ASSERT_PTR(cmdverb->data);
const Verb *verbverbs = verbs_find_cmd_verbs(cmdverb, verbs_end);
if (cmd->pager_flags)
pager_open(*cmd->pager_flags);
@@ -494,7 +516,7 @@ int _command_print_help_full(
help_cmdline(line);
}
else {
bool have_verbs = verbs_array_has_verbs(cmdverb + 1, verbs_end);
bool have_verbs = verbs_array_has_verbs(verbverbs, verbs_end);
help_cmdline(have_verbs ? "[OPTION…] COMMAND …" : "[OPTION…]");
}
@@ -503,7 +525,7 @@ int _command_print_help_full(
if (r < 0)
return r;
r = print_verb_option_help(cmd, cmdverb + 1, verbs_end, options, options_end);
r = print_verb_option_help(cmd, verbverbs, verbs_end, options, options_end);
if (r < 0)
return log_error_errno(r, "Failed to print verb&option help: %m");
@@ -568,9 +590,11 @@ static int command_build_json(
assert(cmdverb);
assert(FLAGS_SET(cmdverb->flags, VERB_COMMAND_MARKER));
assert(cmdverb < verbs_end);
assert(ret);
const CommandDescription *cmd = (const CommandDescription*) ASSERT_PTR(cmdverb->data);
const Verb *verbverbs = verbs_find_cmd_verbs(cmdverb, verbs_end);
NULSTR_FOREACH(name, cmd->names) {
r = sd_json_variant_append_arrayb(&names, SD_JSON_BUILD_STRING(name));
@@ -583,7 +607,7 @@ static int command_build_json(
if (r < 0)
return r;
for (const Verb *verb = cmdverb + 1; verb < verbs_end; verb++) {
for (const Verb *verb = verbverbs; verb < verbs_end; verb++) {
if (FLAGS_SET(verb->flags, VERB_COMMAND_MARKER))
break; /* Start of entries for another command */
+5
View File
@@ -8,6 +8,11 @@
typedef enum CommandFlags {
COMMAND_HELP_SEPARATE = 1 << 0, /* Do not synchronize the width between verbs and options */
COMMAND_VERBS_SHARED = 1 << 1, /* We have a group of COMMANDs with this flag set, and then
* the VERB definitions below. The commands share the same
* set of actual VERB definitions. This is useful for programs
* which are registered under multiple names, but each one
* behaves very similarly. */
} CommandFlags;
typedef struct CommandDescription {