Currently getopt() does not reorder its argv: it stops at the first non-option, which forces options to appear before positional arguments. This is not the usual way that arguments work - people expect options to be recognised wherever they appear on the command line, although some U-Boot commands are hard-wires for particular positions.
Restructure the parser to permute by default. Add argc and a writable args[CONFIG_SYS_MAXARGS + 1] buffer to struct getopt_state, plus a nonopts counter. The init function getopt_init_state() takes argc and argv, copies argv into args, and resets the parse position. Then getopt() and getopt_silent() take only the state and an optstring; argc and argv are read from the state. When getopt() encounters a non-option, it bubbles that argv element to the end of args and increments nonopts, then keeps scanning. The outer condition stops when index + nonopts reaches argc, so the parked non-options are never re-scanned. After parsing finishes, gs.index points at the first parked non-option, with gs.nonopts of them sitting at gs.args[gs.index .. argc - 1] Callers that need the previous POSIX-style 'stop at first non-option' behaviour can prefix optstring with '+'. This matches GNU getopt's convention and keeps to two public entry points (getopt() and getopt_silent()) instead of doubling them up for in-order variants. Update the two existing in-tree callers, cmd/bdinfo.c and cmd/log.c, to the new signatures. bdinfo has no positional arguments so it works the same either way; log filter-list/add/remove preserve their original semantics with the '+' prefix. Update test helper in test/lib/getopt.c too. Signed-off-by: Simon Glass <[email protected]> --- cmd/bdinfo.c | 4 +- cmd/log.c | 12 ++-- include/getopt.h | 146 +++++++++++++++++++++++----------------------- lib/getopt.c | 67 ++++++++++++++++----- test/lib/getopt.c | 6 +- 5 files changed, 135 insertions(+), 100 deletions(-) diff --git a/cmd/bdinfo.c b/cmd/bdinfo.c index ddf77303735..c46094c3ddf 100644 --- a/cmd/bdinfo.c +++ b/cmd/bdinfo.c @@ -188,8 +188,8 @@ int do_bdinfo(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) if (!CONFIG_IS_ENABLED(GETOPT) || argc == 1) return bdinfo_print_all(bd); - getopt_init_state(&gs); - while ((opt = getopt(&gs, argc, argv, "aem")) > 0) { + getopt_init_state(&gs, argc, argv); + while ((opt = getopt(&gs, "aem")) > 0) { switch (opt) { case 'a': return bdinfo_print_all(bd); diff --git a/cmd/log.c b/cmd/log.c index 64add6d8b5a..344f379d8cc 100644 --- a/cmd/log.c +++ b/cmd/log.c @@ -95,8 +95,8 @@ static int do_log_filter_list(struct cmd_tbl *cmdtp, int flag, int argc, struct log_filter *filt; struct log_device *ldev; - getopt_init_state(&gs); - while ((opt = getopt(&gs, argc, argv, "d:")) > 0) { + getopt_init_state(&gs, argc, argv); + while ((opt = getopt(&gs, "+d:")) > 0) { switch (opt) { case 'd': drv_name = gs.arg; @@ -157,8 +157,8 @@ static int do_log_filter_add(struct cmd_tbl *cmdtp, int flag, int argc, enum log_level_t level = LOGL_MAX; struct getopt_state gs; - getopt_init_state(&gs); - while ((opt = getopt(&gs, argc, argv, "Ac:d:Df:F:l:L:p")) > 0) { + getopt_init_state(&gs, argc, argv); + while ((opt = getopt(&gs, "+Ac:d:Df:F:l:L:p")) > 0) { switch (opt) { case 'A': #define do_type() do { \ @@ -250,8 +250,8 @@ static int do_log_filter_remove(struct cmd_tbl *cmdtp, int flag, int argc, const char *drv_name = "console"; struct getopt_state gs; - getopt_init_state(&gs); - while ((opt = getopt(&gs, argc, argv, "ad:")) > 0) { + getopt_init_state(&gs, argc, argv); + while ((opt = getopt(&gs, "+ad:")) > 0) { switch (opt) { case 'a': all = true; diff --git a/include/getopt.h b/include/getopt.h index 0cf7ee84d6f..5a9e7802e65 100644 --- a/include/getopt.h +++ b/include/getopt.h @@ -10,120 +10,120 @@ #define __GETOPT_H #include <stdbool.h> +#include <linux/kconfig.h> /** * struct getopt_state - Saved state across getopt() calls + * + * Initialise with getopt_init_state(); the embedded @args buffer holds a + * working copy of the argv passed to that initialiser. */ struct getopt_state { + /** @argc: Argument count, as passed to getopt_init_state() */ + int argc; + /** + * @args: Working copy of argv. getopt() reorders this in place: as + * options are consumed, non-options are bubbled to the end. + */ + char *args[CONFIG_SYS_MAXARGS + 1]; /** - * @index: Index of the next unparsed argument of @argv. If getopt() has - * parsed all of @argv, then @index will equal @argc. + * @index: Index of the next unparsed argument of @args. When + * parsing has finished, @index sits at the first parked + * non-option (or @argc if none). */ int index; - /** @arg_index: Index within the current argument */ + /** + * @arg_index: Offset of the next unparsed character within + * ``args[index]``. Lets the parser step through grouped short + * options like ``-abc`` (1, 2, 3...). Reset to 1 each time + * @index advances to the next argv slot. + */ int arg_index; + /** + * @nonopts: Number of non-option arguments parked at the tail + * of @args. The parser bubbles them down so it can keep scanning + * for options past them. + */ + int nonopts; union { /** - * @opt: Option being parsed when an error occurs. @opt is only - * valid when getopt() returns ``?`` or ``:``. + * @opt: Option being parsed when an error occurs. @opt is + * only valid when getopt() returns ``?`` or ``:``. */ int opt; /** - * @arg: The argument to an option, NULL if there is none. @arg - * is only valid when getopt() returns an option character. + * @arg: The argument to an option, NULL if there is none. + * @arg is only valid when getopt() returns an option + * character. */ char *arg; }; }; /** - * getopt_init_state() - Initialize a &struct getopt_state - * @gs: The state to initialize - * - * This must be called before using @gs with getopt(). + * getopt_init_state() - Initialise a &struct getopt_state from an argv + * @gs: The state to initialise + * @argc: Argument count + * @argv: Source argv. Copied into @gs->args; the original is not + * modified. @argc must not exceed ``CONFIG_SYS_MAXARGS``; + * excess arguments are silently dropped. */ -void getopt_init_state(struct getopt_state *gs); +void getopt_init_state(struct getopt_state *gs, int argc, + char *const argv[]); -int __getopt(struct getopt_state *gs, int argc, char *const argv[], - const char *optstring, bool silent); +int __getopt(struct getopt_state *gs, const char *optstring, bool silent); /** * getopt() - Parse short command-line options - * @gs: Internal state and out-of-band return arguments. This must be - * initialized with getopt_init_context() beforehand. - * @argc: Number of arguments, not including the %NULL terminator - * @argv: Argument list, terminated by %NULL - * @optstring: Option specification, as described below - * - * getopt() parses short options. Short options are single characters. They may - * be followed by a required argument or an optional argument. Arguments to - * options may occur in the same argument as an option (like ``-larg``), or - * in the following argument (like ``-l arg``). An argument containing - * options begins with a ``-``. If an option expects no arguments, then it may - * be immediately followed by another option (like ``ls -alR``). - * - * @optstring is a list of accepted options. If an option is followed by ``:`` - * in @optstring, then it expects a mandatory argument. If an option is followed - * by ``::`` in @optstring, it expects an optional argument. @gs.arg points - * to the argument, if one is parsed. - * - * getopt() stops parsing options when it encounters the first non-option - * argument, when it encounters the argument ``--``, or when it runs out of - * arguments. For example, in ``ls -l foo -R``, option parsing will stop when - * getopt() encounters ``foo``, if ``l`` does not expect an argument. However, - * the whole list of arguments would be parsed if ``l`` expects an argument. + * @gs: State, initialised with getopt_init_state() + * @optstring: Option specification (see getopt(3)) * - * An example invocation of getopt() might look like:: + * Parses the next short option from @gs. By default, getopt() permutes + * the working argv: when it hits a non-option, it bubbles that argv + * element to the end and keeps scanning, so options can appear + * anywhere on the command line. After parsing finishes (returns -1), + * @gs.index sits at the first parked non-option, and there are + * @gs.nonopts of them at @gs.args[gs.index..argc-1]. * - * char *argv[] = { "program", "-cbx", "-a", "foo", "bar", 0 }; - * int opt, argc = ARRAY_SIZE(argv) - 1; - * struct getopt_state gs; + * If @optstring begins with ``+``, getopt() instead stops at the + * first non-option (POSIX ``getopt`` behaviour). Use this when the + * command's grammar requires options before positionals, or when the + * option string includes optional arguments (``::``) whose adjacency + * to a following positional would be ambiguous under permutation. * - * getopt_init_state(&gs); - * while ((opt = getopt(&gs, argc, argv, "a::b:c")) != -1) - * printf("opt = %c, index = %d, arg = \"%s\"\n", opt, gs.index, gs.arg); - * printf("%d argument(s) left\n", argc - gs.index); + * In @optstring proper, ``x`` is a flag, ``x:`` requires an argument, + * and ``x::`` takes an optional argument. The argument is delivered + * in @gs.arg. * - * and would produce an output of:: - * - * opt = c, index = 1, arg = "<NULL>" - * opt = b, index = 2, arg = "x" - * opt = a, index = 4, arg = "foo" - * 1 argument(s) left - * - * For further information, refer to the getopt(3) man page. + * A literal ``--`` argument terminates option scanning; the parser + * advances past it and returns -1. * * Return: - * * An option character if an option is found. @gs.arg is set to the - * argument if there is one, otherwise it is set to ``NULL``. - * * ``-1`` if there are no more options, if a non-option argument is - * encountered, or if an ``--`` argument is encountered. - * * ``'?'`` if we encounter an option not in @optstring. @gs.opt is set to - * the unknown option. - * * ``':'`` if an argument is required, but no argument follows the - * option. @gs.opt is set to the option missing its argument. - * - * @gs.index is always set to the index of the next unparsed argument in @argv. + * * An option character if an option is found. @gs.arg is set to + * the argument if there is one, otherwise NULL. + * * ``-1`` if there are no more options. + * * ``'?'`` for an option not in @optstring; @gs.opt is the unknown + * option character. + * * ``':'`` for an option missing its required argument; @gs.opt is + * the option character. */ -static inline int getopt(struct getopt_state *gs, int argc, - char *const argv[], const char *optstring) +static inline int getopt(struct getopt_state *gs, const char *optstring) { - return __getopt(gs, argc, argv, optstring, false); + return __getopt(gs, optstring, false); } /** - * getopt_silent() - Parse short command-line options silently + * getopt_silent() - Parse short command-line options without errors * @gs: State - * @argc: Argument count - * @argv: Argument list * @optstring: Option specification * - * Same as getopt(), except no error messages are printed. + * Same as getopt() but does not print error messages for unknown + * options or missing arguments. */ -static inline int getopt_silent(struct getopt_state *gs, int argc, - char *const argv[], const char *optstring) +static inline int getopt_silent(struct getopt_state *gs, + const char *optstring) { - return __getopt(gs, argc, argv, optstring, true); + return __getopt(gs, optstring, true); } #endif /* __GETOPT_H */ diff --git a/lib/getopt.c b/lib/getopt.c index e9175e2fff4..70adb2d0faf 100644 --- a/lib/getopt.c +++ b/lib/getopt.c @@ -10,37 +10,71 @@ #include <getopt.h> #include <log.h> +#include <linux/kernel.h> #include <linux/string.h> -void getopt_init_state(struct getopt_state *gs) +void getopt_init_state(struct getopt_state *gs, int argc, char *const argv[]) { + int max = ARRAY_SIZE(gs->args) - 1; + + if (argc > max) + argc = max; + + gs->argc = argc; + memcpy(gs->args, argv, (argc + 1) * sizeof(*gs->args)); + gs->args[argc] = NULL; gs->index = 1; gs->arg_index = 1; + gs->nonopts = 0; } -int __getopt(struct getopt_state *gs, int argc, char *const argv[], - const char *optstring, bool silent) +int __getopt(struct getopt_state *gs, const char *optstring, bool silent) { - char curopt; /* current option character */ - const char *curoptp; /* pointer to the current option in optstring */ + char curopt; /* current option character */ + const char *curoptp; /* pointer to the current option in optstring */ + bool stop_nonopt = false; + char **argv = gs->args; + int argc = gs->argc; + + if (*optstring == '+') { + stop_nonopt = true; + optstring++; + } while (1) { - log_debug("arg_index: %d index: %d\n", gs->arg_index, - gs->index); + log_debug("arg_index: %d index: %d nonopts: %d\n", + gs->arg_index, gs->index, gs->nonopts); /* `--` indicates the end of options */ - if (gs->arg_index == 1 && argv[gs->index] && + if (gs->arg_index == 1 && gs->index < argc && !strcmp(argv[gs->index], "--")) { gs->index++; return -1; } - /* Out of arguments */ - if (gs->index >= argc) - return -1; + /* + * Bubble non-options to the end so we can keep scanning for + * options past them. In '+' mode (POSIX), stop at the first + * non-option instead. + */ + while (gs->arg_index == 1 && + gs->index + gs->nonopts < argc && + *argv[gs->index] != '-') { + char *tmp; + int i; + + if (stop_nonopt) + return -1; + + tmp = argv[gs->index]; + gs->nonopts++; + for (i = gs->index; i + 1 < argc; i++) + argv[i] = argv[i + 1]; + argv[argc - 1] = tmp; + } - /* Can't parse non-options */ - if (*argv[gs->index] != '-') + /* Out of options to scan */ + if (gs->index + gs->nonopts >= argc) return -1; /* We have found an option */ @@ -48,7 +82,8 @@ int __getopt(struct getopt_state *gs, int argc, char *const argv[], if (curopt) break; /* - * no more options in current argv[] element; try the next one + * No more options in current argv[] element; advance to the + * next one */ gs->index++; gs->arg_index = 1; @@ -80,7 +115,7 @@ int __getopt(struct getopt_state *gs, int argc, char *const argv[], gs->arg_index = 1; return curopt; } - if (gs->index + 1 == argc) { + if (gs->index + gs->nonopts + 1 == argc) { /* We are at the last argv[] element */ gs->arg = NULL; gs->index++; @@ -113,7 +148,7 @@ int __getopt(struct getopt_state *gs, int argc, char *const argv[], gs->index++; gs->arg_index = 1; - if (gs->index >= argc || argv[gs->index][0] == '-') { + if (gs->index + gs->nonopts >= argc || argv[gs->index][0] == '-') { if (!silent) printf("option requires an argument -- %c\n", curopt); gs->opt = curopt; diff --git a/test/lib/getopt.c b/test/lib/getopt.c index 388a076200b..6b384eb016a 100644 --- a/test/lib/getopt.c +++ b/test/lib/getopt.c @@ -18,9 +18,9 @@ static int do_test_getopt(struct unit_test_state *uts, int line, { int opt; - getopt_init_state(gs); + getopt_init_state(gs, args, argv); for (int i = 0; i < expected_count; i++) { - opt = getopt_silent(gs, args, argv, optstring); + opt = getopt_silent(gs, optstring); if (expected[i] != opt) { /* * Fudge the line number so we can tell which test @@ -34,7 +34,7 @@ static int do_test_getopt(struct unit_test_state *uts, int line, } } - opt = getopt_silent(gs, args, argv, optstring); + opt = getopt_silent(gs, optstring); if (opt != -1) { ut_failf(uts, __FILE__, line, __func__, "getopt() != -1", -- 2.43.0

