From mboxrd@z Thu Jan 1 00:00:00 1970 From: Rasmus Villemoes Date: Fri, 25 Sep 2020 13:19:41 +0200 Subject: [PATCH 2/3] cli_hush.c: add "call" command In-Reply-To: <20200925111942.4629-1-rasmus.villemoes@prevas.dk> References: <20200925111942.4629-1-rasmus.villemoes@prevas.dk> Message-ID: <20200925111942.4629-3-rasmus.villemoes@prevas.dk> List-Id: MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit To: u-boot@lists.denx.de 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 --- 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