public inbox for u-boot@lists.denx.de
 help / color / mirror / Atom feed
From: Sean Anderson <seanga2@gmail.com>
To: u-boot@lists.denx.de
Subject: [PATCH v2 17/22] lib: Add getopt
Date: Sat, 10 Oct 2020 15:43:42 -0400	[thread overview]
Message-ID: <20201010194347.90096-18-seanga2@gmail.com> (raw)
In-Reply-To: <20201010194347.90096-1-seanga2@gmail.com>

Some commands can get very unweildy if they have too many positional
arguments. Adding options makes them easier to read, remember, and
understand.

This implementation of getopt has been taken from barebox, which has had
option support for quite a while. I have made a few modifications to their
version, such as the removal of opterr in favor of a separate getopt_silent
function. In addition, I have moved all global variables into struct
getopt_context.

The getopt from barebox also re-orders the arguments passed to it so that
non-options are placed last. This allows users to specify options anywhere.
For example, `ls -l foo/ -R` would be re-ordered to `ls -l -R foo/` as
getopt parsed the options. However, this feature conflicts with the const
argv in cmd_tbl->cmd. This was originally added in 54841ab50c ("Make sure
that argv[] argument pointers are not modified."). The reason stated in
that commit is that hush requires argv to stay unmodified. Has this
situation changed? Barebox also uses hush, and does not have this problem.
Perhaps we could use their fix?

I have assigned maintenance of getopt to Simon Glass, as it is currently
only used by the log command. I would also be fine maintaining it.

Signed-off-by: Sean Anderson <seanga2@gmail.com>
---

Changes in v2:
- Expand documentation of getopt() to include examples
- Remove opt prefix from getopt_state members

 MAINTAINERS        |   1 +
 doc/api/getopt.rst |   8 +++
 doc/api/index.rst  |   1 +
 include/getopt.h   | 130 +++++++++++++++++++++++++++++++++++++++++++++
 lib/Kconfig        |   5 ++
 lib/Makefile       |   1 +
 lib/getopt.c       | 125 +++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 271 insertions(+)
 create mode 100644 doc/api/getopt.rst
 create mode 100644 include/getopt.h
 create mode 100644 lib/getopt.c

diff --git a/MAINTAINERS b/MAINTAINERS
index fb9ba37984..0f8e1f6d4c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -747,6 +747,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 <seanga2@gmail.com>
+
+Option Parsing
+==============
+
+.. kernel-doc:: include/getopt.h
+   :internal:
diff --git a/doc/api/index.rst b/doc/api/index.rst
index 1c261bcb73..d90e70e16a 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 <seanga2@gmail.com>
+ * Copyright (c) 2007 Sascha Hauer <s.hauer@pengutronix.de>, 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 = "<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.
+ *
+ * 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 8efb154f73..a3346eee04 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -542,6 +542,11 @@ config HEXDUMP
 	help
 	  This enables functions for printing dumps of binary data.
 
+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 <seanga2@gmail.com>
+ * Copyright (c) 2007 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
+ */
+
+#define LOG_CATEGORY LOGC_CORE
+
+#include <common.h>
+#include <getopt.h>
+#include <log.h>
+
+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;
+}
-- 
2.28.0

  parent reply	other threads:[~2020-10-10 19:43 UTC|newest]

Thread overview: 43+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-10-10 19:43 [PATCH v2 00/22] log: Add commands for manipulating filters Sean Anderson
2020-10-10 19:43 ` [PATCH v2 01/22] log: Fix missing negation of ENOMEM Sean Anderson
2020-10-10 19:43 ` [PATCH v2 02/22] log: Fix incorrect documentation of log_filter.cat_list Sean Anderson
2020-10-10 19:43 ` [PATCH v2 03/22] log: Add additional const qualifier to arrays Sean Anderson
2020-10-12  3:35   ` Simon Glass
2020-10-10 19:43 ` [PATCH v2 04/22] log: Add new category names to log_cat_name Sean Anderson
2020-10-12  3:35   ` Simon Glass
2020-10-10 19:43 ` [PATCH v2 05/22] log: Use CONFIG_IS_ENABLED() for LOG_TEST Sean Anderson
2020-10-12  3:35   ` Simon Glass
2020-10-12 16:56     ` Sean Anderson
2020-10-14 13:07       ` Simon Glass
2020-10-10 19:43 ` [PATCH v2 06/22] log: Expose some helper functions Sean Anderson
2020-10-12  3:35   ` Simon Glass
2020-10-10 19:43 ` [PATCH v2 07/22] log: Add function to create a filter with flags Sean Anderson
2020-10-12  3:35   ` Simon Glass
2020-10-10 19:43 ` [PATCH v2 08/22] log: Add filter flag to deny on match Sean Anderson
2020-10-12  3:35   ` Simon Glass
2020-10-10 19:43 ` [PATCH v2 09/22] test: Add tests for LOGFF_DENY Sean Anderson
2020-10-12  3:35   ` Simon Glass
2020-10-10 19:43 ` [PATCH v2 10/22] log: Add filter flag to match greater than a log level Sean Anderson
2020-10-12  3:35   ` Simon Glass
2020-10-10 19:43 ` [PATCH v2 11/22] test: Add test for LOGFF_MIN Sean Anderson
2020-10-12  3:35   ` Simon Glass
2020-10-10 19:43 ` [PATCH v2 12/22] cmd: log: Use sub-commands for log Sean Anderson
2020-10-12  3:35   ` Simon Glass
2020-10-10 19:43 ` [PATCH v2 13/22] cmd: log: Split off log level parsing Sean Anderson
2020-10-12  3:35   ` Simon Glass
2020-10-10 19:43 ` [PATCH v2 14/22] cmd: log: Move log test to end of help string Sean Anderson
2020-10-12  3:35   ` Simon Glass
2020-10-10 19:43 ` [PATCH v2 15/22] cmd: log: Add commands to list categories and drivers Sean Anderson
2020-10-12  3:35   ` Simon Glass
2020-10-10 19:43 ` [PATCH v2 16/22] cmd: log: Make "log level" print all log levels Sean Anderson
2020-10-12  3:35   ` Simon Glass
2020-10-10 19:43 ` Sean Anderson [this message]
2020-10-10 19:43 ` [PATCH v2 18/22] test: Add a test for getopt Sean Anderson
2020-10-10 19:43 ` [PATCH v2 19/22] cmd: log: Add commands to manipulate filters Sean Anderson
2020-10-12  3:35   ` Simon Glass
2020-10-10 19:43 ` [PATCH v2 20/22] test: Add a test for log filter-* Sean Anderson
2020-10-12  3:35   ` Simon Glass
2020-10-10 19:43 ` [PATCH v2 21/22] doc: Add log kerneldocs to documentation Sean Anderson
2020-10-12  3:35   ` Simon Glass
2020-10-10 19:43 ` [PATCH v2 22/22] doc: Update logging documentation Sean Anderson
2020-10-12  3:35   ` Simon Glass

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20201010194347.90096-18-seanga2@gmail.com \
    --to=seanga2@gmail.com \
    --cc=u-boot@lists.denx.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox