From b62d32c7b557dc74108f9f7644b623c3dec143fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 20 Aug 2026 10:10:05 +0200 Subject: [PATCH] shared/verbs: add concept of "shared verbs" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/shared/verbs.c | 30 +++++++++++++++++++++++++++--- src/shared/verbs.h | 5 +++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/shared/verbs.c b/src/shared/verbs.c index f4985fc775..1d568d60ac 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -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 */ diff --git a/src/shared/verbs.h b/src/shared/verbs.h index 8c9e7baaf2..99efd2523d 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -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 {