[PATCH 2/3] cli_hush.c: add "call" command

Rasmus Villemoes rasmus.villemoes at prevas.dk
Fri Sep 25 13:19:41 CEST 2020


Currently, the only way to emulate functions with arguments in the
busybox shell is by doing "foo=arg1; bar=arg2; run func" and having
"func" refer to $foo and $bar. That works, but is a bit clunky, and
also suffers from foo and bar being set globally - if func itself wants
to run other "functions" defined in the environment, those other
functions better not use the same parameter names:

  setenv g 'do_g_stuff $foo'
  setenv f 'do_f_stuff $foo $bar; foo=123; run g; do_more_f_stuff $foo $bar'

Sure, f could do a "saved_foo=$foo; .... foo=$saved_foo" dance, but
that makes everything even more clunky.

In order to increase readability, add a little helper "call" that is
like "run", but which sets local shell variables $1 through
$9 (and $#). As in a "real" shell, they are local to the current
function, so if f is called with two arguments, and f calls g with one
argument, g sees $2 as unset. Then the above can be written

  setenv g 'do_g_stuff $1'
  setenv f 'do_f_stuff $1 $2; call g 123; do_more_f_stuff $1 $2'

Everything except

-                       b_addchr(dest, '?');
+                       b_addchr(dest, ch);

is under CONFIG_CMD_CALL, and when CONFIG_CMD_CALL=n, the ch there can
only be '?'. So no functional change when CONFIG_CMD_CALL is not
selected.

"Real shells" have special syntax for defining a function, but calling
a function is the same as calling builtins or external commands. So
the "call" may admittedly be seen as a bit of a kludge. It
should be rather easy to make custom (i.e., defined in the
environment) functions "transparently callable" on top of this
infrastructure, i.e. so one could just say

  f a b c

instead of

  call f a b c

However, that behaviour should be controlled by a separate config
knob, and can be added later if anyone actually wants it.

Signed-off-by: Rasmus Villemoes <rasmus.villemoes at prevas.dk>
---
 cmd/Kconfig       |  8 +++++
 common/cli_hush.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 82 insertions(+), 1 deletion(-)

diff --git a/cmd/Kconfig b/cmd/Kconfig
index 0c984d735d..306f115c32 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -443,6 +443,14 @@ config CMD_RUN
 	help
 	  Run the command in the given environment variable.
 
+config CMD_CALL
+	bool "call"
+	depends on HUSH_PARSER
+	depends on CMD_RUN
+	help
+	  Call function defined in environment variable, setting
+	  positional arguments $1..$9.
+
 config CMD_IMI
 	bool "iminfo"
 	default y
diff --git a/common/cli_hush.c b/common/cli_hush.c
index 072b871f1e..e17fba99ee 100644
--- a/common/cli_hush.c
+++ b/common/cli_hush.c
@@ -135,6 +135,17 @@ DECLARE_GLOBAL_DATA_PTR;
 #define syntax() syntax_err()
 #define xstrdup strdup
 #define error_msg printf
+
+#ifdef CONFIG_CMD_CALL
+#define MAX_CALL_ARGS 9
+struct call_args {
+	struct call_args *prev;
+	int count;
+	char *args[MAX_CALL_ARGS]; /* [0] holds $1 etc. */
+};
+static struct call_args *current_call_args;
+#endif
+
 #else
 typedef enum {
 	REDIRECT_INPUT     = 1,
@@ -2144,6 +2155,10 @@ char *get_local_var(const char *s)
 #ifdef __U_BOOT__
 	if (*s == '$')
 		return get_dollar_var(s[1]);
+	/* To make ${1:-default} work: */
+	if (IS_ENABLED(CONFIG_CMD_CALL) &&
+	    '1' <= s[0] && s[0] <= '9' && !s[1])
+		return get_dollar_var(s[0]);
 #endif
 
 	for (cur = top_vars; cur; cur=cur->next)
@@ -2826,6 +2841,23 @@ static char *get_dollar_var(char ch)
 		case '?':
 			sprintf(buf, "%u", (unsigned int)last_return_code);
 			break;
+#ifdef CONFIG_CMD_CALL
+		case '#':
+			if (!current_call_args)
+				return NULL;
+			sprintf(buf, "%u", current_call_args->count);
+			break;
+		case '1' ... '9': {
+			const struct call_args *ca = current_call_args;
+			int i = ch - '1';
+
+			if (!ca)
+				return NULL;
+			if (i >= ca->count)
+				return NULL;
+			return ca->args[i];
+		}
+#endif
 		default:
 			return NULL;
 	}
@@ -2865,10 +2897,14 @@ static int handle_dollar(o_string *dest, struct p_context *ctx, struct in_str *i
 	} else switch (ch) {
 #ifdef __U_BOOT__
 		case '?':
+#ifdef CONFIG_CMD_CALL
+		case '1' ... '9':
+		case '#':
+#endif
 			ctx->child->sp++;
 			b_addchr(dest, SPECIAL_VAR_SYMBOL);
 			b_addchr(dest, '$');
-			b_addchr(dest, '?');
+			b_addchr(dest, ch);
 			b_addchr(dest, SPECIAL_VAR_SYMBOL);
 			advance = 1;
 			break;
@@ -3711,5 +3747,42 @@ U_BOOT_CMD(
 	"    - print value of hushshell variable 'name'"
 );
 
+#ifdef CONFIG_CMD_CALL
+static int do_cmd_call(struct cmd_tbl *cmdtp, int flag, int argc,
+		      char *const argv[])
+{
+	struct call_args ca;
+	char *run_args[2];
+	int i, ret;
+
+	if (argc < 2)
+		return CMD_RET_USAGE;
+
+	ca.count = argc - 2;
+	for (i = 2; i < argc; ++i)
+		ca.args[i - 2] = argv[i];
+	ca.prev = current_call_args;
+	current_call_args = &ca;
+
+	run_args[0] = "run";
+	run_args[1] = argv[1];
+	ret = do_run(cmdtp, flag, 2, run_args);
+
+	current_call_args = ca.prev;
+
+	return ret;
+}
+
+U_BOOT_CMD_COMPLETE(
+	call, 1 + 1 + MAX_CALL_ARGS, 0, do_cmd_call,
+	"call command in environment variable, setting positional arguments $1..$9",
+        "var [args...]\n"
+        "    - run the command(s) in the environment variable 'var',\n"
+	"      with $1..$9 set to the positional arguments",
+	var_complete
+);
+
+#endif
+
 #endif
 /****************************************************************************/
-- 
2.23.0



More information about the U-Boot mailing list