diff options
author | Denis Vlasenko <vda.linux@googlemail.com> | 2007-05-25 02:16:25 +0000 |
---|---|---|
committer | Denis Vlasenko <vda.linux@googlemail.com> | 2007-05-25 02:16:25 +0000 |
commit | d76c049cc4b1da8d5b42f0013ecdcb4d592c9282 (patch) | |
tree | 89ada1134f9e9dd148160b291ba6a142c00127e2 /shell/hush.c | |
parent | 163a8557310911bcf8b852a282c0aa855b778058 (diff) | |
download | busybox-w32-d76c049cc4b1da8d5b42f0013ecdcb4d592c9282.tar.gz busybox-w32-d76c049cc4b1da8d5b42f0013ecdcb4d592c9282.tar.bz2 busybox-w32-d76c049cc4b1da8d5b42f0013ecdcb4d592c9282.zip |
hush: rework variable storage and environment handling.
More that -100 bytes of code + memory leak plugged.
Added a testcase for it.
Diffstat (limited to 'shell/hush.c')
-rw-r--r-- | shell/hush.c | 330 |
1 files changed, 163 insertions, 167 deletions
diff --git a/shell/hush.c b/shell/hush.c index d96615d54..0b8e7d68d 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
@@ -37,7 +37,6 @@ | |||
37 | * across continuation lines. | 37 | * across continuation lines. |
38 | * | 38 | * |
39 | * Bash grammar not implemented: (how many of these were in original sh?) | 39 | * Bash grammar not implemented: (how many of these were in original sh?) |
40 | * $@ (those sure look like weird quoting rules) | ||
41 | * $_ | 40 | * $_ |
42 | * ! negation operator for pipes | 41 | * ! negation operator for pipes |
43 | * &> and >& redirection of stdout+stderr | 42 | * &> and >& redirection of stdout+stderr |
@@ -279,11 +278,18 @@ struct close_me { | |||
279 | int fd; | 278 | int fd; |
280 | }; | 279 | }; |
281 | 280 | ||
282 | struct variables { | 281 | /* On program start, environ points to initial environment. |
283 | struct variables *next; | 282 | * putenv adds new pointers into it, unsetenv removes them. |
284 | const char *name; | 283 | * Neither of these (de)allocates the strings. |
285 | const char *value; | 284 | * setenv allocates new strings in malloc space and does putenv, |
286 | smallint flg_export; | 285 | * and thus setenv is unusable (leaky) for shell's purposes */ |
286 | #define setenv(...) setenv_is_leaky_dont_use() | ||
287 | struct variable { | ||
288 | struct variable *next; | ||
289 | char *name; /* points to "name=" portion */ | ||
290 | char *value; /* points directly after "=" */ | ||
291 | int max_len; /* if > 0, name is part of initial env; else name is malloced */ | ||
292 | smallint flg_export; /* putenv should be done on this var */ | ||
287 | smallint flg_read_only; | 293 | smallint flg_read_only; |
288 | }; | 294 | }; |
289 | 295 | ||
@@ -322,14 +328,21 @@ enum { | |||
322 | CHAR_SPECIAL = 3, /* example: $ */ | 328 | CHAR_SPECIAL = 3, /* example: $ */ |
323 | }; | 329 | }; |
324 | 330 | ||
331 | #define HUSH_VER_STR "0.02" | ||
325 | 332 | ||
326 | /* "Globals" within this file */ | 333 | static const char version_str[] = "HUSH_VERSION="HUSH_VER_STR; |
327 | 334 | ||
328 | #define HUSH_VER_STR "0.02" | 335 | static const struct variable const_shell_ver = { |
329 | static const struct variables const_shell_ver = { | 336 | .next = NULL, |
330 | NULL, "HUSH_VERSION", HUSH_VER_STR, 1, 1 | 337 | .name = (char*)version_str, |
338 | .value = (char*)version_str + sizeof("HUSH_VERSION=")-1, | ||
339 | .max_len = 1, /* 0 can provoke free(name) */ | ||
340 | .flg_export = 1, | ||
341 | .flg_read_only = 1, | ||
331 | }; | 342 | }; |
332 | 343 | ||
344 | /* "Globals" within this file */ | ||
345 | |||
333 | /* Sorted roughly by size (smaller offsets == smaller code) */ | 346 | /* Sorted roughly by size (smaller offsets == smaller code) */ |
334 | struct globals { | 347 | struct globals { |
335 | #if ENABLE_HUSH_INTERACTIVE | 348 | #if ENABLE_HUSH_INTERACTIVE |
@@ -360,8 +373,8 @@ struct globals { | |||
360 | struct close_me *close_me_head; | 373 | struct close_me *close_me_head; |
361 | const char *cwd; | 374 | const char *cwd; |
362 | unsigned last_bg_pid; | 375 | unsigned last_bg_pid; |
363 | struct variables *top_vars; /* = &shell_ver (both are set in main()) */ | 376 | struct variable *top_var; /* = &shell_ver (both are set in main()) */ |
364 | struct variables shell_ver; /* = const_shell_ver */ | 377 | struct variable shell_ver; /* = const_shell_ver */ |
365 | #if ENABLE_FEATURE_SH_STANDALONE | 378 | #if ENABLE_FEATURE_SH_STANDALONE |
366 | struct nofork_save_area nofork_save; | 379 | struct nofork_save_area nofork_save; |
367 | #endif | 380 | #endif |
@@ -407,7 +420,7 @@ enum { run_list_level = 0 }; | |||
407 | #define close_me_head (G.close_me_head ) | 420 | #define close_me_head (G.close_me_head ) |
408 | #define cwd (G.cwd ) | 421 | #define cwd (G.cwd ) |
409 | #define last_bg_pid (G.last_bg_pid ) | 422 | #define last_bg_pid (G.last_bg_pid ) |
410 | #define top_vars (G.top_vars ) | 423 | #define top_var (G.top_var ) |
411 | #define shell_ver (G.shell_ver ) | 424 | #define shell_ver (G.shell_ver ) |
412 | #if ENABLE_FEATURE_SH_STANDALONE | 425 | #if ENABLE_FEATURE_SH_STANDALONE |
413 | #define nofork_save (G.nofork_save ) | 426 | #define nofork_save (G.nofork_save ) |
@@ -541,8 +554,8 @@ static char **expand_strvec_to_strvec(char **argv); | |||
541 | static char *expand_strvec_to_string(char **argv); | 554 | static char *expand_strvec_to_string(char **argv); |
542 | /* used for expansion of right hand of assignments */ | 555 | /* used for expansion of right hand of assignments */ |
543 | static char *expand_string_to_string(const char *str); | 556 | static char *expand_string_to_string(const char *str); |
544 | static const char *get_local_var(const char *var); | 557 | static struct variable *get_local_var(const char *var); |
545 | static int set_local_var(const char *s, int flg_export); | 558 | static int set_local_var(char *s, int flg_export); |
546 | static void unset_local_var(const char *name); | 559 | static void unset_local_var(const char *name); |
547 | 560 | ||
548 | /* Table of built-in functions. They can be forked or not, depending on | 561 | /* Table of built-in functions. They can be forked or not, depending on |
@@ -795,7 +808,7 @@ static int builtin_exit(char **argv) | |||
795 | /* built-in 'export VAR=value' handler */ | 808 | /* built-in 'export VAR=value' handler */ |
796 | static int builtin_export(char **argv) | 809 | static int builtin_export(char **argv) |
797 | { | 810 | { |
798 | int res = 0; | 811 | const char *value; |
799 | char *name = argv[1]; | 812 | char *name = argv[1]; |
800 | 813 | ||
801 | if (name == NULL) { | 814 | if (name == NULL) { |
@@ -810,36 +823,23 @@ static int builtin_export(char **argv) | |||
810 | return EXIT_SUCCESS; | 823 | return EXIT_SUCCESS; |
811 | } | 824 | } |
812 | 825 | ||
813 | name = xstrdup(name); | 826 | value = strchr(name, '='); |
814 | { | 827 | if (!value) { |
815 | const char *value = strchr(name, '='); | 828 | /* They are exporting something without a =VALUE */ |
816 | 829 | struct variable *var; | |
817 | if (!value) { | ||
818 | char *tmp; | ||
819 | /* They are exporting something without an =VALUE */ | ||
820 | |||
821 | value = get_local_var(name); | ||
822 | if (value) { | ||
823 | size_t ln = strlen(name); | ||
824 | 830 | ||
825 | tmp = xrealloc(name, ln+strlen(value)+2); | 831 | var = get_local_var(name); |
826 | sprintf(tmp+ln, "=%s", value); | 832 | if (var) { |
827 | name = tmp; | 833 | var->flg_export = 1; |
828 | } else { | 834 | putenv(var->name); |
829 | /* bash does not return an error when trying to export | ||
830 | * an undefined variable. Do likewise. */ | ||
831 | res = 1; | ||
832 | } | ||
833 | } | 835 | } |
836 | /* bash does not return an error when trying to export | ||
837 | * an undefined variable. Do likewise. */ | ||
838 | return EXIT_SUCCESS; | ||
834 | } | 839 | } |
835 | if (res < 0) | 840 | |
836 | bb_perror_msg("export"); | 841 | set_local_var(xstrdup(name), 1); |
837 | else if (res == 0) | 842 | return EXIT_SUCCESS; |
838 | res = set_local_var(name, 1); | ||
839 | else | ||
840 | res = 0; | ||
841 | free(name); | ||
842 | return res; | ||
843 | } | 843 | } |
844 | 844 | ||
845 | #if ENABLE_HUSH_JOB | 845 | #if ENABLE_HUSH_JOB |
@@ -965,20 +965,20 @@ static int builtin_read(char **argv) | |||
965 | /* read string. name_len+1 chars are already used by 'name=' */ | 965 | /* read string. name_len+1 chars are already used by 'name=' */ |
966 | fgets(p, sizeof(string) - 1 - name_len, stdin); | 966 | fgets(p, sizeof(string) - 1 - name_len, stdin); |
967 | chomp(p); | 967 | chomp(p); |
968 | return set_local_var(string, 0); | 968 | return set_local_var(xstrdup(string), 0); |
969 | } | 969 | } |
970 | 970 | ||
971 | /* built-in 'set [VAR=value]' handler */ | 971 | /* built-in 'set [VAR=value]' handler */ |
972 | static int builtin_set(char **argv) | 972 | static int builtin_set(char **argv) |
973 | { | 973 | { |
974 | char *temp = argv[1]; | 974 | char *temp = argv[1]; |
975 | struct variables *e; | 975 | struct variable *e; |
976 | 976 | ||
977 | if (temp == NULL) | 977 | if (temp == NULL) |
978 | for (e = top_vars; e; e = e->next) | 978 | for (e = top_var; e; e = e->next) |
979 | printf("%s=%s\n", e->name, e->value); | 979 | puts(e->name); |
980 | else | 980 | else |
981 | set_local_var(temp, 0); | 981 | set_local_var(xstrdup(temp), 0); |
982 | 982 | ||
983 | return EXIT_SUCCESS; | 983 | return EXIT_SUCCESS; |
984 | } | 984 | } |
@@ -1742,26 +1742,9 @@ static int run_pipe_real(struct pipe *pi) | |||
1742 | if (i != 0 && argv[i] == NULL) { | 1742 | if (i != 0 && argv[i] == NULL) { |
1743 | /* assignments, but no command: set the local environment */ | 1743 | /* assignments, but no command: set the local environment */ |
1744 | for (i = 0; argv[i] != NULL; i++) { | 1744 | for (i = 0; argv[i] != NULL; i++) { |
1745 | /* Ok, this case is tricky. We have to decide if this is a | 1745 | debug_printf("local environment set: %s\n", argv[i]); |
1746 | * local variable, or an already exported variable. If it is | ||
1747 | * already exported, we have to export the new value. If it is | ||
1748 | * not exported, we need only set this as a local variable. | ||
1749 | * This junk is all to decide whether or not to export this | ||
1750 | * variable. */ | ||
1751 | int export_me = 0; | ||
1752 | char *name, *value; | ||
1753 | name = xstrdup(argv[i]); | ||
1754 | debug_printf("local environment set: %s\n", name); | ||
1755 | value = strchr(name, '='); | ||
1756 | if (value) | ||
1757 | *value = '\0'; | ||
1758 | if (get_local_var(name)) { | ||
1759 | export_me = 1; | ||
1760 | } | ||
1761 | free(name); | ||
1762 | p = expand_string_to_string(argv[i]); | 1746 | p = expand_string_to_string(argv[i]); |
1763 | set_local_var(p, export_me); | 1747 | set_local_var(p, 0); |
1764 | free(p); | ||
1765 | } | 1748 | } |
1766 | return EXIT_SUCCESS; /* don't worry about errors in set_local_var() yet */ | 1749 | return EXIT_SUCCESS; /* don't worry about errors in set_local_var() yet */ |
1767 | } | 1750 | } |
@@ -2693,109 +2676,115 @@ static char* expand_strvec_to_string(char **argv) | |||
2693 | } | 2676 | } |
2694 | 2677 | ||
2695 | /* This is used to get/check local shell variables */ | 2678 | /* This is used to get/check local shell variables */ |
2696 | static const char *get_local_var(const char *s) | 2679 | static struct variable *get_local_var(const char *s) |
2697 | { | 2680 | { |
2698 | struct variables *cur; | 2681 | struct variable *cur; |
2682 | int len; | ||
2699 | 2683 | ||
2700 | if (!s) | 2684 | if (!s) |
2701 | return NULL; | 2685 | return NULL; |
2702 | for (cur = top_vars; cur; cur = cur->next) { | 2686 | len = strlen(s); |
2703 | if (strcmp(cur->name, s) == 0) | 2687 | for (cur = top_var; cur; cur = cur->next) { |
2704 | return cur->value; | 2688 | if (strncmp(cur->name, s, len) == 0 && cur->name[len] == '=') |
2689 | return cur; | ||
2705 | } | 2690 | } |
2706 | return NULL; | 2691 | return NULL; |
2707 | } | 2692 | } |
2708 | 2693 | ||
2709 | /* This is used to set local shell variables | 2694 | /* name holds "NAME=VAL" and is expected to be malloced. |
2710 | flg_export == 0 if only local (not exporting) variable | 2695 | * We take ownership of it. */ |
2711 | flg_export == 1 if "new" exporting environ | 2696 | static int set_local_var(char *name, int flg_export) |
2712 | flg_export > 1 if current startup environ (not call putenv()) */ | ||
2713 | static int set_local_var(const char *s, int flg_export) | ||
2714 | { | 2697 | { |
2715 | char *name, *value; | 2698 | struct variable *cur; |
2716 | int result = 0; | 2699 | char *value; |
2717 | struct variables *cur; | 2700 | int name_len; |
2718 | |||
2719 | name = xstrdup(s); | ||
2720 | 2701 | ||
2721 | /* Assume when we enter this function that we are already in | ||
2722 | * NAME=VALUE format. So the first order of business is to | ||
2723 | * split 's' on the '=' into 'name' and 'value' */ | ||
2724 | value = strchr(name, '='); | 2702 | value = strchr(name, '='); |
2725 | /*if (value == 0 && ++value == 0) ??? -vda */ | 2703 | if (!value) { /* not expected to ever happen? */ |
2726 | if (value == NULL || value[1] == '\0') { | ||
2727 | free(name); | 2704 | free(name); |
2728 | return -1; | 2705 | return -1; |
2729 | } | 2706 | } |
2730 | *value++ = '\0'; | ||
2731 | 2707 | ||
2732 | for (cur = top_vars; cur; cur = cur->next) { | 2708 | name_len = value - name; |
2733 | if (strcmp(cur->name, name) == 0) { | 2709 | cur = top_var; /* cannot be NULL (we have HUSH_VERSION and it's RO) */ |
2734 | if (strcmp(cur->value, value) == 0) { | 2710 | while (1) { |
2735 | if (flg_export && !cur->flg_export) | 2711 | if (strncmp(cur->name, name, name_len) != 0 || cur->name[name_len] != '=') { |
2736 | cur->flg_export = flg_export; | 2712 | if (!cur->next) { |
2737 | else | 2713 | /* cur points to last var in linked list */ |
2738 | result++; | 2714 | break; |
2739 | } else if (cur->flg_read_only) { | ||
2740 | bb_error_msg("%s: readonly variable", name); | ||
2741 | result = -1; | ||
2742 | } else { | ||
2743 | if (flg_export > 0 || cur->flg_export > 1) | ||
2744 | cur->flg_export = 1; | ||
2745 | free((char*)cur->value); | ||
2746 | cur->value = xstrdup(value); | ||
2747 | } | 2715 | } |
2748 | goto skip; | 2716 | cur = cur->next; |
2717 | continue; | ||
2749 | } | 2718 | } |
2750 | } | 2719 | /* We already have a var with this name */ |
2751 | 2720 | if (cur->flg_read_only) { | |
2752 | cur = xzalloc(sizeof(*cur)); | 2721 | bb_error_msg("%s: readonly variable", name); |
2753 | /*cur->next = 0;*/ | 2722 | free(name); |
2754 | cur->name = xstrdup(name); | 2723 | return -1; |
2755 | cur->value = xstrdup(value); | 2724 | } |
2756 | cur->flg_export = flg_export; | 2725 | *value = '\0'; |
2757 | /*cur->flg_read_only = 0;*/ | 2726 | unsetenv(name); /* just in case */ |
2758 | { | 2727 | *value++ = '='; |
2759 | struct variables *bottom = top_vars; | 2728 | if (strcmp(cur->value, value) == 0) { |
2760 | while (bottom->next) | 2729 | free_and_exp: |
2761 | bottom = bottom->next; | 2730 | free(name); |
2762 | bottom->next = cur; | 2731 | goto exp; |
2763 | } | 2732 | } |
2764 | skip: | 2733 | if (cur->max_len >= strlen(name)) { |
2765 | if (result == 0 && cur->flg_export == 1) { | 2734 | /* This one is from startup env, reuse space */ |
2766 | *(value-1) = '='; | 2735 | strcpy(cur->name, name); |
2767 | result = putenv(name); | 2736 | goto free_and_exp; |
2768 | } else { | 2737 | } |
2769 | free(name); | 2738 | /* max_len == 0 signifies "malloced" var, which we can |
2770 | if (result > 0) /* equivalent to previous set */ | 2739 | * (and has to) free */ |
2771 | result = 0; | 2740 | if (!cur->max_len) |
2772 | } | 2741 | free(cur->name); |
2773 | return result; | 2742 | cur->max_len = 0; |
2743 | goto set_name_and_exp; | ||
2744 | } | ||
2745 | |||
2746 | /* Not found - create next variable struct */ | ||
2747 | cur->next = xzalloc(sizeof(*cur)); | ||
2748 | cur = cur->next; | ||
2749 | |||
2750 | set_name_and_exp: | ||
2751 | cur->name = name; | ||
2752 | exp: | ||
2753 | cur->value = cur->name + name_len + 1; | ||
2754 | if (flg_export) | ||
2755 | cur->flg_export = 1; | ||
2756 | if (cur->flg_export) | ||
2757 | return putenv(cur->name); | ||
2758 | return 0; | ||
2774 | } | 2759 | } |
2775 | 2760 | ||
2776 | static void unset_local_var(const char *name) | 2761 | static void unset_local_var(const char *name) |
2777 | { | 2762 | { |
2778 | struct variables *cur, *next; | 2763 | struct variable *cur; |
2764 | struct variable *prev = prev; /* for gcc */ | ||
2765 | int name_len; | ||
2779 | 2766 | ||
2780 | if (!name) | 2767 | if (!name) |
2781 | return; | 2768 | return; |
2782 | for (cur = top_vars; cur; cur = cur->next) { | 2769 | name_len = strlen(name); |
2783 | if (strcmp(cur->name, name) == 0) { | 2770 | cur = top_var; |
2771 | while (cur) { | ||
2772 | if (strncmp(cur->name, name, name_len) == 0 && cur->name[name_len] == '=') { | ||
2784 | if (cur->flg_read_only) { | 2773 | if (cur->flg_read_only) { |
2785 | bb_error_msg("%s: readonly variable", name); | 2774 | bb_error_msg("%s: readonly variable", name); |
2786 | return; | 2775 | return; |
2787 | } | 2776 | } |
2788 | if (cur->flg_export) | 2777 | /* prev is ok to use here because 1st variable, HUSH_VERSION, |
2789 | unsetenv(cur->name); | 2778 | * is ro, and we cannot reach this code on the 1st pass */ |
2790 | free((char*)cur->name); | 2779 | prev->next = cur->next; |
2791 | free((char*)cur->value); | 2780 | unsetenv(cur->name); |
2792 | next = top_vars; | 2781 | if (!cur->max_len) |
2793 | while (next->next != cur) | 2782 | free(cur->name); |
2794 | next = next->next; | ||
2795 | next->next = cur->next; | ||
2796 | free(cur); | 2783 | free(cur); |
2797 | return; | 2784 | return; |
2798 | } | 2785 | } |
2786 | prev = cur; | ||
2787 | cur = cur->next; | ||
2799 | } | 2788 | } |
2800 | } | 2789 | } |
2801 | 2790 | ||
@@ -3265,13 +3254,10 @@ static int parse_group(o_string *dest, struct p_context *ctx, | |||
3265 | * see the bash man page under "Parameter Expansion" */ | 3254 | * see the bash man page under "Parameter Expansion" */ |
3266 | static const char *lookup_param(const char *src) | 3255 | static const char *lookup_param(const char *src) |
3267 | { | 3256 | { |
3268 | const char *p = NULL; | 3257 | struct variable *var = get_local_var(src); |
3269 | if (src) { | 3258 | if (var) |
3270 | p = getenv(src); | 3259 | return var->value; |
3271 | if (!p) | 3260 | return NULL; |
3272 | p = get_local_var(src); | ||
3273 | } | ||
3274 | return p; | ||
3275 | } | 3261 | } |
3276 | 3262 | ||
3277 | /* return code: 0 for OK, 1 for syntax error */ | 3263 | /* return code: 0 for OK, 1 for syntax error */ |
@@ -3681,10 +3667,29 @@ int hush_main(int argc, char **argv) | |||
3681 | int opt; | 3667 | int opt; |
3682 | FILE *input; | 3668 | FILE *input; |
3683 | char **e; | 3669 | char **e; |
3670 | struct variable *cur_var; | ||
3684 | 3671 | ||
3685 | PTR_TO_GLOBALS = xzalloc(sizeof(G)); | 3672 | PTR_TO_GLOBALS = xzalloc(sizeof(G)); |
3686 | top_vars = &shell_ver; | 3673 | |
3687 | shell_ver = const_shell_ver; /* copying struct here */ | 3674 | shell_ver = const_shell_ver; /* copying struct here */ |
3675 | top_var = &shell_ver; | ||
3676 | /* initialize our shell local variables with the values | ||
3677 | * currently living in the environment */ | ||
3678 | e = environ; | ||
3679 | cur_var = top_var; | ||
3680 | if (e) while (*e) { | ||
3681 | char *value = strchr(*e, '='); | ||
3682 | if (value) { /* paranoia */ | ||
3683 | cur_var->next = xzalloc(sizeof(*cur_var)); | ||
3684 | cur_var = cur_var->next; | ||
3685 | cur_var->name = *e; | ||
3686 | cur_var->value = value + 1; | ||
3687 | cur_var->max_len = strlen(*e); | ||
3688 | cur_var->flg_export = 1; | ||
3689 | } | ||
3690 | e++; | ||
3691 | } | ||
3692 | putenv(shell_ver.name); | ||
3688 | 3693 | ||
3689 | #if ENABLE_FEATURE_EDITING | 3694 | #if ENABLE_FEATURE_EDITING |
3690 | line_input_state = new_line_input_t(FOR_SHELL); | 3695 | line_input_state = new_line_input_t(FOR_SHELL); |
@@ -3701,14 +3706,8 @@ int hush_main(int argc, char **argv) | |||
3701 | PS2 = "> "; | 3706 | PS2 = "> "; |
3702 | #endif | 3707 | #endif |
3703 | 3708 | ||
3704 | /* initialize our shell local variables with the values | 3709 | if (EXIT_SUCCESS) /* otherwise is already done */ |
3705 | * currently living in the environment */ | 3710 | last_return_code = EXIT_SUCCESS; |
3706 | e = environ; | ||
3707 | if (e) | ||
3708 | while (*e) | ||
3709 | set_local_var(*e++, 2); /* without call putenv() */ | ||
3710 | |||
3711 | last_return_code = EXIT_SUCCESS; | ||
3712 | 3711 | ||
3713 | if (argv[0] && argv[0][0] == '-') { | 3712 | if (argv[0] && argv[0][0] == '-') { |
3714 | debug_printf("sourcing /etc/profile\n"); | 3713 | debug_printf("sourcing /etc/profile\n"); |
@@ -3818,23 +3817,20 @@ int hush_main(int argc, char **argv) | |||
3818 | input = xfopen(argv[optind], "r"); | 3817 | input = xfopen(argv[optind], "r"); |
3819 | opt = parse_and_run_file(input); | 3818 | opt = parse_and_run_file(input); |
3820 | 3819 | ||
3820 | final_return: | ||
3821 | |||
3821 | #if ENABLE_FEATURE_CLEAN_UP | 3822 | #if ENABLE_FEATURE_CLEAN_UP |
3822 | fclose(input); | 3823 | fclose(input); |
3823 | if (cwd != bb_msg_unknown) | 3824 | if (cwd != bb_msg_unknown) |
3824 | free((char*)cwd); | 3825 | free((char*)cwd); |
3825 | { | 3826 | cur_var = top_var->next; |
3826 | struct variables *cur, *tmp; | 3827 | while (cur_var) { |
3827 | for (cur = top_vars; cur; cur = tmp) { | 3828 | struct variable *tmp = cur_var; |
3828 | tmp = cur->next; | 3829 | if (!cur_var->max_len) |
3829 | if (!cur->flg_read_only) { | 3830 | free(cur_var->name); |
3830 | free((char*)cur->name); | 3831 | cur_var = cur_var->next; |
3831 | free((char*)cur->value); | 3832 | free(tmp); |
3832 | free(cur); | ||
3833 | } | ||
3834 | } | ||
3835 | } | 3833 | } |
3836 | #endif | 3834 | #endif |
3837 | |||
3838 | final_return: | ||
3839 | hush_exit(opt ? opt : last_return_code); | 3835 | hush_exit(opt ? opt : last_return_code); |
3840 | } | 3836 | } |