extern const Option __stop_SYSTEMD_OPTIONS[];
typedef struct OptionParser {
- /* Those two should stay first so that it's possible to initialize the struct as { argc, argv }. */
- int argc; /* The original argc. */
- char **argv; /* The argv array, possibly reordered. */
+ /* Those three should stay first so that it's possible to initialize the struct as { argc, argv }
+ * or { argc, argv, true/false }. */
+ 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. */
- 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.
- * 0 → we're not parsing short options. */
- int positional_offset; /* Offset to where positional parameters are. After processing has been
- * finished, all options and their args are to the left of this offset. */
- bool parsing_stopped; /* We processed "--" or an option that terminates option parsing. */
+ bool parsing_stopped; /* We processed "--" or an option that terminates option parsing. */
+ 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.
+ * 0 → we're not parsing short options. */
+ int positional_offset; /* Offset to where positional parameters are. After processing has been
+ * finished, all options and their args are to the left of this offset. */
} OptionParser;
int option_parse(
char **argv,
const Option options[],
const Entry *entries,
- char **remaining) {
+ char **remaining,
+ bool stop_at_first_nonoption) {
_cleanup_free_ char *joined = strv_join(argv, ", ");
log_debug("/* %s(%s) */", __func__, joined);
for (const Entry *e = entries; e && (e->long_code || e->short_code != 0); e++)
n_entries++;
- OptionParser state = { argc, argv };
+ OptionParser state = { argc, argv, stop_at_first_nonoption };
const Option *opt;
const char *arg;
for (int c; (c = option_parse(options, options + n_options, &state, &opt, &arg)) != 0; ) {
test_option_parse_one(STRV_MAKE("arg0"),
options,
NULL,
- NULL);
+ NULL,
+ false);
test_option_parse_one(STRV_MAKE("arg0",
"string1",
STRV_MAKE("string1",
"string2",
"string3",
- "string4"));
+ "string4"),
+ false);
test_option_parse_one(STRV_MAKE("arg0",
"--",
STRV_MAKE("string1",
"--help",
"-h",
- "string4"));
+ "string4"),
+ false);
test_option_parse_one(STRV_MAKE("arg0",
"string1",
STRV_MAKE("string1",
"string2",
"--",
- "string4"));
+ "string4"),
+ false);
test_option_parse_one(STRV_MAKE("arg0",
"string1",
STRV_MAKE("string1",
"string2",
"string3",
- "string4"));
+ "string4"),
+ false);
test_option_parse_one(STRV_MAKE("arg0",
"--help"),
{ "help" },
{}
},
- NULL);
+ NULL,
+ false);
+
+ test_option_parse_one(STRV_MAKE("arg0",
+ "--help",
+ "string1",
+ "--help"),
+ options,
+ (Entry[]) {
+ { "help" },
+ {}
+ },
+ STRV_MAKE("string1",
+ "--help"),
+ true);
test_option_parse_one(STRV_MAKE("arg0",
"-h"),
{ "help" },
{}
},
- NULL);
+ NULL,
+ false);
test_option_parse_one(STRV_MAKE("arg0",
"--help",
STRV_MAKE("string1",
"string2",
"string3",
- "string4"));
+ "string4"),
+ false);
test_option_parse_one(STRV_MAKE("arg0",
"-h",
STRV_MAKE("string1",
"string2",
"string3",
- "string4"));
+ "string4"),
+ false);
test_option_parse_one(STRV_MAKE("arg0",
"string1",
STRV_MAKE("string1",
"string2",
"string3",
- "string4"));
+ "string4"),
+ false);
test_option_parse_one(STRV_MAKE("arg0",
"string1",
STRV_MAKE("string1",
"string2",
"string3",
- "string4"));
+ "string4"),
+ false);
test_option_parse_one(STRV_MAKE("arg0",
"string1",
STRV_MAKE("string1",
"string2",
"string3",
- "string4"));
+ "string4"),
+ false);
test_option_parse_one(STRV_MAKE("arg0",
"string1",
STRV_MAKE("string1",
"string2",
"string3",
- "string4"));
+ "string4"),
+ false);
test_option_parse_one(STRV_MAKE("arg0",
"--required1", "reqarg1"),
{ "required1", "reqarg1" },
{}
},
- NULL);
+ NULL,
+ false);
test_option_parse_one(STRV_MAKE("arg0",
"-r", "reqarg1"),
{ "required1", "reqarg1" },
{}
},
- NULL);
+ NULL,
+ false);
test_option_parse_one(STRV_MAKE("arg0",
"string1",
{}
},
STRV_MAKE("string1",
- "string2"));
+ "string2"),
+ false);
+
+ test_option_parse_one(STRV_MAKE("arg0",
+ "string1",
+ "string2",
+ "-r", "reqarg1"),
+ options,
+ NULL,
+ STRV_MAKE("string1",
+ "string2",
+ "-r", "reqarg1"),
+ true);
test_option_parse_one(STRV_MAKE("arg0",
"--optional1=optarg1"),
{ "optional1", "optarg1" },
{}
},
- NULL);
+ NULL,
+ true);
test_option_parse_one(STRV_MAKE("arg0",
"--optional1", "string1"),
{ "optional1", NULL },
{}
},
- STRV_MAKE("string1"));
+ STRV_MAKE("string1"),
+ false);
test_option_parse_one(STRV_MAKE("arg0",
"-ooptarg1"),
{ "optional1", "optarg1" },
{}
},
- NULL);
+ NULL,
+ false);
test_option_parse_one(STRV_MAKE("arg0",
"-o", "string1"),
{ "optional1", NULL },
{}
},
- STRV_MAKE("string1"));
+ STRV_MAKE("string1"),
+ false);
test_option_parse_one(STRV_MAKE("arg0",
"string1",
"string7",
"--help",
"--required1",
- "--optional1"));
+ "--optional1"),
+ false);
}
TEST(option_stops_parsing) {
{}
},
STRV_MAKE("--help",
- "foo"));
+ "foo"),
+ false);
/* Options before --exec are still parsed */
test_option_parse_one(STRV_MAKE("arg0",
{}
},
STRV_MAKE("--version",
- "bar"));
+ "bar"),
+ false);
/* --exec with no trailing args */
test_option_parse_one(STRV_MAKE("arg0",
{ "exec" },
{}
},
- NULL);
+ NULL,
+ false);
/* --exec after positional args */
test_option_parse_one(STRV_MAKE("arg0",
STRV_MAKE("pos1",
"--help",
"--required",
- "val"));
+ "val"),
+ false);
/* "--" 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
{ "exec" },
{}
},
- STRV_MAKE("--help"));
+ STRV_MAKE("--help"),
+ false);
/* "--" before --exec: "--" terminates first, --exec is positional */
test_option_parse_one(STRV_MAKE("arg0",
options,
NULL,
STRV_MAKE("--exec",
- "--help"));
+ "--help"),
+ false);
/* Multiple options then --exec then more option-like args */
test_option_parse_one(STRV_MAKE("arg0",
},
STRV_MAKE("-h",
"--required",
- "val2"));
+ "val2"),
+ false);
}
TEST(option_group_marker) {
{ "debug" },
{}
},
- NULL);
+ NULL,
+ false);
/* Check that group marker name is ignored */
test_option_parse_one(STRV_MAKE("arg0",
{ "version" },
{}
},
- NULL);
+ NULL,
+ false);
/* Verify that the group marker is not mistaken for an option */
test_option_invalid_one(STRV_MAKE("arg0",
{ "Advance" },
{}
},
- NULL);
+ NULL,
+ false);
/* Partial match with multiple candidates */
test_option_invalid_one(STRV_MAKE("arg0",
{ "output", "foo.txt" },
{}
},
- NULL);
+ NULL,
+ false);
/* Long option without = does NOT consume the next arg */
test_option_parse_one(STRV_MAKE("arg0",
{ "output", NULL },
{}
},
- STRV_MAKE("foo.txt"));
+ STRV_MAKE("foo.txt"),
+ false);
/* Short option with inline arg */
test_option_parse_one(STRV_MAKE("arg0",
{ "output", "foo.txt" },
{}
},
- NULL);
+ NULL,
+ false);
/* Short option without inline arg does NOT consume the next arg */
test_option_parse_one(STRV_MAKE("arg0",
{ "output", NULL },
{}
},
- STRV_MAKE("foo.txt"));
+ STRV_MAKE("foo.txt"),
+ false);
/* Optional arg option at end of argv */
test_option_parse_one(STRV_MAKE("arg0",
{ "output", NULL },
{}
},
- NULL);
+ NULL,
+ false);
/* Mixed: optional arg with other options */
test_option_parse_one(STRV_MAKE("arg0",
{ "help" },
{}
},
- NULL);
+ NULL,
+ false);
/* Short combo: -ho (h then o with no arg) */
test_option_parse_one(STRV_MAKE("arg0",
{ "output", NULL },
{}
},
- STRV_MAKE("pos1"));
+ STRV_MAKE("pos1"),
+ false);
/* Short combo: -hobar (h then o with inline arg "bar") */
test_option_parse_one(STRV_MAKE("arg0",
{ "output", "bar" },
{}
},
- NULL);
+ NULL,
+ false);
}
/* Test the OPTION, OPTION_LONG, OPTION_SHORT, OPTION_FULL, OPTION_GROUP macros