hush: make parse errors in sourced file non-fatal in interactive script

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/shell/hush.c b/shell/hush.c
index 8154ac4..e4c3a7d 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -1095,17 +1095,10 @@
 	die_if_script(lineno, "syntax error: unterminated %s", s);
 }
 
-/* It so happens that all such cases are totally fatal
- * even if shell is interactive: EOF while looking for closing
- * delimiter. There is nowhere to read stuff from after that,
- * it's EOF! The only choice is to terminate.
- */
-static void syntax_error_unterm_ch(unsigned lineno, char ch) NORETURN;
 static void syntax_error_unterm_ch(unsigned lineno, char ch)
 {
 	char msg[2] = { ch, '\0' };
 	syntax_error_unterm_str(lineno, msg);
-	xfunc_die();
 }
 
 static void syntax_error_unexpected_ch(unsigned lineno, int ch)
@@ -3539,39 +3532,40 @@
 
 #if ENABLE_HUSH_TICK || ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_DOLLAR_OPS
 /* Subroutines for copying $(...) and `...` things */
-static void add_till_backquote(o_string *dest, struct in_str *input, int in_dquote);
+static int add_till_backquote(o_string *dest, struct in_str *input, int in_dquote);
 /* '...' */
-static void add_till_single_quote(o_string *dest, struct in_str *input)
+static int add_till_single_quote(o_string *dest, struct in_str *input)
 {
 	while (1) {
 		int ch = i_getch(input);
 		if (ch == EOF) {
 			syntax_error_unterm_ch('\'');
-			/*xfunc_die(); - redundant */
+			return 0;
 		}
 		if (ch == '\'')
-			return;
+			return 1;
 		o_addchr(dest, ch);
 	}
 }
 /* "...\"...`..`...." - do we need to handle "...$(..)..." too? */
-static void add_till_double_quote(o_string *dest, struct in_str *input)
+static int add_till_double_quote(o_string *dest, struct in_str *input)
 {
 	while (1) {
 		int ch = i_getch(input);
 		if (ch == EOF) {
 			syntax_error_unterm_ch('"');
-			/*xfunc_die(); - redundant */
+			return 0;
 		}
 		if (ch == '"')
-			return;
+			return 1;
 		if (ch == '\\') {  /* \x. Copy both chars. */
 			o_addchr(dest, ch);
 			ch = i_getch(input);
 		}
 		o_addchr(dest, ch);
 		if (ch == '`') {
-			add_till_backquote(dest, input, /*in_dquote:*/ 1);
+			if (!add_till_backquote(dest, input, /*in_dquote:*/ 1))
+				return 0;
 			o_addchr(dest, ch);
 			continue;
 		}
@@ -3592,12 +3586,12 @@
  * Example                               Output
  * echo `echo '\'TEST\`echo ZZ\`BEST`    \TESTZZBEST
  */
-static void add_till_backquote(o_string *dest, struct in_str *input, int in_dquote)
+static int add_till_backquote(o_string *dest, struct in_str *input, int in_dquote)
 {
 	while (1) {
 		int ch = i_getch(input);
 		if (ch == '`')
-			return;
+			return 1;
 		if (ch == '\\') {
 			/* \x. Copy both unless it is \`, \$, \\ and maybe \" */
 			ch = i_getch(input);
@@ -3611,7 +3605,7 @@
 		}
 		if (ch == EOF) {
 			syntax_error_unterm_ch('`');
-			/*xfunc_die(); - redundant */
+			return 0;
 		}
 		o_addchr(dest, ch);
 	}
@@ -3647,7 +3641,7 @@
 		ch = i_getch(input);
 		if (ch == EOF) {
 			syntax_error_unterm_ch(end_ch);
-			/*xfunc_die(); - redundant */
+			return 0;
 		}
 		if (ch == end_ch  IF_HUSH_BASH_COMPAT( || ch == end_char2)) {
 			if (!dbl)
@@ -3661,22 +3655,26 @@
 		o_addchr(dest, ch);
 		if (ch == '(' || ch == '{') {
 			ch = (ch == '(' ? ')' : '}');
-			add_till_closing_bracket(dest, input, ch);
+			if (!add_till_closing_bracket(dest, input, ch))
+				return 0;
 			o_addchr(dest, ch);
 			continue;
 		}
 		if (ch == '\'') {
-			add_till_single_quote(dest, input);
+			if (!add_till_single_quote(dest, input))
+				return 0;
 			o_addchr(dest, ch);
 			continue;
 		}
 		if (ch == '"') {
-			add_till_double_quote(dest, input);
+			if (!add_till_double_quote(dest, input))
+				return 0;
 			o_addchr(dest, ch);
 			continue;
 		}
 		if (ch == '`') {
-			add_till_backquote(dest, input, /*in_dquote:*/ 0);
+			if (!add_till_backquote(dest, input, /*in_dquote:*/ 0))
+				return 0;
 			o_addchr(dest, ch);
 			continue;
 		}
@@ -3685,7 +3683,7 @@
 			ch = i_getch(input);
 			if (ch == EOF) {
 				syntax_error_unterm_ch(')');
-				/*xfunc_die(); - redundant */
+				return 0;
 			}
 			o_addchr(dest, ch);
 			continue;
@@ -3756,8 +3754,8 @@
 		) {
  bad_dollar_syntax:
 			syntax_error_unterm_str("${name}");
-			debug_printf_parse("parse_dollar return 1: unterminated ${name}\n");
-			return 1;
+			debug_printf_parse("parse_dollar return 0: unterminated ${name}\n");
+			return 0;
 		}
 		nommu_addchr(as_string, ch);
 		ch |= quote_mask;
@@ -3813,6 +3811,8 @@
 					pos = dest->length;
 #if ENABLE_HUSH_DOLLAR_OPS
 				last_ch = add_till_closing_bracket(dest, input, end_ch);
+				if (last_ch == 0) /* error? */
+					return 0;
 #else
 #error Simple code to only allow ${var} is not implemented
 #endif
@@ -3857,7 +3857,8 @@
 			o_addchr(dest, /*quote_mask |*/ '+');
 			if (!BB_MMU)
 				pos = dest->length;
-			add_till_closing_bracket(dest, input, ')' | DOUBLE_CLOSE_CHAR_FLAG);
+			if (!add_till_closing_bracket(dest, input, ')' | DOUBLE_CLOSE_CHAR_FLAG))
+				return 0; /* error */
 			if (as_string) {
 				o_addstr(as_string, dest->data + pos);
 				o_addchr(as_string, ')');
@@ -3872,7 +3873,8 @@
 		o_addchr(dest, quote_mask | '`');
 		if (!BB_MMU)
 			pos = dest->length;
-		add_till_closing_bracket(dest, input, ')');
+		if (!add_till_closing_bracket(dest, input, ')'))
+			return 0; /* error */
 		if (as_string) {
 			o_addstr(as_string, dest->data + pos);
 			o_addchr(as_string, ')');
@@ -3899,8 +3901,8 @@
 	default:
 		o_addQchr(dest, '$');
 	}
-	debug_printf_parse("parse_dollar return 0\n");
-	return 0;
+	debug_printf_parse("parse_dollar return 1 (ok)\n");
+	return 1;
 #undef as_string
 }
 
@@ -3941,13 +3943,13 @@
 	if (ch != EOF)
 		nommu_addchr(as_string, ch);
 	if (ch == dquote_end) { /* may be only '"' or EOF */
-		debug_printf_parse("encode_string return 0\n");
-		return 0;
+		debug_printf_parse("encode_string return 1 (ok)\n");
+		return 1;
 	}
 	/* note: can't move it above ch == dquote_end check! */
 	if (ch == EOF) {
 		syntax_error_unterm_ch('"');
-		/*xfunc_die(); - redundant */
+		return 0; /* error */
 	}
 	next = '\0';
 	if (ch != '\n') {
@@ -3978,10 +3980,10 @@
 		goto again;
 	}
 	if (ch == '$') {
-		if (parse_dollar(as_string, dest, input, /*quote_mask:*/ 0x80) != 0) {
-			debug_printf_parse("encode_string return 1: "
-					"parse_dollar returned non-0\n");
-			return 1;
+		if (!parse_dollar(as_string, dest, input, /*quote_mask:*/ 0x80)) {
+			debug_printf_parse("encode_string return 0: "
+					"parse_dollar returned 0 (error)\n");
+			return 0;
 		}
 		goto again;
 	}
@@ -3990,7 +3992,8 @@
 		//unsigned pos = dest->length;
 		o_addchr(dest, SPECIAL_VAR_SYMBOL);
 		o_addchr(dest, 0x80 | '`');
-		add_till_backquote(dest, input, /*in_dquote:*/ dquote_end == '"');
+		if (!add_till_backquote(dest, input, /*in_dquote:*/ dquote_end == '"'))
+			return 0; /* error */
 		o_addchr(dest, SPECIAL_VAR_SYMBOL);
 		//debug_printf_subst("SUBST RES3 '%s'\n", dest->data + pos);
 		goto again;
@@ -4061,8 +4064,8 @@
 			/* end_trigger == '}' case errors out earlier,
 			 * checking only ')' */
 			if (end_trigger == ')') {
-				syntax_error_unterm_ch('('); /* exits */
-				/* goto parse_error; */
+				syntax_error_unterm_ch('(');
+				goto parse_error;
 			}
 
 			if (done_word(&dest, &ctx)) {
@@ -4353,9 +4356,9 @@
 			dest.has_quoted_part = 1;
 			break;
 		case '$':
-			if (parse_dollar(&ctx.as_string, &dest, input, /*quote_mask:*/ 0) != 0) {
+			if (!parse_dollar(&ctx.as_string, &dest, input, /*quote_mask:*/ 0)) {
 				debug_printf_parse("parse_stream parse error: "
-					"parse_dollar returned non-0\n");
+					"parse_dollar returned 0 (error)\n");
 				goto parse_error;
 			}
 			break;
@@ -4365,7 +4368,7 @@
 				ch = i_getch(input);
 				if (ch == EOF) {
 					syntax_error_unterm_ch('\'');
-					/*xfunc_die(); - redundant */
+					goto parse_error;
 				}
 				nommu_addchr(&ctx.as_string, ch);
 				if (ch == '\'')
@@ -4377,7 +4380,7 @@
 			dest.has_quoted_part = 1;
 			if (dest.o_assignment == NOT_ASSIGNMENT)
 				dest.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS;
-			if (encode_string(&ctx.as_string, &dest, input, '"', /*process_bkslash:*/ 1))
+			if (!encode_string(&ctx.as_string, &dest, input, '"', /*process_bkslash:*/ 1))
 				goto parse_error;
 			dest.o_expflags &= ~EXP_FLAG_ESC_GLOB_CHARS;
 			break;
@@ -4388,7 +4391,8 @@
 			o_addchr(&dest, SPECIAL_VAR_SYMBOL);
 			o_addchr(&dest, '`');
 			pos = dest.length;
-			add_till_backquote(&dest, input, /*in_dquote:*/ 0);
+			if (!add_till_backquote(&dest, input, /*in_dquote:*/ 0))
+				goto parse_error;
 # if !BB_MMU
 			o_addstr(&ctx.as_string, dest.data + pos);
 			o_addchr(&ctx.as_string, '`');
@@ -4664,6 +4668,7 @@
 	 */
 	setup_string_in_str(&input, str);
 	encode_string(NULL, &dest, &input, EOF, process_bkslash);
+//TODO: error check (encode_string returns 0 on error)?
 	//bb_error_msg("'%s' -> '%s'", str, dest.data);
 	exp_str = expand_string_to_string(dest.data, /*unbackslash:*/ do_unbackslash);
 	//bb_error_msg("'%s' -> '%s'", dest.data, exp_str);
@@ -8625,6 +8630,8 @@
 #endif
 	save_and_replace_G_args(&sv, argv);
 
+//TODO: syntax errors in sourced file should never abort the "calling" script.
+//Try: bash -c '. ./bad_file; echo YES'
 	parse_and_run_file(input);
 	fclose(input);