unicode: check $LC_CTYPE too to detect Unicode mode
[platform/upstream/busybox.git] / shell / hush.c
index 81811cb..fc9b89b 100644 (file)
@@ -8,6 +8,8 @@
  * Copyright (C) 2000,2001  Larry Doolittle <larry@doolittle.boa.org>
  * Copyright (C) 2008,2009  Denys Vlasenko <vda.linux@googlemail.com>
  *
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ *
  * Credits:
  *      The parser routines proper are all original material, first
  *      written Dec 2000 and Jan 2001 by Larry Doolittle.  The
@@ -50,7 +52,6 @@
  *
  * Bash compat TODO:
  *      redirection of stdout+stderr: &> and >&
- *      brace expansion: one/{two,three,four}
  *      reserved words: function select
  *      advanced test: [[ ]]
  *      process substitution: <(list) and >(list)
@@ -63,7 +64,9 @@
  *          The EXPR is evaluated according to ARITHMETIC EVALUATION.
  *          This is exactly equivalent to let "EXPR".
  *      $[EXPR]: synonym for $((EXPR))
- *      export builtin should be special, its arguments are assignments
+ *
+ * Won't do:
+ *      In bash, export builtin is special, its arguments are assignments
  *          and therefore expansion of them should be "one-word" expansion:
  *              $ export i=`echo 'a  b'` # export has one arg: "i=a  b"
  *          compare with:
  *              aaa  bbb
  *              $ "export" i=`echo 'aaa  bbb'`; echo "$i"
  *              aaa
- *
- * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  */
-#include "busybox.h"  /* for APPLET_IS_NOFORK/NOEXEC */
-#include <malloc.h>   /* for malloc_trim */
+#if !(defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) \
+       || defined(__APPLE__) \
+    )
+# include <malloc.h>   /* for malloc_trim */
+#endif
 #include <glob.h>
 /* #include <dmalloc.h> */
 #if ENABLE_HUSH_CASE
 # include <fnmatch.h>
 #endif
 
+#include "busybox.h"  /* for APPLET_IS_NOFORK/NOEXEC */
+#include "unicode.h"
 #include "shell_common.h"
 #include "math.h"
 #include "match.h"
 # define PIPE_BUF 4096  /* amount of buffering in a pipe */
 #endif
 
-//applet:IF_HUSH(APPLET(hush, _BB_DIR_BIN, _BB_SUID_DROP))
-//applet:IF_MSH(APPLET(msh, _BB_DIR_BIN, _BB_SUID_DROP))
-//applet:IF_FEATURE_SH_IS_HUSH(APPLET_ODDNAME(sh, hush, _BB_DIR_BIN, _BB_SUID_DROP, sh))
-//applet:IF_FEATURE_BASH_IS_HUSH(APPLET_ODDNAME(bash, hush, _BB_DIR_BIN, _BB_SUID_DROP, bash))
-
-//kbuild:lib-$(CONFIG_HUSH) += hush.o match.o shell_common.o
-//kbuild:lib-$(CONFIG_HUSH_RANDOM_SUPPORT) += random.o
-
 //config:config HUSH
 //config:      bool "hush"
 //config:      default y
 //config:
 //config:        It will compile and work on no-mmu systems.
 //config:
-//config:        It does not handle select, aliases, brace expansion,
-//config:        tilde expansion, &>file and >&file redirection of stdout+stderr.
+//config:        It does not handle select, aliases, tilde expansion,
+//config:        &>file and >&file redirection of stdout+stderr.
 //config:
 //config:config HUSH_BASH_COMPAT
 //config:      bool "bash-compatible extensions"
 //config:      help
 //config:        Enable bash-compatible extensions.
 //config:
+//config:config HUSH_BRACE_EXPANSION
+//config:      bool "Brace expansion"
+//config:      default y
+//config:      depends on HUSH_BASH_COMPAT
+//config:      help
+//config:        Enable {abc,def} extension.
+//config:
 //config:config HUSH_HELP
 //config:      bool "help builtin"
 //config:      default y
 //config:        from stdin just like a shell script from a file.
 //config:        No prompt, no PS1/PS2 magic shell variables.
 //config:
+//config:config HUSH_SAVEHISTORY
+//config:      bool "Save command history to .hush_history"
+//config:      default y
+//config:      depends on HUSH_INTERACTIVE && FEATURE_EDITING_SAVEHISTORY
+//config:      help
+//config:        Enable history saving in hush.
+//config:
 //config:config HUSH_JOB
 //config:      bool "Job control"
 //config:      default y
 //config:        msh is deprecated and will be removed, please migrate to hush.
 //config:
 
-//usage:#define hush_trivial_usage NOUSAGE_STR
-//usage:#define hush_full_usage ""
-//usage:#define msh_trivial_usage NOUSAGE_STR
-//usage:#define msh_full_usage ""
+//applet:IF_HUSH(APPLET(hush, BB_DIR_BIN, BB_SUID_DROP))
+//applet:IF_MSH(APPLET(msh, BB_DIR_BIN, BB_SUID_DROP))
+//applet:IF_FEATURE_SH_IS_HUSH(APPLET_ODDNAME(sh, hush, BB_DIR_BIN, BB_SUID_DROP, sh))
+//applet:IF_FEATURE_BASH_IS_HUSH(APPLET_ODDNAME(bash, hush, BB_DIR_BIN, BB_SUID_DROP, bash))
+
+//kbuild:lib-$(CONFIG_HUSH) += hush.o match.o shell_common.o
+//kbuild:lib-$(CONFIG_HUSH_RANDOM_SUPPORT) += random.o
+
+/* -i (interactive) and -s (read stdin) are also accepted,
+ * but currently do nothing, therefore aren't shown in help.
+ * NOMMU-specific options are not meant to be used by users,
+ * therefore we don't show them either.
+ */
+//usage:#define hush_trivial_usage
+//usage:       "[-nxl] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]"
+//usage:#define hush_full_usage "\n\n"
+//usage:       "Unix shell interpreter"
+
+//usage:#define msh_trivial_usage hush_trivial_usage
+//usage:#define msh_full_usage hush_full_usage
+
+//usage:#if ENABLE_FEATURE_SH_IS_HUSH
+//usage:# define sh_trivial_usage hush_trivial_usage
+//usage:# define sh_full_usage    hush_full_usage
+//usage:#endif
+//usage:#if ENABLE_FEATURE_BASH_IS_HUSH
+//usage:# define bash_trivial_usage hush_trivial_usage
+//usage:# define bash_full_usage    hush_full_usage
+//usage:#endif
 
 
 /* Build knobs */
 # 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? */
@@ -349,7 +388,7 @@ typedef struct nommu_save_t {
 } nommu_save_t;
 #endif
 
-typedef enum reserved_style {
+enum {
        RES_NONE  = 0,
 #if ENABLE_HUSH_IF
        RES_IF    ,
@@ -378,16 +417,13 @@ typedef enum reserved_style {
 #endif
        RES_XXXX  ,
        RES_SNTX
-} reserved_style;
+};
 
 typedef struct o_string {
        char *data;
        int length; /* position where data is appended */
        int maxlen;
-       /* Protect newly added chars against globbing
-        * (by prepending \ to *, ?, [, \) */
-       smallint o_escape;
-       smallint o_glob;
+       int o_expflags;
        /* At least some part of the string was inside '' or "",
         * possibly empty one: word"", wo''rd etc. */
        smallint has_quoted_part;
@@ -395,25 +431,40 @@ typedef struct o_string {
        smallint o_assignment; /* 0:maybe, 1:yes, 2:no */
 } o_string;
 enum {
-       MAYBE_ASSIGNMENT = 0,
+       EXP_FLAG_SINGLEWORD     = 0x80, /* must be 0x80 */
+       EXP_FLAG_GLOB           = 0x2,
+       /* Protect newly added chars against globbing
+        * by prepending \ to *, ?, [, \ */
+       EXP_FLAG_ESC_GLOB_CHARS = 0x1,
+};
+enum {
+       MAYBE_ASSIGNMENT      = 0,
        DEFINITELY_ASSIGNMENT = 1,
-       NOT_ASSIGNMENT = 2,
-       WORD_IS_KEYWORD = 3, /* not assigment, but next word may be: "if v=xyz cmd;" */
+       NOT_ASSIGNMENT        = 2,
+       /* Not an assigment, but next word may be: "if v=xyz cmd;" */
+       WORD_IS_KEYWORD       = 3,
 };
 /* Used for initialization: o_string foo = NULL_O_STRING; */
 #define NULL_O_STRING { NULL }
 
-/* I can almost use ordinary FILE*.  Is open_memstream() universally
- * available?  Where is it documented? */
+#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 */
        char eof_flag; /* meaningless if ->p == NULL */
        char peek_buf[2];
 #if ENABLE_HUSH_INTERACTIVE
-       smallint promptme;
        smallint promptmode; /* 0: PS1, 1: PS2 */
 #endif
+       int last_char;
        FILE *file;
        int (*get) (struct in_str *) FAST_FUNC;
        int (*peek) (struct in_str *) FAST_FUNC;
@@ -471,28 +522,18 @@ 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
-
-/* used for "[[ EXPR ]]" */
 #if ENABLE_HUSH_BASH_COMPAT
+/* used for "[[ EXPR ]]" */
 # define CMD_SINGLEWORD_NOGLOB 2
 #endif
-
-/* used for "export noglob=* glob* a=`echo a b`" */
-//#define CMD_SINGLEWORD_NOGLOB_COND 3
-// It is hard to implement correctly, it adds significant amounts of tricky code,
-// and all this is only useful for really obscure export statements
-// almost nobody would use anyway. #ifdef CMD_SINGLEWORD_NOGLOB_COND
-// guards the code which implements it, but I have doubts it works
-// in all cases (especially with mixed globbed/non-globbed arguments)
-
 #if ENABLE_HUSH_FUNCTIONS
 # define CMD_FUNCDEF 3
 #endif
 
+       smalluint cmd_exitcode;
        /* if non-NULL, this "command" is { list }, ( list ), or a compound statement */
        struct pipe *group;
 #if !BB_MMU
@@ -528,7 +569,6 @@ struct command {
 #define IS_NULL_CMD(cmd) \
        (!(cmd)->group && !(cmd)->argv && !(cmd)->redirects)
 
-
 struct pipe {
        struct pipe *next;
        int num_cmds;               /* total number of commands in pipe */
@@ -623,6 +663,53 @@ struct function {
 #endif
 
 
+/* set -/+o OPT support. (TODO: make it optional)
+ * bash supports the following opts:
+ * allexport       off
+ * braceexpand     on
+ * emacs           on
+ * errexit         off
+ * errtrace        off
+ * functrace       off
+ * hashall         on
+ * histexpand      off
+ * history         on
+ * ignoreeof       off
+ * interactive-comments    on
+ * keyword         off
+ * monitor         on
+ * noclobber       off
+ * noexec          off
+ * noglob          off
+ * nolog           off
+ * notify          off
+ * nounset         off
+ * onecmd          off
+ * physical        off
+ * pipefail        off
+ * posix           off
+ * privileged      off
+ * verbose         off
+ * vi              off
+ * xtrace          off
+ */
+static const char o_opt_strings[] ALIGN1 =
+       "pipefail\0"
+       "noexec\0"
+#if ENABLE_HUSH_MODE_X
+       "xtrace\0"
+#endif
+       ;
+enum {
+       OPT_O_PIPEFAIL,
+       OPT_O_NOEXEC,
+#if ENABLE_HUSH_MODE_X
+       OPT_O_XTRACE,
+#endif
+       NUM_OPT_O
+};
+
+
 /* "Globals" within this file */
 /* Sorted roughly by size (smaller offsets == smaller code) */
 struct globals {
@@ -665,6 +752,12 @@ struct globals {
 #else
 # define G_saved_tty_pgrp 0
 #endif
+       char o_opt[NUM_OPT_O];
+#if ENABLE_HUSH_MODE_X
+# define G_x_mode (G.o_opt[OPT_O_XTRACE])
+#else
+# define G_x_mode 0
+#endif
        smallint flag_SIGINT;
 #if ENABLE_HUSH_LOOPS
        smallint flag_break_continue;
@@ -676,19 +769,11 @@ struct globals {
         */
        smallint flag_return_in_progress;
 #endif
-       smallint n_mode;
-#if ENABLE_HUSH_MODE_X
-       smallint x_mode;
-# define G_x_mode (G.x_mode)
-#else
-# define G_x_mode 0
-#endif
        smallint exiting; /* used to prevent EXIT trap recursion */
        /* These four support $?, $#, and $1 */
        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;
@@ -701,8 +786,7 @@ struct globals {
 #endif
        const char *ifs;
        const char *cwd;
-       struct variable *top_var; /* = &G.shell_ver (set in main()) */
-       struct variable shell_ver;
+       struct variable *top_var;
        char **expanded_assignments;
 #if ENABLE_HUSH_FUNCTIONS
        struct function *top_func;
@@ -717,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)
@@ -734,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)
 
 
@@ -751,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
@@ -820,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
@@ -862,7 +967,7 @@ static const struct built_in_command bltins2[] = {
  */
 #if HUSH_DEBUG
 /* prevent disasters with G.debug_indent < 0 */
-# define indent() fprintf(stderr, "%*s", (G.debug_indent * 2) & 0xff, "")
+# define indent() fdprintf(2, "%*s", (G.debug_indent * 2) & 0xff, "")
 # define debug_enter() (G.debug_indent++)
 # define debug_leave() (G.debug_indent--)
 #else
@@ -872,56 +977,56 @@ static const struct built_in_command bltins2[] = {
 #endif
 
 #ifndef debug_printf
-# define debug_printf(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define debug_printf(...) (indent(), fdprintf(2, __VA_ARGS__))
 #endif
 
 #ifndef debug_printf_parse
-# define debug_printf_parse(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define debug_printf_parse(...) (indent(), fdprintf(2, __VA_ARGS__))
 #endif
 
 #ifndef debug_printf_exec
-#define debug_printf_exec(...) (indent(), fprintf(stderr, __VA_ARGS__))
+#define debug_printf_exec(...) (indent(), fdprintf(2, __VA_ARGS__))
 #endif
 
 #ifndef debug_printf_env
-# define debug_printf_env(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define debug_printf_env(...) (indent(), fdprintf(2, __VA_ARGS__))
 #endif
 
 #ifndef debug_printf_jobs
-# define debug_printf_jobs(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define debug_printf_jobs(...) (indent(), fdprintf(2, __VA_ARGS__))
 # define DEBUG_JOBS 1
 #else
 # define DEBUG_JOBS 0
 #endif
 
 #ifndef debug_printf_expand
-# define debug_printf_expand(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define debug_printf_expand(...) (indent(), fdprintf(2, __VA_ARGS__))
 # define DEBUG_EXPAND 1
 #else
 # define DEBUG_EXPAND 0
 #endif
 
 #ifndef debug_printf_varexp
-# define debug_printf_varexp(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define debug_printf_varexp(...) (indent(), fdprintf(2, __VA_ARGS__))
 #endif
 
 #ifndef debug_printf_glob
-# define debug_printf_glob(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define debug_printf_glob(...) (indent(), fdprintf(2, __VA_ARGS__))
 # define DEBUG_GLOB 1
 #else
 # define DEBUG_GLOB 0
 #endif
 
 #ifndef debug_printf_list
-# define debug_printf_list(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define debug_printf_list(...) (indent(), fdprintf(2, __VA_ARGS__))
 #endif
 
 #ifndef debug_printf_subst
-# define debug_printf_subst(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define debug_printf_subst(...) (indent(), fdprintf(2, __VA_ARGS__))
 #endif
 
 #ifndef debug_printf_clean
-# define debug_printf_clean(...) (indent(), fprintf(stderr, __VA_ARGS__))
+# define debug_printf_clean(...) (indent(), fdprintf(2, __VA_ARGS__))
 # define DEBUG_CLEAN 1
 #else
 # define DEBUG_CLEAN 0
@@ -931,9 +1036,9 @@ static const struct built_in_command bltins2[] = {
 static void debug_print_strings(const char *prefix, char **vv)
 {
        indent();
-       fprintf(stderr, "%s:\n", prefix);
+       fdprintf(2, "%s:\n", prefix);
        while (*vv)
-               fprintf(stderr, " '%s'\n", *vv++);
+               fdprintf(2, " '%s'\n", *vv++);
 }
 #else
 # define debug_print_strings(prefix, vv) ((void)0)
@@ -1001,43 +1106,36 @@ static void die_if_script(unsigned lineno, const char *fmt, ...)
                xfunc_die();
 }
 
-static void syntax_error(unsigned lineno, const char *msg)
+static void syntax_error(unsigned lineno UNUSED_PARAM, const char *msg)
 {
        if (msg)
-               die_if_script(lineno, "syntax error: %s", msg);
+               bb_error_msg("syntax error: %s", msg);
        else
-               die_if_script(lineno, "syntax error", NULL);
+               bb_error_msg("syntax error");
 }
 
-static void syntax_error_at(unsigned lineno, const char *msg)
+static void syntax_error_at(unsigned lineno UNUSED_PARAM, const char *msg)
 {
-       die_if_script(lineno, "syntax error at '%s'", msg);
+       bb_error_msg("syntax error at '%s'", msg);
 }
 
-static void syntax_error_unterm_str(unsigned lineno, const char *s)
+static void syntax_error_unterm_str(unsigned lineno UNUSED_PARAM, const char *s)
 {
-       die_if_script(lineno, "syntax error: unterminated %s", s);
+       bb_error_msg("syntax error: unterminated %s", s);
 }
 
-/* It so happens that all such cases are totally fatal
- * even if shell is interactive: EOF while looking for closing
- * delimiter. There is nowhere to read stuff from after that,
- * it's EOF! The only choice is to terminate.
- */
-static void syntax_error_unterm_ch(unsigned lineno, char ch) NORETURN;
 static void syntax_error_unterm_ch(unsigned lineno, char ch)
 {
        char msg[2] = { ch, '\0' };
        syntax_error_unterm_str(lineno, msg);
-       xfunc_die();
 }
 
-static void syntax_error_unexpected_ch(unsigned lineno, int ch)
+static void syntax_error_unexpected_ch(unsigned lineno UNUSED_PARAM, int ch)
 {
        char msg[2];
        msg[0] = ch;
        msg[1] = '\0';
-       die_if_script(lineno, "syntax error: unexpected %s", ch == EOF ? "EOF" : msg);
+       bb_error_msg("syntax error: unexpected %s", ch == EOF ? "EOF" : msg);
 }
 
 #if HUSH_DEBUG < 2
@@ -1251,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
@@ -1264,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:
@@ -1287,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, of 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 rocorded 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
@@ -1294,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
 
@@ -1325,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)
@@ -1346,21 +1513,70 @@ 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[] = { NULL, G.traps[0], NULL };
-               G.traps[0] = NULL;
-               G.exiting = 1;
+               char *argv[3];
+               /* argv[0] is unused */
+               argv[1] = G.traps[0];
+               argv[2] = NULL;
+               G.exiting = 1; /* prevent EXIT trap recursion */
+               /* Note: G.traps[0] is not cleared!
+                * "trap" will still show it, if executed
+                * in the handler */
                builtin_eval(argv);
-               free(argv[1]);
        }
 
+#if ENABLE_FEATURE_CLEAN_UP
+       {
+               struct variable *cur_var;
+               if (G.cwd != bb_msg_unknown)
+                       free((char*)G.cwd);
+               cur_var = G.top_var;
+               while (cur_var) {
+                       struct variable *tmp = cur_var;
+                       if (!cur_var->max_len)
+                               free(cur_var->varstr);
+                       cur_var = cur_var->next;
+                       free(tmp);
+               }
+       }
+#endif
+
 #if ENABLE_HUSH_JOB
        fflush_all();
        sigexit(- (exitcode & 0xff));
@@ -1369,43 +1585,49 @@ static void hush_exit(int exitcode)
 #endif
 }
 
-static int check_and_run_traps(int sig)
+
+//TODO: return a mask of ALL handled sigs?
+static int check_and_run_traps(void)
 {
-       static const struct timespec zero_timespec;
-       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 */
-                               char *argv[] = { NULL, xstrdup(G.traps[sig]), NULL };
+                               smalluint save_rcode;
+                               char *argv[3];
+                               /* argv[0] is unused */
+                               argv[1] = G.traps[sig];
+                               argv[2] = NULL;
                                save_rcode = G.last_exitcode;
                                builtin_eval(argv);
-                               free(argv[1]);
                                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: {
@@ -1422,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;
                }
        }
@@ -1449,13 +1686,11 @@ static const char *get_cwd(int force)
 /*
  * Shell and environment variable support
  */
-static struct variable **get_ptr_to_local_var(const char *name)
+static struct variable **get_ptr_to_local_var(const char *name, unsigned len)
 {
        struct variable **pp;
        struct variable *cur;
-       int len;
 
-       len = strlen(name);
        pp = &G.top_var;
        while ((cur = *pp) != NULL) {
                if (strncmp(cur->varstr, name, len) == 0 && cur->varstr[len] == '=')
@@ -1465,21 +1700,13 @@ static struct variable **get_ptr_to_local_var(const char *name)
        return NULL;
 }
 
-static struct variable *get_local_var(const char *name)
-{
-       struct variable **pp = get_ptr_to_local_var(name);
-       if (pp)
-               return *pp;
-       return NULL;
-}
-
 static const char* FAST_FUNC get_local_var_value(const char *name)
 {
        struct variable **vpp;
+       unsigned len = strlen(name);
 
        if (G.expanded_assignments) {
                char **cpp = G.expanded_assignments;
-               int len = strlen(name);
                while (*cpp) {
                        char *cp = *cpp;
                        if (strncmp(cp, name, len) == 0 && cp[len] == '=')
@@ -1488,17 +1715,16 @@ static const char* FAST_FUNC get_local_var_value(const char *name)
                }
        }
 
-       vpp = get_ptr_to_local_var(name);
+       vpp = get_ptr_to_local_var(name, len);
        if (vpp)
-               return strchr((*vpp)->varstr, '=') + 1;
+               return (*vpp)->varstr + len + 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) {
+       if (strcmp(name, "RANDOM") == 0)
                return utoa(next_random(&G.random_gen));
-       }
 #endif
        return NULL;
 }
@@ -1687,24 +1913,6 @@ static void unset_vars(char **strings)
        free(strings);
 }
 
-#if ENABLE_SH_MATH_SUPPORT
-# define is_name(c)      ((c) == '_' || isalpha((unsigned char)(c)))
-# define is_in_name(c)   ((c) == '_' || isalnum((unsigned char)(c)))
-static char* FAST_FUNC endofname(const char *name)
-{
-       char *p;
-
-       p = (char *) name;
-       if (!is_name(*p))
-               return p;
-       while (*++p) {
-               if (!is_in_name(*p))
-                       break;
-       }
-       return p;
-}
-#endif
-
 static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val)
 {
        char *var = xasprintf("%s=%s", name, val);
@@ -1748,9 +1956,7 @@ static struct variable *set_vars_and_save_old(char **strings)
 
                eq = strchr(*s, '=');
                if (eq) {
-                       *eq = '\0';
-                       var_pp = get_ptr_to_local_var(*s);
-                       *eq = '=';
+                       var_pp = get_ptr_to_local_var(*s, eq - *s);
                        if (var_pp) {
                                /* Remove variable from global linked list */
                                var_p = *var_pp;
@@ -1776,6 +1982,7 @@ static int FAST_FUNC static_get(struct in_str *i)
        int ch = *i->p;
        if (ch != '\0') {
                i->p++;
+               i->last_char = ch;
                return ch;
        }
        return EOF;
@@ -1802,7 +2009,7 @@ static void cmdedit_update_prompt(void)
                G.PS2 = "> ";
 }
 
-static const charsetup_prompt_string(int promptmode)
+static const char *setup_prompt_string(int promptmode)
 {
        const char *prompt_str;
        debug_printf("setup_prompt_string %d ", promptmode);
@@ -1833,12 +2040,21 @@ static void get_user_input(struct in_str *i)
        /* Enable command line editing only while a command line
         * is actually being read */
        do {
+               /* Unicode support should be activated even if LANG is set
+                * _during_ shell execution, not only if it was set when
+                * shell was started. Therefore, re-check LANG every time:
+                */
+               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(prompt_str, G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1, G.line_input_state);
+               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 */
@@ -1848,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
@@ -1880,22 +2103,18 @@ static int FAST_FUNC file_get(struct in_str *i)
                /* need to double check i->file because we might be doing something
                 * more complicated by now, like sourcing or substituting. */
 #if ENABLE_HUSH_INTERACTIVE
-               if (G_interactive_fd && i->promptme && i->file == stdin) {
+               if (G_interactive_fd && i->file == stdin) {
                        do {
                                get_user_input(i);
                        } while (!*i->p); /* need non-empty line */
                        i->promptmode = 1; /* PS2 */
-                       i->promptme = 0;
                        goto take_cached;
                }
 #endif
                do ch = fgetc(i->file); while (ch == '\0');
        }
        debug_printf("file_get: got '%c' %d\n", ch, ch);
-#if ENABLE_HUSH_INTERACTIVE
-       if (ch == '\n')
-               i->promptme = 1;
-#endif
+       i->last_char = ch;
        return ch;
 }
 
@@ -1922,26 +2141,22 @@ static int FAST_FUNC file_peek(struct in_str *i)
 
 static void setup_file_in_str(struct in_str *i, FILE *f)
 {
+       memset(i, 0, sizeof(*i));
        i->peek = file_peek;
        i->get = file_get;
-#if ENABLE_HUSH_INTERACTIVE
-       i->promptme = 1;
-       i->promptmode = 0; /* PS1 */
-#endif
+       /* i->promptmode = 0; - PS1 (memset did it) */
        i->file = f;
-       i->p = NULL;
+       /* i->p = NULL; */
 }
 
 static void setup_string_in_str(struct in_str *i, const char *s)
 {
+       memset(i, 0, sizeof(*i));
        i->peek = static_peek;
        i->get = static_get;
-#if ENABLE_HUSH_INTERACTIVE
-       i->promptme = 1;
-       i->promptmode = 0; /* PS1 */
-#endif
+       /* i->promptmode = 0; - PS1 (memset did it) */
        i->p = s;
-       i->eof_flag = 0;
+       /* i->eof_flag = 0; */
 }
 
 
@@ -2014,21 +2229,8 @@ static void o_addstr_with_NUL(o_string *o, const char *str)
        o_addblock(o, str, strlen(str) + 1);
 }
 
-static void o_addblock_duplicate_backslash(o_string *o, const char *str, int len)
-{
-       while (len) {
-               o_addchr(o, *str);
-               if (*str == '\\') {
-                       o_addchr(o, '\\');
-               }
-               str++;
-               len--;
-       }
-}
-
-#undef HUSH_BRACE_EXP
 /*
- * HUSH_BRACE_EXP code needs corresponding quoting on variable expansion side.
+ * HUSH_BRACE_EXPANSION code needs corresponding quoting on variable expansion side.
  * Currently, "v='{q,w}'; echo $v" erroneously expands braces in $v.
  * Apparently, on unquoted $v bash still does globbing
  * ("v='*.txt'; echo $v" prints all .txt files),
@@ -2038,7 +2240,7 @@ static void o_addblock_duplicate_backslash(o_string *o, const char *str, int len
  * We have only second one.
  */
 
-#ifdef HUSH_BRACE_EXP
+#if ENABLE_HUSH_BRACE_EXPANSION
 # define MAYBE_BRACES "{}"
 #else
 # define MAYBE_BRACES ""
@@ -2066,7 +2268,9 @@ static void o_addqchr(o_string *o, int ch)
 static void o_addQchr(o_string *o, int ch)
 {
        int sz = 1;
-       if (o->o_escape && strchr("*?[\\" MAYBE_BRACES, ch)) {
+       if ((o->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)
+        && strchr("*?[\\" MAYBE_BRACES, ch)
+       ) {
                sz++;
                o->data[o->length] = '\\';
                o->length++;
@@ -2077,12 +2281,8 @@ static void o_addQchr(o_string *o, int ch)
        o->data[o->length] = '\0';
 }
 
-static void o_addQblock(o_string *o, const char *str, int len)
+static void o_addqblock(o_string *o, const char *str, int len)
 {
-       if (!o->o_escape) {
-               o_addblock(o, str, len);
-               return;
-       }
        while (len) {
                char ch;
                int sz;
@@ -2091,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 */
 
@@ -2105,8 +2305,17 @@ 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)
+{
+       if (!(o->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)) {
+               o_addblock(o, str, len);
+               return;
+       }
+       o_addqblock(o, str, len);
 }
 
 static void o_addQstr(o_string *o, const char *str)
@@ -2132,19 +2341,22 @@ static void debug_print_list(const char *prefix, o_string *o, int n)
        int i = 0;
 
        indent();
-       fprintf(stderr, "%s: list:%p n:%d string_start:%d length:%d maxlen:%d glob:%d quoted:%d escape:%d\n",
-                       prefix, list, n, string_start, o->length, o->maxlen, o->o_glob, o->has_quoted_part, o->o_escape);
+       fdprintf(2, "%s: list:%p n:%d string_start:%d length:%d maxlen:%d glob:%d quoted:%d escape:%d\n",
+                       prefix, list, n, string_start, o->length, o->maxlen,
+                       !!(o->o_expflags & EXP_FLAG_GLOB),
+                       o->has_quoted_part,
+                       !!(o->o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
        while (i < n) {
                indent();
-               fprintf(stderr, " list[%d]=%d '%s' %p\n", i, (int)list[i],
-                               o->data + (int)list[i] + string_start,
-                               o->data + (int)list[i] + string_start);
+               fdprintf(2, " list[%d]=%d '%s' %p\n", i, (int)(uintptr_t)list[i],
+                               o->data + (int)(uintptr_t)list[i] + string_start,
+                               o->data + (int)(uintptr_t)list[i] + string_start);
                i++;
        }
        if (n) {
-               const char *p = o->data + (int)list[n - 1] + string_start;
+               const char *p = o->data + (int)(uintptr_t)list[n - 1] + string_start;
                indent();
-               fprintf(stderr, " total_sz:%ld\n", (long)((p + strlen(p) + 1) - o->data));
+               fdprintf(2, " total_sz:%ld\n", (long)((p + strlen(p) + 1) - o->data));
        }
 }
 #else
@@ -2183,7 +2395,8 @@ static int o_save_ptr_helper(o_string *o, int n)
                                n, string_len, string_start);
                o->has_empty_slot = 0;
        }
-       list[n] = (char*)(ptrdiff_t)string_len;
+       o->has_quoted_part = 0;
+       list[n] = (char*)(uintptr_t)string_len;
        return n + 1;
 }
 
@@ -2193,10 +2406,10 @@ static int o_get_last_ptr(o_string *o, int n)
        char **list = (char**)o->data;
        int string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]);
 
-       return ((int)(ptrdiff_t)list[n-1]) + string_start;
+       return ((int)(uintptr_t)list[n-1]) + string_start;
 }
 
-#ifdef HUSH_BRACE_EXP
+#if ENABLE_HUSH_BRACE_EXPANSION
 /* There in a GNU extension, GLOB_BRACE, but it is not usable:
  * first, it processes even {a} (no commas), second,
  * I didn't manage to make it return strings when they don't match
@@ -2231,9 +2444,9 @@ static const char *next_brace_sub(const char *cp)
                        cp++;
                        continue;
                }
-                /*{*/ if ((*cp == '}' && depth-- == 0) || (*cp == ',' && depth == 0))
+               if ((*cp == '}' && depth-- == 0) || (*cp == ',' && depth == 0))
                        break;
-               if (*cp++ == '{') /*}*/
+               if (*cp++ == '{')
                        depth++;
        }
 
@@ -2255,7 +2468,7 @@ static int glob_brace(char *pattern, o_string *o, int n)
        while (1) {
                if (*begin == '\0')
                        goto simple_glob;
-               if (*begin == '{') /*}*/ {
+               if (*begin == '{') {
                        /* Find the first sub-pattern and at the same time
                         * find the rest after the closing brace */
                        next = next_brace_sub(begin);
@@ -2263,7 +2476,7 @@ static int glob_brace(char *pattern, o_string *o, int n)
                                /* An illegal expression */
                                goto simple_glob;
                        }
-                       /*{*/ if (*next == '}') {
+                       if (*next == '}') {
                                /* "{abc}" with no commas - illegal
                                 * brace expr, disregard and skip it */
                                begin = next + 1;
@@ -2280,7 +2493,7 @@ static int glob_brace(char *pattern, o_string *o, int n)
 
        /* Now find the end of the whole brace expression */
        rest = next;
-       /*{*/ while (*rest != '}') {
+       while (*rest != '}') {
                rest = next_brace_sub(rest);
                if (rest == NULL) {
                        /* An illegal expression */
@@ -2316,7 +2529,7 @@ static int glob_brace(char *pattern, o_string *o, int n)
                 * That's why we re-copy prefix every time (1st memcpy above).
                 */
                n = glob_brace(new_pattern_buf, o, n);
-               /*{*/ if (*next == '}') {
+               if (*next == '}') {
                        /* We saw the last entry */
                        break;
                }
@@ -2366,11 +2579,11 @@ static int glob_brace(char *pattern, o_string *o, int n)
 /* Performs globbing on last list[],
  * saving each result as a new list[].
  */
-static int o_glob(o_string *o, int n)
+static int perform_glob(o_string *o, int n)
 {
        char *pattern, *copy;
 
-       debug_printf_glob("start o_glob: n:%d o->data:%p\n", n, o->data);
+       debug_printf_glob("start perform_glob: n:%d o->data:%p\n", n, o->data);
        if (!o->data)
                return o_save_ptr_helper(o, n);
        pattern = o->data + o_get_last_ptr(o, n);
@@ -2388,11 +2601,11 @@ static int o_glob(o_string *o, int n)
        n = glob_brace(copy, o, n);
        free(copy);
        if (DEBUG_GLOB)
-               debug_print_list("o_glob returning", o, n);
+               debug_print_list("perform_glob returning", o, n);
        return n;
 }
 
-#else /* !HUSH_BRACE_EXP */
+#else /* !HUSH_BRACE_EXPANSION */
 
 /* Helper */
 static int glob_needed(const char *s)
@@ -2413,13 +2626,13 @@ static int glob_needed(const char *s)
 /* Performs globbing on last list[],
  * saving each result as a new list[].
  */
-static int o_glob(o_string *o, int n)
+static int perform_glob(o_string *o, int n)
 {
        glob_t globdata;
        int gr;
        char *pattern;
 
-       debug_printf_glob("start o_glob: n:%d o->data:%p\n", n, o->data);
+       debug_printf_glob("start perform_glob: n:%d o->data:%p\n", n, o->data);
        if (!o->data)
                return o_save_ptr_helper(o, n);
        pattern = o->data + o_get_last_ptr(o, n);
@@ -2465,22 +2678,22 @@ static int o_glob(o_string *o, int n)
        }
        globfree(&globdata);
        if (DEBUG_GLOB)
-               debug_print_list("o_glob returning", o, n);
+               debug_print_list("perform_glob returning", o, n);
        return n;
 }
 
-#endif /* !HUSH_BRACE_EXP */
+#endif /* !HUSH_BRACE_EXPANSION */
 
-/* If o->o_glob == 1, glob the string so far remembered.
+/* If o->o_expflags & EXP_FLAG_GLOB, glob the string so far remembered.
  * Otherwise, just finish current list[] and start new */
 static int o_save_ptr(o_string *o, int n)
 {
-       if (o->o_glob) { /* if globbing is requested */
+       if (o->o_expflags & EXP_FLAG_GLOB) {
                /* If o->has_empty_slot, list[n] was already globbed
                 * (if it was requested back then when it was filled)
                 * so don't do that again! */
                if (!o->has_empty_slot)
-                       return o_glob(o, n); /* o_save_ptr_helper is inside */
+                       return perform_glob(o, n); /* o_save_ptr_helper is inside */
        }
        return o_save_ptr_helper(o, n);
 }
@@ -2500,33 +2713,36 @@ static char **o_finalize_list(o_string *o, int n)
        list[--n] = NULL;
        while (n) {
                n--;
-               list[n] = o->data + (int)(ptrdiff_t)list[n] + string_start;
+               list[n] = o->data + (int)(uintptr_t)list[n] + string_start;
        }
        return list;
 }
 
-static void free_pipe_list(struct pipe *head);
+static void free_pipe_list(struct pipe *pi);
 
-/* Return code is the exit status of the pipe */
-static void free_pipe(struct pipe *pi)
+/* Returns pi->next - next pipe in the list */
+static struct pipe *free_pipe(struct pipe *pi)
 {
-       char **p;
-       struct command *command;
-       struct redir_struct *r, *rnext;
-       int a, i;
+       struct pipe *next;
+       int i;
 
-       if (pi->stopped_cmds > 0) /* why? */
-               return;
-       debug_printf_clean("run pipe: (pid %d)\n", getpid());
+       debug_printf_clean("free_pipe (pid %d)\n", getpid());
        for (i = 0; i < pi->num_cmds; i++) {
+               struct command *command;
+               struct redir_struct *r, *rnext;
+
                command = &pi->cmds[i];
                debug_printf_clean("  command %d:\n", i);
                if (command->argv) {
-                       for (a = 0, p = command->argv; *p; a++, p++) {
-                               debug_printf_clean("   argv[%d] = %s\n", a, *p);
+                       if (DEBUG_CLEAN) {
+                               int a;
+                               char **p;
+                               for (a = 0, p = command->argv; *p; a++, p++) {
+                                       debug_printf_clean("   argv[%d] = %s\n", a, *p);
+                               }
                        }
                        free_strings(command->argv);
-                       command->argv = NULL;
+                       //command->argv = NULL;
                }
                /* not "else if": on syntax error, we may have both! */
                if (command->group) {
@@ -2534,7 +2750,7 @@ static void free_pipe(struct pipe *pi)
                                        command->cmd_type);
                        free_pipe_list(command->group);
                        debug_printf_clean("   end group\n");
-                       command->group = NULL;
+                       //command->group = NULL;
                }
                /* else is crucial here.
                 * If group != NULL, child_func is meaningless */
@@ -2546,7 +2762,7 @@ static void free_pipe(struct pipe *pi)
 #endif
 #if !BB_MMU
                free(command->group_as_string);
-               command->group_as_string = NULL;
+               //command->group_as_string = NULL;
 #endif
                for (r = command->redirects; r; r = rnext) {
                        debug_printf_clean("   redirect %d%s",
@@ -2555,52 +2771,139 @@ static void free_pipe(struct pipe *pi)
                        if (r->rd_filename) {
                                debug_printf_clean(" fname:'%s'\n", r->rd_filename);
                                free(r->rd_filename);
-                               r->rd_filename = NULL;
+                               //r->rd_filename = NULL;
                        }
                        debug_printf_clean(" rd_dup:%d\n", r->rd_dup);
                        rnext = r->next;
                        free(r);
                }
-               command->redirects = NULL;
+               //command->redirects = NULL;
        }
        free(pi->cmds);   /* children are an array, they get freed all at once */
-       pi->cmds = NULL;
+       //pi->cmds = NULL;
 #if ENABLE_HUSH_JOB
        free(pi->cmdtext);
-       pi->cmdtext = NULL;
+       //pi->cmdtext = NULL;
 #endif
+
+       next = pi->next;
+       free(pi);
+       return next;
 }
 
-static void free_pipe_list(struct pipe *head)
+static void free_pipe_list(struct pipe *pi)
 {
-       struct pipe *pi, *next;
-
-       for (pi = head; pi; pi = next) {
+       while (pi) {
 #if HAS_KEYWORDS
-               debug_printf_clean(" pipe reserved word %d\n", pi->res_word);
+               debug_printf_clean("pipe reserved word %d\n", pi->res_word);
 #endif
-               free_pipe(pi);
                debug_printf_clean("pipe followup code %d\n", pi->followup);
-               next = pi->next;
-               /*pi->next = NULL;*/
-               free(pi);
+               pi = free_pipe(pi);
        }
 }
 
 
 /*** Parsing routines ***/
 
-static struct pipe *new_pipe(void)
+#ifndef debug_print_tree
+static void debug_print_tree(struct pipe *pi, int lvl)
 {
-       struct pipe *pi;
-       pi = xzalloc(sizeof(struct pipe));
-       /*pi->followup = 0; - deliberately invalid value */
-       /*pi->res_word = RES_NONE; - RES_NONE is 0 anyway */
-       return pi;
-}
+       static const char *const PIPE[] = {
+               [PIPE_SEQ] = "SEQ",
+               [PIPE_AND] = "AND",
+               [PIPE_OR ] = "OR" ,
+               [PIPE_BG ] = "BG" ,
+       };
+       static const char *RES[] = {
+               [RES_NONE ] = "NONE" ,
+# if ENABLE_HUSH_IF
+               [RES_IF   ] = "IF"   ,
+               [RES_THEN ] = "THEN" ,
+               [RES_ELIF ] = "ELIF" ,
+               [RES_ELSE ] = "ELSE" ,
+               [RES_FI   ] = "FI"   ,
+# 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
+               [RES_IN   ] = "IN"   ,
+# 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
+               [RES_XXXX ] = "XXXX" ,
+               [RES_SNTX ] = "SNTX" ,
+       };
+       static const char *const CMDTYPE[] = {
+               "{}",
+               "()",
+               "[noglob]",
+# if ENABLE_HUSH_FUNCTIONS
+               "func()",
+# endif
+       };
 
-/* Command (member of a pipe) is complete, or we start a new pipe
- * if ctx->command is NULL.
+       int pin, prn;
+
+       pin = 0;
+       while (pi) {
+               fdprintf(2, "%*spipe %d res_word=%s followup=%d %s\n", lvl*2, "",
+                               pin, RES[pi->res_word], pi->followup, PIPE[pi->followup]);
+               prn = 0;
+               while (prn < pi->num_cmds) {
+                       struct command *command = &pi->cmds[prn];
+                       char **argv = command->argv;
+
+                       fdprintf(2, "%*s cmd %d assignment_cnt:%d",
+                                       lvl*2, "", prn,
+                                       command->assignment_cnt);
+                       if (command->group) {
+                               fdprintf(2, " group %s: (argv=%p)%s%s\n",
+                                               CMDTYPE[command->cmd_type],
+                                               argv
+# if !BB_MMU
+                                               , " group_as_string:", command->group_as_string
+# else
+                                               , "", ""
+# endif
+                               );
+                               debug_print_tree(command->group, lvl+1);
+                               prn++;
+                               continue;
+                       }
+                       if (argv) while (*argv) {
+                               fdprintf(2, " '%s'", *argv);
+                               argv++;
+                       }
+                       fdprintf(2, "\n");
+                       prn++;
+               }
+               pi = pi->next;
+               pin++;
+       }
+}
+#endif /* debug_print_tree */
+
+static struct pipe *new_pipe(void)
+{
+       struct pipe *pi;
+       pi = xzalloc(sizeof(struct pipe));
+       /*pi->followup = 0; - deliberately invalid value */
+       /*pi->res_word = RES_NONE; - RES_NONE is 0 anyway */
+       return pi;
+}
+
+/* Command (member of a pipe) is complete, or we start a new pipe
+ * if ctx->command is NULL.
  * No errors possible here.
  */
 static int done_command(struct parse_context *ctx)
@@ -2750,29 +3053,29 @@ 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;
 
-       for (r = reserved_list; r < reserved_list + ARRAY_SIZE(reserved_list); r++) {
+       for (r = reserved_list; r < reserved_list + ARRAY_SIZE(reserved_list); r++) {
                if (strcmp(word->data, r->literal) == 0)
                        return r;
        }
@@ -2833,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;
@@ -2899,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
@@ -2930,22 +3222,14 @@ 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));
                                return (ctx->ctx_res_w == RES_SNTX);
                        }
-# ifdef CMD_SINGLEWORD_NOGLOB_COND
-                       if (strcmp(word->data, "export") == 0
-#  if ENABLE_HUSH_LOCAL
-                        || strcmp(word->data, "local") == 0
-#  endif
-                       ) {
-                               command->cmd_type = CMD_SINGLEWORD_NOGLOB_COND;
-                       } else
-# endif
 # if ENABLE_HUSH_BASH_COMPAT
                        if (strcmp(word->data, "[[") == 0) {
                                command->cmd_type = CMD_SINGLEWORD_NOGLOB;
@@ -2961,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)
@@ -2974,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);
@@ -3171,31 +3464,38 @@ static int redirect_opt_num(o_string *o)
 static char *fetch_till_str(o_string *as_string,
                struct in_str *input,
                const char *word,
-               int skip_tabs)
+               int heredoc_flags)
 {
        o_string heredoc = NULL_O_STRING;
-       int past_EOL = 0;
+       unsigned past_EOL;
+       int prev = 0; /* not \ */
        int ch;
 
        goto jump_in;
+
        while (1) {
                ch = i_getch(input);
-               nommu_addchr(as_string, ch);
-               if (ch == '\n') {
+               if (ch != EOF)
+                       nommu_addchr(as_string, ch);
+               if ((ch == '\n' || ch == EOF)
+                && ((heredoc_flags & HEREDOC_QUOTED) || prev != '\\')
+               ) {
                        if (strcmp(heredoc.data + past_EOL, word) == 0) {
                                heredoc.data[past_EOL] = '\0';
                                debug_printf_parse("parsed heredoc '%s'\n", heredoc.data);
                                return heredoc.data;
                        }
-                       do {
+                       while (ch == '\n') {
                                o_addchr(&heredoc, ch);
-                               past_EOL = heredoc.length;
+                               prev = ch;
  jump_in:
+                               past_EOL = heredoc.length;
                                do {
                                        ch = i_getch(input);
-                                       nommu_addchr(as_string, ch);
-                               } while (skip_tabs && ch == '\t');
-                       } while (ch == '\n');
+                                       if (ch != EOF)
+                                               nommu_addchr(as_string, ch);
+                               } while ((heredoc_flags & HEREDOC_SKIPTABS) && ch == '\t');
+                       }
                }
                if (ch == EOF) {
                        o_free_unsafe(&heredoc);
@@ -3203,6 +3503,11 @@ static char *fetch_till_str(o_string *as_string,
                }
                o_addchr(&heredoc, ch);
                nommu_addchr(as_string, ch);
+               if (prev == '\\' && ch == '\\')
+                       /* Correctly handle foo\\<eol> (not a line cont.) */
+                       prev = 0; /* not \ */
+               else
+                       prev = ch;
        }
 }
 
@@ -3232,7 +3537,7 @@ static int fetch_heredocs(int heredoc_cnt, struct parse_context *ctx, struct in_
                                        redir->rd_type = REDIRECT_HEREDOC2;
                                        /* redir->rd_dup is (ab)used to indicate <<- */
                                        p = fetch_till_str(&ctx->as_string, input,
-                                               redir->rd_filename, redir->rd_dup & HEREDOC_SKIPTABS);
+                                                       redir->rd_filename, redir->rd_dup);
                                        if (!p) {
                                                syntax_error("unexpected EOF in here document");
                                                return 1;
@@ -3379,39 +3684,40 @@ static int parse_group(o_string *dest, struct parse_context *ctx,
 
 #if ENABLE_HUSH_TICK || ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_DOLLAR_OPS
 /* Subroutines for copying $(...) and `...` things */
-static void add_till_backquote(o_string *dest, struct in_str *input);
+static int add_till_backquote(o_string *dest, struct in_str *input, int in_dquote);
 /* '...' */
-static void add_till_single_quote(o_string *dest, struct in_str *input)
+static int add_till_single_quote(o_string *dest, struct in_str *input)
 {
        while (1) {
                int ch = i_getch(input);
                if (ch == EOF) {
                        syntax_error_unterm_ch('\'');
-                       /*xfunc_die(); - redundant */
+                       return 0;
                }
                if (ch == '\'')
-                       return;
+                       return 1;
                o_addchr(dest, ch);
        }
 }
 /* "...\"...`..`...." - do we need to handle "...$(..)..." too? */
-static void add_till_double_quote(o_string *dest, struct in_str *input)
+static int add_till_double_quote(o_string *dest, struct in_str *input)
 {
        while (1) {
                int ch = i_getch(input);
                if (ch == EOF) {
                        syntax_error_unterm_ch('"');
-                       /*xfunc_die(); - redundant */
+                       return 0;
                }
                if (ch == '"')
-                       return;
+                       return 1;
                if (ch == '\\') {  /* \x. Copy both chars. */
                        o_addchr(dest, ch);
                        ch = i_getch(input);
                }
                o_addchr(dest, ch);
                if (ch == '`') {
-                       add_till_backquote(dest, input);
+                       if (!add_till_backquote(dest, input, /*in_dquote:*/ 1))
+                               return 0;
                        o_addchr(dest, ch);
                        continue;
                }
@@ -3432,26 +3738,26 @@ static void add_till_double_quote(o_string *dest, struct in_str *input)
  * Example                               Output
  * echo `echo '\'TEST\`echo ZZ\`BEST`    \TESTZZBEST
  */
-static void add_till_backquote(o_string *dest, struct in_str *input)
+static int add_till_backquote(o_string *dest, struct in_str *input, int in_dquote)
 {
        while (1) {
                int ch = i_getch(input);
-               if (ch == EOF) {
-                       syntax_error_unterm_ch('`');
-                       /*xfunc_die(); - redundant */
-               }
                if (ch == '`')
-                       return;
+                       return 1;
                if (ch == '\\') {
-                       /* \x. Copy both chars unless it is \` */
-                       int ch2 = i_getch(input);
-                       if (ch2 == EOF) {
-                               syntax_error_unterm_ch('`');
-                               /*xfunc_die(); - redundant */
+                       /* \x. Copy both unless it is \`, \$, \\ and maybe \" */
+                       ch = i_getch(input);
+                       if (ch != '`'
+                        && ch != '$'
+                        && ch != '\\'
+                        && (!in_dquote || ch != '"')
+                       ) {
+                               o_addchr(dest, '\\');
                        }
-                       if (ch2 != '`' && ch2 != '$' && ch2 != '\\')
-                               o_addchr(dest, ch);
-                       ch = ch2;
+               }
+               if (ch == EOF) {
+                       syntax_error_unterm_ch('`');
+                       return 0;
                }
                o_addchr(dest, ch);
        }
@@ -3487,7 +3793,7 @@ static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsign
                ch = i_getch(input);
                if (ch == EOF) {
                        syntax_error_unterm_ch(end_ch);
-                       /*xfunc_die(); - redundant */
+                       return 0;
                }
                if (ch == end_ch  IF_HUSH_BASH_COMPAT( || ch == end_char2)) {
                        if (!dbl)
@@ -3501,22 +3807,26 @@ static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsign
                o_addchr(dest, ch);
                if (ch == '(' || ch == '{') {
                        ch = (ch == '(' ? ')' : '}');
-                       add_till_closing_bracket(dest, input, ch);
+                       if (!add_till_closing_bracket(dest, input, ch))
+                               return 0;
                        o_addchr(dest, ch);
                        continue;
                }
                if (ch == '\'') {
-                       add_till_single_quote(dest, input);
+                       if (!add_till_single_quote(dest, input))
+                               return 0;
                        o_addchr(dest, ch);
                        continue;
                }
                if (ch == '"') {
-                       add_till_double_quote(dest, input);
+                       if (!add_till_double_quote(dest, input))
+                               return 0;
                        o_addchr(dest, ch);
                        continue;
                }
                if (ch == '`') {
-                       add_till_backquote(dest, input);
+                       if (!add_till_backquote(dest, input, /*in_dquote:*/ 0))
+                               return 0;
                        o_addchr(dest, ch);
                        continue;
                }
@@ -3525,7 +3835,7 @@ static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsign
                        ch = i_getch(input);
                        if (ch == EOF) {
                                syntax_error_unterm_ch(')');
-                               /*xfunc_die(); - redundant */
+                               return 0;
                        }
                        o_addchr(dest, ch);
                        continue;
@@ -3537,16 +3847,15 @@ static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsign
 
 /* Return code: 0 for OK, 1 for syntax error */
 #if BB_MMU
-#define parse_dollar(as_string, dest, input) \
-       parse_dollar(dest, input)
+#define parse_dollar(as_string, dest, input, quote_mask) \
+       parse_dollar(dest, input, quote_mask)
 #define as_string NULL
 #endif
 static int parse_dollar(o_string *as_string,
                o_string *dest,
-               struct in_str *input)
+               struct in_str *input, unsigned char quote_mask)
 {
        int ch = i_peek(input);  /* first character after the $ */
-       unsigned char quote_mask = dest->o_escape ? 0x80 : 0;
 
        debug_printf_parse("parse_dollar entered: ch='%c'\n", ch);
        if (isalpha(ch)) {
@@ -3588,17 +3897,19 @@ static int parse_dollar(o_string *as_string,
                nommu_addchr(as_string, ch);
 
                ch = i_getch(input); /* first char after '{' */
-               nommu_addchr(as_string, ch);
                /* It should be ${?}, or ${#var},
                 * or even ${?+subst} - operator acting on a special variable,
                 * or the beginning of variable name.
                 */
-               if (!strchr(_SPECIAL_VARS_STR, ch) && !isalnum(ch)) { /* not one of those */
+               if (ch == EOF
+                || (!strchr(_SPECIAL_VARS_STR, ch) && !isalnum(ch)) /* not one of those */
+               ) {
  bad_dollar_syntax:
                        syntax_error_unterm_str("${name}");
-                       debug_printf_parse("parse_dollar return 1: unterminated ${name}\n");
-                       return 1;
+                       debug_printf_parse("parse_dollar return 0: unterminated ${name}\n");
+                       return 0;
                }
+               nommu_addchr(as_string, ch);
                ch |= quote_mask;
 
                /* It's possible to just call add_till_closing_bracket() at this point.
@@ -3652,6 +3963,8 @@ static int parse_dollar(o_string *as_string,
                                        pos = dest->length;
 #if ENABLE_HUSH_DOLLAR_OPS
                                last_ch = add_till_closing_bracket(dest, input, end_ch);
+                               if (last_ch == 0) /* error? */
+                                       return 0;
 #else
 #error Simple code to only allow ${var} is not implemented
 #endif
@@ -3696,7 +4009,8 @@ static int parse_dollar(o_string *as_string,
                        o_addchr(dest, /*quote_mask |*/ '+');
                        if (!BB_MMU)
                                pos = dest->length;
-                       add_till_closing_bracket(dest, input, ')' | DOUBLE_CLOSE_CHAR_FLAG);
+                       if (!add_till_closing_bracket(dest, input, ')' | DOUBLE_CLOSE_CHAR_FLAG))
+                               return 0; /* error */
                        if (as_string) {
                                o_addstr(as_string, dest->data + pos);
                                o_addchr(as_string, ')');
@@ -3711,7 +4025,8 @@ static int parse_dollar(o_string *as_string,
                o_addchr(dest, quote_mask | '`');
                if (!BB_MMU)
                        pos = dest->length;
-               add_till_closing_bracket(dest, input, ')');
+               if (!add_till_closing_bracket(dest, input, ')'))
+                       return 0; /* error */
                if (as_string) {
                        o_addstr(as_string, dest->data + pos);
                        o_addchr(as_string, ')');
@@ -3738,21 +4053,40 @@ static int parse_dollar(o_string *as_string,
        default:
                o_addQchr(dest, '$');
        }
-       debug_printf_parse("parse_dollar return 0\n");
-       return 0;
+       debug_printf_parse("parse_dollar return 1 (ok)\n");
+       return 1;
 #undef as_string
 }
 
 #if BB_MMU
-#define parse_stream_dquoted(as_string, dest, input, dquote_end) \
-       parse_stream_dquoted(dest, input, dquote_end)
+# if ENABLE_HUSH_BASH_COMPAT
+#define encode_string(as_string, dest, input, dquote_end, process_bkslash) \
+       encode_string(dest, input, dquote_end, process_bkslash)
+# else
+/* only ${var/pattern/repl} (its pattern part) needs additional mode */
+#define encode_string(as_string, dest, input, dquote_end, process_bkslash) \
+       encode_string(dest, input, dquote_end)
+# endif
 #define as_string NULL
+
+#else /* !MMU */
+
+# if ENABLE_HUSH_BASH_COMPAT
+/* all parameters are needed, no macro tricks */
+# else
+#define encode_string(as_string, dest, input, dquote_end, process_bkslash) \
+       encode_string(as_string, dest, input, dquote_end)
+# endif
 #endif
-static int parse_stream_dquoted(o_string *as_string,
+static int encode_string(o_string *as_string,
                o_string *dest,
                struct in_str *input,
-               int dquote_end)
+               int dquote_end,
+               int process_bkslash)
 {
+#if !ENABLE_HUSH_BASH_COMPAT
+       const int process_bkslash = 1;
+#endif
        int ch;
        int next;
 
@@ -3761,23 +4095,21 @@ static int parse_stream_dquoted(o_string *as_string,
        if (ch != EOF)
                nommu_addchr(as_string, ch);
        if (ch == dquote_end) { /* may be only '"' or EOF */
-               if (dest->o_assignment == NOT_ASSIGNMENT)
-                       dest->o_escape ^= 1;
-               debug_printf_parse("parse_stream_dquoted return 0\n");
-               return 0;
+               debug_printf_parse("encode_string return 1 (ok)\n");
+               return 1;
        }
        /* note: can't move it above ch == dquote_end check! */
        if (ch == EOF) {
                syntax_error_unterm_ch('"');
-               /*xfunc_die(); - redundant */
+               return 0; /* error */
        }
        next = '\0';
        if (ch != '\n') {
                next = i_peek(input);
        }
        debug_printf_parse("\" ch=%c (%d) escape=%d\n",
-                                       ch, ch, dest->o_escape);
-       if (ch == '\\') {
+                       ch, ch, !!(dest->o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
+       if (process_bkslash && ch == '\\') {
                if (next == EOF) {
                        syntax_error("\\<eof>");
                        xfunc_die();
@@ -3787,24 +4119,23 @@ static int parse_stream_dquoted(o_string *as_string,
                 * only when followed by one of the following characters:
                 * $, `, ", \, or <newline>.  A double quote may be quoted
                 * within double quotes by preceding it with a backslash."
+                * NB: in (unquoted) heredoc, above does not apply to ",
+                * therefore we check for it by "next == dquote_end" cond.
                 */
-               if (strchr("$`\"\\\n", next) != NULL) {
-                       ch = i_getch(input);
-                       if (ch != '\n') {
-                               o_addqchr(dest, ch);
-                               nommu_addchr(as_string, ch);
-                       }
-               } else {
-                       o_addqchr(dest, '\\');
-                       nommu_addchr(as_string, '\\');
-               }
+               if (next == dquote_end || strchr("$`\\\n", next)) {
+                       ch = i_getch(input); /* eat next */
+                       if (ch == '\n')
+                               goto again; /* skip \<newline> */
+               } /* else: ch remains == '\\', and we double it below: */
+               o_addqchr(dest, ch); /* \c if c is a glob char, else just c */
+               nommu_addchr(as_string, ch);
                goto again;
        }
        if (ch == '$') {
-               if (parse_dollar(as_string, dest, input) != 0) {
-                       debug_printf_parse("parse_stream_dquoted return 1: "
-                                       "parse_dollar returned non-0\n");
-                       return 1;
+               if (!parse_dollar(as_string, dest, input, /*quote_mask:*/ 0x80)) {
+                       debug_printf_parse("encode_string return 0: "
+                                       "parse_dollar returned 0 (error)\n");
+                       return 0;
                }
                goto again;
        }
@@ -3813,20 +4144,14 @@ static int parse_stream_dquoted(o_string *as_string,
                //unsigned pos = dest->length;
                o_addchr(dest, SPECIAL_VAR_SYMBOL);
                o_addchr(dest, 0x80 | '`');
-               add_till_backquote(dest, input);
+               if (!add_till_backquote(dest, input, /*in_dquote:*/ dquote_end == '"'))
+                       return 0; /* error */
                o_addchr(dest, SPECIAL_VAR_SYMBOL);
                //debug_printf_subst("SUBST RES3 '%s'\n", dest->data + pos);
                goto again;
        }
 #endif
        o_addQchr(dest, ch);
-       if (ch == '='
-        && (dest->o_assignment == MAYBE_ASSIGNMENT
-           || dest->o_assignment == WORD_IS_KEYWORD)
-        && is_well_formed_var_name(dest->data, '=')
-       ) {
-               dest->o_assignment = DEFINITELY_ASSIGNMENT;
-       }
        goto again;
 #undef as_string
 }
@@ -3835,7 +4160,7 @@ static int parse_stream_dquoted(o_string *as_string,
  * Scan input until EOF or end_trigger char.
  * Return a list of pipes to execute, or NULL on EOF
  * or if end_trigger character is met.
- * On syntax error, exit is shell is not interactive,
+ * On syntax error, exit if shell is not interactive,
  * reset parsing machinery and start parsing anew,
  * or return ERR_PTR.
  */
@@ -3845,12 +4170,10 @@ static struct pipe *parse_stream(char **pstring,
 {
        struct parse_context ctx;
        o_string dest = NULL_O_STRING;
-       int is_in_dquote;
        int heredoc_cnt;
 
-       /* Double-quote state is handled in the state variable is_in_dquote.
-        * A single-quote triggers a bypass of the main loop until its mate is
-        * found.  When recursing, quote state is passed in via dest->o_escape.
+       /* Single-quote triggers a bypass of the main loop until its mate is
+        * found.  When recursing, quote state is passed in via dest->o_expflags.
         */
        debug_printf_parse("parse_stream entered, end_trigger='%c'\n",
                        end_trigger ? end_trigger : 'X');
@@ -3861,37 +4184,26 @@ static struct pipe *parse_stream(char **pstring,
        o_addchr(&dest, '\0');
        dest.length = 0;
 
-       G.ifs = get_local_var_value("IFS");
-       if (G.ifs == NULL)
-               G.ifs = defifs;
+       /* We used to separate words on $IFS here. This was wrong.
+        * $IFS is used only for word splitting when $var is expanded,
+        * here we should use blank chars as separators, not $IFS
+        */
 
- reset:
-#if ENABLE_HUSH_INTERACTIVE
-       input->promptmode = 0; /* PS1 */
-#endif
-       /* dest.o_assignment = MAYBE_ASSIGNMENT; - already is */
+       if (MAYBE_ASSIGNMENT != 0)
+               dest.o_assignment = MAYBE_ASSIGNMENT;
        initialize_context(&ctx);
-       is_in_dquote = 0;
        heredoc_cnt = 0;
        while (1) {
-               const char *is_ifs;
+               const char *is_blank;
                const char *is_special;
                int ch;
                int next;
                int redir_fd;
                redir_type redir_style;
 
-               if (is_in_dquote) {
-                       /* dest.has_quoted_part = 1; - already is (see below) */
-                       if (parse_stream_dquoted(&ctx.as_string, &dest, input, '"')) {
-                               goto parse_error;
-                       }
-                       /* We reached closing '"' */
-                       is_in_dquote = 0;
-               }
                ch = i_getch(input);
                debug_printf_parse(": ch=%c (%d) escape=%d\n",
-                                               ch, ch, dest.o_escape);
+                               ch, ch, !!(dest.o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
                if (ch == EOF) {
                        struct pipe *pi;
 
@@ -3902,8 +4214,8 @@ static struct pipe *parse_stream(char **pstring,
                        /* end_trigger == '}' case errors out earlier,
                         * checking only ')' */
                        if (end_trigger == ')') {
-                               syntax_error_unterm_ch('('); /* exits */
-                               /* goto parse_error; */
+                               syntax_error_unterm_ch('(');
+                               goto parse_error;
                        }
 
                        if (done_word(&dest, &ctx)) {
@@ -3916,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;
@@ -3944,20 +4256,20 @@ static struct pipe *parse_stream(char **pstring,
                if (ctx.command->argv /* word [word]{... - non-special */
                 || dest.length       /* word{... - non-special */
                 || dest.has_quoted_part     /* ""{... - non-special */
-                || (next != ';'            /* }; - special */
-                   && next != ')'          /* }) - special */
-                   && next != '&'          /* }& and }&& ... - special */
-                   && next != '|'          /* }|| ... - special */
-                   && !strchr(G.ifs, next) /* {word - non-special */
+                || (next != ';'             /* }; - special */
+                   && next != ')'           /* }) - special */
+                   && next != '&'           /* }& and }&& ... - special */
+                   && next != '|'           /* }|| ... - special */
+                   && !strchr(defifs, next) /* {word - non-special */
                    )
                ) {
                        /* They are not special, skip "{}" */
                        is_special += 2;
                }
                is_special = strchr(is_special, ch);
-               is_ifs = strchr(G.ifs, ch);
+               is_blank = strchr(defifs, ch);
 
-               if (!is_special && !is_ifs) { /* ordinary char */
+               if (!is_special && !is_blank) { /* ordinary char */
  ordinary_char:
                        o_addQchr(&dest, ch);
                        if ((dest.o_assignment == MAYBE_ASSIGNMENT
@@ -3966,24 +4278,46 @@ 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;
                }
 
-               if (is_ifs) {
+               if (is_blank) {
                        if (done_word(&dest, &ctx)) {
                                goto parse_error;
                        }
                        if (ch == '\n') {
-#if ENABLE_HUSH_CASE
-                               /* "case ... in <newline> word) ..." -
-                                * newlines are ignored (but ';' wouldn't be) */
-                               if (ctx.command->argv == NULL
-                                && ctx.ctx_res_w == RES_MATCH
+                               /* Is this a case when newline is simply ignored?
+                                * Some examples:
+                                * "cmd | <newline> cmd ..."
+                                * "case ... in <newline> word) ..."
+                                */
+                               if (IS_NULL_CMD(ctx.command)
+                                && dest.length == 0 && !dest.has_quoted_part
                                ) {
-                                       continue;
+                                       /* This newline can be ignored. But...
+                                        * Without check #1, interactive shell
+                                        * ignores even bare <newline>,
+                                        * and shows the continuation prompt:
+                                        * ps1_prompt$ <enter>
+                                        * ps2> _   <=== wrong, should be ps1
+                                        * Without check #2, "cmd & <newline>"
+                                        * is similarly mistreated.
+                                        * (BTW, this makes "cmd & cmd"
+                                        * and "cmd && cmd" non-orthogonal.
+                                        * Really, ask yourself, why
+                                        * "cmd && <newline>" doesn't start
+                                        * cmd but waits for more input?
+                                        * No reason...)
+                                        */
+                                       struct pipe *pi = ctx.list_head;
+                                       if (pi->num_cmds != 0       /* check #1 */
+                                        && pi->followup != PIPE_BG /* check #2 */
+                                       ) {
+                                               continue;
+                                       }
                                }
-#endif
                                /* Treat newline as a command separator. */
                                done_pipe(&ctx, PIPE_SEQ);
                                debug_printf_parse("heredoc_cnt:%d\n", heredoc_cnt);
@@ -3994,8 +4328,9 @@ 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_ifs) continue;
+                               /* note: if (is_blank) continue;
                                 * will still trigger for us */
                        }
                }
@@ -4043,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
@@ -4063,7 +4399,7 @@ static struct pipe *parse_stream(char **pstring,
                        }
                }
  skip_end_trigger:
-               if (is_ifs)
+               if (is_blank)
                        continue;
 
                /* Catch <, > before deciding whether this word is
@@ -4115,6 +4451,31 @@ static struct pipe *parse_stream(char **pstring,
                        if (parse_redirect(&ctx, redir_fd, redir_style, input))
                                goto parse_error;
                        continue; /* back to top of while (1) */
+               case '#':
+                       if (dest.length == 0 && !dest.has_quoted_part) {
+                               /* skip "#comment" */
+                               while (1) {
+                                       ch = i_peek(input);
+                                       if (ch == EOF || ch == '\n')
+                                               break;
+                                       i_getch(input);
+                                       /* note: we do not add it to &ctx.as_string */
+                               }
+                               nommu_addchr(&ctx.as_string, '\n');
+                               continue; /* back to top of while (1) */
+                       }
+                       break;
+               case '\\':
+                       if (next == '\n') {
+                               /* It's "\<newline>" */
+#if !BB_MMU
+                               /* Remove trailing '\' from ctx.as_string */
+                               ctx.as_string.data[--ctx.as_string.length] = '\0';
+#endif
+                               ch = i_getch(input); /* eat it */
+                               continue; /* back to top of while (1) */
+                       }
+                       break;
                }
 
                if (dest.o_assignment == MAYBE_ASSIGNMENT
@@ -4124,24 +4485,14 @@ 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 */
 
                switch (ch) {
-               case '#':
-                       if (dest.length == 0) {
-                               while (1) {
-                                       ch = i_peek(input);
-                                       if (ch == EOF || ch == '\n')
-                                               break;
-                                       i_getch(input);
-                                       /* note: we do not add it to &ctx.as_string */
-                               }
-                               nommu_addchr(&ctx.as_string, '\n');
-                       } else {
-                               o_addQchr(&dest, ch);
-                       }
+               case '#': /* non-comment #: "echo a#b" etc */
+                       o_addQchr(&dest, ch);
                        break;
                case '\\':
                        if (next == EOF) {
@@ -4149,57 +4500,63 @@ static struct pipe *parse_stream(char **pstring,
                                xfunc_die();
                        }
                        ch = i_getch(input);
-                       if (ch != '\n') {
-                               o_addchr(&dest, '\\');
-                               /*nommu_addchr(&ctx.as_string, '\\'); - already done */
-                               o_addchr(&dest, ch);
-                               nommu_addchr(&ctx.as_string, ch);
-                               /* Example: echo Hello \2>file
-                                * we need to know that word 2 is quoted */
-                               dest.has_quoted_part = 1;
-                       }
-#if !BB_MMU
-                       else {
-                               /* It's "\<newline>". Remove trailing '\' from ctx.as_string */
-                               ctx.as_string.data[--ctx.as_string.length] = '\0';
-                       }
-#endif
+                       /* note: ch != '\n' (that case does not reach this place) */
+                       o_addchr(&dest, '\\');
+                       /*nommu_addchr(&ctx.as_string, '\\'); - already done */
+                       o_addchr(&dest, ch);
+                       nommu_addchr(&ctx.as_string, ch);
+                       /* Example: echo Hello \2>file
+                        * we need to know that word 2 is quoted */
+                       dest.has_quoted_part = 1;
                        break;
                case '$':
-                       if (parse_dollar(&ctx.as_string, &dest, input) != 0) {
+                       if (!parse_dollar(&ctx.as_string, &dest, input, /*quote_mask:*/ 0)) {
                                debug_printf_parse("parse_stream parse error: "
-                                       "parse_dollar returned non-0\n");
+                                       "parse_dollar returned 0 (error)\n");
                                goto parse_error;
                        }
                        break;
                case '\'':
                        dest.has_quoted_part = 1;
-                       while (1) {
-                               ch = i_getch(input);
-                               if (ch == EOF) {
-                                       syntax_error_unterm_ch('\'');
-                                       /*xfunc_die(); - redundant */
+                       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;
-                       is_in_dquote ^= 1; /* invert */
+                       if (next == '"' && !ctx.pending_redirect)
+                               goto insert_empty_quoted_str_marker;
                        if (dest.o_assignment == NOT_ASSIGNMENT)
-                               dest.o_escape ^= 1;
+                               dest.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS;
+                       if (!encode_string(&ctx.as_string, &dest, input, '"', /*process_bkslash:*/ 1))
+                               goto parse_error;
+                       dest.o_expflags &= ~EXP_FLAG_ESC_GLOB_CHARS;
                        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;
-                       add_till_backquote(&dest, input);
+                       USE_FOR_NOMMU(pos = dest.length;)
+                       if (!add_till_backquote(&dest, input, /*in_dquote:*/ 0))
+                               goto parse_error;
 # if !BB_MMU
                        o_addstr(&ctx.as_string, dest.data + pos);
                        o_addchr(&ctx.as_string, '`');
@@ -4237,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)) {
@@ -4337,23 +4695,15 @@ static struct pipe *parse_stream(char **pstring,
                        }
                        IF_HAS_KEYWORDS(pctx = p2;)
                } while (HAS_KEYWORDS && pctx);
-               /* Free text, clear all dest fields */
+
                o_free(&dest);
-               /* If we are not in top-level parse, we return,
-                * our caller will propagate error.
-                */
-               if (end_trigger != ';') {
+               G.last_exitcode = 1;
 #if !BB_MMU
-                       if (pstring)
-                               *pstring = NULL;
+               if (pstring)
+                       *pstring = NULL;
 #endif
-                       debug_leave();
-                       return ERR_PTR;
-               }
-               /* Discard cached input, force prompt */
-               input->p = NULL;
-               IF_HUSH_INTERACTIVE(input->promptme = 1;)
-               goto reset;
+               debug_leave();
+               return ERR_PTR;
        }
 }
 
@@ -4361,8 +4711,15 @@ static struct pipe *parse_stream(char **pstring,
 /*** Execution routines ***/
 
 /* Expansion can recurse, need forward decls: */
-static char *expand_string_to_string(const char *str);
+#if !ENABLE_HUSH_BASH_COMPAT
+/* only ${var/pattern/repl} (its pattern part) needs additional mode */
+#define expand_string_to_string(str, do_unbackslash) \
+       expand_string_to_string(str)
+#endif
+static char *expand_string_to_string(const char *str, int do_unbackslash);
+#if ENABLE_HUSH_TICK
 static int process_command_subs(o_string *dest, const char *s);
+#endif
 
 /* expand_strvec_to_strvec() takes a list of strings, expands
  * all variable references within and returns a pointer to
@@ -4370,30 +4727,100 @@ static int process_command_subs(o_string *dest, const char *s);
  * of strings. (Think VAR="a b"; echo $VAR).
  * This new list is allocated as a single malloc block.
  * NULL-terminated list of char* pointers is at the beginning of it,
- * followed by strings themself.
+ * followed by strings themselves.
  * Caller can deallocate entire list by single free(list). */
 
+/* A horde of its helpers come first: */
+
+static void o_addblock_duplicate_backslash(o_string *o, const char *str, int len)
+{
+       while (--len >= 0) {
+               char c = *str++;
+
+#if ENABLE_HUSH_BRACE_EXPANSION
+               if (c == '{' || c == '}') {
+                       /* { -> \{, } -> \} */
+                       o_addchr(o, '\\');
+                       /* And now we want to add { or } and continue:
+                        *  o_addchr(o, c);
+                        *  continue;
+                        * luckily, just falling throught achieves this.
+                        */
+               }
+#endif
+               o_addchr(o, c);
+               if (c == '\\') {
+                       /* \z -> \\\z; \<eol> -> \\<eol> */
+                       o_addchr(o, '\\');
+                       if (len) {
+                               len--;
+                               o_addchr(o, '\\');
+                               o_addchr(o, *str++);
+                       }
+               }
+       }
+}
+
 /* 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) {
-                       if (output->o_escape || !output->o_glob)
-                               o_addQblock(output, str, word_len);
-                       else /* protect backslashes against globbing up :) */
+                       /* We have WORD_LEN leading non-IFS chars */
+                       if (!(output->o_expflags & EXP_FLAG_GLOB)) {
+                               o_addblock(output, str, word_len);
+                       } else {
+                               /* Protect backslashes against globbing up :)
+                                * Example: "v='\*'; echo b$v" prints "b\*"
+                                * (and does not try to glob on "*")
+                                */
                                o_addblock_duplicate_backslash(output, str, word_len);
+                               /*/ Why can't we do it easier? */
+                               /*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;
 }
@@ -4405,13 +4832,19 @@ static int expand_on_ifs(o_string *output, int n, const char *str)
  * Returns malloced string.
  * As an optimization, we return NULL if expansion is not needed.
  */
-static char *expand_pseudo_dquoted(const char *str)
+#if !ENABLE_HUSH_BASH_COMPAT
+/* only ${var/pattern/repl} (its pattern part) needs additional mode */
+#define encode_then_expand_string(str, process_bkslash, do_unbackslash) \
+       encode_then_expand_string(str)
+#endif
+static char *encode_then_expand_string(const char *str, int process_bkslash, int do_unbackslash)
 {
        char *exp_str;
        struct in_str input;
        o_string dest = NULL_O_STRING;
 
        if (!strchr(str, '$')
+        && !strchr(str, '\\')
 #if ENABLE_HUSH_TICK
         && !strchr(str, '`')
 #endif
@@ -4423,27 +4856,32 @@ static char *expand_pseudo_dquoted(const char *str)
         * echo $(($a + `echo 1`)) $((1 + $((2)) ))
         */
        setup_string_in_str(&input, str);
-       parse_stream_dquoted(NULL, &dest, &input, EOF);
+       encode_string(NULL, &dest, &input, EOF, process_bkslash);
+//TODO: error check (encode_string returns 0 on error)?
        //bb_error_msg("'%s' -> '%s'", str, dest.data);
-       exp_str = expand_string_to_string(dest.data);
+       exp_str = expand_string_to_string(dest.data, /*unbackslash:*/ do_unbackslash);
        //bb_error_msg("'%s' -> '%s'", dest.data, exp_str);
        o_free_unsafe(&dest);
        return exp_str;
 }
 
 #if ENABLE_SH_MATH_SUPPORT
-static arith_t expand_and_evaluate_arith(const char *arg, int *errcode_p)
+static arith_t expand_and_evaluate_arith(const char *arg, const char **errmsg_p)
 {
-       arith_eval_hooks_t hooks;
+       arith_state_t math_state;
        arith_t res;
        char *exp_str;
 
-       hooks.lookupvar = get_local_var_value;
-       hooks.setvar = set_local_var_from_halves;
-       hooks.endofname = endofname;
-       exp_str = expand_pseudo_dquoted(arg);
-       res = arith(exp_str ? exp_str : arg, errcode_p, &hooks);
+       math_state.lookupvar = get_local_var_value;
+       math_state.setvar = set_local_var_from_halves;
+       //math_state.endofname = endofname;
+       exp_str = encode_then_expand_string(arg, /*process_bkslash:*/ 1, /*unbackslash:*/ 1);
+       res = arith(&math_state, exp_str ? exp_str : arg);
        free(exp_str);
+       if (errmsg_p)
+               *errmsg_p = math_state.errmsg;
+       if (math_state.errmsg)
+               die_if_script(math_state.errmsg);
        return res;
 }
 #endif
@@ -4505,7 +4943,7 @@ static char *replace_pattern(char *val, const char *pattern, const char *repl, c
 /* Helper:
  * Handles <SPECIAL_VAR_SYMBOL>varname...<SPECIAL_VAR_SYMBOL> construct.
  */
-static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, char **pp, char first_ch)
+static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, char **pp)
 {
        const char *val = NULL;
        char *to_be_freed = NULL;
@@ -4516,19 +4954,23 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
        char exp_save = exp_save; /* for compiler */
        char *exp_saveptr; /* points to expansion operator */
        char *exp_word = exp_word; /* for compiler */
+       char arg0;
 
+       *p = '\0'; /* replace trailing SPECIAL_VAR_SYMBOL */
        var = arg;
-       *p = '\0';
        exp_saveptr = arg[1] ? strchr(VAR_ENCODED_SUBST_OPS, arg[1]) : NULL;
-       first_char = arg[0] = first_ch & 0x7f;
+       arg0 = arg[0];
+       first_char = arg[0] = arg0 & 0x7f;
        exp_op = 0;
 
-       if (first_char == '#' && arg[1] && !exp_saveptr) {
-               /* handle length expansion ${#var} */
+       if (first_char == '#'      /* ${#... */
+        && arg[1] && !exp_saveptr /* not ${#} and not ${#<op_char>...} */
+       ) {
+               /* It must be length operator: ${#var} */
                var++;
                exp_op = 'L';
        } else {
-               /* maybe handle parameter expansion */
+               /* Maybe handle parameter expansion */
                if (exp_saveptr /* if 2nd char is one of expansion operators */
                 && strchr(NUMERIC_SPECVARS_STR, first_char) /* 1st char is special variable */
                ) {
@@ -4543,8 +4985,9 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
                        exp_word = exp_saveptr + 1;
                        if (exp_op == ':') {
                                exp_op = *exp_word++;
+//TODO: try ${var:} and ${var:bogus} in non-bash config
                                if (ENABLE_HUSH_BASH_COMPAT
-                                && (exp_op == '\0' || !strchr(MINUS_PLUS_EQUAL_QUESTION, exp_op))
+                                && (!exp_op || !strchr(MINUS_PLUS_EQUAL_QUESTION, exp_op))
                                ) {
                                        /* oops... it's ${var:N[:M]}, not ${var:?xxx} or some such */
                                        exp_op = ':';
@@ -4555,9 +4998,9 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
                } /* else: it's not an expansion op, but bare ${var} */
        }
 
-       /* lookup the variable in question */
+       /* Look up the variable in question */
        if (isdigit(var[0])) {
-               /* parse_dollar() should have vetted var for us */
+               /* parse_dollar should have vetted var for us */
                int n = xatoi_positive(var);
                if (n < G.global_argc)
                        val = G.global_argv[n];
@@ -4598,37 +5041,55 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
                         * Then var's value is matched to it and matching part removed.
                         */
                        if (val && val[0]) {
+                               char *t;
                                char *exp_exp_word;
                                char *loc;
                                unsigned scan_flags = pick_scan(exp_op, *exp_word);
-                               if (exp_op == *exp_word)        /* ## or %% */
+                               if (exp_op == *exp_word)  /* ## or %% */
                                        exp_word++;
-//TODO: avoid xstrdup unless needed
-// (see HACK ALERT below)
-                               val = to_be_freed = xstrdup(val);
-                               exp_exp_word = expand_pseudo_dquoted(exp_word);
+                               exp_exp_word = encode_then_expand_string(exp_word, /*process_bkslash:*/ 1, /*unbackslash:*/ 1);
                                if (exp_exp_word)
                                        exp_word = exp_exp_word;
-                               loc = scan_and_match(to_be_freed, exp_word, scan_flags);
+                               /* HACK ALERT. We depend here on the fact that
+                                * G.global_argv and results of utoa and get_local_var_value
+                                * are actually in writable memory:
+                                * scan_and_match momentarily stores NULs there. */
+                               t = (char*)val;
+                               loc = scan_and_match(t, exp_word, scan_flags);
                                //bb_error_msg("op:%c str:'%s' pat:'%s' res:'%s'",
-                               //              exp_op, to_be_freed, exp_word, loc);
+                               //              exp_op, t, exp_word, loc);
                                free(exp_exp_word);
                                if (loc) { /* match was found */
                                        if (scan_flags & SCAN_MATCH_LEFT_HALF) /* #[#] */
-                                               val = loc;
+                                               val = loc; /* take right part */
                                        else /* %[%] */
-                                               *loc = '\0';
+                                               val = to_be_freed = xstrndup(val, loc - val); /* left */
                                }
                        }
                }
 #if ENABLE_HUSH_BASH_COMPAT
                else if (exp_op == '/' || exp_op == '\\') {
+                       /* It's ${var/[/]pattern[/repl]} thing.
+                        * Note that in encoded form it has TWO parts:
+                        * var/pattern<SPECIAL_VAR_SYMBOL>repl<SPECIAL_VAR_SYMBOL>
+                        * and if // is used, it is encoded as \:
+                        * var\pattern<SPECIAL_VAR_SYMBOL>repl<SPECIAL_VAR_SYMBOL>
+                        */
                        /* Empty variable always gives nothing: */
-                       // "v=''; echo ${v/*/w}" prints ""
+                       // "v=''; echo ${v/*/w}" prints "", not "w"
                        if (val && val[0]) {
-                               /* It's ${var/[/]pattern[/repl]} thing */
+                               /* pattern uses non-standard expansion.
+                                * repl should be unbackslashed and globbed
+                                * by the usual expansion rules:
+                                * >az; >bz;
+                                * v='a bz'; echo "${v/a*z/a*z}" prints "a*z"
+                                * v='a bz'; echo "${v/a*z/\z}"  prints "\z"
+                                * v='a bz'; echo ${v/a*z/a*z}   prints "az"
+                                * v='a bz'; echo ${v/a*z/\z}    prints "z"
+                                * (note that a*z _pattern_ is never globbed!)
+                                */
                                char *pattern, *repl, *t;
-                               pattern = expand_pseudo_dquoted(exp_word);
+                               pattern = encode_then_expand_string(exp_word, /*process_bkslash:*/ 0, /*unbackslash:*/ 0);
                                if (!pattern)
                                        pattern = xstrdup(exp_word);
                                debug_printf_varexp("pattern:'%s'->'%s'\n", exp_word, pattern);
@@ -4636,7 +5097,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
                                exp_word = p;
                                p = strchr(p, SPECIAL_VAR_SYMBOL);
                                *p = '\0';
-                               repl = expand_pseudo_dquoted(exp_word);
+                               repl = encode_then_expand_string(exp_word, /*process_bkslash:*/ arg0 & 0x80, /*unbackslash:*/ 1);
                                debug_printf_varexp("repl:'%s'->'%s'\n", exp_word, repl);
                                /* HACK ALERT. We depend here on the fact that
                                 * G.global_argv and results of utoa and get_local_var_value
@@ -4661,24 +5122,28 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
                         * var:N<SPECIAL_VAR_SYMBOL>M<SPECIAL_VAR_SYMBOL>
                         */
                        arith_t beg, len;
-                       int errcode = 0;
+                       const char *errmsg;
 
-                       beg = expand_and_evaluate_arith(exp_word, &errcode);
+                       beg = expand_and_evaluate_arith(exp_word, &errmsg);
+                       if (errmsg)
+                               goto arith_err;
                        debug_printf_varexp("beg:'%s'=%lld\n", exp_word, (long long)beg);
                        *p++ = SPECIAL_VAR_SYMBOL;
                        exp_word = p;
                        p = strchr(p, SPECIAL_VAR_SYMBOL);
                        *p = '\0';
-                       len = expand_and_evaluate_arith(exp_word, &errcode);
+                       len = expand_and_evaluate_arith(exp_word, &errmsg);
+                       if (errmsg)
+                               goto arith_err;
                        debug_printf_varexp("len:'%s'=%lld\n", exp_word, (long long)len);
-
-                       if (errcode >= 0 && len >= 0) { /* bash compat: len < 0 is illegal */
+                       if (len >= 0) { /* bash compat: len < 0 is illegal */
                                if (beg < 0) /* bash compat */
                                        beg = 0;
                                debug_printf_varexp("from val:'%s'\n", val);
-                               if (len == 0 || !val || beg >= strlen(val))
-                                       val = "";
-                               else {
+                               if (len == 0 || !val || beg >= strlen(val)) {
+ arith_err:
+                                       val = NULL;
+                               } else {
                                        /* Paranoia. What if user entered 9999999999999
                                         * which fits in arith_t but not int? */
                                        if (len >= INT_MAX)
@@ -4690,7 +5155,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
 #endif
                        {
                                die_if_script("malformed ${%s:...}", var);
-                               val = "";
+                               val = NULL;
                        }
                } else { /* one of "-=+?" */
                        /* Standard-mandated substitution ops:
@@ -4721,7 +5186,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
                        debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op,
                                        (exp_save == ':') ? "true" : "false", use_word);
                        if (use_word) {
-                               to_be_freed = expand_pseudo_dquoted(exp_word);
+                               to_be_freed = encode_then_expand_string(exp_word, /*process_bkslash:*/ 1, /*unbackslash:*/ 1);
                                if (to_be_freed)
                                        exp_word = to_be_freed;
                                if (exp_op == '?') {
@@ -4752,7 +5217,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
                *exp_saveptr = exp_save;
        } /* if (exp_op) */
 
-       arg[0] = first_ch;
+       arg[0] = arg0;
 
        *pp = p;
        *to_be_freed_pp = to_be_freed;
@@ -4764,25 +5229,23 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
  * 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 NOINLINE 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)
 {
-       /* or_mask is either 0 (normal case) or 0x80 -
+       /* output->o_expflags & EXP_FLAG_SINGLEWORD (0x80) if we are in
         * expansion of right-hand side of assignment == 1-element expand.
-        * It will also do no globbing, and thus we must not backslash-quote!
         */
-       char ored_ch;
+       char cant_be_null = 0; /* only bit 0x80 matters */
+       int ended_in_ifs = 0;  /* did last unquoted expansion end with IFS chars? */
        char *p;
 
-       ored_ch = 0;
-
-       debug_printf_expand("expand_vars_to_list: arg:'%s' or_mask:%x\n", arg, or_mask);
+       debug_printf_expand("expand_vars_to_list: arg:'%s' singleword:%x\n", arg,
+                       !!(output->o_expflags & EXP_FLAG_SINGLEWORD));
        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);
 
        while ((p = strchr(arg, SPECIAL_VAR_SYMBOL)) != NULL) {
                char first_ch;
-               int i;
                char *to_be_freed = NULL;
                const char *val = NULL;
 #if ENABLE_HUSH_TICK
@@ -4791,31 +5254,42 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
 #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;
                p = strchr(p, SPECIAL_VAR_SYMBOL);
 
-               first_ch = arg[0] | or_mask; /* forced to "quoted" if or_mask = 0x80 */
-               /* "$@" is special. Even if quoted, it can still
-                * expand to nothing (not even an empty string) */
+               /* Fetch special var name (if it is indeed one of them)
+                * and quote bit, force the bit on if singleword expansion -
+                * important for not getting v=$@ expand to many words. */
+               first_ch = arg[0] | (output->o_expflags & EXP_FLAG_SINGLEWORD);
+
+               /* Is this variable quoted and thus expansion can't be null?
+                * "$@" is special. Even if quoted, it can still
+                * expand to nothing (not even an empty string),
+                * thus it is excluded. */
                if ((first_ch & 0x7f) != '@')
-                       ored_ch |= first_ch;
+                       cant_be_null |= first_ch;
 
                switch (first_ch & 0x7f) {
                /* Highest bit in first_ch indicates that var is double-quoted */
                case '*':
-               case '@':
-                       i = 1;
-                       if (!G.global_argv[i])
+               case '@': {
+                       int i;
+                       if (!G.global_argv[1])
                                break;
-                       ored_ch |= first_ch; /* do it for "$@" _now_, when we know it's not empty */
+                       i = 1;
+                       cant_be_null |= first_ch; /* do it for "$@" _now_, when we know it's not empty */
                        if (!(first_ch & 0x80)) { /* unquoted $* or $@ */
-                               smallint sv = output->o_escape;
-                               /* unquoted var's contents should be globbed, so don't escape */
-                               output->o_escape = 0;
                                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:
@@ -4826,11 +5300,12 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
                                                debug_print_list("expand_vars_to_list[3]", output, n);
                                        }
                                }
-                               output->o_escape = sv;
                        } else
-                       /* If or_mask is nonzero, we handle assignment 'a=....$@.....'
+                       /* If EXP_FLAG_SINGLEWORD, we handle assignment 'a=....$@.....'
                         * and in this case should treat it like '$*' - see 'else...' below */
-                       if (first_ch == ('@'|0x80) && !or_mask) { /* quoted $@ */
+                       if (first_ch == ('@'|0x80)  /* quoted $@ */
+                        && !(output->o_expflags & EXP_FLAG_SINGLEWORD) /* not v="$@" case */
+                       ) {
                                while (1) {
                                        o_addQstr(output, G.global_argv[i]);
                                        if (++i >= G.global_argc)
@@ -4839,7 +5314,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
                                        debug_print_list("expand_vars_to_list[4]", output, n);
                                        n = o_save_ptr(output, n);
                                }
-                       } else { /* quoted $*: add as one word */
+                       } else { /* quoted $* (or v="$@" case): add as one word */
                                while (1) {
                                        o_addQstr(output, G.global_argv[i]);
                                        if (!G.global_argv[++i])
@@ -4847,16 +5322,19 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
                                        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++;
-                       ored_ch = 0x80;
+                       cant_be_null = 0x80;
                        break;
 #if ENABLE_HUSH_TICK
                case '`': /* <SPECIAL_VAR_SYMBOL>`cmd<SPECIAL_VAR_SYMBOL> */
-                       *p = '\0';
+                       *p = '\0'; /* replace trailing <SPECIAL_VAR_SYMBOL> */
                        arg++;
                        /* Can't just stuff it into output o_string,
                         * expanded result may need to be globbed
@@ -4870,49 +5348,31 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
 #if ENABLE_SH_MATH_SUPPORT
                case '+': { /* <SPECIAL_VAR_SYMBOL>+cmd<SPECIAL_VAR_SYMBOL> */
                        arith_t res;
-                       int errcode;
 
                        arg++; /* skip '+' */
                        *p = '\0'; /* replace trailing <SPECIAL_VAR_SYMBOL> */
                        debug_printf_subst("ARITH '%s' first_ch %x\n", arg, first_ch);
-                       res = expand_and_evaluate_arith(arg, &errcode);
-
-                       if (errcode < 0) {
-                               const char *msg = "error in arithmetic";
-                               switch (errcode) {
-                               case -3:
-                                       msg = "exponent less than 0";
-                                       break;
-                               case -2:
-                                       msg = "divide by 0";
-                                       break;
-                               case -5:
-                                       msg = "expression recursion loop detected";
-                                       break;
-                               }
-                               die_if_script(msg);
-                       }
-                       debug_printf_subst("ARITH RES '"arith_t_fmt"'\n", res);
-                       sprintf(arith_buf, arith_t_fmt, res);
+                       res = expand_and_evaluate_arith(arg, NULL);
+                       debug_printf_subst("ARITH RES '"ARITH_FMT"'\n", res);
+                       sprintf(arith_buf, ARITH_FMT, res);
                        val = arith_buf;
                        break;
                }
 #endif
                default:
-                       val = expand_one_var(&to_be_freed, arg, &p, first_ch);
+                       val = expand_one_var(&to_be_freed, arg, &p);
  IF_HUSH_TICK(store_val:)
                        if (!(first_ch & 0x80)) { /* unquoted $VAR */
-                               debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val, output->o_escape);
+                               debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val,
+                                               !!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
                                if (val && val[0]) {
-                                       /* unquoted var's contents should be globbed, so don't escape */
-                                       smallint sv = output->o_escape;
-                                       output->o_escape = 0;
-                                       n = expand_on_ifs(output, n, val);
+                                       n = expand_on_ifs(&ended_in_ifs, output, n, val);
                                        val = NULL;
-                                       output->o_escape = sv;
                                }
                        } else { /* quoted $VAR, val will be appended below */
-                               debug_printf_expand("quoted '%s', output->o_escape:%d\n", val, output->o_escape);
+                               output->has_quoted_part = 1;
+                               debug_printf_expand("quoted '%s', output->o_escape:%d\n", val,
+                                               !!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
                        }
                        break;
 
@@ -4922,7 +5382,9 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
                        o_addQstr(output, val);
                }
                free(to_be_freed);
-               /* Do the check to avoid writing to a const string */
+
+               /* Restore NULL'ed SPECIAL_VAR_SYMBOL.
+                * Do the check to avoid writing to a const string. */
                if (*p != SPECIAL_VAR_SYMBOL)
                        *p = SPECIAL_VAR_SYMBOL;
 
@@ -4933,13 +5395,17 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
        } /* 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! */
                o_addstr_with_NUL(output, arg);
                debug_print_list("expand_vars_to_list[b]", output, n);
        } else if (output->length == o_get_last_ptr(output, n) /* expansion is empty */
-        && !(ored_ch & 0x80) /* and all vars were not quoted. */
+        && !(cant_be_null & 0x80) /* and all vars were not quoted. */
        ) {
                n--;
                /* allow to reuse list[n] later without re-growth */
@@ -4951,28 +5417,18 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
        return n;
 }
 
-enum {
-       EXPVAR_FLAG_GLOB = 0x200,
-       EXPVAR_FLAG_ESCAPE_VARS = 0x100,
-       EXPVAR_FLAG_SINGLEWORD = 0x80, /* must be 0x80 */
-};
-static char **expand_variables(char **argv, unsigned or_mask)
+static char **expand_variables(char **argv, unsigned expflags)
 {
        int n;
        char **list;
-       char **v;
        o_string output = NULL_O_STRING;
 
-       /* protect against globbing for "$var"? */
-       /* (unquoted $var will temporarily switch it off) */
-       output.o_escape = 1 & (or_mask / EXPVAR_FLAG_ESCAPE_VARS);
-       output.o_glob = 1 & (or_mask / EXPVAR_FLAG_GLOB);
+       output.o_expflags = expflags;
 
        n = 0;
-       v = argv;
-       while (*v) {
-               n = expand_vars_to_list(&output, n, *v, (unsigned char)or_mask);
-               v++;
+       while (*argv) {
+               n = expand_vars_to_list(&output, n, *argv);
+               argv++;
        }
        debug_print_list("expand_variables", &output, n);
 
@@ -4984,78 +5440,54 @@ static char **expand_variables(char **argv, unsigned or_mask)
 
 static char **expand_strvec_to_strvec(char **argv)
 {
-       return expand_variables(argv, EXPVAR_FLAG_GLOB | EXPVAR_FLAG_ESCAPE_VARS);
+       return expand_variables(argv, EXP_FLAG_GLOB | EXP_FLAG_ESC_GLOB_CHARS);
 }
 
 #if ENABLE_HUSH_BASH_COMPAT
 static char **expand_strvec_to_strvec_singleword_noglob(char **argv)
 {
-       return expand_variables(argv, EXPVAR_FLAG_SINGLEWORD);
+       return expand_variables(argv, EXP_FLAG_SINGLEWORD);
 }
 #endif
 
-#ifdef CMD_SINGLEWORD_NOGLOB_COND
-static char **expand_strvec_to_strvec_singleword_noglob_cond(char **argv)
+/* Used for expansion of right hand of assignments,
+ * $((...)), heredocs, variable espansion parts.
+ *
+ * NB: should NOT do globbing!
+ * "export v=/bin/c*; env | grep ^v=" outputs "v=/bin/c*"
+ */
+static char *expand_string_to_string(const char *str, int do_unbackslash)
 {
-       int n;
-       char **list;
-       char **v;
-       o_string output = NULL_O_STRING;
-
-       n = 0;
-       v = argv;
-       while (*v) {
-               int is_var = is_well_formed_var_name(*v, '=');
-               /* is_var * 0x80: singleword expansion for vars */
-               n = expand_vars_to_list(&output, n, *v, is_var * 0x80);
-
-               /* Subtle! expand_vars_to_list did not glob last word yet.
-                * It does this only when fed with further data.
-                * Therefore we set globbing flags AFTER it, not before:
-                */
-
-               /* if it is not recognizably abc=...; then: */
-               output.o_escape = !is_var; /* protect against globbing for "$var" */
-               /* (unquoted $var will temporarily switch it off) */
-               output.o_glob = !is_var; /* and indeed do globbing */
-               v++;
-       }
-       debug_print_list("expand_cond", &output, n);
-
-       /* output.data (malloced in one block) gets returned in "list" */
-       list = o_finalize_list(&output, n);
-       debug_print_strings("expand_cond[1]", list);
-       return list;
-}
+#if !ENABLE_HUSH_BASH_COMPAT
+       const int do_unbackslash = 1;
 #endif
-
-/* Used for expansion of right hand of assignments */
-/* NB: should NOT do globbing!
- * "export v=/bin/c*; env | grep ^v=" outputs "v=/bin/c*" */
-static char *expand_string_to_string(const char *str)
-{
        char *argv[2], **list;
 
+       debug_printf_expand("string_to_string<='%s'\n", str);
        /* This is generally an optimization, but it also
         * handles "", which otherwise trips over !list[0] check below.
         * (is this ever happens that we actually get str="" here?)
         */
        if (!strchr(str, SPECIAL_VAR_SYMBOL) && !strchr(str, '\\')) {
                //TODO: Can use on strings with \ too, just unbackslash() them?
-               debug_printf_expand("string_to_string(fast)='%s'\n", str);
+               debug_printf_expand("string_to_string(fast)=>'%s'\n", str);
                return xstrdup(str);
        }
 
        argv[0] = (char*)str;
        argv[1] = NULL;
-       list = expand_variables(argv, EXPVAR_FLAG_ESCAPE_VARS | EXPVAR_FLAG_SINGLEWORD);
+       list = expand_variables(argv, do_unbackslash
+                       ? EXP_FLAG_ESC_GLOB_CHARS | EXP_FLAG_SINGLEWORD
+                       : EXP_FLAG_SINGLEWORD
+       );
        if (HUSH_DEBUG)
                if (!list[0] || list[1])
                        bb_error_msg_and_die("BUG in varexp2");
        /* actually, just move string 2*sizeof(char*) bytes back */
        overlapping_strcpy((char*)list, list[0]);
-       unbackslash((char*)list);
-       debug_printf_expand("string_to_string='%s'\n", (char*)list);
+       if (do_unbackslash)
+               unbackslash((char*)list);
+       debug_printf_expand("string_to_string=>'%s'\n", (char*)list);
        return (char*)list;
 }
 
@@ -5064,7 +5496,7 @@ static char* expand_strvec_to_string(char **argv)
 {
        char **list;
 
-       list = expand_variables(argv, EXPVAR_FLAG_SINGLEWORD);
+       list = expand_variables(argv, EXP_FLAG_SINGLEWORD);
        /* Convert all NULs to spaces */
        if (list[0]) {
                int n = 1;
@@ -5090,13 +5522,32 @@ static char **expand_assignments(char **argv, int count)
        G.expanded_assignments = p = NULL;
        /* Expand assignments into one string each */
        for (i = 0; i < count; i++) {
-               G.expanded_assignments = p = add_string_to_strings(p, expand_string_to_string(argv[i]));
+               G.expanded_assignments = p = add_string_to_strings(p, expand_string_to_string(argv[i], /*unbackslash:*/ 1));
        }
        G.expanded_assignments = NULL;
        return p;
 }
 
-
+
+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,
@@ -5116,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 */
@@ -5267,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;
@@ -5286,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] == '/')
@@ -5317,9 +5756,29 @@ static void parse_and_run_stream(struct in_str *inp, int end_trigger)
        while (1) {
                struct pipe *pipe_list;
 
+#if ENABLE_HUSH_INTERACTIVE
+               if (end_trigger == ';')
+                       inp->promptmode = 0; /* PS1 */
+#endif
                pipe_list = parse_stream(NULL, inp, end_trigger);
-               if (!pipe_list) { /* EOF */
-                       if (empty)
+               if (!pipe_list || pipe_list == ERR_PTR) { /* EOF/error */
+                       /* If we are in "big" script
+                        * (not in `cmd` or something similar)...
+                        */
+                       if (pipe_list == ERR_PTR && end_trigger == ';') {
+                               /* Discard cached input (rest of line) */
+                               int ch = inp->last_char;
+                               while (ch != EOF && ch != '\n') {
+                                       //bb_error_msg("Discarded:'%c'", ch);
+                                       ch = i_getch(inp);
+                               }
+                               /* Force prompt */
+                               inp->p = NULL;
+                               /* This stream isn't empty */
+                               empty = 0;
+                               continue;
+                       }
+                       if (!pipe_list && empty)
                                G.last_exitcode = 0;
                        break;
                }
@@ -5327,6 +5786,10 @@ static void parse_and_run_stream(struct in_str *inp, int end_trigger)
                debug_printf_exec("parse_and_run_stream: run_and_free_list\n");
                run_and_free_list(pipe_list);
                empty = 0;
+#if ENABLE_HUSH_FUNCTIONS
+               if (G.flag_return_in_progress == 1)
+                       break;
+#endif
        }
 }
 
@@ -5499,7 +5962,7 @@ static void setup_heredoc(struct redir_struct *redir)
 
        expanded = NULL;
        if (!(redir->rd_dup & HEREDOC_QUOTED)) {
-               expanded = expand_pseudo_dquoted(heredoc);
+               expanded = encode_then_expand_string(heredoc, /*process_bkslash:*/ 1, /*unbackslash:*/ 1);
                if (expanded)
                        heredoc = expanded;
        }
@@ -5599,7 +6062,7 @@ static int setup_redirects(struct command *prog, int squirrel[])
                                continue;
                        }
                        mode = redir_table[redir->rd_type].mode;
-                       p = expand_string_to_string(redir->rd_filename);
+                       p = expand_string_to_string(redir->rd_filename, /*unbackslash:*/ 1);
                        openfd = open_or_warn(p, mode);
                        free(p);
                        if (openfd < 0) {
@@ -5677,7 +6140,7 @@ static char *find_in_path(const char *arg)
        return ret;
 }
 
-static const struct built_in_commandfind_builtin_helper(const char *name,
+static const struct built_in_command *find_builtin_helper(const char *name,
                const struct built_in_command *x,
                const struct built_in_command *end)
 {
@@ -5691,11 +6154,11 @@ static const struct built_in_command* find_builtin_helper(const char *name,
        }
        return NULL;
 }
-static const struct built_in_commandfind_builtin1(const char *name)
+static const struct built_in_command *find_builtin1(const char *name)
 {
        return find_builtin_helper(name, bltins1, &bltins1[ARRAY_SIZE(bltins1)]);
 }
-static const struct built_in_commandfind_builtin(const char *name)
+static const struct built_in_command *find_builtin(const char *name)
 {
        const struct built_in_command *x = find_builtin1(name);
        if (x)
@@ -5893,10 +6356,13 @@ static void exec_builtin(char ***to_free,
                char **argv)
 {
 #if BB_MMU
-       int rcode = x->b_function(argv);
+       int rcode;
+       fflush_all();
+       rcode = x->b_function(argv);
        fflush_all();
        _exit(rcode);
 #else
+       fflush_all();
        /* On NOMMU, we must never block!
         * Example: { sleep 99 | read line; } & echo Ok
         */
@@ -5913,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 */
@@ -6045,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 */
@@ -6191,15 +6661,13 @@ static void remove_bg_job(struct pipe *pi)
 static void delete_finished_bg_job(struct pipe *pi)
 {
        remove_bg_job(pi);
-       pi->stopped_cmds = 0;
        free_pipe(pi);
-       free(pi);
 }
 #endif /* JOB */
 
 /* Check to see if any processes have exited -- if they
  * have, figure out why and see if a job has completed */
-static int checkjobs(struct pipefg_pipe)
+static int checkjobs(struct pipe *fg_pipe)
 {
        int attributes;
        int status;
@@ -6279,48 +6747,58 @@ static int checkjobs(struct pipe* fg_pipe)
 #endif
                /* Were we asked to wait for fg pipe? */
                if (fg_pipe) {
-                       for (i = 0; i < fg_pipe->num_cmds; i++) {
+                       i = fg_pipe->num_cmds;
+                       while (--i >= 0) {
                                debug_printf_jobs("check pid %d\n", fg_pipe->cmds[i].pid);
                                if (fg_pipe->cmds[i].pid != childpid)
                                        continue;
                                if (dead) {
+                                       int ex;
                                        fg_pipe->cmds[i].pid = 0;
                                        fg_pipe->alive_cmds--;
-                                       if (i == fg_pipe->num_cmds - 1) {
-                                               /* last process gives overall exitstatus */
-                                               rcode = WEXITSTATUS(status);
-                                               /* bash prints killer signal's name for *last*
-                                                * process in pipe (prints just newline for SIGINT).
-                                                * Mimic this. Example: "sleep 5" + (^\ or kill -QUIT)
-                                                */
-                                               if (WIFSIGNALED(status)) {
-                                                       int sig = WTERMSIG(status);
-                                                       printf("%s\n", sig == SIGINT ? "" : get_signame(sig));
-                                                       /* TODO: MIPS has 128 sigs (1..128), what if sig==128 here?
-                                                        * Maybe we need to use sig | 128? */
-                                                       rcode = sig + 128;
-                                               }
-                                               IF_HAS_KEYWORDS(if (fg_pipe->pi_inverted) rcode = !rcode;)
+                                       ex = WEXITSTATUS(status);
+                                       /* bash prints killer signal's name for *last*
+                                        * process in pipe (prints just newline for SIGINT/SIGPIPE).
+                                        * Mimic this. Example: "sleep 5" + (^\ or kill -QUIT)
+                                        */
+                                       if (WIFSIGNALED(status)) {
+                                               int sig = WTERMSIG(status);
+                                               if (i == fg_pipe->num_cmds-1)
+                                                       /* TODO: use strsignal() instead for bash compat? but that's bloat... */
+                                                       printf("%s\n", sig == SIGINT || sig == SIGPIPE ? "" : get_signame(sig));
+                                               /* TODO: if (WCOREDUMP(status)) + " (core dumped)"; */
+                                               /* TODO: MIPS has 128 sigs (1..128), what if sig==128 here?
+                                                * Maybe we need to use sig | 128? */
+                                               ex = sig + 128;
                                        }
+                                       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",
                                                fg_pipe->alive_cmds, fg_pipe->stopped_cmds);
-                               if (fg_pipe->alive_cmds - fg_pipe->stopped_cmds <= 0) {
+                               if (fg_pipe->alive_cmds == fg_pipe->stopped_cmds) {
                                        /* All processes in fg pipe have exited or stopped */
+                                       i = fg_pipe->num_cmds;
+                                       while (--i >= 0) {
+                                               rcode = fg_pipe->cmds[i].cmd_exitcode;
+                                               /* usually last process gives overall exitstatus,
+                                                * but with "set -o pipefail", last *failed* process does */
+                                               if (G.o_opt[OPT_O_PIPEFAIL] == 0 || rcode != 0)
+                                                       break;
+                                       }
+                                       IF_HAS_KEYWORDS(if (fg_pipe->pi_inverted) rcode = !rcode;)
 /* Note: *non-interactive* bash does not continue if all processes in fg pipe
  * are stopped. Testcase: "cat | cat" in a script (not on command line!)
  * and "killall -STOP cat" */
                                        if (G_interactive_fd) {
 #if ENABLE_HUSH_JOB
-                                               if (fg_pipe->alive_cmds)
+                                               if (fg_pipe->alive_cmds != 0)
                                                        insert_bg_job(fg_pipe);
 #endif
                                                return rcode;
                                        }
-                                       if (!fg_pipe->alive_cmds)
+                                       if (fg_pipe->alive_cmds == 0)
                                                return rcode;
                                }
                                /* There are still running processes in the fg pipe */
@@ -6355,7 +6833,6 @@ static int checkjobs(struct pipe* fg_pipe)
                        }
                } else {
                        /* child stopped */
-                       pi->cmds[i].is_stopped = 1;
                        pi->stopped_cmds++;
                }
 #endif
@@ -6365,7 +6842,7 @@ static int checkjobs(struct pipe* fg_pipe)
 }
 
 #if ENABLE_HUSH_JOB
-static int checkjobs_and_fg_shell(struct pipefg_pipe)
+static int checkjobs_and_fg_shell(struct pipe *fg_pipe)
 {
        pid_t p;
        int rcode = checkjobs(fg_pipe);
@@ -6396,7 +6873,7 @@ static int checkjobs_and_fg_shell(struct pipe* fg_pipe)
  * cmd ; ...   { list } ; ...
  * cmd && ...  { list } && ...
  * cmd || ...  { list } || ...
- * If it is, then we can run cmd as a builtin, NOFORK [do we do this?],
+ * If it is, then we can run cmd as a builtin, NOFORK,
  * or (if SH_STANDALONE) an applet, and we can run the { list }
  * with run_list. If it isn't one of these, we fork and exec cmd.
  *
@@ -6406,7 +6883,7 @@ static int checkjobs_and_fg_shell(struct pipe* fg_pipe)
  * subshell:     ( list ) [&]
  */
 #if !ENABLE_HUSH_MODE_X
-#define redirect_and_varexp_helper(new_env_p, old_vars_p, command, squirrel, char argv_expanded) \
+#define redirect_and_varexp_helper(new_env_p, old_vars_p, command, squirrel, argv_expanded) \
        redirect_and_varexp_helper(new_env_p, old_vars_p, command, squirrel)
 #endif
 static int redirect_and_varexp_helper(char ***new_env_p,
@@ -6446,6 +6923,13 @@ static NOINLINE int run_pipe(struct pipe *pi)
        debug_printf_exec("run_pipe start: members:%d\n", pi->num_cmds);
        debug_enter();
 
+       /* Testcase: set -- q w e; (IFS='' echo "$*"; IFS=''; echo "$*"); echo "$*"
+        * Result should be 3 lines: q w e, qwe, q w e
+        */
+       G.ifs = get_local_var_value("IFS");
+       if (!G.ifs)
+               G.ifs = defifs;
+
        IF_HUSH_JOB(pi->pgrp = -1;)
        pi->stopped_cmds = 0;
        command = &pi->cmds[0];
@@ -6545,7 +7029,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
                        if (G_x_mode)
                                bb_putchar_stderr('+');
                        while (*argv) {
-                               char *p = expand_string_to_string(*argv);
+                               char *p = expand_string_to_string(*argv, /*unbackslash:*/ 1);
                                if (G_x_mode)
                                        fprintf(stderr, " %s", p);
                                debug_printf_exec("set shell var:'%s'->'%s'\n",
@@ -6571,18 +7055,12 @@ static NOINLINE int run_pipe(struct pipe *pi)
                }
 
                /* Expand the rest into (possibly) many strings each */
-               if (0) {}
 #if ENABLE_HUSH_BASH_COMPAT
-               else if (command->cmd_type == CMD_SINGLEWORD_NOGLOB) {
+               if (command->cmd_type == CMD_SINGLEWORD_NOGLOB) {
                        argv_expanded = expand_strvec_to_strvec_singleword_noglob(argv + command->assignment_cnt);
-               }
-#endif
-#ifdef CMD_SINGLEWORD_NOGLOB_COND
-               else if (command->cmd_type == CMD_SINGLEWORD_NOGLOB_COND) {
-                       argv_expanded = expand_strvec_to_strvec_singleword_noglob_cond(argv + command->assignment_cnt);
-               }
+               } else
 #endif
-               else {
+               {
                        argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt);
                }
 
@@ -6612,6 +7090,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
                                if (!funcp) {
                                        debug_printf_exec(": builtin '%s' '%s'...\n",
                                                x->b_cmd, argv_expanded[1]);
+                                       fflush_all();
                                        rcode = x->b_function(argv_expanded) & 0xff;
                                        fflush_all();
                                }
@@ -6644,7 +7123,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
                        return rcode;
                }
 
-               if (ENABLE_FEATURE_SH_STANDALONE) {
+               if (ENABLE_FEATURE_SH_NOFORK) {
                        int n = find_applet_by_name(argv_expanded[0]);
                        if (n >= 0 && APPLET_IS_NOFORK(n)) {
                                rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, squirrel, argv_expanded);
@@ -6733,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... */
@@ -6789,94 +7265,6 @@ static NOINLINE int run_pipe(struct pipe *pi)
        return -1;
 }
 
-#ifndef debug_print_tree
-static void debug_print_tree(struct pipe *pi, int lvl)
-{
-       static const char *const PIPE[] = {
-               [PIPE_SEQ] = "SEQ",
-               [PIPE_AND] = "AND",
-               [PIPE_OR ] = "OR" ,
-               [PIPE_BG ] = "BG" ,
-       };
-       static const char *RES[] = {
-               [RES_NONE ] = "NONE" ,
-# if ENABLE_HUSH_IF
-               [RES_IF   ] = "IF"   ,
-               [RES_THEN ] = "THEN" ,
-               [RES_ELIF ] = "ELIF" ,
-               [RES_ELSE ] = "ELSE" ,
-               [RES_FI   ] = "FI"   ,
-# 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
-               [RES_IN   ] = "IN"   ,
-# 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
-               [RES_XXXX ] = "XXXX" ,
-               [RES_SNTX ] = "SNTX" ,
-       };
-       static const char *const CMDTYPE[] = {
-               "{}",
-               "()",
-               "[noglob]",
-# if ENABLE_HUSH_FUNCTIONS
-               "func()",
-# endif
-       };
-
-       int pin, prn;
-
-       pin = 0;
-       while (pi) {
-               fprintf(stderr, "%*spipe %d res_word=%s followup=%d %s\n", lvl*2, "",
-                               pin, RES[pi->res_word], pi->followup, PIPE[pi->followup]);
-               prn = 0;
-               while (prn < pi->num_cmds) {
-                       struct command *command = &pi->cmds[prn];
-                       char **argv = command->argv;
-
-                       fprintf(stderr, "%*s cmd %d assignment_cnt:%d",
-                                       lvl*2, "", prn,
-                                       command->assignment_cnt);
-                       if (command->group) {
-                               fprintf(stderr, " group %s: (argv=%p)%s%s\n",
-                                               CMDTYPE[command->cmd_type],
-                                               argv
-# if !BB_MMU
-                                               , " group_as_string:", command->group_as_string
-# else
-                                               , "", ""
-# endif
-                               );
-                               debug_print_tree(command->group, lvl+1);
-                               prn++;
-                               continue;
-                       }
-                       if (argv) while (*argv) {
-                               fprintf(stderr, " '%s'", *argv);
-                               argv++;
-                       }
-                       fprintf(stderr, "\n");
-                       prn++;
-               }
-               pi = pi->next;
-               pin++;
-       }
-}
-#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!) */
 static int run_list(struct pipe *pi)
@@ -6897,7 +7285,7 @@ static int run_list(struct pipe *pi)
        enum { cond_code = 0 };
 #endif
 #if HAS_KEYWORDS
-       smallint rword; /* enum reserved_style */
+       smallint rword;      /* RES_foo */
        smallint last_rword; /* ditto */
 #endif
 
@@ -6906,27 +7294,30 @@ static int run_list(struct pipe *pi)
 
 #if ENABLE_HUSH_LOOPS
        /* Check syntax for "for" */
-       for (struct pipe *cpipe = pi; cpipe; cpipe = cpipe->next) {
-               if (cpipe->res_word != RES_FOR && cpipe->res_word != RES_IN)
-                       continue;
-               /* current word is FOR or IN (BOLD in comments below) */
-               if (cpipe->next == NULL) {
-                       syntax_error("malformed for");
-                       debug_leave();
-                       debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level);
-                       return 1;
-               }
-               /* "FOR v; do ..." and "for v IN a b; do..." are ok */
-               if (cpipe->next->res_word == RES_DO)
-                       continue;
-               /* next word is not "do". It must be "in" then ("FOR v in ...") */
-               if (cpipe->res_word == RES_IN /* "for v IN a b; not_do..."? */
-                || cpipe->next->res_word != RES_IN /* FOR v not_do_and_not_in..."? */
-               ) {
-                       syntax_error("malformed for");
-                       debug_leave();
-                       debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level);
-                       return 1;
+       {
+               struct pipe *cpipe;
+               for (cpipe = pi; cpipe; cpipe = cpipe->next) {
+                       if (cpipe->res_word != RES_FOR && cpipe->res_word != RES_IN)
+                               continue;
+                       /* current word is FOR or IN (BOLD in comments below) */
+                       if (cpipe->next == NULL) {
+                               syntax_error("malformed for");
+                               debug_leave();
+                               debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level);
+                               return 1;
+                       }
+                       /* "FOR v; do ..." and "for v IN a b; do..." are ok */
+                       if (cpipe->next->res_word == RES_DO)
+                               continue;
+                       /* next word is not "do". It must be "in" then ("FOR v in ...") */
+                       if (cpipe->res_word == RES_IN /* "for v IN a b; not_do..."? */
+                        || cpipe->next->res_word != RES_IN /* FOR v not_do_and_not_in..."? */
+                       ) {
+                               syntax_error("malformed for");
+                               debug_leave();
+                               debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level);
+                               return 1;
+                       }
                }
        }
 #endif
@@ -6972,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;
@@ -7054,7 +7445,7 @@ static int run_list(struct pipe *pi)
                        /* all prev words didn't match, does this one match? */
                        argv = pi->cmds->argv;
                        while (*argv) {
-                               char *pattern = expand_string_to_string(*argv);
+                               char *pattern = expand_string_to_string(*argv, /*unbackslash:*/ 1);
                                /* TODO: which FNM_xxx flags to use? */
                                cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0);
                                free(pattern);
@@ -7098,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) {
@@ -7111,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;
@@ -7120,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;
                                }
@@ -7130,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);
@@ -7145,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;
                        }
@@ -7162,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
@@ -7209,7 +7602,7 @@ static int run_and_free_list(struct pipe *pi)
 {
        int rcode = 0;
        debug_printf_exec("run_and_free_list entered\n");
-       if (!G.n_mode) {
+       if (!G.o_opt[OPT_O_NOEXEC]) {
                debug_printf_exec(": run_list: 1st pipe with %d cmds\n", pi->num_cmds);
                rcode = run_list(pi);
        }
@@ -7222,98 +7615,119 @@ 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
 
-static int set_mode(const char cstate, const char mode)
+static int set_mode(int state, char mode, const char *o_opt)
 {
-       int state = (cstate == '-' ? 1 : 0);
+       int idx;
        switch (mode) {
-               case 'n': G.n_mode = state; break;
-               case 'x': IF_HUSH_MODE_X(G_x_mode = state;) break;
-               default:  return EXIT_FAILURE;
+       case 'n':
+               G.o_opt[OPT_O_NOEXEC] = state;
+               break;
+       case 'x':
+               IF_HUSH_MODE_X(G_x_mode = state;)
+               break;
+       case 'o':
+               if (!o_opt) {
+                       /* "set -+o" without parameter.
+                        * in bash, set -o produces this output:
+                        *  pipefail        off
+                        * and set +o:
+                        *  set +o pipefail
+                        * We always use the second form.
+                        */
+                       const char *p = o_opt_strings;
+                       idx = 0;
+                       while (*p) {
+                               printf("set %co %s\n", (G.o_opt[idx] ? '-' : '+'), p);
+                               idx++;
+                               p += strlen(p) + 1;
+                       }
+                       break;
+               }
+               idx = index_in_strings(o_opt_strings, o_opt);
+               if (idx >= 0) {
+                       G.o_opt[idx] = state;
+                       break;
+               }
+       default:
+               return EXIT_FAILURE;
        }
        return EXIT_SUCCESS;
 }
@@ -7321,32 +7735,37 @@ static int set_mode(const char cstate, const char mode)
 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;
        struct variable *cur_var;
+       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
        /* Deal with HUSH_VERSION */
-       G.shell_ver.flg_export = 1;
-       G.shell_ver.flg_read_only = 1;
-       /* Code which handles ${var/P/R} needs writable values for all variables,
+       shell_ver = xzalloc(sizeof(*shell_ver));
+       shell_ver->flg_export = 1;
+       shell_ver->flg_read_only = 1;
+       /* Code which handles ${var<op>...} needs writable values for all variables,
         * therefore we xstrdup: */
-       G.shell_ver.varstr = xstrdup(hush_version_str),
-       G.top_var = &G.shell_ver;
+       shell_ver->varstr = xstrdup(hush_version_str);
+       /* Create shell local variables from the values
+        * currently living in the environment */
        debug_printf_env("unsetenv '%s'\n", "HUSH_VERSION");
        unsetenv("HUSH_VERSION"); /* in case it exists in initial env */
-       /* reinstate HUSH_VERSION in environment */
-       debug_printf_env("putenv '%s'\n", G.shell_ver.varstr);
-       putenv(G.shell_ver.varstr);
-
-       /* Initialize our shell local variables with the values
-        * currently living in the environment */
+       G.top_var = shell_ver;
        cur_var = G.top_var;
        e = environ;
        if (e) while (*e) {
@@ -7360,6 +7779,9 @@ int hush_main(int argc, char **argv)
                }
                e++;
        }
+       /* (Re)insert HUSH_VERSION into env (AFTER we scanned the env!) */
+       debug_printf_env("putenv '%s'\n", shell_ver->varstr);
+       putenv(shell_ver->varstr);
 
        /* Export PWD */
        set_pwd_var(/*exp:*/ 1);
@@ -7403,8 +7825,7 @@ int hush_main(int argc, char **argv)
 #if ENABLE_FEATURE_EDITING
        G.line_input_state = new_line_input_t(FOR_SHELL);
 #endif
-       G.global_argc = argc;
-       G.global_argv = argv;
+
        /* Initialize some more globals to non-zero values */
        cmdedit_update_prompt();
 
@@ -7416,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
@@ -7458,12 +7880,13 @@ 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...] "" */
                                        G.global_argv += builtin_argc;
                                        G.global_argv[-1] = NULL; /* replace "" */
+                                       fflush_all();
                                        G.last_exitcode = x->b_function(argv + optind - 1);
                                }
                                goto final_return;
@@ -7474,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':
@@ -7486,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);
@@ -7506,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++;
@@ -7539,7 +7964,7 @@ int hush_main(int argc, char **argv)
 #endif
                case 'n':
                case 'x':
-                       if (set_mode('-', opt) == 0) /* no error */
+                       if (set_mode(1, opt, NULL) == 0) /* no error */
                                break;
                default:
 #ifndef BB_VER
@@ -7552,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);
                }
@@ -7577,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);
@@ -7598,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,
@@ -7650,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); */
@@ -7665,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 */
@@ -7683,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
@@ -7706,18 +8157,6 @@ int hush_main(int argc, char **argv)
        parse_and_run_file(stdin);
 
  final_return:
-#if ENABLE_FEATURE_CLEAN_UP
-       if (G.cwd != bb_msg_unknown)
-               free((char*)G.cwd);
-       cur_var = G.top_var->next;
-       while (cur_var) {
-               struct variable *tmp = cur_var;
-               if (!cur_var->max_len)
-                       free(cur_var->varstr);
-               cur_var = cur_var->next;
-               free(tmp);
-       }
-#endif
        hush_exit(G.last_exitcode);
 }
 
@@ -7832,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);
@@ -7850,9 +8289,9 @@ 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)
         *
-        * we can use G.exiting = -1 as indicator "last cmd was exit"
+        * TODO: we can use G.exiting = -1 as indicator "last cmd was exit"
         */
 
        /* note: EXIT trap is run by hush_exit */
@@ -7892,13 +8331,16 @@ static void helper_export_local(char **argv, int exp, int lvl)
 {
        do {
                char *name = *argv;
+               char *name_end = strchrnul(name, '=');
 
                /* So far we do not check that name is valid (TODO?) */
 
-               if (strchr(name, '=') == NULL) {
-                       struct variable *var;
+               if (*name_end == '\0') {
+                       struct variable *var, **vpp;
+
+                       vpp = get_ptr_to_local_var(name, name_end - name);
+                       var = vpp ? *vpp : NULL;
 
-                       var = get_local_var(name);
                        if (exp == -1) { /* unexporting? */
                                /* export -n NAME (without =VALUE) */
                                if (var) {
@@ -8026,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;
@@ -8044,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;
        }
 
@@ -8160,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;
 
@@ -8198,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)
 {
@@ -8256,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;
@@ -8263,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.
@@ -8272,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,
@@ -8283,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;
@@ -8326,15 +8802,18 @@ static int FAST_FUNC builtin_set(char **argv)
        }
 
        do {
-               if (!strcmp(arg, "--")) {
+               if (strcmp(arg, "--") == 0) {
                        ++argv;
                        goto set_argv;
                }
                if (arg[0] != '+' && arg[0] != '-')
                        break;
-               for (n = 1; arg[n]; ++n)
-                       if (set_mode(arg[0], arg[n]))
+               for (n = 1; arg[n]; ++n) {
+                       if (set_mode((arg[0] == '-'), arg[n], argv[1]))
                                goto error;
+                       if (arg[n] == 'o' && argv[1])
+                               argv++;
+               }
        } while ((arg = *++argv) != NULL);
        /* Now argv[0] is 1st argument */
 
@@ -8417,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));
@@ -8426,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
@@ -8512,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) {
@@ -8532,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;
        }