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.villem...@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

Reply via email to