Bump to version 1.22.1
[platform/upstream/busybox.git] / shell / hush.c
index d3e957c..9271934 100644 (file)
  * therefore we don't show them either.
  */
 //usage:#define hush_trivial_usage
-//usage:       "[-nx] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]"
+//usage:       "[-nxl] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]"
 //usage:#define hush_full_usage "\n\n"
 //usage:       "Unix shell interpreter"
 
 # define ENABLE_FEATURE_EDITING 0
 # undef ENABLE_FEATURE_EDITING_FANCY_PROMPT
 # define ENABLE_FEATURE_EDITING_FANCY_PROMPT 0
+# undef ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
+# define ENABLE_FEATURE_EDITING_SAVE_ON_EXIT 0
 #endif
 
 /* Do we support ANY keywords? */
@@ -445,6 +447,15 @@ enum {
 /* Used for initialization: o_string foo = NULL_O_STRING; */
 #define NULL_O_STRING { NULL }
 
+#ifndef debug_printf_parse
+static const char *const assignment_flag[] = {
+       "MAYBE_ASSIGNMENT",
+       "DEFINITELY_ASSIGNMENT",
+       "NOT_ASSIGNMENT",
+       "WORD_IS_KEYWORD",
+};
+#endif
+
 typedef struct in_str {
        const char *p;
        /* eof_flag=1: last char in ->p is really an EOF */
@@ -511,7 +522,6 @@ typedef enum redir_type {
 struct command {
        pid_t pid;                  /* 0 if exited */
        int assignment_cnt;         /* how many argv[i] are assignments? */
-       smallint is_stopped;        /* is the command currently running? */
        smallint cmd_type;          /* CMD_xxx */
 #define CMD_NORMAL   0
 #define CMD_SUBSHELL 1
@@ -764,7 +774,6 @@ struct globals {
        smalluint last_exitcode;
        /* are global_argv and global_argv[1..n] malloced? (note: not [0]) */
        smalluint global_args_malloced;
-       smalluint inherited_set_is_saved;
        /* how many non-NULL argv's we have. NB: $# + 1 */
        int global_argc;
        char **global_argv;
@@ -792,15 +801,27 @@ struct globals {
        unsigned handled_SIGCHLD;
        smallint we_have_children;
 #endif
-       /* which signals have non-DFL handler (even with no traps set)? */
-       unsigned non_DFL_mask;
+       /* Which signals have non-DFL handler (even with no traps set)?
+        * Set at the start to:
+        * (SIGQUIT + maybe SPECIAL_INTERACTIVE_SIGS + maybe SPECIAL_JOBSTOP_SIGS)
+        * SPECIAL_INTERACTIVE_SIGS are cleared after fork.
+        * The rest is cleared right before execv syscalls.
+        * Other than these two times, never modified.
+        */
+       unsigned special_sig_mask;
+#if ENABLE_HUSH_JOB
+       unsigned fatal_sig_mask;
+# define G_fatal_sig_mask G.fatal_sig_mask
+#else
+# define G_fatal_sig_mask 0
+#endif
        char **traps; /* char *traps[NSIG] */
-       sigset_t blocked_set;
-       sigset_t inherited_set;
+       sigset_t pending_set;
 #if HUSH_DEBUG
        unsigned long memleak_value;
        int debug_indent;
 #endif
+       struct sigaction sa;
        char user_input_buf[ENABLE_FEATURE_EDITING ? CONFIG_FEATURE_EDITING_MAX_LEN : 2];
 };
 #define G (*ptr_to_globals)
@@ -809,6 +830,9 @@ struct globals {
  * is global, thus "G." prefix is a useful hint */
 #define INIT_G() do { \
        SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+       /* memset(&G.sa, 0, sizeof(G.sa)); */  \
+       sigfillset(&G.sa.sa_mask); \
+       G.sa.sa_flags = SA_RESTART; \
 } while (0)
 
 
@@ -826,6 +850,9 @@ static int builtin_jobs(char **argv) FAST_FUNC;
 #if ENABLE_HUSH_HELP
 static int builtin_help(char **argv) FAST_FUNC;
 #endif
+#if MAX_HISTORY && ENABLE_FEATURE_EDITING
+static int builtin_history(char **argv) FAST_FUNC;
+#endif
 #if ENABLE_HUSH_LOCAL
 static int builtin_local(char **argv) FAST_FUNC;
 #endif
@@ -895,6 +922,9 @@ static const struct built_in_command bltins1[] = {
 #if ENABLE_HUSH_HELP
        BLTIN("help"     , builtin_help    , NULL),
 #endif
+#if MAX_HISTORY && ENABLE_FEATURE_EDITING
+       BLTIN("history"  , builtin_history , "Show command history"),
+#endif
 #if ENABLE_HUSH_JOB
        BLTIN("jobs"     , builtin_jobs    , "List jobs"),
 #endif
@@ -1280,7 +1310,7 @@ static void restore_G_args(save_arg_t *sv, char **argv)
  * backgrounds (i.e. stops) or kills all members of currently running
  * pipe.
  *
- * Wait builtin in interruptible by signals for which user trap is set
+ * Wait builtin is interruptible by signals for which user trap is set
  * or by SIGINT in interactive shell.
  *
  * Trap handlers will execute even within trap handlers. (right?)
@@ -1319,12 +1349,14 @@ static void restore_G_args(save_arg_t *sv, char **argv)
  *    "echo $$; sleep 5 & wait; ls -l" + "kill -INT <pid>"
  *    Example 3: this does not wait 5 sec, but executes ls:
  *    "sleep 5; ls -l" + press ^C
+ *    Example 4: this does not wait and does not execute ls:
+ *    "sleep 5 & wait; ls -l" + press ^C
  *
  * (What happens to signals which are IGN on shell start?)
  * (What happens with signal mask on shell start?)
  *
- * Implementation in hush
- * ======================
+ * Old implementation
+ * ==================
  * We use in-kernel pending signal mask to determine which signals were sent.
  * We block all signals which we don't want to take action immediately,
  * i.e. we block all signals which need to have special handling as described
@@ -1332,11 +1364,11 @@ static void restore_G_args(save_arg_t *sv, char **argv)
  * After each pipe execution, we extract any pending signals via sigtimedwait()
  * and act on them.
  *
- * unsigned non_DFL_mask: a mask of such "special" signals
+ * unsigned special_sig_mask: a mask of such "special" signals
  * sigset_t blocked_set:  current blocked signal set
  *
  * "trap - SIGxxx":
- *    clear bit in blocked_set unless it is also in non_DFL_mask
+ *    clear bit in blocked_set unless it is also in special_sig_mask
  * "trap 'cmd' SIGxxx":
  *    set bit in blocked_set (even if 'cmd' is '')
  * after [v]fork, if we plan to be a shell:
@@ -1355,6 +1387,49 @@ static void restore_G_args(save_arg_t *sv, char **argv)
  * Standard says "When a subshell is entered, traps that are not being ignored
  * are set to the default actions". bash interprets it so that traps which
  * are set to '' (ignore) are NOT reset to defaults. We do the same.
+ *
+ * Problem: the above approach makes it unwieldy to catch signals while
+ * we are in read builtin, or while we read commands from stdin:
+ * masked signals are not visible!
+ *
+ * New implementation
+ * ==================
+ * We record each signal we are interested in by installing signal handler
+ * for them - a bit like emulating kernel pending signal mask in userspace.
+ * We are interested in: signals which need to have special handling
+ * as described above, and all signals which have traps set.
+ * Signals are recorded in pending_set.
+ * After each pipe execution, we extract any pending signals
+ * and act on them.
+ *
+ * unsigned special_sig_mask: a mask of shell-special signals.
+ * unsigned fatal_sig_mask: a mask of signals on which we restore tty pgrp.
+ * char *traps[sig] if trap for sig is set (even if it's '').
+ * sigset_t pending_set: set of sigs we received.
+ *
+ * "trap - SIGxxx":
+ *    if sig is in special_sig_mask, set handler back to:
+ *        record_pending_signo, or to IGN if it's a tty stop signal
+ *    if sig is in fatal_sig_mask, set handler back to sigexit.
+ *    else: set handler back to SIG_DFL
+ * "trap 'cmd' SIGxxx":
+ *    set handler to record_pending_signo.
+ * "trap '' SIGxxx":
+ *    set handler to SIG_IGN.
+ * after [v]fork, if we plan to be a shell:
+ *    set signals with special interactive handling to SIG_DFL
+ *    (because child shell is not interactive),
+ *    unset all traps except '' (note: regardless of child shell's type - {}, (), etc)
+ * after [v]fork, if we plan to exec:
+ *    POSIX says fork clears pending signal mask in child - no need to clear it.
+ *
+ * To make wait builtin interruptible, we handle SIGCHLD as special signal,
+ * otherwise (if we leave it SIG_DFL) sigsuspend in wait builtin will not wake up on it.
+ *
+ * Note (compat):
+ * Standard says "When a subshell is entered, traps that are not being ignored
+ * are set to the default actions". bash interprets it so that traps which
+ * are set to '' (ignore) are NOT reset to defaults. We do the same.
  */
 enum {
        SPECIAL_INTERACTIVE_SIGS = 0
@@ -1362,21 +1437,43 @@ enum {
                | (1 << SIGINT)
                | (1 << SIGHUP)
                ,
-       SPECIAL_JOB_SIGS = 0
+       SPECIAL_JOBSTOP_SIGS = 0
 #if ENABLE_HUSH_JOB
                | (1 << SIGTTIN)
                | (1 << SIGTTOU)
                | (1 << SIGTSTP)
 #endif
+               ,
 };
 
-#if ENABLE_HUSH_FAST
-static void SIGCHLD_handler(int sig UNUSED_PARAM)
+static void record_pending_signo(int sig)
 {
-       G.count_SIGCHLD++;
+       sigaddset(&G.pending_set, sig);
+#if ENABLE_HUSH_FAST
+       if (sig == SIGCHLD) {
+               G.count_SIGCHLD++;
 //bb_error_msg("[%d] SIGCHLD_handler: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
-}
+       }
 #endif
+}
+
+static sighandler_t install_sighandler(int sig, sighandler_t handler)
+{
+       struct sigaction old_sa;
+
+       /* We could use signal() to install handlers... almost:
+        * except that we need to mask ALL signals while handlers run.
+        * I saw signal nesting in strace, race window isn't small.
+        * SA_RESTART is also needed, but in Linux, signal()
+        * sets SA_RESTART too.
+        */
+       /* memset(&G.sa, 0, sizeof(G.sa)); - already done */
+       /* sigfillset(&G.sa.sa_mask);      - already done */
+       /* G.sa.sa_flags = SA_RESTART;     - already done */
+       G.sa.sa_handler = handler;
+       sigaction(sig, &G.sa, &old_sa);
+       return old_sa.sa_handler;
+}
 
 #if ENABLE_HUSH_JOB
 
@@ -1393,13 +1490,15 @@ static void SIGCHLD_handler(int sig UNUSED_PARAM)
 static void sigexit(int sig) NORETURN;
 static void sigexit(int sig)
 {
-       /* Disable all signals: job control, SIGPIPE, etc. */
-       sigprocmask_allsigs(SIG_BLOCK);
-
        /* 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)
+       if (G_saved_tty_pgrp && getpid() == G.root_pid) {
+               /* Disable all signals: job control, SIGPIPE, etc.
+                * Mostly paranoid measure, to prevent infinite SIGTTOU.
+                */
+               sigprocmask_allsigs(SIG_BLOCK);
                tcsetpgrp(G_interactive_fd, G_saved_tty_pgrp);
+       }
 
        /* Not a signal, just exit */
        if (sig <= 0)
@@ -1414,15 +1513,43 @@ static void sigexit(int sig)
 
 #endif
 
+static sighandler_t pick_sighandler(unsigned sig)
+{
+       sighandler_t handler = SIG_DFL;
+       if (sig < sizeof(unsigned)*8) {
+               unsigned sigmask = (1 << sig);
+
+#if ENABLE_HUSH_JOB
+               /* is sig fatal? */
+               if (G_fatal_sig_mask & sigmask)
+                       handler = sigexit;
+               else
+#endif
+               /* sig has special handling? */
+               if (G.special_sig_mask & sigmask) {
+                       handler = record_pending_signo;
+                       /* TTIN/TTOU/TSTP can't be set to record_pending_signo
+                        * in order to ignore them: they will be raised
+                        * in an endless loop when we try to do some
+                        * terminal ioctls! We do have to _ignore_ these.
+                        */
+                       if (SPECIAL_JOBSTOP_SIGS & sigmask)
+                               handler = SIG_IGN;
+               }
+       }
+       return handler;
+}
+
 /* Restores tty foreground process group, and exits. */
 static void hush_exit(int exitcode) NORETURN;
 static void hush_exit(int exitcode)
 {
+#if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
+       save_history(G.line_input_state);
+#endif
+
        fflush_all();
        if (G.exiting <= 0 && G.traps && G.traps[0] && G.traps[0][0]) {
-               /* Prevent recursion:
-                * trap "echo Hi; exit" EXIT; exit
-                */
                char *argv[3];
                /* argv[0] is unused */
                argv[1] = G.traps[0];
@@ -1459,28 +1586,30 @@ static void hush_exit(int exitcode)
 }
 
 
-static int check_and_run_traps(int sig)
+//TODO: return a mask of ALL handled sigs?
+static int check_and_run_traps(void)
 {
-       /* I want it in rodata, not in bss.
-        * gcc 4.2.1 puts it in rodata only if it has { 0, 0 }
-        * initializer. But other compilers may still use bss.
-        * TODO: find more portable solution.
-        */
-       static const struct timespec zero_timespec = { 0, 0 };
-       smalluint save_rcode;
        int last_sig = 0;
 
-       if (sig)
-               goto jump_in;
        while (1) {
-               sig = sigtimedwait(&G.blocked_set, NULL, &zero_timespec);
-               if (sig <= 0)
+               int sig;
+
+               if (sigisemptyset(&G.pending_set))
                        break;
- jump_in:
-               last_sig = sig;
+               sig = 0;
+               do {
+                       sig++;
+                       if (sigismember(&G.pending_set, sig)) {
+                               sigdelset(&G.pending_set, sig);
+                               goto got_sig;
+                       }
+               } while (sig < NSIG);
+               break;
+ got_sig:
                if (G.traps && G.traps[sig]) {
                        if (G.traps[sig][0]) {
                                /* We have user-defined handler */
+                               smalluint save_rcode;
                                char *argv[3];
                                /* argv[0] is unused */
                                argv[1] = G.traps[sig];
@@ -1488,21 +1617,17 @@ static int check_and_run_traps(int sig)
                                save_rcode = G.last_exitcode;
                                builtin_eval(argv);
                                G.last_exitcode = save_rcode;
+                               last_sig = sig;
                        } /* else: "" trap, ignoring signal */
                        continue;
                }
                /* not a trap: special action */
                switch (sig) {
-#if ENABLE_HUSH_FAST
-               case SIGCHLD:
-                       G.count_SIGCHLD++;
-//bb_error_msg("[%d] check_and_run_traps: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
-                       break;
-#endif
                case SIGINT:
                        /* Builtin was ^C'ed, make it look prettier: */
                        bb_putchar('\n');
                        G.flag_SIGINT = 1;
+                       last_sig = sig;
                        break;
 #if ENABLE_HUSH_JOB
                case SIGHUP: {
@@ -1519,8 +1644,23 @@ static int check_and_run_traps(int sig)
                        sigexit(SIGHUP);
                }
 #endif
+#if ENABLE_HUSH_FAST
+               case SIGCHLD:
+                       G.count_SIGCHLD++;
+//bb_error_msg("[%d] check_and_run_traps: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD);
+                       /* Note:
+                        * We dont do 'last_sig = sig' here -> NOT returning this sig.
+                        * This simplifies wait builtin a bit.
+                        */
+                       break;
+#endif
                default: /* ignored: */
                        /* SIGTERM, SIGQUIT, SIGTTIN, SIGTTOU, SIGTSTP */
+                       /* Note:
+                        * We dont do 'last_sig = sig' here -> NOT returning this sig.
+                        * Example: wait is not interrupted by TERM
+                        * in interactive shell, because TERM is ignored.
+                        */
                        break;
                }
        }
@@ -1904,14 +2044,17 @@ static void get_user_input(struct in_str *i)
                 * _during_ shell execution, not only if it was set when
                 * shell was started. Therefore, re-check LANG every time:
                 */
-               reinit_unicode(get_local_var_value("LANG"));
+               const char *s = get_local_var_value("LC_ALL");
+               if (!s) s = get_local_var_value("LC_CTYPE");
+               if (!s) s = get_local_var_value("LANG");
+               reinit_unicode(s);
 
                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(G.line_input_state, prompt_str, G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1, /*timeout*/ -1);
                /* catch *SIGINT* etc (^C is handled by read_line_input) */
-               check_and_run_traps(0);
+               check_and_run_traps();
        } while (r == 0 || G.flag_SIGINT); /* repeat if ^C or SIGINT */
        i->eof_flag = (r < 0);
        if (i->eof_flag) { /* EOF/error detected */
@@ -1921,11 +2064,18 @@ static void get_user_input(struct in_str *i)
 # else
        do {
                G.flag_SIGINT = 0;
-               fputs(prompt_str, stdout);
+               if (i->last_char == '\0' || i->last_char == '\n') {
+                       /* Why check_and_run_traps here? Try this interactively:
+                        * $ trap 'echo INT' INT; (sleep 2; kill -INT $$) &
+                        * $ <[enter], repeatedly...>
+                        * Without check_and_run_traps, handler never runs.
+                        */
+                       check_and_run_traps();
+                       fputs(prompt_str, stdout);
+               }
                fflush_all();
                G.user_input_buf[0] = r = fgetc(i->file);
                /*G.user_input_buf[1] = '\0'; - already is and never changed */
-//do we need check_and_run_traps(0)? (maybe only if stdin)
        } while (G.flag_SIGINT);
        i->eof_flag = (r == EOF);
 # endif
@@ -2141,7 +2291,7 @@ static void o_addqblock(o_string *o, const char *str, int len)
                        ordinary_cnt = len;
                o_addblock(o, str, ordinary_cnt);
                if (ordinary_cnt == len)
-                       return;
+                       return; /* NUL is already added by o_addblock */
                str += ordinary_cnt;
                len -= ordinary_cnt + 1; /* we are processing + 1 char below */
 
@@ -2155,8 +2305,8 @@ static void o_addqblock(o_string *o, const char *str, int len)
                o_grow_by(o, sz);
                o->data[o->length] = ch;
                o->length++;
-               o->data[o->length] = '\0';
        }
+       o->data[o->length] = '\0';
 }
 
 static void o_addQblock(o_string *o, const char *str, int len)
@@ -2245,6 +2395,7 @@ static int o_save_ptr_helper(o_string *o, int n)
                                n, string_len, string_start);
                o->has_empty_slot = 0;
        }
+       o->has_quoted_part = 0;
        list[n] = (char*)(uintptr_t)string_len;
        return n + 1;
 }
@@ -2902,24 +3053,24 @@ static const struct reserved_combo* match_reserved_word(o_string *word)
         */
        static const struct reserved_combo reserved_list[] = {
 # 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  },
+               { "!",     RES_NONE,  NOT_ASSIGNMENT  , 0 },
+               { "if",    RES_IF,    MAYBE_ASSIGNMENT, FLAG_THEN | FLAG_START },
+               { "then",  RES_THEN,  MAYBE_ASSIGNMENT, FLAG_ELIF | FLAG_ELSE | FLAG_FI },
+               { "elif",  RES_ELIF,  MAYBE_ASSIGNMENT, FLAG_THEN },
+               { "else",  RES_ELSE,  MAYBE_ASSIGNMENT, FLAG_FI   },
+               { "fi",    RES_FI,    NOT_ASSIGNMENT  , FLAG_END  },
 # 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  },
+               { "for",   RES_FOR,   NOT_ASSIGNMENT  , FLAG_IN | FLAG_DO | FLAG_START },
+               { "while", RES_WHILE, MAYBE_ASSIGNMENT, FLAG_DO | FLAG_START },
+               { "until", RES_UNTIL, MAYBE_ASSIGNMENT, FLAG_DO | FLAG_START },
+               { "in",    RES_IN,    NOT_ASSIGNMENT  , FLAG_DO   },
+               { "do",    RES_DO,    MAYBE_ASSIGNMENT, FLAG_DONE },
+               { "done",  RES_DONE,  NOT_ASSIGNMENT  , FLAG_END  },
 # endif
 # if ENABLE_HUSH_CASE
-               { "case",  RES_CASE,  NOT_ASSIGNMENT , FLAG_MATCH | FLAG_START },
-               { "esac",  RES_ESAC,  NOT_ASSIGNMENT , FLAG_END  },
+               { "case",  RES_CASE,  NOT_ASSIGNMENT  , FLAG_MATCH | FLAG_START },
+               { "esac",  RES_ESAC,  NOT_ASSIGNMENT  , FLAG_END  },
 # endif
        };
        const struct reserved_combo *r;
@@ -2985,6 +3136,7 @@ static int reserved_word(o_string *word, struct parse_context *ctx)
        ctx->ctx_res_w = r->res;
        ctx->old_flag = r->flag;
        word->o_assignment = r->assignment_flag;
+       debug_printf_parse("word->o_assignment='%s'\n", assignment_flag[word->o_assignment]);
 
        if (ctx->old_flag & FLAG_END) {
                struct parse_context *old;
@@ -3051,18 +3203,6 @@ static int done_word(o_string *word, struct parse_context *ctx)
                debug_printf_parse("word stored in rd_filename: '%s'\n", word->data);
                ctx->pending_redirect = NULL;
        } else {
-               /* If this word wasn't an assignment, next ones definitely
-                * can't be assignments. Even if they look like ones. */
-               if (word->o_assignment != DEFINITELY_ASSIGNMENT
-                && word->o_assignment != WORD_IS_KEYWORD
-               ) {
-                       word->o_assignment = NOT_ASSIGNMENT;
-               } else {
-                       if (word->o_assignment == DEFINITELY_ASSIGNMENT)
-                               command->assignment_cnt++;
-                       word->o_assignment = MAYBE_ASSIGNMENT;
-               }
-
 #if HAS_KEYWORDS
 # if ENABLE_HUSH_CASE
                if (ctx->ctx_dsemicolon
@@ -3082,8 +3222,9 @@ static int done_word(o_string *word, struct parse_context *ctx)
                 && ctx->ctx_res_w != RES_CASE
 # endif
                ) {
-                       debug_printf_parse("checking '%s' for reserved-ness\n", word->data);
-                       if (reserved_word(word, ctx)) {
+                       int reserved = reserved_word(word, ctx);
+                       debug_printf_parse("checking for reserved-ness: %d\n", reserved);
+                       if (reserved) {
                                o_reset_to_empty_unquoted(word);
                                debug_printf_parse("done_word return %d\n",
                                                (ctx->ctx_res_w == RES_SNTX));
@@ -3104,6 +3245,23 @@ static int done_word(o_string *word, struct parse_context *ctx)
                                        "groups and arglists don't mix\n");
                        return 1;
                }
+
+               /* If this word wasn't an assignment, next ones definitely
+                * can't be assignments. Even if they look like ones. */
+               if (word->o_assignment != DEFINITELY_ASSIGNMENT
+                && word->o_assignment != WORD_IS_KEYWORD
+               ) {
+                       word->o_assignment = NOT_ASSIGNMENT;
+               } else {
+                       if (word->o_assignment == DEFINITELY_ASSIGNMENT) {
+                               command->assignment_cnt++;
+                               debug_printf_parse("++assignment_cnt=%d\n", command->assignment_cnt);
+                       }
+                       debug_printf_parse("word->o_assignment was:'%s'\n", assignment_flag[word->o_assignment]);
+                       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)
@@ -3117,14 +3275,6 @@ static int done_word(o_string *word, struct parse_context *ctx)
                        ) {
                                p += 3;
                        }
-                       if (p == word->data || p[0] != '\0') {
-                               /* saw no "$@", or not only "$@" but some
-                                * real text is there too */
-                               /* insert "empty variable" reference, this makes
-                                * e.g. "", $empty"" etc to not disappear */
-                               o_addchr(word, SPECIAL_VAR_SYMBOL);
-                               o_addchr(word, SPECIAL_VAR_SYMBOL);
-                       }
                }
                command->argv = add_string_to_strings(command->argv, xstrdup(word->data));
                debug_print_strings("word appended to argv", command->argv);
@@ -3322,6 +3472,7 @@ static char *fetch_till_str(o_string *as_string,
        int ch;
 
        goto jump_in;
+
        while (1) {
                ch = i_getch(input);
                if (ch != EOF)
@@ -4077,7 +4228,7 @@ static struct pipe *parse_stream(char **pstring,
                        /* (this makes bare "&" cmd a no-op.
                         * bash says: "syntax error near unexpected token '&'") */
                        if (pi->num_cmds == 0
-                           IF_HAS_KEYWORDS( && pi->res_word == RES_NONE)
+                       IF_HAS_KEYWORDS(&& pi->res_word == RES_NONE)
                        ) {
                                free_pipe_list(pi);
                                pi = NULL;
@@ -4127,6 +4278,7 @@ static struct pipe *parse_stream(char **pstring,
                         && is_well_formed_var_name(dest.data, '=')
                        ) {
                                dest.o_assignment = DEFINITELY_ASSIGNMENT;
+                               debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]);
                        }
                        continue;
                }
@@ -4176,6 +4328,7 @@ static struct pipe *parse_stream(char **pstring,
                                        heredoc_cnt = 0;
                                }
                                dest.o_assignment = MAYBE_ASSIGNMENT;
+                               debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]);
                                ch = ';';
                                /* note: if (is_blank) continue;
                                 * will still trigger for us */
@@ -4225,9 +4378,10 @@ static struct pipe *parse_stream(char **pstring,
                        }
                        done_pipe(&ctx, PIPE_SEQ);
                        dest.o_assignment = MAYBE_ASSIGNMENT;
+                       debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]);
                        /* Do we sit outside of any if's, loops or case's? */
                        if (!HAS_KEYWORDS
-                        IF_HAS_KEYWORDS(|| (ctx.ctx_res_w == RES_NONE && ctx.old_flag == 0))
+                       IF_HAS_KEYWORDS(|| (ctx.ctx_res_w == RES_NONE && ctx.old_flag == 0))
                        ) {
                                o_free(&dest);
 #if !BB_MMU
@@ -4331,6 +4485,7 @@ static struct pipe *parse_stream(char **pstring,
                        /* ch is a special char and thus this word
                         * cannot be an assignment */
                        dest.o_assignment = NOT_ASSIGNMENT;
+                       debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]);
                }
 
                /* Note: nommu_addchr(&ctx.as_string, ch) is already done */
@@ -4363,20 +4518,30 @@ static struct pipe *parse_stream(char **pstring,
                        break;
                case '\'':
                        dest.has_quoted_part = 1;
-                       while (1) {
-                               ch = i_getch(input);
-                               if (ch == EOF) {
-                                       syntax_error_unterm_ch('\'');
-                                       goto parse_error;
+                       if (next == '\'' && !ctx.pending_redirect) {
+ insert_empty_quoted_str_marker:
+                               nommu_addchr(&ctx.as_string, next);
+                               i_getch(input); /* eat second ' */
+                               o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+                               o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+                       } else {
+                               while (1) {
+                                       ch = i_getch(input);
+                                       if (ch == EOF) {
+                                               syntax_error_unterm_ch('\'');
+                                               goto parse_error;
+                                       }
+                                       nommu_addchr(&ctx.as_string, ch);
+                                       if (ch == '\'')
+                                               break;
+                                       o_addqchr(&dest, ch);
                                }
-                               nommu_addchr(&ctx.as_string, ch);
-                               if (ch == '\'')
-                                       break;
-                               o_addqchr(&dest, ch);
                        }
                        break;
                case '"':
                        dest.has_quoted_part = 1;
+                       if (next == '"' && !ctx.pending_redirect)
+                               goto insert_empty_quoted_str_marker;
                        if (dest.o_assignment == NOT_ASSIGNMENT)
                                dest.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS;
                        if (!encode_string(&ctx.as_string, &dest, input, '"', /*process_bkslash:*/ 1))
@@ -4385,11 +4550,11 @@ static struct pipe *parse_stream(char **pstring,
                        break;
 #if ENABLE_HUSH_TICK
                case '`': {
-                       unsigned pos;
+                       USE_FOR_NOMMU(unsigned pos;)
 
                        o_addchr(&dest, SPECIAL_VAR_SYMBOL);
                        o_addchr(&dest, '`');
-                       pos = dest.length;
+                       USE_FOR_NOMMU(pos = dest.length;)
                        if (!add_till_backquote(&dest, input, /*in_dquote:*/ 0))
                                goto parse_error;
 # if !BB_MMU
@@ -4429,6 +4594,7 @@ static struct pipe *parse_stream(char **pstring,
                        /* We just finished a cmd. New one may start
                         * with an assignment */
                        dest.o_assignment = MAYBE_ASSIGNMENT;
+                       debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]);
                        break;
                case '&':
                        if (done_word(&dest, &ctx)) {
@@ -4597,12 +4763,22 @@ static void o_addblock_duplicate_backslash(o_string *o, const char *str, int len
 
 /* Store given string, finalizing the word and starting new one whenever
  * we encounter IFS char(s). This is used for expanding variable values.
- * End-of-string does NOT finalize word: think about 'echo -$VAR-' */
-static int expand_on_ifs(o_string *output, int n, const char *str)
+ * End-of-string does NOT finalize word: think about 'echo -$VAR-'.
+ * Return in *ended_with_ifs:
+ * 1 - ended with IFS char, else 0 (this includes case of empty str).
+ */
+static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const char *str)
 {
+       int last_is_ifs = 0;
+
        while (1) {
-               int word_len = strcspn(str, G.ifs);
+               int word_len;
+
+               if (!*str)  /* EOL - do not finalize word */
+                       break;
+               word_len = strcspn(str, G.ifs);
                if (word_len) {
+                       /* We have WORD_LEN leading non-IFS chars */
                        if (!(output->o_expflags & EXP_FLAG_GLOB)) {
                                o_addblock(output, str, word_len);
                        } else {
@@ -4615,15 +4791,36 @@ static int expand_on_ifs(o_string *output, int n, const char *str)
                                /*o_addblock(output, str, word_len); - WRONG: "v='\*'; echo Z$v" prints "Z*" instead of "Z\*" */
                                /*o_addqblock(output, str, word_len); - WRONG: "v='*'; echo Z$v" prints "Z*" instead of Z* files */
                        }
+                       last_is_ifs = 0;
                        str += word_len;
+                       if (!*str)  /* EOL - do not finalize word */
+                               break;
                }
+
+               /* We know str here points to at least one IFS char */
+               last_is_ifs = 1;
+               str += strspn(str, G.ifs); /* skip IFS chars */
                if (!*str)  /* EOL - do not finalize word */
                        break;
-               o_addchr(output, '\0');
-               debug_print_list("expand_on_ifs", output, n);
-               n = o_save_ptr(output, n);
-               str += strspn(str, G.ifs); /* skip ifs chars */
+
+               /* Start new word... but not always! */
+               /* Case "v=' a'; echo ''$v": we do need to finalize empty word: */
+               if (output->has_quoted_part
+               /* Case "v=' a'; echo $v":
+                * here nothing precedes the space in $v expansion,
+                * therefore we should not finish the word
+                * (IOW: if there *is* word to finalize, only then do it):
+                */
+                || (n > 0 && output->data[output->length - 1])
+               ) {
+                       o_addchr(output, '\0');
+                       debug_print_list("expand_on_ifs", output, n);
+                       n = o_save_ptr(output, n);
+               }
        }
+
+       if (ended_with_ifs)
+               *ended_with_ifs = last_is_ifs;
        debug_print_list("expand_on_ifs[1]", output, n);
        return n;
 }
@@ -5038,6 +5235,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
         * expansion of right-hand side of assignment == 1-element expand.
         */
        char cant_be_null = 0; /* only bit 0x80 matters */
+       int ended_in_ifs = 0;  /* did last unquoted expansion end with IFS chars? */
        char *p;
 
        debug_printf_expand("expand_vars_to_list: arg:'%s' singleword:%x\n", arg,
@@ -5056,6 +5254,13 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
 #if ENABLE_SH_MATH_SUPPORT
                char arith_buf[sizeof(arith_t)*3 + 2];
 #endif
+
+               if (ended_in_ifs) {
+                       o_addchr(output, '\0');
+                       n = o_save_ptr(output, n);
+                       ended_in_ifs = 0;
+               }
+
                o_addblock(output, arg, p - arg);
                debug_print_list("expand_vars_to_list[1]", output, n);
                arg = ++p;
@@ -5084,7 +5289,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
                        cant_be_null |= first_ch; /* do it for "$@" _now_, when we know it's not empty */
                        if (!(first_ch & 0x80)) { /* unquoted $* or $@ */
                                while (G.global_argv[i]) {
-                                       n = expand_on_ifs(output, n, G.global_argv[i]);
+                                       n = expand_on_ifs(NULL, output, n, G.global_argv[i]);
                                        debug_printf_expand("expand_vars_to_list: argv %d (last %d)\n", i, G.global_argc - 1);
                                        if (G.global_argv[i++][0] && G.global_argv[i]) {
                                                /* this argv[] is not empty and not last:
@@ -5117,11 +5322,13 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
                                        if (G.ifs[0])
                                                o_addchr(output, G.ifs[0]);
                                }
+                               output->has_quoted_part = 1;
                        }
                        break;
                }
                case SPECIAL_VAR_SYMBOL: /* <SPECIAL_VAR_SYMBOL><SPECIAL_VAR_SYMBOL> */
                        /* "Empty variable", used to make "" etc to not disappear */
+                       output->has_quoted_part = 1;
                        arg++;
                        cant_be_null = 0x80;
                        break;
@@ -5159,10 +5366,11 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
                                debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val,
                                                !!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
                                if (val && val[0]) {
-                                       n = expand_on_ifs(output, n, val);
+                                       n = expand_on_ifs(&ended_in_ifs, output, n, val);
                                        val = NULL;
                                }
                        } else { /* quoted $VAR, val will be appended below */
+                               output->has_quoted_part = 1;
                                debug_printf_expand("quoted '%s', output->o_escape:%d\n", val,
                                                !!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
                        }
@@ -5187,6 +5395,10 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
        } /* end of "while (SPECIAL_VAR_SYMBOL is found) ..." */
 
        if (arg[0]) {
+               if (ended_in_ifs) {
+                       o_addchr(output, '\0');
+                       n = o_save_ptr(output, n);
+               }
                debug_print_list("expand_vars_to_list[a]", output, n);
                /* this part is literal, and it was already pre-quoted
                 * if needed (much earlier), do not use o_addQstr here! */
@@ -5317,6 +5529,25 @@ static char **expand_assignments(char **argv, int count)
 }
 
 
+static void switch_off_special_sigs(unsigned mask)
+{
+       unsigned sig = 0;
+       while ((mask >>= 1) != 0) {
+               sig++;
+               if (!(mask & 1))
+                       continue;
+               if (G.traps) {
+                       if (G.traps[sig] && !G.traps[sig][0])
+                               /* trap is '', has to remain SIG_IGN */
+                               continue;
+                       free(G.traps[sig]);
+                       G.traps[sig] = NULL;
+               }
+               /* We are here only if no trap or trap was not '' */
+               install_sighandler(sig, SIG_DFL);
+       }
+}
+
 #if BB_MMU
 /* never called */
 void re_execute_shell(char ***to_free, const char *s,
@@ -5336,47 +5567,36 @@ static void reset_traps_to_defaults(void)
         * Testcase: (while :; do :; done) + ^Z should background.
         * Same goes for SIGTERM, SIGHUP, SIGINT.
         */
-       if (!G.traps && !(G.non_DFL_mask & SPECIAL_INTERACTIVE_SIGS))
-               return; /* already no traps and no SPECIAL_INTERACTIVE_SIGS */
-
-       /* Switching off SPECIAL_INTERACTIVE_SIGS.
-        * Stupid. It can be done with *single* &= op, but we can't use
-        * the fact that G.blocked_set is implemented as a bitmask
-        * in libc... */
-       mask = (SPECIAL_INTERACTIVE_SIGS >> 1);
-       sig = 1;
-       while (1) {
-               if (mask & 1) {
-                       /* Careful. Only if no trap or trap is not "" */
-                       if (!G.traps || !G.traps[sig] || G.traps[sig][0])
-                               sigdelset(&G.blocked_set, sig);
-               }
-               mask >>= 1;
-               if (!mask)
-                       break;
-               sig++;
-       }
-       /* Our homegrown sig mask is saner to work with :) */
-       G.non_DFL_mask &= ~SPECIAL_INTERACTIVE_SIGS;
+       mask = (G.special_sig_mask & SPECIAL_INTERACTIVE_SIGS) | G_fatal_sig_mask;
+       if (!G.traps && !mask)
+               return; /* already no traps and no special sigs */
 
-       /* Resetting all traps to default except empty ones */
-       mask = G.non_DFL_mask;
-       if (G.traps) for (sig = 0; sig < NSIG; sig++, mask >>= 1) {
-               if (!G.traps[sig] || !G.traps[sig][0])
-                       continue;
+       /* Switch off special sigs */
+       switch_off_special_sigs(mask);
+#if ENABLE_HUSH_JOB
+       G_fatal_sig_mask = 0;
+#endif
+       G.special_sig_mask &= ~SPECIAL_INTERACTIVE_SIGS;
+       /* SIGQUIT,SIGCHLD and maybe SPECIAL_JOBSTOP_SIGS
+        * remain set in G.special_sig_mask */
+
+       if (!G.traps)
+               return;
+
+       /* Reset all sigs to default except ones with empty traps */
+       for (sig = 0; sig < NSIG; sig++) {
+               if (!G.traps[sig])
+                       continue; /* no trap: nothing to do */
+               if (!G.traps[sig][0])
+                       continue; /* empty trap: has to remain SIG_IGN */
+               /* sig has non-empty trap, reset it: */
                free(G.traps[sig]);
                G.traps[sig] = NULL;
-               /* There is no signal for 0 (EXIT) */
+               /* There is no signal for trap 0 (EXIT) */
                if (sig == 0)
                        continue;
-               /* There was a trap handler, we just removed it.
-                * But if sig still has non-DFL handling,
-                * we should not unblock the sig. */
-               if (mask & 1)
-                       continue;
-               sigdelset(&G.blocked_set, sig);
+               install_sighandler(sig, pick_sighandler(sig));
        }
-       sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
 }
 
 #else /* !BB_MMU */
@@ -5487,9 +5707,6 @@ static void re_execute_shell(char ***to_free, const char *s,
         * _inside_ group (just before echo 1), it works.
         *
         * I conclude it means we don't need to pass active traps here.
-        * Even if we would use signal handlers instead of signal masking
-        * in order to implement trap handling,
-        * exec syscall below resets signals to SIG_DFL for us.
         */
        *pp++ = (char *) "-c";
        *pp++ = (char *) s;
@@ -5506,7 +5723,9 @@ static void re_execute_shell(char ***to_free, const char *s,
 
  do_exec:
        debug_printf_exec("re_execute_shell pid:%d cmd:'%s'\n", getpid(), s);
-       sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
+       /* Don't propagate SIG_IGN to the child */
+       if (SPECIAL_JOBSTOP_SIGS != 0)
+               switch_off_special_sigs(G.special_sig_mask & SPECIAL_JOBSTOP_SIGS);
        execve(bb_busybox_exec_path, argv, pp);
        /* Fallback. Useful for init=/bin/hush usage etc */
        if (argv[0][0] == '/')
@@ -6160,7 +6379,9 @@ 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);
+       /* Don't propagate SIG_IGN to the child */
+       if (SPECIAL_JOBSTOP_SIGS != 0)
+               switch_off_special_sigs(G.special_sig_mask & SPECIAL_JOBSTOP_SIGS);
        execvp(argv[0], argv);
        bb_perror_msg("can't execute '%s'", argv[0]);
        _exit(127); /* bash compat */
@@ -6292,7 +6513,9 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
 # endif
                        /* Re-exec ourselves */
                        debug_printf_exec("re-execing applet '%s'\n", argv[0]);
-                       sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
+                       /* Don't propagate SIG_IGN to the child */
+                       if (SPECIAL_JOBSTOP_SIGS != 0)
+                               switch_off_special_sigs(G.special_sig_mask & SPECIAL_JOBSTOP_SIGS);
                        execv(bb_busybox_exec_path, argv);
                        /* If they called chroot or otherwise made the binary no longer
                         * executable, fall through */
@@ -6550,7 +6773,6 @@ static int checkjobs(struct pipe *fg_pipe)
                                        }
                                        fg_pipe->cmds[i].cmd_exitcode = ex;
                                } else {
-                                       fg_pipe->cmds[i].is_stopped = 1;
                                        fg_pipe->stopped_cmds++;
                                }
                                debug_printf_jobs("fg_pipe: alive_cmds %d stopped_cmds %d\n",
@@ -6611,7 +6833,6 @@ static int checkjobs(struct pipe *fg_pipe)
                        }
                } else {
                        /* child stopped */
-                       pi->cmds[i].is_stopped = 1;
                        pi->stopped_cmds++;
                }
 #endif
@@ -6991,9 +7212,6 @@ static NOINLINE int run_pipe(struct pipe *pi)
                        if (setup_redirects(command, NULL))
                                _exit(1);
 
-                       /* Restore default handlers just prior to exec */
-                       /*signal(SIGCHLD, SIG_DFL); - so far we don't have any handlers */
-
                        /* Stores to nommu_save list of env vars putenv'ed
                         * (NOMMU, on MMU we don't need that) */
                        /* cast away volatility... */
@@ -7145,7 +7363,7 @@ static int run_list(struct pipe *pi)
                                 * and we should not execute CMD */
                                debug_printf_exec("skipped cmd because of || or &&\n");
                                last_followup = pi->followup;
-                               continue;
+                               goto dont_check_jobs_but_continue;
                        }
                }
                last_followup = pi->followup;
@@ -7271,7 +7489,7 @@ static int run_list(struct pipe *pi)
                                 * and we don't need to wait for anything. */
                                G.last_exitcode = rcode;
                                debug_printf_exec(": builtin/func exitcode %d\n", rcode);
-                               check_and_run_traps(0);
+                               check_and_run_traps();
 #if ENABLE_HUSH_LOOPS
                                /* Was it "break" or "continue"? */
                                if (G.flag_break_continue) {
@@ -7284,8 +7502,10 @@ static int run_list(struct pipe *pi)
                                                        G.flag_break_continue = 0;
                                                /* else: e.g. "continue 2" should *break* once, *then* continue */
                                        } /* else: "while... do... { we are here (innermost list is not a loop!) };...done" */
-                                       if (G.depth_break_continue != 0 || fbc == BC_BREAK)
-                                               goto check_jobs_and_break;
+                                       if (G.depth_break_continue != 0 || fbc == BC_BREAK) {
+                                               checkjobs(NULL);
+                                               break;
+                                       }
                                        /* "continue": simulate end of loop */
                                        rword = RES_DONE;
                                        continue;
@@ -7293,7 +7513,6 @@ static int run_list(struct pipe *pi)
 #endif
 #if ENABLE_HUSH_FUNCTIONS
                                if (G.flag_return_in_progress == 1) {
-                                       /* same as "goto check_jobs_and_break" */
                                        checkjobs(NULL);
                                        break;
                                }
@@ -7303,7 +7522,7 @@ static int run_list(struct pipe *pi)
                                /* even bash 3.2 doesn't do that well with nested bg:
                                 * try "{ { sleep 10; echo DEEP; } & echo HERE; } &".
                                 * I'm NOT treating inner &'s as jobs */
-                               check_and_run_traps(0);
+                               check_and_run_traps();
 #if ENABLE_HUSH_JOB
                                if (G.run_list_level == 1)
                                        insert_bg_job(pi);
@@ -7318,13 +7537,13 @@ static int run_list(struct pipe *pi)
                                        /* Waits for completion, then fg's main shell */
                                        rcode = checkjobs_and_fg_shell(pi);
                                        debug_printf_exec(": checkjobs_and_fg_shell exitcode %d\n", rcode);
-                                       check_and_run_traps(0);
+                                       check_and_run_traps();
                                } else
 #endif
                                { /* This one just waits for completion */
                                        rcode = checkjobs(pi);
                                        debug_printf_exec(": checkjobs exitcode %d\n", rcode);
-                                       check_and_run_traps(0);
+                                       check_and_run_traps();
                                }
                                G.last_exitcode = rcode;
                        }
@@ -7335,30 +7554,31 @@ static int run_list(struct pipe *pi)
                if (rword == RES_IF || rword == RES_ELIF)
                        cond_code = rcode;
 #endif
+ check_jobs_and_continue:
+               checkjobs(NULL);
+ dont_check_jobs_but_continue: ;
 #if ENABLE_HUSH_LOOPS
                /* Beware of "while false; true; do ..."! */
-               if (pi->next && pi->next->res_word == RES_DO) {
+               if (pi->next
+                && (pi->next->res_word == RES_DO || pi->next->res_word == RES_DONE)
+                /* check for RES_DONE is needed for "while ...; do \n done" case */
+               ) {
                        if (rword == RES_WHILE) {
                                if (rcode) {
                                        /* "while false; do...done" - exitcode 0 */
                                        G.last_exitcode = rcode = EXIT_SUCCESS;
                                        debug_printf_exec(": while expr is false: breaking (exitcode:EXIT_SUCCESS)\n");
-                                       goto check_jobs_and_break;
+                                       break;
                                }
                        }
                        if (rword == RES_UNTIL) {
                                if (!rcode) {
                                        debug_printf_exec(": until expr is true: breaking\n");
- check_jobs_and_break:
-                                       checkjobs(NULL);
                                        break;
                                }
                        }
                }
 #endif
-
- check_jobs_and_continue:
-               checkjobs(NULL);
        } /* for (pi) */
 
 #if ENABLE_HUSH_JOB
@@ -7395,88 +7615,81 @@ static int run_and_free_list(struct pipe *pi)
 }
 
 
+static void install_sighandlers(unsigned mask)
+{
+       sighandler_t old_handler;
+       unsigned sig = 0;
+       while ((mask >>= 1) != 0) {
+               sig++;
+               if (!(mask & 1))
+                       continue;
+               old_handler = install_sighandler(sig, pick_sighandler(sig));
+               /* POSIX allows shell to re-enable SIGCHLD
+                * even if it was SIG_IGN on entry.
+                * Therefore we skip IGN check for it:
+                */
+               if (sig == SIGCHLD)
+                       continue;
+               if (old_handler == SIG_IGN) {
+                       /* oops... restore back to IGN, and record this fact */
+                       install_sighandler(sig, old_handler);
+                       if (!G.traps)
+                               G.traps = xzalloc(sizeof(G.traps[0]) * NSIG);
+                       free(G.traps[sig]);
+                       G.traps[sig] = xzalloc(1); /* == xstrdup(""); */
+               }
+       }
+}
+
 /* Called a few times only (or even once if "sh -c") */
-static void init_sigmasks(void)
+static void install_special_sighandlers(void)
 {
-       unsigned sig;
        unsigned mask;
-       sigset_t old_blocked_set;
-
-       if (!G.inherited_set_is_saved) {
-               sigprocmask(SIG_SETMASK, NULL, &G.blocked_set);
-               G.inherited_set = G.blocked_set;
-       }
-       old_blocked_set = G.blocked_set;
 
-       mask = (1 << SIGQUIT);
+       /* Which signals are shell-special? */
+       mask = (1 << SIGQUIT) | (1 << SIGCHLD);
        if (G_interactive_fd) {
-               mask = (1 << SIGQUIT) | SPECIAL_INTERACTIVE_SIGS;
+               mask |= SPECIAL_INTERACTIVE_SIGS;
                if (G_saved_tty_pgrp) /* we have ctty, job control sigs work */
-                       mask |= SPECIAL_JOB_SIGS;
+                       mask |= SPECIAL_JOBSTOP_SIGS;
        }
-       G.non_DFL_mask = mask;
-
-       sig = 0;
-       while (mask) {
-               if (mask & 1)
-                       sigaddset(&G.blocked_set, sig);
-               mask >>= 1;
-               sig++;
+       /* Careful, do not re-install handlers we already installed */
+       if (G.special_sig_mask != mask) {
+               unsigned diff = mask & ~G.special_sig_mask;
+               G.special_sig_mask = mask;
+               install_sighandlers(diff);
        }
-       sigdelset(&G.blocked_set, SIGCHLD);
-
-       if (memcmp(&old_blocked_set, &G.blocked_set, sizeof(old_blocked_set)) != 0)
-               sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
-
-       /* POSIX allows shell to re-enable SIGCHLD
-        * even if it was SIG_IGN on entry */
-#if ENABLE_HUSH_FAST
-       G.count_SIGCHLD++; /* ensure it is != G.handled_SIGCHLD */
-       if (!G.inherited_set_is_saved)
-               signal(SIGCHLD, SIGCHLD_handler);
-#else
-       if (!G.inherited_set_is_saved)
-               signal(SIGCHLD, SIG_DFL);
-#endif
-
-       G.inherited_set_is_saved = 1;
 }
 
 #if ENABLE_HUSH_JOB
 /* helper */
-static void maybe_set_to_sigexit(int sig)
-{
-       void (*handler)(int);
-       /* non_DFL_mask'ed signals are, well, masked,
-        * no need to set handler for them.
-        */
-       if (!((G.non_DFL_mask >> sig) & 1)) {
-               handler = signal(sig, sigexit);
-               if (handler == SIG_IGN) /* oops... restore back to IGN! */
-                       signal(sig, handler);
-       }
-}
 /* Set handlers to restore tty pgrp and exit */
-static void set_fatal_handlers(void)
-{
-       /* We _must_ restore tty pgrp on fatal signals */
-       if (HUSH_DEBUG) {
-               maybe_set_to_sigexit(SIGILL );
-               maybe_set_to_sigexit(SIGFPE );
-               maybe_set_to_sigexit(SIGBUS );
-               maybe_set_to_sigexit(SIGSEGV);
-               maybe_set_to_sigexit(SIGTRAP);
-       } /* else: hush is perfect. what SEGV? */
-       maybe_set_to_sigexit(SIGABRT);
+static void install_fatal_sighandlers(void)
+{
+       unsigned mask;
+
+       /* We will restore tty pgrp on these signals */
+       mask = 0
+               + (1 << SIGILL ) * HUSH_DEBUG
+               + (1 << SIGFPE ) * HUSH_DEBUG
+               + (1 << SIGBUS ) * HUSH_DEBUG
+               + (1 << SIGSEGV) * HUSH_DEBUG
+               + (1 << SIGTRAP) * HUSH_DEBUG
+               + (1 << SIGABRT)
        /* bash 3.2 seems to handle these just like 'fatal' ones */
-       maybe_set_to_sigexit(SIGPIPE);
-       maybe_set_to_sigexit(SIGALRM);
-       /* if we are interactive, SIGHUP, SIGTERM and SIGINT are masked.
+               + (1 << SIGPIPE)
+               + (1 << SIGALRM)
+       /* if we are interactive, SIGHUP, SIGTERM and SIGINT are special sigs.
         * if we aren't interactive... but in this case
-        * we never want to restore pgrp on exit, and this fn is not called */
-       /*maybe_set_to_sigexit(SIGHUP );*/
-       /*maybe_set_to_sigexit(SIGTERM);*/
-       /*maybe_set_to_sigexit(SIGINT );*/
+        * we never want to restore pgrp on exit, and this fn is not called
+        */
+               /*+ (1 << SIGHUP )*/
+               /*+ (1 << SIGTERM)*/
+               /*+ (1 << SIGINT )*/
+       ;
+       G_fatal_sig_mask = mask;
+
+       install_sighandlers(mask);
 }
 #endif
 
@@ -7522,6 +7735,10 @@ static int set_mode(int state, char mode, const char *o_opt)
 int hush_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int hush_main(int argc, char **argv)
 {
+       enum {
+               OPT_login = (1 << 0),
+       };
+       unsigned flags;
        int opt;
        unsigned builtin_argc;
        char **e;
@@ -7529,8 +7746,11 @@ int hush_main(int argc, char **argv)
        struct variable *shell_ver;
 
        INIT_G();
-       if (EXIT_SUCCESS) /* if EXIT_SUCCESS == 0, it is already done */
+       if (EXIT_SUCCESS != 0) /* if EXIT_SUCCESS == 0, it is already done */
                G.last_exitcode = EXIT_SUCCESS;
+#if ENABLE_HUSH_FAST
+       G.count_SIGCHLD++; /* ensure it is != G.handled_SIGCHLD */
+#endif
 #if !BB_MMU
        G.argv0_for_re_execing = argv[0];
 #endif
@@ -7604,26 +7824,8 @@ int hush_main(int argc, char **argv)
 
 #if ENABLE_FEATURE_EDITING
        G.line_input_state = new_line_input_t(FOR_SHELL);
-# if defined MAX_HISTORY && MAX_HISTORY > 0 && ENABLE_HUSH_SAVEHISTORY
-       {
-               const char *hp = get_local_var_value("HISTFILE");
-               if (!hp) {
-                       hp = get_local_var_value("HOME");
-                       if (hp) {
-                               G.line_input_state->hist_file = concat_path_file(hp, ".hush_history");
-                               //set_local_var(xasprintf("HISTFILE=%s", ...));
-                       }
-               }
-# if ENABLE_FEATURE_SH_HISTFILESIZE
-               hp = get_local_var_value("HISTFILESIZE");
-               G.line_input_state->max_history = size_from_HISTFILESIZE(hp);
-# endif
-       }
-# endif
 #endif
 
-       G.global_argc = argc;
-       G.global_argv = argv;
        /* Initialize some more globals to non-zero values */
        cmdedit_update_prompt();
 
@@ -7635,17 +7837,18 @@ int hush_main(int argc, char **argv)
        }
 
        /* Shell is non-interactive at first. We need to call
-        * init_sigmasks() if we are going to execute "sh <script>",
+        * install_special_sighandlers() if we are going to execute "sh <script>",
         * "sh -c <cmds>" or login shell's /etc/profile and friends.
-        * If we later decide that we are interactive, we run init_sigmasks()
+        * If we later decide that we are interactive, we run install_special_sighandlers()
         * in order to intercept (more) signals.
         */
 
        /* Parse options */
        /* http://www.opengroup.org/onlinepubs/9699919799/utilities/sh.html */
+       flags = (argv[0] && argv[0][0] == '-') ? OPT_login : 0;
        builtin_argc = 0;
        while (1) {
-               opt = getopt(argc, argv, "+c:xins"
+               opt = getopt(argc, argv, "+c:xinsl"
 #if !BB_MMU
                                "<:$:R:V:"
 # if ENABLE_HUSH_FUNCTIONS
@@ -7677,7 +7880,7 @@ int hush_main(int argc, char **argv)
                                /* -c 'builtin' [BARGV...] "" ARG0 [ARG1...] */
                                const struct built_in_command *x;
 
-                               init_sigmasks();
+                               install_special_sighandlers();
                                x = find_builtin(optarg);
                                if (x) { /* paranoia */
                                        G.global_argc -= builtin_argc; /* skip [BARGV...] "" */
@@ -7694,7 +7897,7 @@ int hush_main(int argc, char **argv)
                                G.global_argv[0] = argv[0];
                                G.global_argc++;
                        } /* else -c 'script' ARG0 [ARG1...]: $0 is ARG0 */
-                       init_sigmasks();
+                       install_special_sighandlers();
                        parse_and_run_string(optarg);
                        goto final_return;
                case 'i':
@@ -7706,6 +7909,9 @@ int hush_main(int argc, char **argv)
                        /* "-s" means "read from stdin", but this is how we always
                         * operate, so simply do nothing here. */
                        break;
+               case 'l':
+                       flags |= OPT_login;
+                       break;
 #if !BB_MMU
                case '<': /* "big heredoc" support */
                        full_write1_str(optarg);
@@ -7726,15 +7932,14 @@ int hush_main(int argc, char **argv)
                        empty_trap_mask = bb_strtoull(optarg, &optarg, 16);
                        if (empty_trap_mask != 0) {
                                int sig;
-                               init_sigmasks();
+                               install_special_sighandlers();
                                G.traps = xzalloc(sizeof(G.traps[0]) * NSIG);
                                for (sig = 1; sig < NSIG; sig++) {
                                        if (empty_trap_mask & (1LL << sig)) {
                                                G.traps[sig] = xzalloc(1); /* == xstrdup(""); */
-                                               sigaddset(&G.blocked_set, sig);
+                                               install_sighandler(sig, SIG_IGN);
                                        }
                                }
-                               sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
                        }
 # if ENABLE_HUSH_LOOPS
                        optarg++;
@@ -7772,19 +7977,24 @@ int hush_main(int argc, char **argv)
                }
        } /* option parsing loop */
 
+       /* Skip options. Try "hush -l": $1 should not be "-l"! */
+       G.global_argc = argc - (optind - 1);
+       G.global_argv = argv + (optind - 1);
+       G.global_argv[0] = argv[0];
+
        if (!G.root_pid) {
                G.root_pid = getpid();
                G.root_ppid = getppid();
        }
 
        /* If we are login shell... */
-       if (argv[0] && argv[0][0] == '-') {
+       if (flags & OPT_login) {
                FILE *input;
                debug_printf("sourcing /etc/profile\n");
                input = fopen_for_read("/etc/profile");
                if (input != NULL) {
                        close_on_exec_on(fileno(input));
-                       init_sigmasks();
+                       install_special_sighandlers();
                        parse_and_run_file(input);
                        fclose(input);
                }
@@ -7797,19 +8007,19 @@ int hush_main(int argc, char **argv)
                 */
        }
 
-       if (argv[optind]) {
+       if (G.global_argv[1]) {
                FILE *input;
                /*
                 * "bash <script>" (which is never interactive (unless -i?))
                 * sources $BASH_ENV here (without scanning $PATH).
                 * If called as sh, does the same but with $ENV.
                 */
-               debug_printf("running script '%s'\n", argv[optind]);
-               G.global_argv = argv + optind;
-               G.global_argc = argc - optind;
-               input = xfopen_for_read(argv[optind]);
+               G.global_argc--;
+               G.global_argv++;
+               debug_printf("running script '%s'\n", G.global_argv[0]);
+               input = xfopen_for_read(G.global_argv[0]);
                close_on_exec_on(fileno(input));
-               init_sigmasks();
+               install_special_sighandlers();
                parse_and_run_file(input);
 #if ENABLE_FEATURE_CLEAN_UP
                fclose(input);
@@ -7818,7 +8028,7 @@ int hush_main(int argc, char **argv)
        }
 
        /* Up to here, shell was non-interactive. Now it may become one.
-        * NB: don't forget to (re)run init_sigmasks() as needed.
+        * NB: don't forget to (re)run install_special_sighandlers() as needed.
         */
 
        /* A shell is interactive if the '-i' flag was given,
@@ -7870,12 +8080,12 @@ int hush_main(int argc, char **argv)
                        }
                }
 
-               /* Block some signals */
-               init_sigmasks();
+               /* Install more signal handlers */
+               install_special_sighandlers();
 
                if (G_saved_tty_pgrp) {
                        /* Set other signals to restore saved_tty_pgrp */
-                       set_fatal_handlers();
+                       install_fatal_sighandlers();
                        /* Put ourselves in our own process group
                         * (bash, too, does this only if ctty is available) */
                        bb_setpgrp(); /* is the same as setpgid(our_pid, our_pid); */
@@ -7885,8 +8095,29 @@ int hush_main(int argc, char **argv)
                /* -1 is special - makes xfuncs longjmp, not exit
                 * (we reset die_sleep = 0 whereever we [v]fork) */
                enable_restore_tty_pgrp_on_exit(); /* sets die_sleep = -1 */
+
+# if ENABLE_HUSH_SAVEHISTORY && MAX_HISTORY > 0
+               {
+                       const char *hp = get_local_var_value("HISTFILE");
+                       if (!hp) {
+                               hp = get_local_var_value("HOME");
+                               if (hp)
+                                       hp = concat_path_file(hp, ".hush_history");
+                       } else {
+                               hp = xstrdup(hp);
+                       }
+                       if (hp) {
+                               G.line_input_state->hist_file = hp;
+                               //set_local_var(xasprintf("HISTFILE=%s", ...));
+                       }
+#  if ENABLE_FEATURE_SH_HISTFILESIZE
+                       hp = get_local_var_value("HISTFILESIZE");
+                       G.line_input_state->max_history = size_from_HISTFILESIZE(hp);
+#  endif
+               }
+# endif
        } else {
-               init_sigmasks();
+               install_special_sighandlers();
        }
 #elif ENABLE_HUSH_INTERACTIVE
        /* No job control compiled in, only prompt/line editing */
@@ -7903,10 +8134,10 @@ int hush_main(int argc, char **argv)
        if (G_interactive_fd) {
                close_on_exec_on(G_interactive_fd);
        }
-       init_sigmasks();
+       install_special_sighandlers();
 #else
        /* We have interactiveness code disabled */
-       init_sigmasks();
+       install_special_sighandlers();
 #endif
        /* bash:
         * if interactive but not a login shell, sources ~/.bashrc
@@ -8040,7 +8271,7 @@ static int FAST_FUNC builtin_exec(char **argv)
                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)
+        * We'll need to undo trap cleanup (it's inside execvp_or_die)
         * and tcsetpgrp, and this is inherently racy.
         */
        execvp_or_die(argv);
@@ -8058,7 +8289,7 @@ static int FAST_FUNC builtin_exit(char **argv)
         * (if there are _stopped_ jobs, running ones don't count)
         * # exit
         * exit
-        # EEE (then bash exits)
+        * EEE (then bash exits)
         *
         * TODO: we can use G.exiting = -1 as indicator "last cmd was exit"
         */
@@ -8237,6 +8468,8 @@ static int FAST_FUNC builtin_trap(char **argv)
  process_sig_list:
                ret = EXIT_SUCCESS;
                while (*argv) {
+                       sighandler_t handler;
+
                        sig = get_signum(*argv++);
                        if (sig < 0 || sig >= NSIG) {
                                ret = EXIT_FAILURE;
@@ -8255,18 +8488,13 @@ static int FAST_FUNC builtin_trap(char **argv)
                        if (sig == 0)
                                continue;
 
-                       if (new_cmd) {
-                               sigaddset(&G.blocked_set, sig);
-                       } else {
-                               /* There was a trap handler, we are removing it
-                                * (if sig has non-DFL handling,
-                                * we don't need to do anything) */
-                               if (sig < 32 && (G.non_DFL_mask & (1 << sig)))
-                                       continue;
-                               sigdelset(&G.blocked_set, sig);
-                       }
+                       if (new_cmd)
+                               handler = (new_cmd[0] ? record_pending_signo : SIG_IGN);
+                       else
+                               /* We are removing trap handler */
+                               handler = pick_sighandler(sig);
+                       install_sighandler(sig, handler);
                }
-               sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
                return ret;
        }
 
@@ -8371,7 +8599,6 @@ static int FAST_FUNC builtin_fg_bg(char **argv)
        debug_printf_jobs("reviving %d procs, pgrp %d\n", pi->num_cmds, pi->pgrp);
        for (i = 0; i < pi->num_cmds; i++) {
                debug_printf_jobs("reviving pid %d\n", pi->cmds[i].pid);
-               pi->cmds[i].is_stopped = 0;
        }
        pi->stopped_cmds = 0;
 
@@ -8409,6 +8636,14 @@ static int FAST_FUNC builtin_help(char **argv UNUSED_PARAM)
 }
 #endif
 
+#if MAX_HISTORY && ENABLE_FEATURE_EDITING
+static int FAST_FUNC builtin_history(char **argv UNUSED_PARAM)
+{
+       show_history(G.line_input_state);
+       return EXIT_SUCCESS;
+}
+#endif
+
 #if ENABLE_HUSH_JOB
 static int FAST_FUNC builtin_jobs(char **argv UNUSED_PARAM)
 {
@@ -8467,6 +8702,27 @@ static int FAST_FUNC builtin_pwd(char **argv UNUSED_PARAM)
        return EXIT_SUCCESS;
 }
 
+/* Interruptibility of read builtin in bash
+ * (tested on bash-4.2.8 by sending signals (not by ^C)):
+ *
+ * Empty trap makes read ignore corresponding signal, for any signal.
+ *
+ * SIGINT:
+ * - terminates non-interactive shell;
+ * - interrupts read in interactive shell;
+ * if it has non-empty trap:
+ * - executes trap and returns to command prompt in interactive shell;
+ * - executes trap and returns to read in non-interactive shell;
+ * SIGTERM:
+ * - is ignored (does not interrupt) read in interactive shell;
+ * - terminates non-interactive shell;
+ * if it has non-empty trap:
+ * - executes trap and returns to read;
+ * SIGHUP:
+ * - terminates shell (regardless of interactivity);
+ * if it has non-empty trap:
+ * - executes trap and returns to read;
+ */
 static int FAST_FUNC builtin_read(char **argv)
 {
        const char *r;
@@ -8474,6 +8730,7 @@ static int FAST_FUNC builtin_read(char **argv)
        char *opt_p = NULL;
        char *opt_t = NULL;
        char *opt_u = NULL;
+       const char *ifs;
        int read_flags;
 
        /* "!": do not abort on errors.
@@ -8483,10 +8740,12 @@ static int FAST_FUNC builtin_read(char **argv)
        if (read_flags == (uint32_t)-1)
                return EXIT_FAILURE;
        argv += optind;
+       ifs = get_local_var_value("IFS"); /* can be NULL */
 
+ again:
        r = shell_builtin_read(set_local_var_from_halves,
                argv,
-               get_local_var_value("IFS"), /* can be NULL */
+               ifs,
                read_flags,
                opt_n,
                opt_p,
@@ -8494,6 +8753,12 @@ static int FAST_FUNC builtin_read(char **argv)
                opt_u
        );
 
+       if ((uintptr_t)r == 1 && errno == EINTR) {
+               unsigned sig = check_and_run_traps();
+               if (sig && sig != SIGINT)
+                       goto again;
+       }
+
        if ((uintptr_t)r > 1) {
                bb_error_msg("%s", r);
                r = (char*)(uintptr_t)1;
@@ -8631,6 +8896,9 @@ static int FAST_FUNC builtin_source(char **argv)
        free(arg_path);
        if (!input) {
                /* bb_perror_msg("%s", *argv); - done by fopen_or_warn */
+               /* POSIX: non-interactive shell should abort here,
+                * not merely fail. So far no one complained :)
+                */
                return EXIT_FAILURE;
        }
        close_on_exec_on(fileno(input));
@@ -8640,12 +8908,14 @@ static int FAST_FUNC builtin_source(char **argv)
        /* "we are inside sourced file, ok to use return" */
        G.flag_return_in_progress = -1;
 #endif
-       save_and_replace_G_args(&sv, argv);
+       if (argv[1])
+               save_and_replace_G_args(&sv, argv);
 
        parse_and_run_file(input);
        fclose(input);
 
-       restore_G_args(&sv, argv);
+       if (argv[1])
+               restore_G_args(&sv, argv);
 #if ENABLE_HUSH_FUNCTIONS
        G.flag_return_in_progress = sv_flg;
 #endif
@@ -8726,7 +8996,7 @@ static int FAST_FUNC builtin_unset(char **argv)
 static int FAST_FUNC builtin_wait(char **argv)
 {
        int ret = EXIT_SUCCESS;
-       int status, sig;
+       int status;
 
        argv = skip_dash_dash(argv);
        if (argv[0] == NULL) {
@@ -8746,25 +9016,53 @@ static int FAST_FUNC builtin_wait(char **argv)
                 * ^C <-- after ~4 sec from keyboard
                 * $
                 */
-               sigaddset(&G.blocked_set, SIGCHLD);
-               sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
                while (1) {
-                       checkjobs(NULL);
-                       if (errno == ECHILD)
+                       int sig;
+                       sigset_t oldset, allsigs;
+
+                       /* waitpid is not interruptible by SA_RESTARTed
+                        * signals which we use. Thus, this ugly dance:
+                        */
+
+                       /* Make sure possible SIGCHLD is stored in kernel's
+                        * pending signal mask before we call waitpid.
+                        * Or else we may race with SIGCHLD, lose it,
+                        * and get stuck in sigwaitinfo...
+                        */
+                       sigfillset(&allsigs);
+                       sigprocmask(SIG_SETMASK, &allsigs, &oldset);
+
+                       if (!sigisemptyset(&G.pending_set)) {
+                               /* Crap! we raced with some signal! */
+                       //      sig = 0;
+                               goto restore;
+                       }
+
+                       checkjobs(NULL); /* waitpid(WNOHANG) inside */
+                       if (errno == ECHILD) {
+                               sigprocmask(SIG_SETMASK, &oldset, NULL);
+                               break;
+                       }
+
+                       /* Wait for SIGCHLD or any other signal */
+                       //sig = sigwaitinfo(&allsigs, NULL);
+                       /* It is vitally important for sigsuspend that SIGCHLD has non-DFL handler! */
+                       /* Note: sigsuspend invokes signal handler */
+                       sigsuspend(&oldset);
+ restore:
+                       sigprocmask(SIG_SETMASK, &oldset, NULL);
+
+                       /* So, did we get a signal? */
+                       //if (sig > 0)
+                       //      raise(sig); /* run handler */
+                       sig = check_and_run_traps();
+                       if (sig /*&& sig != SIGCHLD - always true */) {
+                               /* see note 2 */
+                               ret = 128 + sig;
                                break;
-                       /* Wait for SIGCHLD or any other signal of interest */
-                       /* sigtimedwait with infinite timeout: */
-                       sig = sigwaitinfo(&G.blocked_set, NULL);
-                       if (sig > 0) {
-                               sig = check_and_run_traps(sig);
-                               if (sig && sig != SIGCHLD) { /* see note 2 */
-                                       ret = 128 + sig;
-                                       break;
-                               }
                        }
+                       /* SIGCHLD, or no signal, or ignored one, such as SIGQUIT. Repeat */
                }
-               sigdelset(&G.blocked_set, SIGCHLD);
-               sigprocmask(SIG_SETMASK, &G.blocked_set, NULL);
                return ret;
        }