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

Reply via email to