On Thu, May 28, 2009 at 8:11 AM, Mike Frysinger <[email protected]> wrote:
> about the only major thing i see missing in hush after testing on random
> packages out there is the "local" keyword.
>
> x=1
> f() { local x=$((x + 3)) ; echo $x ; }
> g() { local x=$((x + 1)) ; f ; echo $x ; }
> g
> echo $x
>
> the keyword causes the variable to only apply to children of the active
> function scope. so here, we should see:
> 5 - global 1 added to g()'s 1 added to f()'s 3
> 2 - global 1 added to g()'s 1 unaffected by f()
> 1 - global 1 unaffected by g() or f()
>
> another example:
> x=1
> f() { x=2; }
> g() { local x=3; f; echo $x; }
> g
> we see the x is declared local to g()'s scope, and f() modifies it since it is
> called inside that scope.
>
> based on these needs, i'm thinking:
> - new variable state for set_local_var()
> - when a variable is marked local and it shadows an existing variable, we
> insert it into the linked list of variables before the existing copy
> - at the end of run_and_free_list(), we walk the variable list destroying all
> variables marked local
Please try attached patch.
--
vda
diff -d -urpN busybox.1/shell/Config.in busybox.2/shell/Config.in
--- busybox.1/shell/Config.in 2009-05-27 14:23:54.000000000 +0200
+++ busybox.2/shell/Config.in 2009-06-03 12:04:41.000000000 +0200
@@ -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 -d -urpN busybox.1/shell/hush.c busybox.2/shell/hush.c
--- busybox.1/shell/hush.c 2009-06-02 12:58:34.000000000 +0200
+++ busybox.2/shell/hush.c 2009-06-03 12:04:41.000000000 +0200
@@ -408,6 +408,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;
@@ -502,6 +505,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
@@ -543,6 +550,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
@@ -613,6 +623,9 @@ static const struct built_in_command blt
#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
@@ -1275,12 +1288,21 @@ static const char *get_local_var_value(c
* -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;
@@ -1292,15 +1314,10 @@ static int set_local_var(char *str, int
}
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 */
@@ -1312,33 +1329,61 @@ static int set_local_var(char *str, int
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;
@@ -1432,7 +1477,7 @@ static void arith_set_local_var(const ch
{
/* 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
@@ -1452,7 +1497,7 @@ static void add_vars(struct variable *va
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;
}
@@ -1485,7 +1530,7 @@ static struct variable *set_vars_and_sav
var_p->next = old;
old = var_p;
}
- set_local_var(*s, 1, 0);
+ set_local_var(*s, /*exp:*/ 1, /*lvl:*/ 0, /*ro:*/ 0);
}
s++;
}
@@ -2316,7 +2361,7 @@ static int expand_vars_to_list(o_string
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);
}
}
}
@@ -3050,9 +3095,13 @@ static int run_function(const struct fun
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
@@ -3066,7 +3115,32 @@ static int run_function(const struct fun
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;
@@ -3620,7 +3694,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?
@@ -3665,9 +3739,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
}
@@ -4064,7 +4146,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) {
@@ -6264,7 +6346,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': {
@@ -6597,6 +6679,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;
@@ -6639,49 +6770,22 @@ static int builtin_export(char **argv)
argv++;
#endif
- do {
- char *name = *argv;
-
- /* So far we do not check that name is valid (TODO?) */
-
- if (strchr(name, '=') == NULL) {
- struct variable *var;
+ helper_export_local(argv, (opt_unexport ? -1 : 1), 0);
- 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)
{
@@ -6958,7 +7062,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 -d -urpN busybox.1/shell/hush_test/hush-misc/func_local1.right busybox.2/shell/hush_test/hush-misc/func_local1.right
--- busybox.1/shell/hush_test/hush-misc/func_local1.right 1970-01-01 01:00:00.000000000 +0100
+++ busybox.2/shell/hush_test/hush-misc/func_local1.right 2009-06-03 11:52:58.000000000 +0200
@@ -0,0 +1,3 @@
+z=a
+z=z
+Done
diff -d -urpN busybox.1/shell/hush_test/hush-misc/func_local1.tests busybox.2/shell/hush_test/hush-misc/func_local1.tests
--- busybox.1/shell/hush_test/hush-misc/func_local1.tests 1970-01-01 01:00:00.000000000 +0100
+++ busybox.2/shell/hush_test/hush-misc/func_local1.tests 2009-06-03 11:52:58.000000000 +0200
@@ -0,0 +1,5 @@
+export z=z
+f() { local z=a; env | grep ^z; }
+f
+env | grep ^z
+echo Done
diff -d -urpN busybox.1/shell/hush_test/hush-misc/func_local2.right busybox.2/shell/hush_test/hush-misc/func_local2.right
--- busybox.1/shell/hush_test/hush-misc/func_local2.right 1970-01-01 01:00:00.000000000 +0100
+++ busybox.2/shell/hush_test/hush-misc/func_local2.right 2009-06-03 11:52:58.000000000 +0200
@@ -0,0 +1,14 @@
+1
+2
+1
+2
+1
+1
+2
+2
+3
+2
+2
+3
+1
+Done
diff -d -urpN busybox.1/shell/hush_test/hush-misc/func_local2.tests busybox.2/shell/hush_test/hush-misc/func_local2.tests
--- busybox.1/shell/hush_test/hush-misc/func_local2.tests 1970-01-01 01:00:00.000000000 +0100
+++ busybox.2/shell/hush_test/hush-misc/func_local2.tests 2009-06-03 11:52:58.000000000 +0200
@@ -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
_______________________________________________
busybox mailing list
[email protected]
http://lists.busybox.net/mailman/listinfo/busybox