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>
This commit is contained in:
parent
c08c3f5d26
commit
6696eac274
124
shell/hush.c
124
shell/hush.c
@ -507,6 +507,7 @@ struct command {
|
||||
# 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 @@ struct function {
|
||||
#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 @@ struct globals {
|
||||
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 @@ static int checkjobs(struct pipe *fg_pipe)
|
||||
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 @@ static int checkjobs(struct pipe *fg_pipe)
|
||||
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 @@ static void set_fatal_handlers(void)
|
||||
}
|
||||
#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 @@ int hush_main(int argc, char **argv)
|
||||
#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 @@ static int FAST_FUNC builtin_set(char **argv)
|
||||
}
|
||||
|
||||
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 */
|
||||
|
||||
|
40
shell/hush_test/hush-misc/pipefail.right
Normal file
40
shell/hush_test/hush-misc/pipefail.right
Normal file
@ -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
|
45
shell/hush_test/hush-misc/pipefail.tests
Executable file
45
shell/hush_test/hush-misc/pipefail.tests
Executable file
@ -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
|
Loading…
Reference in New Issue
Block a user