shared/options: convert mode boolean to an enum

This will make it easier to add new modes of operation later.
But I'm happy with how this came out — I think the mode setting
is nicer to read then the old bool.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2026-04-16 09:05:01 +02:00
parent 961e92854a
commit 6133fc0e1c
6 changed files with 63 additions and 52 deletions
+1 -1
View File
@@ -59,7 +59,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
assert(argc >= 0);
assert(argv);
OptionParser state = { argc, argv, /* stop_at_first_nonoption= */ true };
OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION };
const char *arg;
int r;
+1 -1
View File
@@ -586,7 +586,7 @@ static int parse_argv(int argc, char *argv[]) {
assert(argc >= 0);
assert(argv);
OptionParser state = { argc, argv, /* stop_at_first_nonoption= */ true };
OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION };
const Option *opt;
const char *arg;
+3 -1
View File
@@ -75,6 +75,8 @@ int option_parse(
/* Check and initialize */
if (state->optind == 0) {
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");
@@ -109,7 +111,7 @@ int option_parse(
/* Looks like we found an option parameter */
break;
if (state->stop_at_first_nonoption) {
if (state->mode == OPTION_PARSER_STOP_AT_FIRST_NONOPTION) {
state->parsing_stopped = true;
return 0;
}
+12 -3
View File
@@ -92,13 +92,22 @@ typedef struct Option {
extern const Option __start_SYSTEMD_OPTIONS[];
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,
/* Same as "+…" for getopt_long — only parse options before the first positional argument. */
OPTION_PARSER_STOP_AT_FIRST_NONOPTION,
_OPTION_PARSER_MODE_MAX,
} OptionParserMode;
typedef struct OptionParser {
/* Those three should stay first so that it's possible to initialize the struct as { argc, argv }
* or { argc, argv, true/false }. */
* or { argc, argv, mode }. */
int argc; /* The original argc. */
char **argv; /* The argv array, possibly reordered. */
bool stop_at_first_nonoption; /* Same as "+…" for getopt_long — only parse options before the first
* positional argument. */
OptionParserMode mode;
bool parsing_stopped; /* We processed "--" or an option that terminates option parsing. */
int optind; /* Position of the parameter being handled.
+45 -45
View File
@@ -15,7 +15,7 @@ static void test_option_parse_one(
const Option options[],
const Entry *entries,
char **remaining,
bool stop_at_first_nonoption) {
OptionParserMode mode) {
_cleanup_free_ char *joined = strv_join(argv, ", ");
log_debug("/* %s(%s) */", __func__, joined);
@@ -32,7 +32,7 @@ static void test_option_parse_one(
for (const Entry *e = entries; e && (e->long_code || e->short_code != 0); e++)
n_entries++;
OptionParser state = { argc, argv, stop_at_first_nonoption };
OptionParser state = { argc, argv, mode };
const Option *opt;
const char *arg;
for (int c; (c = option_parse(options, options + n_options, &state, &opt, &arg)) != 0; ) {
@@ -101,7 +101,7 @@ TEST(option_parse) {
options,
NULL,
NULL,
false);
OPTION_PARSER_NORMAL);
test_option_parse_one(STRV_MAKE("arg0",
"string1",
@@ -114,7 +114,7 @@ TEST(option_parse) {
"string2",
"string3",
"string4"),
false);
OPTION_PARSER_NORMAL);
test_option_parse_one(STRV_MAKE("arg0",
"--",
@@ -128,7 +128,7 @@ TEST(option_parse) {
"--help",
"-h",
"string4"),
false);
OPTION_PARSER_NORMAL);
test_option_parse_one(STRV_MAKE("arg0",
"string1",
@@ -142,7 +142,7 @@ TEST(option_parse) {
"string2",
"--",
"string4"),
false);
OPTION_PARSER_NORMAL);
test_option_parse_one(STRV_MAKE("arg0",
"string1",
@@ -156,7 +156,7 @@ TEST(option_parse) {
"string2",
"string3",
"string4"),
false);
OPTION_PARSER_NORMAL);
test_option_parse_one(STRV_MAKE("arg0",
"--help"),
@@ -166,7 +166,7 @@ TEST(option_parse) {
{}
},
NULL,
false);
OPTION_PARSER_NORMAL);
test_option_parse_one(STRV_MAKE("arg0",
"--help",
@@ -179,7 +179,7 @@ TEST(option_parse) {
},
STRV_MAKE("string1",
"--help"),
true);
OPTION_PARSER_STOP_AT_FIRST_NONOPTION);
test_option_parse_one(STRV_MAKE("arg0",
"-h"),
@@ -189,7 +189,7 @@ TEST(option_parse) {
{}
},
NULL,
false);
OPTION_PARSER_NORMAL);
test_option_parse_one(STRV_MAKE("arg0",
"--help",
@@ -206,7 +206,7 @@ TEST(option_parse) {
"string2",
"string3",
"string4"),
false);
OPTION_PARSER_NORMAL);
test_option_parse_one(STRV_MAKE("arg0",
"-h",
@@ -223,7 +223,7 @@ TEST(option_parse) {
"string2",
"string3",
"string4"),
false);
OPTION_PARSER_NORMAL);
test_option_parse_one(STRV_MAKE("arg0",
"string1",
@@ -240,7 +240,7 @@ TEST(option_parse) {
"string2",
"string3",
"string4"),
false);
OPTION_PARSER_NORMAL);
test_option_parse_one(STRV_MAKE("arg0",
"string1",
@@ -257,7 +257,7 @@ TEST(option_parse) {
"string2",
"string3",
"string4"),
false);
OPTION_PARSER_NORMAL);
test_option_parse_one(STRV_MAKE("arg0",
"string1",
@@ -274,7 +274,7 @@ TEST(option_parse) {
"string2",
"string3",
"string4"),
false);
OPTION_PARSER_NORMAL);
test_option_parse_one(STRV_MAKE("arg0",
"string1",
@@ -291,7 +291,7 @@ TEST(option_parse) {
"string2",
"string3",
"string4"),
false);
OPTION_PARSER_NORMAL);
test_option_parse_one(STRV_MAKE("arg0",
"--required1", "reqarg1"),
@@ -301,7 +301,7 @@ TEST(option_parse) {
{}
},
NULL,
false);
OPTION_PARSER_NORMAL);
test_option_parse_one(STRV_MAKE("arg0",
"-r", "reqarg1"),
@@ -311,7 +311,7 @@ TEST(option_parse) {
{}
},
NULL,
false);
OPTION_PARSER_NORMAL);
test_option_parse_one(STRV_MAKE("arg0",
"string1",
@@ -324,7 +324,7 @@ TEST(option_parse) {
},
STRV_MAKE("string1",
"string2"),
false);
OPTION_PARSER_NORMAL);
test_option_parse_one(STRV_MAKE("arg0",
"string1",
@@ -335,7 +335,7 @@ TEST(option_parse) {
STRV_MAKE("string1",
"string2",
"-r", "reqarg1"),
true);
OPTION_PARSER_STOP_AT_FIRST_NONOPTION);
test_option_parse_one(STRV_MAKE("arg0",
"--optional1=optarg1"),
@@ -345,7 +345,7 @@ TEST(option_parse) {
{}
},
NULL,
true);
OPTION_PARSER_STOP_AT_FIRST_NONOPTION);
test_option_parse_one(STRV_MAKE("arg0",
"--optional1", "string1"),
@@ -355,7 +355,7 @@ TEST(option_parse) {
{}
},
STRV_MAKE("string1"),
false);
OPTION_PARSER_NORMAL);
test_option_parse_one(STRV_MAKE("arg0",
"-ooptarg1"),
@@ -365,7 +365,7 @@ TEST(option_parse) {
{}
},
NULL,
false);
OPTION_PARSER_NORMAL);
test_option_parse_one(STRV_MAKE("arg0",
"-o", "string1"),
@@ -375,7 +375,7 @@ TEST(option_parse) {
{}
},
STRV_MAKE("string1"),
false);
OPTION_PARSER_NORMAL);
test_option_parse_one(STRV_MAKE("arg0",
"string1",
@@ -445,7 +445,7 @@ TEST(option_parse) {
"--help",
"--required1",
"--optional1"),
false);
OPTION_PARSER_NORMAL);
}
TEST(option_stops_parsing) {
@@ -469,7 +469,7 @@ TEST(option_stops_parsing) {
},
STRV_MAKE("--help",
"foo"),
false);
OPTION_PARSER_NORMAL);
/* Options before --exec are still parsed */
test_option_parse_one(STRV_MAKE("arg0",
@@ -485,7 +485,7 @@ TEST(option_stops_parsing) {
},
STRV_MAKE("--version",
"bar"),
false);
OPTION_PARSER_NORMAL);
/* --exec with no trailing args */
test_option_parse_one(STRV_MAKE("arg0",
@@ -496,7 +496,7 @@ TEST(option_stops_parsing) {
{}
},
NULL,
false);
OPTION_PARSER_NORMAL);
/* --exec after positional args */
test_option_parse_one(STRV_MAKE("arg0",
@@ -513,7 +513,7 @@ TEST(option_stops_parsing) {
"--help",
"--required",
"val"),
false);
OPTION_PARSER_NORMAL);
/* "--" 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
@@ -529,7 +529,7 @@ TEST(option_stops_parsing) {
{}
},
STRV_MAKE("--help"),
false);
OPTION_PARSER_NORMAL);
/* "--" before --exec: "--" terminates first, --exec is positional */
test_option_parse_one(STRV_MAKE("arg0",
@@ -540,7 +540,7 @@ TEST(option_stops_parsing) {
NULL,
STRV_MAKE("--exec",
"--help"),
false);
OPTION_PARSER_NORMAL);
/* Multiple options then --exec then more option-like args */
test_option_parse_one(STRV_MAKE("arg0",
@@ -559,7 +559,7 @@ TEST(option_stops_parsing) {
STRV_MAKE("-h",
"--required",
"val2"),
false);
OPTION_PARSER_NORMAL);
}
TEST(option_group_marker) {
@@ -584,7 +584,7 @@ TEST(option_group_marker) {
{}
},
NULL,
false);
OPTION_PARSER_NORMAL);
/* Check that group marker name is ignored */
test_option_parse_one(STRV_MAKE("arg0",
@@ -597,7 +597,7 @@ TEST(option_group_marker) {
{}
},
NULL,
false);
OPTION_PARSER_NORMAL);
/* Verify that the group marker is not mistaken for an option */
test_option_invalid_one(STRV_MAKE("arg0",
@@ -625,7 +625,7 @@ TEST(option_group_marker) {
{}
},
NULL,
false);
OPTION_PARSER_NORMAL);
/* Partial match with multiple candidates */
test_option_invalid_one(STRV_MAKE("arg0",
@@ -649,7 +649,7 @@ TEST(option_optional_arg) {
{}
},
NULL,
false);
OPTION_PARSER_NORMAL);
/* Long option without = does NOT consume the next arg */
test_option_parse_one(STRV_MAKE("arg0",
@@ -660,7 +660,7 @@ TEST(option_optional_arg) {
{}
},
STRV_MAKE("foo.txt"),
false);
OPTION_PARSER_NORMAL);
/* Short option with inline arg */
test_option_parse_one(STRV_MAKE("arg0",
@@ -671,7 +671,7 @@ TEST(option_optional_arg) {
{}
},
NULL,
false);
OPTION_PARSER_NORMAL);
/* Short option without inline arg does NOT consume the next arg */
test_option_parse_one(STRV_MAKE("arg0",
@@ -682,7 +682,7 @@ TEST(option_optional_arg) {
{}
},
STRV_MAKE("foo.txt"),
false);
OPTION_PARSER_NORMAL);
/* Optional arg option at end of argv */
test_option_parse_one(STRV_MAKE("arg0",
@@ -693,7 +693,7 @@ TEST(option_optional_arg) {
{}
},
NULL,
false);
OPTION_PARSER_NORMAL);
/* Mixed: optional arg with other options */
test_option_parse_one(STRV_MAKE("arg0",
@@ -708,7 +708,7 @@ TEST(option_optional_arg) {
{}
},
NULL,
false);
OPTION_PARSER_NORMAL);
/* Short combo: -ho (h then o with no arg) */
test_option_parse_one(STRV_MAKE("arg0",
@@ -720,7 +720,7 @@ TEST(option_optional_arg) {
{}
},
STRV_MAKE("pos1"),
false);
OPTION_PARSER_NORMAL);
/* Short combo: -hobar (h then o with inline arg "bar") */
test_option_parse_one(STRV_MAKE("arg0",
@@ -732,7 +732,7 @@ TEST(option_optional_arg) {
{}
},
NULL,
false);
OPTION_PARSER_NORMAL);
}
/* Test the OPTION, OPTION_LONG, OPTION_SHORT, OPTION_FULL, OPTION_GROUP macros
@@ -1110,7 +1110,7 @@ TEST(option_optional_arg_consume) {
{}
},
NULL,
/* stop_at_first_nonoption= */ false);
OPTION_PARSER_NORMAL);
/* --user without arg: next arg is an option, so no consumption */
test_option_parse_one(STRV_MAKE("arg0",
@@ -1123,7 +1123,7 @@ TEST(option_optional_arg_consume) {
{}
},
NULL,
/* stop_at_first_nonoption= */ false);
OPTION_PARSER_NORMAL);
/* --user without arg: next arg is positional (doesn't start with -).
* The option parser returns NULL for the arg. The caller would then
+1 -1
View File
@@ -319,7 +319,7 @@ static int parse_argv(int argc, char *argv[]) {
assert(argc >= 0);
assert(argv);
OptionParser state = { argc, argv, /* stop_at_first_nonoption= */ true };
OptionParser state = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION };
const Option *current;
const char *arg;