libbb/bb_do_delay.c: shrink
[oweals/busybox.git] / shell / hush.c
index b515eabd2a485f2c1c6dae41ea296ff456243624..1187cbe8fb60b7e5a9acd6af06a68c091c94e038 100644 (file)
 #endif
 #include "math.h"
 #include "match.h"
+#if ENABLE_HUSH_RANDOM_SUPPORT
+# include "random.h"
+#else
+# define CLEAR_RANDOM_T(rnd) ((void)0)
+#endif
 #ifndef PIPE_BUF
 # define PIPE_BUF 4096  /* amount of buffering in a pipe */
 #endif
@@ -449,9 +454,9 @@ struct function {
        char *name;
        struct command *parent_cmd;
        struct pipe *body;
-#if !BB_MMU
+# if !BB_MMU
        char *body_as_string;
-#endif
+# endif
 };
 #endif
 
@@ -484,7 +489,11 @@ struct globals {
        line_input_t *line_input_state;
 #endif
        pid_t root_pid;
+       pid_t root_ppid;
        pid_t last_bg_pid;
+#if ENABLE_HUSH_RANDOM_SUPPORT
+       random_t random_gen;
+#endif
 #if ENABLE_HUSH_JOB
        int run_list_level;
        int last_jobid;
@@ -547,7 +556,7 @@ struct globals {
        unsigned long memleak_value;
        int debug_indent;
 #endif
-       char user_input_buf[ENABLE_FEATURE_EDITING ? BUFSIZ : 2];
+       char user_input_buf[ENABLE_FEATURE_EDITING ? CONFIG_FEATURE_EDITING_MAX_LEN : 2];
 };
 #define G (*ptr_to_globals)
 /* Not #defining name to G.name - this quickly gets unwieldy
@@ -578,6 +587,9 @@ static int builtin_local(char **argv) FAST_FUNC;
 #if HUSH_DEBUG
 static int builtin_memleak(char **argv) FAST_FUNC;
 #endif
+#if ENABLE_PRINTF
+static int builtin_printf(char **argv) FAST_FUNC;
+#endif
 static int builtin_pwd(char **argv) FAST_FUNC;
 static int builtin_read(char **argv) FAST_FUNC;
 static int builtin_set(char **argv) FAST_FUNC;
@@ -665,6 +677,9 @@ static const struct built_in_command bltins1[] = {
 static const struct built_in_command bltins2[] = {
        BLTIN("["        , builtin_test    , NULL),
        BLTIN("echo"     , builtin_echo    , NULL),
+#if ENABLE_PRINTF
+       BLTIN("printf"   , builtin_printf  , NULL),
+#endif
        BLTIN("pwd"      , builtin_pwd     , NULL),
        BLTIN("test"     , builtin_test    , NULL),
 };
@@ -1307,6 +1322,14 @@ static const char *get_local_var_value(const char *name)
        struct variable **pp = get_ptr_to_local_var(name);
        if (pp)
                return strchr((*pp)->varstr, '=') + 1;
+       if (strcmp(name, "PPID") == 0)
+               return utoa(G.root_ppid);
+       // bash compat: UID? EUID?
+#if ENABLE_HUSH_RANDOM_SUPPORT
+       if (strcmp(name, "RANDOM") == 0) {
+               return utoa(next_random(&G.random_gen));
+       }
+#endif
        return NULL;
 }
 
@@ -1643,7 +1666,7 @@ static void get_user_input(struct in_str *i)
                G.flag_SIGINT = 0;
                /* buglet: SIGINT will not make new prompt to appear _at once_,
                 * only after <Enter>. (^C will work) */
-               r = read_line_input(prompt_str, G.user_input_buf, BUFSIZ-1, G.line_input_state);
+               r = read_line_input(prompt_str, G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1, G.line_input_state);
                /* catch *SIGINT* etc (^C is handled by read_line_input) */
                check_and_run_traps(0);
        } while (r == 0 || G.flag_SIGINT); /* repeat if ^C or SIGINT */
@@ -2151,7 +2174,7 @@ static char *expand_pseudo_dquoted(const char *str)
  * to be filled). This routine is extremely tricky: has to deal with
  * variables/parameters with whitespace, $* and $@, and constructs like
  * 'echo -$*-'. If you play here, you must run testsuite afterwards! */
-static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
+static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
 {
        /* or_mask is either 0 (normal case) or 0x80 -
         * expansion of right-hand side of assignment == 1-element expand.
@@ -2162,7 +2185,7 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
 
        ored_ch = 0;
 
-       debug_printf_expand("expand_vars_to_list: arg '%s'\n", arg);
+       debug_printf_expand("expand_vars_to_list: arg:'%s' or_mask:%x\n", arg, or_mask);
        debug_print_list("expand_vars_to_list", output, n);
        n = o_save_ptr(output, n);
        debug_print_list("expand_vars_to_list[0]", output, n);
@@ -2666,9 +2689,9 @@ static void re_execute_shell(char ***to_free, const char *s,
        char param_buf[sizeof("-$%x:%x:%x:%x:%x") + sizeof(unsigned) * 2];
        char *heredoc_argv[4];
        struct variable *cur;
-#if ENABLE_HUSH_FUNCTIONS
+# if ENABLE_HUSH_FUNCTIONS
        struct function *funcp;
-#endif
+# endif
        char **argv, **pp;
        unsigned cnt;
 
@@ -2687,8 +2710,9 @@ static void re_execute_shell(char ***to_free, const char *s,
        if (pp) while (*pp++)
                cnt++;
 
-       sprintf(param_buf, "-$%x:%x:%x:%x" IF_HUSH_LOOPS(":%x")
+       sprintf(param_buf, "-$%x:%x:%x:%x:%x" IF_HUSH_LOOPS(":%x")
                        , (unsigned) G.root_pid
+                       , (unsigned) G.root_ppid
                        , (unsigned) G.last_bg_pid
                        , (unsigned) G.last_exitcode
                        , cnt
@@ -2702,10 +2726,10 @@ static void re_execute_shell(char ***to_free, const char *s,
                if (!cur->flg_export || cur->flg_read_only)
                        cnt += 2;
        }
-#if ENABLE_HUSH_FUNCTIONS
+# if ENABLE_HUSH_FUNCTIONS
        for (funcp = G.top_func; funcp; funcp = funcp->next)
                cnt += 3;
-#endif
+# endif
        pp = g_argv;
        while (*pp++)
                cnt++;
@@ -2723,13 +2747,13 @@ static void re_execute_shell(char ***to_free, const char *s,
                        *pp++ = cur->varstr;
                }
        }
-#if ENABLE_HUSH_FUNCTIONS
+# if ENABLE_HUSH_FUNCTIONS
        for (funcp = G.top_func; funcp; funcp = funcp->next) {
                *pp++ = (char *) "-F";
                *pp++ = funcp->name;
                *pp++ = funcp->body_as_string;
        }
-#endif
+# endif
        /* We can pass activated traps here. Say, -Tnn:trap_string
         *
         * However, POSIX says that subshells reset signals with traps
@@ -3218,9 +3242,9 @@ 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
+# if ENABLE_HUSH_LOCAL
        G.func_nest_level++;
-#endif
+# endif
 
        /* On MMU, funcp->body is always non-NULL */
 # if !BB_MMU
@@ -3234,7 +3258,7 @@ static int run_function(const struct function *funcp, char **argv)
                rc = run_list(funcp->body);
        }
 
-#if ENABLE_HUSH_LOCAL
+# if ENABLE_HUSH_LOCAL
        {
                struct variable *var;
                struct variable **var_pp;
@@ -3257,7 +3281,7 @@ static int run_function(const struct function *funcp, char **argv)
                }
                G.func_nest_level--;
        }
-#endif
+# endif
        G.flag_return_in_progress = sv_flg;
 
        restore_G_args(&sv, argv);
@@ -3267,13 +3291,13 @@ static int run_function(const struct function *funcp, char **argv)
 #endif /* ENABLE_HUSH_FUNCTIONS */
 
 
-# if BB_MMU
+#if BB_MMU
 #define exec_builtin(to_free, x, argv) \
        exec_builtin(x, argv)
-# else
+#else
 #define exec_builtin(to_free, x, argv) \
        exec_builtin(to_free, argv)
-# endif
+#endif
 static void exec_builtin(char ***to_free,
                const struct built_in_command *x,
                char **argv) NORETURN;
@@ -3281,11 +3305,11 @@ static void exec_builtin(char ***to_free,
                const struct built_in_command *x,
                char **argv)
 {
-# if BB_MMU
+#if BB_MMU
        int rcode = x->function(argv);
        fflush(NULL);
        _exit(rcode);
-# else
+#else
        /* On NOMMU, we must never block!
         * Example: { sleep 99 | read line; } & echo Ok
         */
@@ -3294,10 +3318,20 @@ static void exec_builtin(char ***to_free,
                        G.global_argv[0],
                        G.global_argv + 1,
                        argv);
-# endif
+#endif
 }
 
 
+static void execvp_or_die(char **argv) NORETURN;
+static void execvp_or_die(char **argv)
+{
+       debug_printf_exec("execing '%s'\n", argv[0]);
+       sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
+       execvp(argv[0], argv);
+       bb_perror_msg("can't execute '%s'", argv[0]);
+       _exit(127); /* bash compat */
+}
+
 #if BB_MMU
 #define pseudo_exec_argv(nommu_save, argv, assignment_cnt, argv_expanded) \
        pseudo_exec_argv(argv, assignment_cnt, argv_expanded)
@@ -3313,7 +3347,7 @@ static void exec_builtin(char ***to_free,
 static void pseudo_exec_argv(nommu_save_t *nommu_save,
                char **argv, int assignment_cnt,
                char **argv_expanded) NORETURN;
-static void pseudo_exec_argv(nommu_save_t *nommu_save,
+static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
                char **argv, int assignment_cnt,
                char **argv_expanded)
 {
@@ -3397,11 +3431,7 @@ static void pseudo_exec_argv(nommu_save_t *nommu_save,
 #if ENABLE_FEATURE_SH_STANDALONE || BB_MMU
  skip:
 #endif
-       debug_printf_exec("execing '%s'\n", argv[0]);
-       sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
-       execvp(argv[0], argv);
-       bb_perror_msg("can't execute '%s'", argv[0]);
-       _exit(EXIT_FAILURE);
+       execvp_or_die(argv);
 }
 
 /* Called after [v]fork() in run_pipe
@@ -3750,7 +3780,7 @@ static int checkjobs_and_fg_shell(struct pipe* fg_pipe)
  * backgrounded: cmd &     { list } &
  * subshell:     ( list ) [&]
  */
-static int run_pipe(struct pipe *pi)
+static NOINLINE int run_pipe(struct pipe *pi)
 {
        static const char *const null_ptr = NULL;
        int i;
@@ -3871,6 +3901,12 @@ static int run_pipe(struct pipe *pi)
                        argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt);
                }
 
+               /* if someone gives us an empty string: `cmd with empty output` */
+               if (!argv_expanded[0]) {
+                       debug_leave();
+                       return 0;
+               }
+
                x = find_builtin(argv_expanded[0]);
 #if ENABLE_HUSH_FUNCTIONS
                funcp = NULL;
@@ -3982,6 +4018,7 @@ static int run_pipe(struct pipe *pi)
                if (!command->pid) { /* child */
 #if ENABLE_HUSH_JOB
                        disable_restore_tty_pgrp_on_exit();
+                       CLEAR_RANDOM_T(&G.random_gen); /* or else $RANDOM repeats in child */
 
                        /* Every child adds itself to new process group
                         * with pgid == pid_of_first_child_in_pipe */
@@ -4084,30 +4121,30 @@ static void debug_print_tree(struct pipe *pi, int lvl)
        };
        static const char *RES[] = {
                [RES_NONE ] = "NONE" ,
-#if ENABLE_HUSH_IF
+# if ENABLE_HUSH_IF
                [RES_IF   ] = "IF"   ,
                [RES_THEN ] = "THEN" ,
                [RES_ELIF ] = "ELIF" ,
                [RES_ELSE ] = "ELSE" ,
                [RES_FI   ] = "FI"   ,
-#endif
-#if ENABLE_HUSH_LOOPS
+# endif
+# if ENABLE_HUSH_LOOPS
                [RES_FOR  ] = "FOR"  ,
                [RES_WHILE] = "WHILE",
                [RES_UNTIL] = "UNTIL",
                [RES_DO   ] = "DO"   ,
                [RES_DONE ] = "DONE" ,
-#endif
-#if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE
+# endif
+# if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE
                [RES_IN   ] = "IN"   ,
-#endif
-#if ENABLE_HUSH_CASE
+# endif
+# if ENABLE_HUSH_CASE
                [RES_CASE ] = "CASE" ,
                [RES_CASE_IN ] = "CASE_IN" ,
                [RES_MATCH] = "MATCH",
                [RES_CASE_BODY] = "CASE_BODY",
                [RES_ESAC ] = "ESAC" ,
-#endif
+# endif
                [RES_XXXX ] = "XXXX" ,
                [RES_SNTX ] = "SNTX" ,
        };
@@ -4115,9 +4152,9 @@ static void debug_print_tree(struct pipe *pi, int lvl)
                "{}",
                "()",
                "[noglob]",
-#if ENABLE_HUSH_FUNCTIONS
+# if ENABLE_HUSH_FUNCTIONS
                "func()",
-#endif
+# endif
        };
 
        int pin, prn;
@@ -4153,7 +4190,7 @@ static void debug_print_tree(struct pipe *pi, int lvl)
                pin++;
        }
 }
-#endif
+#endif /* debug_print_tree */
 
 /* NB: called by pseudo_exec, and therefore must not modify any
  * global data until exec/_exit (we can be a child after vfork!) */
@@ -4627,25 +4664,25 @@ struct reserved_combo {
 };
 enum {
        FLAG_END   = (1 << RES_NONE ),
-#if ENABLE_HUSH_IF
+# if ENABLE_HUSH_IF
        FLAG_IF    = (1 << RES_IF   ),
        FLAG_THEN  = (1 << RES_THEN ),
        FLAG_ELIF  = (1 << RES_ELIF ),
        FLAG_ELSE  = (1 << RES_ELSE ),
        FLAG_FI    = (1 << RES_FI   ),
-#endif
-#if ENABLE_HUSH_LOOPS
+# endif
+# if ENABLE_HUSH_LOOPS
        FLAG_FOR   = (1 << RES_FOR  ),
        FLAG_WHILE = (1 << RES_WHILE),
        FLAG_UNTIL = (1 << RES_UNTIL),
        FLAG_DO    = (1 << RES_DO   ),
        FLAG_DONE  = (1 << RES_DONE ),
        FLAG_IN    = (1 << RES_IN   ),
-#endif
-#if ENABLE_HUSH_CASE
+# endif
+# if ENABLE_HUSH_CASE
        FLAG_MATCH = (1 << RES_MATCH),
        FLAG_ESAC  = (1 << RES_ESAC ),
-#endif
+# endif
        FLAG_START = (1 << RES_XXXX ),
 };
 
@@ -4657,26 +4694,26 @@ static const struct reserved_combo* match_reserved_word(o_string *word)
         * FLAG_START means the word must start a new compound list.
         */
        static const struct reserved_combo reserved_list[] = {
-#if ENABLE_HUSH_IF
+# if ENABLE_HUSH_IF
                { "!",     RES_NONE,  NOT_ASSIGNMENT , 0 },
                { "if",    RES_IF,    WORD_IS_KEYWORD, FLAG_THEN | FLAG_START },
                { "then",  RES_THEN,  WORD_IS_KEYWORD, FLAG_ELIF | FLAG_ELSE | FLAG_FI },
                { "elif",  RES_ELIF,  WORD_IS_KEYWORD, FLAG_THEN },
                { "else",  RES_ELSE,  WORD_IS_KEYWORD, FLAG_FI   },
                { "fi",    RES_FI,    NOT_ASSIGNMENT , FLAG_END  },
-#endif
-#if ENABLE_HUSH_LOOPS
+# endif
+# if ENABLE_HUSH_LOOPS
                { "for",   RES_FOR,   NOT_ASSIGNMENT , FLAG_IN | FLAG_DO | FLAG_START },
                { "while", RES_WHILE, WORD_IS_KEYWORD, FLAG_DO | FLAG_START },
                { "until", RES_UNTIL, WORD_IS_KEYWORD, FLAG_DO | FLAG_START },
                { "in",    RES_IN,    NOT_ASSIGNMENT , FLAG_DO   },
                { "do",    RES_DO,    WORD_IS_KEYWORD, FLAG_DONE },
                { "done",  RES_DONE,  NOT_ASSIGNMENT , FLAG_END  },
-#endif
-#if ENABLE_HUSH_CASE
+# endif
+# if ENABLE_HUSH_CASE
                { "case",  RES_CASE,  NOT_ASSIGNMENT , FLAG_MATCH | FLAG_START },
                { "esac",  RES_ESAC,  NOT_ASSIGNMENT , FLAG_END  },
-#endif
+# endif
        };
        const struct reserved_combo *r;
 
@@ -4690,11 +4727,11 @@ static const struct reserved_combo* match_reserved_word(o_string *word)
  */
 static int reserved_word(o_string *word, struct parse_context *ctx)
 {
-#if ENABLE_HUSH_CASE
+# if ENABLE_HUSH_CASE
        static const struct reserved_combo reserved_match = {
                "",        RES_MATCH, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_ESAC
        };
-#endif
+# endif
        const struct reserved_combo *r;
 
        if (word->o_quoted)
@@ -4704,12 +4741,12 @@ static int reserved_word(o_string *word, struct parse_context *ctx)
                return 0;
 
        debug_printf("found reserved word %s, res %d\n", r->literal, r->res);
-#if ENABLE_HUSH_CASE
+# if ENABLE_HUSH_CASE
        if (r->res == RES_IN && ctx->ctx_res_w == RES_CASE_IN) {
                /* "case word IN ..." - IN part starts first MATCH part */
                r = &reserved_match;
        } else
-#endif
+# endif
        if (r->flag == 0) { /* '!' */
                if (ctx->ctx_inverted) { /* bash doesn't accept '! ! true' */
                        syntax_error("! ! command");
@@ -4750,19 +4787,19 @@ static int reserved_word(o_string *word, struct parse_context *ctx)
                old = ctx->stack;
                old->command->group = ctx->list_head;
                old->command->cmd_type = CMD_NORMAL;
-#if !BB_MMU
+# if !BB_MMU
                o_addstr(&old->as_string, ctx->as_string.data);
                o_free_unsafe(&ctx->as_string);
                old->command->group_as_string = xstrdup(old->as_string.data);
                debug_printf_parse("pop, remembering as:'%s'\n",
                                old->command->group_as_string);
-#endif
+# endif
                *ctx = *old;   /* physical copy */
                free(old);
        }
        return 1;
 }
-#endif
+#endif /* HAS_KEYWORDS */
 
 /* Word is complete, look at it and update parsing context.
  * Normal return is 0. Syntax errors return 1.
@@ -5169,9 +5206,9 @@ static FILE *generate_stream_from_string(const char *s)
 {
        FILE *pf;
        int pid, channel[2];
-#if !BB_MMU
+# if !BB_MMU
        char **to_free;
-#endif
+# endif
 
        xpipe(channel);
        pid = BB_MMU ? fork() : vfork();
@@ -5189,6 +5226,7 @@ static FILE *generate_stream_from_string(const char *s)
                        + (1 << SIGTTIN)
                        + (1 << SIGTTOU)
                        , SIG_IGN);
+               CLEAR_RANDOM_T(&G.random_gen); /* or else $RANDOM repeats in child */
                close(channel[0]); /* NB: close _first_, then move fd! */
                xmove_fd(channel[1], 1);
                /* Prevent it from trying to handle ctrl-z etc */
@@ -5234,11 +5272,11 @@ static FILE *generate_stream_from_string(const char *s)
                        builtin_trap((char**)argv);
                        exit(0); /* not _exit() - we need to fflush */
                }
-#if BB_MMU
+# if BB_MMU
                reset_traps_to_defaults();
                parse_and_run_string(s);
                _exit(G.last_exitcode);
-#else
+# else
        /* We re-execute after vfork on NOMMU. This makes this script safe:
         * yes "0123456789012345678901234567890" | dd bs=32 count=64k >BIG
         * huge=`cat BIG` # was blocking here forever
@@ -5249,18 +5287,18 @@ static FILE *generate_stream_from_string(const char *s)
                                G.global_argv[0],
                                G.global_argv + 1,
                                NULL);
-#endif
+# endif
        }
 
        /* parent */
-#if ENABLE_HUSH_FAST
+# if ENABLE_HUSH_FAST
        G.count_SIGCHLD++;
 //bb_error_msg("[%d] fork in generate_stream_from_string: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
-#endif
+# endif
        enable_restore_tty_pgrp_on_exit();
-#if !BB_MMU
+# if !BB_MMU
        free(to_free);
-#endif
+# endif
        close(channel[1]);
        pf = fdopen(channel[0], "r");
        return pf;
@@ -5304,7 +5342,7 @@ static int process_command_subs(o_string *dest, const char *s)
        debug_printf("closed FILE from child. return 0\n");
        return 0;
 }
-#endif
+#endif /* ENABLE_HUSH_TICK */
 
 static int parse_group(o_string *dest, struct parse_context *ctx,
        struct in_str *input, int ch)
@@ -5672,7 +5710,7 @@ static int handle_dollar(o_string *as_string,
                o_addchr(dest, SPECIAL_VAR_SYMBOL);
                break;
        }
-#if (ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_TICK)
+#if ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_TICK
        case '(': {
 # if !BB_MMU
                int pos;
@@ -5770,7 +5808,7 @@ static int parse_stream_dquoted(o_string *as_string,
        if (ch != '\n') {
                next = i_peek(input);
        }
-       debug_printf_parse(": ch=%c (%d) escape=%d\n",
+       debug_printf_parse("\" ch=%c (%d) escape=%d\n",
                                        ch, ch, dest->o_escape);
        if (ch == '\\') {
                if (next == EOF) {
@@ -5850,6 +5888,11 @@ static struct pipe *parse_stream(char **pstring,
                        end_trigger ? end_trigger : 'X');
        debug_enter();
 
+       /* If very first arg is "" or '', dest.data may end up NULL.
+        * Preventing this: */
+       o_addchr(&dest, '\0');
+       dest.length = 0;
+
        G.ifs = get_local_var_value("IFS");
        if (G.ifs == NULL)
                G.ifs = " \t\n";
@@ -6132,12 +6175,13 @@ static struct pipe *parse_stream(char **pstring,
                                /* Example: echo Hello \2>file
                                 * we need to know that word 2 is quoted */
                                dest.o_quoted = 1;
-                       } else {
+                       }
 #if !BB_MMU
+                       else {
                                /* It's "\<newline>". Remove trailing '\' from ctx.as_string */
                                ctx.as_string.data[--ctx.as_string.length] = '\0';
-#endif
                        }
+#endif
                        break;
                case '$':
                        if (handle_dollar(&ctx.as_string, &dest, input) != 0) {
@@ -6510,7 +6554,7 @@ int hush_main(int argc, char **argv)
         * MACHTYPE=i386-pc-linux-gnu
         * OSTYPE=linux-gnu
         * HOSTNAME=<xxxxxxxxxx>
-        * PPID=<NNNNN>
+        * PPID=<NNNNN> - we also do it elsewhere
         * EUID=<NNNNN>
         * UID=<NNNNN>
         * GROUPS=()
@@ -6587,8 +6631,10 @@ int hush_main(int argc, char **argv)
                         * Note: this form never happens:
                         * sh ... -c 'builtin' [BARGV...] ""
                         */
-                       if (!G.root_pid)
+                       if (!G.root_pid) {
                                G.root_pid = getpid();
+                               G.root_ppid = getppid();
+                       }
                        G.global_argv = argv + optind;
                        G.global_argc = argc - optind;
                        if (builtin_argc) {
@@ -6630,6 +6676,8 @@ int hush_main(int argc, char **argv)
                case '$':
                        G.root_pid = bb_strtou(optarg, &optarg, 16);
                        optarg++;
+                       G.root_ppid = bb_strtou(optarg, &optarg, 16);
+                       optarg++;
                        G.last_bg_pid = bb_strtou(optarg, &optarg, 16);
                        optarg++;
                        G.last_exitcode = bb_strtou(optarg, &optarg, 16);
@@ -6670,8 +6718,10 @@ int hush_main(int argc, char **argv)
                }
        } /* option parsing loop */
 
-       if (!G.root_pid)
+       if (!G.root_pid) {
                G.root_pid = getpid();
+               G.root_ppid = getppid();
+       }
 
        /* If we are login shell... */
        if (argv[0] && argv[0][0] == '-') {
@@ -6816,10 +6866,13 @@ int hush_main(int argc, char **argv)
         */
 
        if (!ENABLE_FEATURE_SH_EXTRA_QUIET && G_interactive_fd) {
-               printf("\n\n%s hush - the humble shell\n", bb_banner);
-               if (ENABLE_HUSH_HELP)
-                       puts("Enter 'help' for a list of built-in commands.");
-               puts("");
+               /* note: ash and hush share this string */
+               printf("\n\n%s %s\n"
+                       IF_HUSH_HELP("Enter 'help' for a list of built-in commands.\n")
+                       "\n",
+                       bb_banner,
+                       "hush - the humble shell"
+               );
        }
 
        parse_and_run_file(stdin);
@@ -6868,25 +6921,32 @@ static int FAST_FUNC builtin_true(char **argv UNUSED_PARAM)
        return 0;
 }
 
-static int FAST_FUNC builtin_test(char **argv)
+static int run_applet_main(char **argv, int (*applet_main_func)(int argc, char **argv))
 {
        int argc = 0;
        while (*argv) {
                argc++;
                argv++;
        }
-       return test_main(argc, argv - argc);
+       return applet_main_func(argc, argv - argc);
+}
+
+static int FAST_FUNC builtin_test(char **argv)
+{
+       return run_applet_main(argv, test_main);
 }
 
 static int FAST_FUNC builtin_echo(char **argv)
 {
-       int argc = 0;
-       while (*argv) {
-               argc++;
-               argv++;
-       }
-       return echo_main(argc, argv - argc);
+       return run_applet_main(argv, echo_main);
+}
+
+#if ENABLE_PRINTF
+static int FAST_FUNC builtin_printf(char **argv)
+{
+       return run_applet_main(argv, printf_main);
 }
+#endif
 
 static int FAST_FUNC builtin_eval(char **argv)
 {
@@ -6933,14 +6993,17 @@ static int FAST_FUNC builtin_exec(char **argv)
 {
        if (*++argv == NULL)
                return EXIT_SUCCESS; /* bash does this */
-       {
-#if !BB_MMU
-               nommu_save_t dummy;
-#endif
-               /* TODO: if exec fails, bash does NOT exit! We do... */
-               pseudo_exec_argv(&dummy, argv, 0, NULL);
-               /* never returns */
-       }
+
+       /* Careful: we can end up here after [v]fork. Do not restore
+        * tty pgrp then, only top-level shell process does that */
+       if (G_saved_tty_pgrp && getpid() == G.root_pid)
+               tcsetpgrp(G_interactive_fd, G_saved_tty_pgrp);
+
+       /* TODO: if exec fails, bash does NOT exit! We do.
+        * We'll need to undo sigprocmask (it's inside execvp_or_die)
+        * and tcsetpgrp, and this is inherently racy.
+        */
+       execvp_or_die(argv);
 }
 
 static int FAST_FUNC builtin_exit(char **argv)
@@ -7326,10 +7389,10 @@ static int FAST_FUNC builtin_memleak(char **argv UNUSED_PARAM)
        void *p;
        unsigned long l;
 
-#ifdef M_TRIM_THRESHOLD
+# ifdef M_TRIM_THRESHOLD
        /* Optional. Reduces probability of false positives */
        malloc_trim(0);
-#endif
+# endif
        /* Crude attempt to find where "free memory" starts,
         * sans fragmentation. */
        p = malloc(240);