od_bloaty: fix debug code
[oweals/busybox.git] / shell / hush.c
index 0fae809b3e06b5b78f8d26e659005a929ca30a3e..7b83c736c1ec8e77632be08d8226f9c58496ebd6 100644 (file)
  *      follow IFS rules more precisely, including update semantics
  *      tilde expansion
  *      aliases
- *      builtins mandated by standards we don't support:
- *          [un]alias, command, fc, getopts, 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
+ *      "command" missing features:
+ *          command -p CMD: run CMD using default $PATH
+ *              (can use this to override standalone shell as well?)
  *          command BLTIN: disables special-ness (e.g. errors do not abort)
- *          getopts: getopt() for shells
- *          times: print getrusage(SELF/CHILDREN).ru_utime/ru_stime
+ *          command -V CMD1 CMD2 CMD3 (multiple args) (not in standard)
+ *      builtins mandated by standards we don't support:
+ *          [un]alias, fc:
  *          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
 //config:      default y
 //config:      depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
 //config:
+//config:config HUSH_COMMAND
+//config:      bool "command builtin"
+//config:      default y
+//config:      depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
+//config:
 //config:config HUSH_TRAP
 //config:      bool "trap builtin"
 //config:      default y
 //config:      default y
 //config:      depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
 //config:
+//config:config HUSH_TIMES
+//config:      bool "times builtin"
+//config:      default y
+//config:      depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
+//config:
 //config:config HUSH_READ
 //config:      bool "read builtin"
 //config:      default y
 //config:      default y
 //config:      depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
 //config:
+//config:config HUSH_GETOPTS
+//config:      bool "getopts builtin"
+//config:      default y
+//config:      depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
+//config:
 //config:config HUSH_MEMLEAK
 //config:      bool "memleak builtin (debugging)"
 //config:      default n
 #if ENABLE_HUSH_CASE
 # include <fnmatch.h>
 #endif
+#include <sys/times.h>
 #include <sys/utsname.h> /* for setting $HOSTNAME */
 
 #include "busybox.h"  /* for APPLET_IS_NOFORK/NOEXEC */
 #define BASH_SUBSTR        ENABLE_HUSH_BASH_COMPAT
 #define BASH_SOURCE        ENABLE_HUSH_BASH_COMPAT
 #define BASH_HOSTNAME_VAR  ENABLE_HUSH_BASH_COMPAT
+#define BASH_LINENO_VAR    ENABLE_HUSH_BASH_COMPAT
 #define BASH_TEST2         (ENABLE_HUSH_BASH_COMPAT && ENABLE_HUSH_TEST)
+#define BASH_READ_D        ENABLE_HUSH_BASH_COMPAT
 
 
 /* Build knobs */
 # define MINUS_PLUS_EQUAL_QUESTION ("%#:-=+?" + 3)
 #endif
 
-#define SPECIAL_VAR_SYMBOL   3
+#define SPECIAL_VAR_SYMBOL_STR "\3"
+#define SPECIAL_VAR_SYMBOL       3
+/* The "variable" with name "\1" emits string "\3". Testcase: "echo ^C" */
+#define SPECIAL_VAR_QUOTED_SVS   1
 
 struct variable;
 
@@ -598,6 +613,9 @@ typedef enum redir_type {
 struct command {
        pid_t pid;                  /* 0 if exited */
        int assignment_cnt;         /* how many argv[i] are assignments? */
+#if BASH_LINENO_VAR
+       unsigned lineno;
+#endif
        smallint cmd_type;          /* CMD_xxx */
 #define CMD_NORMAL   0
 #define CMD_SUBSHELL 1
@@ -896,6 +914,9 @@ struct globals {
 #if ENABLE_HUSH_LOOPS
        unsigned depth_break_continue;
        unsigned depth_of_loop;
+#endif
+#if ENABLE_HUSH_GETOPTS
+       unsigned getopt_count;
 #endif
        const char *ifs;
        const char *cwd;
@@ -913,6 +934,10 @@ struct globals {
        unsigned count_SIGCHLD;
        unsigned handled_SIGCHLD;
        smallint we_have_children;
+#endif
+#if BASH_LINENO_VAR
+       unsigned lineno;
+       char *lineno_var;
 #endif
        struct FILE_list *FILE_list;
        /* Which signals have non-DFL handler (even with no traps set)?
@@ -977,6 +1002,9 @@ static int builtin_readonly(char **argv) FAST_FUNC;
 static int builtin_fg_bg(char **argv) FAST_FUNC;
 static int builtin_jobs(char **argv) FAST_FUNC;
 #endif
+#if ENABLE_HUSH_GETOPTS
+static int builtin_getopts(char **argv) FAST_FUNC;
+#endif
 #if ENABLE_HUSH_HELP
 static int builtin_help(char **argv) FAST_FUNC;
 #endif
@@ -1010,6 +1038,9 @@ static int builtin_trap(char **argv) FAST_FUNC;
 #if ENABLE_HUSH_TYPE
 static int builtin_type(char **argv) FAST_FUNC;
 #endif
+#if ENABLE_HUSH_TIMES
+static int builtin_times(char **argv) FAST_FUNC;
+#endif
 static int builtin_true(char **argv) FAST_FUNC;
 #if ENABLE_HUSH_UMASK
 static int builtin_umask(char **argv) FAST_FUNC;
@@ -1070,6 +1101,9 @@ static const struct built_in_command bltins1[] = {
 #if ENABLE_HUSH_JOB
        BLTIN("fg"       , builtin_fg_bg   , "Bring job to foreground"),
 #endif
+#if ENABLE_HUSH_GETOPTS
+       BLTIN("getopts"  , builtin_getopts , NULL),
+#endif
 #if ENABLE_HUSH_HELP
        BLTIN("help"     , builtin_help    , NULL),
 #endif
@@ -1104,6 +1138,9 @@ static const struct built_in_command bltins1[] = {
 #if BASH_SOURCE
        BLTIN("source"   , builtin_source  , NULL),
 #endif
+#if ENABLE_HUSH_TIMES
+       BLTIN("times"    , builtin_times   , NULL),
+#endif
 #if ENABLE_HUSH_TRAP
        BLTIN("trap"     , builtin_trap    , "Trap signals"),
 #endif
@@ -1272,7 +1309,7 @@ static void xxfree(void *ptr)
  * HUSH_DEBUG >= 2 prints line number in this file where it was detected.
  */
 #if HUSH_DEBUG < 2
-# define die_if_script(lineno, ...)             die_if_script(__VA_ARGS__)
+# define msg_and_die_if_script(lineno, ...)     msg_and_die_if_script(__VA_ARGS__)
 # define syntax_error(lineno, msg)              syntax_error(msg)
 # define syntax_error_at(lineno, msg)           syntax_error_at(msg)
 # define syntax_error_unterm_ch(lineno, ch)     syntax_error_unterm_ch(ch)
@@ -1280,7 +1317,16 @@ static void xxfree(void *ptr)
 # define syntax_error_unexpected_ch(lineno, ch) syntax_error_unexpected_ch(ch)
 #endif
 
-static void die_if_script(unsigned lineno, const char *fmt, ...)
+static void die_if_script(void)
+{
+       if (!G_interactive_fd) {
+               if (G.last_exitcode) /* sometines it's 2, not 1 (bash compat) */
+                       xfunc_error_retval = G.last_exitcode;
+               xfunc_die();
+       }
+}
+
+static void msg_and_die_if_script(unsigned lineno, const char *fmt, ...)
 {
        va_list p;
 
@@ -1290,8 +1336,7 @@ static void die_if_script(unsigned lineno, const char *fmt, ...)
        va_start(p, fmt);
        bb_verror_msg(fmt, p, NULL);
        va_end(p);
-       if (!G_interactive_fd)
-               xfunc_die();
+       die_if_script();
 }
 
 static void syntax_error(unsigned lineno UNUSED_PARAM, const char *msg)
@@ -1300,16 +1345,20 @@ static void syntax_error(unsigned lineno UNUSED_PARAM, const char *msg)
                bb_error_msg("syntax error: %s", msg);
        else
                bb_error_msg("syntax error");
+       die_if_script();
 }
 
 static void syntax_error_at(unsigned lineno UNUSED_PARAM, const char *msg)
 {
        bb_error_msg("syntax error at '%s'", msg);
+       die_if_script();
 }
 
 static void syntax_error_unterm_str(unsigned lineno UNUSED_PARAM, const char *s)
 {
        bb_error_msg("syntax error: unterminated %s", s);
+//? source4.tests fails: in bash, echo ${^} in script does not terminate the script
+//     die_if_script();
 }
 
 static void syntax_error_unterm_ch(unsigned lineno, char ch)
@@ -1327,17 +1376,18 @@ static void syntax_error_unexpected_ch(unsigned lineno UNUSED_PARAM, int ch)
        bb_error_msg("hush.c:%u", lineno);
 #endif
        bb_error_msg("syntax error: unexpected %s", ch == EOF ? "EOF" : msg);
+       die_if_script();
 }
 
 #if HUSH_DEBUG < 2
-# undef die_if_script
+# undef msg_and_die_if_script
 # undef syntax_error
 # undef syntax_error_at
 # undef syntax_error_unterm_ch
 # undef syntax_error_unterm_str
 # undef syntax_error_unexpected_ch
 #else
-# define die_if_script(...)             die_if_script(__LINE__, __VA_ARGS__)
+# define msg_and_die_if_script(...)     msg_and_die_if_script(__LINE__, __VA_ARGS__)
 # define syntax_error(msg)              syntax_error(__LINE__, msg)
 # define syntax_error_at(msg)           syntax_error_at(__LINE__, msg)
 # define syntax_error_unterm_ch(ch)     syntax_error_unterm_ch(__LINE__, ch)
@@ -1454,11 +1504,11 @@ static int fcntl_F_DUPFD(int fd, int avoid_fd)
        return newfd;
 }
 
-static int xdup_and_close(int fd, int F_DUPFD_maybe_CLOEXEC, int avoid_fd)
+static int xdup_CLOEXEC_and_close(int fd, int avoid_fd)
 {
        int newfd;
  repeat:
-       newfd = fcntl(fd, F_DUPFD_maybe_CLOEXEC, avoid_fd + 1);
+       newfd = fcntl(fd, F_DUPFD_CLOEXEC, avoid_fd + 1);
        if (newfd < 0) {
                if (errno == EBUSY)
                        goto repeat;
@@ -1469,6 +1519,8 @@ static int xdup_and_close(int fd, int F_DUPFD_maybe_CLOEXEC, int avoid_fd)
                        return fd;
                xfunc_die();
        }
+       if (F_DUPFD_CLOEXEC == F_DUPFD) /* if old libc (w/o F_DUPFD_CLOEXEC) */
+               fcntl(newfd, F_SETFD, FD_CLOEXEC);
        close(fd);
        return newfd;
 }
@@ -1507,7 +1559,7 @@ static int save_FILEs_on_redirect(int fd, int avoid_fd)
        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, avoid_fd);
+                       fl->fd = xdup_CLOEXEC_and_close(fd, avoid_fd);
                        debug_printf_redir("redirect_fd %d: matches a script fd, moving it to %d\n", fd, fl->fd);
                        return 1;
                }
@@ -1544,6 +1596,16 @@ static void close_all_FILE_list(void)
        }
 }
 #endif
+static int fd_in_FILEs(int fd)
+{
+       struct FILE_list *fl = G.FILE_list;
+       while (fl) {
+               if (fl->fd == fd)
+                       return 1;
+               fl = fl->next;
+       }
+       return 0;
+}
 
 
 /* Helpers for setting new $n and restoring them back
@@ -1788,7 +1850,7 @@ static void restore_ttypgrp_and__exit(void)
  *     echo END_OF_SCRIPT
  * lseeks fd in input FILE object from EOF to "e" in "echo END_OF_SCRIPT".
  * This makes "echo END_OF_SCRIPT" executed twice.
- * Similar problems can be seen with die_if_script() -> xfunc_die()
+ * Similar problems can be seen with msg_and_die_if_script() -> xfunc_die()
  * and in `cmd` handling.
  * If set as die_func(), this makes xfunc_die() exit via _exit(), not exit():
  */
@@ -1874,7 +1936,7 @@ static void hush_exit(int exitcode)
        if (G.exiting <= 0 && G_traps && G_traps[0] && G_traps[0][0]) {
                char *argv[3];
                /* argv[0] is unused */
-               argv[1] = G_traps[0];
+               argv[1] = xstrdup(G_traps[0]); /* copy, since EXIT trap handler may modify G_traps[0] */
                argv[2] = NULL;
                G.exiting = 1; /* prevent EXIT trap recursion */
                /* Note: G_traps[0] is not cleared!
@@ -1954,6 +2016,9 @@ static int check_and_run_traps(void)
                        break;
 #if ENABLE_HUSH_JOB
                case SIGHUP: {
+//TODO: why are we doing this? ash and dash don't do this,
+//they have no handler for SIGHUP at all,
+//they rely on kernel to send SIGHUP+SIGCONT to orphaned process groups
                        struct pipe *job;
                        debug_printf_exec("%s: sig:%d default SIGHUP handler\n", __func__, sig);
                        /* bash is observed to signal whole process groups,
@@ -2078,6 +2143,13 @@ static int set_local_var(char *str, unsigned flags)
        }
 
        name_len = eq_sign - str + 1; /* including '=' */
+#if BASH_LINENO_VAR
+       if (G.lineno_var) {
+               if (name_len == 7 && strncmp("LINENO", str, 6) == 0)
+                       G.lineno_var = NULL;
+       }
+#endif
+
        var_pp = &G.top_var;
        while ((cur = *var_pp) != NULL) {
                if (strncmp(cur->varstr, str, name_len) != 0) {
@@ -2164,6 +2236,11 @@ static int set_local_var(char *str, unsigned flags)
                cur->flg_export = 1;
        if (name_len == 4 && cur->varstr[0] == 'P' && cur->varstr[1] == 'S')
                cmdedit_update_prompt();
+#if ENABLE_HUSH_GETOPTS
+       /* defoptindvar is a "OPTIND=..." constant string */
+       if (strncmp(cur->varstr, defoptindvar, 7) == 0)
+               G.getopt_count = 0;
+#endif
        if (cur->flg_export) {
                if (flags & SETFLAG_UNEXPORT) {
                        cur->flg_export = 0;
@@ -2194,6 +2271,16 @@ static int unset_local_var_len(const char *name, int name_len)
 
        if (!name)
                return EXIT_SUCCESS;
+
+#if ENABLE_HUSH_GETOPTS
+       if (name_len == 6 && strncmp(name, "OPTIND", 6) == 0)
+               G.getopt_count = 0;
+#endif
+#if BASH_LINENO_VAR
+       if (name_len == 6 && G.lineno_var && strncmp(name, "LINENO", 6) == 0)
+               G.lineno_var = NULL;
+#endif
+
        var_pp = &G.top_var;
        while ((cur = *var_pp) != NULL) {
                if (strncmp(cur->varstr, name, name_len) == 0 && cur->varstr[name_len] == '=') {
@@ -2216,7 +2303,7 @@ static int unset_local_var_len(const char *name, int name_len)
        return EXIT_SUCCESS;
 }
 
-#if ENABLE_HUSH_UNSET
+#if ENABLE_HUSH_UNSET || ENABLE_HUSH_GETOPTS
 static int unset_local_var(const char *name)
 {
        return unset_local_var_len(name, strlen(name));
@@ -2238,7 +2325,7 @@ static void unset_vars(char **strings)
        free(strings);
 }
 
-#if BASH_HOSTNAME_VAR || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_READ
+#if BASH_HOSTNAME_VAR || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_READ || ENABLE_HUSH_GETOPTS
 static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val)
 {
        char *var = xasprintf("%s=%s", name, val);
@@ -2399,18 +2486,17 @@ static int get_user_input(struct in_str *i)
                /* buglet: SIGINT will not make new prompt to appear _at once_,
                 * only after <Enter>. (^C works immediately) */
                r = read_line_input(G.line_input_state, prompt_str,
-                               G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1,
-                               /*timeout*/ -1
+                               G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1
                );
                /* read_line_input intercepts ^C, "convert" it to SIGINT */
-               if (r == 0) {
-                       write(STDOUT_FILENO, "^C", 2);
+               if (r == 0)
                        raise(SIGINT);
-               }
                check_and_run_traps();
                if (r != 0 && !G.flag_SIGINT)
                        break;
                /* ^C or SIGINT: repeat */
+               /* bash prints ^C even on real SIGINT (non-kbd generated) */
+               write(STDOUT_FILENO, "^C", 2);
                G.last_exitcode = 128 + SIGINT;
        }
        if (r < 0) {
@@ -2513,6 +2599,10 @@ static int i_getch(struct in_str *i)
  out:
        debug_printf("file_get: got '%c' %d\n", ch, ch);
        i->last_char = ch;
+#if BASH_LINENO_VAR
+       if (ch == '\n')
+               G.lineno++;
+#endif
        return ch;
 }
 
@@ -3372,7 +3462,7 @@ static int done_command(struct parse_context *ctx)
 #if 0  /* Instead we emit error message at run time */
        if (ctx->pending_redirect) {
                /* For example, "cmd >" (no filename to redirect to) */
-               die_if_script("syntax error: %s", "invalid redirect");
+               syntax_error("invalid redirect");
                ctx->pending_redirect = NULL;
        }
 #endif
@@ -3395,6 +3485,9 @@ static int done_command(struct parse_context *ctx)
        ctx->command = command = &pi->cmds[pi->num_cmds];
  clear_and_ret:
        memset(command, 0, sizeof(*command));
+#if BASH_LINENO_VAR
+       command->lineno = G.lineno;
+#endif
        return pi->num_cmds; /* used only for 0/nonzero check */
 }
 
@@ -3779,21 +3872,6 @@ static int done_word(o_string *word, struct parse_context *ctx)
                        word->o_assignment = MAYBE_ASSIGNMENT;
                }
                debug_printf_parse("word->o_assignment='%s'\n", assignment_flag[word->o_assignment]);
-
-               if (word->has_quoted_part
-                /* optimization: and if it's ("" or '') or ($v... or `cmd`...): */
-                && (word->data[0] == '\0' || word->data[0] == SPECIAL_VAR_SYMBOL)
-                /* (otherwise it's known to be not empty and is already safe) */
-               ) {
-                       /* exclude "$@" - it can expand to no word despite "" */
-                       char *p = word->data;
-                       while (p[0] == SPECIAL_VAR_SYMBOL
-                           && (p[1] & 0x7f) == '@'
-                           && p[2] == SPECIAL_VAR_SYMBOL
-                       ) {
-                               p += 3;
-                       }
-               }
                command->argv = add_string_to_strings(command->argv, xstrdup(word->data));
                debug_print_strings("word appended to argv", command->argv);
        }
@@ -3938,7 +4016,7 @@ static int parse_redirect(struct parse_context *ctx,
 #if 0          /* Instead we emit error message at run time */
                if (ctx->pending_redirect) {
                        /* For example, "cmd > <file" */
-                       die_if_script("syntax error: %s", "invalid redirect");
+                       syntax_error("invalid redirect");
                }
 #endif
                /* Set ctx->pending_redirect, so we know what to do at the
@@ -4853,7 +4931,8 @@ static struct pipe *parse_stream(char **pstring,
                        next = i_peek(input);
 
                is_special = "{}<>;&|()#'" /* special outside of "str" */
-                               "\\$\"" IF_HUSH_TICK("`"); /* always special */
+                               "\\$\"" IF_HUSH_TICK("`") /* always special */
+                               SPECIAL_VAR_SYMBOL_STR;
                /* Are { and } special here? */
                if (ctx.command->argv /* word [word]{... - non-special */
                 || dest.length       /* word{... - non-special */
@@ -5010,10 +5089,16 @@ static struct pipe *parse_stream(char **pstring,
                                else
                                        o_free_unsafe(&ctx.as_string);
 #endif
-                               debug_leave();
+                               if (ch != ';' && IS_NULL_PIPE(ctx.list_head)) {
+                                       /* Example: bare "{ }", "()" */
+                                       G.last_exitcode = 2; /* bash compat */
+                                       syntax_error_unexpected_ch(ch);
+                                       goto parse_error2;
+                               }
                                debug_printf_parse("parse_stream return %p: "
                                                "end_trigger char found\n",
                                                ctx.list_head);
+                               debug_leave();
                                return ctx.list_head;
                        }
                }
@@ -5073,14 +5158,23 @@ static struct pipe *parse_stream(char **pstring,
                case '#':
                        if (dest.length == 0 && !dest.has_quoted_part) {
                                /* skip "#comment" */
+                               /* note: we do not add it to &ctx.as_string */
+/* TODO: in bash:
+ * comment inside $() goes to the next \n, even inside quoted string (!):
+ * cmd "$(cmd2 #comment)" - syntax error
+ * cmd "`cmd2 #comment`" - ok
+ * We accept both (comment ends where command subst ends, in both cases).
+ */
                                while (1) {
                                        ch = i_peek(input);
-                                       if (ch == EOF || ch == '\n')
+                                       if (ch == '\n') {
+                                               nommu_addchr(&ctx.as_string, '\n');
+                                               break;
+                                       }
+                                       ch = i_getch(input);
+                                       if (ch == EOF)
                                                break;
-                                       i_getch(input);
-                                       /* note: we do not add it to &ctx.as_string */
                                }
-                               nommu_addchr(&ctx.as_string, '\n');
                                continue; /* back to top of while (1) */
                        }
                        break;
@@ -5110,8 +5204,14 @@ static struct pipe *parse_stream(char **pstring,
                /* Note: nommu_addchr(&ctx.as_string, ch) is already done */
 
                switch (ch) {
-               case '#': /* non-comment #: "echo a#b" etc */
-                       o_addQchr(&dest, ch);
+               case SPECIAL_VAR_SYMBOL:
+                       /* Convert raw ^C to corresponding special variable reference */
+                       o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+                       o_addchr(&dest, SPECIAL_VAR_QUOTED_SVS);
+                       /* fall through */
+               case '#':
+                       /* non-comment #: "echo a#b" etc */
+                       o_addchr(&dest, ch);
                        break;
                case '\\':
                        if (next == EOF) {
@@ -5153,6 +5253,11 @@ static struct pipe *parse_stream(char **pstring,
                                        nommu_addchr(&ctx.as_string, ch);
                                        if (ch == '\'')
                                                break;
+                                       if (ch == SPECIAL_VAR_SYMBOL) {
+                                               /* Convert raw ^C to corresponding special variable reference */
+                                               o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+                                               o_addchr(&dest, SPECIAL_VAR_QUOTED_SVS);
+                                       }
                                        o_addqchr(&dest, ch);
                                }
                        }
@@ -5271,8 +5376,8 @@ static struct pipe *parse_stream(char **pstring,
                        /* proper use of this character is caught by end_trigger:
                         * if we see {, we call parse_group(..., end_trigger='}')
                         * and it will match } earlier (not here). */
-                       syntax_error_unexpected_ch(ch);
                        G.last_exitcode = 2;
+                       syntax_error_unexpected_ch(ch);
                        goto parse_error2;
                default:
                        if (HUSH_DEBUG)
@@ -5458,7 +5563,7 @@ static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const cha
 static char *encode_then_expand_string(const char *str, int process_bkslash, int do_unbackslash)
 {
 #if !BASH_PATTERN_SUBST
-       const int do_unbackslash = 1;
+       enum { do_unbackslash = 1 };
 #endif
        char *exp_str;
        struct in_str input;
@@ -5502,7 +5607,7 @@ static arith_t expand_and_evaluate_arith(const char *arg, const char **errmsg_p)
        if (errmsg_p)
                *errmsg_p = math_state.errmsg;
        if (math_state.errmsg)
-               die_if_script(math_state.errmsg);
+               msg_and_die_if_script(math_state.errmsg);
        return res;
 }
 #endif
@@ -5769,7 +5874,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
                                /* 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);
+                                       msg_and_die_if_script("%s: substring expression < 0", var);
                        }
                        if (len <= 0 || !val || beg >= strlen(val)) {
  arith_err:
@@ -5783,7 +5888,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
                        }
                        debug_printf_varexp("val:'%s'\n", val);
 #else /* not (HUSH_SUBSTR_EXPANSION && FEATURE_SH_MATH) */
-                       die_if_script("malformed ${%s:...}", var);
+                       msg_and_die_if_script("malformed ${%s:...}", var);
                        val = NULL;
 #endif
                } else { /* one of "-=+?" */
@@ -5820,7 +5925,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
                                        exp_word = to_be_freed;
                                if (exp_op == '?') {
                                        /* mimic bash message */
-                                       die_if_script("%s: %s",
+                                       msg_and_die_if_script("%s: %s",
                                                var,
                                                exp_word[0]
                                                ? exp_word
@@ -5837,7 +5942,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
                                        /* ${var=[word]} or ${var:=[word]} */
                                        if (isdigit(var[0]) || var[0] == '#') {
                                                /* mimic bash message */
-                                               die_if_script("$%s: cannot assign in this way", var);
+                                               msg_and_die_if_script("$%s: cannot assign in this way", var);
                                                val = NULL;
                                        } else {
                                                char *new_var = xasprintf("%s=%s", var, val);
@@ -5965,6 +6070,11 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
                        arg++;
                        cant_be_null = 0x80;
                        break;
+               case SPECIAL_VAR_QUOTED_SVS:
+                       /* <SPECIAL_VAR_SYMBOL><SPECIAL_VAR_QUOTED_SVS><SPECIAL_VAR_SYMBOL> */
+                       arg++;
+                       val = SPECIAL_VAR_SYMBOL_STR;
+                       break;
 #if ENABLE_HUSH_TICK
                case '`': /* <SPECIAL_VAR_SYMBOL>`cmd<SPECIAL_VAR_SYMBOL> */
                        *p = '\0'; /* replace trailing <SPECIAL_VAR_SYMBOL> */
@@ -6123,7 +6233,7 @@ static char *expand_string_to_string(const char *str, int do_unbackslash)
        return (char*)list;
 }
 
-/* Used for "eval" builtin and case string */
+#if ENABLE_HUSH_CASE
 static char* expand_strvec_to_string(char **argv)
 {
        char **list;
@@ -6145,6 +6255,7 @@ static char* expand_strvec_to_string(char **argv)
        debug_printf_expand("strvec_to_string='%s'\n", (char*)list);
        return (char*)list;
 }
+#endif
 
 static char **expand_assignments(char **argv, int count)
 {
@@ -6437,8 +6548,17 @@ static void parse_and_run_string(const char *s)
 static void parse_and_run_file(FILE *f)
 {
        struct in_str input;
+#if BASH_LINENO_VAR
+       unsigned sv;
+
+       sv = G.lineno;
+       G.lineno = 1;
+#endif
        setup_file_in_str(&input, f);
        parse_and_run_stream(&input, ';');
+#if BASH_LINENO_VAR
+       G.lineno = sv;
+#endif
 }
 
 #if ENABLE_HUSH_TICK
@@ -6684,9 +6804,10 @@ static struct squirrel *append_squirrel(struct squirrel *sq, int i, int orig, in
 static struct squirrel *add_squirrel(struct squirrel *sq, int fd, int avoid_fd)
 {
        int moved_to;
-       int i = 0;
+       int i;
 
-       if (sq) while (sq[i].orig_fd >= 0) {
+       i = 0;
+       if (sq) for (; sq[i].orig_fd >= 0; i++) {
                /* 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);
@@ -6700,7 +6821,6 @@ static struct squirrel *add_squirrel(struct squirrel *sq, int fd, int avoid_fd)
                        debug_printf_redir("redirect_fd %d: already moved\n", fd);
                        return sq;
                }
-               i++;
        }
 
        /* If this fd is open, we move and remember it; if it's closed, moved_to = -1 */
@@ -6711,18 +6831,42 @@ static struct squirrel *add_squirrel(struct squirrel *sq, int fd, int avoid_fd)
        return append_squirrel(sq, i, fd, moved_to);
 }
 
+static struct squirrel *add_squirrel_closed(struct squirrel *sq, int fd)
+{
+       int i;
+
+       i = 0;
+       if (sq) for (; sq[i].orig_fd >= 0; i++) {
+               /* If we collide with an already moved fd... */
+               if (fd == sq[i].orig_fd) {
+                       /* Examples:
+                        * "echo 3>FILE 3>&- 3>FILE"
+                        * "echo 3>&- 3>FILE"
+                        * No need for last redirect to insert
+                        * another "need to close 3" indicator.
+                        */
+                       debug_printf_redir("redirect_fd %d: already moved or closed\n", fd);
+                       return sq;
+               }
+       }
+
+       debug_printf_redir("redirect_fd %d: previous fd was closed\n", fd);
+       return append_squirrel(sq, i, fd, -1);
+}
+
 /* 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)
+static int save_fd_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, avoid_fd);
+       if (fd == G.interactive_fd) {
+               /* Testcase: "ls -l /proc/$$/fd 255>&-" should work */
+               G.interactive_fd = xdup_CLOEXEC_and_close(G.interactive_fd, 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" */
        }
@@ -6748,10 +6892,9 @@ static int save_fds_on_redirect(int fd, int avoid_fd, struct squirrel **sqp)
 
 static void restore_redirects(struct squirrel *sq)
 {
-
        if (sq) {
-               int i = 0;
-               while (sq[i].orig_fd >= 0) {
+               int i;
+               for (i = 0; sq[i].orig_fd >= 0; i++) {
                        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);
@@ -6761,7 +6904,6 @@ static void restore_redirects(struct squirrel *sq)
                                debug_printf_redir("restoring redirected fd %d: closing it\n", sq[i].orig_fd);
                                close(sq[i].orig_fd);
                        }
-                       i++;
                }
                free(sq);
        }
@@ -6771,17 +6913,47 @@ static void restore_redirects(struct squirrel *sq)
        restore_redirected_FILEs();
 }
 
+#if ENABLE_FEATURE_SH_STANDALONE && BB_MMU
+static void close_saved_fds_and_FILE_fds(void)
+{
+       if (G_interactive_fd)
+               close(G_interactive_fd);
+       close_all_FILE_list();
+}
+#endif
+
+static int internally_opened_fd(int fd, struct squirrel *sq)
+{
+       int i;
+
+#if ENABLE_HUSH_INTERACTIVE
+       if (fd == G.interactive_fd)
+               return 1;
+#endif
+       /* If this one of script's fds? */
+       if (fd_in_FILEs(fd))
+               return 1;
+
+       if (sq) for (i = 0; sq[i].orig_fd >= 0; i++) {
+               if (fd == sq[i].moved_to)
+                       return 1;
+       }
+       return 0;
+}
+
 /* squirrel != NULL means we squirrel away copies of stdin, stdout,
  * and stderr if they are redirected. */
 static int setup_redirects(struct command *prog, struct squirrel **sqp)
 {
-       int openfd, mode;
        struct redir_struct *redir;
 
        for (redir = prog->redirects; redir; redir = redir->next) {
+               int newfd;
+               int closed;
+
                if (redir->rd_type == REDIRECT_HEREDOC2) {
                        /* "rd_fd<<HERE" case */
-                       save_fds_on_redirect(redir->rd_fd, /*avoid:*/ 0, sqp);
+                       save_fd_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",
@@ -6793,20 +6965,22 @@ static int setup_redirects(struct command *prog, struct squirrel **sqp)
                if (redir->rd_dup == REDIRFD_TO_FILE) {
                        /* "rd_fd<*>file" case (<*> is <,>,>>,<>) */
                        char *p;
+                       int mode;
+
                        if (redir->rd_filename == NULL) {
                                /*
                                 * Examples:
                                 * "cmd >" (no filename)
                                 * "cmd > <file" (2nd redirect starts too early)
                                 */
-                               die_if_script("syntax error: %s", "invalid redirect");
+                               syntax_error("invalid redirect");
                                continue;
                        }
                        mode = redir_table[redir->rd_type].mode;
                        p = expand_string_to_string(redir->rd_filename, /*unbackslash:*/ 1);
-                       openfd = open_or_warn(p, mode);
+                       newfd = open_or_warn(p, mode);
                        free(p);
-                       if (openfd < 0) {
+                       if (newfd < 0) {
                                /* Error message from open_or_warn can be lost
                                 * if stderr has been redirected, but bash
                                 * and ash both lose it as well
@@ -6814,40 +6988,52 @@ static int setup_redirects(struct command *prog, struct squirrel **sqp)
                                 */
                                return 1;
                        }
-                       if (openfd == redir->rd_fd && sqp) {
+                       if (newfd == redir->rd_fd && sqp) {
                                /* open() gave us precisely the fd we wanted.
                                 * This means that this fd was not busy
                                 * (not opened to anywhere).
                                 * Remember to close it on restore:
                                 */
-                               struct squirrel *sq = *sqp;
-                               int i = 0;
-                               if (sq) while (sq[i].orig_fd >= 0)
-                                       i++;
-                               *sqp = append_squirrel(sq, i, openfd, -1); /* -1 = "it was closed" */
-                               debug_printf_redir("redir to previously closed fd %d\n", openfd);
+                               *sqp = add_squirrel_closed(*sqp, newfd);
+                               debug_printf_redir("redir to previously closed fd %d\n", newfd);
                        }
                } else {
-                       /* "rd_fd<*>rd_dup" or "rd_fd<*>-" cases */
-                       openfd = redir->rd_dup;
-               }
-
-               if (openfd != redir->rd_fd) {
-                       int closed = save_fds_on_redirect(redir->rd_fd, /*avoid:*/ openfd, sqp);
-                       if (openfd == REDIRFD_CLOSE) {
-                               /* "rd_fd >&-" means "close me" */
-                               if (!closed) {
-                                       /* ^^^ optimization: saving may already
-                                        * have closed it. If not... */
-                                       close(redir->rd_fd);
-                               }
-                       } else {
-                               xdup2(openfd, redir->rd_fd);
-                               if (redir->rd_dup == REDIRFD_TO_FILE)
-                                       /* "rd_fd > FILE" */
-                                       close(openfd);
-                               /* else: "rd_fd > rd_dup" */
+                       /* "rd_fd>&rd_dup" or "rd_fd>&-" case */
+                       newfd = redir->rd_dup;
+               }
+
+               if (newfd == redir->rd_fd)
+                       continue;
+
+               /* if "N>FILE": move newfd to redir->rd_fd */
+               /* if "N>&M": dup newfd to redir->rd_fd */
+               /* if "N>&-": close redir->rd_fd (newfd is REDIRFD_CLOSE) */
+
+               closed = save_fd_on_redirect(redir->rd_fd, /*avoid:*/ newfd, sqp);
+               if (newfd == REDIRFD_CLOSE) {
+                       /* "N>&-" means "close me" */
+                       if (!closed) {
+                               /* ^^^ optimization: saving may already
+                                * have closed it. If not... */
+                               close(redir->rd_fd);
+                       }
+                       /* Sometimes we do another close on restore, getting EBADF.
+                        * Consider "echo 3>FILE 3>&-"
+                        * first redirect remembers "need to close 3",
+                        * and second redirect closes 3! Restore code then closes 3 again.
+                        */
+               } else {
+                       /* if newfd is a script fd or saved fd, simulate EBADF */
+                       if (internally_opened_fd(newfd, sqp ? *sqp : NULL)) {
+                               //errno = EBADF;
+                               //bb_perror_msg_and_die("can't duplicate file descriptor");
+                               newfd = -1; /* same effect as code above */
                        }
+                       xdup2(newfd, redir->rd_fd);
+                       if (redir->rd_dup == REDIRFD_TO_FILE)
+                               /* "rd_fd > FILE" */
+                               close(newfd);
+                       /* else: "rd_fd > rd_dup" */
                }
        }
        return 0;
@@ -7014,11 +7200,34 @@ static void exec_function(char ***to_free,
        argv[0] = G.global_argv[0];
        G.global_argv = argv;
        G.global_argc = n = 1 + string_array_len(argv + 1);
+
+// Example when we are here: "cmd | func"
+// func will run with saved-redirect fds open.
+// $ f() { echo /proc/self/fd/*; }
+// $ true | f
+// /proc/self/fd/0 /proc/self/fd/1 /proc/self/fd/2 /proc/self/fd/255 /proc/self/fd/3
+// stdio^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ G_interactive_fd^ DIR fd for glob
+// Same in script:
+// $ . ./SCRIPT
+// /proc/self/fd/0 /proc/self/fd/1 /proc/self/fd/2 /proc/self/fd/255 /proc/self/fd/3 /proc/self/fd/4
+// stdio^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ G_interactive_fd^ opened ./SCRIPT DIR fd for glob
+// They are CLOEXEC so external programs won't see them, but
+// for "more correctness" we might want to close those extra fds here:
+//?    close_saved_fds_and_FILE_fds();
+
+       /* "we are in function, ok to use return" */
+       G_flag_return_in_progress = -1;
+       IF_HUSH_LOCAL(G.func_nest_level++;)
+
        /* On MMU, funcp->body is always non-NULL */
        n = run_list(funcp->body);
        fflush_all();
        _exit(n);
 # else
+//?    close_saved_fds_and_FILE_fds();
+
+//TODO: check whether "true | func_with_return" works
+
        re_execute_shell(to_free,
                        funcp->body_as_string,
                        G.global_argv[0],
@@ -7038,9 +7247,7 @@ static int run_function(const struct function *funcp, char **argv)
        /* "we are in function, ok to use return" */
        sv_flg = G_flag_return_in_progress;
        G_flag_return_in_progress = -1;
-# if ENABLE_HUSH_LOCAL
-       G.func_nest_level++;
-# endif
+       IF_HUSH_LOCAL(G.func_nest_level++;)
 
        /* On MMU, funcp->body is always non-NULL */
 # if !BB_MMU
@@ -7104,6 +7311,7 @@ static void exec_builtin(char ***to_free,
 #if BB_MMU
        int rcode;
        fflush_all();
+//?    close_saved_fds_and_FILE_fds();
        rcode = x->b_function(argv);
        fflush_all();
        _exit(rcode);
@@ -7166,6 +7374,32 @@ static void dump_cmd_in_x_mode(char **argv)
 # define dump_cmd_in_x_mode(argv) ((void)0)
 #endif
 
+#if ENABLE_HUSH_COMMAND
+static void if_command_vV_print_and_exit(char opt_vV, char *cmd, const char *explanation)
+{
+       char *to_free;
+
+       if (!opt_vV)
+               return;
+
+       to_free = NULL;
+       if (!explanation) {
+               char *path = getenv("PATH");
+               explanation = to_free = find_executable(cmd, &path); /* path == NULL is ok */
+               if (!explanation)
+                       _exit(1); /* PROG was not found */
+               if (opt_vV != 'V')
+                       cmd = to_free; /* -v PROG prints "/path/to/PROG" */
+       }
+       printf((opt_vV == 'V') ? "%s is %s\n" : "%s\n", cmd, explanation);
+       free(to_free);
+       fflush_all();
+       _exit(0);
+}
+#else
+# define if_command_vV_print_and_exit(a,b,c) ((void)0)
+#endif
+
 #if BB_MMU
 #define pseudo_exec_argv(nommu_save, argv, assignment_cnt, argv_expanded) \
        pseudo_exec_argv(argv, assignment_cnt, argv_expanded)
@@ -7186,7 +7420,11 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
                char **argv, int assignment_cnt,
                char **argv_expanded)
 {
+       const struct built_in_command *x;
        char **new_env;
+#if ENABLE_HUSH_COMMAND
+       char opt_vV = 0;
+#endif
 
        new_env = expand_assignments(argv, assignment_cnt);
        dump_cmd_in_x_mode(new_env);
@@ -7225,24 +7463,8 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
                goto skip;
 #endif
 
-       /* Check if the command matches any of the builtins.
-        * Depending on context, this might be redundant.  But it's
-        * easier to waste a few CPU cycles than it is to figure out
-        * if this is one of those cases.
-        */
-       {
-               /* On NOMMU, it is more expensive to re-execute shell
-                * just in order to run echo or test builtin.
-                * It's better to skip it here and run corresponding
-                * non-builtin later. */
-               const struct built_in_command *x;
-               x = BB_MMU ? find_builtin(argv[0]) : find_builtin1(argv[0]);
-               if (x) {
-                       exec_builtin(&nommu_save->argv_from_re_execing, x, argv);
-               }
-       }
 #if ENABLE_HUSH_FUNCTIONS
-       /* Check if the command matches any functions */
+       /* Check if the command matches any functions (this goes before bltins) */
        {
                const struct function *funcp = find_function(argv[0]);
                if (funcp) {
@@ -7251,17 +7473,78 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
        }
 #endif
 
+#if ENABLE_HUSH_COMMAND
+       /* "command BAR": run BAR without looking it up among functions
+        * "command -v BAR": print "BAR" or "/path/to/BAR"; or exit 1
+        * "command -V BAR": print "BAR is {a function,a shell builtin,/path/to/BAR}"
+        */
+       while (strcmp(argv[0], "command") == 0 && argv[1]) {
+               char *p;
+
+               argv++;
+               p = *argv;
+               if (p[0] != '-' || !p[1])
+                       continue; /* bash allows "command command command [-OPT] BAR" */
+
+               for (;;) {
+                       p++;
+                       switch (*p) {
+                       case '\0':
+                               argv++;
+                               p = *argv;
+                               if (p[0] != '-' || !p[1])
+                                       goto after_opts;
+                               continue; /* next arg is also -opts, process it too */
+                       case 'v':
+                       case 'V':
+                               opt_vV = *p;
+                               continue;
+                       default:
+                               bb_error_msg_and_die("%s: %s: invalid option", "command", argv[0]);
+                       }
+               }
+       }
+ after_opts:
+# if ENABLE_HUSH_FUNCTIONS
+       if (opt_vV && find_function(argv[0]))
+               if_command_vV_print_and_exit(opt_vV, argv[0], "a function");
+# endif
+#endif
+
+       /* Check if the command matches any of the builtins.
+        * Depending on context, this might be redundant.  But it's
+        * easier to waste a few CPU cycles than it is to figure out
+        * if this is one of those cases.
+        */
+       /* Why "BB_MMU ? :" difference in logic? -
+        * On NOMMU, it is more expensive to re-execute shell
+        * just in order to run echo or test builtin.
+        * It's better to skip it here and run corresponding
+        * non-builtin later. */
+       x = BB_MMU ? find_builtin(argv[0]) : find_builtin1(argv[0]);
+       if (x) {
+               if_command_vV_print_and_exit(opt_vV, argv[0], "a shell builtin");
+               exec_builtin(&nommu_save->argv_from_re_execing, x, argv);
+       }
+
 #if ENABLE_FEATURE_SH_STANDALONE
        /* Check if the command matches any busybox applets */
        {
                int a = find_applet_by_name(argv[0]);
                if (a >= 0) {
+                       if_command_vV_print_and_exit(opt_vV, argv[0], "an applet");
 # if BB_MMU /* see above why on NOMMU it is not allowed */
                        if (APPLET_IS_NOEXEC(a)) {
-                               /* Do not leak open fds from opened script files etc */
-                               close_all_FILE_list();
+                               /* Do not leak open fds from opened script files etc.
+                                * Testcase: interactive "ls -l /proc/self/fd"
+                                * should not show tty fd open.
+                                */
+                               close_saved_fds_and_FILE_fds();
+//FIXME: should also close saved redir fds
+                               /* Without this, "rm -i FILE" can't be ^C'ed: */
+                               switch_off_special_sigs(G.special_sig_mask);
                                debug_printf_exec("running applet '%s'\n", argv[0]);
-                               run_applet_no_and_exit(a, argv[0], argv);
+                               run_noexec_applet_and_exit(a, argv[0], argv);
                        }
 # endif
                        /* Re-exec ourselves */
@@ -7279,6 +7562,7 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
 #if ENABLE_FEATURE_SH_STANDALONE || BB_MMU
  skip:
 #endif
+       if_command_vV_print_and_exit(opt_vV, argv[0], NULL);
        execvp_or_die(argv);
 }
 
@@ -7821,6 +8105,11 @@ static NOINLINE int run_pipe(struct pipe *pi)
                char **new_env = NULL;
                struct variable *old_vars = NULL;
 
+#if BASH_LINENO_VAR
+               if (G.lineno_var)
+                       strcpy(G.lineno_var + sizeof("LINENO=")-1, utoa(command->lineno));
+#endif
+
                if (argv[command->assignment_cnt] == NULL) {
                        /* Assignments, but no command */
                        /* Ensure redirects take effect (that is, create files).
@@ -7896,12 +8185,13 @@ static NOINLINE int run_pipe(struct pipe *pi)
                        return G.last_exitcode;
                }
 
-               x = find_builtin(argv_expanded[0]);
 #if ENABLE_HUSH_FUNCTIONS
-               funcp = NULL;
-               if (!x)
-                       funcp = find_function(argv_expanded[0]);
+               /* Check if argv[0] matches any functions (this goes before bltins) */
+               funcp = find_function(argv_expanded[0]);
 #endif
+               x = NULL;
+               if (!funcp)
+                       x = find_builtin(argv_expanded[0]);
                if (x || funcp) {
                        if (!funcp) {
                                if (x->b_function == builtin_exec && argv_expanded[1] == NULL) {
@@ -7941,6 +8231,24 @@ static NOINLINE int run_pipe(struct pipe *pi)
                        add_vars(old_vars);
 /* clean_up_and_ret0: */
                        restore_redirects(squirrel);
+                       /*
+                        * Try "usleep 99999999" + ^C + "echo $?"
+                        * with FEATURE_SH_NOFORK=y.
+                        */
+                       if (!funcp) {
+                               /* It was builtin or nofork.
+                                * if this would be a real fork/execed program,
+                                * it should have died if a fatal sig was received.
+                                * But OTOH, there was no separate process,
+                                * the sig was sent to _shell_, not to non-existing
+                                * child.
+                                * Let's just handle ^C only, this one is obvious:
+                                * we aren't ok with exitcode 0 when ^C was pressed
+                                * during builtin/nofork.
+                                */
+                               if (sigismember(&G.pending_set, SIGINT))
+                                       rcode = 128 + SIGINT;
+                       }
  clean_up_and_ret1:
                        free(argv_expanded);
                        IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;)
@@ -7949,13 +8257,21 @@ static NOINLINE int run_pipe(struct pipe *pi)
                        return rcode;
                }
 
-               if (ENABLE_FEATURE_SH_NOFORK) {
+               if (ENABLE_FEATURE_SH_NOFORK && NUM_APPLETS > 1) {
                        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);
                                if (rcode == 0) {
                                        debug_printf_exec(": run_nofork_applet '%s' '%s'...\n",
                                                argv_expanded[0], argv_expanded[1]);
+                                       /*
+                                        * Note: signals (^C) can't interrupt here.
+                                        * We remember them and they will be acted upon
+                                        * after applet returns.
+                                        * This makes applets which can run for a long time
+                                        * and/or wait for user input ineligible for NOFORK:
+                                        * for example, "yes" or "rm" (rm -i waits for input).
+                                        */
                                        rcode = run_nofork_applet(n, argv_expanded);
                                }
                                goto clean_up_and_ret;
@@ -7998,6 +8314,11 @@ static NOINLINE int run_pipe(struct pipe *pi)
                if (cmd_no < pi->num_cmds)
                        xpiped_pair(pipefds);
 
+#if BASH_LINENO_VAR
+               if (G.lineno_var)
+                       strcpy(G.lineno_var + sizeof("LINENO=")-1, utoa(command->lineno));
+#endif
+
                command->pid = BB_MMU ? fork() : vfork();
                if (!command->pid) { /* child */
 #if ENABLE_HUSH_JOB
@@ -8189,7 +8510,10 @@ static int run_list(struct pipe *pi)
                                rword, cond_code, last_rword);
 
                sv_errexit_depth = G.errexit_depth;
-               if (IF_HAS_KEYWORDS(rword == RES_IF || rword == RES_ELIF ||)
+               if (
+#if ENABLE_HUSH_IF
+                   rword == RES_IF || rword == RES_ELIF ||
+#endif
                    pi->followup != PIPE_SEQ
                ) {
                        G.errexit_depth++;
@@ -8387,7 +8711,7 @@ static int run_list(struct pipe *pi)
                        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 */
+/* Check pi->pi_inverted? "! sleep 1 & echo $?": bash says 1. dash and ash say 0 */
                        rcode = EXIT_SUCCESS;
                        goto check_traps;
                } else {
@@ -8496,6 +8820,10 @@ static void install_sighandlers(unsigned mask)
                 */
                if (sig == SIGCHLD)
                        continue;
+               /* bash re-enables SIGHUP which is SIG_IGNed on entry.
+                * Try: "trap '' HUP; bash; echo RET" and type "kill -HUP $$"
+                */
+               //if (sig == SIGHUP) continue; - TODO?
                if (old_handler == SIG_IGN) {
                        /* oops... restore back to IGN, and record this fact */
                        install_sighandler(sig, old_handler);
@@ -8626,17 +8954,19 @@ int hush_main(int argc, char **argv)
 #if !BB_MMU
        G.argv0_for_re_execing = argv[0];
 #endif
+
        /* Deal with HUSH_VERSION */
+       debug_printf_env("unsetenv '%s'\n", "HUSH_VERSION");
+       unsetenv("HUSH_VERSION"); /* in case it exists in initial env */
        shell_ver = xzalloc(sizeof(*shell_ver));
        shell_ver->flg_export = 1;
        shell_ver->flg_read_only = 1;
        /* Code which handles ${var<op>...} needs writable values for all variables,
         * therefore we xstrdup: */
        shell_ver->varstr = xstrdup(hush_version_str);
+
        /* Create shell local variables from the values
         * currently living in the environment */
-       debug_printf_env("unsetenv '%s'\n", "HUSH_VERSION");
-       unsetenv("HUSH_VERSION"); /* in case it exists in initial env */
        G.top_var = shell_ver;
        cur_var = G.top_var;
        e = environ;
@@ -8702,6 +9032,14 @@ int hush_main(int argc, char **argv)
         */
 #endif
 
+#if BASH_LINENO_VAR
+       if (BASH_LINENO_VAR) {
+               char *p = xasprintf("LINENO=%*s", (int)(sizeof(int)*3), "");
+               set_local_var(p, /*flags*/ 0);
+               G.lineno_var = p; /* can't assign before set_local_var("LINENO=...") */
+       }
+#endif
+
 #if ENABLE_FEATURE_EDITING
        G.line_input_state = new_line_input_t(FOR_SHELL);
 #endif
@@ -9144,13 +9482,34 @@ static int FAST_FUNC builtin_eval(char **argv)
        int rcode = EXIT_SUCCESS;
 
        argv = skip_dash_dash(argv);
-       if (*argv) {
-               char *str = expand_strvec_to_string(argv);
+       if (argv[0]) {
+               char *str = NULL;
+
+               if (argv[1]) {
+                       /* "The eval utility shall construct a command by
+                        * concatenating arguments together, separating
+                        * each with a <space> character."
+                        */
+                       char *p;
+                       unsigned len = 0;
+                       char **pp = argv;
+                       do
+                               len += strlen(*pp) + 1;
+                       while (*++pp);
+                       str = p = xmalloc(len);
+                       pp = argv;
+                       do {
+                               p = stpcpy(p, *pp);
+                               *p++ = ' ';
+                       } while (*++pp);
+                       p[-1] = '\0';
+               }
+
                /* bash:
                 * eval "echo Hi; done" ("done" is syntax error):
                 * "echo Hi" will not execute too.
                 */
-               parse_and_run_string(str);
+               parse_and_run_string(str ? str : argv[0]);
                free(str);
                rcode = G.last_exitcode;
        }
@@ -9168,6 +9527,14 @@ static int FAST_FUNC builtin_exec(char **argv)
        if (G_saved_tty_pgrp && getpid() == G.root_pid)
                tcsetpgrp(G_interactive_fd, G_saved_tty_pgrp);
 
+       /* Saved-redirect fds, script fds and G_interactive_fd are still
+        * open here. However, they are all CLOEXEC, and execv below
+        * closes them. Try interactive "exec ls -l /proc/self/fd",
+        * it should show no extra open fds in the "ls" process.
+        * If we'd try to run builtins/NOEXECs, this would need improving.
+        */
+       //close_saved_fds_and_FILE_fds();
+
        /* TODO: if exec fails, bash does NOT exit! We do.
         * We'll need to undo trap cleanup (it's inside execvp_or_die)
         * and tcsetpgrp, and this is inherently racy.
@@ -9269,13 +9636,20 @@ static int FAST_FUNC builtin_read(char **argv)
        char *opt_p = NULL;
        char *opt_t = NULL;
        char *opt_u = NULL;
+       char *opt_d = NULL; /* optimized out if !BASH */
        const char *ifs;
        int read_flags;
 
        /* "!": do not abort on errors.
         * Option string must start with "sr" to match BUILTIN_READ_xxx
         */
-       read_flags = getopt32(argv, "!srn:p:t:u:", &opt_n, &opt_p, &opt_t, &opt_u);
+       read_flags = getopt32(argv,
+#if BASH_READ_D
+               "!srn:p:t:u:d:", &opt_n, &opt_p, &opt_t, &opt_u, &opt_d
+#else
+               "!srn:p:t:u:", &opt_n, &opt_p, &opt_t, &opt_u
+#endif
+       );
        if (read_flags == (uint32_t)-1)
                return EXIT_FAILURE;
        argv += optind;
@@ -9289,7 +9663,8 @@ static int FAST_FUNC builtin_read(char **argv)
                opt_n,
                opt_p,
                opt_t,
-               opt_u
+               opt_u,
+               opt_d
        );
 
        if ((uintptr_t)r == 1 && errno == EINTR) {
@@ -9637,7 +10012,7 @@ static int FAST_FUNC builtin_set(char **argv)
 
        /* Nothing known, so abort */
  error:
-       bb_error_msg("set: %s: invalid option", arg);
+       bb_error_msg("%s: %s: invalid option", "set", arg);
        return EXIT_FAILURE;
 }
 #endif
@@ -9674,6 +10049,131 @@ static int FAST_FUNC builtin_shift(char **argv)
        return EXIT_FAILURE;
 }
 
+#if ENABLE_HUSH_GETOPTS
+static int FAST_FUNC builtin_getopts(char **argv)
+{
+/* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/getopts.html
+
+TODO:
+If a required argument is not found, and getopts is not silent,
+a question mark (?) is placed in VAR, OPTARG is unset, and a
+diagnostic message is printed.  If getopts is silent, then a
+colon (:) is placed in VAR and OPTARG is set to the option
+character found.
+
+Test that VAR is a valid variable name?
+
+"Whenever the shell is invoked, OPTIND shall be initialized to 1"
+*/
+       char cbuf[2];
+       const char *cp, *optstring, *var;
+       int c, n, exitcode, my_opterr;
+       unsigned count;
+
+       optstring = *++argv;
+       if (!optstring || !(var = *++argv)) {
+               bb_error_msg("usage: getopts OPTSTRING VAR [ARGS]");
+               return EXIT_FAILURE;
+       }
+
+       if (argv[1])
+               argv[0] = G.global_argv[0]; /* for error messages in getopt() */
+       else
+               argv = G.global_argv;
+       cbuf[1] = '\0';
+
+       my_opterr = 0;
+       if (optstring[0] != ':') {
+               cp = get_local_var_value("OPTERR");
+               /* 0 if "OPTERR=0", 1 otherwise */
+               my_opterr = (!cp || NOT_LONE_CHAR(cp, '0'));
+       }
+
+       /* getopts stops on first non-option. Add "+" to force that */
+       /*if (optstring[0] != '+')*/ {
+               char *s = alloca(strlen(optstring) + 2);
+               sprintf(s, "+%s", optstring);
+               optstring = s;
+       }
+
+       /* Naively, now we should just
+        *      cp = get_local_var_value("OPTIND");
+        *      optind = cp ? atoi(cp) : 0;
+        *      optarg = NULL;
+        *      opterr = my_opterr;
+        *      c = getopt(string_array_len(argv), argv, optstring);
+        * and be done? Not so fast...
+        * Unlike normal getopt() usage in C programs, here
+        * each successive call will (usually) have the same argv[] CONTENTS,
+        * but not the ADDRESSES. Worse yet, it's possible that between
+        * invocations of "getopts", there will be calls to shell builtins
+        * which use getopt() internally. Example:
+        *      while getopts "abc" RES -a -bc -abc de; do
+        *              unset -ff func
+        *      done
+        * This would not work correctly: getopt() call inside "unset"
+        * modifies internal libc state which is tracking position in
+        * multi-option strings ("-abc"). At best, it can skip options
+        * or return the same option infinitely. With glibc implementation
+        * of getopt(), it would use outright invalid pointers and return
+        * garbage even _without_ "unset" mangling internal state.
+        *
+        * We resort to resetting getopt() state and calling it N times,
+        * until we get Nth result (or failure).
+        * (N == G.getopt_count is reset to 0 whenever OPTIND is [un]set).
+        */
+       GETOPT_RESET();
+       count = 0;
+       n = string_array_len(argv);
+       do {
+               optarg = NULL;
+               opterr = (count < G.getopt_count) ? 0 : my_opterr;
+               c = getopt(n, argv, optstring);
+               if (c < 0)
+                       break;
+               count++;
+       } while (count <= G.getopt_count);
+
+       /* Set OPTIND. Prevent resetting of the magic counter! */
+       set_local_var_from_halves("OPTIND", utoa(optind));
+       G.getopt_count = count; /* "next time, give me N+1'th result" */
+       GETOPT_RESET(); /* just in case */
+
+       /* Set OPTARG */
+       /* Always set or unset, never left as-is, even on exit/error:
+        * "If no option was found, or if the option that was found
+        * does not have an option-argument, OPTARG shall be unset."
+        */
+       cp = optarg;
+       if (c == '?') {
+               /* If ":optstring" and unknown option is seen,
+                * it is stored to OPTARG.
+                */
+               if (optstring[1] == ':') {
+                       cbuf[0] = optopt;
+                       cp = cbuf;
+               }
+       }
+       if (cp)
+               set_local_var_from_halves("OPTARG", cp);
+       else
+               unset_local_var("OPTARG");
+
+       /* Convert -1 to "?" */
+       exitcode = EXIT_SUCCESS;
+       if (c < 0) { /* -1: end of options */
+               exitcode = EXIT_FAILURE;
+               c = '?';
+       }
+
+       /* Set VAR */
+       cbuf[0] = c;
+       set_local_var_from_halves(var, cbuf);
+
+       return exitcode;
+}
+#endif
+
 static int FAST_FUNC builtin_source(char **argv)
 {
        char *arg_path, *filename;
@@ -10066,6 +10566,7 @@ static int wait_for_child_or_signal(struct pipe *waitfor_pipe, pid_t waitfor_pid
                /* So, did we get a signal? */
                sig = check_and_run_traps();
                if (sig /*&& sig != SIGCHLD - always true */) {
+                       /* Do this for any (non-ignored) signal, not only for ^C */
                        ret = 128 + sig;
                        break;
                }
@@ -10232,6 +10733,41 @@ static int FAST_FUNC builtin_return(char **argv)
 }
 #endif
 
+#if ENABLE_HUSH_TIMES
+static int FAST_FUNC builtin_times(char **argv UNUSED_PARAM)
+{
+       static const uint8_t times_tbl[] ALIGN1 = {
+               ' ',  offsetof(struct tms, tms_utime),
+               '\n', offsetof(struct tms, tms_stime),
+               ' ',  offsetof(struct tms, tms_cutime),
+               '\n', offsetof(struct tms, tms_cstime),
+               0
+       };
+       const uint8_t *p;
+       unsigned clk_tck;
+       struct tms buf;
+
+       clk_tck = bb_clk_tck();
+
+       times(&buf);
+       p = times_tbl;
+       do {
+               unsigned sec, frac;
+               unsigned long t;
+               t = *(clock_t *)(((char *) &buf) + p[1]);
+               sec = t / clk_tck;
+               frac = t % clk_tck;
+               printf("%um%u.%03us%c",
+                       sec / 60, sec % 60,
+                       (frac * 1000) / clk_tck,
+                       p[0]);
+               p += 2;
+       } while (*p);
+
+       return EXIT_SUCCESS;
+}
+#endif
+
 #if ENABLE_HUSH_MEMLEAK
 static int FAST_FUNC builtin_memleak(char **argv UNUSED_PARAM)
 {