ash: "you disabled math" is wrong: user did not disable it, builder of ash did
[oweals/busybox.git] / shell / hush.c
index 30add72f01aa6831c55db95ae9f6d4254fd6d2da..c8356f4b8552745349bbe2742ff5994309374b4d 100644 (file)
  *
  * 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 >&
  *          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"
  * 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"
 
 #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 <enter> 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 && <newline>" doesn't start
                                         * cmd but waits for more input?
-                                        * No reason...)
+                                        * The only reason is that it might be
+                                        * a "cmd1 && <nl> 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<<HERE" case */
-                       save_fds_on_redirect(redir->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) {