From 1ab7c2fc6daf252f20d17949e70829905a7fd72a Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Thu, 3 Nov 2016 20:17:23 +0100
Subject: ash: while (!got_sig) pause() is not reliable, use sigsuspend()

dash was doing it for a reason. Unfortunately, it had no comment why...
now I know.

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash.c | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

(limited to 'shell')

diff --git a/shell/ash.c b/shell/ash.c
index ecd2146e4..f75642868 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -3933,6 +3933,8 @@ wait_block_or_sig(int *status)
 	int pid;
 
 	do {
+		sigset_t mask;
+
 		/* Poll all children for changes in their state */
 		got_sigchld = 0;
 		/* if job control is active, accept stopped processes too */
@@ -3941,14 +3943,13 @@ wait_block_or_sig(int *status)
 			break; /* Error (e.g. EINTR, ECHILD) or pid */
 
 		/* Children exist, but none are ready. Sleep until interesting signal */
-#if 0 /* dash does this */
-		sigset_t mask;
+#if 1
 		sigfillset(&mask);
 		sigprocmask(SIG_SETMASK, &mask, &mask);
 		while (!got_sigchld && !pending_sig)
 			sigsuspend(&mask);
 		sigprocmask(SIG_SETMASK, &mask, NULL);
-#else
+#else /* unsafe: a signal can set pending_sig after check, but before pause() */
 		while (!got_sigchld && !pending_sig)
 			pause();
 #endif
@@ -3987,9 +3988,9 @@ dowait(int block, struct job *job)
 	 * either enter a sleeping waitpid() (BUG), or need to busy-loop.
 	 *
 	 * Because of this, we run inside INT_OFF, but use a special routine
-	 * which combines waitpid() and pause().
+	 * which combines waitpid() and sigsuspend().
 	 * This is the reason why we need to have a handler for SIGCHLD:
-	 * SIG_DFL handler does not wake pause().
+	 * SIG_DFL handler does not wake sigsuspend().
 	 */
 	INT_OFF;
 	if (block == DOWAIT_BLOCK_OR_SIG) {
-- 
cgit v1.2.3-55-g6feb


From 06b114900fc57cac0e422d26228f4d0aaf5d2288 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Fri, 4 Nov 2016 16:43:18 +0100
Subject: ash: fix "duplicate local" code (forgot to re-enable interrupts)

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash.c | 23 ++++++++++++++++++++---
 1 file changed, 20 insertions(+), 3 deletions(-)

(limited to 'shell')

diff --git a/shell/ash.c b/shell/ash.c
index f75642868..87f2127a1 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -32,6 +32,7 @@
 #define DEBUG_TIME 0
 #define DEBUG_PID 1
 #define DEBUG_SIG 1
+#define DEBUG_INTONOFF 0
 
 #define PROFILE 0
 
@@ -442,10 +443,18 @@ static void exitshell(void) NORETURN;
  * much more efficient and portable.  (But hacking the kernel is so much
  * more fun than worrying about efficiency and portability. :-))
  */
-#define INT_OFF do { \
+#if DEBUG_INTONOFF
+# define INT_OFF do { \
+	TRACE(("%s:%d INT_OFF(%d)\n", __func__, __LINE__, suppress_int)); \
 	suppress_int++; \
 	barrier(); \
 } while (0)
+#else
+# define INT_OFF do { \
+	suppress_int++; \
+	barrier(); \
+} while (0)
+#endif
 
 /*
  * Called to raise an exception.  Since C doesn't include exceptions, we
@@ -513,7 +522,14 @@ int_on(void)
 		raise_interrupt();
 	}
 }
-#define INT_ON int_on()
+#if DEBUG_INTONOFF
+# define INT_ON do { \
+	TRACE(("%s:%d INT_ON(%d)\n", __func__, __LINE__, suppress_int-1)); \
+	int_on(); \
+} while (0)
+#else
+# define INT_ON int_on()
+#endif
 static IF_ASH_OPTIMIZE_FOR_SIZE(inline) void
 force_int_on(void)
 {
@@ -9101,7 +9117,7 @@ mklocal(char *name)
 			/* else:
 			 * it's a duplicate "local VAR" declaration, do nothing
 			 */
-			return;
+			goto ret;
 		}
 		lvp = lvp->next;
 	}
@@ -9140,6 +9156,7 @@ mklocal(char *name)
 	lvp->vp = vp;
 	lvp->next = localvars;
 	localvars = lvp;
+ ret:
 	INT_ON;
 }
 
-- 
cgit v1.2.3-55-g6feb


From 672a55e606fc50e4ffe7810bd0d9cd1cf9b980a3 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Fri, 4 Nov 2016 18:46:14 +0100
Subject: hush: allow { cmd } to not be terminated by semicolon in some cases

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash_test/ash-misc/group_in_braces.right   |  5 ++++
 shell/ash_test/ash-misc/group_in_braces.tests   | 11 +++++++++
 shell/hush.c                                    | 32 ++++++++++++++++++++-----
 shell/hush_test/hush-misc/group_in_braces.right |  5 ++++
 shell/hush_test/hush-misc/group_in_braces.tests | 11 +++++++++
 5 files changed, 58 insertions(+), 6 deletions(-)
 create mode 100644 shell/ash_test/ash-misc/group_in_braces.right
 create mode 100755 shell/ash_test/ash-misc/group_in_braces.tests
 create mode 100644 shell/hush_test/hush-misc/group_in_braces.right
 create mode 100755 shell/hush_test/hush-misc/group_in_braces.tests

(limited to 'shell')

diff --git a/shell/ash_test/ash-misc/group_in_braces.right b/shell/ash_test/ash-misc/group_in_braces.right
new file mode 100644
index 000000000..a7064499b
--- /dev/null
+++ b/shell/ash_test/ash-misc/group_in_braces.right
@@ -0,0 +1,5 @@
+Zero:0
+Zero:0
+Zero:0
+Zero:0
+Zero:0
diff --git a/shell/ash_test/ash-misc/group_in_braces.tests b/shell/ash_test/ash-misc/group_in_braces.tests
new file mode 100755
index 000000000..f6571c35d
--- /dev/null
+++ b/shell/ash_test/ash-misc/group_in_braces.tests
@@ -0,0 +1,11 @@
+# Test cases where { cmd } does not require semicolon after "cmd"
+(exit 2); { { true; } }
+echo Zero:$?
+(exit 2); {(true)}
+echo Zero:$?
+(exit 2); { true | { true; } }
+echo Zero:$?
+(exit 2); { while false; do :; done }
+echo Zero:$?
+(exit 2); { case a in b) ;; esac }
+echo Zero:$?
diff --git a/shell/hush.c b/shell/hush.c
index 7c2f157b8..336de75ad 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -3915,12 +3915,17 @@ static int parse_group(o_string *dest, struct parse_context *ctx,
 		command->cmd_type = CMD_SUBSHELL;
 	} else {
 		/* bash does not allow "{echo...", requires whitespace */
-		ch = i_getch(input);
-		if (ch != ' ' && ch != '\t' && ch != '\n') {
+		ch = i_peek(input);
+		if (ch != ' ' && ch != '\t' && ch != '\n'
+		 && ch != '('	/* but "{(..." is allowed (without whitespace) */
+		) {
 			syntax_error_unexpected_ch(ch);
 			return 1;
 		}
-		nommu_addchr(&ctx->as_string, ch);
+		if (ch != '(') {
+			ch = i_getch(input);
+			nommu_addchr(&ctx->as_string, ch);
+		}
 	}
 
 	{
@@ -4575,6 +4580,7 @@ static struct pipe *parse_stream(char **pstring,
 		 || dest.has_quoted_part     /* ""{... - non-special */
 		 || (next != ';'             /* }; - special */
 		    && next != ')'           /* }) - special */
+		    && next != '('           /* {( - special */
 		    && next != '&'           /* }& and }&& ... - special */
 		    && next != '|'           /* }|| ... - special */
 		    && !strchr(defifs, next) /* {word - non-special */
@@ -4657,17 +4663,31 @@ static struct pipe *parse_stream(char **pstring,
 		 * Pathological example: { ""}; } should exec "}" cmd
 		 */
 		if (ch == '}') {
-			if (!IS_NULL_CMD(ctx.command) /* cmd } */
-			 || dest.length != 0 /* word} */
+			if (dest.length != 0 /* word} */
 			 || dest.has_quoted_part    /* ""} */
 			) {
 				goto ordinary_char;
 			}
+			if (!IS_NULL_CMD(ctx.command)) { /* cmd } */
+				/* Generally, there should be semicolon: "cmd; }"
+				 * However, bash allows to omit it if "cmd" is
+				 * a group. Examples:
+				 * { { echo 1; } }
+				 * {(echo 1)}
+				 * { echo 0 >&2 | { echo 1; } }
+				 * { while false; do :; done }
+				 * { case a in b) ;; esac }
+				 */
+				if (ctx.command->group)
+					goto term_group;
+				goto ordinary_char;
+			}
 			if (!IS_NULL_PIPE(ctx.pipe)) /* cmd | } */
+				/* Can't be an end of {cmd}, skip the check */
 				goto skip_end_trigger;
 			/* else: } does terminate a group */
 		}
-
+ term_group:
 		if (end_trigger && end_trigger == ch
 		 && (ch != ';' || heredoc_cnt == 0)
 #if ENABLE_HUSH_CASE
diff --git a/shell/hush_test/hush-misc/group_in_braces.right b/shell/hush_test/hush-misc/group_in_braces.right
new file mode 100644
index 000000000..a7064499b
--- /dev/null
+++ b/shell/hush_test/hush-misc/group_in_braces.right
@@ -0,0 +1,5 @@
+Zero:0
+Zero:0
+Zero:0
+Zero:0
+Zero:0
diff --git a/shell/hush_test/hush-misc/group_in_braces.tests b/shell/hush_test/hush-misc/group_in_braces.tests
new file mode 100755
index 000000000..f6571c35d
--- /dev/null
+++ b/shell/hush_test/hush-misc/group_in_braces.tests
@@ -0,0 +1,11 @@
+# Test cases where { cmd } does not require semicolon after "cmd"
+(exit 2); { { true; } }
+echo Zero:$?
+(exit 2); {(true)}
+echo Zero:$?
+(exit 2); { true | { true; } }
+echo Zero:$?
+(exit 2); { while false; do :; done }
+echo Zero:$?
+(exit 2); { case a in b) ;; esac }
+echo Zero:$?
-- 
cgit v1.2.3-55-g6feb


From 30bfcf612b0862e4dd98d923eabb308b54012d24 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Fri, 4 Nov 2016 18:52:48 +0100
Subject: hush: non-matching "case" statement sets $? to 0

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 2 ++
 1 file changed, 2 insertions(+)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index 336de75ad..4c2ed6cea 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -7874,6 +7874,8 @@ static int run_list(struct pipe *pi)
 #endif
 #if ENABLE_HUSH_CASE
 		if (rword == RES_CASE) {
+			/* Case which does not match and execute anything still sets $? to 0 */
+			G.last_exitcode = rcode = EXIT_SUCCESS;
 			case_word = expand_strvec_to_string(pi->cmds->argv);
 			continue;
 		}
-- 
cgit v1.2.3-55-g6feb


From aeaee43d5a4fc95405cc2047227d096b1ada55b6 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Fri, 4 Nov 2016 20:14:04 +0100
Subject: hush: case logic for setting $? was still wrong

Resetting to 0 should happen in "esac". Matched branch must
still see previous $?.

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index 4c2ed6cea..0bc67ecc9 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -7874,14 +7874,14 @@ static int run_list(struct pipe *pi)
 #endif
 #if ENABLE_HUSH_CASE
 		if (rword == RES_CASE) {
-			/* Case which does not match and execute anything still sets $? to 0 */
-			G.last_exitcode = rcode = EXIT_SUCCESS;
+			debug_printf_exec("CASE cond_code:%d\n", cond_code);
 			case_word = expand_strvec_to_string(pi->cmds->argv);
 			continue;
 		}
 		if (rword == RES_MATCH) {
 			char **argv;
 
+			debug_printf_exec("MATCH cond_code:%d\n", cond_code);
 			if (!case_word) /* "case ... matched_word) ... WORD)": we executed selected branch, stop */
 				break;
 			/* all prev words didn't match, does this one match? */
@@ -7892,8 +7892,8 @@ static int run_list(struct pipe *pi)
 				cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0);
 				free(pattern);
 				if (cond_code == 0) { /* match! we will execute this branch */
-					free(case_word); /* make future "word)" stop */
-					case_word = NULL;
+					free(case_word);
+					case_word = NULL; /* make future "word)" stop */
 					break;
 				}
 				argv++;
@@ -7901,9 +7901,17 @@ static int run_list(struct pipe *pi)
 			continue;
 		}
 		if (rword == RES_CASE_BODY) { /* inside of a case branch */
+			debug_printf_exec("CASE_BODY cond_code:%d\n", cond_code);
 			if (cond_code != 0)
 				continue; /* not matched yet, skip this pipe */
 		}
+		if (rword == RES_ESAC) {
+			debug_printf_exec("ESAC cond_code:%d\n", cond_code);
+			if (case_word) {
+				/* "case" did not match anything: still set $? (to 0) */
+				G.last_exitcode = rcode = EXIT_SUCCESS;
+			}
+		}
 #endif
 		/* Just pressing <enter> in shell should check for jobs.
 		 * OTOH, in non-interactive shell this is useless
-- 
cgit v1.2.3-55-g6feb


From 4224647c8d0990f8ffb17d8481655827161a94d4 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Mon, 7 Nov 2016 16:22:35 +0100
Subject: hush: do not allow sh -c '{ echo boo }'

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index 0bc67ecc9..a01db9c75 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -4533,12 +4533,14 @@ static struct pipe *parse_stream(char **pstring,
 				syntax_error_unterm_str("here document");
 				goto parse_error;
 			}
-			/* end_trigger == '}' case errors out earlier,
-			 * checking only ')' */
 			if (end_trigger == ')') {
 				syntax_error_unterm_ch('(');
 				goto parse_error;
 			}
+			if (end_trigger == '}') {
+				syntax_error_unterm_ch('{');
+				goto parse_error;
+			}
 
 			if (done_word(&dest, &ctx)) {
 				goto parse_error;
-- 
cgit v1.2.3-55-g6feb


From 5d5a611df2cfd8efa721866e3571e12a45c3b8d8 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Mon, 7 Nov 2016 19:36:50 +0100
Subject: hush: comment fix

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index a01db9c75..aa474afb2 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -5009,8 +5009,8 @@ static struct pipe *parse_stream(char **pstring,
 		 * Run it from interactive shell, watch pmap `pidof hush`.
 		 * while if false; then false; fi; do break; fi
 		 * Samples to catch leaks at execution:
-		 * while if (true | {true;}); then echo ok; fi; do break; done
-		 * while if (true | {true;}); then echo ok; fi; do (if echo ok; break; then :; fi) | cat; break; done
+		 * while if (true | { true;}); then echo ok; fi; do break; done
+		 * while if (true | { true;}); then echo ok; fi; do (if echo ok; break; then :; fi) | cat; break; done
 		 */
 		pctx = &ctx;
 		do {
-- 
cgit v1.2.3-55-g6feb


From 4e1c8b4f6a5765e1c2b5033f6fd2196408874233 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Mon, 7 Nov 2016 20:06:40 +0100
Subject: hush: factor out %jobspec parsing

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 33 ++++++++++++++++++++++-----------
 1 file changed, 22 insertions(+), 11 deletions(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index aa474afb2..3b87d283c 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -9226,10 +9226,28 @@ static int FAST_FUNC builtin_type(char **argv)
 }
 
 #if ENABLE_HUSH_JOB
+static struct pipe *parse_jobspec(const char *str)
+{
+	struct pipe *pi;
+	int jobnum;
+
+	if (sscanf(str, "%%%d", &jobnum) != 1) {
+		bb_error_msg("bad argument '%s'", str);
+		return NULL;
+	}
+	for (pi = G.job_list; pi; pi = pi->next) {
+		if (pi->jobid == jobnum) {
+			return pi;
+		}
+	}
+	bb_error_msg("%d: no such job", jobnum);
+	return NULL;
+}
+
 /* built-in 'fg' and 'bg' handler */
 static int FAST_FUNC builtin_fg_bg(char **argv)
 {
-	int i, jobnum;
+	int i;
 	struct pipe *pi;
 
 	if (!G_interactive_fd)
@@ -9245,17 +9263,10 @@ static int FAST_FUNC builtin_fg_bg(char **argv)
 		bb_error_msg("%s: no current job", argv[0]);
 		return EXIT_FAILURE;
 	}
-	if (sscanf(argv[1], "%%%d", &jobnum) != 1) {
-		bb_error_msg("%s: bad argument '%s'", argv[0], argv[1]);
+
+	pi = parse_jobspec(argv[1]);
+	if (!pi)
 		return EXIT_FAILURE;
-	}
-	for (pi = G.job_list; pi; pi = pi->next) {
-		if (pi->jobid == jobnum) {
-			goto found;
-		}
-	}
-	bb_error_msg("%s: %d: no such job", argv[0], jobnum);
-	return EXIT_FAILURE;
  found:
 	/* TODO: bash prints a string representation
 	 * of job being foregrounded (like "sleep 1 | cat") */
-- 
cgit v1.2.3-55-g6feb


From 62b717b75ebff3ab00aae2fee0087a8106208a39 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Mon, 7 Nov 2016 22:12:18 +0100
Subject: hush: implement "wait %jobspec"

function                                             old     new   delta
parse_jobspec                                          -      83     +83
job_exited_or_stopped                                  -      79     +79
builtin_wait                                         236     302     +66
wait_for_child_or_signal                             199     228     +29
checkjobs                                            142     158     +16
builtin_jobs                                          59      68      +9
process_wait_result                                  453     408     -45
builtin_fg_bg                                        272     203     -69
------------------------------------------------------------------------------
(add/remove: 2/0 grow/shrink: 4/2 up/down: 282/-114)          Total: 168 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 117 ++++++++++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 88 insertions(+), 29 deletions(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index 3b87d283c..b842d6eec 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -45,7 +45,6 @@
  *      tilde expansion
  *      aliases
  *      kill %jobspec
- *      wait %jobspec
  *      follow IFS rules more precisely, including update semantics
  *      builtins mandated by standards we don't support:
  *          [un]alias, command, fc, getopts, newgrp, readonly, times
@@ -7065,6 +7064,27 @@ static void delete_finished_bg_job(struct pipe *pi)
 }
 #endif /* JOB */
 
+static int job_exited_or_stopped(struct pipe *pi)
+{
+	int rcode, i;
+
+	if (pi->alive_cmds != pi->stopped_cmds)
+		return -1;
+
+	/* All processes in fg pipe have exited or stopped */
+	rcode = 0;
+	i = pi->num_cmds;
+	while (--i >= 0) {
+		rcode = pi->cmds[i].cmd_exitcode;
+		/* usually last process gives overall exitstatus,
+		 * but with "set -o pipefail", last *failed* process does */
+		if (G.o_opt[OPT_O_PIPEFAIL] == 0 || rcode != 0)
+			break;
+	}
+	IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;)
+	return rcode;
+}
+
 static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status)
 {
 #if ENABLE_HUSH_JOB
@@ -7088,7 +7108,10 @@ static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status)
 	/* Were we asked to wait for a fg pipe? */
 	if (fg_pipe) {
 		i = fg_pipe->num_cmds;
+
 		while (--i >= 0) {
+			int rcode;
+
 			debug_printf_jobs("check pid %d\n", fg_pipe->cmds[i].pid);
 			if (fg_pipe->cmds[i].pid != childpid)
 				continue;
@@ -7117,18 +7140,8 @@ static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status)
 			}
 			debug_printf_jobs("fg_pipe: alive_cmds %d stopped_cmds %d\n",
 					fg_pipe->alive_cmds, fg_pipe->stopped_cmds);
-			if (fg_pipe->alive_cmds == fg_pipe->stopped_cmds) {
-				/* All processes in fg pipe have exited or stopped */
-				int rcode = 0;
-				i = fg_pipe->num_cmds;
-				while (--i >= 0) {
-					rcode = fg_pipe->cmds[i].cmd_exitcode;
-					/* usually last process gives overall exitstatus,
-					 * but with "set -o pipefail", last *failed* process does */
-					if (G.o_opt[OPT_O_PIPEFAIL] == 0 || rcode != 0)
-						break;
-				}
-				IF_HAS_KEYWORDS(if (fg_pipe->pi_inverted) rcode = !rcode;)
+			rcode = job_exited_or_stopped(fg_pipe);
+			if (rcode >= 0) {
 /* Note: *non-interactive* bash does not continue if all processes in fg pipe
  * are stopped. Testcase: "cat | cat" in a script (not on command line!)
  * and "killall -STOP cat" */
@@ -7185,9 +7198,18 @@ static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status)
 
 /* Check to see if any processes have exited -- if they have,
  * figure out why and see if a job has completed.
- * Alternatively (fg_pipe == NULL, waitfor_pid != 0),
- * wait for a specific pid to complete, return exitcode+1
- * (this allows to distinguish zero as "no children exited" result).
+ *
+ * If non-NULL fg_pipe: wait for its completion or stop.
+ * Return its exitcode or zero if stopped.
+ *
+ * Alternatively (fg_pipe == NULL, waitfor_pid != 0):
+ * waitpid(WNOHANG), if waitfor_pid exits or stops, return exitcode+1,
+ * else return <0 if waitpid errors out (e.g. ECHILD: nothing to wait for)
+ * or 0 if no children changed status.
+ *
+ * Alternatively (fg_pipe == NULL, waitfor_pid == 0),
+ * return <0 if waitpid errors out (e.g. ECHILD: nothing to wait for)
+ * or 0 if no children changed status.
  */
 static int checkjobs(struct pipe *fg_pipe, pid_t waitfor_pid)
 {
@@ -7256,9 +7278,13 @@ static int checkjobs(struct pipe *fg_pipe, pid_t waitfor_pid)
 			break;
 		}
 		if (childpid == waitfor_pid) {
+			debug_printf_exec("childpid==waitfor_pid:%d status:0x%08x\n", childpid, status);
 			rcode = WEXITSTATUS(status);
 			if (WIFSIGNALED(status))
 				rcode = 128 + WTERMSIG(status);
+			if (WIFSTOPPED(status))
+				/* bash: "cmd & wait $!" and cmd stops: $? = 128 + stopsig */
+				rcode = 128 + WSTOPSIG(status);
 			rcode++;
 			break; /* "wait PID" called us, give it exitcode+1 */
 		}
@@ -9329,6 +9355,7 @@ static int FAST_FUNC builtin_jobs(char **argv UNUSED_PARAM)
 	struct pipe *job;
 	const char *status_string;
 
+	checkjobs(NULL, 0 /*(no pid to wait for)*/);
 	for (job = G.job_list; job; job = job->next) {
 		if (job->alive_cmds == job->stopped_cmds)
 			status_string = "Stopped";
@@ -9481,12 +9508,15 @@ static int FAST_FUNC builtin_umask(char **argv)
 }
 
 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/wait.html */
-static int wait_for_child_or_signal(pid_t waitfor_pid)
+#if !ENABLE_HUSH_JOB
+# define wait_for_child_or_signal(pipe,pid) wait_for_child_or_signal(pid)
+#endif
+static int wait_for_child_or_signal(struct pipe *waitfor_pipe, pid_t waitfor_pid)
 {
 	int ret = 0;
 	for (;;) {
 		int sig;
-		sigset_t oldset, allsigs;
+		sigset_t oldset;
 
 		/* waitpid is not interruptible by SA_RESTARTed
 		 * signals which we use. Thus, this ugly dance:
@@ -9495,10 +9525,10 @@ static int wait_for_child_or_signal(pid_t waitfor_pid)
 		/* Make sure possible SIGCHLD is stored in kernel's
 		 * pending signal mask before we call waitpid.
 		 * Or else we may race with SIGCHLD, lose it,
-		 * and get stuck in sigwaitinfo...
+		 * and get stuck in sigsuspend...
 		 */
-		sigfillset(&allsigs);
-		sigprocmask(SIG_SETMASK, &allsigs, &oldset);
+		sigfillset(&oldset); /* block all signals, remember old set */
+		sigprocmask(SIG_SETMASK, &oldset, &oldset);
 
 		if (!sigisemptyset(&G.pending_set)) {
 			/* Crap! we raced with some signal! */
@@ -9507,19 +9537,31 @@ static int wait_for_child_or_signal(pid_t waitfor_pid)
 		}
 
 		/*errno = 0; - checkjobs does this */
+/* Can't pass waitfor_pipe into checkjobs(): it won't be interruptible */
 		ret = checkjobs(NULL, waitfor_pid); /* waitpid(WNOHANG) inside */
+		debug_printf_exec("checkjobs:%d\n", ret);
+#if ENABLE_HUSH_JOB
+		if (waitfor_pipe) {
+			int rcode = job_exited_or_stopped(waitfor_pipe);
+			debug_printf_exec("job_exited_or_stopped:%d\n", rcode);
+			if (rcode >= 0) {
+				ret = rcode;
+				sigprocmask(SIG_SETMASK, &oldset, NULL);
+				break;
+			}
+		}
+#endif
 		/* if ECHILD, there are no children (ret is -1 or 0) */
 		/* if ret == 0, no children changed state */
 		/* if ret != 0, it's exitcode+1 of exited waitfor_pid child */
-		if (errno == ECHILD || ret--) {
-			if (ret < 0) /* if ECHILD, may need to fix */
+		if (errno == ECHILD || ret) {
+			ret--;
+			if (ret < 0) /* if ECHILD, may need to fix "ret" */
 				ret = 0;
 			sigprocmask(SIG_SETMASK, &oldset, NULL);
 			break;
 		}
-
 		/* Wait for SIGCHLD or any other signal */
-		//sig = sigwaitinfo(&allsigs, NULL);
 		/* It is vitally important for sigsuspend that SIGCHLD has non-DFL handler! */
 		/* Note: sigsuspend invokes signal handler */
 		sigsuspend(&oldset);
@@ -9544,6 +9586,7 @@ static int FAST_FUNC builtin_wait(char **argv)
 {
 	int ret;
 	int status;
+	struct pipe *wait_pipe = NULL;
 
 	argv = skip_dash_dash(argv);
 	if (argv[0] == NULL) {
@@ -9563,18 +9606,27 @@ static int FAST_FUNC builtin_wait(char **argv)
 		 * ^C <-- after ~4 sec from keyboard
 		 * $
 		 */
-		return wait_for_child_or_signal(0 /*(no pid to wait for)*/);
+		return wait_for_child_or_signal(NULL, 0 /*(no job and no pid to wait for)*/);
 	}
 
-	/* TODO: support "wait %jobspec" */
 	do {
 		pid_t pid = bb_strtou(*argv, NULL, 10);
 		if (errno || pid <= 0) {
+#if ENABLE_HUSH_JOB
+			if (argv[0][0] == '%') {
+				wait_pipe = parse_jobspec(*argv);
+				if (wait_pipe) {
+					pid = - wait_pipe->pgrp;
+					goto do_wait;
+				}
+			}
+#endif
 			/* mimic bash message */
 			bb_error_msg("wait: '%s': not a pid or valid job spec", *argv);
 			ret = EXIT_FAILURE;
 			continue; /* bash checks all argv[] */
 		}
+ IF_HUSH_JOB(do_wait:)
 		/* Do we have such child? */
 		ret = waitpid(pid, &status, WNOHANG);
 		if (ret < 0) {
@@ -9599,13 +9651,20 @@ static int FAST_FUNC builtin_wait(char **argv)
 		}
 		if (ret == 0) {
 			/* Yes, and it still runs */
-			ret = wait_for_child_or_signal(pid);
+			ret = wait_for_child_or_signal(wait_pipe, wait_pipe ? 0 : pid);
 		} else {
 			/* Yes, and it just exited */
-			process_wait_result(NULL, pid, status);
+			process_wait_result(NULL, ret, status);
 			ret = WEXITSTATUS(status);
 			if (WIFSIGNALED(status))
 				ret = 128 + WTERMSIG(status);
+#if ENABLE_HUSH_JOB
+			if (wait_pipe) {
+				ret = job_exited_or_stopped(wait_pipe);
+				if (ret < 0)
+					goto do_wait;
+			}
+#endif
 		}
 	} while (*++argv);
 
-- 
cgit v1.2.3-55-g6feb


From 26ad94bedcc6a4aa3feb07ea032709bcd517ee46 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Mon, 7 Nov 2016 23:07:21 +0100
Subject: hush: "wait $!; echo $?" should return 127 if $! already exited

It would be nice to provide bash-like "remember las exitcode"
thingy, but it's a bit complex. For now, match ash and dash.

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index b842d6eec..7683a3749 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -9636,12 +9636,13 @@ static int FAST_FUNC builtin_wait(char **argv)
 					/* "wait $!" but last bg task has already exited. Try:
 					 * (sleep 1; exit 3) & sleep 2; echo $?; wait $!; echo $?
 					 * In bash it prints exitcode 0, then 3.
+					 * In dash, it is 127.
 					 */
-					ret = 0; /* FIXME */
-					continue;
+					/* ret = G.last_bg_pid_exitstatus - FIXME */
+				} else {
+					/* Example: "wait 1". mimic bash message */
+					bb_error_msg("wait: pid %d is not a child of this shell", (int)pid);
 				}
-				/* Example: "wait 1". mimic bash message */
-				bb_error_msg("wait: pid %d is not a child of this shell", (int)pid);
 			} else {
 				/* ??? */
 				bb_perror_msg("wait %s", *argv);
-- 
cgit v1.2.3-55-g6feb


From 02affb4afd4a71b0c1ca6286f9b0f6bf3b10e0a1 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Tue, 8 Nov 2016 00:59:29 +0100
Subject: hush: rework "wait %jobspec" to work in non-interactive shells too

Also add tests. wait5.tests so far fails (but works for ash and dash).

function                                             old     new   delta
builtin_wait                                         305     283     -22

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/ash_test/ash-misc/wait4.right   |  1 +
 shell/ash_test/ash-misc/wait4.tests   |  2 ++
 shell/ash_test/ash-misc/wait5.right   |  2 ++
 shell/ash_test/ash-misc/wait5.tests   |  5 +++++
 shell/hush.c                          | 21 ++++++++-------------
 shell/hush_test/hush-misc/wait4.right |  1 +
 shell/hush_test/hush-misc/wait4.tests |  2 ++
 shell/hush_test/hush-misc/wait5.right |  2 ++
 shell/hush_test/hush-misc/wait5.tests |  5 +++++
 9 files changed, 28 insertions(+), 13 deletions(-)
 create mode 100644 shell/ash_test/ash-misc/wait4.right
 create mode 100755 shell/ash_test/ash-misc/wait4.tests
 create mode 100644 shell/ash_test/ash-misc/wait5.right
 create mode 100755 shell/ash_test/ash-misc/wait5.tests
 create mode 100644 shell/hush_test/hush-misc/wait4.right
 create mode 100755 shell/hush_test/hush-misc/wait4.tests
 create mode 100644 shell/hush_test/hush-misc/wait5.right
 create mode 100755 shell/hush_test/hush-misc/wait5.tests

(limited to 'shell')

diff --git a/shell/ash_test/ash-misc/wait4.right b/shell/ash_test/ash-misc/wait4.right
new file mode 100644
index 000000000..f7987db32
--- /dev/null
+++ b/shell/ash_test/ash-misc/wait4.right
@@ -0,0 +1 @@
+Three:3
diff --git a/shell/ash_test/ash-misc/wait4.tests b/shell/ash_test/ash-misc/wait4.tests
new file mode 100755
index 000000000..cc34059ac
--- /dev/null
+++ b/shell/ash_test/ash-misc/wait4.tests
@@ -0,0 +1,2 @@
+sleep 1 | (sleep 1;exit 3) & wait %1
+echo Three:$?
diff --git a/shell/ash_test/ash-misc/wait5.right b/shell/ash_test/ash-misc/wait5.right
new file mode 100644
index 000000000..82c9d5696
--- /dev/null
+++ b/shell/ash_test/ash-misc/wait5.right
@@ -0,0 +1,2 @@
+Zero:0
+Three:3
diff --git a/shell/ash_test/ash-misc/wait5.tests b/shell/ash_test/ash-misc/wait5.tests
new file mode 100755
index 000000000..1b4762d89
--- /dev/null
+++ b/shell/ash_test/ash-misc/wait5.tests
@@ -0,0 +1,5 @@
+sleep 0 | (sleep 0;exit 3) &
+sleep 1
+echo Zero:$?
+wait %1
+echo Three:$?
diff --git a/shell/hush.c b/shell/hush.c
index 7683a3749..ddbf2f7d8 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -9586,7 +9586,6 @@ static int FAST_FUNC builtin_wait(char **argv)
 {
 	int ret;
 	int status;
-	struct pipe *wait_pipe = NULL;
 
 	argv = skip_dash_dash(argv);
 	if (argv[0] == NULL) {
@@ -9614,10 +9613,13 @@ static int FAST_FUNC builtin_wait(char **argv)
 		if (errno || pid <= 0) {
 #if ENABLE_HUSH_JOB
 			if (argv[0][0] == '%') {
+				struct pipe *wait_pipe;
 				wait_pipe = parse_jobspec(*argv);
 				if (wait_pipe) {
-					pid = - wait_pipe->pgrp;
-					goto do_wait;
+					ret = job_exited_or_stopped(wait_pipe);
+					if (ret < 0)
+						ret = wait_for_child_or_signal(wait_pipe, 0);
+					continue;
 				}
 			}
 #endif
@@ -9626,7 +9628,7 @@ static int FAST_FUNC builtin_wait(char **argv)
 			ret = EXIT_FAILURE;
 			continue; /* bash checks all argv[] */
 		}
- IF_HUSH_JOB(do_wait:)
+
 		/* Do we have such child? */
 		ret = waitpid(pid, &status, WNOHANG);
 		if (ret < 0) {
@@ -9652,20 +9654,13 @@ static int FAST_FUNC builtin_wait(char **argv)
 		}
 		if (ret == 0) {
 			/* Yes, and it still runs */
-			ret = wait_for_child_or_signal(wait_pipe, wait_pipe ? 0 : pid);
+			ret = wait_for_child_or_signal(NULL, pid);
 		} else {
 			/* Yes, and it just exited */
-			process_wait_result(NULL, ret, status);
+			process_wait_result(NULL, pid, status);
 			ret = WEXITSTATUS(status);
 			if (WIFSIGNALED(status))
 				ret = 128 + WTERMSIG(status);
-#if ENABLE_HUSH_JOB
-			if (wait_pipe) {
-				ret = job_exited_or_stopped(wait_pipe);
-				if (ret < 0)
-					goto do_wait;
-			}
-#endif
 		}
 	} while (*++argv);
 
diff --git a/shell/hush_test/hush-misc/wait4.right b/shell/hush_test/hush-misc/wait4.right
new file mode 100644
index 000000000..f7987db32
--- /dev/null
+++ b/shell/hush_test/hush-misc/wait4.right
@@ -0,0 +1 @@
+Three:3
diff --git a/shell/hush_test/hush-misc/wait4.tests b/shell/hush_test/hush-misc/wait4.tests
new file mode 100755
index 000000000..cc34059ac
--- /dev/null
+++ b/shell/hush_test/hush-misc/wait4.tests
@@ -0,0 +1,2 @@
+sleep 1 | (sleep 1;exit 3) & wait %1
+echo Three:$?
diff --git a/shell/hush_test/hush-misc/wait5.right b/shell/hush_test/hush-misc/wait5.right
new file mode 100644
index 000000000..82c9d5696
--- /dev/null
+++ b/shell/hush_test/hush-misc/wait5.right
@@ -0,0 +1,2 @@
+Zero:0
+Three:3
diff --git a/shell/hush_test/hush-misc/wait5.tests b/shell/hush_test/hush-misc/wait5.tests
new file mode 100755
index 000000000..1b4762d89
--- /dev/null
+++ b/shell/hush_test/hush-misc/wait5.tests
@@ -0,0 +1,5 @@
+sleep 0 | (sleep 0;exit 3) &
+sleep 1
+echo Zero:$?
+wait %1
+echo Three:$?
-- 
cgit v1.2.3-55-g6feb


From 830ea35484cecb8b4cdbe0f30cc5d573ff0e3411 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Tue, 8 Nov 2016 04:59:11 +0100
Subject: hush: make "wait %1" less likely to play with signal mask

Was playing with "sleep 3 | exit 3 & wait %1" and noticed that often
SIGCHLD arrives even before I get to signal masking. Can avoid it in this
case.

function                                             old     new   delta
wait_for_child_or_signal                             228     265     +37

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index ddbf2f7d8..5e51adfdc 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -8146,11 +8146,11 @@ static void install_fatal_sighandlers(void)
 
 	/* We will restore tty pgrp on these signals */
 	mask = 0
-		+ (1 << SIGILL ) * HUSH_DEBUG
-		+ (1 << SIGFPE ) * HUSH_DEBUG
+		/*+ (1 << SIGILL ) * HUSH_DEBUG*/
+		/*+ (1 << SIGFPE ) * HUSH_DEBUG*/
 		+ (1 << SIGBUS ) * HUSH_DEBUG
 		+ (1 << SIGSEGV) * HUSH_DEBUG
-		+ (1 << SIGTRAP) * HUSH_DEBUG
+		/*+ (1 << SIGTRAP) * HUSH_DEBUG*/
 		+ (1 << SIGABRT)
 	/* bash 3.2 seems to handle these just like 'fatal' ones */
 		+ (1 << SIGPIPE)
@@ -9518,6 +9518,9 @@ static int wait_for_child_or_signal(struct pipe *waitfor_pipe, pid_t waitfor_pid
 		int sig;
 		sigset_t oldset;
 
+		if (!sigisemptyset(&G.pending_set))
+			goto check_sig;
+
 		/* waitpid is not interruptible by SA_RESTARTed
 		 * signals which we use. Thus, this ugly dance:
 		 */
@@ -9532,7 +9535,6 @@ static int wait_for_child_or_signal(struct pipe *waitfor_pipe, pid_t waitfor_pid
 
 		if (!sigisemptyset(&G.pending_set)) {
 			/* Crap! we raced with some signal! */
-		//	sig = 0;
 			goto restore;
 		}
 
@@ -9567,13 +9569,10 @@ static int wait_for_child_or_signal(struct pipe *waitfor_pipe, pid_t waitfor_pid
 		sigsuspend(&oldset);
  restore:
 		sigprocmask(SIG_SETMASK, &oldset, NULL);
-
+ check_sig:
 		/* So, did we get a signal? */
-		//if (sig > 0)
-		//	raise(sig); /* run handler */
 		sig = check_and_run_traps();
 		if (sig /*&& sig != SIGCHLD - always true */) {
-			/* see note 2 */
 			ret = 128 + sig;
 			break;
 		}
-- 
cgit v1.2.3-55-g6feb


From 1eada9ad8d31addd57213a072ddfc278e5776740 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Tue, 8 Nov 2016 17:28:45 +0100
Subject: hush: simplify insert_bg_jobs

function                                             old     new   delta
insert_bg_job                                        366     281     -85

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 18 ++++++++----------
 1 file changed, 8 insertions(+), 10 deletions(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index 5e51adfdc..78a8f5c03 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -6983,12 +6983,12 @@ static const char *get_cmdtext(struct pipe *pi)
 	 * On subsequent bg argv is trashed, but we won't use it */
 	if (pi->cmdtext)
 		return pi->cmdtext;
+
 	argv = pi->cmds[0].argv;
-	if (!argv || !argv[0]) {
+	if (!argv) {
 		pi->cmdtext = xzalloc(1);
 		return pi->cmdtext;
 	}
-
 	len = 0;
 	do {
 		len += strlen(*argv) + 1;
@@ -6997,9 +6997,7 @@ static const char *get_cmdtext(struct pipe *pi)
 	pi->cmdtext = p;
 	argv = pi->cmds[0].argv;
 	do {
-		len = strlen(*argv);
-		memcpy(p, *argv, len);
-		p += len;
+		p = stpcpy(p, *argv);
 		*p++ = ' ';
 	} while (*++argv);
 	p[-1] = '\0';
@@ -7965,8 +7963,8 @@ static int run_list(struct pipe *pi)
 				/* We ran a builtin, function, or group.
 				 * rcode is already known
 				 * and we don't need to wait for anything. */
-				G.last_exitcode = rcode;
 				debug_printf_exec(": builtin/func exitcode %d\n", rcode);
+				G.last_exitcode = rcode;
 				check_and_run_traps();
 #if ENABLE_HUSH_LOOPS
 				/* Was it "break" or "continue"? */
@@ -7998,30 +7996,30 @@ static int run_list(struct pipe *pi)
 				/* even bash 3.2 doesn't do that well with nested bg:
 				 * try "{ { sleep 10; echo DEEP; } & echo HERE; } &".
 				 * I'm NOT treating inner &'s as jobs */
-				check_and_run_traps();
 #if ENABLE_HUSH_JOB
 				if (G.run_list_level == 1)
 					insert_bg_job(pi);
 #endif
 				/* Last command's pid goes to $! */
 				G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid;
-				G.last_exitcode = rcode = EXIT_SUCCESS;
 				debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n");
+/* Check pi->pi_inverted? "! sleep 1 & echo $?": bash says 1. dash and ash says 0 */
+				G.last_exitcode = rcode = EXIT_SUCCESS;
+				check_and_run_traps();
 			} else {
 #if ENABLE_HUSH_JOB
 				if (G.run_list_level == 1 && G_interactive_fd) {
 					/* Waits for completion, then fg's main shell */
 					rcode = checkjobs_and_fg_shell(pi);
 					debug_printf_exec(": checkjobs_and_fg_shell exitcode %d\n", rcode);
-					check_and_run_traps();
 				} else
 #endif
 				{ /* This one just waits for completion */
 					rcode = checkjobs(pi, 0 /*(no pid to wait for)*/);
 					debug_printf_exec(": checkjobs exitcode %d\n", rcode);
-					check_and_run_traps();
 				}
 				G.last_exitcode = rcode;
+				check_and_run_traps();
 			}
 		}
 
-- 
cgit v1.2.3-55-g6feb


From 5cc9bf6a21ae0738528c2fb301ff4be2ab662ee9 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Tue, 8 Nov 2016 17:34:44 +0100
Subject: hush: deindent large block of code, no code changes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 115 +++++++++++++++++++++++++++++------------------------------
 1 file changed, 57 insertions(+), 58 deletions(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index 78a8f5c03..a5f059924 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -7803,6 +7803,8 @@ static int run_list(struct pipe *pi)
 
 	/* Go through list of pipes, (maybe) executing them. */
 	for (; pi; pi = IF_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) {
+		int r;
+
 		if (G.flag_SIGINT)
 			break;
 		if (G_flag_return_in_progress == 1)
@@ -7953,74 +7955,71 @@ static int run_list(struct pipe *pi)
 		 * after run_pipe to collect any background children,
 		 * even if list execution is to be stopped. */
 		debug_printf_exec(": run_pipe with %d members\n", pi->num_cmds);
-		{
-			int r;
-#if ENABLE_HUSH_LOOPS
-			G.flag_break_continue = 0;
-#endif
-			rcode = r = run_pipe(pi); /* NB: rcode is a smallint */
-			if (r != -1) {
-				/* We ran a builtin, function, or group.
-				 * rcode is already known
-				 * and we don't need to wait for anything. */
-				debug_printf_exec(": builtin/func exitcode %d\n", rcode);
-				G.last_exitcode = rcode;
-				check_and_run_traps();
 #if ENABLE_HUSH_LOOPS
-				/* Was it "break" or "continue"? */
-				if (G.flag_break_continue) {
-					smallint fbc = G.flag_break_continue;
-					/* We might fall into outer *loop*,
-					 * don't want to break it too */
-					if (loop_top) {
-						G.depth_break_continue--;
-						if (G.depth_break_continue == 0)
-							G.flag_break_continue = 0;
-						/* else: e.g. "continue 2" should *break* once, *then* continue */
-					} /* else: "while... do... { we are here (innermost list is not a loop!) };...done" */
-					if (G.depth_break_continue != 0 || fbc == BC_BREAK) {
-						checkjobs(NULL, 0 /*(no pid to wait for)*/);
-						break;
-					}
-					/* "continue": simulate end of loop */
-					rword = RES_DONE;
-					continue;
-				}
+		G.flag_break_continue = 0;
 #endif
-				if (G_flag_return_in_progress == 1) {
+		rcode = r = run_pipe(pi); /* NB: rcode is a smalluint, r is int */
+		if (r != -1) {
+			/* We ran a builtin, function, or group.
+			 * rcode is already known
+			 * and we don't need to wait for anything. */
+			debug_printf_exec(": builtin/func exitcode %d\n", rcode);
+			G.last_exitcode = rcode;
+			check_and_run_traps();
+#if ENABLE_HUSH_LOOPS
+			/* Was it "break" or "continue"? */
+			if (G.flag_break_continue) {
+				smallint fbc = G.flag_break_continue;
+				/* We might fall into outer *loop*,
+				 * don't want to break it too */
+				if (loop_top) {
+					G.depth_break_continue--;
+					if (G.depth_break_continue == 0)
+						G.flag_break_continue = 0;
+					/* else: e.g. "continue 2" should *break* once, *then* continue */
+				} /* else: "while... do... { we are here (innermost list is not a loop!) };...done" */
+				if (G.depth_break_continue != 0 || fbc == BC_BREAK) {
 					checkjobs(NULL, 0 /*(no pid to wait for)*/);
 					break;
 				}
-			} else if (pi->followup == PIPE_BG) {
-				/* What does bash do with attempts to background builtins? */
-				/* even bash 3.2 doesn't do that well with nested bg:
-				 * try "{ { sleep 10; echo DEEP; } & echo HERE; } &".
-				 * I'm NOT treating inner &'s as jobs */
+				/* "continue": simulate end of loop */
+				rword = RES_DONE;
+				continue;
+			}
+#endif
+			if (G_flag_return_in_progress == 1) {
+				checkjobs(NULL, 0 /*(no pid to wait for)*/);
+				break;
+			}
+		} else if (pi->followup == PIPE_BG) {
+			/* What does bash do with attempts to background builtins? */
+			/* even bash 3.2 doesn't do that well with nested bg:
+			 * try "{ { sleep 10; echo DEEP; } & echo HERE; } &".
+			 * I'm NOT treating inner &'s as jobs */
 #if ENABLE_HUSH_JOB
-				if (G.run_list_level == 1)
-					insert_bg_job(pi);
+			if (G.run_list_level == 1)
+				insert_bg_job(pi);
 #endif
-				/* Last command's pid goes to $! */
-				G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid;
-				debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n");
+			/* Last command's pid goes to $! */
+			G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid;
+			debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n");
 /* Check pi->pi_inverted? "! sleep 1 & echo $?": bash says 1. dash and ash says 0 */
-				G.last_exitcode = rcode = EXIT_SUCCESS;
-				check_and_run_traps();
-			} else {
+			G.last_exitcode = rcode = EXIT_SUCCESS;
+			check_and_run_traps();
+		} else {
 #if ENABLE_HUSH_JOB
-				if (G.run_list_level == 1 && G_interactive_fd) {
-					/* Waits for completion, then fg's main shell */
-					rcode = checkjobs_and_fg_shell(pi);
-					debug_printf_exec(": checkjobs_and_fg_shell exitcode %d\n", rcode);
-				} else
-#endif
-				{ /* This one just waits for completion */
-					rcode = checkjobs(pi, 0 /*(no pid to wait for)*/);
-					debug_printf_exec(": checkjobs exitcode %d\n", rcode);
-				}
-				G.last_exitcode = rcode;
-				check_and_run_traps();
+			if (G.run_list_level == 1 && G_interactive_fd) {
+				/* Waits for completion, then fg's main shell */
+				rcode = checkjobs_and_fg_shell(pi);
+				debug_printf_exec(": checkjobs_and_fg_shell exitcode %d\n", rcode);
+			} else
+#endif
+			{ /* This one just waits for completion */
+				rcode = checkjobs(pi, 0 /*(no pid to wait for)*/);
+				debug_printf_exec(": checkjobs exitcode %d\n", rcode);
 			}
+			G.last_exitcode = rcode;
+			check_and_run_traps();
 		}
 
 		/* Analyze how result affects subsequent commands */
-- 
cgit v1.2.3-55-g6feb


From 6c635d62d41477e92f0b30b0f525c7838e64a07d Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Tue, 8 Nov 2016 20:26:11 +0100
Subject: hush: small optimization in run_list

I thought gcc can detect this itself. It doesn't.

function                                             old     new   delta
run_list                                            1030    1021      -9

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index a5f059924..5a36a7692 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -8004,20 +8004,21 @@ static int run_list(struct pipe *pi)
 			G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid;
 			debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n");
 /* Check pi->pi_inverted? "! sleep 1 & echo $?": bash says 1. dash and ash says 0 */
-			G.last_exitcode = rcode = EXIT_SUCCESS;
-			check_and_run_traps();
+			rcode = EXIT_SUCCESS;
+			goto check_traps;
 		} else {
 #if ENABLE_HUSH_JOB
 			if (G.run_list_level == 1 && G_interactive_fd) {
 				/* Waits for completion, then fg's main shell */
 				rcode = checkjobs_and_fg_shell(pi);
 				debug_printf_exec(": checkjobs_and_fg_shell exitcode %d\n", rcode);
-			} else
-#endif
-			{ /* This one just waits for completion */
-				rcode = checkjobs(pi, 0 /*(no pid to wait for)*/);
-				debug_printf_exec(": checkjobs exitcode %d\n", rcode);
+				goto check_traps;
 			}
+#endif
+			/* This one just waits for completion */
+			rcode = checkjobs(pi, 0 /*(no pid to wait for)*/);
+			debug_printf_exec(": checkjobs exitcode %d\n", rcode);
+ check_traps:
 			G.last_exitcode = rcode;
 			check_and_run_traps();
 		}
-- 
cgit v1.2.3-55-g6feb


From 00a06b971531031103c25d650e2078c801afbe39 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Tue, 8 Nov 2016 20:35:53 +0100
Subject: hush: renumber PIPE_foo, make PIPE_SEQ = 0

PIPE_SEQ is used most often, having it zero makes code smaller:

function                                             old     new   delta
done_word                                            719     707     -12
parse_stream                                        2546    2531     -15

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index 5a36a7692..57252a17a 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -588,10 +588,10 @@ struct pipe {
 	IF_HAS_KEYWORDS(smallint res_word;) /* needed for if, for, while, until... */
 };
 typedef enum pipe_style {
-	PIPE_SEQ = 1,
-	PIPE_AND = 2,
-	PIPE_OR  = 3,
-	PIPE_BG  = 4,
+	PIPE_SEQ = 0,
+	PIPE_AND = 1,
+	PIPE_OR  = 2,
+	PIPE_BG  = 3,
 } pipe_style;
 /* Is there anything in this pipe at all? */
 #define IS_NULL_PIPE(pi) \
@@ -3139,7 +3139,6 @@ static struct pipe *new_pipe(void)
 {
 	struct pipe *pi;
 	pi = xzalloc(sizeof(struct pipe));
-	/*pi->followup = 0; - deliberately invalid value */
 	/*pi->res_word = RES_NONE; - RES_NONE is 0 anyway */
 	return pi;
 }
-- 
cgit v1.2.3-55-g6feb


From 87e039d0160be16a9a242f74af2e90cdb9f97e12 Mon Sep 17 00:00:00 2001
From: Denys Vlasenko <vda.linux@googlemail.com>
Date: Tue, 8 Nov 2016 22:35:05 +0100
Subject: hush: make getch/peek functions directly called

Indirect calls are more difficult to predict.
Unfortunately, on x64 direct call is 5 bytes while indirect "call (reg+ofs)"
is 3 bytes:

function                                             old     new   delta
i_getch                                                -      82     +82
i_peek                                                 -      63     +63
parse_stream                                        2531    2579     +48
parse_dollar                                         771     797     +26
parse_redirect                                       296     321     +25
add_till_closing_bracket                             408     420     +12
encode_string                                        256     265      +9
i_peek_and_eat_bkslash_nl                             93      99      +6
add_till_backquote                                   110     114      +4
parse_and_run_stream                                 139     141      +2
expand_vars_to_list                                 1143    1144      +1
static_peek                                            6       -      -6
setup_string_in_str                                   39      18     -21
setup_file_in_str                                     40      19     -21
static_get                                            27       -     -27
file_peek                                             52       -     -52
file_get                                              65       -     -65
------------------------------------------------------------------------------
(add/remove: 2/4 grow/shrink: 9/2 up/down: 278/-192)           Total: 86 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
---
 shell/hush.c | 51 ++++++++++++++++++++++++---------------------------
 1 file changed, 24 insertions(+), 27 deletions(-)

(limited to 'shell')

diff --git a/shell/hush.c b/shell/hush.c
index 57252a17a..2f07f4ac1 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -469,11 +469,7 @@ typedef struct in_str {
 	int peek_buf[2];
 	int last_char;
 	FILE *file;
-	int (*get) (struct in_str *) FAST_FUNC;
-	int (*peek) (struct in_str *) FAST_FUNC;
 } in_str;
-#define i_getch(input) ((input)->get(input))
-#define i_peek(input)  ((input)->peek(input))
 
 /* The descrip member of this structure is only used to make
  * debugging output pretty */
@@ -2259,10 +2255,23 @@ static inline int fgetc_interactive(struct in_str *i)
 }
 #endif  /* INTERACTIVE */
 
-static int FAST_FUNC file_get(struct in_str *i)
+static int i_getch(struct in_str *i)
 {
 	int ch;
 
+	if (!i->file) {
+		/* string-based in_str */
+		ch = (unsigned char)*i->p;
+		if (ch != '\0') {
+			i->p++;
+			i->last_char = ch;
+			return ch;
+		}
+		return EOF;
+	}
+
+	/* FILE-based in_str */
+
 #if ENABLE_FEATURE_EDITING
 	/* This can be stdin, check line editing char[] buffer */
 	if (i->p && *i->p != '\0') {
@@ -2288,10 +2297,18 @@ static int FAST_FUNC file_get(struct in_str *i)
 	return ch;
 }
 
-static int FAST_FUNC file_peek(struct in_str *i)
+static int i_peek(struct in_str *i)
 {
 	int ch;
 
+	if (!i->file) {
+		/* string-based in_str */
+		/* Doesn't report EOF on NUL. None of the callers care. */
+		return (unsigned char)*i->p;
+	}
+
+	/* FILE-based in_str */
+
 #if ENABLE_FEATURE_EDITING && ENABLE_HUSH_INTERACTIVE
 	/* This can be stdin, check line editing char[] buffer */
 	if (i->p && *i->p != '\0')
@@ -2318,23 +2335,6 @@ static int FAST_FUNC file_peek(struct in_str *i)
 	return ch;
 }
 
-static int FAST_FUNC static_get(struct in_str *i)
-{
-	int ch = (unsigned char)*i->p;
-	if (ch != '\0') {
-		i->p++;
-		i->last_char = ch;
-		return ch;
-	}
-	return EOF;
-}
-
-static int FAST_FUNC static_peek(struct in_str *i)
-{
-	/* Doesn't report EOF on NUL. None of the callers care. */
-	return (unsigned char)*i->p;
-}
-
 /* Only ever called if i_peek() was called, and did not return EOF.
  * IOW: we know the previous peek saw an ordinary char, not EOF, not NUL,
  * not end-of-line. Therefore we never need to read a new editing line here.
@@ -2370,8 +2370,6 @@ static int i_peek2(struct in_str *i)
 static void setup_file_in_str(struct in_str *i, FILE *f)
 {
 	memset(i, 0, sizeof(*i));
-	i->get = file_get;
-	i->peek = file_peek;
 	/* i->promptmode = 0; - PS1 (memset did it) */
 	i->file = f;
 	/* i->p = NULL; */
@@ -2380,9 +2378,8 @@ static void setup_file_in_str(struct in_str *i, FILE *f)
 static void setup_string_in_str(struct in_str *i, const char *s)
 {
 	memset(i, 0, sizeof(*i));
-	i->get = static_get;
-	i->peek = static_peek;
 	/* i->promptmode = 0; - PS1 (memset did it) */
+	/*i->file = NULL */;
 	i->p = s;
 }
 
-- 
cgit v1.2.3-55-g6feb