hush: do not allow sh -c '{ echo boo }'
[oweals/busybox.git] / shell / ash.c
index 133b2d40e3ba1abe5204f65fddea65dbcca289c5..87f2127a1f491d81da431e2692a2bbe8cb94a0dd 100644 (file)
@@ -32,6 +32,7 @@
 #define DEBUG_TIME 0
 #define DEBUG_PID 1
 #define DEBUG_SIG 1
+#define DEBUG_INTONOFF 0
 
 #define PROFILE 0
 
@@ -295,14 +296,13 @@ struct globals_misc {
 
        volatile int suppress_int; /* counter */
        volatile /*sig_atomic_t*/ smallint pending_int; /* 1 = got SIGINT */
-       /* last pending signal */
-       volatile /*sig_atomic_t*/ smallint pending_sig;
+       volatile /*sig_atomic_t*/ smallint got_sigchld; /* 1 = got SIGCHLD */
+       volatile /*sig_atomic_t*/ smallint pending_sig; /* last pending signal */
        smallint exception_type; /* kind of exception (0..5) */
        /* exceptions */
 #define EXINT 0         /* SIGINT received */
 #define EXERROR 1       /* a generic error */
 #define EXEXIT 4        /* exit the shell */
-#define EXSIG 5         /* trapped signal in wait(1) */
 
        smallint isloginsh;
        char nullstr[1];        /* zero length string */
@@ -371,6 +371,7 @@ extern struct globals_misc *const ash_ptr_to_globals_misc;
 #define exception_type    (G_misc.exception_type   )
 #define suppress_int      (G_misc.suppress_int     )
 #define pending_int       (G_misc.pending_int      )
+#define got_sigchld       (G_misc.got_sigchld      )
 #define pending_sig       (G_misc.pending_sig      )
 #define isloginsh   (G_misc.isloginsh  )
 #define nullstr     (G_misc.nullstr    )
@@ -442,10 +443,18 @@ static void exitshell(void) NORETURN;
  * much more efficient and portable.  (But hacking the kernel is so much
  * more fun than worrying about efficiency and portability. :-))
  */
-#define INT_OFF do { \
+#if DEBUG_INTONOFF
+# define INT_OFF do { \
+       TRACE(("%s:%d INT_OFF(%d)\n", __func__, __LINE__, suppress_int)); \
        suppress_int++; \
        barrier(); \
 } while (0)
+#else
+# define INT_OFF do { \
+       suppress_int++; \
+       barrier(); \
+} while (0)
+#endif
 
 /*
  * Called to raise an exception.  Since C doesn't include exceptions, we
@@ -482,26 +491,20 @@ static void raise_interrupt(void) NORETURN;
 static void
 raise_interrupt(void)
 {
-       int ex_type;
-
        pending_int = 0;
        /* Signal is not automatically unmasked after it is raised,
         * do it ourself - unmask all signals */
        sigprocmask_allsigs(SIG_UNBLOCK);
        /* pending_sig = 0; - now done in signal_handler() */
 
-       ex_type = EXSIG;
-       if (gotsig[SIGINT - 1] && !trap[SIGINT]) {
-               if (!(rootshell && iflag)) {
-                       /* Kill ourself with SIGINT */
-                       signal(SIGINT, SIG_DFL);
-                       raise(SIGINT);
-               }
-               ex_type = EXINT;
+       if (!(rootshell && iflag)) {
+               /* Kill ourself with SIGINT */
+               signal(SIGINT, SIG_DFL);
+               raise(SIGINT);
        }
        /* bash: ^C even on empty command line sets $? */
        exitstatus = SIGINT + 128;
-       raise_exception(ex_type);
+       raise_exception(EXINT);
        /* NOTREACHED */
 }
 #if DEBUG
@@ -519,7 +522,14 @@ int_on(void)
                raise_interrupt();
        }
 }
-#define INT_ON int_on()
+#if DEBUG_INTONOFF
+# define INT_ON do { \
+       TRACE(("%s:%d INT_ON(%d)\n", __func__, __LINE__, suppress_int-1)); \
+       int_on(); \
+} while (0)
+#else
+# define INT_ON int_on()
+#endif
 static IF_ASH_OPTIMIZE_FOR_SIZE(inline) void
 force_int_on(void)
 {
@@ -850,13 +860,8 @@ trace_vprintf(const char *fmt, va_list va)
 {
        if (debug != 1)
                return;
-       if (DEBUG_TIME)
-               fprintf(tracefile, "%u ", (int) time(NULL));
-       if (DEBUG_PID)
-               fprintf(tracefile, "[%u] ", (int) getpid());
-       if (DEBUG_SIG)
-               fprintf(tracefile, "pending s:%d i:%d(supp:%d) ", pending_sig, pending_int, suppress_int);
        vfprintf(tracefile, fmt, va);
+       fprintf(tracefile, "\n");
 }
 
 static void
@@ -1249,11 +1254,10 @@ ash_vmsg_and_raise(int cond, const char *msg, va_list ap)
 {
 #if DEBUG
        if (msg) {
-               TRACE(("ash_vmsg_and_raise(%d, \"", cond));
+               TRACE(("ash_vmsg_and_raise(%d):", cond));
                TRACEV((msg, ap));
-               TRACE(("\") pid=%d\n", getpid()));
        } else
-               TRACE(("ash_vmsg_and_raise(%d, NULL) pid=%d\n", cond, getpid()));
+               TRACE(("ash_vmsg_and_raise(%d):NULL\n", cond));
        if (msg)
 #endif
                ash_vmsg(msg, ap);
@@ -2185,6 +2189,7 @@ setvareq(char *s, int flags)
                        if (flags & VNOSAVE)
                                free(s);
                        n = vp->var_text;
+                       exitstatus = 1;
                        ash_msg_and_raise_error("%.*s: is read only", strchrnul(n, '=') - n, n);
                }
 
@@ -2590,7 +2595,7 @@ setpwd(const char *val, int setold)
 static void hashcd(void);
 
 /*
- * Actually do the chdir.  We also call hashcd to let the routines in exec.c
+ * Actually do the chdir.  We also call hashcd to let other routines
  * know that the current directory has changed.
  */
 static int
@@ -3395,7 +3400,14 @@ ignoresig(int signo)
 static void
 signal_handler(int signo)
 {
+       if (signo == SIGCHLD) {
+               got_sigchld = 1;
+               if (!trap[SIGCHLD])
+                       return;
+       }
+
        gotsig[signo - 1] = 1;
+       pending_sig = signo;
 
        if (signo == SIGINT && !trap[SIGINT]) {
                if (!suppress_int) {
@@ -3403,8 +3415,6 @@ signal_handler(int signo)
                        raise_interrupt(); /* does not return */
                }
                pending_int = 1;
-       } else {
-               pending_sig = signo;
        }
 }
 
@@ -3462,6 +3472,9 @@ setsignal(int signo)
 //whereas we have to restore it to what shell got on entry
 //from the parent. See comment above
 
+       if (signo == SIGCHLD)
+               new_act = S_CATCH;
+
        t = &sigmode[signo - 1];
        cur_act = *t;
        if (cur_act == 0) {
@@ -3511,10 +3524,6 @@ setsignal(int signo)
 #define CUR_RUNNING 1
 #define CUR_STOPPED 0
 
-/* mode flags for dowait */
-#define DOWAIT_NONBLOCK WNOHANG
-#define DOWAIT_BLOCK    0
-
 #if JOBS
 /* pgrp of shell on invocation */
 static int initialpgrp; //references:2
@@ -3935,27 +3944,88 @@ sprint_status48(char *s, int status, int sigonly)
 }
 
 static int
-dowait(int wait_flags, struct job *job)
+wait_block_or_sig(int *status)
+{
+       int pid;
+
+       do {
+               sigset_t mask;
+
+               /* Poll all children for changes in their state */
+               got_sigchld = 0;
+               /* if job control is active, accept stopped processes too */
+               pid = waitpid(-1, status, doing_jobctl ? (WNOHANG|WUNTRACED) : WNOHANG);
+               if (pid != 0)
+                       break; /* Error (e.g. EINTR, ECHILD) or pid */
+
+               /* Children exist, but none are ready. Sleep until interesting signal */
+#if 1
+               sigfillset(&mask);
+               sigprocmask(SIG_SETMASK, &mask, &mask);
+               while (!got_sigchld && !pending_sig)
+                       sigsuspend(&mask);
+               sigprocmask(SIG_SETMASK, &mask, NULL);
+#else /* unsafe: a signal can set pending_sig after check, but before pause() */
+               while (!got_sigchld && !pending_sig)
+                       pause();
+#endif
+
+               /* If it was SIGCHLD, poll children again */
+       } while (got_sigchld);
+
+       return pid;
+}
+
+#define DOWAIT_NONBLOCK 0
+#define DOWAIT_BLOCK    1
+#define DOWAIT_BLOCK_OR_SIG 2
+
+static int
+dowait(int block, struct job *job)
 {
        int pid;
        int status;
        struct job *jp;
-       struct job *thisjob;
+       struct job *thisjob = NULL;
 
-       TRACE(("dowait(0x%x) called\n", wait_flags));
+       TRACE(("dowait(0x%x) called\n", block));
 
-       /* Do a wait system call. If job control is compiled in, we accept
-        * stopped processes. wait_flags may have WNOHANG, preventing blocking.
-        * NB: _not_ safe_waitpid, we need to detect EINTR */
-       if (doing_jobctl)
-               wait_flags |= WUNTRACED;
-       pid = waitpid(-1, &status, wait_flags);
+       /* It's wrong to call waitpid() outside of INT_OFF region:
+        * signal can arrive just after syscall return and handler can
+        * longjmp away, losing stop/exit notification processing.
+        * Thus, for "jobs" builtin, and for waiting for a fg job,
+        * we call waitpid() (blocking or non-blocking) inside INT_OFF.
+        *
+        * However, for "wait" builtin it is wrong to simply call waitpid()
+        * in INT_OFF region: "wait" needs to wait for any running job
+        * to change state, but should exit on any trap too.
+        * In INT_OFF region, a signal just before syscall entry can set
+        * pending_sig variables, but we can't check them, and we would
+        * either enter a sleeping waitpid() (BUG), or need to busy-loop.
+        *
+        * Because of this, we run inside INT_OFF, but use a special routine
+        * which combines waitpid() and sigsuspend().
+        * This is the reason why we need to have a handler for SIGCHLD:
+        * SIG_DFL handler does not wake sigsuspend().
+        */
+       INT_OFF;
+       if (block == DOWAIT_BLOCK_OR_SIG) {
+               pid = wait_block_or_sig(&status);
+       } else {
+               int wait_flags = 0;
+               if (block == DOWAIT_NONBLOCK)
+                       wait_flags = WNOHANG;
+               /* if job control is active, accept stopped processes too */
+               if (doing_jobctl)
+                       wait_flags |= WUNTRACED;
+               /* NB: _not_ safe_waitpid, we need to detect EINTR */
+               pid = waitpid(-1, &status, wait_flags);
+       }
        TRACE(("wait returns pid=%d, status=0x%x, errno=%d(%s)\n",
                                pid, status, errno, strerror(errno)));
        if (pid <= 0)
-               return pid;
+               goto out;
 
-       INT_OFF;
        thisjob = NULL;
        for (jp = curjob; jp; jp = jp->prev_job) {
                int jobstate;
@@ -4027,15 +4097,6 @@ dowait(int wait_flags, struct job *job)
        return pid;
 }
 
-static int
-blocking_wait_with_raise_on_sig(void)
-{
-       pid_t pid = dowait(DOWAIT_BLOCK, NULL);
-       if (pid <= 0 && pending_sig)
-               raise_exception(EXSIG);
-       return pid;
-}
-
 #if JOBS
 static void
 showjob(struct job *jp, int mode)
@@ -4205,9 +4266,6 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
        int retval;
        struct job *jp;
 
-       if (pending_sig)
-               raise_exception(EXSIG);
-
        nextopt(nullstr);
        retval = 0;
 
@@ -4224,21 +4282,20 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
                                jp->waited = 1;
                                jp = jp->prev_job;
                        }
-                       blocking_wait_with_raise_on_sig();
        /* man bash:
         * "When bash is waiting for an asynchronous command via
         * the wait builtin, the reception of a signal for which a trap
         * has been set will cause the wait builtin to return immediately
         * with an exit status greater than 128, immediately after which
         * the trap is executed."
-        *
-        * blocking_wait_with_raise_on_sig raises signal handlers
-        * if it gets no pid (pid < 0). However,
-        * if child sends us a signal *and immediately exits*,
-        * blocking_wait_with_raise_on_sig gets pid > 0
-        * and does not handle pending_sig. Check this case: */
+        */
+                       dowait(DOWAIT_BLOCK_OR_SIG, NULL);
+       /* if child sends us a signal *and immediately exits*,
+        * dowait() returns pid > 0. Check this case,
+        * not "if (dowait() < 0)"!
+        */
                        if (pending_sig)
-                               raise_exception(EXSIG);
+                               goto sigout;
                }
        }
 
@@ -4258,8 +4315,11 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
                        job = getjob(*argv, 0);
                }
                /* loop until process terminated or stopped */
-               while (job->state == JOBRUNNING)
-                       blocking_wait_with_raise_on_sig();
+               while (job->state == JOBRUNNING) {
+                       dowait(DOWAIT_BLOCK_OR_SIG, NULL);
+                       if (pending_sig)
+                               goto sigout;
+               }
                job->waited = 1;
                retval = getstatus(job);
  repeat: ;
@@ -4267,6 +4327,9 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
 
  ret:
        return retval;
+ sigout:
+       retval = 128 + pending_sig;
+       return retval;
 }
 
 static struct job *
@@ -4660,25 +4723,26 @@ clear_traps(void)
 {
        char **tp;
 
+       INT_OFF;
        for (tp = trap; tp < &trap[NSIG]; tp++) {
                if (*tp && **tp) {      /* trap not NULL or "" (SIG_IGN) */
-                       INT_OFF;
                        if (trap_ptr == trap)
                                free(*tp);
                        /* else: it "belongs" to trap_ptr vector, don't free */
                        *tp = NULL;
                        if ((tp - trap) != 0)
                                setsignal(tp - trap);
-                       INT_ON;
                }
        }
        may_have_traps = 0;
+       INT_ON;
 }
 
 /* Lives far away from here, needed for forkchild */
 static void closescript(void);
 
 /* Called after fork(), in child */
+/* jp and n are NULL when called by openhere() for heredoc support */
 static NOINLINE void
 forkchild(struct job *jp, union node *n, int mode)
 {
@@ -4810,6 +4874,7 @@ forkparent(struct job *jp, union node *n, int mode, pid_t pid)
 {
        TRACE(("In parent shell: child = %d\n", pid));
        if (!jp) {
+               /* jp is NULL when called by openhere() for heredoc support */
                while (jobless && dowait(DOWAIT_NONBLOCK, NULL) > 0)
                        continue;
                jobless++;
@@ -4843,6 +4908,7 @@ forkparent(struct job *jp, union node *n, int mode, pid_t pid)
        }
 }
 
+/* jp and n are NULL when called by openhere() for heredoc support */
 static int
 forkshell(struct job *jp, union node *n, int mode)
 {
@@ -4972,8 +5038,7 @@ stoppedjobs(void)
 }
 
 
-/* ============ redir.c
- *
+/*
  * Code for dealing with input/output redirection.
  */
 
@@ -5566,6 +5631,119 @@ cvtnum(arith_t num)
        return len;
 }
 
+/*
+ * Break the argument string into pieces based upon IFS and add the
+ * strings to the argument list.  The regions of the string to be
+ * searched for IFS characters have been stored by recordregion.
+ */
+static void
+ifsbreakup(char *string, struct arglist *arglist)
+{
+       struct ifsregion *ifsp;
+       struct strlist *sp;
+       char *start;
+       char *p;
+       char *q;
+       const char *ifs, *realifs;
+       int ifsspc;
+       int nulonly;
+
+       start = string;
+       if (ifslastp != NULL) {
+               ifsspc = 0;
+               nulonly = 0;
+               realifs = ifsset() ? ifsval() : defifs;
+               ifsp = &ifsfirst;
+               do {
+                       p = string + ifsp->begoff;
+                       nulonly = ifsp->nulonly;
+                       ifs = nulonly ? nullstr : realifs;
+                       ifsspc = 0;
+                       while (p < string + ifsp->endoff) {
+                               q = p;
+                               if ((unsigned char)*p == CTLESC)
+                                       p++;
+                               if (!strchr(ifs, *p)) {
+                                       p++;
+                                       continue;
+                               }
+                               if (!nulonly)
+                                       ifsspc = (strchr(defifs, *p) != NULL);
+                               /* Ignore IFS whitespace at start */
+                               if (q == start && ifsspc) {
+                                       p++;
+                                       start = p;
+                                       continue;
+                               }
+                               *q = '\0';
+                               sp = stzalloc(sizeof(*sp));
+                               sp->text = start;
+                               *arglist->lastp = sp;
+                               arglist->lastp = &sp->next;
+                               p++;
+                               if (!nulonly) {
+                                       for (;;) {
+                                               if (p >= string + ifsp->endoff) {
+                                                       break;
+                                               }
+                                               q = p;
+                                               if ((unsigned char)*p == CTLESC)
+                                                       p++;
+                                               if (strchr(ifs, *p) == NULL) {
+                                                       p = q;
+                                                       break;
+                                               }
+                                               if (strchr(defifs, *p) == NULL) {
+                                                       if (ifsspc) {
+                                                               p++;
+                                                               ifsspc = 0;
+                                                       } else {
+                                                               p = q;
+                                                               break;
+                                                       }
+                                               } else
+                                                       p++;
+                                       }
+                               }
+                               start = p;
+                       } /* while */
+                       ifsp = ifsp->next;
+               } while (ifsp != NULL);
+               if (nulonly)
+                       goto add;
+       }
+
+       if (!*start)
+               return;
+
+ add:
+       sp = stzalloc(sizeof(*sp));
+       sp->text = start;
+       *arglist->lastp = sp;
+       arglist->lastp = &sp->next;
+}
+
+static void
+ifsfree(void)
+{
+       struct ifsregion *p = ifsfirst.next;
+
+       if (!p)
+               goto out;
+
+       INT_OFF;
+       do {
+               struct ifsregion *ifsp;
+               ifsp = p->next;
+               free(p);
+               p = ifsp;
+       } while (p);
+       ifsfirst.next = NULL;
+       INT_ON;
+ out:
+       ifslastp = NULL;
+}
+
 static size_t
 esclen(const char *start, const char *p)
 {
@@ -5860,6 +6038,7 @@ evalbackcmd(union node *n, struct backcmd *result)
                ash_msg_and_raise_error("pipe call failed");
        jp = makejob(/*n,*/ 1);
        if (forkshell(jp, n, FORK_NOJOB) == 0) {
+               /* child */
                FORCE_INT_ON;
                close(pip[0]);
                if (pip[1] != 1) {
@@ -5876,9 +6055,11 @@ evalbackcmd(union node *n, struct backcmd *result)
  * For now, preserve bash-like behavior, it seems to be somewhat more useful:
  */
                eflag = 0;
+               ifsfree();
                evaltree(n, EV_EXIT); /* actually evaltreenr... */
                /* NOTREACHED */
        }
+       /* parent */
        close(pip[1]);
        result->fd = pip[0];
        result->jp = jp;
@@ -6844,116 +7025,6 @@ evalvar(char *p, int flag, struct strlist *var_str_list)
        return p;
 }
 
-/*
- * Break the argument string into pieces based upon IFS and add the
- * strings to the argument list.  The regions of the string to be
- * searched for IFS characters have been stored by recordregion.
- */
-static void
-ifsbreakup(char *string, struct arglist *arglist)
-{
-       struct ifsregion *ifsp;
-       struct strlist *sp;
-       char *start;
-       char *p;
-       char *q;
-       const char *ifs, *realifs;
-       int ifsspc;
-       int nulonly;
-
-       start = string;
-       if (ifslastp != NULL) {
-               ifsspc = 0;
-               nulonly = 0;
-               realifs = ifsset() ? ifsval() : defifs;
-               ifsp = &ifsfirst;
-               do {
-                       p = string + ifsp->begoff;
-                       nulonly = ifsp->nulonly;
-                       ifs = nulonly ? nullstr : realifs;
-                       ifsspc = 0;
-                       while (p < string + ifsp->endoff) {
-                               q = p;
-                               if ((unsigned char)*p == CTLESC)
-                                       p++;
-                               if (!strchr(ifs, *p)) {
-                                       p++;
-                                       continue;
-                               }
-                               if (!nulonly)
-                                       ifsspc = (strchr(defifs, *p) != NULL);
-                               /* Ignore IFS whitespace at start */
-                               if (q == start && ifsspc) {
-                                       p++;
-                                       start = p;
-                                       continue;
-                               }
-                               *q = '\0';
-                               sp = stzalloc(sizeof(*sp));
-                               sp->text = start;
-                               *arglist->lastp = sp;
-                               arglist->lastp = &sp->next;
-                               p++;
-                               if (!nulonly) {
-                                       for (;;) {
-                                               if (p >= string + ifsp->endoff) {
-                                                       break;
-                                               }
-                                               q = p;
-                                               if ((unsigned char)*p == CTLESC)
-                                                       p++;
-                                               if (strchr(ifs, *p) == NULL) {
-                                                       p = q;
-                                                       break;
-                                               }
-                                               if (strchr(defifs, *p) == NULL) {
-                                                       if (ifsspc) {
-                                                               p++;
-                                                               ifsspc = 0;
-                                                       } else {
-                                                               p = q;
-                                                               break;
-                                                       }
-                                               } else
-                                                       p++;
-                                       }
-                               }
-                               start = p;
-                       } /* while */
-                       ifsp = ifsp->next;
-               } while (ifsp != NULL);
-               if (nulonly)
-                       goto add;
-       }
-
-       if (!*start)
-               return;
-
- add:
-       sp = stzalloc(sizeof(*sp));
-       sp->text = start;
-       *arglist->lastp = sp;
-       arglist->lastp = &sp->next;
-}
-
-static void
-ifsfree(void)
-{
-       struct ifsregion *p;
-
-       INT_OFF;
-       p = ifsfirst.next;
-       do {
-               struct ifsregion *ifsp;
-               ifsp = p->next;
-               free(p);
-               p = ifsp;
-       } while (p);
-       ifslastp = NULL;
-       ifsfirst.next = NULL;
-       INT_ON;
-}
-
 /*
  * Add a file name to the list.
  */
@@ -6993,6 +7064,21 @@ expandmeta(struct strlist *str /*, int flag*/)
 
                if (fflag)
                        goto nometa;
+
+               /* Avoid glob() (and thus, stat() et al) for words like "echo" */
+               p = str->text;
+               while (*p) {
+                       if (*p == '*')
+                               goto need_glob;
+                       if (*p == '?')
+                               goto need_glob;
+                       if (*p == '[')
+                               goto need_glob;
+                       p++;
+               }
+               goto nometa;
+
+ need_glob:
                INT_OFF;
                p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP);
 // GLOB_NOMAGIC (GNU): if no *?[ chars in pattern, return it even if no match
@@ -7292,15 +7378,14 @@ expandarg(union node *arg, struct arglist *arglist, int flag)
 
        argbackq = arg->narg.backquote;
        STARTSTACKSTR(expdest);
-       ifsfirst.next = NULL;
-       ifslastp = NULL;
        TRACE(("expandarg: argstr('%s',flags:%x)\n", arg->narg.text, flag));
        argstr(arg->narg.text, flag,
                        /* var_str_list: */ arglist ? arglist->list : NULL);
        p = _STPUTC('\0', expdest);
        expdest = p - 1;
        if (arglist == NULL) {
-               return;                 /* here document expanded */
+               /* here document expanded */
+               goto out;
        }
        p = grabstackstr(p);
        TRACE(("expandarg: p:'%s'\n", p));
@@ -7323,13 +7408,14 @@ expandarg(union node *arg, struct arglist *arglist, int flag)
                *exparg.lastp = sp;
                exparg.lastp = &sp->next;
        }
-       if (ifsfirst.next)
-               ifsfree();
        *exparg.lastp = NULL;
        if (exparg.list) {
                *arglist->lastp = exparg.list;
                arglist->lastp = exparg.lastp;
        }
+
+ out:
+       ifsfree();
 }
 
 /*
@@ -7363,10 +7449,10 @@ casematch(union node *pattern, char *val)
        setstackmark(&smark);
        argbackq = pattern->narg.backquote;
        STARTSTACKSTR(expdest);
-       ifslastp = NULL;
        argstr(pattern->narg.text, EXP_TILDE | EXP_CASE,
                        /* var_str_list: */ NULL);
        STACKSTRNUL(expdest);
+       ifsfree();
        result = patmatch(stackblock(), val);
        popstackmark(&smark);
        return result;
@@ -8473,41 +8559,18 @@ static void prehash(union node *);
 static int
 evaltree(union node *n, int flags)
 {
-       struct jmploc *volatile savehandler = exception_handler;
-       struct jmploc jmploc;
        int checkexit = 0;
        int (*evalfn)(union node *, int);
        int status = 0;
-       int int_level;
-
-       SAVE_INT(int_level);
 
        if (n == NULL) {
                TRACE(("evaltree(NULL) called\n"));
-               goto out1;
+               goto out;
        }
        TRACE(("evaltree(%p: %d, %d) called\n", n, n->type, flags));
 
        dotrap();
 
-       exception_handler = &jmploc;
-       {
-               int err = setjmp(jmploc.loc);
-               if (err) {
-                       /* if it was a signal, check for trap handlers */
-                       if (exception_type == EXSIG) {
-                               TRACE(("exception %d (EXSIG) in evaltree, err=%d\n",
-                                               exception_type, err));
-                               goto out;
-                       }
-                       /* continue on the way out */
-                       TRACE(("exception %d in evaltree, propagating err=%d\n",
-                                       exception_type, err));
-                       exception_handler = savehandler;
-                       longjmp(exception_handler->loc, err);
-               }
-       }
-
        switch (n->type) {
        default:
 #if DEBUG
@@ -8598,11 +8661,7 @@ evaltree(union node *n, int flags)
                exitstatus = status;
                break;
        }
-
  out:
-       exception_handler = savehandler;
-
- out1:
        /* Order of checks below is important:
         * signal handlers trigger before exit caused by "set -e".
         */
@@ -8613,9 +8672,7 @@ evaltree(union node *n, int flags)
        if (flags & EV_EXIT)
                raise_exception(EXEXIT);
 
-       RESTORE_INT(int_level);
        TRACE(("leaving evaltree (no interrupts)\n"));
-
        return exitstatus;
 }
 
@@ -8764,6 +8821,7 @@ evalsubshell(union node *n, int flags)
                evaltreenr(n->nredir.n, flags);
                /* never returns */
        }
+       /* parent */
        status = 0;
        if (!backgnd)
                status = waitforjob(jp);
@@ -8869,6 +8927,7 @@ evalpipe(union node *n, int flags)
                        }
                }
                if (forkshell(jp, lp->n, n->npipe.pipe_backgnd) == 0) {
+                       /* child */
                        INT_ON;
                        if (pip[1] >= 0) {
                                close(pip[0]);
@@ -8884,6 +8943,7 @@ evalpipe(union node *n, int flags)
                        evaltreenr(lp->n, flags);
                        /* never returns */
                }
+               /* parent */
                if (prevfd >= 0)
                        close(prevfd);
                prevfd = pip[0];
@@ -9057,7 +9117,7 @@ mklocal(char *name)
                        /* else:
                         * it's a duplicate "local VAR" declaration, do nothing
                         */
-                       return;
+                       goto ret;
                }
                lvp = lvp->next;
        }
@@ -9096,6 +9156,7 @@ mklocal(char *name)
        lvp->vp = vp;
        lvp->next = localvars;
        localvars = lvp;
+ ret:
        INT_ON;
 }
 
@@ -9569,21 +9630,12 @@ evalcommand(union node *cmd, int flags)
                dowait(DOWAIT_NONBLOCK, NULL);
 
                if (evalbltin(cmdentry.u.cmd, argc, argv, flags)) {
-                       int exit_status;
-                       int i = exception_type;
-                       if (i == EXEXIT)
-                               goto raise;
-                       exit_status = 2;
-                       if (i == EXINT)
-                               exit_status = 128 + SIGINT;
-                       if (i == EXSIG)
-                               exit_status = 128 + pending_sig;
-                       exitstatus = exit_status;
-                       if (i == EXINT || spclbltin > 0) {
- raise:
-                               longjmp(exception_handler->loc, 1);
+                       if (exception_type == EXERROR && spclbltin <= 0) {
+                               FORCE_INT_ON;
+                               goto readstatus;
                        }
-                       FORCE_INT_ON;
+ raise:
+                       longjmp(exception_handler->loc, 1);
                }
                goto readstatus;
 
@@ -9702,8 +9754,7 @@ breakcmd(int argc UNUSED_PARAM, char **argv)
 }
 
 
-/* ============ input.c
- *
+/*
  * This implements the input routines used by the parser.
  */
 
@@ -10101,6 +10152,9 @@ popfile(void)
 {
        struct parsefile *pf = g_parsefile;
 
+       if (pf == &basepf)
+               return;
+
        INT_OFF;
        if (pf->pf_fd >= 0)
                close(pf->pf_fd);
@@ -10198,8 +10252,7 @@ setinputstring(char *string)
 }
 
 
-/* ============ mail.c
- *
+/*
  * Routines to check for mail.
  */
 
@@ -12263,6 +12316,10 @@ expandstr(const char *ps)
 static int
 evalstring(char *s, int flags)
 {
+       struct jmploc *volatile savehandler;
+       struct jmploc jmploc;
+       int ex;
+
        union node *n;
        struct stackmark smark;
        int status;
@@ -12272,6 +12329,19 @@ evalstring(char *s, int flags)
        setstackmark(&smark);
 
        status = 0;
+       /* On exception inside execution loop, we must popfile().
+        * Try interactively:
+        *      readonly a=a
+        *      command eval "a=b"  # throws "is read only" error
+        * "command BLTIN" is not supposed to abort (even in non-interactive use).
+        * But if we skip popfile(), we hit EOF in eval's string, and exit.
+        */
+       savehandler = exception_handler;
+       ex = setjmp(jmploc.loc);
+       if (ex)
+               goto out;
+       exception_handler = &jmploc;
+
        while ((n = parsecmd(0)) != NODE_EOF) {
                int i;
 
@@ -12282,10 +12352,15 @@ evalstring(char *s, int flags)
                if (evalskip)
                        break;
        }
+ out:
        popstackmark(&smark);
        popfile();
        stunalloc(s);
 
+       exception_handler = savehandler;
+       if (ex)
+                longjmp(exception_handler->loc, ex);
+
        return status;
 }
 
@@ -12748,12 +12823,13 @@ trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
                if (action) {
                        if (LONE_DASH(action))
                                action = NULL;
-                       else
+                       else {
+                               if (action[0]) /* not NULL and not "" and not "-" */
+                                       may_have_traps = 1;
                                action = ckstrdup(action);
+                       }
                }
                free(trap[signo]);
-               if (action)
-                       may_have_traps = 1;
                trap[signo] = action;
                if (signo != 0)
                        setsignal(signo);
@@ -13150,7 +13226,9 @@ init(void)
        /* we will never free this */
        basepf.next_to_pgetc = basepf.buf = ckmalloc(IBUFSIZ);
 
-       signal(SIGCHLD, SIG_DFL);
+       sigmode[SIGCHLD - 1] = S_DFL;
+       setsignal(SIGCHLD);
+
        /* bash re-enables SIGHUP which is SIG_IGNed on entry.
         * Try: "trap '' HUP; bash; echo RET" and type "kill -HUP $$"
         */
@@ -13272,11 +13350,12 @@ procargs(char **argv)
 }
 
 /*
- * Read /etc/profile or .profile.
+ * Read /etc/profile, ~/.profile, $ENV.
  */
 static void
 read_profile(const char *name)
 {
+       name = expandstr(name);
        if (setinputfile(name, INPUT_PUSH_FILE | INPUT_NOFILE_OK) < 0)
                return;
        cmdloop(0);
@@ -13286,6 +13365,7 @@ read_profile(const char *name)
 /*
  * This routine is called when an error or an interrupt occurs in an
  * interactive shell and control is returned to the main command loop.
+ * (In dash, this function is auto-generated by build machinery).
  */
 static void
 reset(void)
@@ -13293,13 +13373,15 @@ reset(void)
        /* from eval.c: */
        evalskip = 0;
        loopnest = 0;
+
+       /* from expand.c: */
+       ifsfree();
+
        /* from input.c: */
        g_parsefile->left_in_buffer = 0;
        g_parsefile->left_in_line = 0;      /* clear input buffer */
        popallfiles();
-       /* from parser.c: */
-       tokpushback = 0;
-       checkkwd = 0;
+
        /* from redir.c: */
        while (redirlist)
                popredir(/*drop:*/ 0, /*restore:*/ 0);
@@ -13320,7 +13402,6 @@ extern int etext();
 int ash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int ash_main(int argc UNUSED_PARAM, char **argv)
 {
-       const char *shinit;
        volatile smallint state;
        struct jmploc jmploc;
        struct stackmark smark;
@@ -13368,16 +13449,15 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
                goto state4;
        }
        exception_handler = &jmploc;
-#if DEBUG
-       opentrace();
-       TRACE(("Shell args: "));
-       trace_puts_args(argv);
-#endif
        rootpid = getpid();
 
        init();
        setstackmark(&smark);
        procargs(argv);
+#if DEBUG
+       TRACE(("Shell args: "));
+       trace_puts_args(argv);
+#endif
 
        if (argv[0] && argv[0][0] == '-')
                isloginsh = 1;
@@ -13389,11 +13469,8 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
  state1:
                state = 2;
                hp = lookupvar("HOME");
-               if (hp) {
-                       hp = concat_path_file(hp, ".profile");
-                       read_profile(hp);
-                       free((char*)hp);
-               }
+               if (hp)
+                       read_profile("$HOME/.profile");
        }
  state2:
        state = 3;
@@ -13403,11 +13480,11 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
 #endif
         iflag
        ) {
-               shinit = lookupvar("ENV");
-               if (shinit != NULL && *shinit != '\0') {
+               const char *shinit = lookupvar("ENV");
+               if (shinit != NULL && *shinit != '\0')
                        read_profile(shinit);
-               }
        }
+       popstackmark(&smark);
  state3:
        state = 4;
        if (minusc) {