Add "namespaces" to option macros, convert systemd-run+run0 (#41844)

This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2026-04-28 19:38:04 +02:00
committed by GitHub
4 changed files with 798 additions and 738 deletions
+381 -595
View File
File diff suppressed because it is too large Load Diff
+147 -49
View File
@@ -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);
@@ -72,21 +73,77 @@ int option_parse(
const Option options_end[],
OptionParser *state) {
/* We define this one early, since we use goto below, and need to guarantee its initialization */
_cleanup_free_ char *_optname = NULL; /* allocated option name */
int r;
assert(state);
/* Check and initialize */
if (state->optind == 0) {
switch (state->state) {
case OPTION_PARSER_INIT: {
assert(state->mode >= 0 && state->mode < _OPTION_PARSER_MODE_MAX);
if (state->argc < 1 || strv_isempty(state->argv))
return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "argv cannot be empty");
if (state->argc < 1) {
r = log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "argv cannot be empty");
goto fail;
}
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 <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;
/* Verify that the option array didn't get mangled within a namespace. */
for (opt = options; opt < options_end; opt++)
if (opt + 1 < options_end && !FLAGS_SET((opt + 1)->flags, OPTION_NAMESPACE_MARKER))
assert_se(opt->id < (opt + 1)->id);
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:
break;
case OPTION_PARSER_DONE:
goto done;
case OPTION_PARSER_FAILED:
return log_error_errno(SYNTHETIC_ERRNO(ESTALE), "Option parser failed before, refusing.");
default:
assert_not_reached();
}
/* Look for the next option */
const Option *option = NULL; /* initialization to appease gcc 13 */
const char *optname = NULL, *optval = NULL;
_cleanup_free_ char *_optname = NULL; /* allocated option name */
bool separate_optval = false;
bool handling_positional_arg = false;
@@ -94,27 +151,27 @@ int option_parse(
/* Handle non-option parameters */
for (;;) {
if (state->optind == state->argc)
goto finished;
goto done;
if (streq(state->argv[state->optind], "--")) {
/* No more options. Move "--" before positional args so that
* the list of positional args is clean. */
shift_arg(state->argv, state->positional_offset++, state->optind++);
state->parsing_stopped = true;
goto done;
}
if (state->parsing_stopped)
goto finished;
/* If we are in OPTION_PARSER_STOPPING state we only wanted to read one more "--" if
* there is one, nothing else, hence it's time to say goodbye now. */
if (state->state == OPTION_PARSER_STOPPING)
goto done;
if (state->argv[state->optind][0] == '-' &&
state->argv[state->optind][1] != '\0')
/* Looks like we found an option parameter */
break;
if (state->mode == OPTION_PARSER_STOP_AT_FIRST_NONOPTION) {
state->parsing_stopped = true;
goto finished;
}
if (state->mode == OPTION_PARSER_STOP_AT_FIRST_NONOPTION)
goto done;
if (state->mode == OPTION_PARSER_RETURN_POSITIONAL_ARGS) {
handling_positional_arg = true;
@@ -131,10 +188,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;
@@ -145,8 +202,10 @@ int option_parse(
char *eq = strchr(state->argv[state->optind], '=');
if (eq) {
optname = _optname = strndup(state->argv[state->optind], eq - state->argv[state->optind]);
if (!_optname)
return log_oom();
if (!_optname) {
r = log_oom();
goto fail;
}
/* joined argument */
optval = eq + 1;
@@ -157,14 +216,22 @@ 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) {
if (n_partial_matches == 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"%s: unrecognized option '%s'",
program_invocation_short_name, optname);
if (n_partial_matches > 1)
return partial_match_error(options, options_end, optname, n_partial_matches);
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'",
program_invocation_short_name, optname);
goto fail;
}
if (n_partial_matches > 1) {
r = partial_match_error(
state->namespace_start,
state->namespace_end,
optname,
n_partial_matches);
goto fail;
}
/* just one partial — good */
option = last_partial;
@@ -193,15 +260,19 @@ int option_parse(
if (state->short_option_offset > 0) {
char optchar = state->argv[state->optind][state->short_option_offset];
if (asprintf(&_optname, "-%c", optchar) < 0)
return log_oom();
if (asprintf(&_optname, "-%c", optchar) < 0) {
r = log_oom();
goto fail;
}
optname = _optname;
for (option = options;; option++) {
if (option >= options_end)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"%s: unrecognized option '%s'",
program_invocation_short_name, optname);
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);
goto fail;
}
if (option_is_metadata(option) || optchar != option->short_code)
continue;
@@ -223,15 +294,19 @@ int option_parse(
assert(option);
if (!handling_positional_arg && optval && !option_takes_arg(option))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"%s: option '%s' doesn't allow an argument",
program_invocation_short_name, optname);
if (!handling_positional_arg && optval && !option_takes_arg(option)) {
r = log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"%s: option '%s' doesn't allow an argument",
program_invocation_short_name, optname);
goto fail;
}
if (!handling_positional_arg && !optval && option_arg_required(option)) {
if (!state->argv[state->optind + 1])
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"%s: option '%s' requires an argument",
program_invocation_short_name, optname);
if (!state->argv[state->optind + 1]) {
r = log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"%s: option '%s' requires an argument",
program_invocation_short_name, optname);
goto fail;
}
optval = state->argv[state->optind + 1];
separate_optval = true;
}
@@ -250,16 +325,23 @@ int option_parse(
}
if (FLAGS_SET(option->flags, OPTION_STOPS_PARSING))
state->parsing_stopped = true;
state->state = OPTION_PARSER_STOPPING;
state->opt = option;
state->arg = optval;
return option->id;
finished:
done:
state->state = OPTION_PARSER_DONE;
state->opt = NULL;
state->arg = NULL;
return 0;
fail:
/* Invalidate the object for good on the first error */
assert(r < 0);
state->state = OPTION_PARSER_FAILED;
return r;
}
char* option_parser_next_arg(const OptionParser *state) {
@@ -292,7 +374,7 @@ char** option_parser_get_args(const OptionParser *state) {
* original argv array, so it must not be freed or modified. */
assert(state->optind > 0);
assert(state->optind == state->argc || state->parsing_stopped);
assert(state->state == OPTION_PARSER_DONE);
assert(state->positional_offset <= state->argc);
return state->argv + state->positional_offset;
@@ -300,7 +382,7 @@ char** option_parser_get_args(const OptionParser *state) {
size_t option_parser_get_n_args(const OptionParser *state) {
assert(state->optind > 0);
assert(state->optind == state->argc || state->parsing_stopped);
assert(state->state == OPTION_PARSER_DONE);
assert(state->positional_offset <= state->argc);
return state->argc - state->positional_offset;
@@ -308,7 +390,8 @@ size_t option_parser_get_n_args(const OptionParser *state) {
char* option_get_synopsis(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 (opt->flags & (OPTION_HELP_ENTRY_VERBATIM | OPTION_POSITIONAL_ENTRY))
return strdup(ASSERT_PTR(opt->long_code));
@@ -354,9 +437,10 @@ char* option_get_synopsis(const Option *opt, const char *joiner, bool show_metav
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;
@@ -367,11 +451,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 <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
* <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
* <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);
@@ -409,6 +505,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;
+49 -11
View File
@@ -4,13 +4,30 @@
#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.
*
* Note: if multiple namespaces are used, they should all be named, i.e. each separate parse_argv
* instance should have OPTION_NAMESPACE first, and then its set of OPTION()s. (This is because
* clang reorders OPTIONs coming from different functions. So an unnamed group could end up being
* merged with one of the earlier groups. It seems that reordering within a single function does
* not happen.)
*
* When groups are used, the first group may be named (with OPTION_GROUP appearing before any
* options), or it may be unnamed. Both variants should work fine.
*/
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 +58,13 @@ typedef struct Option {
}; \
case (0x100 + counter)
/* Magic entry in the table (which will not be returned) that designates the start of the namespace <ns>.
* 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 <gr>.
* 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)
@@ -140,7 +161,7 @@ extern const Option __stop_SYSTEMD_OPTIONS[];
typedef enum OptionParserMode {
/* The default mode. This is the implicit default and doesn't have to be specified. */
OPTION_PARSER_NORMAL = 0,
OPTION_PARSER_NORMAL,
/* Same as "+…" for getopt_long — only parse options before the first positional argument. */
OPTION_PARSER_STOP_AT_FIRST_NONOPTION,
@@ -152,14 +173,28 @@ typedef enum OptionParserMode {
_OPTION_PARSER_MODE_MAX,
} OptionParserMode;
typedef enum OptionParserState {
OPTION_PARSER_INIT,
OPTION_PARSER_RUNNING,
OPTION_PARSER_STOPPING, /* We processed an option with OPTION_STOPS_PARSING, and will eat up one
* more "--", but nothing else. */
OPTION_PARSER_DONE, /* Option parsing completed (could be because we reached the end, or because
* "--" was fully processed, or because we hit a terminating option). */
OPTION_PARSER_FAILED, /* We encountered a parse error, and terminated option parsing. */
_OPTION_PARSER_MAX,
} 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. */
bool parsing_stopped; /* We processed "--" or an option that terminates option parsing. */
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. */
int short_option_offset; /* Set when we're parsing an argument with one or more short options.
@@ -193,12 +228,15 @@ char** option_parser_get_args(const OptionParser *state);
size_t option_parser_get_n_args(const OptionParser *state);
char* option_get_synopsis(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)
+221 -83
View File
@@ -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,17 +625,18 @@ TEST(option_stops_parsing) {
STRV_MAKE("-h",
"--required",
"val2"),
OPTION_PARSER_NORMAL);
OPTION_PARSER_NORMAL,
NULL);
}
TEST(option_group_marker) {
static const Option options[] = {
{ 1, .short_code = 'h', .long_code = "help" },
{ 2, .long_code = "version" },
{ 0, .long_code = "AdvancedGroup", .flags = OPTION_GROUP_MARKER },
{ 3, .long_code = "debug" },
{ 4, .long_code = "Advance" }, /* prefix match with the group */
{ 5, .long_code = "defilbrilate" },
{ __COUNTER__, .short_code = 'h', .long_code = "help" },
{ __COUNTER__, .long_code = "version" },
{ __COUNTER__, .long_code = "AdvancedGroup", .flags = OPTION_GROUP_MARKER },
{ __COUNTER__, .long_code = "debug" },
{ __COUNTER__, .long_code = "Advance" }, /* prefix match with the group */
{ __COUNTER__, .long_code = "defilbrilate" },
{}
};
@@ -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,16 +1267,39 @@ 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
* peeks at the next arg to handle legacy "space-separated" form. */
TEST(option_optional_arg_consume) {
static const Option options[] = {
{ 1, .short_code = 'h', .long_code = "help" },
{ 2, .long_code = "user", .metavar = "NAME", .flags = OPTION_OPTIONAL_ARG },
{ 3, .short_code = 'u', .long_code = "uid", .metavar = "USER" },
{ __COUNTER__, .short_code = 'h', .long_code = "help" },
{ __COUNTER__, .long_code = "user", .metavar = "NAME", .flags = OPTION_OPTIONAL_ARG },
{ __COUNTER__, .short_code = 'u', .long_code = "uid", .metavar = "USER" },
{}
};
@@ -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