diff --git a/src/shared/options.c b/src/shared/options.c index faefbe659e..364b57792c 100644 --- a/src/shared/options.c +++ b/src/shared/options.c @@ -21,7 +21,8 @@ static bool option_arg_required(const Option *opt) { static bool option_is_metadata(const Option *opt) { /* A metadata entry that is not a real option, like the group marker */ - return ASSERT_PTR(opt)->flags & (OPTION_GROUP_MARKER | + return ASSERT_PTR(opt)->flags & (OPTION_NAMESPACE_MARKER | + OPTION_GROUP_MARKER | OPTION_POSITIONAL_ENTRY | OPTION_HELP_ENTRY | OPTION_HELP_ENTRY_VERBATIM); @@ -81,7 +82,7 @@ int option_parse( /* Check and initialize */ switch (state->state) { - case OPTION_PARSER_INIT: + case OPTION_PARSER_INIT: { assert(state->mode >= 0 && state->mode < _OPTION_PARSER_MODE_MAX); if (state->argc < 1) { @@ -90,9 +91,34 @@ int option_parse( } assert_se((size_t) state->argc == strv_length(state->argv)); /* Make sure argc/argv are consistent */ + + /* Figure out the right range of options */ + bool in_ns = state->namespace == NULL; /* Are we currently in the section of the array that + * forms namespace ? The first part is the + * default unnamed namespace, so if the namespace was + * not specified, we are in it. */ + if (in_ns) + state->namespace_start = options; + + const Option *opt; + for (opt = options; opt < options_end; opt++) { + bool ns_marker = FLAGS_SET(opt->flags, OPTION_NAMESPACE_MARKER); + if (!in_ns) { + in_ns = ns_marker && streq(state->namespace, opt->long_code); + if (in_ns) + state->namespace_start = opt + 1; + continue; + } + if (ns_marker) + break; /* End of namespace */ + } + assert(state->namespace_start); + state->namespace_end = opt; + state->optind = state->positional_offset = 1; state->state = OPTION_PARSER_RUNNING; break; + } case OPTION_PARSER_RUNNING: case OPTION_PARSER_STOPPING: @@ -156,10 +182,10 @@ int option_parse( if (handling_positional_arg) /* We are supposed to return the positional arg to be handled. */ - for (option = options;; option++) { + for (option = state->namespace_start;; option++) { /* If OPTION_PARSER_RETURN_POSITIONAL_ARGS is specified, * OPTION_POSITIONAL must be used. */ - assert(option < options_end); + assert(option < state->namespace_end); if (FLAGS_SET(option->flags, OPTION_POSITIONAL_ENTRY)) break; @@ -184,8 +210,8 @@ int option_parse( const Option *last_partial = NULL; unsigned n_partial_matches = 0; /* The commandline option matches a defined prefix. */ - for (option = options;; option++) { - if (option >= options_end) { + for (option = state->namespace_start;; option++) { + if (option >= state->namespace_end) { if (n_partial_matches == 0) { r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s: unrecognized option '%s'", @@ -193,7 +219,11 @@ int option_parse( goto fail; } if (n_partial_matches > 1) { - r = partial_match_error(options, options_end, optname, n_partial_matches); + r = partial_match_error( + state->namespace_start, + state->namespace_end, + optname, + n_partial_matches); goto fail; } @@ -230,8 +260,8 @@ int option_parse( } optname = _optname; - for (option = options;; option++) { - if (option >= options_end) { + for (option = state->namespace_start;; option++) { + if (option >= state->namespace_end) { r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s: unrecognized option '%s'", program_invocation_short_name, optname); @@ -354,7 +384,8 @@ size_t option_parser_get_n_args(const OptionParser *state) { char* option_get_synopsis(const char *prefix, const Option *opt, const char *joiner, bool show_metavar) { assert(opt); - assert(!FLAGS_SET(opt->flags, OPTION_GROUP_MARKER)); /* A group marker should not be displayed */ + assert(!(opt->flags & (OPTION_NAMESPACE_MARKER | + OPTION_GROUP_MARKER))); /* The markers should not be displayed */ if (!prefix) prefix = ""; @@ -405,9 +436,10 @@ char* option_get_synopsis(const char *prefix, const Option *opt, const char *joi option_arg_optional(opt) ? "]" : ""); } -int _option_parser_get_help_table( +int _option_parser_get_help_table_full( const Option options[], const Option options_end[], + const char *namespace, const char *group, Table **ret) { int r; @@ -418,11 +450,23 @@ int _option_parser_get_help_table( 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. */ + bool in_ns = namespace == NULL; /* Are we currently in the section of the array that forms namespace + * ? The first part is the default unnamed namespace, so + * if the namespace was not specified, we are in it. */ + + bool in_group = group == NULL; /* Are we currently in the section of the array that forms group + * ? The first part is the default group, so if the group was + * not specified, we are in it. */ for (const Option *opt = options; opt < options_end; opt++) { + bool ns_marker = FLAGS_SET(opt->flags, OPTION_NAMESPACE_MARKER); + if (!in_ns) { + in_ns = ns_marker && streq(namespace, opt->long_code); + continue; + } + if (ns_marker) + break; /* End of namespace */ + bool group_marker = FLAGS_SET(opt->flags, OPTION_GROUP_MARKER); if (!in_group) { in_group = group_marker && streq(group, opt->long_code); @@ -455,6 +499,8 @@ int _option_parser_get_help_table( return table_log_add_error(r); } + assert(!table_isempty(table)); /* The namespace or group were not found. Something is off. */ + table_set_header(table, false); *ret = TAKE_PTR(table); return 0; diff --git a/src/shared/options.h b/src/shared/options.h index 81b92d2da5..7d8507d0e5 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -4,13 +4,21 @@ #include "memory-util.h" #include "shared-forward.h" +/* Option namespace/group explanation: + * the list of options is split into namespaces, and a namespace is split into groups. + * By default, options defined in a single program are all placed in a single (unnamed) namespace + * and in a single (unnamed) group. OPTION_NAMESPACE() marks the beginning of a named namespace. + * OPTION_GROUP() marks the beginning of a named group. + */ + typedef enum OptionFlags { OPTION_OPTIONAL_ARG = 1U << 0, /* Same as optional_argument in getopt */ OPTION_POSITIONAL_ENTRY = 1U << 1, /* The "option" to handle positional arguments */ OPTION_STOPS_PARSING = 1U << 2, /* This option acts like "--" */ - OPTION_GROUP_MARKER = 1U << 3, /* Fake option entry to separate groups */ - OPTION_HELP_ENTRY = 1U << 4, /* Fake option entry to insert an additional help line */ - OPTION_HELP_ENTRY_VERBATIM = 1U << 5, /* Same, but use the long_code in the first column as written */ + OPTION_NAMESPACE_MARKER = 1U << 3, /* Fake option entry to separate namespaces */ + OPTION_GROUP_MARKER = 1U << 4, /* Fake option entry to separate groups */ + OPTION_HELP_ENTRY = 1U << 5, /* Fake option entry to insert an additional help line */ + OPTION_HELP_ENTRY_VERBATIM = 1U << 6, /* Same, but use the long_code in the first column as written */ } OptionFlags; typedef struct Option { @@ -41,9 +49,13 @@ typedef struct Option { }; \ case (0x100 + counter) +/* Magic entry in the table (which will not be returned) that designates the start of the namespace . + * The define is structured as 'case' so that it can be followed by ':' and indented appropriately. */ +#define OPTION_NAMESPACE(ns) \ + _OPTION(__COUNTER__, OPTION_NAMESPACE_MARKER, /* sc= */ 0, /* lc= */ ns, /* mv= */ NULL, /* d= */ 0u, /* h= */ NULL) + /* Magic entry in the table (which will not be returned) that designates the start of the group . - * The define is structured as 'case' so that it can be followed by ':' and indented appropriately. - */ + * The define is structured as 'case' so that it can be followed by ':' and indented appropriately. */ #define OPTION_GROUP(gr) \ _OPTION(__COUNTER__, OPTION_GROUP_MARKER, /* sc= */ 0, /* lc= */ gr, /* mv= */ NULL, /* d= */ 0u, /* h= */ NULL) @@ -164,11 +176,15 @@ typedef enum OptionParserState { } OptionParserState; typedef struct OptionParser { - /* Those three should stay first so that it's possible to initialize the struct as { argc, argv } - * or { argc, argv, mode }. */ + /* Those four should stay first so that it's possible to initialize the struct as { argc, argv } + * or { argc, argv, mode } or { argc, argv, mode, namespace }. */ int argc; /* The original argc. */ char **argv; /* The argv array, possibly reordered. */ OptionParserMode mode; + const char *namespace; /* The namespace, may be NULL. */ + + const Option *namespace_start, *namespace_end; /* The range of options that are part of our namespace. */ + OptionParserState state; int optind; /* Position of the parameter being handled. * 0 → option parsing hasn't been started yet. */ @@ -203,12 +219,15 @@ char** option_parser_get_args(const OptionParser *state); size_t option_parser_get_n_args(const OptionParser *state); char* option_get_synopsis(const char *prefix, const Option *opt, const char *joiner, bool show_metavar); -int _option_parser_get_help_table( +int _option_parser_get_help_table_full( const Option options[], const Option options_end[], + const char *namespace, const char *group, Table **ret); +#define option_parser_get_help_table_full(namespace, group, ret) \ + _option_parser_get_help_table_full(ALIGN_PTR(__start_SYSTEMD_OPTIONS), __stop_SYSTEMD_OPTIONS, namespace, group, ret) #define option_parser_get_help_table_group(group, ret) \ - _option_parser_get_help_table(ALIGN_PTR(__start_SYSTEMD_OPTIONS), __stop_SYSTEMD_OPTIONS, group, ret) + option_parser_get_help_table_full(/* namespace= */ NULL, group, ret) #define option_parser_get_help_table(ret) \ option_parser_get_help_table_group(/* group= */ NULL, ret) diff --git a/src/test/test-options.c b/src/test/test-options.c index 6a0744e673..a77964df4e 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -15,7 +15,8 @@ static void test_option_parse_one( const Option options[], const Entry *entries, char **remaining, - OptionParserMode mode) { + OptionParserMode mode, + const char *namespace) { _cleanup_free_ char *joined = strv_join(argv, ", "); log_debug("/* %s(%s) */", __func__, joined); @@ -32,7 +33,7 @@ static void test_option_parse_one( for (const Entry *e = entries; e && (e->long_code || e->short_code != 0); e++) n_entries++; - OptionParser opts = { argc, argv, mode }; + OptionParser opts = { argc, argv, mode, namespace }; for (int c; (c = option_parse(options, options + n_options, &opts)) != 0; ) { ASSERT_OK(c); ASSERT_NOT_NULL(opts.opt); @@ -90,6 +91,12 @@ TEST(option_parse) { { 4, .long_code = "required2", .metavar = "ARG" }, { 5, .short_code = 'o', .long_code = "optional1", .metavar = "ARG", .flags = OPTION_OPTIONAL_ARG }, { 6, .long_code = "optional2", .metavar = "ARG", .flags = OPTION_OPTIONAL_ARG }, + { 7, .long_code = "NS2", .flags = OPTION_NAMESPACE_MARKER }, + { 8, .short_code = 'h', .long_code = "help2" }, + { 9, .long_code = "version2" }, + { 10, .long_code = "NS3", .flags = OPTION_NAMESPACE_MARKER }, + { 11, .short_code = 'h', .long_code = "help3" }, + { 12, .long_code = "version3" }, {} }; @@ -97,7 +104,8 @@ TEST(option_parse) { options, NULL, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -110,7 +118,8 @@ TEST(option_parse) { "string2", "string3", "string4"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "--", @@ -124,7 +133,8 @@ TEST(option_parse) { "--help", "-h", "string4"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -138,7 +148,8 @@ TEST(option_parse) { "string2", "--", "string4"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -152,7 +163,8 @@ TEST(option_parse) { "string2", "string3", "string4"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "--help"), @@ -162,7 +174,8 @@ TEST(option_parse) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "--help", @@ -175,7 +188,8 @@ TEST(option_parse) { }, STRV_MAKE("string1", "--help"), - OPTION_PARSER_STOP_AT_FIRST_NONOPTION); + OPTION_PARSER_STOP_AT_FIRST_NONOPTION, + NULL); test_option_parse_one(STRV_MAKE("arg0", "-h"), @@ -185,7 +199,8 @@ TEST(option_parse) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "--help", @@ -202,7 +217,8 @@ TEST(option_parse) { "string2", "string3", "string4"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "-h", @@ -219,7 +235,8 @@ TEST(option_parse) { "string2", "string3", "string4"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -236,7 +253,8 @@ TEST(option_parse) { "string2", "string3", "string4"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -253,7 +271,8 @@ TEST(option_parse) { "string2", "string3", "string4"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -270,7 +289,8 @@ TEST(option_parse) { "string2", "string3", "string4"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -287,7 +307,8 @@ TEST(option_parse) { "string2", "string3", "string4"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "--required1", "reqarg1"), @@ -297,7 +318,8 @@ TEST(option_parse) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "-r", "reqarg1"), @@ -307,7 +329,8 @@ TEST(option_parse) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -320,7 +343,8 @@ TEST(option_parse) { }, STRV_MAKE("string1", "string2"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -331,7 +355,8 @@ TEST(option_parse) { STRV_MAKE("string1", "string2", "-r", "reqarg1"), - OPTION_PARSER_STOP_AT_FIRST_NONOPTION); + OPTION_PARSER_STOP_AT_FIRST_NONOPTION, + NULL); test_option_parse_one(STRV_MAKE("arg0", "--optional1=optarg1"), @@ -341,7 +366,8 @@ TEST(option_parse) { {} }, NULL, - OPTION_PARSER_STOP_AT_FIRST_NONOPTION); + OPTION_PARSER_STOP_AT_FIRST_NONOPTION, + NULL); test_option_parse_one(STRV_MAKE("arg0", "--optional1", "string1"), @@ -351,7 +377,8 @@ TEST(option_parse) { {} }, STRV_MAKE("string1"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "-ooptarg1"), @@ -361,7 +388,8 @@ TEST(option_parse) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "-o", "string1"), @@ -371,7 +399,8 @@ TEST(option_parse) { {} }, STRV_MAKE("string1"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); test_option_parse_one(STRV_MAKE("arg0", "string1", @@ -441,7 +470,42 @@ TEST(option_parse) { "--help", "--required1", "--optional1"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); + + /* Check that we can access options from NS2 */ + test_option_parse_one(STRV_MAKE("arg0", + "-h", /* This verifies that we're using the right namespace */ + "--help2", + "--version2", + "string1"), + options, + (Entry[]) { + { "help2" }, + { "help2" }, + { "version2" }, + {} + }, + STRV_MAKE("string1"), + OPTION_PARSER_NORMAL, + "NS2"); + + /* Check that we can access options from NS3 */ + test_option_parse_one(STRV_MAKE("arg0", + "-h", /* This verifies that we're using the right namespace */ + "--help3", + "--version3", + "string1"), + options, + (Entry[]) { + { "help3" }, + { "help3" }, + { "version3" }, + {} + }, + STRV_MAKE("string1"), + OPTION_PARSER_NORMAL, + "NS3"); } TEST(option_stops_parsing) { @@ -465,7 +529,8 @@ TEST(option_stops_parsing) { }, STRV_MAKE("--help", "foo"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Options before --exec are still parsed */ test_option_parse_one(STRV_MAKE("arg0", @@ -481,7 +546,8 @@ TEST(option_stops_parsing) { }, STRV_MAKE("--version", "bar"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* --exec with no trailing args */ test_option_parse_one(STRV_MAKE("arg0", @@ -492,7 +558,8 @@ TEST(option_stops_parsing) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* --exec after positional args */ test_option_parse_one(STRV_MAKE("arg0", @@ -509,7 +576,8 @@ TEST(option_stops_parsing) { "--help", "--required", "val"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* "--" after --exec: "--" is still consumed as end-of-options marker. This is needed for * backwards compatibility, systemd-dissect implemented this behaviour. But also, it makes @@ -525,7 +593,8 @@ TEST(option_stops_parsing) { {} }, STRV_MAKE("--help"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* "--" before --exec: "--" terminates first, --exec is positional */ test_option_parse_one(STRV_MAKE("arg0", @@ -536,7 +605,8 @@ TEST(option_stops_parsing) { NULL, STRV_MAKE("--exec", "--help"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Multiple options then --exec then more option-like args */ test_option_parse_one(STRV_MAKE("arg0", @@ -555,7 +625,8 @@ TEST(option_stops_parsing) { STRV_MAKE("-h", "--required", "val2"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); } TEST(option_group_marker) { @@ -580,7 +651,8 @@ TEST(option_group_marker) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Check that group marker name is ignored */ test_option_parse_one(STRV_MAKE("arg0", @@ -593,7 +665,8 @@ TEST(option_group_marker) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Verify that the group marker is not mistaken for an option */ test_option_invalid_one(STRV_MAKE("arg0", @@ -621,7 +694,8 @@ TEST(option_group_marker) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Partial match with multiple candidates */ test_option_invalid_one(STRV_MAKE("arg0", @@ -645,7 +719,8 @@ TEST(option_optional_arg) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Long option without = does NOT consume the next arg */ test_option_parse_one(STRV_MAKE("arg0", @@ -656,7 +731,8 @@ TEST(option_optional_arg) { {} }, STRV_MAKE("foo.txt"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Short option with inline arg */ test_option_parse_one(STRV_MAKE("arg0", @@ -667,7 +743,8 @@ TEST(option_optional_arg) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Short option without inline arg does NOT consume the next arg */ test_option_parse_one(STRV_MAKE("arg0", @@ -678,7 +755,8 @@ TEST(option_optional_arg) { {} }, STRV_MAKE("foo.txt"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Optional arg option at end of argv */ test_option_parse_one(STRV_MAKE("arg0", @@ -689,7 +767,8 @@ TEST(option_optional_arg) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Mixed: optional arg with other options */ test_option_parse_one(STRV_MAKE("arg0", @@ -704,7 +783,8 @@ TEST(option_optional_arg) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Short combo: -ho (h then o with no arg) */ test_option_parse_one(STRV_MAKE("arg0", @@ -716,7 +796,8 @@ TEST(option_optional_arg) { {} }, STRV_MAKE("pos1"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Short combo: -hobar (h then o with inline arg "bar") */ test_option_parse_one(STRV_MAKE("arg0", @@ -728,7 +809,8 @@ TEST(option_optional_arg) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); } /* Test the OPTION, OPTION_LONG, OPTION_SHORT, OPTION_FULL, OPTION_GROUP macros @@ -738,7 +820,8 @@ static void test_macros_parse_one( char **argv, const Entry *entries, char **remaining, - OptionParserMode mode) { + OptionParserMode mode, + const char *namespace) { _cleanup_free_ char *joined = strv_join(argv, ", "); log_debug("/* %s(%s) */", __func__, joined); @@ -752,7 +835,7 @@ static void test_macros_parse_one( for (const Entry *e = entries; e && (e->long_code || e->short_code != 0); e++) n_entries++; - OptionParser opts = { argc, argv, mode }; + OptionParser opts = { argc, argv, mode, namespace }; FOREACH_OPTION(c, &opts, assert_not_reached()) { log_debug("%c %s: %s=%s", @@ -814,9 +897,14 @@ static void test_macros_parse_one( OPTION_POSITIONAL: break; + OPTION_NAMESPACE("namespaced options"): {} + + OPTION('r', "required2", "ARG", "Required arg option"): + break; + default: log_error("Unexpected option id: %d", c); - ASSERT_TRUE(false); + assert_not_reached(); } } @@ -838,7 +926,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION: short form */ test_macros_parse_one(STRV_MAKE("arg0", @@ -848,7 +937,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_LONG: only accessible via long form */ test_macros_parse_one(STRV_MAKE("arg0", @@ -858,7 +948,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_SHORT: only accessible via short form */ test_macros_parse_one(STRV_MAKE("arg0", @@ -868,7 +959,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION with required arg: long --required=ARG */ test_macros_parse_one(STRV_MAKE("arg0", @@ -878,7 +970,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION with required arg: long --required ARG */ test_macros_parse_one(STRV_MAKE("arg0", @@ -888,7 +981,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION with required arg: short -r ARG */ test_macros_parse_one(STRV_MAKE("arg0", @@ -898,7 +992,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION with required arg: short -rARG */ test_macros_parse_one(STRV_MAKE("arg0", @@ -908,7 +1003,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_FULL with OPTION_OPTIONAL_ARG: long with = */ test_macros_parse_one(STRV_MAKE("arg0", @@ -918,7 +1014,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_FULL with OPTION_OPTIONAL_ARG: long without = doesn't consume next */ test_macros_parse_one(STRV_MAKE("arg0", @@ -928,7 +1025,8 @@ TEST(option_macros) { {} }, STRV_MAKE("pos1"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_FULL with OPTION_OPTIONAL_ARG: short inline */ test_macros_parse_one(STRV_MAKE("arg0", @@ -938,7 +1036,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_FULL with OPTION_OPTIONAL_ARG: short without inline */ test_macros_parse_one(STRV_MAKE("arg0", @@ -948,7 +1047,8 @@ TEST(option_macros) { {} }, STRV_MAKE("pos1"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_FULL with OPTION_STOPS_PARSING: stops further option parsing */ test_macros_parse_one(STRV_MAKE("arg0", @@ -961,7 +1061,8 @@ TEST(option_macros) { }, STRV_MAKE("--help", "--version"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_STOPS_PARSING: options before are still parsed */ test_macros_parse_one(STRV_MAKE("arg0", @@ -976,7 +1077,8 @@ TEST(option_macros) { }, STRV_MAKE("-h", "--debug"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_STOPS_PARSING with "--": "--" after exec is still consumed */ test_macros_parse_one(STRV_MAKE("arg0", @@ -988,7 +1090,8 @@ TEST(option_macros) { {} }, STRV_MAKE("--help"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_STOPS_PARSING with "--": "--" before exec takes precedence */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1000,7 +1103,8 @@ TEST(option_macros) { }, STRV_MAKE("--exec", "--help"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_GROUP: group marker is transparent to parsing, --debug in Advanced group works */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1010,7 +1114,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Mixed: all macro types together */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1039,7 +1144,8 @@ TEST(option_macros) { }, STRV_MAKE("pos1", "pos2"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Short option combos with macros: -hv (help + verbose) */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1050,7 +1156,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Short option combo with required arg: -hrval (help + required with arg "val") */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1061,7 +1168,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Short option combo with optional arg: -hoval (help + optional with arg "val") */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1072,7 +1180,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_STOPS_PARSING then "--": "--" is still consumed after exec */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1088,7 +1197,8 @@ TEST(option_macros) { }, STRV_MAKE("--version", "-h"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_STOPS_PARSING then later "--": "--" is not consumed */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1105,7 +1215,8 @@ TEST(option_macros) { STRV_MAKE("--version", "--", "-h"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* OPTION_STOPS_PARSING then "--" twice: second "--" is not consumed */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1123,7 +1234,8 @@ TEST(option_macros) { STRV_MAKE("--", "--version", "-h"), - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* Basic OPTION_POSITIONAL use */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1139,7 +1251,8 @@ TEST(option_macros) { {} }, NULL, - OPTION_PARSER_RETURN_POSITIONAL_ARGS); + OPTION_PARSER_RETURN_POSITIONAL_ARGS, + NULL); /* OPTION_POSITIONAL combined with OPTION_STOPS_PARSING */ test_macros_parse_one(STRV_MAKE("arg0", @@ -1154,7 +1267,30 @@ TEST(option_macros) { {} }, STRV_MAKE("arg2"), - OPTION_PARSER_RETURN_POSITIONAL_ARGS); + OPTION_PARSER_RETURN_POSITIONAL_ARGS, + NULL); + + /* Second namespace, OPTION: long form */ + test_macros_parse_one(STRV_MAKE("arg0", + "--required2=arg"), + (Entry[]) { + { "required2", "arg" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + "namespaced options"); + + /* Second namespace, OPTION: short form */ + test_macros_parse_one(STRV_MAKE("arg0", + "-rarg"), + (Entry[]) { + { "required2", "arg" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + "namespaced options"); } /* Test the pattern used by nspawn's --user: an optional-arg option that also @@ -1176,7 +1312,8 @@ TEST(option_optional_arg_consume) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* --user without arg: next arg is an option, so no consumption */ test_option_parse_one(STRV_MAKE("arg0", @@ -1189,7 +1326,8 @@ TEST(option_optional_arg_consume) { {} }, NULL, - OPTION_PARSER_NORMAL); + OPTION_PARSER_NORMAL, + NULL); /* --user without arg: next arg is positional (doesn't start with -). * The option parser returns NULL for the arg. The caller would then