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
232 help 232 help
233 Enable support for shell functions in hush. +800 bytes. 233 Enable support for shell functions in hush. +800 bytes.
234 234
235config HUSH_LOCAL
236 bool "Support local builtin"
237 default n
238 depends on HUSH_FUNCTIONS
239 help
240 Enable support for local variables in functions.
241
235config HUSH_EXPORT_N 242config HUSH_EXPORT_N
236 bool "Support export '-n' option" 243 bool "Support export '-n' option"
237 default n 244 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 {
394struct variable { 394struct variable {
395 struct variable *next; 395 struct variable *next;
396 char *varstr; /* points to "name=" portion */ 396 char *varstr; /* points to "name=" portion */
397#if ENABLE_HUSH_LOCAL
398 unsigned func_nest_level;
399#endif
397 int max_len; /* if > 0, name is part of initial env; else name is malloced */ 400 int max_len; /* if > 0, name is part of initial env; else name is malloced */
398 smallint flg_export; /* putenv should be done on this var */ 401 smallint flg_export; /* putenv should be done on this var */
399 smallint flg_read_only; 402 smallint flg_read_only;
@@ -488,6 +491,10 @@ struct globals {
488 struct variable shell_ver; 491 struct variable shell_ver;
489#if ENABLE_HUSH_FUNCTIONS 492#if ENABLE_HUSH_FUNCTIONS
490 struct function *top_func; 493 struct function *top_func;
494# if ENABLE_HUSH_LOCAL
495 struct variable **shadowed_vars_pp;
496 unsigned func_nest_level;
497# endif
491#endif 498#endif
492 /* Signal and trap handling */ 499 /* Signal and trap handling */
493#if ENABLE_HUSH_FAST 500#if ENABLE_HUSH_FAST
@@ -529,6 +536,9 @@ static int builtin_jobs(char **argv);
529#if ENABLE_HUSH_HELP 536#if ENABLE_HUSH_HELP
530static int builtin_help(char **argv); 537static int builtin_help(char **argv);
531#endif 538#endif
539#if ENABLE_HUSH_LOCAL
540static int builtin_local(char **argv);
541#endif
532#if HUSH_DEBUG 542#if HUSH_DEBUG
533static int builtin_memleak(char **argv); 543static int builtin_memleak(char **argv);
534#endif 544#endif
@@ -599,6 +609,9 @@ static const struct built_in_command bltins[] = {
599#if ENABLE_HUSH_JOB 609#if ENABLE_HUSH_JOB
600 BLTIN("jobs" , builtin_jobs , "List active jobs"), 610 BLTIN("jobs" , builtin_jobs , "List active jobs"),
601#endif 611#endif
612#if ENABLE_HUSH_LOCAL
613 BLTIN("local" , builtin_local , "Set local variable"),
614#endif
602#if HUSH_DEBUG 615#if HUSH_DEBUG
603 BLTIN("memleak" , builtin_memleak , "Debug tool"), 616 BLTIN("memleak" , builtin_memleak , "Debug tool"),
604#endif 617#endif
@@ -1261,12 +1274,21 @@ static const char *get_local_var_value(const char *name)
1261 * -1: clear export flag and unsetenv the variable 1274 * -1: clear export flag and unsetenv the variable
1262 * flg_read_only is set only when we handle -R var=val 1275 * flg_read_only is set only when we handle -R var=val
1263 */ 1276 */
1264#if BB_MMU 1277#if !BB_MMU && ENABLE_HUSH_LOCAL
1265#define set_local_var(str, flg_export, flg_read_only) \ 1278/* all params are used */
1279#elif BB_MMU && ENABLE_HUSH_LOCAL
1280#define set_local_var(str, flg_export, local_lvl, flg_read_only) \
1281 set_local_var(str, flg_export, local_lvl)
1282#elif BB_MMU && !ENABLE_HUSH_LOCAL
1283#define set_local_var(str, flg_export, local_lvl, flg_read_only) \
1266 set_local_var(str, flg_export) 1284 set_local_var(str, flg_export)
1285#elif !BB_MMU && !ENABLE_HUSH_LOCAL
1286#define set_local_var(str, flg_export, local_lvl, flg_read_only) \
1287 set_local_var(str, flg_export, flg_read_only)
1267#endif 1288#endif
1268static int set_local_var(char *str, int flg_export, int flg_read_only) 1289static int set_local_var(char *str, int flg_export, int local_lvl, int flg_read_only)
1269{ 1290{
1291 struct variable **var_pp;
1270 struct variable *cur; 1292 struct variable *cur;
1271 char *eq_sign; 1293 char *eq_sign;
1272 int name_len; 1294 int name_len;
@@ -1278,15 +1300,10 @@ static int set_local_var(char *str, int flg_export, int flg_read_only)
1278 } 1300 }
1279 1301
1280 name_len = eq_sign - str + 1; /* including '=' */ 1302 name_len = eq_sign - str + 1; /* including '=' */
1281 cur = G.top_var; /* cannot be NULL (we have HUSH_VERSION and it's RO) */ 1303 var_pp = &G.top_var;
1282 while (1) { 1304 while ((cur = *var_pp) != NULL) {
1283 if (strncmp(cur->varstr, str, name_len) != 0) { 1305 if (strncmp(cur->varstr, str, name_len) != 0) {
1284 if (!cur->next) { 1306 var_pp = &cur->next;
1285 /* Bail out. Note that now cur points
1286 * to the last var in the linked list */
1287 break;
1288 }
1289 cur = cur->next;
1290 continue; 1307 continue;
1291 } 1308 }
1292 /* We found an existing var with this name */ 1309 /* 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)
1298 free(str); 1315 free(str);
1299 return -1; 1316 return -1;
1300 } 1317 }
1301 if (flg_export == -1) { 1318 if (flg_export == -1) { // "&& cur->flg_export" ?
1302 debug_printf_env("%s: unsetenv '%s'\n", __func__, str); 1319 debug_printf_env("%s: unsetenv '%s'\n", __func__, str);
1303 *eq_sign = '\0'; 1320 *eq_sign = '\0';
1304 unsetenv(str); 1321 unsetenv(str);
1305 *eq_sign = '='; 1322 *eq_sign = '=';
1306 } 1323 }
1324#if ENABLE_HUSH_LOCAL
1325 if (cur->func_nest_level < local_lvl) {
1326 /* New variable is declared as local,
1327 * and existing one is global, or local
1328 * from enclosing function.
1329 * Remove and save old one: */
1330 *var_pp = cur->next;
1331 cur->next = *G.shadowed_vars_pp;
1332 *G.shadowed_vars_pp = cur;
1333 /* bash 3.2.33(1) and exported vars:
1334 * # export z=z
1335 * # f() { local z=a; env | grep ^z; }
1336 * # f
1337 * z=a
1338 * # env | grep ^z
1339 * z=z
1340 */
1341 if (cur->flg_export)
1342 flg_export = 1;
1343 break;
1344 }
1345#endif
1307 if (strcmp(cur->varstr + name_len, eq_sign + 1) == 0) { 1346 if (strcmp(cur->varstr + name_len, eq_sign + 1) == 0) {
1308 free_and_exp: 1347 free_and_exp:
1309 free(str); 1348 free(str);
1310 goto exp; 1349 goto exp;
1311 } 1350 }
1312 if (cur->max_len >= strlen(str)) { 1351 if (cur->max_len != 0) {
1313 /* This one is from startup env, reuse space */ 1352 if (cur->max_len >= strlen(str)) {
1314 strcpy(cur->varstr, str); 1353 /* This one is from startup env, reuse space */
1315 goto free_and_exp; 1354 strcpy(cur->varstr, str);
1316 } 1355 goto free_and_exp;
1317 /* max_len == 0 signifies "malloced" var, which we can 1356 }
1318 * (and has to) free */ 1357 } else {
1319 if (!cur->max_len) 1358 /* max_len == 0 signifies "malloced" var, which we can
1359 * (and has to) free */
1320 free(cur->varstr); 1360 free(cur->varstr);
1361 }
1321 cur->max_len = 0; 1362 cur->max_len = 0;
1322 goto set_str_and_exp; 1363 goto set_str_and_exp;
1323 } 1364 }
1324 1365
1325 /* Not found - create next variable struct */ 1366 /* Not found - create new variable struct */
1326 cur->next = xzalloc(sizeof(*cur)); 1367 cur = xzalloc(sizeof(*cur));
1327 cur = cur->next; 1368#if ENABLE_HUSH_LOCAL
1369 cur->func_nest_level = local_lvl;
1370#endif
1371 cur->next = *var_pp;
1372 *var_pp = cur;
1328 1373
1329 set_str_and_exp: 1374 set_str_and_exp:
1330 cur->varstr = str; 1375 cur->varstr = str;
@@ -1418,7 +1463,7 @@ static void arith_set_local_var(const char *name, const char *val, int flags)
1418{ 1463{
1419 /* arith code doesnt malloc space, so do it for it */ 1464 /* arith code doesnt malloc space, so do it for it */
1420 char *var = xasprintf("%s=%s", name, val); 1465 char *var = xasprintf("%s=%s", name, val);
1421 set_local_var(var, flags, 0); 1466 set_local_var(var, flags, /*lvl:*/ 0, /*ro:*/ 0);
1422} 1467}
1423#endif 1468#endif
1424 1469
@@ -1438,7 +1483,7 @@ static void add_vars(struct variable *var)
1438 debug_printf_env("%s: restoring exported '%s'\n", __func__, var->varstr); 1483 debug_printf_env("%s: restoring exported '%s'\n", __func__, var->varstr);
1439 putenv(var->varstr); 1484 putenv(var->varstr);
1440 } else { 1485 } else {
1441 debug_printf_env("%s: restoring local '%s'\n", __func__, var->varstr); 1486 debug_printf_env("%s: restoring variable '%s'\n", __func__, var->varstr);
1442 } 1487 }
1443 var = next; 1488 var = next;
1444 } 1489 }
@@ -1471,7 +1516,7 @@ static struct variable *set_vars_and_save_old(char **strings)
1471 var_p->next = old; 1516 var_p->next = old;
1472 old = var_p; 1517 old = var_p;
1473 } 1518 }
1474 set_local_var(*s, 1, 0); 1519 set_local_var(*s, /*exp:*/ 1, /*lvl:*/ 0, /*ro:*/ 0);
1475 } 1520 }
1476 s++; 1521 s++;
1477 } 1522 }
@@ -2302,7 +2347,7 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
2302 val = NULL; 2347 val = NULL;
2303 } else { 2348 } else {
2304 char *new_var = xasprintf("%s=%s", var, val); 2349 char *new_var = xasprintf("%s=%s", var, val);
2305 set_local_var(new_var, 0, 0); 2350 set_local_var(new_var, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
2306 } 2351 }
2307 } 2352 }
2308 } 2353 }
@@ -3036,9 +3081,13 @@ static int run_function(const struct function *funcp, char **argv)
3036 smallint sv_flg; 3081 smallint sv_flg;
3037 3082
3038 save_and_replace_G_args(&sv, argv); 3083 save_and_replace_G_args(&sv, argv);
3084
3039 /* "we are in function, ok to use return" */ 3085 /* "we are in function, ok to use return" */
3040 sv_flg = G.flag_return_in_progress; 3086 sv_flg = G.flag_return_in_progress;
3041 G.flag_return_in_progress = -1; 3087 G.flag_return_in_progress = -1;
3088#if ENABLE_HUSH_LOCAL
3089 G.func_nest_level++;
3090#endif
3042 3091
3043 /* On MMU, funcp->body is always non-NULL */ 3092 /* On MMU, funcp->body is always non-NULL */
3044# if !BB_MMU 3093# if !BB_MMU
@@ -3052,7 +3101,32 @@ static int run_function(const struct function *funcp, char **argv)
3052 rc = run_list(funcp->body); 3101 rc = run_list(funcp->body);
3053 } 3102 }
3054 3103
3104#if ENABLE_HUSH_LOCAL
3105 {
3106 struct variable *var;
3107 struct variable **var_pp;
3108
3109 var_pp = &G.top_var;
3110 while ((var = *var_pp) != NULL) {
3111 if (var->func_nest_level < G.func_nest_level) {
3112 var_pp = &var->next;
3113 continue;
3114 }
3115 /* Unexport */
3116 if (var->flg_export)
3117 bb_unsetenv(var->varstr);
3118 /* Remove from global list */
3119 *var_pp = var->next;
3120 /* Free */
3121 if (!var->max_len)
3122 free(var->varstr);
3123 free(var);
3124 }
3125 G.func_nest_level--;
3126 }
3127#endif
3055 G.flag_return_in_progress = sv_flg; 3128 G.flag_return_in_progress = sv_flg;
3129
3056 restore_G_args(&sv, argv); 3130 restore_G_args(&sv, argv);
3057 3131
3058 return rc; 3132 return rc;
@@ -3606,7 +3680,7 @@ static int run_pipe(struct pipe *pi)
3606 p = expand_string_to_string(*argv); 3680 p = expand_string_to_string(*argv);
3607 debug_printf_exec("set shell var:'%s'->'%s'\n", 3681 debug_printf_exec("set shell var:'%s'->'%s'\n",
3608 *argv, p); 3682 *argv, p);
3609 set_local_var(p, 0, 0); 3683 set_local_var(p, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
3610 argv++; 3684 argv++;
3611 } 3685 }
3612 /* Do we need to flag set_local_var() errors? 3686 /* Do we need to flag set_local_var() errors?
@@ -3651,9 +3725,17 @@ static int run_pipe(struct pipe *pi)
3651 } 3725 }
3652#if ENABLE_HUSH_FUNCTIONS 3726#if ENABLE_HUSH_FUNCTIONS
3653 else { 3727 else {
3728# if ENABLE_HUSH_LOCAL
3729 struct variable **sv;
3730 sv = G.shadowed_vars_pp;
3731 G.shadowed_vars_pp = &old_vars;
3732# endif
3654 debug_printf_exec(": function '%s' '%s'...\n", 3733 debug_printf_exec(": function '%s' '%s'...\n",
3655 funcp->name, argv_expanded[1]); 3734 funcp->name, argv_expanded[1]);
3656 rcode = run_function(funcp, argv_expanded) & 0xff; 3735 rcode = run_function(funcp, argv_expanded) & 0xff;
3736# if ENABLE_HUSH_LOCAL
3737 G.shadowed_vars_pp = sv;
3738# endif
3657 } 3739 }
3658#endif 3740#endif
3659 } 3741 }
@@ -4050,7 +4132,7 @@ static int run_list(struct pipe *pi)
4050 } 4132 }
4051 /* Insert next value from for_lcur */ 4133 /* Insert next value from for_lcur */
4052 /* note: *for_lcur already has quotes removed, $var expanded, etc */ 4134 /* note: *for_lcur already has quotes removed, $var expanded, etc */
4053 set_local_var(xasprintf("%s=%s", pi->cmds[0].argv[0], *for_lcur++), 0, 0); 4135 set_local_var(xasprintf("%s=%s", pi->cmds[0].argv[0], *for_lcur++), /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
4054 continue; 4136 continue;
4055 } 4137 }
4056 if (rword == RES_IN) { 4138 if (rword == RES_IN) {
@@ -6250,7 +6332,7 @@ int hush_main(int argc, char **argv)
6250 break; 6332 break;
6251 case 'R': 6333 case 'R':
6252 case 'V': 6334 case 'V':
6253 set_local_var(xstrdup(optarg), 0, opt == 'R'); 6335 set_local_var(xstrdup(optarg), /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ opt == 'R');
6254 break; 6336 break;
6255# if ENABLE_HUSH_FUNCTIONS 6337# if ENABLE_HUSH_FUNCTIONS
6256 case 'F': { 6338 case 'F': {
@@ -6583,6 +6665,55 @@ static void print_escaped(const char *s)
6583 } while (*s); 6665 } while (*s);
6584} 6666}
6585 6667
6668#if !ENABLE_HUSH_LOCAL
6669#define helper_export_local(argv, exp, lvl) \
6670 helper_export_local(argv, exp)
6671#endif
6672static void helper_export_local(char **argv, int exp, int lvl)
6673{
6674 do {
6675 char *name = *argv;
6676
6677 /* So far we do not check that name is valid (TODO?) */
6678
6679 if (strchr(name, '=') == NULL) {
6680 struct variable *var;
6681
6682 var = get_local_var(name);
6683 if (exp == -1) { /* unexporting? */
6684 /* export -n NAME (without =VALUE) */
6685 if (var) {
6686 var->flg_export = 0;
6687 debug_printf_env("%s: unsetenv '%s'\n", __func__, name);
6688 unsetenv(name);
6689 } /* else: export -n NOT_EXISTING_VAR: no-op */
6690 continue;
6691 }
6692 if (exp == 1) { /* exporting? */
6693 /* export NAME (without =VALUE) */
6694 if (var) {
6695 var->flg_export = 1;
6696 debug_printf_env("%s: putenv '%s'\n", __func__, var->varstr);
6697 putenv(var->varstr);
6698 continue;
6699 }
6700 }
6701 /* Exporting non-existing variable.
6702 * bash does not put it in environment,
6703 * but remembers that it is exported,
6704 * and does put it in env when it is set later.
6705 * We just set it to "" and export. */
6706 /* Or, it's "local NAME" (without =VALUE).
6707 * bash sets the value to "". */
6708 name = xasprintf("%s=", name);
6709 } else {
6710 /* (Un)exporting/making local NAME=VALUE */
6711 name = xstrdup(name);
6712 }
6713 set_local_var(name, /*exp:*/ exp, /*lvl:*/ lvl, /*ro:*/ 0);
6714 } while (*++argv);
6715}
6716
6586static int builtin_export(char **argv) 6717static int builtin_export(char **argv)
6587{ 6718{
6588 unsigned opt_unexport; 6719 unsigned opt_unexport;
@@ -6625,49 +6756,22 @@ static int builtin_export(char **argv)
6625 argv++; 6756 argv++;
6626#endif 6757#endif
6627 6758
6628 do { 6759 helper_export_local(argv, (opt_unexport ? -1 : 1), 0);
6629 char *name = *argv;
6630 6760
6631 /* So far we do not check that name is valid (TODO?) */ 6761 return EXIT_SUCCESS;
6632 6762}
6633 if (strchr(name, '=') == NULL) {
6634 struct variable *var;
6635
6636 var = get_local_var(name);
6637 if (opt_unexport) {
6638 /* export -n NAME (without =VALUE) */
6639 if (var) {
6640 var->flg_export = 0;
6641 debug_printf_env("%s: unsetenv '%s'\n", __func__, name);
6642 unsetenv(name);
6643 } /* else: export -n NOT_EXISTING_VAR: no-op */
6644 continue;
6645 }
6646 /* export NAME (without =VALUE) */
6647 if (var) {
6648 var->flg_export = 1;
6649 debug_printf_env("%s: putenv '%s'\n", __func__, var->varstr);
6650 putenv(var->varstr);
6651 continue;
6652 }
6653 /* Exporting non-existing variable.
6654 * bash does not put it in environment,
6655 * but remembers that it is exported,
6656 * and does put it in env when it is set later.
6657 * We just set it to "" and export. */
6658 name = xasprintf("%s=", name);
6659 } else {
6660 /* (Un)exporting NAME=VALUE */
6661 name = xstrdup(name);
6662 }
6663 set_local_var(name,
6664 /*export:*/ (opt_unexport ? -1 : 1),
6665 /*readonly:*/ 0
6666 );
6667 } while (*++argv);
6668 6763
6764#if ENABLE_HUSH_LOCAL
6765static int builtin_local(char **argv)
6766{
6767 if (G.func_nest_level == 0) {
6768 bb_error_msg("%s: not in a function", argv[0]);
6769 return EXIT_FAILURE; /* bash compat */
6770 }
6771 helper_export_local(argv, 0, G.func_nest_level);
6669 return EXIT_SUCCESS; 6772 return EXIT_SUCCESS;
6670} 6773}
6774#endif
6671 6775
6672static int builtin_trap(char **argv) 6776static int builtin_trap(char **argv)
6673{ 6777{
@@ -6944,7 +7048,7 @@ static int builtin_read(char **argv)
6944//TODO: bash unbackslashes input, splits words and puts them in argv[i] 7048//TODO: bash unbackslashes input, splits words and puts them in argv[i]
6945 7049
6946 string = xmalloc_reads(STDIN_FILENO, xasprintf("%s=", name), NULL); 7050 string = xmalloc_reads(STDIN_FILENO, xasprintf("%s=", name), NULL);
6947 return set_local_var(string, 0, 0); 7051 return set_local_var(string, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
6948} 7052}
6949 7053
6950/* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#set 7054/* 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 @@
1z=a
2z=z
3Done
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 @@
1export z=z
2f() { local z=a; env | grep ^z; }
3f
4env | grep ^z
5echo 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 @@
11
22
31
42
51
61
72
82
93
102
112
123
131
14Done
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 @@
1x=1
2f() { echo $x; local x=$((x+1)); echo $x; }
3g() { f; echo $x; f; local x=$((x+1)); f; echo $x; f; }
4f
5g
6echo $x
7echo Done