hush: add support for local builtin
authorDenys Vlasenko <vda.linux@googlemail.com>
Wed, 3 Jun 2009 10:47:26 +0000 (12:47 +0200)
committerDenys Vlasenko <vda.linux@googlemail.com>
Wed, 3 Jun 2009 10:47:26 +0000 (12:47 +0200)
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
shell/Config.in
shell/hush.c
shell/hush_test/hush-misc/func_local1.right [new file with mode: 0644]
shell/hush_test/hush-misc/func_local1.tests [new file with mode: 0755]
shell/hush_test/hush-misc/func_local2.right [new file with mode: 0644]
shell/hush_test/hush-misc/func_local2.tests [new file with mode: 0755]

index 57969f0..ee56d89 100644 (file)
@@ -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
index e3f7b6e..1ad5fcd 100644 (file)
@@ -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 (file)
index 0000000..3121783
--- /dev/null
@@ -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 (executable)
index 0000000..1d594e2
--- /dev/null
@@ -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 (file)
index 0000000..fe9343a
--- /dev/null
@@ -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 (executable)
index 0000000..1a9ae55
--- /dev/null
@@ -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