sandbox: add getopt support

This adds simple command-line parsing to sandbox. The idea is that it
sets up the state with options provided, and this state can then be
queried later, as needed.

New flags are declared with the SB_CMDLINE_OPT_SHORT helper macro,
pointers are automatically gathered up in a special section, and
then the core code takes care of gathering them up and processing
at runtime.  This way there is no central place where we have to
store a list of flags with ifdefs.

Signed-off-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Mike Frysinger <vapier@gentoo.org>
diff --git a/arch/sandbox/cpu/os.c b/arch/sandbox/cpu/os.c
index cb469e0..36637af 100644
--- a/arch/sandbox/cpu/os.c
+++ b/arch/sandbox/cpu/os.c
@@ -21,6 +21,7 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <getopt.h>
 #include <stdlib.h>
 #include <termios.h>
 #include <time.h>
@@ -31,6 +32,9 @@
 #include <sys/types.h>
 #include <linux/types.h>
 
+#include <asm/getopt.h>
+#include <asm/sections.h>
+#include <asm/state.h>
 #include <os.h>
 
 /* Operating System Interface */
@@ -155,3 +159,97 @@
 	return tv.tv_sec * 1000000000ULL + tv.tv_usec * 1000;
 #endif
 }
+
+static char *short_opts;
+static struct option *long_opts;
+
+int os_parse_args(struct sandbox_state *state, int argc, char *argv[])
+{
+	struct sb_cmdline_option **sb_opt = __u_boot_sandbox_option_start;
+	size_t num_options = __u_boot_sandbox_option_count();
+	size_t i;
+
+	int hidden_short_opt;
+	size_t si;
+
+	int c;
+
+	if (short_opts || long_opts)
+		return 1;
+
+	state->argc = argc;
+	state->argv = argv;
+
+	/* dynamically construct the arguments to the system getopt_long */
+	short_opts = os_malloc(sizeof(*short_opts) * num_options * 2 + 1);
+	long_opts = os_malloc(sizeof(*long_opts) * num_options);
+	if (!short_opts || !long_opts)
+		return 1;
+
+	/*
+	 * getopt_long requires "val" to be unique (since that is what the
+	 * func returns), so generate unique values automatically for flags
+	 * that don't have a short option.  pick 0x100 as that is above the
+	 * single byte range (where ASCII/ISO-XXXX-X charsets live).
+	 */
+	hidden_short_opt = 0x100;
+	si = 0;
+	for (i = 0; i < num_options; ++i) {
+		long_opts[i].name = sb_opt[i]->flag;
+		long_opts[i].has_arg = sb_opt[i]->has_arg ?
+			required_argument : no_argument;
+		long_opts[i].flag = NULL;
+
+		if (sb_opt[i]->flag_short) {
+			short_opts[si++] = long_opts[i].val = sb_opt[i]->flag_short;
+			if (long_opts[i].has_arg == required_argument)
+				short_opts[si++] = ':';
+		} else
+			long_opts[i].val = sb_opt[i]->flag_short = hidden_short_opt++;
+	}
+	short_opts[si] = '\0';
+
+	/* we need to handle output ourselves since u-boot provides printf */
+	opterr = 0;
+
+	/*
+	 * walk all of the options the user gave us on the command line,
+	 * figure out what u-boot option structure they belong to (via
+	 * the unique short val key), and call the appropriate callback.
+	 */
+	while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
+		for (i = 0; i < num_options; ++i) {
+			if (sb_opt[i]->flag_short == c) {
+				if (sb_opt[i]->callback(state, optarg)) {
+					state->parse_err = sb_opt[i]->flag;
+					return 0;
+				}
+				break;
+			}
+		}
+		if (i == num_options) {
+			/*
+			 * store the faulting flag for later display.  we have to
+			 * store the flag itself as the getopt parsing itself is
+			 * tricky: need to handle the following flags (assume all
+			 * of the below are unknown):
+			 *   -a        optopt='a' optind=<next>
+			 *   -abbbb    optopt='a' optind=<this>
+			 *   -aaaaa    optopt='a' optind=<this>
+			 *   --a       optopt=0   optind=<this>
+			 * as you can see, it is impossible to determine the exact
+			 * faulting flag without doing the parsing ourselves, so
+			 * we just report the specific flag that failed.
+			 */
+			if (optopt) {
+				static char parse_err[3] = { '-', 0, '\0', };
+				parse_err[1] = optopt;
+				state->parse_err = parse_err;
+			} else
+				state->parse_err = argv[optind - 1];
+			break;
+		}
+	}
+
+	return 0;
+}
diff --git a/arch/sandbox/cpu/start.c b/arch/sandbox/cpu/start.c
index 4a84486..6c3e8eb 100644
--- a/arch/sandbox/cpu/start.c
+++ b/arch/sandbox/cpu/start.c
@@ -20,21 +20,109 @@
  */
 
 #include <common.h>
+#include <asm/getopt.h>
+#include <asm/sections.h>
 #include <asm/state.h>
 
+#include <os.h>
+
+int sandbox_early_getopt_check(void)
+{
+	struct sandbox_state *state = state_get_current();
+	struct sb_cmdline_option **sb_opt = __u_boot_sandbox_option_start;
+	size_t num_options = __u_boot_sandbox_option_count();
+	size_t i;
+	int max_arg_len, max_noarg_len;
+
+	/* parse_err will be a string of the faulting option */
+	if (!state->parse_err)
+		return 0;
+
+	if (strcmp(state->parse_err, "help")) {
+		printf("u-boot: error: failed while parsing option: %s\n"
+			"\ttry running with --help for more information.\n",
+			state->parse_err);
+		os_exit(1);
+	}
+
+	printf(
+		"u-boot, a command line test interface to U-Boot\n\n"
+		"Usage: u-boot [options]\n"
+		"Options:\n");
+
+	max_arg_len = 0;
+	for (i = 0; i < num_options; ++i)
+		max_arg_len = max(strlen(sb_opt[i]->flag), max_arg_len);
+	max_noarg_len = max_arg_len + 7;
+
+	for (i = 0; i < num_options; ++i) {
+		struct sb_cmdline_option *opt = sb_opt[i];
+
+		/* first output the short flag if it has one */
+		if (opt->flag_short >= 0x100)
+			printf("      ");
+		else
+			printf("  -%c, ", opt->flag_short);
+
+		/* then the long flag */
+		if (opt->has_arg)
+			printf("--%-*s", max_noarg_len, opt->flag);
+		else
+			printf("--%-*s <arg> ", max_arg_len, opt->flag);
+
+		/* finally the help text */
+		printf("  %s\n", opt->help);
+	}
+
+	os_exit(0);
+}
+
+static int sb_cmdline_cb_help(struct sandbox_state *state, const char *arg)
+{
+	/* just flag to sandbox_early_getopt_check to show usage */
+	return 1;
+}
+SB_CMDLINE_OPT_SHORT(help, 'h', 0, "Display help");
+
 int sandbox_main_loop_init(void)
 {
+	struct sandbox_state *state = state_get_current();
+
+	/* Execute command if required */
+	if (state->cmd) {
+		/* TODO: redo this when cmd tidy-up series lands */
+#ifdef CONFIG_SYS_HUSH_PARSER
+		run_command(state->cmd, 0);
+#else
+		parse_string_outer(state->cmd, FLAG_PARSE_SEMICOLON |
+				    FLAG_EXIT_FROM_LOOP);
+#endif
+		os_exit(state->exit_type);
+	}
+
 	return 0;
 }
 
+static int sb_cmdline_cb_command(struct sandbox_state *state, const char *arg)
+{
+	state->cmd = arg;
+	return 0;
+}
+SB_CMDLINE_OPT_SHORT(command, 'c', 1, "Execute U-Boot command");
+
 int main(int argc, char *argv[])
 {
+	struct sandbox_state *state;
 	int err;
 
 	err = state_init();
 	if (err)
 		return err;
 
+	state = state_get_current();
+	if (os_parse_args(state, argc, argv))
+		return 1;
+
 	/*
 	 * Do pre- and post-relocation init, then start up U-Boot. This will
 	 * never return.
diff --git a/arch/sandbox/cpu/u-boot.lds b/arch/sandbox/cpu/u-boot.lds
index 0c56aa7..9960138 100644
--- a/arch/sandbox/cpu/u-boot.lds
+++ b/arch/sandbox/cpu/u-boot.lds
@@ -28,6 +28,10 @@
 	_u_boot_cmd : { *(.u_boot_cmd) }
 	__u_boot_cmd_end = .;
 
+	__u_boot_sandbox_option_start = .;
+	_u_boot_sandbox_getopt : { *(.u_boot_sandbox_getopt) }
+	__u_boot_sandbox_option_end = .;
+
 	__bss_start = .;
 }
 
diff --git a/arch/sandbox/include/asm/getopt.h b/arch/sandbox/include/asm/getopt.h
new file mode 100644
index 0000000..685883c
--- /dev/null
+++ b/arch/sandbox/include/asm/getopt.h
@@ -0,0 +1,71 @@
+/*
+ * Code for setting up command line flags like `./u-boot --help`
+ *
+ * Copyright (c) 2011 The Chromium OS Authors.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#ifndef __SANDBOX_GETOPT_H
+#define __SANDBOX_GETOPT_H
+
+struct sandbox_state;
+
+/*
+ * Internal structure for storing details about the flag.
+ * Most people should not have to dig around in this as
+ * it only gets parsed by the core sandbox code.  End
+ * consumer code should focus on the macros below and
+ * the callback function.
+ */
+struct sb_cmdline_option {
+	/* The long flag name: "help" for "--help" */
+	const char *flag;
+	/* The (optional) short flag name: "h" for "-h" */
+	int flag_short;
+	/* The help string shown to the user when processing --help */
+	const char *help;
+	/* Whether this flag takes an argument */
+	int has_arg;
+	/* Callback into the end consumer code with the option */
+	int (*callback)(struct sandbox_state *state, const char *opt);
+};
+
+/*
+ * Internal macro to expand the lower macros into the necessary
+ * magic junk that makes this all work.
+ */
+#define _SB_CMDLINE_OPT(f, s, ha, h) \
+	static struct sb_cmdline_option sb_cmdline_option_##f = { \
+		.flag = #f, \
+		.flag_short = s, \
+		.help = h, \
+		.has_arg = ha, \
+		.callback = sb_cmdline_cb_##f, \
+	}; \
+	/* Ppointer to the struct in a special section for the linker script */ \
+	static __attribute__((section(".u_boot_sandbox_getopt"), used)) \
+		struct sb_cmdline_option *sb_cmdline_option_##f##_ptr = \
+		&sb_cmdline_option_##f
+
+/**
+ * Macros for end code to declare new command line flags.
+ *
+ * @param f   The long flag name e.g. help
+ * @param ha  Does the flag have an argument e.g. 0/1
+ * @param h   The help string displayed when showing --help
+ *
+ * This invocation:
+ *   SB_CMDLINE_OPT(foo, 0, "The foo arg");
+ * Will create a new flag named "--foo" (no short option) that takes
+ * no argument.  If the user specifies "--foo", then the callback func
+ * sb_cmdline_cb_foo() will automatically be called.
+ */
+#define SB_CMDLINE_OPT(f, ha, h) _SB_CMDLINE_OPT(f, 0, ha, h)
+/*
+ * Same as above, but @s is used to specify a short flag e.g.
+ *   SB_CMDLINE_OPT(foo, 'f', 0, "The foo arg");
+ */
+#define SB_CMDLINE_OPT_SHORT(f, s, ha, h) _SB_CMDLINE_OPT(f, s, ha, h)
+
+#endif
diff --git a/arch/sandbox/include/asm/sections.h b/arch/sandbox/include/asm/sections.h
new file mode 100644
index 0000000..eafce7d
--- /dev/null
+++ b/arch/sandbox/include/asm/sections.h
@@ -0,0 +1,22 @@
+/*
+ * decls for symbols defined in the linker script
+ *
+ * Copyright (c) 2012 The Chromium OS Authors.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#ifndef __SANDBOX_SECTIONS_H
+#define __SANDBOX_SECTIONS_H
+
+struct sb_cmdline_option;
+
+extern struct sb_cmdline_option *__u_boot_sandbox_option_start[],
+	*__u_boot_sandbox_option_end[];
+
+static inline size_t __u_boot_sandbox_option_count(void)
+{
+	return __u_boot_sandbox_option_end - __u_boot_sandbox_option_start;
+}
+
+#endif
diff --git a/arch/sandbox/include/asm/state.h b/arch/sandbox/include/asm/state.h
index 5b34e94..2b62b46 100644
--- a/arch/sandbox/include/asm/state.h
+++ b/arch/sandbox/include/asm/state.h
@@ -22,6 +22,8 @@
 #ifndef __SANDBOX_STATE_H
 #define __SANDBOX_STATE_H
 
+#include <config.h>
+
 /* How we exited U-Boot */
 enum exit_type_id {
 	STATE_EXIT_NORMAL,
@@ -33,6 +35,9 @@
 struct sandbox_state {
 	const char *cmd;		/* Command to execute */
 	enum exit_type_id exit_type;	/* How we exited U-Boot */
+	const char *parse_err;		/* Error to report from parsing */
+	int argc;			/* Program arguments */
+	char **argv;
 };
 
 /**
diff --git a/arch/sandbox/include/asm/u-boot-sandbox.h b/arch/sandbox/include/asm/u-boot-sandbox.h
index 99e950b..50bf8c6 100644
--- a/arch/sandbox/include/asm/u-boot-sandbox.h
+++ b/arch/sandbox/include/asm/u-boot-sandbox.h
@@ -36,6 +36,7 @@
 int dram_init(void);
 
 /* start.c */
+int sandbox_early_getopt_check(void);
 int sandbox_main_loop_init(void);
 
 #endif	/* _U_BOOT_SANDBOX_H_ */
diff --git a/arch/sandbox/lib/board.c b/arch/sandbox/lib/board.c
index 25a8d02..306d1ec 100644
--- a/arch/sandbox/lib/board.c
+++ b/arch/sandbox/lib/board.c
@@ -134,6 +134,7 @@
 	env_init,		/* initialize environment */
 	serial_init,		/* serial communications setup */
 	console_init_f,		/* stage 1 init of console */
+	sandbox_early_getopt_check,	/* process command line flags (err/help) */
 	display_banner,		/* say that we are here */
 #if defined(CONFIG_DISPLAY_CPUINFO)
 	print_cpuinfo,		/* display cpu info (and speed) */
diff --git a/include/os.h b/include/os.h
index 6b7ee47..45729c1 100644
--- a/include/os.h
+++ b/include/os.h
@@ -27,6 +27,8 @@
 #ifndef __OS_H__
 #define __OS_H__
 
+struct sandbox_state;
+
 /**
  * Access to the OS read() system call
  *
@@ -122,4 +124,16 @@
  */
 u64 os_get_nsec(void);
 
+/**
+ * Parse arguments and update sandbox state.
+ *
+ * @param state		Sandbox state to update
+ * @param argc		Argument count
+ * @param argv		Argument vector
+ * @return 0 if ok, and program should continue;
+ *	1 if ok, but program should stop;
+ *	-1 on error: program should terminate.
+ */
+int os_parse_args(struct sandbox_state *state, int argc, char *argv[]);
+
 #endif