diff --git a/MAINTAINERS b/MAINTAINERS index 1505e74559..874cf2c0e5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -758,6 +758,7 @@ T: git https://gitlab.denx.de/u-boot/u-boot.git F: common/log* F: cmd/log.c F: doc/develop/logging.rst +F: lib/getopt.c F: test/log/ F: test/py/tests/test_log.py diff --git a/doc/api/getopt.rst b/doc/api/getopt.rst new file mode 100644 index 0000000000..773f79aeb6 --- /dev/null +++ b/doc/api/getopt.rst @@ -0,0 +1,8 @@ +.. SPDX-License-Identifier: GPL-2.0+ +.. Copyright (C) 2020 Sean Anderson + +Option Parsing +============== + +.. kernel-doc:: include/getopt.h + :internal: diff --git a/doc/api/index.rst b/doc/api/index.rst index 787b6778e5..ae4a1b6c63 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -8,6 +8,7 @@ U-Boot API documentation dfu efi + getopt linker_lists pinctrl rng diff --git a/include/getopt.h b/include/getopt.h new file mode 100644 index 0000000000..6f5811e64b --- /dev/null +++ b/include/getopt.h @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * getopt.h - a simple getopt(3) implementation. + * + * Copyright (C) 2020 Sean Anderson + * Copyright (c) 2007 Sascha Hauer , Pengutronix + */ + +#ifndef __GETOPT_H +#define __GETOPT_H + +/** + * struct getopt_state - Saved state across getopt() calls + */ +struct getopt_state { + /** + * @index: Index of the next unparsed argument of @argv. If getopt() has + * parsed all of @argv, then @index will equal @argc. + */ + int index; + /* private: */ + /** @arg_index: Index within the current argument */ + int arg_index; + union { + /* public: */ + /** + * @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. + */ + char *arg; + /* private: */ + }; +}; + +/** + * getopt_init_state() - Initialize a &struct getopt_state + * @gs: The state to initialize + * + * This must be called before using @gs with getopt(). + */ +void getopt_init_state(struct getopt_state *gs); + +int __getopt(struct getopt_state *gs, int argc, char *const argv[], + 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. + * + * An example invocation of getopt() might look like:: + * + * char *argv[] = { "program", "-cbx", "-a", "foo", "bar", 0 }; + * int opt, argc = ARRAY_SIZE(argv) - 1; + * struct getopt_state gs; + * + * 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); + * + * and would produce an output of:: + * + * opt = c, index = 1, arg = "" + * 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. + * + * 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. + */ +static inline int getopt(struct getopt_state *gs, int argc, + char *const argv[], const char *optstring) +{ + return __getopt(gs, argc, argv, optstring, false); +} + +/** + * getopt_silent() - Parse short command-line options silently + * @gs: State + * @argc: Argument count + * @argv: Argument list + * @optstring: Option specification + * + * Same as getopt(), except no error messages are printed. + */ +static inline int getopt_silent(struct getopt_state *gs, int argc, + char *const argv[], const char *optstring) +{ + return __getopt(gs, argc, argv, optstring, true); +} + +#endif /* __GETOPT_H */ diff --git a/lib/Kconfig b/lib/Kconfig index 37aae73a26..79651eaad1 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -550,6 +550,11 @@ config SPL_HEXDUMP This enables functions for printing dumps of binary data in SPL. +config GETOPT + bool "Enable getopt" + help + This enables functions for parsing command-line options. + config OF_LIBFDT bool "Enable the FDT library" default y if OF_CONTROL diff --git a/lib/Makefile b/lib/Makefile index 0cd7bea282..7c7fb9aae7 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -106,6 +106,7 @@ obj-y += string.o obj-y += tables_csum.o obj-y += time.o obj-y += hexdump.o +obj-$(CONFIG_GETOPT) += getopt.o obj-$(CONFIG_TRACE) += trace.o obj-$(CONFIG_LIB_UUID) += uuid.o obj-$(CONFIG_LIB_RAND) += rand.o diff --git a/lib/getopt.c b/lib/getopt.c new file mode 100644 index 0000000000..8b4515dc19 --- /dev/null +++ b/lib/getopt.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * getopt.c - a simple getopt(3) implementation. See getopt.h for explanation. + * + * Copyright (C) 2020 Sean Anderson + * Copyright (c) 2007 Sascha Hauer , Pengutronix + */ + +#define LOG_CATEGORY LOGC_CORE + +#include +#include +#include + +void getopt_init_state(struct getopt_state *gs) +{ + gs->index = 1; + gs->arg_index = 1; +} + +int __getopt(struct getopt_state *gs, int argc, char *const argv[], + const char *optstring, bool silent) +{ + char curopt; /* current option character */ + const char *curoptp; /* pointer to the current option in optstring */ + + while (1) { + log_debug("arg_index: %d index: %d\n", gs->arg_index, + gs->index); + + /* `--` indicates the end of options */ + if (gs->arg_index == 1 && argv[gs->index] && + !strcmp(argv[gs->index], "--")) { + gs->index++; + return -1; + } + + /* Out of arguments */ + if (gs->index >= argc) + return -1; + + /* Can't parse non-options */ + if (*argv[gs->index] != '-') + return -1; + + /* We have found an option */ + curopt = argv[gs->index][gs->arg_index]; + if (curopt) + break; + /* + * no more options in current argv[] element; try the next one + */ + gs->index++; + gs->arg_index = 1; + } + + /* look up current option in optstring */ + curoptp = strchr(optstring, curopt); + + if (!curoptp) { + if (!silent) + printf("%s: invalid option -- %c\n", argv[0], curopt); + gs->opt = curopt; + gs->arg_index++; + return '?'; + } + + if (*(curoptp + 1) != ':') { + /* option with no argument. Just return it */ + gs->arg = NULL; + gs->arg_index++; + return curopt; + } + + if (*(curoptp + 1) && *(curoptp + 2) == ':') { + /* optional argument */ + if (argv[gs->index][gs->arg_index + 1]) { + /* optional argument with directly following arg */ + gs->arg = argv[gs->index++] + gs->arg_index + 1; + gs->arg_index = 1; + return curopt; + } + if (gs->index + 1 == argc) { + /* We are at the last argv[] element */ + gs->arg = NULL; + gs->index++; + return curopt; + } + if (*argv[gs->index + 1] != '-') { + /* + * optional argument with arg in next argv[] element + */ + gs->index++; + gs->arg = argv[gs->index++]; + gs->arg_index = 1; + return curopt; + } + + /* no optional argument found */ + gs->arg = NULL; + gs->arg_index = 1; + gs->index++; + return curopt; + } + + if (argv[gs->index][gs->arg_index + 1]) { + /* required argument with directly following arg */ + gs->arg = argv[gs->index++] + gs->arg_index + 1; + gs->arg_index = 1; + return curopt; + } + + gs->index++; + gs->arg_index = 1; + + if (gs->index >= argc || argv[gs->index][0] == '-') { + if (!silent) + printf("option requires an argument -- %c\n", curopt); + gs->opt = curopt; + return ':'; + } + + gs->arg = argv[gs->index++]; + return curopt; +}