aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenis Vlasenko <vda.linux@googlemail.com>2007-05-23 13:01:10 +0000
committerDenis Vlasenko <vda.linux@googlemail.com>2007-05-23 13:01:10 +0000
commit170435c575bb53e1e4d55aa74d471c293c21039e (patch)
tree7bde6bd5f00ee9c91a78b5f707657dee4ae40b57
parent1a7358612ff2dfdfc9fa461faa946c577666787f (diff)
downloadbusybox-w32-170435c575bb53e1e4d55aa74d471c293c21039e.tar.gz
busybox-w32-170435c575bb53e1e4d55aa74d471c293c21039e.tar.bz2
busybox-w32-170435c575bb53e1e4d55aa74d471c293c21039e.zip
hush: fix job control with eval /bin/external_prog
hush: fix parsing of unterminated "str with no EOL hush: improved make_string() (smaller, faster, needs less RAM) hush: renamed several functions
-rw-r--r--shell/hush.c185
-rw-r--r--shell/hush_test/hush-bugs/noeol3.right1
-rwxr-xr-xshell/hush_test/hush-bugs/noeol3.tests2
3 files changed, 96 insertions, 92 deletions
diff --git a/shell/hush.c b/shell/hush.c
index aab6ff3a3..1545b041f 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -117,6 +117,9 @@ extern char **environ; /* This is in <unistd.h>, but protected with __USE_GNU */
117#define DEBUG_EXPAND 1 117#define DEBUG_EXPAND 1
118#endif 118#endif
119 119
120/* Keep unconditionally on for now */
121#define ENABLE_HUSH_DEBUG 1
122
120#ifndef debug_printf_clean 123#ifndef debug_printf_clean
121/* broken, of course, but OK for testing */ 124/* broken, of course, but OK for testing */
122static const char *indenter(int i) 125static const char *indenter(int i)
@@ -497,13 +500,12 @@ static int process_command_subs(o_string *dest, struct p_context *ctx, struct in
497#endif 500#endif
498static int parse_group(o_string *dest, struct p_context *ctx, struct in_str *input, int ch); 501static int parse_group(o_string *dest, struct p_context *ctx, struct in_str *input, int ch);
499static const char *lookup_param(const char *src); 502static const char *lookup_param(const char *src);
500static char *make_string(char **inp);
501static int handle_dollar(o_string *dest, struct p_context *ctx, struct in_str *input); 503static int handle_dollar(o_string *dest, struct p_context *ctx, struct in_str *input);
502static int parse_stream(o_string *dest, struct p_context *ctx, struct in_str *input0, const char *end_trigger); 504static int parse_stream(o_string *dest, struct p_context *ctx, struct in_str *input0, const char *end_trigger);
503/* setup: */ 505/* setup: */
504static int parse_stream_outer(struct in_str *inp, int parse_flag); 506static int parse_and_run_stream(struct in_str *inp, int parse_flag);
505static int parse_string_outer(const char *s, int parse_flag); 507static int parse_and_run_string(const char *s, int parse_flag);
506static int parse_file_outer(FILE *f); 508static int parse_and_run_file(FILE *f);
507/* job management: */ 509/* job management: */
508static int checkjobs(struct pipe* fg_pipe); 510static int checkjobs(struct pipe* fg_pipe);
509#if ENABLE_HUSH_JOB 511#if ENABLE_HUSH_JOB
@@ -515,9 +517,11 @@ static void delete_finished_bg_job(struct pipe *pi);
515int checkjobs_and_fg_shell(struct pipe* fg_pipe); /* never called */ 517int checkjobs_and_fg_shell(struct pipe* fg_pipe); /* never called */
516#endif 518#endif
517/* local variable support */ 519/* local variable support */
518static char **expand_variables_to_list(char **argv); 520static char **expand_strvec_to_strvec(char **argv);
521/* used for eval */
522static char *expand_strvec_to_string(char **argv);
519/* used for expansion of right hand of assignments */ 523/* used for expansion of right hand of assignments */
520static char *expand_variables_to_string(const char *str); 524static char *expand_string_to_string(const char *str);
521static const char *get_local_var(const char *var); 525static const char *get_local_var(const char *var);
522static int set_local_var(const char *s, int flg_export); 526static int set_local_var(const char *s, int flg_export);
523static void unset_local_var(const char *name); 527static void unset_local_var(const char *name);
@@ -716,12 +720,11 @@ static const char *set_cwd(void)
716/* built-in 'eval' handler */ 720/* built-in 'eval' handler */
717static int builtin_eval(char **argv) 721static int builtin_eval(char **argv)
718{ 722{
719 char *str = NULL;
720 int rcode = EXIT_SUCCESS; 723 int rcode = EXIT_SUCCESS;
721 724
722 if (argv[1]) { 725 if (argv[1]) {
723 str = make_string(argv + 1); 726 char *str = expand_strvec_to_string(argv + 1);
724 parse_string_outer(str, PARSEFLAG_EXIT_FROM_LOOP | 727 parse_and_run_string(str, PARSEFLAG_EXIT_FROM_LOOP |
725 PARSEFLAG_SEMICOLON); 728 PARSEFLAG_SEMICOLON);
726 free(str); 729 free(str);
727 rcode = last_return_code; 730 rcode = last_return_code;
@@ -732,9 +735,9 @@ static int builtin_eval(char **argv)
732/* built-in 'cd <path>' handler */ 735/* built-in 'cd <path>' handler */
733static int builtin_cd(char **argv) 736static int builtin_cd(char **argv)
734{ 737{
735 char *newdir; 738 const char *newdir;
736 if (argv[1] == NULL) 739 if (argv[1] == NULL)
737 newdir = getenv("HOME"); 740 newdir = getenv("HOME") ? : "/";
738 else 741 else
739 newdir = argv[1]; 742 newdir = argv[1];
740 if (chdir(newdir)) { 743 if (chdir(newdir)) {
@@ -999,7 +1002,7 @@ static int builtin_source(char **argv)
999 * (pointer only is OK!) on this stack frame, 1002 * (pointer only is OK!) on this stack frame,
1000 * set global_argv=argv+1, recurse, and restore. */ 1003 * set global_argv=argv+1, recurse, and restore. */
1001 mark_open(fileno(input)); 1004 mark_open(fileno(input));
1002 status = parse_file_outer(input); 1005 status = parse_and_run_file(input);
1003 mark_closed(fileno(input)); 1006 mark_closed(fileno(input));
1004 fclose(input); 1007 fclose(input);
1005 return status; 1008 return status;
@@ -1146,12 +1149,10 @@ static void get_user_input(struct in_str *i)
1146 1149
1147 prompt_str = setup_prompt_string(i->promptmode); 1150 prompt_str = setup_prompt_string(i->promptmode);
1148#if ENABLE_FEATURE_EDITING 1151#if ENABLE_FEATURE_EDITING
1149 /* 1152 /* Enable command line editing only while a command line
1150 ** enable command line editing only while a command line 1153 * is actually being read; otherwise, we'll end up bequeathing
1151 ** is actually being read; otherwise, we'll end up bequeathing 1154 * atexit() handlers and other unwanted stuff to our
1152 ** atexit() handlers and other unwanted stuff to our 1155 * child processes (rob@sysgo.de) */
1153 ** child processes (rob@sysgo.de)
1154 */
1155 r = read_line_input(prompt_str, user_input_buf, BUFSIZ-1, line_input_state); 1156 r = read_line_input(prompt_str, user_input_buf, BUFSIZ-1, line_input_state);
1156 i->eof_flag = (r < 0); 1157 i->eof_flag = (r < 0);
1157 if (i->eof_flag) { /* EOF/error detected */ 1158 if (i->eof_flag) { /* EOF/error detected */
@@ -1343,8 +1344,8 @@ static void pseudo_exec_argv(char **argv)
1343 debug_printf_exec("pid %d environment modification: %s\n", 1344 debug_printf_exec("pid %d environment modification: %s\n",
1344 getpid(), argv[i]); 1345 getpid(), argv[i]);
1345// FIXME: vfork case?? 1346// FIXME: vfork case??
1346 p = expand_variables_to_string(argv[i]); 1347 p = expand_string_to_string(argv[i]);
1347 putenv(p == argv[i] ? xstrdup(p) : p); 1348 putenv(p);
1348 } 1349 }
1349 argv += i; 1350 argv += i;
1350 /* If a variable is assigned in a forest, and nobody listens, 1351 /* If a variable is assigned in a forest, and nobody listens,
@@ -1354,7 +1355,7 @@ static void pseudo_exec_argv(char **argv)
1354 _exit(EXIT_SUCCESS); 1355 _exit(EXIT_SUCCESS);
1355 } 1356 }
1356 1357
1357 argv = expand_variables_to_list(argv); 1358 argv = expand_strvec_to_strvec(argv);
1358 1359
1359 /* 1360 /*
1360 * Check if the command matches any of the builtins. 1361 * Check if the command matches any of the builtins.
@@ -1652,8 +1653,8 @@ static int checkjobs_and_fg_shell(struct pipe* fg_pipe)
1652 pid_t p; 1653 pid_t p;
1653 int rcode = checkjobs(fg_pipe); 1654 int rcode = checkjobs(fg_pipe);
1654 /* Job finished, move the shell to the foreground */ 1655 /* Job finished, move the shell to the foreground */
1655 p = getpgid(0); 1656 p = getpgid(0); /* pgid of our process */
1656 debug_printf("fg'ing ourself: getpgid(0)=%d\n", (int)p); 1657 debug_printf_jobs("fg'ing ourself: getpgid(0)=%d\n", (int)p);
1657 if (tcsetpgrp(interactive_fd, p) && errno != ENOTTY) 1658 if (tcsetpgrp(interactive_fd, p) && errno != ENOTTY)
1658 bb_perror_msg("tcsetpgrp-4a"); 1659 bb_perror_msg("tcsetpgrp-4a");
1659 return rcode; 1660 return rcode;
@@ -1675,6 +1676,9 @@ static int checkjobs_and_fg_shell(struct pipe* fg_pipe)
1675 * subshell, when that is in fact necessary. The subshell process 1676 * subshell, when that is in fact necessary. The subshell process
1676 * now has its stdout directed to the input of the appropriate pipe, 1677 * now has its stdout directed to the input of the appropriate pipe,
1677 * so this routine is noticeably simpler. 1678 * so this routine is noticeably simpler.
1679 *
1680 * Returns -1 only if started some children. IOW: we have to
1681 * mask out retvals of builtins etc with 0xff!
1678 */ 1682 */
1679static int run_pipe_real(struct pipe *pi) 1683static int run_pipe_real(struct pipe *pi)
1680{ 1684{
@@ -1710,7 +1714,7 @@ static int run_pipe_real(struct pipe *pi)
1710 rcode = run_list_real(child->group); 1714 rcode = run_list_real(child->group);
1711 restore_redirects(squirrel); 1715 restore_redirects(squirrel);
1712 debug_printf_exec("run_pipe_real return %d\n", rcode); 1716 debug_printf_exec("run_pipe_real return %d\n", rcode);
1713 return rcode; 1717 return rcode; // do we need to add '... & 0xff' ?
1714 } 1718 }
1715 1719
1716 if (single_fg && child->argv != NULL) { 1720 if (single_fg && child->argv != NULL) {
@@ -1739,21 +1743,16 @@ static int run_pipe_real(struct pipe *pi)
1739 export_me = 1; 1743 export_me = 1;
1740 } 1744 }
1741 free(name); 1745 free(name);
1742 p = expand_variables_to_string(argv[i]); 1746 p = expand_string_to_string(argv[i]);
1743 set_local_var(p, export_me); 1747 set_local_var(p, export_me);
1744 if (p != argv[i]) 1748 free(p);
1745 free(p);
1746 } 1749 }
1747 return EXIT_SUCCESS; /* don't worry about errors in set_local_var() yet */ 1750 return EXIT_SUCCESS; /* don't worry about errors in set_local_var() yet */
1748 } 1751 }
1749 for (i = 0; is_assignment(argv[i]); i++) { 1752 for (i = 0; is_assignment(argv[i]); i++) {
1750 p = expand_variables_to_string(argv[i]); 1753 p = expand_string_to_string(argv[i]);
1751 if (p != argv[i]) { 1754 //sp: child->sp--;
1752 //sp: child->sp--; 1755 putenv(p);
1753 putenv(p);
1754 } else {
1755 putenv(xstrdup(p));
1756 }
1757 } 1756 }
1758 for (x = bltins; x->cmd; x++) { 1757 for (x = bltins; x->cmd; x++) {
1759 if (strcmp(argv[i], x->cmd) == 0) { 1758 if (strcmp(argv[i], x->cmd) == 0) {
@@ -1770,8 +1769,8 @@ static int run_pipe_real(struct pipe *pi)
1770 setup_redirects(child, squirrel); 1769 setup_redirects(child, squirrel);
1771 debug_printf_exec(": builtin '%s' '%s'...\n", x->cmd, argv[i+1]); 1770 debug_printf_exec(": builtin '%s' '%s'...\n", x->cmd, argv[i+1]);
1772 //sp: if (child->sp) /* btw we can do it unconditionally... */ 1771 //sp: if (child->sp) /* btw we can do it unconditionally... */
1773 argv_expanded = expand_variables_to_list(argv + i); 1772 argv_expanded = expand_strvec_to_strvec(argv + i);
1774 rcode = x->function(argv_expanded); 1773 rcode = x->function(argv_expanded) & 0xff;
1775 free(argv_expanded); 1774 free(argv_expanded);
1776 restore_redirects(squirrel); 1775 restore_redirects(squirrel);
1777 debug_printf_exec("run_pipe_real return %d\n", rcode); 1776 debug_printf_exec("run_pipe_real return %d\n", rcode);
@@ -1786,9 +1785,9 @@ static int run_pipe_real(struct pipe *pi)
1786 save_nofork_data(&nofork_save); 1785 save_nofork_data(&nofork_save);
1787 argv_expanded = argv + i; 1786 argv_expanded = argv + i;
1788 //sp: if (child->sp) 1787 //sp: if (child->sp)
1789 argv_expanded = expand_variables_to_list(argv + i); 1788 argv_expanded = expand_strvec_to_strvec(argv + i);
1790 debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", argv_expanded[0], argv_expanded[1]); 1789 debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", argv_expanded[0], argv_expanded[1]);
1791 rcode = run_nofork_applet_prime(&nofork_save, a, argv_expanded); 1790 rcode = run_nofork_applet_prime(&nofork_save, a, argv_expanded) & 0xff;
1792 free(argv_expanded); 1791 free(argv_expanded);
1793 restore_redirects(squirrel); 1792 restore_redirects(squirrel);
1794 debug_printf_exec("run_pipe_real return %d\n", rcode); 1793 debug_printf_exec("run_pipe_real return %d\n", rcode);
@@ -1832,7 +1831,7 @@ static int run_pipe_real(struct pipe *pi)
1832 /* Every child adds itself to new process group 1831 /* Every child adds itself to new process group
1833 * with pgid == pid of first child in pipe */ 1832 * with pgid == pid of first child in pipe */
1834#if ENABLE_HUSH_JOB 1833#if ENABLE_HUSH_JOB
1835 if (interactive_fd) { 1834 if (run_list_level == 1 && interactive_fd) {
1836 /* Don't do pgrp restore anymore on fatal signals */ 1835 /* Don't do pgrp restore anymore on fatal signals */
1837 set_fatal_sighandler(SIG_DFL); 1836 set_fatal_sighandler(SIG_DFL);
1838 if (pi->pgrp < 0) /* true for 1st process only */ 1837 if (pi->pgrp < 0) /* true for 1st process only */
@@ -2078,7 +2077,7 @@ static int run_list_real(struct pipe *pi)
2078 if (!pi->next->progs->argv) 2077 if (!pi->next->progs->argv)
2079 continue; 2078 continue;
2080 /* create list of variable values */ 2079 /* create list of variable values */
2081 for_list = expand_variables_to_list(pi->next->progs->argv); 2080 for_list = expand_strvec_to_strvec(pi->next->progs->argv);
2082 for_lcur = for_list; 2081 for_lcur = for_list;
2083 for_varname = pi->progs->argv[0]; 2082 for_varname = pi->progs->argv[0];
2084 pi->progs->argv[0] = NULL; 2083 pi->progs->argv[0] = NULL;
@@ -2122,24 +2121,24 @@ static int run_list_real(struct pipe *pi)
2122 * of run_pipe_real(), and we don't need to wait for anything. */ 2121 * of run_pipe_real(), and we don't need to wait for anything. */
2123 } else if (pi->followup == PIPE_BG) { 2122 } else if (pi->followup == PIPE_BG) {
2124 /* What does bash do with attempts to background builtins? */ 2123 /* What does bash do with attempts to background builtins? */
2125
2126 /* Even bash 3.2 doesn't do that well with nested bg: 2124 /* Even bash 3.2 doesn't do that well with nested bg:
2127 * try "{ { sleep 10; echo DEEP; } & echo HERE; } &". 2125 * try "{ { sleep 10; echo DEEP; } & echo HERE; } &".
2128 * I'm considering NOT treating inner bgs as jobs - 2126 * I'm NOT treating inner &'s as jobs */
2129 * thus maybe "if (run_list_level == 1 && pi->followup == PIPE_BG)"
2130 * above? */
2131#if ENABLE_HUSH_JOB 2127#if ENABLE_HUSH_JOB
2132 insert_bg_job(pi); 2128 if (run_list_level == 1)
2129 insert_bg_job(pi);
2133#endif 2130#endif
2134 rcode = EXIT_SUCCESS; 2131 rcode = EXIT_SUCCESS;
2135 } else { 2132 } else {
2136#if ENABLE_HUSH_JOB 2133#if ENABLE_HUSH_JOB
2137 /* Paranoia, just "interactive_fd" should be enough */ 2134 /* Paranoia, just "interactive_fd" should be enough? */
2138 if (run_list_level == 1 && interactive_fd) { 2135 if (run_list_level == 1 && interactive_fd) {
2136 /* waits for completion, then fg's main shell */
2139 rcode = checkjobs_and_fg_shell(pi); 2137 rcode = checkjobs_and_fg_shell(pi);
2140 } else 2138 } else
2141#endif 2139#endif
2142 { 2140 {
2141 /* this one just waits for completion */
2143 rcode = checkjobs(pi); 2142 rcode = checkjobs(pi);
2144 } 2143 }
2145 debug_printf_exec(": checkjobs returned %d\n", rcode); 2144 debug_printf_exec(": checkjobs returned %d\n", rcode);
@@ -2343,7 +2342,7 @@ static int xglob(o_string *dest, int flags, glob_t *pglob)
2343 return gr; 2342 return gr;
2344} 2343}
2345 2344
2346/* expand_variables_to_list() takes a list of strings, expands 2345/* expand_strvec_to_strvec() takes a list of strings, expands
2347 * all variable references within and returns a pointer to 2346 * all variable references within and returns a pointer to
2348 * a list of expanded strings, possibly with larger number 2347 * a list of expanded strings, possibly with larger number
2349 * of strings. (Think VAR="a b"; echo $VAR). 2348 * of strings. (Think VAR="a b"; echo $VAR).
@@ -2629,29 +2628,51 @@ static char **expand_variables(char **argv, char or_mask)
2629 debug_printf_expand("used_space=%d\n", pos - (char*)list); 2628 debug_printf_expand("used_space=%d\n", pos - (char*)list);
2630 } 2629 }
2631#endif 2630#endif
2632 /* To be removed / made conditional later. */ 2631 if (ENABLE_HUSH_DEBUG)
2633 if (pos - (char*)list > len) 2632 if (pos - (char*)list > len)
2634 bb_error_msg_and_die("BUG in varexp"); 2633 bb_error_msg_and_die("BUG in varexp");
2635 return list; 2634 return list;
2636} 2635}
2637 2636
2638static char **expand_variables_to_list(char **argv) 2637static char **expand_strvec_to_strvec(char **argv)
2639{ 2638{
2640 return expand_variables(argv, 0); 2639 return expand_variables(argv, 0);
2641} 2640}
2642 2641
2643static char *expand_variables_to_string(const char *str) 2642static char *expand_string_to_string(const char *str)
2644{ 2643{
2645 char *argv[2], **list; 2644 char *argv[2], **list;
2646 2645
2647 argv[0] = (char*)str; 2646 argv[0] = (char*)str;
2648 argv[1] = NULL; 2647 argv[1] = NULL;
2649 list = expand_variables(argv, 0x80); /* 0x80: make one-element expansion */ 2648 list = expand_variables(argv, 0x80); /* 0x80: make one-element expansion */
2650 /* To be removed / made conditional later. */ 2649 if (ENABLE_HUSH_DEBUG)
2651 if (!list[0] || list[1]) 2650 if (!list[0] || list[1])
2652 bb_error_msg_and_die("BUG in varexp"); 2651 bb_error_msg_and_die("BUG in varexp2");
2653 /* actually, just move string 2*sizeof(char*) bytes back */ 2652 /* actually, just move string 2*sizeof(char*) bytes back */
2654 strcpy((char*)list, list[0]); 2653 strcpy((char*)list, list[0]);
2654 debug_printf_expand("string_to_string='%s'\n", (char*)list);
2655 return (char*)list;
2656}
2657
2658static char* expand_strvec_to_string(char **argv)
2659{
2660 char **list;
2661
2662 list = expand_variables(argv, 0x80);
2663 /* Convert all NULs to spaces */
2664 if (list[0]) {
2665 int n = 1;
2666 while (list[n]) {
2667 if (ENABLE_HUSH_DEBUG)
2668 if (list[n-1] + strlen(list[n-1]) + 1 != list[n])
2669 bb_error_msg_and_die("BUG in varexp3");
2670 list[n][-1] = ' '; /* TODO: or to ifs[0]? */
2671 n++;
2672 }
2673 }
2674 strcpy((char*)list, list[0]);
2675 debug_printf_expand("strvec_to_string='%s'\n", (char*)list);
2655 return (char*)list; 2676 return (char*)list;
2656} 2677}
2657 2678
@@ -2713,9 +2734,9 @@ static int set_local_var(const char *s, int flg_export)
2713 } 2734 }
2714 2735
2715 cur = xzalloc(sizeof(*cur)); 2736 cur = xzalloc(sizeof(*cur));
2737 /*cur->next = 0;*/
2716 cur->name = xstrdup(name); 2738 cur->name = xstrdup(name);
2717 cur->value = xstrdup(value); 2739 cur->value = xstrdup(value);
2718 /*cur->next = 0;*/
2719 cur->flg_export = flg_export; 2740 cur->flg_export = flg_export;
2720 /*cur->flg_read_only = 0;*/ 2741 /*cur->flg_read_only = 0;*/
2721 { 2742 {
@@ -3242,31 +3263,6 @@ static const char *lookup_param(const char *src)
3242 return p; 3263 return p;
3243} 3264}
3244 3265
3245/* Make new string for parser */
3246static char* make_string(char **inp)
3247{
3248 char *p;
3249 char *str = NULL;
3250 int n;
3251 int val_len;
3252 int len = 0;
3253
3254 for (n = 0; inp[n]; n++) {
3255 p = expand_variables_to_string(inp[n]);
3256 val_len = strlen(p);
3257 str = xrealloc(str, len + val_len + 3); /* +3: space, '\n', <nul>*/
3258 str[len++] = ' ';
3259 strcpy(str + len, p);
3260 len += val_len;
3261 if (p != inp[n]) free(p);
3262 }
3263 /* We do not check for case where loop had no iterations at all
3264 * - cannot happen? */
3265 str[len] = '\n';
3266 str[len+1] = '\0';
3267 return str;
3268}
3269
3270/* return code: 0 for OK, 1 for syntax error */ 3266/* return code: 0 for OK, 1 for syntax error */
3271static int handle_dollar(o_string *dest, struct p_context *ctx, struct in_str *input) 3267static int handle_dollar(o_string *dest, struct p_context *ctx, struct in_str *input)
3272{ 3268{
@@ -3358,9 +3354,9 @@ static int parse_stream(o_string *dest, struct p_context *ctx,
3358 debug_printf_parse("parse_stream entered, end_trigger='%s'\n", end_trigger); 3354 debug_printf_parse("parse_stream entered, end_trigger='%s'\n", end_trigger);
3359 3355
3360 while (1) { 3356 while (1) {
3361 ch = b_getch(input);
3362 m = CHAR_IFS; 3357 m = CHAR_IFS;
3363 next = '\0'; 3358 next = '\0';
3359 ch = b_getch(input);
3364 if (ch != EOF) { 3360 if (ch != EOF) {
3365 m = charmap[ch]; 3361 m = charmap[ch];
3366 if (ch != '\n') 3362 if (ch != '\n')
@@ -3371,6 +3367,11 @@ static int parse_stream(o_string *dest, struct p_context *ctx,
3371 if (m == CHAR_ORDINARY 3367 if (m == CHAR_ORDINARY
3372 || (m != CHAR_SPECIAL && dest->quote) 3368 || (m != CHAR_SPECIAL && dest->quote)
3373 ) { 3369 ) {
3370 if (ch == EOF) {
3371 syntax();
3372 debug_printf_parse("parse_stream return 1: unterminated \"\n");
3373 return 1;
3374 }
3374 b_addqchr(dest, ch, dest->quote); 3375 b_addqchr(dest, ch, dest->quote);
3375 continue; 3376 continue;
3376 } 3377 }
@@ -3564,8 +3565,8 @@ static void update_charmap(void)
3564} 3565}
3565 3566
3566/* most recursion does not come through here, the exception is 3567/* most recursion does not come through here, the exception is
3567 * from builtin_source() */ 3568 * from builtin_source() and builtin_eval() */
3568static int parse_stream_outer(struct in_str *inp, int parse_flag) 3569static int parse_and_run_stream(struct in_str *inp, int parse_flag)
3569{ 3570{
3570 struct p_context ctx; 3571 struct p_context ctx;
3571 o_string temp = NULL_O_STRING; 3572 o_string temp = NULL_O_STRING;
@@ -3607,19 +3608,19 @@ static int parse_stream_outer(struct in_str *inp, int parse_flag)
3607 return 0; 3608 return 0;
3608} 3609}
3609 3610
3610static int parse_string_outer(const char *s, int parse_flag) 3611static int parse_and_run_string(const char *s, int parse_flag)
3611{ 3612{
3612 struct in_str input; 3613 struct in_str input;
3613 setup_string_in_str(&input, s); 3614 setup_string_in_str(&input, s);
3614 return parse_stream_outer(&input, parse_flag); 3615 return parse_and_run_stream(&input, parse_flag);
3615} 3616}
3616 3617
3617static int parse_file_outer(FILE *f) 3618static int parse_and_run_file(FILE *f)
3618{ 3619{
3619 int rcode; 3620 int rcode;
3620 struct in_str input; 3621 struct in_str input;
3621 setup_file_in_str(&input, f); 3622 setup_file_in_str(&input, f);
3622 rcode = parse_stream_outer(&input, PARSEFLAG_SEMICOLON); 3623 rcode = parse_and_run_stream(&input, PARSEFLAG_SEMICOLON);
3623 return rcode; 3624 return rcode;
3624} 3625}
3625 3626
@@ -3698,7 +3699,7 @@ int hush_main(int argc, char **argv)
3698 input = fopen("/etc/profile", "r"); 3699 input = fopen("/etc/profile", "r");
3699 if (input != NULL) { 3700 if (input != NULL) {
3700 mark_open(fileno(input)); 3701 mark_open(fileno(input));
3701 parse_file_outer(input); 3702 parse_and_run_file(input);
3702 mark_closed(fileno(input)); 3703 mark_closed(fileno(input));
3703 fclose(input); 3704 fclose(input);
3704 } 3705 }
@@ -3710,7 +3711,7 @@ int hush_main(int argc, char **argv)
3710 case 'c': 3711 case 'c':
3711 global_argv = argv + optind; 3712 global_argv = argv + optind;
3712 global_argc = argc - optind; 3713 global_argc = argc - optind;
3713 opt = parse_string_outer(optarg, PARSEFLAG_SEMICOLON); 3714 opt = parse_and_run_string(optarg, PARSEFLAG_SEMICOLON);
3714 goto final_return; 3715 goto final_return;
3715 case 'i': 3716 case 'i':
3716 /* Well, we cannot just declare interactiveness, 3717 /* Well, we cannot just declare interactiveness,
@@ -3791,7 +3792,7 @@ int hush_main(int argc, char **argv)
3791#endif 3792#endif
3792 3793
3793 if (argv[optind] == NULL) { 3794 if (argv[optind] == NULL) {
3794 opt = parse_file_outer(stdin); 3795 opt = parse_and_run_file(stdin);
3795 goto final_return; 3796 goto final_return;
3796 } 3797 }
3797 3798
@@ -3799,7 +3800,7 @@ int hush_main(int argc, char **argv)
3799 global_argv = argv + optind; 3800 global_argv = argv + optind;
3800 global_argc = argc - optind; 3801 global_argc = argc - optind;
3801 input = xfopen(argv[optind], "r"); 3802 input = xfopen(argv[optind], "r");
3802 opt = parse_file_outer(input); 3803 opt = parse_and_run_file(input);
3803 3804
3804#if ENABLE_FEATURE_CLEAN_UP 3805#if ENABLE_FEATURE_CLEAN_UP
3805 fclose(input); 3806 fclose(input);
diff --git a/shell/hush_test/hush-bugs/noeol3.right b/shell/hush_test/hush-bugs/noeol3.right
new file mode 100644
index 000000000..a0ce47f30
--- /dev/null
+++ b/shell/hush_test/hush-bugs/noeol3.right
@@ -0,0 +1 @@
hush: syntax error hush.c:3370
diff --git a/shell/hush_test/hush-bugs/noeol3.tests b/shell/hush_test/hush-bugs/noeol3.tests
new file mode 100755
index 000000000..ec958ed7a
--- /dev/null
+++ b/shell/hush_test/hush-bugs/noeol3.tests
@@ -0,0 +1,2 @@
1# last line has no EOL!
2echo "unterminated \ No newline at end of file