hush: add support for "set -o pipefail"
function old new delta
checkjobs 467 517 +50
builtin_set 259 286 +27
o_opt_strings - 10 +10
hush_main 1011 1013 +2
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 3/0 up/down: 89/0) Total: 89 bytes
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/shell/hush.c b/shell/hush.c
index 126aaf0..50e9ce3 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -507,6 +507,7 @@
# define CMD_FUNCDEF 3
#endif
+ smalluint cmd_exitcode;
/* if non-NULL, this "command" is { list }, ( list ), or a compound statement */
struct pipe *group;
#if !BB_MMU
@@ -637,6 +638,43 @@
#endif
+/* set -/+o OPT support. (TODO: make it optional)
+ * bash supports the following opts:
+ * allexport off
+ * braceexpand on
+ * emacs on
+ * errexit off
+ * errtrace off
+ * functrace off
+ * hashall on
+ * histexpand off
+ * history on
+ * ignoreeof off
+ * interactive-comments on
+ * keyword off
+ * monitor on
+ * noclobber off
+ * noexec off
+ * noglob off
+ * nolog off
+ * notify off
+ * nounset off
+ * onecmd off
+ * physical off
+ * pipefail off
+ * posix off
+ * privileged off
+ * verbose off
+ * vi off
+ * xtrace off
+ */
+static const char o_opt_strings[] ALIGN1 = "pipefail\0";
+enum {
+ OPT_O_PIPEFAIL,
+ NUM_OPT_O
+};
+
+
/* "Globals" within this file */
/* Sorted roughly by size (smaller offsets == smaller code) */
struct globals {
@@ -675,6 +713,7 @@
int last_jobid;
pid_t saved_tty_pgrp;
struct pipe *job_list;
+ char o_opt[NUM_OPT_O];
# define G_saved_tty_pgrp (G.saved_tty_pgrp)
#else
# define G_saved_tty_pgrp 0
@@ -6315,24 +6354,23 @@
if (fg_pipe->cmds[i].pid != childpid)
continue;
if (dead) {
+ int ex;
fg_pipe->cmds[i].pid = 0;
fg_pipe->alive_cmds--;
- if (i == fg_pipe->num_cmds - 1) {
- /* last process gives overall exitstatus */
- rcode = WEXITSTATUS(status);
- /* bash prints killer signal's name for *last*
- * process in pipe (prints just newline for SIGINT).
- * Mimic this. Example: "sleep 5" + (^\ or kill -QUIT)
- */
- if (WIFSIGNALED(status)) {
- int sig = WTERMSIG(status);
+ ex = WEXITSTATUS(status);
+ /* bash prints killer signal's name for *last*
+ * process in pipe (prints just newline for SIGINT).
+ * Mimic this. Example: "sleep 5" + (^\ or kill -QUIT)
+ */
+ if (WIFSIGNALED(status)) {
+ int sig = WTERMSIG(status);
+ if (i == fg_pipe->num_cmds-1)
printf("%s\n", sig == SIGINT ? "" : get_signame(sig));
- /* TODO: MIPS has 128 sigs (1..128), what if sig==128 here?
- * Maybe we need to use sig | 128? */
- rcode = sig + 128;
- }
- IF_HAS_KEYWORDS(if (fg_pipe->pi_inverted) rcode = !rcode;)
+ /* TODO: MIPS has 128 sigs (1..128), what if sig==128 here?
+ * Maybe we need to use sig | 128? */
+ ex = sig + 128;
}
+ fg_pipe->cmds[i].cmd_exitcode = ex;
} else {
fg_pipe->cmds[i].is_stopped = 1;
fg_pipe->stopped_cmds++;
@@ -6341,6 +6379,15 @@
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 */
+ 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;)
/* 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" */
@@ -7340,13 +7387,41 @@
}
#endif
-static int set_mode(const char cstate, const char mode)
+static int set_mode(int state, char mode, const char *o_opt)
{
- int state = (cstate == '-' ? 1 : 0);
+ int idx;
switch (mode) {
- case 'n': G.n_mode = state; break;
- case 'x': IF_HUSH_MODE_X(G_x_mode = state;) break;
- default: return EXIT_FAILURE;
+ case 'n':
+ G.n_mode = state;
+ break;
+ case 'x':
+ IF_HUSH_MODE_X(G_x_mode = state;)
+ break;
+ case 'o':
+ if (!o_opt) {
+ /* "set -+o" without parameter.
+ * in bash, set -o produces this output:
+ * pipefail off
+ * and set +o:
+ * set +o pipefail
+ * We always use the second form.
+ */
+ const char *p = o_opt_strings;
+ idx = 0;
+ while (*p) {
+ printf("set %co %s\n", (G.o_opt[idx] ? '-' : '+'), p);
+ idx++;
+ p += strlen(p) + 1;
+ }
+ break;
+ }
+ idx = index_in_strings(o_opt_strings, o_opt);
+ if (idx >= 0) {
+ G.o_opt[idx] = state;
+ break;
+ }
+ default:
+ return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
@@ -7586,7 +7661,7 @@
#endif
case 'n':
case 'x':
- if (set_mode('-', opt) == 0) /* no error */
+ if (set_mode(1, opt, NULL) == 0) /* no error */
break;
default:
#ifndef BB_VER
@@ -8376,15 +8451,18 @@
}
do {
- if (!strcmp(arg, "--")) {
+ if (strcmp(arg, "--") == 0) {
++argv;
goto set_argv;
}
if (arg[0] != '+' && arg[0] != '-')
break;
- for (n = 1; arg[n]; ++n)
- if (set_mode(arg[0], arg[n]))
+ for (n = 1; arg[n]; ++n) {
+ if (set_mode((arg[0] == '-'), arg[n], argv[1]))
goto error;
+ if (arg[n] == 'o' && argv[1])
+ argv++;
+ }
} while ((arg = *++argv) != NULL);
/* Now argv[0] is 1st argument */
diff --git a/shell/hush_test/hush-misc/pipefail.right b/shell/hush_test/hush-misc/pipefail.right
new file mode 100644
index 0000000..5845d89
--- /dev/null
+++ b/shell/hush_test/hush-misc/pipefail.right
@@ -0,0 +1,40 @@
+Default:
+true | true:
+0
+1
+true | false:
+1
+0
+false | true:
+0
+1
+exit 2 | exit 3 | exit 4:
+4
+0
+Pipefail on:
+true | true:
+0
+1
+true | false:
+1
+0
+false | true:
+1
+0
+exit 2 | exit 3 | exit 4:
+4
+0
+Pipefail off:
+true | true:
+0
+1
+true | false:
+1
+0
+false | true:
+0
+1
+exit 2 | exit 3 | exit 4:
+4
+0
+Done
diff --git a/shell/hush_test/hush-misc/pipefail.tests b/shell/hush_test/hush-misc/pipefail.tests
new file mode 100755
index 0000000..9df8418
--- /dev/null
+++ b/shell/hush_test/hush-misc/pipefail.tests
@@ -0,0 +1,45 @@
+echo Default:
+echo "true | true:"
+ true | true; echo $?
+! true | true; echo $?
+echo "true | false:"
+ true | false; echo $?
+! true | false; echo $?
+echo "false | true:"
+ false | true; echo $?
+! false | true; echo $?
+echo "exit 2 | exit 3 | exit 4:"
+ exit 2 | exit 3 | exit 4; echo $?
+! exit 2 | exit 3 | exit 4; echo $?
+
+echo Pipefail on:
+set -o pipefail
+echo "true | true:"
+ true | true; echo $?
+! true | true; echo $?
+echo "true | false:"
+ true | false; echo $?
+! true | false; echo $?
+echo "false | true:"
+ false | true; echo $?
+! false | true; echo $?
+echo "exit 2 | exit 3 | exit 4:"
+ exit 2 | exit 3 | exit 4; echo $?
+! exit 2 | exit 3 | exit 4; echo $?
+
+echo Pipefail off:
+set +o pipefail
+echo "true | true:"
+ true | true; echo $?
+! true | true; echo $?
+echo "true | false:"
+ true | false; echo $?
+! true | false; echo $?
+echo "false | true:"
+ false | true; echo $?
+! false | true; echo $?
+echo "exit 2 | exit 3 | exit 4:"
+ exit 2 | exit 3 | exit 4; echo $?
+! exit 2 | exit 3 | exit 4; echo $?
+
+echo Done