aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--shell/Config.in7
-rw-r--r--shell/hush.c244
-rw-r--r--shell/hush_test/hush-misc/func_local1.right3
-rwxr-xr-xshell/hush_test/hush-misc/func_local1.tests5
-rw-r--r--shell/hush_test/hush-misc/func_local2.right14
-rwxr-xr-xshell/hush_test/hush-misc/func_local2.tests7
6 files changed, 210 insertions, 70 deletions
diff --git a/shell/Config.in b/shell/Config.in
index 57969f02c..ee56d89f3 100644
--- a/shell/Config.in
+++ b/shell/Config.in
@@ -232,6 +232,13 @@ config HUSH_FUNCTIONS
help
Enable support for shell functions in hush. +800 bytes.
+config HUSH_LOCAL
+ bool "Support local builtin"
+ default n
+ depends on HUSH_FUNCTIONS
+ help
+ Enable support for local variables in functions.
+
config HUSH_EXPORT_N
bool "Support export '-n' option"
default n
diff --git a/shell/hush.c b/shell/hush.c
index e3f7b6eb7..1ad5fcd72 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -394,6 +394,9 @@ struct parse_context {
struct variable {
struct variable *next;
char *varstr; /* points to "name=" portion */
+#if ENABLE_HUSH_LOCAL
+ unsigned func_nest_level;
+#endif
int max_len; /* if > 0, name is part of initial env; else name is malloced */
smallint flg_export; /* putenv should be done on this var */
smallint flg_read_only;
@@ -488,6 +491,10 @@ struct globals {
struct variable shell_ver;
#if ENABLE_HUSH_FUNCTIONS
struct function *top_func;
+# if ENABLE_HUSH_LOCAL
+ struct variable **shadowed_vars_pp;
+ unsigned func_nest_level;
+# endif
#endif
/* Signal and trap handling */
#if ENABLE_HUSH_FAST
@@ -529,6 +536,9 @@ static int builtin_jobs(char **argv);
#if ENABLE_HUSH_HELP
static int builtin_help(char **argv);
#endif
+#if ENABLE_HUSH_LOCAL
+static int builtin_local(char **argv);
+#endif
#if HUSH_DEBUG
static int builtin_memleak(char **argv);
#endif
@@ -599,6 +609,9 @@ static const struct built_in_command bltins[] = {
#if ENABLE_HUSH_JOB
BLTIN("jobs" , builtin_jobs , "List active jobs"),
#endif
+#if ENABLE_HUSH_LOCAL
+ BLTIN("local" , builtin_local , "Set local variable"),
+#endif
#if HUSH_DEBUG
BLTIN("memleak" , builtin_memleak , "Debug tool"),
#endif
@@ -1261,12 +1274,21 @@ static const char *get_local_var_value(const char *name)
* -1: clear export flag and unsetenv the variable
* flg_read_only is set only when we handle -R var=val
*/
-#if BB_MMU
-#define set_local_var(str, flg_export, flg_read_only) \
+#if !BB_MMU && ENABLE_HUSH_LOCAL
+/* all params are used */
+#elif BB_MMU && ENABLE_HUSH_LOCAL
+#define set_local_var(str, flg_export, local_lvl, flg_read_only) \
+ set_local_var(str, flg_export, local_lvl)
+#elif BB_MMU && !ENABLE_HUSH_LOCAL
+#define set_local_var(str, flg_export, local_lvl, flg_read_only) \
set_local_var(str, flg_export)
+#elif !BB_MMU && !ENABLE_HUSH_LOCAL
+#define set_local_var(str, flg_export, local_lvl, flg_read_only) \
+ set_local_var(str, flg_export, flg_read_only)
#endif
-static int set_local_var(char *str, int flg_export, int flg_read_only)
+static int set_local_var(char *str, int flg_export, int local_lvl, int flg_read_only)
{
+ struct variable **var_pp;
struct variable *cur;
char *eq_sign;
int name_len;
@@ -1278,15 +1300,10 @@ static int set_local_var(char *str, int flg_export, int flg_read_only)
}
name_len = eq_sign - str + 1; /* including '=' */
- cur = G.top_var; /* cannot be NULL (we have HUSH_VERSION and it's RO) */
- while (1) {
+ var_pp = &G.top_var;
+ while ((cur = *var_pp) != NULL) {
if (strncmp(cur->varstr, str, name_len) != 0) {
- if (!cur->next) {
- /* Bail out. Note that now cur points
- * to the last var in the linked list */
- break;
- }
- cur = cur->next;
+ var_pp = &cur->next;
continue;
}
/* We found an existing var with this name */
@@ -1298,33 +1315,61 @@ static int set_local_var(char *str, int flg_export, int flg_read_only)
free(str);
return -1;
}
- if (flg_export == -1) {
+ if (flg_export == -1) { // "&& cur->flg_export" ?
debug_printf_env("%s: unsetenv '%s'\n", __func__, str);
*eq_sign = '\0';
unsetenv(str);
*eq_sign = '=';
}
+#if ENABLE_HUSH_LOCAL
+ if (cur->func_nest_level < local_lvl) {
+ /* New variable is declared as local,
+ * and existing one is global, or local
+ * from enclosing function.
+ * Remove and save old one: */
+ *var_pp = cur->next;
+ cur->next = *G.shadowed_vars_pp;
+ *G.shadowed_vars_pp = cur;
+ /* bash 3.2.33(1) and exported vars:
+ * # export z=z
+ * # f() { local z=a; env | grep ^z; }
+ * # f
+ * z=a
+ * # env | grep ^z
+ * z=z
+ */
+ if (cur->flg_export)
+ flg_export = 1;
+ break;
+ }
+#endif
if (strcmp(cur->varstr + name_len, eq_sign + 1) == 0) {
free_and_exp:
free(str);
goto exp;
}
- if (cur->max_len >= strlen(str)) {
- /* This one is from startup env, reuse space */
- strcpy(cur->varstr, str);
- goto free_and_exp;
- }
- /* max_len == 0 signifies "malloced" var, which we can
- * (and has to) free */
- if (!cur->max_len)
+ if (cur->max_len != 0) {
+ if (cur->max_len >= strlen(str)) {
+ /* This one is from startup env, reuse space */
+ strcpy(cur->varstr, str);
+ goto free_and_exp;
+ }
+ } else {
+ /* max_len == 0 signifies "malloced" var, which we can
+ * (and has to) free */
free(cur->varstr);
+ }
cur->max_len = 0;
goto set_str_and_exp;
}
- /* Not found - create next variable struct */
- cur->next = xzalloc(sizeof(*cur));
- cur = cur->next;
+ /* Not found - create new variable struct */
+ cur = xzalloc(sizeof(*cur));
+#if ENABLE_HUSH_LOCAL
+ cur->func_nest_level = local_lvl;
+#endif
+ cur->next = *var_pp;
+ *var_pp = cur;
set_str_and_exp:
cur->varstr = str;
@@ -1418,7 +1463,7 @@ static void arith_set_local_var(const char *name, const char *val, int flags)
{
/* arith code doesnt malloc space, so do it for it */
char *var = xasprintf("%s=%s", name, val);
- set_local_var(var, flags, 0);
+ set_local_var(var, flags, /*lvl:*/ 0, /*ro:*/ 0);
}
#endif
@@ -1438,7 +1483,7 @@ static void add_vars(struct variable *var)
debug_printf_env("%s: restoring exported '%s'\n", __func__, var->varstr);
putenv(var->varstr);
} else {
- debug_printf_env("%s: restoring local '%s'\n", __func__, var->varstr);
+ debug_printf_env("%s: restoring variable '%s'\n", __func__, var->varstr);
}
var = next;
}
@@ -1471,7 +1516,7 @@ static struct variable *set_vars_and_save_old(char **strings)
var_p->next = old;
old = var_p;
}
- set_local_var(*s, 1, 0);
+ set_local_var(*s, /*exp:*/ 1, /*lvl:*/ 0, /*ro:*/ 0);
}
s++;
}
@@ -2302,7 +2347,7 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
val = NULL;
} else {
char *new_var = xasprintf("%s=%s", var, val);
- set_local_var(new_var, 0, 0);
+ set_local_var(new_var, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
}
}
}
@@ -3036,9 +3081,13 @@ static int run_function(const struct function *funcp, char **argv)
smallint sv_flg;
save_and_replace_G_args(&sv, argv);
+
/* "we are in function, ok to use return" */
sv_flg = G.flag_return_in_progress;
G.flag_return_in_progress = -1;
+#if ENABLE_HUSH_LOCAL
+ G.func_nest_level++;
+#endif
/* On MMU, funcp->body is always non-NULL */
# if !BB_MMU
@@ -3052,7 +3101,32 @@ static int run_function(const struct function *funcp, char **argv)
rc = run_list(funcp->body);
}
+#if ENABLE_HUSH_LOCAL
+ {
+ struct variable *var;
+ struct variable **var_pp;
+
+ var_pp = &G.top_var;
+ while ((var = *var_pp) != NULL) {
+ if (var->func_nest_level < G.func_nest_level) {
+ var_pp = &var->next;
+ continue;
+ }
+ /* Unexport */
+ if (var->flg_export)
+ bb_unsetenv(var->varstr);
+ /* Remove from global list */
+ *var_pp = var->next;
+ /* Free */
+ if (!var->max_len)
+ free(var->varstr);
+ free(var);
+ }
+ G.func_nest_level--;
+ }
+#endif
G.flag_return_in_progress = sv_flg;
+
restore_G_args(&sv, argv);
return rc;
@@ -3606,7 +3680,7 @@ static int run_pipe(struct pipe *pi)
p = expand_string_to_string(*argv);
debug_printf_exec("set shell var:'%s'->'%s'\n",
*argv, p);
- set_local_var(p, 0, 0);
+ set_local_var(p, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
argv++;
}
/* Do we need to flag set_local_var() errors?
@@ -3651,9 +3725,17 @@ static int run_pipe(struct pipe *pi)
}
#if ENABLE_HUSH_FUNCTIONS
else {
+# if ENABLE_HUSH_LOCAL
+ struct variable **sv;
+ sv = G.shadowed_vars_pp;
+ G.shadowed_vars_pp = &old_vars;
+# endif
debug_printf_exec(": function '%s' '%s'...\n",
funcp->name, argv_expanded[1]);
rcode = run_function(funcp, argv_expanded) & 0xff;
+# if ENABLE_HUSH_LOCAL
+ G.shadowed_vars_pp = sv;
+# endif
}
#endif
}
@@ -4050,7 +4132,7 @@ static int run_list(struct pipe *pi)
}
/* Insert next value from for_lcur */
/* note: *for_lcur already has quotes removed, $var expanded, etc */
- set_local_var(xasprintf("%s=%s", pi->cmds[0].argv[0], *for_lcur++), 0, 0);
+ set_local_var(xasprintf("%s=%s", pi->cmds[0].argv[0], *for_lcur++), /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
continue;
}
if (rword == RES_IN) {
@@ -6250,7 +6332,7 @@ int hush_main(int argc, char **argv)
break;
case 'R':
case 'V':
- set_local_var(xstrdup(optarg), 0, opt == 'R');
+ set_local_var(xstrdup(optarg), /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ opt == 'R');
break;
# if ENABLE_HUSH_FUNCTIONS
case 'F': {
@@ -6583,6 +6665,55 @@ static void print_escaped(const char *s)
} while (*s);
}
+#if !ENABLE_HUSH_LOCAL
+#define helper_export_local(argv, exp, lvl) \
+ helper_export_local(argv, exp)
+#endif
+static void helper_export_local(char **argv, int exp, int lvl)
+{
+ do {
+ char *name = *argv;
+
+ /* So far we do not check that name is valid (TODO?) */
+
+ if (strchr(name, '=') == NULL) {
+ struct variable *var;
+
+ var = get_local_var(name);
+ if (exp == -1) { /* unexporting? */
+ /* export -n NAME (without =VALUE) */
+ if (var) {
+ var->flg_export = 0;
+ debug_printf_env("%s: unsetenv '%s'\n", __func__, name);
+ unsetenv(name);
+ } /* else: export -n NOT_EXISTING_VAR: no-op */
+ continue;
+ }
+ if (exp == 1) { /* exporting? */
+ /* export NAME (without =VALUE) */
+ if (var) {
+ var->flg_export = 1;
+ debug_printf_env("%s: putenv '%s'\n", __func__, var->varstr);
+ putenv(var->varstr);
+ continue;
+ }
+ }
+ /* Exporting non-existing variable.
+ * bash does not put it in environment,
+ * but remembers that it is exported,
+ * and does put it in env when it is set later.
+ * We just set it to "" and export. */
+ /* Or, it's "local NAME" (without =VALUE).
+ * bash sets the value to "". */
+ name = xasprintf("%s=", name);
+ } else {
+ /* (Un)exporting/making local NAME=VALUE */
+ name = xstrdup(name);
+ }
+ set_local_var(name, /*exp:*/ exp, /*lvl:*/ lvl, /*ro:*/ 0);
+ } while (*++argv);
+}
+
static int builtin_export(char **argv)
{
unsigned opt_unexport;
@@ -6625,49 +6756,22 @@ static int builtin_export(char **argv)
argv++;
#endif
- do {
- char *name = *argv;
+ helper_export_local(argv, (opt_unexport ? -1 : 1), 0);
- /* So far we do not check that name is valid (TODO?) */
-
- if (strchr(name, '=') == NULL) {
- struct variable *var;
-
- var = get_local_var(name);
- if (opt_unexport) {
- /* export -n NAME (without =VALUE) */
- if (var) {
- var->flg_export = 0;
- debug_printf_env("%s: unsetenv '%s'\n", __func__, name);
- unsetenv(name);
- } /* else: export -n NOT_EXISTING_VAR: no-op */
- continue;
- }
- /* export NAME (without =VALUE) */
- if (var) {
- var->flg_export = 1;
- debug_printf_env("%s: putenv '%s'\n", __func__, var->varstr);
- putenv(var->varstr);
- continue;
- }
- /* Exporting non-existing variable.
- * bash does not put it in environment,
- * but remembers that it is exported,
- * and does put it in env when it is set later.
- * We just set it to "" and export. */
- name = xasprintf("%s=", name);
- } else {
- /* (Un)exporting NAME=VALUE */
- name = xstrdup(name);
- }
- set_local_var(name,
- /*export:*/ (opt_unexport ? -1 : 1),
- /*readonly:*/ 0
- );
- } while (*++argv);
+ return EXIT_SUCCESS;
+}
+#if ENABLE_HUSH_LOCAL
+static int builtin_local(char **argv)
+{
+ if (G.func_nest_level == 0) {
+ bb_error_msg("%s: not in a function", argv[0]);
+ return EXIT_FAILURE; /* bash compat */
+ }
+ helper_export_local(argv, 0, G.func_nest_level);
return EXIT_SUCCESS;
}
+#endif
static int builtin_trap(char **argv)
{
@@ -6944,7 +7048,7 @@ static int builtin_read(char **argv)
//TODO: bash unbackslashes input, splits words and puts them in argv[i]
string = xmalloc_reads(STDIN_FILENO, xasprintf("%s=", name), NULL);
- return set_local_var(string, 0, 0);
+ return set_local_var(string, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
}
/* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#set
diff --git a/shell/hush_test/hush-misc/func_local1.right b/shell/hush_test/hush-misc/func_local1.right
new file mode 100644
index 000000000..312178366
--- /dev/null
+++ b/shell/hush_test/hush-misc/func_local1.right
@@ -0,0 +1,3 @@
+z=a
+z=z
+Done
diff --git a/shell/hush_test/hush-misc/func_local1.tests b/shell/hush_test/hush-misc/func_local1.tests
new file mode 100755
index 000000000..1d594e20c
--- /dev/null
+++ b/shell/hush_test/hush-misc/func_local1.tests
@@ -0,0 +1,5 @@
+export z=z
+f() { local z=a; env | grep ^z; }
+f
+env | grep ^z
+echo Done
diff --git a/shell/hush_test/hush-misc/func_local2.right b/shell/hush_test/hush-misc/func_local2.right
new file mode 100644
index 000000000..fe9343ac8
--- /dev/null
+++ b/shell/hush_test/hush-misc/func_local2.right
@@ -0,0 +1,14 @@
+1
+2
+1
+2
+1
+1
+2
+2
+3
+2
+2
+3
+1
+Done
diff --git a/shell/hush_test/hush-misc/func_local2.tests b/shell/hush_test/hush-misc/func_local2.tests
new file mode 100755
index 000000000..1a9ae559d
--- /dev/null
+++ b/shell/hush_test/hush-misc/func_local2.tests
@@ -0,0 +1,7 @@
+x=1
+f() { echo $x; local x=$((x+1)); echo $x; }
+g() { f; echo $x; f; local x=$((x+1)); f; echo $x; f; }
+f
+g
+echo $x
+echo Done