hush: add support for local builtin
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/shell/hush.c b/shell/hush.c
index e3f7b6e..1ad5fcd 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -394,6 +394,9 @@
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 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 @@
#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 @@
#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 @@
* -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 @@
}
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 @@
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 @@
{
/* 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
#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 @@
}
/* 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 @@
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 @@
} 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,50 +6756,23 @@
argv++;
#endif
- 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 (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);
+ helper_export_local(argv, (opt_unexport ? -1 : 1), 0);
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)
{
int sig;
@@ -6944,7 +7048,7 @@
//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