X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=shell%2Fhush.c;h=c8356f4b8552745349bbe2742ff5994309374b4d;hb=4f8079de877cf2b7206e4cabaf465fb7d8cc4f62;hp=30add72f01aa6831c55db95ae9f6d4254fd6d2da;hpb=d4e4fdb5ce5ccc067b3d35d877f7a7d978869517;p=oweals%2Fbusybox.git diff --git a/shell/hush.c b/shell/hush.c index 30add72f0..c8356f4b8 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -41,14 +41,29 @@ * * TODOs: * grep for "TODO" and fix (some of them are easy) + * make complex ${var%...} constructs support optional + * make here documents optional * special variables (done: PWD, PPID, RANDOM) + * follow IFS rules more precisely, including update semantics * tilde expansion * aliases - * follow IFS rules more precisely, including update semantics * builtins mandated by standards we don't support: - * [un]alias, command, fc, getopts, newgrp, readonly, times - * make complex ${var%...} constructs support optional - * make here documents optional + * [un]alias, command, fc, getopts, readonly, times: + * command -v CMD: print "/path/to/CMD" + * prints "CMD" for builtins + * prints "alias ALIAS='EXPANSION'" for aliases + * prints nothing and sets $? to 1 if not found + * command -V CMD: print "CMD is /path/CMD|a shell builtin|etc" + * command [-p] CMD: run CMD, even if a function CMD also exists + * (can use this to override standalone shell as well) + * -p: use default $PATH + * readonly VAR[=VAL]...: make VARs readonly + * readonly [-p]: list all such VARs (-p has no effect in bash) + * getopts: getopt() for shells + * times: print getrusage(SELF/CHILDREN).ru_utime/ru_stime + * fc -l[nr] [BEG] [END]: list range of commands in history + * fc [-e EDITOR] [BEG] [END]: edit/rerun range of commands + * fc -s [PAT=REP] [CMD]: rerun CMD, replacing PAT with REP * * Bash compat TODO: * redirection of stdout+stderr: &> and >& @@ -64,8 +79,13 @@ * The EXPR is evaluated according to ARITHMETIC EVALUATION. * This is exactly equivalent to let "EXPR". * $[EXPR]: synonym for $((EXPR)) + * indirect expansion: ${!VAR} + * substring op on @: ${@:n:m} * * Won't do: + * Some builtins mandated by standards: + * newgrp [GRP]: not a builtin in bash but a suid binary + * which spawns a new shell with new group ID * In bash, export builtin is special, its arguments are assignments * and therefore expansion of them should be "one-word" expansion: * $ export i=`echo 'a b'` # export has one arg: "i=a b" @@ -285,7 +305,7 @@ * therefore we don't show them either. */ //usage:#define hush_trivial_usage -//usage: "[-nxl] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]" +//usage: "[-enxl] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]" //usage:#define hush_full_usage "\n\n" //usage: "Unix shell interpreter" @@ -402,6 +422,7 @@ #define debug_printf_expand(...) do {} while (0) #define debug_printf_varexp(...) do {} while (0) #define debug_printf_glob(...) do {} while (0) +#define debug_printf_redir(...) do {} while (0) #define debug_printf_list(...) do {} while (0) #define debug_printf_subst(...) do {} while (0) #define debug_printf_clean(...) do {} while (0) @@ -745,6 +766,7 @@ struct function { static const char o_opt_strings[] ALIGN1 = "pipefail\0" "noexec\0" + "errexit\0" #if ENABLE_HUSH_MODE_X "xtrace\0" #endif @@ -752,6 +774,7 @@ static const char o_opt_strings[] ALIGN1 = enum { OPT_O_PIPEFAIL, OPT_O_NOEXEC, + OPT_O_ERREXIT, #if ENABLE_HUSH_MODE_X OPT_O_XTRACE, #endif @@ -808,6 +831,25 @@ struct globals { #else # define G_saved_tty_pgrp 0 #endif + /* How deeply are we in context where "set -e" is ignored */ + int errexit_depth; + /* "set -e" rules (do we follow them correctly?): + * Exit if pipe, list, or compound command exits with a non-zero status. + * Shell does not exit if failed command is part of condition in + * if/while, part of && or || list except the last command, any command + * in a pipe but the last, or if the command's return value is being + * inverted with !. If a compound command other than a subshell returns a + * non-zero status because a command failed while -e was being ignored, the + * shell does not exit. A trap on ERR, if set, is executed before the shell + * exits [ERR is a bashism]. + * + * If a compound command or function executes in a context where -e is + * ignored, none of the commands executed within are affected by the -e + * setting. If a compound command or function sets -e while executing in a + * context where -e is ignored, that setting does not have any effect until + * the compound command or the command containing the function call completes. + */ + char o_opt[NUM_OPT_O]; #if ENABLE_HUSH_MODE_X # define G_x_mode (G.o_opt[OPT_O_XTRACE]) @@ -831,6 +873,7 @@ struct globals { smallint exiting; /* used to prevent EXIT trap recursion */ /* These four support $?, $#, and $1 */ smalluint last_exitcode; + smalluint last_bg_pid_exitcode; #if ENABLE_HUSH_SET /* are global_argv and global_argv[1..n] malloced? (note: not [0]) */ smalluint global_args_malloced; @@ -1146,6 +1189,10 @@ static const struct built_in_command bltins2[] = { # define DEBUG_GLOB 0 #endif +#ifndef debug_printf_redir +# define debug_printf_redir(...) (indent(), fdprintf(2, __VA_ARGS__)) +#endif + #ifndef debug_printf_list # define debug_printf_list(...) (indent(), fdprintf(2, __VA_ARGS__)) #endif @@ -1381,12 +1428,30 @@ static void free_strings(char **strings) free(strings); } +static int fcntl_F_DUPFD(int fd, int avoid_fd) +{ + int newfd; + repeat: + newfd = fcntl(fd, F_DUPFD, avoid_fd + 1); + if (newfd < 0) { + if (errno == EBUSY) + goto repeat; + if (errno == EINTR) + goto repeat; + } + return newfd; +} -static int xdup_and_close(int fd, int F_DUPFD_maybe_CLOEXEC) +static int xdup_and_close(int fd, int F_DUPFD_maybe_CLOEXEC, int avoid_fd) { - /* We avoid taking stdio fds. Mimicking ash: use fds above 9 */ - int newfd = fcntl(fd, F_DUPFD_maybe_CLOEXEC, 10); + int newfd; + repeat: + newfd = fcntl(fd, F_DUPFD_maybe_CLOEXEC, avoid_fd + 1); if (newfd < 0) { + if (errno == EBUSY) + goto repeat; + if (errno == EINTR) + goto repeat; /* fd was not open? */ if (errno == EBADF) return fd; @@ -1424,13 +1489,14 @@ static void fclose_and_forget(FILE *fp) } fclose(fp); } -static int save_FILEs_on_redirect(int fd) +static int save_FILEs_on_redirect(int fd, int avoid_fd) { struct FILE_list *fl = G.FILE_list; while (fl) { if (fd == fl->fd) { /* We use it only on script files, they are all CLOEXEC */ - fl->fd = xdup_and_close(fd, F_DUPFD_CLOEXEC); + fl->fd = xdup_and_close(fd, F_DUPFD_CLOEXEC, avoid_fd); + debug_printf_redir("redirect_fd %d: matches a script fd, moving it to %d\n", fd, fl->fd); return 1; } fl = fl->next; @@ -1443,6 +1509,7 @@ static void restore_redirected_FILEs(void) while (fl) { int should_be = fileno(fl->fp); if (fl->fd != should_be) { + debug_printf_redir("restoring script fd from %d to %d\n", fl->fd, should_be); xmove_fd(fl->fd, should_be); fl->fd = should_be; } @@ -3326,12 +3393,49 @@ static void done_pipe(struct parse_context *ctx, pipe_style type) debug_printf_parse("done_pipe entered, followup %d\n", type); /* Close previous command */ not_null = done_command(ctx); - ctx->pipe->followup = type; #if HAS_KEYWORDS ctx->pipe->pi_inverted = ctx->ctx_inverted; ctx->ctx_inverted = 0; ctx->pipe->res_word = ctx->ctx_res_w; #endif + if (type == PIPE_BG && ctx->list_head != ctx->pipe) { + /* Necessary since && and || have precedence over &: + * "cmd1 && cmd2 &" must spawn both cmds, not only cmd2, + * in a backgrounded subshell. + */ + struct pipe *pi; + struct command *command; + + /* Is this actually this construct, all pipes end with && or ||? */ + pi = ctx->list_head; + while (pi != ctx->pipe) { + if (pi->followup != PIPE_AND && pi->followup != PIPE_OR) + goto no_conv; + pi = pi->next; + } + + debug_printf_parse("BG with more than one pipe, converting to { p1 &&...pN; } &\n"); + pi->followup = PIPE_SEQ; /* close pN _not_ with "&"! */ + pi = xzalloc(sizeof(*pi)); + pi->followup = PIPE_BG; + pi->num_cmds = 1; + pi->cmds = xzalloc(sizeof(pi->cmds[0])); + command = &pi->cmds[0]; + if (CMD_NORMAL != 0) /* "if xzalloc didn't do that already" */ + command->cmd_type = CMD_NORMAL; + command->group = ctx->list_head; +#if !BB_MMU + command->group_as_string = xstrndup( + ctx->as_string.data, + ctx->as_string.length - 1 /* do not copy last char, "&" */ + ); +#endif + /* Replace all pipes in ctx with one newly created */ + ctx->list_head = ctx->pipe = pi; + } else { + no_conv: + ctx->pipe->followup = type; + } /* Without this check, even just on command line generates * tree of three NOPs (!). Which is harmless but annoying. @@ -3501,9 +3605,8 @@ static int reserved_word(o_string *word, struct parse_context *ctx) if (r->flag & FLAG_START) { struct parse_context *old; - old = xmalloc(sizeof(*old)); + old = xmemdup(ctx, sizeof(*ctx)); debug_printf_parse("push stack %p\n", old); - *old = *ctx; /* physical copy */ initialize_context(ctx); ctx->stack = old; } else if (/*ctx->ctx_res_w == RES_NONE ||*/ !(ctx->old_flag & (1 << r->res))) { @@ -4774,7 +4877,9 @@ static struct pipe *parse_stream(char **pstring, * Really, ask yourself, why * "cmd && " doesn't start * cmd but waits for more input? - * No reason...) + * The only reason is that it might be + * a "cmd1 && cmd2 &" construct, + * cmd1 may need to run in BG). */ struct pipe *pi = ctx.list_head; if (pi->num_cmds != 0 /* check #1 */ @@ -5133,7 +5238,7 @@ static struct pipe *parse_stream(char **pstring, * and it will match } earlier (not here). */ syntax_error_unexpected_ch(ch); G.last_exitcode = 2; - goto parse_error1; + goto parse_error2; default: if (HUSH_DEBUG) bb_error_msg_and_die("BUG: unexpected %c\n", ch); @@ -5142,7 +5247,7 @@ static struct pipe *parse_stream(char **pstring, parse_error: G.last_exitcode = 1; - parse_error1: + parse_error2: { struct parse_context *pctx; IF_HAS_KEYWORDS(struct parse_context *p2;) @@ -5189,7 +5294,7 @@ static struct pipe *parse_stream(char **pstring, /*** Execution routines ***/ /* Expansion can recurse, need forward decls: */ -#if !BASH_PATTERN_SUBST +#if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE /* only ${var/pattern/repl} (its pattern part) needs additional mode */ #define expand_string_to_string(str, do_unbackslash) \ expand_string_to_string(str) @@ -5317,6 +5422,9 @@ static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const cha #endif static char *encode_then_expand_string(const char *str, int process_bkslash, int do_unbackslash) { +#if !BASH_PATTERN_SUBST + const int do_unbackslash = 1; +#endif char *exp_str; struct in_str input; o_string dest = NULL_O_STRING; @@ -5615,27 +5723,34 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha if (errmsg) goto arith_err; debug_printf_varexp("len:'%s'=%lld\n", exp_word, (long long)len); - if (len >= 0) { /* bash compat: len < 0 is illegal */ - if (beg < 0) /* bash compat */ - beg = 0; - debug_printf_varexp("from val:'%s'\n", val); - if (len == 0 || !val || beg >= strlen(val)) { + if (beg < 0) { + /* negative beg counts from the end */ + beg = (arith_t)strlen(val) + beg; + if (beg < 0) /* ${v: -999999} is "" */ + beg = len = 0; + } + debug_printf_varexp("from val:'%s'\n", val); + if (len < 0) { + /* in bash, len=-n means strlen()-n */ + len = (arith_t)strlen(val) - beg + len; + if (len < 0) /* bash compat */ + die_if_script("%s: substring expression < 0", var); + } + if (len <= 0 || !val || beg >= strlen(val)) { arith_err: - val = NULL; - } else { - /* Paranoia. What if user entered 9999999999999 - * which fits in arith_t but not int? */ - if (len >= INT_MAX) - len = INT_MAX; - val = to_be_freed = xstrndup(val + beg, len); - } - debug_printf_varexp("val:'%s'\n", val); - } else -#endif /* HUSH_SUBSTR_EXPANSION && FEATURE_SH_MATH */ - { - die_if_script("malformed ${%s:...}", var); val = NULL; + } else { + /* Paranoia. What if user entered 9999999999999 + * which fits in arith_t but not int? */ + if (len >= INT_MAX) + len = INT_MAX; + val = to_be_freed = xstrndup(val + beg, len); } + debug_printf_varexp("val:'%s'\n", val); +#else /* not (HUSH_SUBSTR_EXPANSION && FEATURE_SH_MATH) */ + die_if_script("malformed ${%s:...}", var); + val = NULL; +#endif } else { /* one of "-=+?" */ /* Standard-mandated substitution ops: * ${var?word} - indicate error if unset @@ -5936,7 +6051,7 @@ static char **expand_strvec_to_strvec_singleword_noglob(char **argv) */ static char *expand_string_to_string(const char *str, int do_unbackslash) { -#if !BASH_PATTERN_SUBST +#if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE const int do_unbackslash = 1; #endif char *argv[2], **list; @@ -5969,7 +6084,7 @@ static char *expand_string_to_string(const char *str, int do_unbackslash) return (char*)list; } -/* Used for "eval" builtin */ +/* Used for "eval" builtin and case string */ static char* expand_strvec_to_string(char **argv) { char **list; @@ -6511,77 +6626,108 @@ static void setup_heredoc(struct redir_struct *redir) wait(NULL); /* wait till child has died */ } -/* fd: redirect wants this fd to be used (e.g. 3>file). - * Move all conflicting internally used fds, - * and remember them so that we can restore them later. - */ -static int save_fds_on_redirect(int fd, int squirrel[3]) +struct squirrel { + int orig_fd; + int moved_to; + /* moved_to = n: fd was moved to n; restore back to orig_fd after redir */ + /* moved_to = -1: fd was opened by redirect; close orig_fd after redir */ +}; + +static struct squirrel *add_squirrel(struct squirrel *sq, int fd, int avoid_fd) { - if (squirrel) { - /* Handle redirects of fds 0,1,2 */ + int i = 0; - /* If we collide with an already moved stdio fd... */ - if (fd == squirrel[0]) { - squirrel[0] = xdup_and_close(squirrel[0], F_DUPFD); - return 1; - } - if (fd == squirrel[1]) { - squirrel[1] = xdup_and_close(squirrel[1], F_DUPFD); - return 1; - } - if (fd == squirrel[2]) { - squirrel[2] = xdup_and_close(squirrel[2], F_DUPFD); - return 1; - } - /* If we are about to redirect stdio fd, and did not yet move it... */ - if (fd <= 2 && squirrel[fd] < 0) { - /* We avoid taking stdio fds */ - squirrel[fd] = fcntl(fd, F_DUPFD, 10); - if (squirrel[fd] < 0 && errno != EBADF) + if (sq) while (sq[i].orig_fd >= 0) { + /* If we collide with an already moved fd... */ + if (fd == sq[i].moved_to) { + sq[i].moved_to = fcntl_F_DUPFD(sq[i].moved_to, avoid_fd); + debug_printf_redir("redirect_fd %d: already busy, moving to %d\n", fd, sq[i].moved_to); + if (sq[i].moved_to < 0) /* what? */ xfunc_die(); - return 0; /* "we did not close fd" */ + return sq; } + if (fd == sq[i].orig_fd) { + /* Example: echo Hello >/dev/null 1>&2 */ + debug_printf_redir("redirect_fd %d: already moved\n", fd); + return sq; + } + i++; } + sq = xrealloc(sq, (i + 2) * sizeof(sq[0])); + sq[i].orig_fd = fd; + /* If this fd is open, we move and remember it; if it's closed, moved_to = -1 */ + sq[i].moved_to = fcntl_F_DUPFD(fd, avoid_fd); + debug_printf_redir("redirect_fd %d: previous fd is moved to %d (-1 if it was closed)\n", fd, sq[i].moved_to); + if (sq[i].moved_to < 0 && errno != EBADF) + xfunc_die(); + sq[i+1].orig_fd = -1; /* end marker */ + return sq; +} + +/* fd: redirect wants this fd to be used (e.g. 3>file). + * Move all conflicting internally used fds, + * and remember them so that we can restore them later. + */ +static int save_fds_on_redirect(int fd, int avoid_fd, struct squirrel **sqp) +{ + if (avoid_fd < 9) /* the important case here is that it can be -1 */ + avoid_fd = 9; + #if ENABLE_HUSH_INTERACTIVE if (fd != 0 && fd == G.interactive_fd) { - G.interactive_fd = xdup_and_close(G.interactive_fd, F_DUPFD_CLOEXEC); - return 1; + G.interactive_fd = xdup_and_close(G.interactive_fd, F_DUPFD_CLOEXEC, avoid_fd); + debug_printf_redir("redirect_fd %d: matches interactive_fd, moving it to %d\n", fd, G.interactive_fd); + return 1; /* "we closed fd" */ } #endif - /* Are we called from setup_redirects(squirrel==NULL)? Two cases: * (1) Redirect in a forked child. No need to save FILEs' fds, * we aren't going to use them anymore, ok to trash. - * (2) "exec 3>FILE". Bummer. We can save FILEs' fds, - * but how are we doing to use them? + * (2) "exec 3>FILE". Bummer. We can save script FILEs' fds, + * but how are we doing to restore them? * "fileno(fd) = new_fd" can't be done. */ - if (!squirrel) + if (!sqp) return 0; - return save_FILEs_on_redirect(fd); + /* If this one of script's fds? */ + if (save_FILEs_on_redirect(fd, avoid_fd)) + return 1; /* yes. "we closed fd" */ + + /* Check whether it collides with any open fds (e.g. stdio), save fds as needed */ + *sqp = add_squirrel(*sqp, fd, avoid_fd); + return 0; /* "we did not close fd" */ } -static void restore_redirects(int squirrel[3]) +static void restore_redirects(struct squirrel *sq) { - int i, fd; - for (i = 0; i <= 2; i++) { - fd = squirrel[i]; - if (fd != -1) { - /* We simply die on error */ - xmove_fd(fd, i); + + if (sq) { + int i = 0; + while (sq[i].orig_fd >= 0) { + if (sq[i].moved_to >= 0) { + /* We simply die on error */ + debug_printf_redir("restoring redirected fd from %d to %d\n", sq[i].moved_to, sq[i].orig_fd); + xmove_fd(sq[i].moved_to, sq[i].orig_fd); + } else { + /* cmd1 9>FILE; cmd2_should_see_fd9_closed */ + debug_printf_redir("restoring redirected fd %d: closing it\n", sq[i].orig_fd); + close(sq[i].orig_fd); + } + i++; } + free(sq); } - /* Moved G.interactive_fd stays on new fd, not doing anything for it */ + /* If moved, G.interactive_fd stays on new fd, not restoring it */ restore_redirected_FILEs(); } /* squirrel != NULL means we squirrel away copies of stdin, stdout, * and stderr if they are redirected. */ -static int setup_redirects(struct command *prog, int squirrel[]) +static int setup_redirects(struct command *prog, struct squirrel **sqp) { int openfd, mode; struct redir_struct *redir; @@ -6589,7 +6735,7 @@ static int setup_redirects(struct command *prog, int squirrel[]) for (redir = prog->redirects; redir; redir = redir->next) { if (redir->rd_type == REDIRECT_HEREDOC2) { /* "rd_fd<rd_fd, squirrel); + save_fds_on_redirect(redir->rd_fd, /*avoid:*/ 0, sqp); /* for REDIRECT_HEREDOC2, rd_filename holds _contents_ * of the heredoc */ debug_printf_parse("set heredoc '%s'\n", @@ -6628,7 +6774,7 @@ static int setup_redirects(struct command *prog, int squirrel[]) } if (openfd != redir->rd_fd) { - int closed = save_fds_on_redirect(redir->rd_fd, squirrel); + int closed = save_fds_on_redirect(redir->rd_fd, /*avoid:*/ openfd, sqp); if (openfd == REDIRFD_CLOSE) { /* "rd_fd >&-" means "close me" */ if (!closed) { @@ -7056,7 +7202,7 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, /* Do not leak open fds from opened script files etc */ close_all_FILE_list(); debug_printf_exec("running applet '%s'\n", argv[0]); - run_applet_no_and_exit(a, argv); + run_applet_no_and_exit(a, argv[0], argv); } # endif /* Re-exec ourselves */ @@ -7153,24 +7299,54 @@ static const char *get_cmdtext(struct pipe *pi) return pi->cmdtext; } -static void insert_bg_job(struct pipe *pi) +static void remove_job_from_table(struct pipe *pi) +{ + struct pipe *prev_pipe; + + if (pi == G.job_list) { + G.job_list = pi->next; + } else { + prev_pipe = G.job_list; + while (prev_pipe->next != pi) + prev_pipe = prev_pipe->next; + prev_pipe->next = pi->next; + } + G.last_jobid = 0; + if (G.job_list) + G.last_jobid = G.job_list->jobid; +} + +static void delete_finished_job(struct pipe *pi) +{ + remove_job_from_table(pi); + free_pipe(pi); +} + +static void clean_up_last_dead_job(void) +{ + if (G.job_list && !G.job_list->alive_cmds) + delete_finished_job(G.job_list); +} + +static void insert_job_into_table(struct pipe *pi) { struct pipe *job, **jobp; int i; - /* Linear search for the ID of the job to use */ - pi->jobid = 1; - for (job = G.job_list; job; job = job->next) - if (job->jobid >= pi->jobid) - pi->jobid = job->jobid + 1; + clean_up_last_dead_job(); - /* Add job to the list of running jobs */ + /* Find the end of the list, and find next job ID to use */ + i = 0; jobp = &G.job_list; - while ((job = *jobp) != NULL) + while ((job = *jobp) != NULL) { + if (job->jobid > i) + i = job->jobid; jobp = &job->next; - job = *jobp = xmalloc(sizeof(*job)); + } + pi->jobid = i + 1; - *job = *pi; /* physical copy */ + /* Create a new job struct at the end */ + job = *jobp = xmemdup(pi, sizeof(*pi)); job->next = NULL; job->cmds = xzalloc(sizeof(pi->cmds[0]) * pi->num_cmds); /* Cannot copy entire pi->cmds[] vector! This causes double frees */ @@ -7184,31 +7360,6 @@ static void insert_bg_job(struct pipe *pi) printf("[%u] %u %s\n", job->jobid, (unsigned)job->cmds[0].pid, job->cmdtext); G.last_jobid = job->jobid; } - -static void remove_bg_job(struct pipe *pi) -{ - struct pipe *prev_pipe; - - if (pi == G.job_list) { - G.job_list = pi->next; - } else { - prev_pipe = G.job_list; - while (prev_pipe->next != pi) - prev_pipe = prev_pipe->next; - prev_pipe->next = pi->next; - } - if (G.job_list) - G.last_jobid = G.job_list->jobid; - else - G.last_jobid = 0; -} - -/* Remove a backgrounded job */ -static void delete_finished_bg_job(struct pipe *pi) -{ - remove_bg_job(pi); - free_pipe(pi); -} #endif /* JOB */ static int job_exited_or_stopped(struct pipe *pi) @@ -7295,7 +7446,7 @@ static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status) if (G_interactive_fd) { #if ENABLE_HUSH_JOB if (fg_pipe->alive_cmds != 0) - insert_bg_job(fg_pipe); + insert_job_into_table(fg_pipe); #endif return rcode; } @@ -7324,16 +7475,31 @@ static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status) found_pi_and_prognum: if (dead) { /* child exited */ - pi->cmds[i].pid = 0; - pi->cmds[i].cmd_exitcode = WEXITSTATUS(status); + int rcode = WEXITSTATUS(status); if (WIFSIGNALED(status)) - pi->cmds[i].cmd_exitcode = 128 + WTERMSIG(status); + rcode = 128 + WTERMSIG(status); + pi->cmds[i].cmd_exitcode = rcode; + if (G.last_bg_pid == pi->cmds[i].pid) + G.last_bg_pid_exitcode = rcode; + pi->cmds[i].pid = 0; pi->alive_cmds--; if (!pi->alive_cmds) { - if (G_interactive_fd) + if (G_interactive_fd) { printf(JOB_STATUS_FORMAT, pi->jobid, "Done", pi->cmdtext); - delete_finished_bg_job(pi); + delete_finished_job(pi); + } else { +/* + * bash deletes finished jobs from job table only in interactive mode, + * after "jobs" cmd, or if pid of a new process matches one of the old ones + * (see cleanup_dead_jobs(), delete_old_job(), J_NOTIFIED in bash source). + * Testcase script: "(exit 3) & sleep 1; wait %1; echo $?" prints 3 in bash. + * We only retain one "dead" job, if it's the single job on the list. + * This covers most of real-world scenarios where this is useful. + */ + if (pi != G.job_list) + delete_finished_job(pi); + } } } else { /* child stopped */ @@ -7490,14 +7656,14 @@ static int checkjobs_and_fg_shell(struct pipe *fg_pipe) static int redirect_and_varexp_helper(char ***new_env_p, struct variable **old_vars_p, struct command *command, - int squirrel[3], + struct squirrel **sqp, char **argv_expanded) { /* setup_redirects acts on file descriptors, not FILEs. * This is perfect for work that comes after exec(). * Is it really safe for inline use? Experimentally, * things seem to work. */ - int rcode = setup_redirects(command, squirrel); + int rcode = setup_redirects(command, sqp); if (rcode == 0) { char **new_env = expand_assignments(command->argv, command->assignment_cnt); *new_env_p = new_env; @@ -7517,8 +7683,7 @@ static NOINLINE int run_pipe(struct pipe *pi) struct command *command; char **argv_expanded; char **argv; - /* it is not always needed, but we aim to smaller code */ - int squirrel[] = { -1, -1, -1 }; + struct squirrel *squirrel = NULL; int rcode; debug_printf_exec("run_pipe start: members:%d\n", pi->num_cmds); @@ -7575,7 +7740,7 @@ static NOINLINE int run_pipe(struct pipe *pi) /* { list } */ debug_printf("non-subshell group\n"); rcode = 1; /* exitcode if redir failed */ - if (setup_redirects(command, squirrel) == 0) { + if (setup_redirects(command, &squirrel) == 0) { debug_printf_exec(": run_list\n"); rcode = run_list(command->group) & 0xff; } @@ -7602,7 +7767,7 @@ static NOINLINE int run_pipe(struct pipe *pi) /* Ensure redirects take effect (that is, create files). * Try "a=t >file" */ #if 0 /* A few cases in testsuite fail with this code. FIXME */ - rcode = redirect_and_varexp_helper(&new_env, /*old_vars:*/ NULL, command, squirrel, /*argv_expanded:*/ NULL); + rcode = redirect_and_varexp_helper(&new_env, /*old_vars:*/ NULL, command, &squirrel, /*argv_expanded:*/ NULL); /* Set shell variables */ if (new_env) { argv = new_env; @@ -7624,7 +7789,7 @@ static NOINLINE int run_pipe(struct pipe *pi) #else /* Older, bigger, but more correct code */ - rcode = setup_redirects(command, squirrel); + rcode = setup_redirects(command, &squirrel); restore_redirects(squirrel); /* Set shell variables */ if (G_x_mode) @@ -7687,7 +7852,7 @@ static NOINLINE int run_pipe(struct pipe *pi) goto clean_up_and_ret1; } } - rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, squirrel, argv_expanded); + rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, &squirrel, argv_expanded); if (rcode == 0) { if (!funcp) { debug_printf_exec(": builtin '%s' '%s'...\n", @@ -7728,7 +7893,7 @@ static NOINLINE int run_pipe(struct pipe *pi) if (ENABLE_FEATURE_SH_NOFORK) { int n = find_applet_by_name(argv_expanded[0]); if (n >= 0 && APPLET_IS_NOFORK(n)) { - rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, squirrel, argv_expanded); + rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, &squirrel, argv_expanded); if (rcode == 0) { debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", argv_expanded[0], argv_expanded[1]); @@ -7953,6 +8118,7 @@ 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; + int sv_errexit_depth; if (G.flag_SIGINT) break; @@ -7962,6 +8128,13 @@ static int run_list(struct pipe *pi) IF_HAS_KEYWORDS(rword = pi->res_word;) debug_printf_exec(": rword=%d cond_code=%d last_rword=%d\n", rword, cond_code, last_rword); + + sv_errexit_depth = G.errexit_depth; + if (IF_HAS_KEYWORDS(rword == RES_IF || rword == RES_ELIF ||) + pi->followup != PIPE_SEQ + ) { + G.errexit_depth++; + } #if ENABLE_HUSH_LOOPS if ((rword == RES_WHILE || rword == RES_UNTIL || rword == RES_FOR) && loop_top == NULL /* avoid bumping G.depth_of_loop twice */ @@ -8053,6 +8226,7 @@ static int run_list(struct pipe *pi) if (rword == RES_CASE) { debug_printf_exec("CASE cond_code:%d\n", cond_code); case_word = expand_strvec_to_string(pi->cmds->argv); + unbackslash(case_word); continue; } if (rword == RES_MATCH) { @@ -8064,9 +8238,10 @@ static int run_list(struct pipe *pi) /* all prev words didn't match, does this one match? */ argv = pi->cmds->argv; while (*argv) { - char *pattern = expand_string_to_string(*argv, /*unbackslash:*/ 1); + char *pattern = expand_string_to_string(*argv, /*unbackslash:*/ 0); /* TODO: which FNM_xxx flags to use? */ cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0); + debug_printf_exec("fnmatch(pattern:'%s',str:'%s'):%d\n", pattern, case_word, cond_code); free(pattern); if (cond_code == 0) { /* match! we will execute this branch */ free(case_word); @@ -8147,10 +8322,11 @@ static int run_list(struct pipe *pi) * I'm NOT treating inner &'s as jobs */ #if ENABLE_HUSH_JOB if (G.run_list_level == 1) - insert_bg_job(pi); + insert_job_into_table(pi); #endif /* Last command's pid goes to $! */ G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid; + G.last_bg_pid_exitcode = 0; debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n"); /* Check pi->pi_inverted? "! sleep 1 & echo $?": bash says 1. dash and ash says 0 */ rcode = EXIT_SUCCESS; @@ -8172,6 +8348,14 @@ static int run_list(struct pipe *pi) check_and_run_traps(); } + /* Handle "set -e" */ + if (rcode != 0 && G.o_opt[OPT_O_ERREXIT]) { + debug_printf_exec("ERREXIT:1 errexit_depth:%d\n", G.errexit_depth); + if (G.errexit_depth == 0) + hush_exit(rcode); + } + G.errexit_depth = sv_errexit_depth; + /* Analyze how result affects subsequent commands */ #if ENABLE_HUSH_IF if (rword == RES_IF || rword == RES_ELIF) @@ -8351,6 +8535,9 @@ static int set_mode(int state, char mode, const char *o_opt) G.o_opt[idx] = state; break; } + case 'e': + G.o_opt[OPT_O_ERREXIT] = state; + break; default: return EXIT_FAILURE; } @@ -8477,7 +8664,7 @@ int hush_main(int argc, char **argv) flags = (argv[0] && argv[0][0] == '-') ? OPT_login : 0; builtin_argc = 0; while (1) { - opt = getopt(argc, argv, "+c:xinsl" + opt = getopt(argc, argv, "+c:exinsl" #if !BB_MMU "<:$:R:V:" # if ENABLE_HUSH_FUNCTIONS @@ -8595,6 +8782,7 @@ int hush_main(int argc, char **argv) #endif case 'n': case 'x': + case 'e': if (set_mode(1, opt, NULL) == 0) /* no error */ break; default: @@ -8681,7 +8869,7 @@ int hush_main(int argc, char **argv) G_saved_tty_pgrp = 0; /* try to dup stdin to high fd#, >= 255 */ - G_interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255); + G_interactive_fd = fcntl_F_DUPFD(STDIN_FILENO, 254); if (G_interactive_fd < 0) { /* try to dup to any fd */ G_interactive_fd = dup(STDIN_FILENO); @@ -8754,7 +8942,7 @@ int hush_main(int argc, char **argv) #elif ENABLE_HUSH_INTERACTIVE /* No job control compiled in, only prompt/line editing */ if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) { - G_interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255); + G_interactive_fd = fcntl_F_DUPFD(STDIN_FILENO, 254); if (G_interactive_fd < 0) { /* try to dup to any fd */ G_interactive_fd = dup(STDIN_FILENO); @@ -9368,7 +9556,18 @@ static int FAST_FUNC builtin_shift(char **argv) int n = 1; argv = skip_dash_dash(argv); if (argv[0]) { - n = atoi(argv[0]); + n = bb_strtou(argv[0], NULL, 10); + if (errno || n < 0) { + /* shared string with ash.c */ + bb_error_msg("Illegal number: %s", argv[0]); + /* + * ash aborts in this case. + * bash prints error message and set $? to 1. + * Interestingly, for "shift 99999" bash does not + * print error message, but does set $? to 1 + * (and does no shifting at all). + */ + } } if (n >= 0 && n < G.global_argc) { if (G_global_args_malloced) { @@ -9481,7 +9680,7 @@ static int FAST_FUNC builtin_trap(char **argv) if (sig < 0 || sig >= NSIG) { ret = EXIT_FAILURE; /* Mimic bash message exactly */ - bb_perror_msg("trap: %s: invalid signal specification", argv[-1]); + bb_error_msg("trap: %s: invalid signal specification", argv[-1]); continue; } @@ -9574,6 +9773,9 @@ static int FAST_FUNC builtin_jobs(char **argv UNUSED_PARAM) printf(JOB_STATUS_FORMAT, job->jobid, status_string, job->cmdtext); } + + clean_up_last_dead_job(); + return EXIT_SUCCESS; } @@ -9618,14 +9820,14 @@ static int FAST_FUNC builtin_fg_bg(char **argv) i = kill(- pi->pgrp, SIGCONT); if (i < 0) { if (errno == ESRCH) { - delete_finished_bg_job(pi); + delete_finished_job(pi); return EXIT_SUCCESS; } bb_perror_msg("kill (SIGCONT)"); } if (argv[0][0] == 'f') { - remove_bg_job(pi); + remove_job_from_table(pi); /* FG job shouldn't be in job table */ return checkjobs_and_fg_shell(pi); } return EXIT_SUCCESS; @@ -9817,8 +10019,12 @@ static int FAST_FUNC builtin_wait(char **argv) wait_pipe = parse_jobspec(*argv); if (wait_pipe) { ret = job_exited_or_stopped(wait_pipe); - if (ret < 0) + if (ret < 0) { ret = wait_for_child_or_signal(wait_pipe, 0); + } else { + /* waiting on "last dead job" removes it */ + clean_up_last_dead_job(); + } } /* else: parse_jobspec() already emitted error msg */ continue; @@ -9834,14 +10040,15 @@ static int FAST_FUNC builtin_wait(char **argv) ret = waitpid(pid, &status, WNOHANG); if (ret < 0) { /* No */ + ret = 127; if (errno == ECHILD) { - if (G.last_bg_pid > 0 && pid == G.last_bg_pid) { + if (pid == G.last_bg_pid) { /* "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 = G.last_bg_pid_exitstatus - FIXME */ + ret = G.last_bg_pid_exitcode; } else { /* Example: "wait 1". mimic bash message */ bb_error_msg("wait: pid %d is not a child of this shell", (int)pid); @@ -9850,7 +10057,6 @@ static int FAST_FUNC builtin_wait(char **argv) /* ??? */ bb_perror_msg("wait %s", *argv); } - ret = 127; continue; /* bash checks all argv[] */ } if (ret == 0) {