diff --git a/src/shared/options.c b/src/shared/options.c index 2bc160f4cd..72fc93245f 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -4,6 +4,7 @@ #include "format-table.h" #include "log.h" +#include "nulstr-util.h" #include "options.h" #include "stdio-util.h" #include "string-util.h" @@ -198,18 +199,27 @@ int option_parse( * First, figure out if we have a long option or a short option. */ assert(handling_positional_arg || state->argv[state->optind][0] == '-'); - if (handling_positional_arg) - /* We are supposed to return the positional arg to be handled. */ + if (handling_positional_arg) { + /* Find the positional arg to be handled. */ + + const char *group = NULL; /* We start in the default unnamed group. */ + for (option = state->namespace_start;; option++) { /* If OPTION_PARSER_RETURN_POSITIONAL_ARGS is specified, * OPTION_POSITIONAL must be used. */ assert(option < state->namespace_end); + /* If state.option_groups are specified, ignore options outside of the group */ + if (FLAGS_SET(option->flags, OPTION_GROUP_MARKER)) /* Start of a new option group */ + group = option->long_code; + if (state->option_groups && !(group && nulstr_contains(state->option_groups, group))) + continue; + if (FLAGS_SET(option->flags, OPTION_POSITIONAL_ENTRY)) break; } - else if (state->argv[state->optind][1] == '-') { + } else if (state->argv[state->optind][1] == '-') { /* We have a long option. */ char *eq = strchr(state->argv[state->optind], '='); if (eq) { @@ -238,6 +248,8 @@ int option_parse( const Option *last_partial = NULL; unsigned n_partial_matches = 0; /* The commandline option matches a defined prefix. */ + const char *group = NULL; /* We start in the default unnamed group. */ + for (option = state->namespace_start;; option++) { if (option >= state->namespace_end) { if (n_partial_matches == 0) { @@ -262,6 +274,12 @@ int option_parse( break; } + /* If state.option_groups are specified, ignore options outside of the group */ + if (FLAGS_SET(option->flags, OPTION_GROUP_MARKER)) /* Start of a new option group */ + group = option->long_code; + if (state->option_groups && !(group && nulstr_contains(state->option_groups, group))) + continue; + if (option_is_metadata(option) || !option->long_code) continue; @@ -290,6 +308,8 @@ int option_parse( } optname = _optname; + const char *group = NULL; /* We start in the default unnamed group. */ + for (option = state->namespace_start;; option++) { if (option >= state->namespace_end) { r = log_full_errno(LOG_ERR + state->log_level_shift, @@ -299,6 +319,12 @@ int option_parse( goto fail; } + /* If state.option_groups are specified, ignore options outside of the group */ + if (FLAGS_SET(option->flags, OPTION_GROUP_MARKER)) /* Start of a new option group */ + group = option->long_code; + if (state->option_groups && !(group && nulstr_contains(state->option_groups, group))) + continue; + if (option_is_metadata(option) || optchar != option->short_code) continue; @@ -573,6 +599,7 @@ int _option_parser_get_help_table_full( int options_get_help_table_group( const Option options[], const Option options_end[], + const char *option_groups, Table **ret, const char **ret_group) { int r; @@ -580,15 +607,24 @@ int options_get_help_table_group( assert(ret); assert(ret_group); - _cleanup_(table_unrefp) Table *table = table_new("names", "help"); - if (!table) - return log_oom(); + _cleanup_(table_unrefp) Table *table = NULL; assert(options_end > options); bool group_marker = FLAGS_SET(options[0].flags, OPTION_GROUP_MARKER); const char *group = group_marker ? ASSERT_PTR(options[0].long_code) : NULL; + /* Determine if we should ignore the whole option group. We do this if the option_groups option is + * specified and the current group name is not listed. The unnamed group cannot be specified by the + * nulstr, so always ignore it. */ + bool ignore_group = option_groups && !(group && nulstr_contains(option_groups, group)); + + if (!ignore_group) { + table = table_new("names", "help"); + if (!table) + return log_oom(); + } + const Option *opt; for (opt = options + group_marker; opt < options_end; opt++) { if (FLAGS_SET(opt->flags, OPTION_NAMESPACE_MARKER)) @@ -599,6 +635,9 @@ int options_get_help_table_group( /* End of the group */ break; + if (ignore_group) + continue; + if (!opt->help) /* No help string — we do not show the option */ continue; @@ -629,9 +668,11 @@ int options_get_help_table_group( return table_log_add_error(r); } - assert(!table_isempty(table)); /* Empty group? Something is off. */ + if (!ignore_group) { + assert(!table_isempty(table)); /* Empty group? Something is off. */ + table_set_header(table, false); + } - table_set_header(table, false); *ret = TAKE_PTR(table); *ret_group = group; @@ -687,6 +728,7 @@ int options_build_json( const Option options[], const Option options_end[], const char *namespace, + const char *option_groups, sd_json_variant **ret) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; @@ -714,6 +756,10 @@ int options_build_json( /* Positional and help entries are only used for display */ continue; + if (option_groups && !(group && nulstr_contains(option_groups, group))) + /* This group shall be ignored */ + continue; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *o = NULL; r = option_build_json(opt, group, &o); if (r < 0) diff --git a/src/shared/options.h b/src/shared/options.h index 8fe0cae2e5..5b971879c8 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -226,6 +226,8 @@ typedef struct OptionParser { char **argv; /* The argv array, possibly reordered. */ OptionParserMode mode; const char *namespace; /* The namespace, may be NULL. */ + const char *option_groups; /* Optional nulstr with option groups to look at. When specified, + * only named option groups found in this list are used. */ int log_level_shift; /* The log level difference from the default of LOG_ERR. * Allowed values are -3..4. * Use 4 == LOG_DEBUG - LOG_ERR to log at debug level. */ @@ -297,6 +299,7 @@ int _option_parser_get_help_table_full( int options_get_help_table_group( const Option options[], const Option options_end[], + const char *option_groups, Table **ret, const char **ret_group); @@ -306,4 +309,5 @@ int options_build_json( const Option options[], const Option options_end[], const char *namespace, + const char *option_groups, sd_json_variant **ret); diff --git a/src/shared/verbs.c b/src/shared/verbs.c index 1d568d60ac..d8f5f1ca51 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -72,7 +72,7 @@ static bool verb_is_metadata(const Verb *verb) { FLAGS_SET(ASSERT_PTR(verb)->flags, VERB_GROUP_MARKER); } -const Verb* verbs_find_verb(const char *name, const Verb verbs[], const Verb verbs_end[]) { +const Verb* _verbs_find_verb(const Verb verbs[], const Verb verbs_end[], const char *name) { assert(verbs); assert(verbs_end > verbs); assert((uintptr_t) verbs % sizeof(void*) == 0); @@ -101,7 +101,7 @@ int _dispatch_verb(char **args, const Verb verbs[], const Verb verbs_end[], void const char *name = args ? args[0] : NULL; size_t left = strv_length(args); - const Verb *verb = verbs_find_verb(name, verbs, verbs_end); + const Verb *verb = _verbs_find_verb(verbs, verbs_end, name); if (!verb) { _cleanup_strv_free_ char **verb_strv = NULL; @@ -224,7 +224,12 @@ static int verb_add_help_one(Table *table, const Verb *verb) { return 0; } -static const Verb* verbs_get_command(const Verb verbs[], const Verb verbs_end[], const char *name) { +const Verb* _verbs_find_command( + const Verb verbs[], + const Verb verbs_end[], + const char *name, + const CommandDescription **ret_cmd) { + assert(verbs); assert(verbs_end > verbs); assert((uintptr_t) verbs % sizeof(void*) == 0); @@ -235,8 +240,14 @@ static const Verb* verbs_get_command(const Verb verbs[], const Verb verbs_end[], continue; const CommandDescription *cmd = (const CommandDescription*) ASSERT_PTR(verb->data); - if (!name || nulstr_contains(cmd->names, name)) - return verb; + if (name && !nulstr_contains(cmd->names, name)) + continue; + + /* This function returns the Verb slot through the return value, and the + * CommandDescription it points to in the optional output parameter. */ + if (ret_cmd) + *ret_cmd = cmd; + return verb; } /* At the end of the list? */ @@ -332,7 +343,8 @@ static int print_verb_option_help( assert(verb->verb); - /* What is the the first group? */ + /* The group marker gives us the group name. + * If it's missing, we're in the default (unnamed) group. */ const char *group = FLAGS_SET(verb->flags, VERB_GROUP_MARKER) ? verb->verb : NULL; _cleanup_(table_unrefp) Table *table = NULL; @@ -368,17 +380,19 @@ static int print_verb_option_help( /* End of our namespace */ break; - r = options_get_help_table_group(opt, options_end, &table, &group); + r = options_get_help_table_group(opt, options_end, cmd->option_groups, &table, &group); if (r < 0) return r; - if (!GREEDY_REALLOC0(tables, n_verbs + n_opts + 2)) - return -ENOMEM; - if (!GREEDY_REALLOC0(groups, n_verbs + n_opts + 2)) - return -ENOMEM; + if (table) { + if (!GREEDY_REALLOC0(tables, n_verbs + n_opts + 2)) + return -ENOMEM; + if (!GREEDY_REALLOC0(groups, n_verbs + n_opts + 2)) + return -ENOMEM; - tables[n_verbs + n_opts] = TAKE_PTR(table); - groups[n_verbs + n_opts++] = group ?: "Options"; + tables[n_verbs + n_opts] = TAKE_PTR(table); + groups[n_verbs + n_opts++] = group ?: "Options"; + } opt += r; /* Skip over the whole option group */ } @@ -497,13 +511,9 @@ int _command_print_help_full( const char *footer_ansi_seq) { int r; - const Verb *cmdverb = verbs_get_command(verbs, verbs_end, name); - if (!cmdverb) - return log_error_errno(SYNTHETIC_ERRNO(ENODATA), - "Command %s not known", name); - - const CommandDescription *cmd = (const CommandDescription*) ASSERT_PTR(cmdverb->data); - const Verb *verbverbs = verbs_find_cmd_verbs(cmdverb, verbs_end); + const CommandDescription *cmd; + const Verb *cmdverb = ASSERT_PTR(_verbs_find_command(verbs, verbs_end, name, &cmd)); + const Verb *verbverbs = ASSERT_PTR(verbs_find_cmd_verbs(cmdverb, verbs_end)); if (cmd->pager_flags) pager_open(*cmd->pager_flags); @@ -603,7 +613,7 @@ static int command_build_json( } assert(names); /* At least the primary name must be defined */ - r = options_build_json(options, options_end, cmd->option_namespace, &opts); + r = options_build_json(options, options_end, cmd->option_namespace, cmd->option_groups, &opts); if (r < 0) return r; diff --git a/src/shared/verbs.h b/src/shared/verbs.h index 99efd2523d..673ef5870b 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -21,7 +21,8 @@ typedef struct CommandDescription { const char *argspec; /* optional specification of positional args in synopsis */ const char *footer; /* optional footer to print right above man page links */ const char *man_pages; /* nulstr with man page names */ - const char *option_namespace; /* to be used when the options are in a namespace */ + const char *option_namespace; /* optional option namespace for this command */ + const char *option_groups; /* optional nulstr with option groups for this command */ const PagerFlags *pager_flags; /* optional pointer to the runtime pager flags variable */ CommandFlags flags; } CommandDescription; @@ -107,7 +108,13 @@ 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[], const Verb verbs_end[]); +const Verb* _verbs_find_command(const Verb verbs[], const Verb verbs_end[], const char *name, const CommandDescription **ret_cmd); +#define verbs_find_command(name, ret_cmd) \ + _verbs_find_command(__start_SYSTEMD_VERBS, __stop_SYSTEMD_VERBS, name, ret_cmd) + +const Verb* _verbs_find_verb(const Verb verbs[], const Verb verbs_end[], const char *name); +#define verbs_find_verb(name) \ + _verbs_find_verb(__start_SYSTEMD_VERBS, __stop_SYSTEMD_VERBS, name) int _dispatch_verb(char **args, const Verb verbs[], const Verb verbs_end[], void *userdata); #define dispatch_verb(args, userdata) \ diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 659de527d3..6a36afc6d3 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -1093,8 +1093,7 @@ VERB_SCOPE(, verb_start, "condrestart", NULL, 2, VERB_ANY, VERB_SCOPE(, verb_is_active, "check", NULL, 2, VERB_ANY, VERB_ONLINE_ONLY, /* help= */ NULL); /* deprecated alias of is-active */ int systemctl_main(char **args) { - assert((uintptr_t) __start_SYSTEMD_VERBS % sizeof(void*) == 0); - const Verb *verb = verbs_find_verb(args[0], __start_SYSTEMD_VERBS, __stop_SYSTEMD_VERBS); + const Verb *verb = verbs_find_verb(args[0]); if (verb && (verb->flags & VERB_ONLINE_ONLY) && arg_root) return log_error_errno(SYNTHETIC_ERRNO(EINVAL),